From 1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Sun, 20 Nov 2016 15:16:41 -0500 Subject: [PATCH] Port of Simon Tatham's Puzzle Collection Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9 MIT Licensed. Some games still crash and others are unplayable due to issues with controls. Still need a "real" polygon filling algorithm. Currently builds one plugin per puzzle (about 40 in total, around 100K each on ARM), but can easily be made to build a single monolithic overlay (800K or so on ARM). The following games are at least partially broken for various reasons, and have been disabled on this commit: Cube: failed assertion with "Icosahedron" setting Keen: input issues Mines: weird stuff happens on target Palisade: input issues Solo: input issues, occasional crash on target Towers: input issues Undead: input issues Unequal: input and drawing issues (concave polys) Untangle: input issues Features left to do: - In-game help system - Figure out the weird bugs Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368 --- apps/plugins/CATEGORIES | 40 + apps/plugins/SOURCES | 4 + apps/plugins/SUBDIRS | 1 + apps/plugins/puzzles/Buildscr | 194 + apps/plugins/puzzles/CHECKLST.txt | 70 + apps/plugins/puzzles/LICENCE | 25 + apps/plugins/puzzles/PuzzleApplet.java | 617 ++ apps/plugins/puzzles/README | 54 + apps/plugins/puzzles/Recipe | 157 + apps/plugins/puzzles/SOURCES | 26 + apps/plugins/puzzles/benchmark.pl | 197 + apps/plugins/puzzles/benchmark.sh | 27 + apps/plugins/puzzles/blackbox.R | 19 + apps/plugins/puzzles/blackbox.c | 1543 +++++ apps/plugins/puzzles/bridges.R | 21 + apps/plugins/puzzles/bridges.c | 3262 ++++++++++ apps/plugins/puzzles/chm.but | 21 + apps/plugins/puzzles/chm.css | 7 + apps/plugins/puzzles/combi.c | 110 + apps/plugins/puzzles/configure.ac | 85 + apps/plugins/puzzles/cube.R | 19 + apps/plugins/puzzles/cube.c | 1774 ++++++ apps/plugins/puzzles/desktop.pl | 52 + apps/plugins/puzzles/devel.but | 4777 ++++++++++++++ apps/plugins/puzzles/divvy.c | 781 +++ apps/plugins/puzzles/dominosa.R | 21 + apps/plugins/puzzles/dominosa.c | 1748 +++++ apps/plugins/puzzles/drawing.c | 351 + apps/plugins/puzzles/dsf.c | 192 + apps/plugins/puzzles/emcc.c | 867 +++ apps/plugins/puzzles/emcclib.js | 757 +++ apps/plugins/puzzles/emccpre.js | 364 ++ apps/plugins/puzzles/emccx.json | 29 + apps/plugins/puzzles/fifteen.R | 22 + apps/plugins/puzzles/fifteen.c | 1215 ++++ apps/plugins/puzzles/filling.R | 24 + apps/plugins/puzzles/filling.c | 2179 +++++++ apps/plugins/puzzles/findloop.c | 500 ++ apps/plugins/puzzles/flip.R | 21 + apps/plugins/puzzles/flip.c | 1349 ++++ apps/plugins/puzzles/flood.R | 19 + apps/plugins/puzzles/flood.c | 1372 ++++ apps/plugins/puzzles/galaxies.R | 28 + apps/plugins/puzzles/galaxies.c | 3995 ++++++++++++ apps/plugins/puzzles/grid.c | 2896 +++++++++ apps/plugins/puzzles/grid.h | 132 + apps/plugins/puzzles/gtk.c | 3215 ++++++++++ apps/plugins/puzzles/guess.R | 19 + apps/plugins/puzzles/guess.c | 1518 +++++ apps/plugins/puzzles/html/blackbox.html | 16 + apps/plugins/puzzles/html/bridges.html | 13 + apps/plugins/puzzles/html/cube.html | 14 + apps/plugins/puzzles/html/dominosa.html | 10 + apps/plugins/puzzles/html/fifteen.html | 6 + apps/plugins/puzzles/html/filling.html | 12 + apps/plugins/puzzles/html/flip.html | 10 + apps/plugins/puzzles/html/flood.html | 8 + apps/plugins/puzzles/html/galaxies.html | 11 + apps/plugins/puzzles/html/group.html | 52 + apps/plugins/puzzles/html/guess.html | 12 + apps/plugins/puzzles/html/inertia.html | 14 + apps/plugins/puzzles/html/javapage.pl | 104 + apps/plugins/puzzles/html/jspage.pl | 120 + apps/plugins/puzzles/html/keen.html | 15 + apps/plugins/puzzles/html/lightup.html | 10 + apps/plugins/puzzles/html/loopy.html | 13 + apps/plugins/puzzles/html/magnets.html | 17 + apps/plugins/puzzles/html/map.html | 15 + apps/plugins/puzzles/html/mines.html | 18 + apps/plugins/puzzles/html/net.html | 17 + apps/plugins/puzzles/html/netslide.html | 14 + apps/plugins/puzzles/html/palisade.html | 11 + apps/plugins/puzzles/html/pattern.html | 12 + apps/plugins/puzzles/html/pearl.html | 13 + apps/plugins/puzzles/html/pegs.html | 8 + apps/plugins/puzzles/html/range.html | 21 + apps/plugins/puzzles/html/rect.html | 10 + apps/plugins/puzzles/html/samegame.html | 14 + apps/plugins/puzzles/html/signpost.html | 14 + apps/plugins/puzzles/html/singles.html | 11 + apps/plugins/puzzles/html/sixteen.html | 8 + apps/plugins/puzzles/html/slant.html | 9 + apps/plugins/puzzles/html/solo.html | 20 + apps/plugins/puzzles/html/tents.html | 20 + apps/plugins/puzzles/html/towers.html | 22 + apps/plugins/puzzles/html/tracks.html | 19 + apps/plugins/puzzles/html/twiddle.html | 15 + apps/plugins/puzzles/html/undead.html | 22 + apps/plugins/puzzles/html/unequal.html | 14 + apps/plugins/puzzles/html/unruly.html | 11 + apps/plugins/puzzles/html/untangle.html | 5 + apps/plugins/puzzles/icons/Makefile | 153 + apps/plugins/puzzles/icons/blackbox.sav | 27 + apps/plugins/puzzles/icons/bridges.sav | 72 + apps/plugins/puzzles/icons/cicon.pl | 27 + apps/plugins/puzzles/icons/crop.sh | 37 + apps/plugins/puzzles/icons/cube.sav | 22 + apps/plugins/puzzles/icons/dominosa.sav | 53 + apps/plugins/puzzles/icons/fifteen.sav | 74 + apps/plugins/puzzles/icons/filling.sav | 38 + apps/plugins/puzzles/icons/flip.sav | 20 + apps/plugins/puzzles/icons/flood.sav | 14 + apps/plugins/puzzles/icons/galaxies.sav | 51 + apps/plugins/puzzles/icons/guess.sav | 15 + apps/plugins/puzzles/icons/icon.pl | 270 + apps/plugins/puzzles/icons/inertia.sav | 30 + apps/plugins/puzzles/icons/keen.sav | 62 + apps/plugins/puzzles/icons/lightup.sav | 24 + apps/plugins/puzzles/icons/loopy.sav | 120 + apps/plugins/puzzles/icons/magnets.sav | 33 + apps/plugins/puzzles/icons/map.sav | 27 + apps/plugins/puzzles/icons/mines.sav | 67 + apps/plugins/puzzles/icons/net.sav | 53 + apps/plugins/puzzles/icons/netslide.sav | 47 + apps/plugins/puzzles/icons/palisade.sav | 50 + apps/plugins/puzzles/icons/pattern.sav | 29 + apps/plugins/puzzles/icons/pearl.sav | 23 + apps/plugins/puzzles/icons/pegs.sav | 16 + apps/plugins/puzzles/icons/range.sav | 36 + apps/plugins/puzzles/icons/rect.sav | 17 + apps/plugins/puzzles/icons/samegame.sav | 34 + apps/plugins/puzzles/icons/screenshot.sh | 25 + apps/plugins/puzzles/icons/signpost.sav | 23 + apps/plugins/puzzles/icons/singles.sav | 45 + apps/plugins/puzzles/icons/sixteen.sav | 39 + apps/plugins/puzzles/icons/slant.sav | 51 + apps/plugins/puzzles/icons/solo.sav | 36 + apps/plugins/puzzles/icons/square.pl | 95 + apps/plugins/puzzles/icons/tents.sav | 32 + apps/plugins/puzzles/icons/towers.sav | 26 + apps/plugins/puzzles/icons/tracks.sav | 31 + apps/plugins/puzzles/icons/twiddle.sav | 35 + apps/plugins/puzzles/icons/undead.sav | 14 + apps/plugins/puzzles/icons/unequal.sav | 25 + apps/plugins/puzzles/icons/unruly.sav | 22 + apps/plugins/puzzles/icons/untangle.sav | 16 + apps/plugins/puzzles/icons/win16pal.xpm | 23 + apps/plugins/puzzles/inertia.R | 19 + apps/plugins/puzzles/inertia.c | 2249 +++++++ apps/plugins/puzzles/keen.R | 25 + apps/plugins/puzzles/keen.c | 2479 ++++++++ apps/plugins/puzzles/keymaps.h | 206 + apps/plugins/puzzles/latin.c | 1436 +++++ apps/plugins/puzzles/latin.h | 122 + apps/plugins/puzzles/laydomino.c | 291 + apps/plugins/puzzles/lightup.R | 24 + apps/plugins/puzzles/lightup.c | 2405 +++++++ apps/plugins/puzzles/list.c | 55 + apps/plugins/puzzles/loopgen.c | 536 ++ apps/plugins/puzzles/loopgen.h | 35 + apps/plugins/puzzles/loopy.R | 31 + apps/plugins/puzzles/loopy.c | 3688 +++++++++++ apps/plugins/puzzles/magnets.R | 24 + apps/plugins/puzzles/magnets.c | 2641 ++++++++ apps/plugins/puzzles/makedist.sh | 47 + apps/plugins/puzzles/malloc.c | 61 + apps/plugins/puzzles/map.R | 24 + apps/plugins/puzzles/map.c | 3340 ++++++++++ apps/plugins/puzzles/maxflow.c | 461 ++ apps/plugins/puzzles/maxflow.h | 95 + apps/plugins/puzzles/midend.c | 2136 +++++++ apps/plugins/puzzles/mines.R | 24 + apps/plugins/puzzles/mines.c | 3250 ++++++++++ apps/plugins/puzzles/misc.c | 363 ++ apps/plugins/puzzles/mkauto.sh | 2 + apps/plugins/puzzles/mkfiles.pl | 1807 ++++++ apps/plugins/puzzles/nestedvm.c | 432 ++ apps/plugins/puzzles/net.R | 23 + apps/plugins/puzzles/net.c | 3210 ++++++++++ apps/plugins/puzzles/netslide.R | 21 + apps/plugins/puzzles/netslide.c | 1893 ++++++ apps/plugins/puzzles/no-icon.c | 8 + apps/plugins/puzzles/noicon.rc | 11 + apps/plugins/puzzles/nullfe.c | 69 + apps/plugins/puzzles/nullgame.R | 12 + apps/plugins/puzzles/nullgame.c | 303 + apps/plugins/puzzles/obfusc.c | 126 + apps/plugins/puzzles/osx-help.but | 14 + apps/plugins/puzzles/osx-info.plist | 34 + apps/plugins/puzzles/osx.icns | Bin 0 -> 48589 bytes apps/plugins/puzzles/osx.m | 1724 +++++ apps/plugins/puzzles/padtoolbar.bmp | Bin 0 -> 1198 bytes apps/plugins/puzzles/palisade.R | 21 + apps/plugins/puzzles/palisade.c | 1383 ++++ apps/plugins/puzzles/pattern.R | 25 + apps/plugins/puzzles/pattern.c | 2255 +++++++ apps/plugins/puzzles/pearl.R | 23 + apps/plugins/puzzles/pearl.c | 2772 ++++++++ apps/plugins/puzzles/pegs.R | 21 + apps/plugins/puzzles/pegs.c | 1340 ++++ apps/plugins/puzzles/penrose.c | 629 ++ apps/plugins/puzzles/penrose.h | 59 + apps/plugins/puzzles/printing.c | 247 + apps/plugins/puzzles/ps.c | 433 ++ apps/plugins/puzzles/puzzles.but | 3401 ++++++++++ apps/plugins/puzzles/puzzles.h | 636 ++ apps/plugins/puzzles/puzzles.make | 113 + apps/plugins/puzzles/puzzles.rc2 | 65 + apps/plugins/puzzles/random.c | 350 + apps/plugins/puzzles/range.R | 21 + apps/plugins/puzzles/range.c | 1833 ++++++ apps/plugins/puzzles/rbassert.h | 13 + apps/plugins/puzzles/rbcompat.h | 62 + apps/plugins/puzzles/rbwrappers.c | 2378 +++++++ apps/plugins/puzzles/rect.R | 19 + apps/plugins/puzzles/rect.c | 3000 +++++++++ apps/plugins/puzzles/resource.h | 20 + apps/plugins/puzzles/rockbox.c | 1682 +++++ apps/plugins/puzzles/samegame.R | 19 + apps/plugins/puzzles/samegame.c | 1679 +++++ apps/plugins/puzzles/signpost.R | 23 + apps/plugins/puzzles/signpost.c | 2480 ++++++++ apps/plugins/puzzles/singles.R | 23 + apps/plugins/puzzles/singles.c | 2004 ++++++ apps/plugins/puzzles/sixteen.R | 19 + apps/plugins/puzzles/sixteen.c | 1214 ++++ apps/plugins/puzzles/slant.R | 24 + apps/plugins/puzzles/slant.c | 2278 +++++++ apps/plugins/puzzles/solo.R | 24 + apps/plugins/puzzles/solo.c | 5656 +++++++++++++++++ apps/plugins/puzzles/tdq.c | 88 + apps/plugins/puzzles/tents.R | 24 + apps/plugins/puzzles/tents.c | 2740 ++++++++ apps/plugins/puzzles/towers.R | 25 + apps/plugins/puzzles/towers.c | 2104 ++++++ apps/plugins/puzzles/tracks.R | 21 + apps/plugins/puzzles/tracks.c | 2660 ++++++++ apps/plugins/puzzles/tree234.c | 2200 +++++++ apps/plugins/puzzles/tree234.h | 202 + apps/plugins/puzzles/twiddle.R | 19 + apps/plugins/puzzles/twiddle.c | 1319 ++++ apps/plugins/puzzles/undead.R | 18 + apps/plugins/puzzles/undead.c | 2738 ++++++++ apps/plugins/puzzles/unequal.R | 27 + apps/plugins/puzzles/unequal.c | 2267 +++++++ apps/plugins/puzzles/unfinished/README | 9 + apps/plugins/puzzles/unfinished/group.R | 25 + apps/plugins/puzzles/unfinished/group.c | 2198 +++++++ apps/plugins/puzzles/unfinished/group.gap | 97 + apps/plugins/puzzles/unfinished/numgame.c | 1290 ++++ apps/plugins/puzzles/unfinished/path.c | 786 +++ apps/plugins/puzzles/unfinished/separate.R | 21 + apps/plugins/puzzles/unfinished/separate.c | 859 +++ apps/plugins/puzzles/unfinished/slide.R | 24 + apps/plugins/puzzles/unfinished/slide.c | 2445 +++++++ apps/plugins/puzzles/unfinished/sokoban.R | 19 + apps/plugins/puzzles/unfinished/sokoban.c | 1479 +++++ apps/plugins/puzzles/unruly.R | 21 + apps/plugins/puzzles/unruly.c | 2071 ++++++ apps/plugins/puzzles/untangle.R | 21 + apps/plugins/puzzles/untangle.c | 1491 +++++ apps/plugins/puzzles/version.c | 7 + apps/plugins/puzzles/version.h | 11 + apps/plugins/puzzles/wceinf.pl | 65 + apps/plugins/puzzles/webpage.pl | 69 + apps/plugins/puzzles/website.url | 2 + apps/plugins/puzzles/windows.c | 3760 +++++++++++ apps/plugins/puzzles/winiss.pl | 79 + apps/plugins/puzzles/winwix.mc | 334 + apps/plugins/sgt-puzzles.c | 33 + docs/CREDITS | 1 + .../images/ss-puzzles-cube-128x128x16.png | Bin 0 -> 920 bytes .../images/ss-puzzles-cube-128x160x16.png | Bin 0 -> 1007 bytes .../images/ss-puzzles-cube-128x96x16.png | Bin 0 -> 549 bytes .../images/ss-puzzles-cube-132x80x16.png | Bin 0 -> 486 bytes .../images/ss-puzzles-cube-160x128x16.png | Bin 0 -> 843 bytes .../images/ss-puzzles-cube-176x132x16.png | Bin 0 -> 925 bytes .../images/ss-puzzles-cube-176x220x16.png | Bin 0 -> 1672 bytes .../images/ss-puzzles-cube-220x176x16.png | Bin 0 -> 1045 bytes .../images/ss-puzzles-cube-240x320x16.png | Bin 0 -> 2582 bytes .../images/ss-puzzles-cube-240x400x16.png | Bin 0 -> 2836 bytes .../images/ss-puzzles-cube-320x240x16.png | Bin 0 -> 2162 bytes .../images/ss-puzzles-cube-320x240x24.png | Bin 0 -> 2162 bytes .../images/ss-puzzles-cube-96x96x16.png | Bin 0 -> 565 bytes .../images/ss-puzzles-map-128x128x16.png | Bin 0 -> 1254 bytes .../images/ss-puzzles-map-128x160x16.png | Bin 0 -> 1342 bytes .../images/ss-puzzles-map-128x96x16.png | Bin 0 -> 1145 bytes .../images/ss-puzzles-map-132x80x16.png | Bin 0 -> 985 bytes .../images/ss-puzzles-map-160x128x16.png | Bin 0 -> 1438 bytes .../images/ss-puzzles-map-176x132x16.png | Bin 0 -> 1580 bytes .../images/ss-puzzles-map-176x220x16.png | Bin 0 -> 1833 bytes .../images/ss-puzzles-map-220x176x16.png | Bin 0 -> 2086 bytes .../images/ss-puzzles-map-240x320x16.png | Bin 0 -> 2659 bytes .../images/ss-puzzles-map-240x400x16.png | Bin 0 -> 2917 bytes .../images/ss-puzzles-map-320x240x16.png | Bin 0 -> 3103 bytes .../images/ss-puzzles-map-320x240x24.png | Bin 0 -> 3100 bytes .../images/ss-puzzles-map-96x96x16.png | Bin 0 -> 956 bytes manual/plugins/main.tex | 2 + manual/plugins/puzzles.tex | 7 + 289 files changed, 147273 insertions(+) create mode 100644 apps/plugins/puzzles/Buildscr create mode 100644 apps/plugins/puzzles/CHECKLST.txt create mode 100644 apps/plugins/puzzles/LICENCE create mode 100644 apps/plugins/puzzles/PuzzleApplet.java create mode 100644 apps/plugins/puzzles/README create mode 100644 apps/plugins/puzzles/Recipe create mode 100644 apps/plugins/puzzles/SOURCES create mode 100755 apps/plugins/puzzles/benchmark.pl create mode 100755 apps/plugins/puzzles/benchmark.sh create mode 100644 apps/plugins/puzzles/blackbox.R create mode 100644 apps/plugins/puzzles/blackbox.c create mode 100644 apps/plugins/puzzles/bridges.R create mode 100644 apps/plugins/puzzles/bridges.c create mode 100644 apps/plugins/puzzles/chm.but create mode 100644 apps/plugins/puzzles/chm.css create mode 100644 apps/plugins/puzzles/combi.c create mode 100644 apps/plugins/puzzles/configure.ac create mode 100644 apps/plugins/puzzles/cube.R create mode 100644 apps/plugins/puzzles/cube.c create mode 100755 apps/plugins/puzzles/desktop.pl create mode 100644 apps/plugins/puzzles/devel.but create mode 100644 apps/plugins/puzzles/divvy.c create mode 100644 apps/plugins/puzzles/dominosa.R create mode 100644 apps/plugins/puzzles/dominosa.c create mode 100644 apps/plugins/puzzles/drawing.c create mode 100644 apps/plugins/puzzles/dsf.c create mode 100644 apps/plugins/puzzles/emcc.c create mode 100644 apps/plugins/puzzles/emcclib.js create mode 100644 apps/plugins/puzzles/emccpre.js create mode 100644 apps/plugins/puzzles/emccx.json create mode 100644 apps/plugins/puzzles/fifteen.R create mode 100644 apps/plugins/puzzles/fifteen.c create mode 100644 apps/plugins/puzzles/filling.R create mode 100644 apps/plugins/puzzles/filling.c create mode 100644 apps/plugins/puzzles/findloop.c create mode 100644 apps/plugins/puzzles/flip.R create mode 100644 apps/plugins/puzzles/flip.c create mode 100644 apps/plugins/puzzles/flood.R create mode 100644 apps/plugins/puzzles/flood.c create mode 100644 apps/plugins/puzzles/galaxies.R create mode 100644 apps/plugins/puzzles/galaxies.c create mode 100644 apps/plugins/puzzles/grid.c create mode 100644 apps/plugins/puzzles/grid.h create mode 100644 apps/plugins/puzzles/gtk.c create mode 100644 apps/plugins/puzzles/guess.R create mode 100644 apps/plugins/puzzles/guess.c create mode 100644 apps/plugins/puzzles/html/blackbox.html create mode 100644 apps/plugins/puzzles/html/bridges.html create mode 100644 apps/plugins/puzzles/html/cube.html create mode 100644 apps/plugins/puzzles/html/dominosa.html create mode 100644 apps/plugins/puzzles/html/fifteen.html create mode 100644 apps/plugins/puzzles/html/filling.html create mode 100644 apps/plugins/puzzles/html/flip.html create mode 100644 apps/plugins/puzzles/html/flood.html create mode 100644 apps/plugins/puzzles/html/galaxies.html create mode 100644 apps/plugins/puzzles/html/group.html create mode 100644 apps/plugins/puzzles/html/guess.html create mode 100644 apps/plugins/puzzles/html/inertia.html create mode 100755 apps/plugins/puzzles/html/javapage.pl create mode 100755 apps/plugins/puzzles/html/jspage.pl create mode 100644 apps/plugins/puzzles/html/keen.html create mode 100644 apps/plugins/puzzles/html/lightup.html create mode 100644 apps/plugins/puzzles/html/loopy.html create mode 100644 apps/plugins/puzzles/html/magnets.html create mode 100644 apps/plugins/puzzles/html/map.html create mode 100644 apps/plugins/puzzles/html/mines.html create mode 100644 apps/plugins/puzzles/html/net.html create mode 100644 apps/plugins/puzzles/html/netslide.html create mode 100644 apps/plugins/puzzles/html/palisade.html create mode 100644 apps/plugins/puzzles/html/pattern.html create mode 100644 apps/plugins/puzzles/html/pearl.html create mode 100644 apps/plugins/puzzles/html/pegs.html create mode 100644 apps/plugins/puzzles/html/range.html create mode 100644 apps/plugins/puzzles/html/rect.html create mode 100644 apps/plugins/puzzles/html/samegame.html create mode 100644 apps/plugins/puzzles/html/signpost.html create mode 100644 apps/plugins/puzzles/html/singles.html create mode 100644 apps/plugins/puzzles/html/sixteen.html create mode 100644 apps/plugins/puzzles/html/slant.html create mode 100644 apps/plugins/puzzles/html/solo.html create mode 100644 apps/plugins/puzzles/html/tents.html create mode 100644 apps/plugins/puzzles/html/towers.html create mode 100644 apps/plugins/puzzles/html/tracks.html create mode 100644 apps/plugins/puzzles/html/twiddle.html create mode 100644 apps/plugins/puzzles/html/undead.html create mode 100644 apps/plugins/puzzles/html/unequal.html create mode 100644 apps/plugins/puzzles/html/unruly.html create mode 100644 apps/plugins/puzzles/html/untangle.html create mode 100644 apps/plugins/puzzles/icons/Makefile create mode 100644 apps/plugins/puzzles/icons/blackbox.sav create mode 100644 apps/plugins/puzzles/icons/bridges.sav create mode 100755 apps/plugins/puzzles/icons/cicon.pl create mode 100755 apps/plugins/puzzles/icons/crop.sh create mode 100644 apps/plugins/puzzles/icons/cube.sav create mode 100644 apps/plugins/puzzles/icons/dominosa.sav create mode 100644 apps/plugins/puzzles/icons/fifteen.sav create mode 100644 apps/plugins/puzzles/icons/filling.sav create mode 100644 apps/plugins/puzzles/icons/flip.sav create mode 100644 apps/plugins/puzzles/icons/flood.sav create mode 100644 apps/plugins/puzzles/icons/galaxies.sav create mode 100644 apps/plugins/puzzles/icons/guess.sav create mode 100755 apps/plugins/puzzles/icons/icon.pl create mode 100644 apps/plugins/puzzles/icons/inertia.sav create mode 100644 apps/plugins/puzzles/icons/keen.sav create mode 100644 apps/plugins/puzzles/icons/lightup.sav create mode 100644 apps/plugins/puzzles/icons/loopy.sav create mode 100644 apps/plugins/puzzles/icons/magnets.sav create mode 100644 apps/plugins/puzzles/icons/map.sav create mode 100644 apps/plugins/puzzles/icons/mines.sav create mode 100644 apps/plugins/puzzles/icons/net.sav create mode 100644 apps/plugins/puzzles/icons/netslide.sav create mode 100644 apps/plugins/puzzles/icons/palisade.sav create mode 100644 apps/plugins/puzzles/icons/pattern.sav create mode 100644 apps/plugins/puzzles/icons/pearl.sav create mode 100644 apps/plugins/puzzles/icons/pegs.sav create mode 100644 apps/plugins/puzzles/icons/range.sav create mode 100644 apps/plugins/puzzles/icons/rect.sav create mode 100644 apps/plugins/puzzles/icons/samegame.sav create mode 100755 apps/plugins/puzzles/icons/screenshot.sh create mode 100644 apps/plugins/puzzles/icons/signpost.sav create mode 100644 apps/plugins/puzzles/icons/singles.sav create mode 100644 apps/plugins/puzzles/icons/sixteen.sav create mode 100644 apps/plugins/puzzles/icons/slant.sav create mode 100644 apps/plugins/puzzles/icons/solo.sav create mode 100755 apps/plugins/puzzles/icons/square.pl create mode 100644 apps/plugins/puzzles/icons/tents.sav create mode 100644 apps/plugins/puzzles/icons/towers.sav create mode 100644 apps/plugins/puzzles/icons/tracks.sav create mode 100644 apps/plugins/puzzles/icons/twiddle.sav create mode 100644 apps/plugins/puzzles/icons/undead.sav create mode 100644 apps/plugins/puzzles/icons/unequal.sav create mode 100644 apps/plugins/puzzles/icons/unruly.sav create mode 100644 apps/plugins/puzzles/icons/untangle.sav create mode 100644 apps/plugins/puzzles/icons/win16pal.xpm create mode 100644 apps/plugins/puzzles/inertia.R create mode 100644 apps/plugins/puzzles/inertia.c create mode 100644 apps/plugins/puzzles/keen.R create mode 100644 apps/plugins/puzzles/keen.c create mode 100644 apps/plugins/puzzles/keymaps.h create mode 100644 apps/plugins/puzzles/latin.c create mode 100644 apps/plugins/puzzles/latin.h create mode 100644 apps/plugins/puzzles/laydomino.c create mode 100644 apps/plugins/puzzles/lightup.R create mode 100644 apps/plugins/puzzles/lightup.c create mode 100644 apps/plugins/puzzles/list.c create mode 100644 apps/plugins/puzzles/loopgen.c create mode 100644 apps/plugins/puzzles/loopgen.h create mode 100644 apps/plugins/puzzles/loopy.R create mode 100644 apps/plugins/puzzles/loopy.c create mode 100644 apps/plugins/puzzles/magnets.R create mode 100644 apps/plugins/puzzles/magnets.c create mode 100755 apps/plugins/puzzles/makedist.sh create mode 100644 apps/plugins/puzzles/malloc.c create mode 100644 apps/plugins/puzzles/map.R create mode 100644 apps/plugins/puzzles/map.c create mode 100644 apps/plugins/puzzles/maxflow.c create mode 100644 apps/plugins/puzzles/maxflow.h create mode 100644 apps/plugins/puzzles/midend.c create mode 100644 apps/plugins/puzzles/mines.R create mode 100644 apps/plugins/puzzles/mines.c create mode 100644 apps/plugins/puzzles/misc.c create mode 100755 apps/plugins/puzzles/mkauto.sh create mode 100755 apps/plugins/puzzles/mkfiles.pl create mode 100644 apps/plugins/puzzles/nestedvm.c create mode 100644 apps/plugins/puzzles/net.R create mode 100644 apps/plugins/puzzles/net.c create mode 100644 apps/plugins/puzzles/netslide.R create mode 100644 apps/plugins/puzzles/netslide.c create mode 100644 apps/plugins/puzzles/no-icon.c create mode 100644 apps/plugins/puzzles/noicon.rc create mode 100644 apps/plugins/puzzles/nullfe.c create mode 100644 apps/plugins/puzzles/nullgame.R create mode 100644 apps/plugins/puzzles/nullgame.c create mode 100644 apps/plugins/puzzles/obfusc.c create mode 100644 apps/plugins/puzzles/osx-help.but create mode 100644 apps/plugins/puzzles/osx-info.plist create mode 100644 apps/plugins/puzzles/osx.icns create mode 100644 apps/plugins/puzzles/osx.m create mode 100644 apps/plugins/puzzles/padtoolbar.bmp create mode 100644 apps/plugins/puzzles/palisade.R create mode 100644 apps/plugins/puzzles/palisade.c create mode 100644 apps/plugins/puzzles/pattern.R create mode 100644 apps/plugins/puzzles/pattern.c create mode 100644 apps/plugins/puzzles/pearl.R create mode 100644 apps/plugins/puzzles/pearl.c create mode 100644 apps/plugins/puzzles/pegs.R create mode 100644 apps/plugins/puzzles/pegs.c create mode 100644 apps/plugins/puzzles/penrose.c create mode 100644 apps/plugins/puzzles/penrose.h create mode 100644 apps/plugins/puzzles/printing.c create mode 100644 apps/plugins/puzzles/ps.c create mode 100644 apps/plugins/puzzles/puzzles.but create mode 100644 apps/plugins/puzzles/puzzles.h create mode 100644 apps/plugins/puzzles/puzzles.make create mode 100644 apps/plugins/puzzles/puzzles.rc2 create mode 100644 apps/plugins/puzzles/random.c create mode 100644 apps/plugins/puzzles/range.R create mode 100644 apps/plugins/puzzles/range.c create mode 100644 apps/plugins/puzzles/rbassert.h create mode 100644 apps/plugins/puzzles/rbcompat.h create mode 100644 apps/plugins/puzzles/rbwrappers.c create mode 100644 apps/plugins/puzzles/rect.R create mode 100644 apps/plugins/puzzles/rect.c create mode 100644 apps/plugins/puzzles/resource.h create mode 100644 apps/plugins/puzzles/rockbox.c create mode 100644 apps/plugins/puzzles/samegame.R create mode 100644 apps/plugins/puzzles/samegame.c create mode 100644 apps/plugins/puzzles/signpost.R create mode 100644 apps/plugins/puzzles/signpost.c create mode 100644 apps/plugins/puzzles/singles.R create mode 100644 apps/plugins/puzzles/singles.c create mode 100644 apps/plugins/puzzles/sixteen.R create mode 100644 apps/plugins/puzzles/sixteen.c create mode 100644 apps/plugins/puzzles/slant.R create mode 100644 apps/plugins/puzzles/slant.c create mode 100644 apps/plugins/puzzles/solo.R create mode 100644 apps/plugins/puzzles/solo.c create mode 100644 apps/plugins/puzzles/tdq.c create mode 100644 apps/plugins/puzzles/tents.R create mode 100644 apps/plugins/puzzles/tents.c create mode 100644 apps/plugins/puzzles/towers.R create mode 100644 apps/plugins/puzzles/towers.c create mode 100644 apps/plugins/puzzles/tracks.R create mode 100644 apps/plugins/puzzles/tracks.c create mode 100644 apps/plugins/puzzles/tree234.c create mode 100644 apps/plugins/puzzles/tree234.h create mode 100644 apps/plugins/puzzles/twiddle.R create mode 100644 apps/plugins/puzzles/twiddle.c create mode 100644 apps/plugins/puzzles/undead.R create mode 100644 apps/plugins/puzzles/undead.c create mode 100644 apps/plugins/puzzles/unequal.R create mode 100644 apps/plugins/puzzles/unequal.c create mode 100644 apps/plugins/puzzles/unfinished/README create mode 100644 apps/plugins/puzzles/unfinished/group.R create mode 100644 apps/plugins/puzzles/unfinished/group.c create mode 100644 apps/plugins/puzzles/unfinished/group.gap create mode 100644 apps/plugins/puzzles/unfinished/numgame.c create mode 100644 apps/plugins/puzzles/unfinished/path.c create mode 100644 apps/plugins/puzzles/unfinished/separate.R create mode 100644 apps/plugins/puzzles/unfinished/separate.c create mode 100644 apps/plugins/puzzles/unfinished/slide.R create mode 100644 apps/plugins/puzzles/unfinished/slide.c create mode 100644 apps/plugins/puzzles/unfinished/sokoban.R create mode 100644 apps/plugins/puzzles/unfinished/sokoban.c create mode 100644 apps/plugins/puzzles/unruly.R create mode 100644 apps/plugins/puzzles/unruly.c create mode 100644 apps/plugins/puzzles/untangle.R create mode 100644 apps/plugins/puzzles/untangle.c create mode 100644 apps/plugins/puzzles/version.c create mode 100644 apps/plugins/puzzles/version.h create mode 100644 apps/plugins/puzzles/wceinf.pl create mode 100755 apps/plugins/puzzles/webpage.pl create mode 100644 apps/plugins/puzzles/website.url create mode 100644 apps/plugins/puzzles/windows.c create mode 100755 apps/plugins/puzzles/winiss.pl create mode 100644 apps/plugins/puzzles/winwix.mc create mode 100644 apps/plugins/sgt-puzzles.c create mode 100644 manual/plugins/images/ss-puzzles-cube-128x128x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-128x160x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-128x96x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-132x80x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-160x128x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-176x132x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-176x220x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-220x176x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-240x320x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-240x400x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-320x240x16.png create mode 100644 manual/plugins/images/ss-puzzles-cube-320x240x24.png create mode 100644 manual/plugins/images/ss-puzzles-cube-96x96x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-128x128x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-128x160x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-128x96x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-132x80x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-160x128x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-176x132x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-176x220x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-220x176x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-240x320x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-240x400x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-320x240x16.png create mode 100644 manual/plugins/images/ss-puzzles-map-320x240x24.png create mode 100644 manual/plugins/images/ss-puzzles-map-96x96x16.png create mode 100644 manual/plugins/puzzles.tex 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 _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/.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= 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 +. + +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 +. 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 '' >$*.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 < + + + +Puzzle generation-time benchmarks + + + +

Puzzle generation-time benchmarks

+

Sort order: + + + + + +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 "\n"; + $index++; +} + +print < + +EOF + +sub escape { + my ($text) = @_; + $text =~ s/&/&/g; + $text =~ s//>/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 +#include +#include +#include "rbassert.h" +#include +#include + +#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, ¥d, &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 +#include +#include +#include "rbassert.h" +#include +#include + +#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}{} 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 + +#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 + +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 + #include + #include + #include + #include + #include + #include + #include + + #include + #include + + #include + #include + + #include + + #include + #include + #include + #include + ],[ + 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 +#include +#include +#include "rbassert.h" +#include +#include + +#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 [ [ ]]\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{} 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 +#include +#include + +#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 +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +/* nth triangular number */ +#define TRI(n) ( (n) * ((n) + 1) / 2 ) +/* number of dominoes for value n */ +#define DCOUNT(n) TRI((n)+1) +/* map a pair of numbers to a unique domino index from 0 upwards. */ +#define DINDEX(n1,n2) ( TRI(max(n1,n2)) + min(n1,n2) ) + +#define FLASH_TIME 0.13F + +enum { + COL_BACKGROUND, + COL_TEXT, + COL_DOMINO, + COL_DOMINOCLASH, + COL_DOMINOTEXT, + COL_EDGE, + COL_HIGHLIGHT_1, + COL_HIGHLIGHT_2, + NCOLOURS +}; + +struct game_params { + int n; + int unique; +}; + +struct game_numbers { + int refcount; + int *numbers; /* h x w */ +}; + +#define EDGE_L 0x100 +#define EDGE_R 0x200 +#define EDGE_T 0x400 +#define EDGE_B 0x800 + +struct game_state { + game_params params; + int w, h; + struct game_numbers *numbers; + int *grid; + unsigned short *edges; /* h x w */ + int completed, cheated; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->n = 6; + ret->unique = TRUE; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + int n; + char buf[80]; + + switch (i) { + case 0: n = 3; break; + case 1: n = 4; break; + case 2: n = 5; break; + case 3: n = 6; break; + case 4: n = 7; break; + case 5: n = 8; break; + case 6: n = 9; break; + default: return FALSE; + } + + sprintf(buf, "Up to double-%d", n); + *name = dupstr(buf); + + *params = ret = snew(game_params); + ret->n = n; + ret->unique = TRUE; + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->n = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'a') + params->unique = FALSE; +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[80]; + sprintf(buf, "%d", params->n); + if (full && !params->unique) + strcat(buf, "a"); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Maximum number on dominoes"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->n); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Ensure unique solution"; + ret[1].type = C_BOOLEAN; + ret[1].sval = NULL; + ret[1].ival = params->unique; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->n = atoi(cfg[0].sval); + ret->unique = cfg[1].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->n < 1) + return "Maximum face number must be at least one"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +static int find_overlaps(int w, int h, int placement, int *set) +{ + int x, y, n; + + n = 0; /* number of returned placements */ + + x = placement / 2; + y = x / w; + x %= w; + + if (placement & 1) { + /* + * Horizontal domino, indexed by its left end. + */ + if (x > 0) + set[n++] = placement-2; /* horizontal domino to the left */ + if (y > 0) + set[n++] = placement-2*w-1;/* vertical domino above left side */ + if (y+1 < h) + set[n++] = placement-1; /* vertical domino below left side */ + if (x+2 < w) + set[n++] = placement+2; /* horizontal domino to the right */ + if (y > 0) + set[n++] = placement-2*w+2-1;/* vertical domino above right side */ + if (y+1 < h) + set[n++] = placement+2-1; /* vertical domino below right side */ + } else { + /* + * Vertical domino, indexed by its top end. + */ + if (y > 0) + set[n++] = placement-2*w; /* vertical domino above */ + if (x > 0) + set[n++] = placement-2+1; /* horizontal domino left of top */ + if (x+1 < w) + set[n++] = placement+1; /* horizontal domino right of top */ + if (y+2 < h) + set[n++] = placement+2*w; /* vertical domino below */ + if (x > 0) + set[n++] = placement-2+2*w+1;/* horizontal domino left of bottom */ + if (x+1 < w) + set[n++] = placement+2*w+1;/* horizontal domino right of bottom */ + } + + return n; +} + +/* + * Returns 0, 1 or 2 for number of solutions. 2 means `any number + * more than one', or more accurately `we were unable to prove + * there was only one'. + * + * Outputs in a `placements' array, indexed the same way as the one + * within this function (see below); entries in there are <0 for a + * placement ruled out, 0 for an uncertain placement, and 1 for a + * definite one. + */ +static int solver(int w, int h, int n, int *grid, int *output) +{ + int wh = w*h, dc = DCOUNT(n); + int *placements, *heads; + int i, j, x, y, ret; + + /* + * This array has one entry for every possible domino + * placement. Vertical placements are indexed by their top + * half, at (y*w+x)*2; horizontal placements are indexed by + * their left half at (y*w+x)*2+1. + * + * This array is used to link domino placements together into + * linked lists, so that we can track all the possible + * placements of each different domino. It's also used as a + * quick means of looking up an individual placement to see + * whether we still think it's possible. Actual values stored + * in this array are -2 (placement not possible at all), -1 + * (end of list), or the array index of the next item. + * + * Oh, and -3 for `not even valid', used for array indices + * which don't even represent a plausible placement. + */ + placements = snewn(2*wh, int); + for (i = 0; i < 2*wh; i++) + placements[i] = -3; /* not even valid */ + + /* + * This array has one entry for every domino, and it is an + * index into `placements' denoting the head of the placement + * list for that domino. + */ + heads = snewn(dc, int); + for (i = 0; i < dc; i++) + heads[i] = -1; + + /* + * Set up the initial possibility lists by scanning the grid. + */ + for (y = 0; y < h-1; y++) + for (x = 0; x < w; x++) { + int di = DINDEX(grid[y*w+x], grid[(y+1)*w+x]); + placements[(y*w+x)*2] = heads[di]; + heads[di] = (y*w+x)*2; + } + for (y = 0; y < h; y++) + for (x = 0; x < w-1; x++) { + int di = DINDEX(grid[y*w+x], grid[y*w+(x+1)]); + placements[(y*w+x)*2+1] = heads[di]; + heads[di] = (y*w+x)*2+1; + } + +#ifdef SOLVER_DIAGNOSTICS + printf("before solver:\n"); + for (i = 0; i <= n; i++) + for (j = 0; j <= i; j++) { + int k, m; + m = 0; + printf("%2d [%d %d]:", DINDEX(i, j), i, j); + for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k]) + printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v'); + printf("\n"); + } +#endif + + while (1) { + int done_something = FALSE; + + /* + * For each domino, look at its possible placements, and + * for each placement consider the placements (of any + * domino) it overlaps. Any placement overlapped by all + * placements of this domino can be ruled out. + * + * Each domino placement overlaps only six others, so we + * need not do serious set theory to work this out. + */ + for (i = 0; i < dc; i++) { + int permset[6], permlen = 0, p; + + + if (heads[i] == -1) { /* no placement for this domino */ + ret = 0; /* therefore puzzle is impossible */ + goto done; + } + for (j = heads[i]; j >= 0; j = placements[j]) { + assert(placements[j] != -2); + + if (j == heads[i]) { + permlen = find_overlaps(w, h, j, permset); + } else { + int tempset[6], templen, m, n, k; + + templen = find_overlaps(w, h, j, tempset); + + /* + * Pathetically primitive set intersection + * algorithm, which I'm only getting away with + * because I know my sets are bounded by a very + * small size. + */ + for (m = n = 0; m < permlen; m++) { + for (k = 0; k < templen; k++) + if (tempset[k] == permset[m]) + break; + if (k < templen) + permset[n++] = permset[m]; + } + permlen = n; + } + } + for (p = 0; p < permlen; p++) { + j = permset[p]; + if (placements[j] != -2) { + int p1, p2, di; + + done_something = TRUE; + + /* + * Rule out this placement. First find what + * domino it is... + */ + p1 = j / 2; + p2 = (j & 1) ? p1 + 1 : p1 + w; + di = DINDEX(grid[p1], grid[p2]); +#ifdef SOLVER_DIAGNOSTICS + printf("considering domino %d: ruling out placement %d" + " for %d\n", i, j, di); +#endif + + /* + * ... then walk that domino's placement list, + * removing this placement when we find it. + */ + if (heads[di] == j) + heads[di] = placements[j]; + else { + int k = heads[di]; + while (placements[k] != -1 && placements[k] != j) + k = placements[k]; + assert(placements[k] == j); + placements[k] = placements[j]; + } + placements[j] = -2; + } + } + } + + /* + * For each square, look at the available placements + * involving that square. If all of them are for the same + * domino, then rule out any placements for that domino + * _not_ involving this square. + */ + for (i = 0; i < wh; i++) { + int list[4], k, n, adi; + + x = i % w; + y = i / w; + + j = 0; + if (x > 0) + list[j++] = 2*(i-1)+1; + if (x+1 < w) + list[j++] = 2*i+1; + if (y > 0) + list[j++] = 2*(i-w); + if (y+1 < h) + list[j++] = 2*i; + + for (n = k = 0; k < j; k++) + if (placements[list[k]] >= -1) + list[n++] = list[k]; + + adi = -1; + + for (j = 0; j < n; j++) { + int p1, p2, di; + k = list[j]; + + p1 = k / 2; + p2 = (k & 1) ? p1 + 1 : p1 + w; + di = DINDEX(grid[p1], grid[p2]); + + if (adi == -1) + adi = di; + if (adi != di) + break; + } + + if (j == n) { + int nn; + + assert(adi >= 0); + /* + * We've found something. All viable placements + * involving this square are for domino `adi'. If + * the current placement list for that domino is + * longer than n, reduce it to precisely this + * placement list and we've done something. + */ + nn = 0; + for (k = heads[adi]; k >= 0; k = placements[k]) + nn++; + if (nn > n) { + done_something = TRUE; +#ifdef SOLVER_DIAGNOSTICS + printf("considering square %d,%d: reducing placements " + "of domino %d\n", x, y, adi); +#endif + /* + * Set all other placements on the list to + * impossible. + */ + k = heads[adi]; + while (k >= 0) { + int tmp = placements[k]; + placements[k] = -2; + k = tmp; + } + /* + * Set up the new list. + */ + heads[adi] = list[0]; + for (k = 0; k < n; k++) + placements[list[k]] = (k+1 == n ? -1 : list[k+1]); + } + } + } + + if (!done_something) + break; + } + +#ifdef SOLVER_DIAGNOSTICS + printf("after solver:\n"); + for (i = 0; i <= n; i++) + for (j = 0; j <= i; j++) { + int k, m; + m = 0; + printf("%2d [%d %d]:", DINDEX(i, j), i, j); + for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k]) + printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v'); + printf("\n"); + } +#endif + + ret = 1; + for (i = 0; i < wh*2; i++) { + if (placements[i] == -2) { + if (output) + output[i] = -1; /* ruled out */ + } else if (placements[i] != -3) { + int p1, p2, di; + + p1 = i / 2; + p2 = (i & 1) ? p1 + 1 : p1 + w; + di = DINDEX(grid[p1], grid[p2]); + + if (i == heads[di] && placements[i] == -1) { + if (output) + output[i] = 1; /* certain */ + } else { + if (output) + output[i] = 0; /* uncertain */ + ret = 2; + } + } + } + + done: + /* + * Free working data. + */ + sfree(placements); + sfree(heads); + + return ret; +} + +/* ---------------------------------------------------------------------- + * End of solver code. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int n = params->n, w = n+2, h = n+1, wh = w*h; + int *grid, *grid2, *list; + int i, j, k, len; + char *ret; + + /* + * Allocate space in which to lay the grid out. + */ + grid = snewn(wh, int); + grid2 = snewn(wh, int); + list = snewn(2*wh, int); + + /* + * I haven't been able to think of any particularly clever + * techniques for generating instances of Dominosa with a + * unique solution. Many of the deductions used in this puzzle + * are based on information involving half the grid at a time + * (`of all the 6s, exactly one is next to a 3'), so a strategy + * of partially solving the grid and then perturbing the place + * where the solver got stuck seems particularly likely to + * accidentally destroy the information which the solver had + * used in getting that far. (Contrast with, say, Mines, in + * which most deductions are local so this is an excellent + * strategy.) + * + * Therefore I resort to the basest of brute force methods: + * generate a random grid, see if it's solvable, throw it away + * and try again if not. My only concession to sophistication + * and cleverness is to at least _try_ not to generate obvious + * 2x2 ambiguous sections (see comment below in the domino- + * flipping section). + * + * During tests performed on 2005-07-15, I found that the brute + * force approach without that tweak had to throw away about 87 + * grids on average (at the default n=6) before finding a + * unique one, or a staggering 379 at n=9; good job the + * generator and solver are fast! When I added the + * ambiguous-section avoidance, those numbers came down to 19 + * and 26 respectively, which is a lot more sensible. + */ + + do { + domino_layout_prealloc(w, h, rs, grid, grid2, list); + + /* + * Now we have a complete layout covering the whole + * rectangle with dominoes. So shuffle the actual domino + * values and fill the rectangle with numbers. + */ + k = 0; + for (i = 0; i <= params->n; i++) + for (j = 0; j <= i; j++) { + list[k++] = i; + list[k++] = j; + } + shuffle(list, k/2, 2*sizeof(*list), rs); + j = 0; + for (i = 0; i < wh; i++) + if (grid[i] > i) { + /* Optionally flip the domino round. */ + int flip = -1; + + if (params->unique) { + int t1, t2; + /* + * If we're after a unique solution, we can do + * something here to improve the chances. If + * we're placing a domino so that it forms a + * 2x2 rectangle with one we've already placed, + * and if that domino and this one share a + * number, we can try not to put them so that + * the identical numbers are diagonally + * separated, because that automatically causes + * non-uniqueness: + * + * +---+ +-+-+ + * |2 3| |2|3| + * +---+ -> | | | + * |4 2| |4|2| + * +---+ +-+-+ + */ + t1 = i; + t2 = grid[i]; + if (t2 == t1 + w) { /* this domino is vertical */ + if (t1 % w > 0 &&/* and not on the left hand edge */ + grid[t1-1] == t2-1 &&/* alongside one to left */ + (grid2[t1-1] == list[j] || /* and has a number */ + grid2[t1-1] == list[j+1] || /* in common */ + grid2[t2-1] == list[j] || + grid2[t2-1] == list[j+1])) { + if (grid2[t1-1] == list[j] || + grid2[t2-1] == list[j+1]) + flip = 0; + else + flip = 1; + } + } else { /* this domino is horizontal */ + if (t1 / w > 0 &&/* and not on the top edge */ + grid[t1-w] == t2-w &&/* alongside one above */ + (grid2[t1-w] == list[j] || /* and has a number */ + grid2[t1-w] == list[j+1] || /* in common */ + grid2[t2-w] == list[j] || + grid2[t2-w] == list[j+1])) { + if (grid2[t1-w] == list[j] || + grid2[t2-w] == list[j+1]) + flip = 0; + else + flip = 1; + } + } + } + + if (flip < 0) + flip = random_upto(rs, 2); + + grid2[i] = list[j + flip]; + grid2[grid[i]] = list[j + 1 - flip]; + j += 2; + } + assert(j == k); + } while (params->unique && solver(w, h, n, grid2, NULL) > 1); + +#ifdef GENERATION_DIAGNOSTICS + for (j = 0; j < h; j++) { + for (i = 0; i < w; i++) { + putchar('0' + grid2[j*w+i]); + } + putchar('\n'); + } + putchar('\n'); +#endif + + /* + * Encode the resulting game state. + * + * Our encoding is a string of digits. Any number greater than + * 9 is represented by a decimal integer within square + * brackets. We know there are n+2 of every number (it's paired + * with each number from 0 to n inclusive, and one of those is + * itself so that adds another occurrence), so we can work out + * the string length in advance. + */ + + /* + * To work out the total length of the decimal encodings of all + * the numbers from 0 to n inclusive: + * - every number has a units digit; total is n+1. + * - all numbers above 9 have a tens digit; total is max(n+1-10,0). + * - all numbers above 99 have a hundreds digit; total is max(n+1-100,0). + * - and so on. + */ + len = n+1; + for (i = 10; i <= n; i *= 10) + len += max(n + 1 - i, 0); + /* Now add two square brackets for each number above 9. */ + len += 2 * max(n + 1 - 10, 0); + /* And multiply by n+2 for the repeated occurrences of each number. */ + len *= n+2; + + /* + * Now actually encode the string. + */ + ret = snewn(len+1, char); + j = 0; + for (i = 0; i < wh; i++) { + k = grid2[i]; + if (k < 10) + ret[j++] = '0' + k; + else + j += sprintf(ret+j, "[%d]", k); + assert(j <= len); + } + assert(j == len); + ret[j] = '\0'; + + /* + * Encode the solved state as an aux_info. + */ + { + char *auxinfo = snewn(wh+1, char); + + for (i = 0; i < wh; i++) { + int v = grid[i]; + auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' : + v == i+w ? 'T' : v == i-w ? 'B' : '.'); + } + auxinfo[wh] = '\0'; + + *aux = auxinfo; + } + + sfree(list); + sfree(grid2); + sfree(grid); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int n = params->n, w = n+2, h = n+1, wh = w*h; + int *occurrences; + int i, j; + char *ret; + + ret = NULL; + occurrences = snewn(n+1, int); + for (i = 0; i <= n; i++) + occurrences[i] = 0; + + for (i = 0; i < wh; i++) { + if (!*desc) { + ret = ret ? ret : "Game description is too short"; + } else { + if (*desc >= '0' && *desc <= '9') + j = *desc++ - '0'; + else if (*desc == '[') { + desc++; + j = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + if (*desc != ']') + ret = ret ? ret : "Missing ']' in game description"; + else + desc++; + } else { + j = -1; + ret = ret ? ret : "Invalid syntax in game description"; + } + if (j < 0 || j > n) + ret = ret ? ret : "Number out of range in game description"; + else + occurrences[j]++; + } + } + + if (*desc) + ret = ret ? ret : "Game description is too long"; + + if (!ret) { + for (i = 0; i <= n; i++) + if (occurrences[i] != n+2) + ret = "Incorrect number balance in game description"; + } + + sfree(occurrences); + + return ret; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int n = params->n, w = n+2, h = n+1, wh = w*h; + game_state *state = snew(game_state); + int i, j; + + state->params = *params; + state->w = w; + state->h = h; + + state->grid = snewn(wh, int); + for (i = 0; i < wh; i++) + state->grid[i] = i; + + state->edges = snewn(wh, unsigned short); + for (i = 0; i < wh; i++) + state->edges[i] = 0; + + state->numbers = snew(struct game_numbers); + state->numbers->refcount = 1; + state->numbers->numbers = snewn(wh, int); + + for (i = 0; i < wh; i++) { + assert(*desc); + if (*desc >= '0' && *desc <= '9') + j = *desc++ - '0'; + else { + assert(*desc == '['); + desc++; + j = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + assert(*desc == ']'); + desc++; + } + assert(j >= 0 && j <= n); + state->numbers->numbers[i] = j; + } + + state->completed = state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int n = state->params.n, w = n+2, h = n+1, wh = w*h; + game_state *ret = snew(game_state); + + ret->params = state->params; + ret->w = state->w; + ret->h = state->h; + ret->grid = snewn(wh, int); + memcpy(ret->grid, state->grid, wh * sizeof(int)); + ret->edges = snewn(wh, unsigned short); + memcpy(ret->edges, state->edges, wh * sizeof(unsigned short)); + ret->numbers = state->numbers; + ret->numbers->refcount++; + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->edges); + if (--state->numbers->refcount <= 0) { + sfree(state->numbers->numbers); + sfree(state->numbers); + } + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int n = state->params.n, w = n+2, h = n+1, wh = w*h; + int *placements; + char *ret; + int retlen, retsize; + int i, v; + char buf[80]; + int extra; + + if (aux) { + retsize = 256; + ret = snewn(retsize, char); + retlen = sprintf(ret, "S"); + + for (i = 0; i < wh; i++) { + if (aux[i] == 'L') + extra = sprintf(buf, ";D%d,%d", i, i+1); + else if (aux[i] == 'T') + extra = sprintf(buf, ";D%d,%d", i, i+w); + else + continue; + + if (retlen + extra + 1 >= retsize) { + retsize = retlen + extra + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += extra; + } + + } else { + + placements = snewn(wh*2, int); + for (i = 0; i < wh*2; i++) + placements[i] = -3; + solver(w, h, n, state->numbers->numbers, placements); + + /* + * First make a pass putting in edges for -1, then make a pass + * putting in dominoes for +1. + */ + retsize = 256; + ret = snewn(retsize, char); + retlen = sprintf(ret, "S"); + + for (v = -1; v <= +1; v += 2) + for (i = 0; i < wh*2; i++) + if (placements[i] == v) { + int p1 = i / 2; + int p2 = (i & 1) ? p1+1 : p1+w; + + extra = sprintf(buf, ";%c%d,%d", + (int)(v==-1 ? 'E' : 'D'), p1, p2); + + if (retlen + extra + 1 >= retsize) { + retsize = retlen + extra + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += extra; + } + + sfree(placements); + } + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return params->n < 1000; +} + +static void draw_domino(char *board, int start, char corner, + int dshort, int nshort, char cshort, + int dlong, int nlong, char clong) +{ + int go_short = nshort*dshort, go_long = nlong*dlong, i; + + board[start] = corner; + board[start + go_short] = corner; + board[start + go_long] = corner; + board[start + go_short + go_long] = corner; + + for (i = 1; i < nshort; ++i) { + int j = start + i*dshort, k = start + i*dshort + go_long; + if (board[j] != corner) board[j] = cshort; + if (board[k] != corner) board[k] = cshort; + } + + for (i = 1; i < nlong; ++i) { + int j = start + i*dlong, k = start + i*dlong + go_short; + if (board[j] != corner) board[j] = clong; + if (board[k] != corner) board[k] = clong; + } +} + +static char *game_text_format(const game_state *state) +{ + int w = state->w, h = state->h, r, c; + int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh; + char *board = snewn(len + 1, char); + + memset(board, ' ', len); + + for (r = 0; r < h; ++r) { + for (c = 0; c < w; ++c) { + int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; + int i = r*w + c, num = state->numbers->numbers[i]; + + if (num < 100) { + board[center] = '0' + num % 10; + if (num >= 10) board[center - 1] = '0' + num / 10; + } else { + board[center+1] = '0' + num % 10; + board[center] = '0' + num / 10 % 10; + board[center-1] = '0' + num / 100; + } + + if (state->edges[i] & EDGE_L) board[center - cw/2] = '|'; + if (state->edges[i] & EDGE_R) board[center + cw/2] = '|'; + if (state->edges[i] & EDGE_T) board[center - gw] = '-'; + if (state->edges[i] & EDGE_B) board[center + gw] = '-'; + + if (state->grid[i] == i) continue; /* no domino pairing */ + if (state->grid[i] < i) continue; /* already done */ + assert (state->grid[i] == i + 1 || state->grid[i] == i + w); + if (state->grid[i] == i + 1) + draw_domino(board, cell, '+', gw, ch, '|', +1, 2*cw, '-'); + else if (state->grid[i] == i + w) + draw_domino(board, cell, '+', +1, cw, '-', gw, 2*ch, '|'); + } + board[r*ch*gw + gw - 1] = '\n'; + board[r*ch*gw + gw + gw - 1] = '\n'; + } + board[len - 1] = '\n'; + board[len] = '\0'; + return board; +} + +struct game_ui { + int cur_x, cur_y, cur_visible, highlight_1, highlight_2; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = ui->cur_y = 0; + ui->cur_visible = 0; + ui->highlight_1 = ui->highlight_2 = -1; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (!oldstate->completed && newstate->completed) + ui->cur_visible = 0; +} + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE * 3 / 4) +#define DOMINO_GUTTER (TILESIZE / 16) +#define DOMINO_RADIUS (TILESIZE / 8) +#define DOMINO_COFFSET (DOMINO_GUTTER + DOMINO_RADIUS) +#define CURSOR_RADIUS (TILESIZE / 4) + +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) + +struct game_drawstate { + int started; + int w, h, tilesize; + unsigned long *visible; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->w, h = state->h; + char buf[80]; + + /* + * A left-click between two numbers toggles a domino covering + * them. A right-click toggles an edge. + */ + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + int tx = FROMCOORD(x), ty = FROMCOORD(y), t = ty*w+tx; + int dx, dy; + int d1, d2; + + if (tx < 0 || tx >= w || ty < 0 || ty >= h) + return NULL; + + /* + * Now we know which square the click was in, decide which + * edge of the square it was closest to. + */ + dx = 2 * (x - COORD(tx)) - TILESIZE; + dy = 2 * (y - COORD(ty)) - TILESIZE; + + if (abs(dx) > abs(dy) && dx < 0 && tx > 0) + d1 = t - 1, d2 = t; /* clicked in right side of domino */ + else if (abs(dx) > abs(dy) && dx > 0 && tx+1 < w) + d1 = t, d2 = t + 1; /* clicked in left side of domino */ + else if (abs(dy) > abs(dx) && dy < 0 && ty > 0) + d1 = t - w, d2 = t; /* clicked in bottom half of domino */ + else if (abs(dy) > abs(dx) && dy > 0 && ty+1 < h) + d1 = t, d2 = t + w; /* clicked in top half of domino */ + else + return NULL; + + /* + * We can't mark an edge next to any domino. + */ + if (button == RIGHT_BUTTON && + (state->grid[d1] != d1 || state->grid[d2] != d2)) + return NULL; + + ui->cur_visible = 0; + sprintf(buf, "%c%d,%d", (int)(button == RIGHT_BUTTON ? 'E' : 'D'), d1, d2); + return dupstr(buf); + } else if (IS_CURSOR_MOVE(button)) { + ui->cur_visible = 1; + + move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, 0); + + return ""; + } else if (IS_CURSOR_SELECT(button)) { + int d1, d2; + + if (!((ui->cur_x ^ ui->cur_y) & 1)) + return NULL; /* must have exactly one dimension odd */ + d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2); + d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2); + + /* + * We can't mark an edge next to any domino. + */ + if (button == CURSOR_SELECT2 && + (state->grid[d1] != d1 || state->grid[d2] != d2)) + return NULL; + + sprintf(buf, "%c%d,%d", (int)(button == CURSOR_SELECT2 ? 'E' : 'D'), d1, d2); + return dupstr(buf); + } else if (isdigit(button)) { + int n = state->params.n, num = button - '0'; + if (num > n) { + return NULL; + } else if (ui->highlight_1 == num) { + ui->highlight_1 = -1; + } else if (ui->highlight_2 == num) { + ui->highlight_2 = -1; + } else if (ui->highlight_1 == -1) { + ui->highlight_1 = num; + } else if (ui->highlight_2 == -1) { + ui->highlight_2 = num; + } else { + return NULL; + } + return ""; + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int n = state->params.n, w = n+2, h = n+1, wh = w*h; + int d1, d2, d3, p; + game_state *ret = dup_game(state); + + while (*move) { + if (move[0] == 'S') { + int i; + + ret->cheated = TRUE; + + /* + * Clear the existing edges and domino placements. We + * expect the S to be followed by other commands. + */ + for (i = 0; i < wh; i++) { + ret->grid[i] = i; + ret->edges[i] = 0; + } + move++; + } else if (move[0] == 'D' && + sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 && + d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2) { + + /* + * Toggle domino presence between d1 and d2. + */ + if (ret->grid[d1] == d2) { + assert(ret->grid[d2] == d1); + ret->grid[d1] = d1; + ret->grid[d2] = d2; + } else { + /* + * Erase any dominoes that might overlap the new one. + */ + d3 = ret->grid[d1]; + if (d3 != d1) + ret->grid[d3] = d3; + d3 = ret->grid[d2]; + if (d3 != d2) + ret->grid[d3] = d3; + /* + * Place the new one. + */ + ret->grid[d1] = d2; + ret->grid[d2] = d1; + + /* + * Destroy any edges lurking around it. + */ + if (ret->edges[d1] & EDGE_L) { + assert(d1 - 1 >= 0); + ret->edges[d1 - 1] &= ~EDGE_R; + } + if (ret->edges[d1] & EDGE_R) { + assert(d1 + 1 < wh); + ret->edges[d1 + 1] &= ~EDGE_L; + } + if (ret->edges[d1] & EDGE_T) { + assert(d1 - w >= 0); + ret->edges[d1 - w] &= ~EDGE_B; + } + if (ret->edges[d1] & EDGE_B) { + assert(d1 + 1 < wh); + ret->edges[d1 + w] &= ~EDGE_T; + } + ret->edges[d1] = 0; + if (ret->edges[d2] & EDGE_L) { + assert(d2 - 1 >= 0); + ret->edges[d2 - 1] &= ~EDGE_R; + } + if (ret->edges[d2] & EDGE_R) { + assert(d2 + 1 < wh); + ret->edges[d2 + 1] &= ~EDGE_L; + } + if (ret->edges[d2] & EDGE_T) { + assert(d2 - w >= 0); + ret->edges[d2 - w] &= ~EDGE_B; + } + if (ret->edges[d2] & EDGE_B) { + assert(d2 + 1 < wh); + ret->edges[d2 + w] &= ~EDGE_T; + } + ret->edges[d2] = 0; + } + + move += p+1; + } else if (move[0] == 'E' && + sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 && + d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 && + ret->grid[d1] == d1 && ret->grid[d2] == d2) { + + /* + * Toggle edge presence between d1 and d2. + */ + if (d2 == d1 + 1) { + ret->edges[d1] ^= EDGE_R; + ret->edges[d2] ^= EDGE_L; + } else { + ret->edges[d1] ^= EDGE_B; + ret->edges[d2] ^= EDGE_T; + } + + move += p+1; + } else { + free_game(ret); + return NULL; + } + + if (*move) { + if (*move != ';') { + free_game(ret); + return NULL; + } + move++; + } + } + + /* + * After modifying the grid, check completion. + */ + if (!ret->completed) { + int i, ok = 0; + unsigned char *used = snewn(TRI(n+1), unsigned char); + + memset(used, 0, TRI(n+1)); + for (i = 0; i < wh; i++) + if (ret->grid[i] > i) { + int n1, n2, di; + + n1 = ret->numbers->numbers[i]; + n2 = ret->numbers->numbers[ret->grid[i]]; + + di = DINDEX(n1, n2); + assert(di >= 0 && di < TRI(n+1)); + + if (!used[di]) { + used[di] = 1; + ok++; + } + } + + sfree(used); + if (ok == DCOUNT(n)) + ret->completed = TRUE; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + int n = params->n, w = n+2, h = n+1; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = w * TILESIZE + 2*BORDER; + *y = h * TILESIZE + 2*BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_TEXT * 3 + 0] = 0.0F; + ret[COL_TEXT * 3 + 1] = 0.0F; + ret[COL_TEXT * 3 + 2] = 0.0F; + + ret[COL_DOMINO * 3 + 0] = 0.0F; + ret[COL_DOMINO * 3 + 1] = 0.0F; + ret[COL_DOMINO * 3 + 2] = 0.0F; + + ret[COL_DOMINOCLASH * 3 + 0] = 0.5F; + ret[COL_DOMINOCLASH * 3 + 1] = 0.0F; + ret[COL_DOMINOCLASH * 3 + 2] = 0.0F; + + ret[COL_DOMINOTEXT * 3 + 0] = 1.0F; + ret[COL_DOMINOTEXT * 3 + 1] = 1.0F; + ret[COL_DOMINOTEXT * 3 + 2] = 1.0F; + + ret[COL_EDGE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2 / 3; + ret[COL_EDGE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2 / 3; + ret[COL_EDGE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2 / 3; + + ret[COL_HIGHLIGHT_1 * 3 + 0] = 0.85; + ret[COL_HIGHLIGHT_1 * 3 + 1] = 0.20; + ret[COL_HIGHLIGHT_1 * 3 + 2] = 0.20; + + ret[COL_HIGHLIGHT_2 * 3 + 0] = 0.30; + ret[COL_HIGHLIGHT_2 * 3 + 1] = 0.85; + ret[COL_HIGHLIGHT_2 * 3 + 2] = 0.20; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->started = FALSE; + ds->w = state->w; + ds->h = state->h; + ds->visible = snewn(ds->w * ds->h, unsigned long); + ds->tilesize = 0; /* not decided yet */ + for (i = 0; i < ds->w * ds->h; i++) + ds->visible[i] = 0xFFFF; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +enum { + TYPE_L, + TYPE_R, + TYPE_T, + TYPE_B, + TYPE_BLANK, + TYPE_MASK = 0x0F +}; + +/* These flags must be disjoint with: + * the above enum (TYPE_*) [0x000 -- 0x00F] + * EDGE_* [0x100 -- 0xF00] + * and must fit into an unsigned long (32 bits). + */ +#define DF_HIGHLIGHT_1 0x10 +#define DF_HIGHLIGHT_2 0x20 +#define DF_FLASH 0x40 +#define DF_CLASH 0x80 + +#define DF_CURSOR 0x01000 +#define DF_CURSOR_USEFUL 0x02000 +#define DF_CURSOR_XBASE 0x10000 +#define DF_CURSOR_XMASK 0x30000 +#define DF_CURSOR_YBASE 0x40000 +#define DF_CURSOR_YMASK 0xC0000 + +#define CEDGE_OFF (TILESIZE / 8) +#define IS_EMPTY(s,x,y) ((s)->grid[(y)*(s)->w+(x)] == ((y)*(s)->w+(x))) + +static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, + int x, int y, int type, int highlight_1, int highlight_2) +{ + int w = state->w /*, h = state->h */; + int cx = COORD(x), cy = COORD(y); + int nc; + char str[80]; + int flags; + + clip(dr, cx, cy, TILESIZE, TILESIZE); + draw_rect(dr, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND); + + flags = type &~ TYPE_MASK; + type &= TYPE_MASK; + + if (type != TYPE_BLANK) { + int i, bg; + + /* + * Draw one end of a domino. This is composed of: + * + * - two filled circles (rounded corners) + * - two rectangles + * - a slight shift in the number + */ + + if (flags & DF_CLASH) + bg = COL_DOMINOCLASH; + else + bg = COL_DOMINO; + nc = COL_DOMINOTEXT; + + if (flags & DF_FLASH) { + int tmp = nc; + nc = bg; + bg = tmp; + } + + if (type == TYPE_L || type == TYPE_T) + draw_circle(dr, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET, + DOMINO_RADIUS, bg, bg); + if (type == TYPE_R || type == TYPE_T) + draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET, + DOMINO_RADIUS, bg, bg); + if (type == TYPE_L || type == TYPE_B) + draw_circle(dr, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET, + DOMINO_RADIUS, bg, bg); + if (type == TYPE_R || type == TYPE_B) + draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, + cy+TILESIZE-1-DOMINO_COFFSET, + DOMINO_RADIUS, bg, bg); + + for (i = 0; i < 2; i++) { + int x1, y1, x2, y2; + + x1 = cx + (i ? DOMINO_GUTTER : DOMINO_COFFSET); + y1 = cy + (i ? DOMINO_COFFSET : DOMINO_GUTTER); + x2 = cx + TILESIZE-1 - (i ? DOMINO_GUTTER : DOMINO_COFFSET); + y2 = cy + TILESIZE-1 - (i ? DOMINO_COFFSET : DOMINO_GUTTER); + if (type == TYPE_L) + x2 = cx + TILESIZE + TILESIZE/16; + else if (type == TYPE_R) + x1 = cx - TILESIZE/16; + else if (type == TYPE_T) + y2 = cy + TILESIZE + TILESIZE/16; + else if (type == TYPE_B) + y1 = cy - TILESIZE/16; + + draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg); + } + } else { + if (flags & EDGE_T) + draw_rect(dr, cx+DOMINO_GUTTER, cy, + TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE); + if (flags & EDGE_B) + draw_rect(dr, cx+DOMINO_GUTTER, cy+TILESIZE-1, + TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE); + if (flags & EDGE_L) + draw_rect(dr, cx, cy+DOMINO_GUTTER, + 1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE); + if (flags & EDGE_R) + draw_rect(dr, cx+TILESIZE-1, cy+DOMINO_GUTTER, + 1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE); + nc = COL_TEXT; + } + + if (flags & DF_CURSOR) { + int curx = ((flags & DF_CURSOR_XMASK) / DF_CURSOR_XBASE) & 3; + int cury = ((flags & DF_CURSOR_YMASK) / DF_CURSOR_YBASE) & 3; + int ox = cx + curx*TILESIZE/2; + int oy = cy + cury*TILESIZE/2; + + draw_rect_corners(dr, ox, oy, CURSOR_RADIUS, nc); + if (flags & DF_CURSOR_USEFUL) + draw_rect_corners(dr, ox, oy, CURSOR_RADIUS+1, nc); + } + + if (flags & DF_HIGHLIGHT_1) { + nc = COL_HIGHLIGHT_1; + } else if (flags & DF_HIGHLIGHT_2) { + nc = COL_HIGHLIGHT_2; + } + + sprintf(str, "%d", state->numbers->numbers[y*w+x]); + draw_text(dr, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2, + ALIGN_HCENTRE | ALIGN_VCENTRE, nc, str); + + draw_update(dr, cx, cy, TILESIZE, TILESIZE); + unclip(dr); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int n = state->params.n, w = state->w, h = state->h, wh = w*h; + int x, y, i; + unsigned char *used; + + if (!ds->started) { + int pw, ph; + game_compute_size(&state->params, TILESIZE, &pw, &ph); + draw_rect(dr, 0, 0, pw, ph, COL_BACKGROUND); + draw_update(dr, 0, 0, pw, ph); + ds->started = TRUE; + } + + /* + * See how many dominoes of each type there are, so we can + * highlight clashes in red. + */ + used = snewn(TRI(n+1), unsigned char); + memset(used, 0, TRI(n+1)); + for (i = 0; i < wh; i++) + if (state->grid[i] > i) { + int n1, n2, di; + + n1 = state->numbers->numbers[i]; + n2 = state->numbers->numbers[state->grid[i]]; + + di = DINDEX(n1, n2); + assert(di >= 0 && di < TRI(n+1)); + + if (used[di] < 2) + used[di]++; + } + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int n = y*w+x; + int n1, n2, di; + unsigned long c; + + if (state->grid[n] == n-1) + c = TYPE_R; + else if (state->grid[n] == n+1) + c = TYPE_L; + else if (state->grid[n] == n-w) + c = TYPE_B; + else if (state->grid[n] == n+w) + c = TYPE_T; + else + c = TYPE_BLANK; + + n1 = state->numbers->numbers[n]; + if (c != TYPE_BLANK) { + n2 = state->numbers->numbers[state->grid[n]]; + di = DINDEX(n1, n2); + if (used[di] > 1) + c |= DF_CLASH; /* highlight a clash */ + } else { + c |= state->edges[n]; + } + + if (n1 == ui->highlight_1) + c |= DF_HIGHLIGHT_1; + if (n1 == ui->highlight_2) + c |= DF_HIGHLIGHT_2; + + if (flashtime != 0) + c |= DF_FLASH; /* we're flashing */ + + if (ui->cur_visible) { + unsigned curx = (unsigned)(ui->cur_x - (2*x-1)); + unsigned cury = (unsigned)(ui->cur_y - (2*y-1)); + if (curx < 3 && cury < 3) { + c |= (DF_CURSOR | + (curx * DF_CURSOR_XBASE) | + (cury * DF_CURSOR_YBASE)); + if ((ui->cur_x ^ ui->cur_y) & 1) + c |= DF_CURSOR_USEFUL; + } + } + + if (ds->visible[n] != c) { + draw_tile(dr, ds, state, x, y, c, + ui->highlight_1, ui->highlight_2); + ds->visible[n] = c; + } + } + + sfree(used); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + { + ui->highlight_1 = ui->highlight_2 = -1; + return FLASH_TIME; + } + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->w, h = state->h; + int c, x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND); + c = print_mono_colour(dr, 0); assert(c == COL_TEXT); + c = print_mono_colour(dr, 0); assert(c == COL_DOMINO); + c = print_mono_colour(dr, 0); assert(c == COL_DOMINOCLASH); + c = print_mono_colour(dr, 1); assert(c == COL_DOMINOTEXT); + c = print_mono_colour(dr, 0); assert(c == COL_EDGE); + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int n = y*w+x; + unsigned long c; + + if (state->grid[n] == n-1) + c = TYPE_R; + else if (state->grid[n] == n+1) + c = TYPE_L; + else if (state->grid[n] == n-w) + c = TYPE_B; + else if (state->grid[n] == n+w) + c = TYPE_T; + else + c = TYPE_BLANK; + + draw_tile(dr, ds, state, x, y, c, -1, -1); + } +} + +#ifdef COMBINED +#define thegame dominosa +#endif + +const struct game thegame = { + "Dominosa", "games.dominosa", "dominosa", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 :set textwidth=80: */ + diff --git a/apps/plugins/puzzles/drawing.c b/apps/plugins/puzzles/drawing.c new file mode 100644 index 0000000000..8bf58d5530 --- /dev/null +++ b/apps/plugins/puzzles/drawing.c @@ -0,0 +1,351 @@ +/* + * drawing.c: Intermediary between the drawing interface as + * presented to the back end, and that implemented by the front + * end. + * + * Mostly just looks up calls in a vtable and passes them through + * unchanged. However, on the printing side it tracks print colours + * so the front end API doesn't have to. + * + * FIXME: + * + * - I'd _like_ to do automatic draw_updates, but it's a pain for + * draw_text in particular. I'd have to invent a front end API + * which retrieved the text bounds. + * + that might allow me to do the alignment centrally as well? + * * perhaps not, because PS can't return this information, + * so there would have to be a special case for it. + * + however, that at least doesn't stand in the way of using + * the text bounds for draw_update, because PS doesn't need + * draw_update since it's printing-only. Any _interactive_ + * drawing API couldn't get away with refusing to tell you + * what parts of the screen a text draw had covered, because + * you would inevitably need to erase it later on. + */ + +#include +#include +#include +#include "rbassert.h" +#include + +#include "puzzles.h" + +struct print_colour { + int hatch; + int hatch_when; /* 0=never 1=only-in-b&w 2=always */ + float r, g, b; + float grey; +}; + +struct drawing { + const drawing_api *api; + void *handle; + struct print_colour *colours; + int ncolours, coloursize; + float scale; + /* `me' is only used in status_bar(), so print-oriented instances of + * this may set it to NULL. */ + midend *me; + char *laststatus; +}; + +drawing *drawing_new(const drawing_api *api, midend *me, void *handle) +{ + drawing *dr = snew(drawing); + dr->api = api; + dr->handle = handle; + dr->colours = NULL; + dr->ncolours = dr->coloursize = 0; + dr->scale = 1.0F; + dr->me = me; + dr->laststatus = NULL; + return dr; +} + +void drawing_free(drawing *dr) +{ + sfree(dr->laststatus); + sfree(dr->colours); + sfree(dr); +} + +void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text) +{ + dr->api->draw_text(dr->handle, x, y, fonttype, fontsize, align, + colour, text); +} + +void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) +{ + dr->api->draw_rect(dr->handle, x, y, w, h, colour); +} + +void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) +{ + dr->api->draw_line(dr->handle, x1, y1, x2, y2, colour); +} + +void draw_thick_line(drawing *dr, float thickness, + float x1, float y1, float x2, float y2, int colour) +{ + if (dr->api->draw_thick_line) { + dr->api->draw_thick_line(dr->handle, thickness, + x1, y1, x2, y2, colour); + } else { + /* We'll fake it up with a filled polygon. The tweak to the + * thickness empirically compensates for rounding errors, because + * polygon rendering uses integer coordinates. + */ + float len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); + float tvhatx = (x2 - x1)/len * (thickness/2 - 0.2); + float tvhaty = (y2 - y1)/len * (thickness/2 - 0.2); + int p[8]; + + p[0] = x1 - tvhaty; + p[1] = y1 + tvhatx; + p[2] = x2 - tvhaty; + p[3] = y2 + tvhatx; + p[4] = x2 + tvhaty; + p[5] = y2 - tvhatx; + p[6] = x1 + tvhaty; + p[7] = y1 - tvhatx; + dr->api->draw_polygon(dr->handle, p, 4, colour, colour); + } +} + +void draw_polygon(drawing *dr, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + dr->api->draw_polygon(dr->handle, coords, npoints, fillcolour, + outlinecolour); +} + +void draw_circle(drawing *dr, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + dr->api->draw_circle(dr->handle, cx, cy, radius, fillcolour, + outlinecolour); +} + +void draw_update(drawing *dr, int x, int y, int w, int h) +{ + if (dr->api->draw_update) + dr->api->draw_update(dr->handle, x, y, w, h); +} + +void clip(drawing *dr, int x, int y, int w, int h) +{ + dr->api->clip(dr->handle, x, y, w, h); +} + +void unclip(drawing *dr) +{ + dr->api->unclip(dr->handle); +} + +void start_draw(drawing *dr) +{ + dr->api->start_draw(dr->handle); +} + +void end_draw(drawing *dr) +{ + dr->api->end_draw(dr->handle); +} + +char *text_fallback(drawing *dr, const char *const *strings, int nstrings) +{ + int i; + + /* + * If the drawing implementation provides one of these, use it. + */ + if (dr && dr->api->text_fallback) + return dr->api->text_fallback(dr->handle, strings, nstrings); + + /* + * Otherwise, do the simple thing and just pick the first string + * that fits in plain ASCII. It will then need no translation + * out of UTF-8. + */ + for (i = 0; i < nstrings; i++) { + const char *p; + + for (p = strings[i]; *p; p++) + if (*p & 0x80) + break; + if (!*p) + return dupstr(strings[i]); + } + + /* + * The caller was responsible for making sure _some_ string in + * the list was in plain ASCII. + */ + assert(!"Should never get here"); + return NULL; /* placate optimiser */ +} + +void status_bar(drawing *dr, char *text) +{ + char *rewritten; + + if (!dr->api->status_bar) + return; + + assert(dr->me); + + rewritten = midend_rewrite_statusbar(dr->me, text); + if (!dr->laststatus || strcmp(rewritten, dr->laststatus)) { + dr->api->status_bar(dr->handle, rewritten); + sfree(dr->laststatus); + dr->laststatus = rewritten; + } else { + sfree(rewritten); + } +} + +blitter *blitter_new(drawing *dr, int w, int h) +{ + return dr->api->blitter_new(dr->handle, w, h); +} + +void blitter_free(drawing *dr, blitter *bl) +{ + dr->api->blitter_free(dr->handle, bl); +} + +void blitter_save(drawing *dr, blitter *bl, int x, int y) +{ + dr->api->blitter_save(dr->handle, bl, x, y); +} + +void blitter_load(drawing *dr, blitter *bl, int x, int y) +{ + dr->api->blitter_load(dr->handle, bl, x, y); +} + +void print_begin_doc(drawing *dr, int pages) +{ + dr->api->begin_doc(dr->handle, pages); +} + +void print_begin_page(drawing *dr, int number) +{ + dr->api->begin_page(dr->handle, number); +} + +void print_begin_puzzle(drawing *dr, float xm, float xc, + float ym, float yc, int pw, int ph, float wmm, + float scale) +{ + dr->scale = scale; + dr->ncolours = 0; + dr->api->begin_puzzle(dr->handle, xm, xc, ym, yc, pw, ph, wmm); +} + +void print_end_puzzle(drawing *dr) +{ + dr->api->end_puzzle(dr->handle); + dr->scale = 1.0F; +} + +void print_end_page(drawing *dr, int number) +{ + dr->api->end_page(dr->handle, number); +} + +void print_end_doc(drawing *dr) +{ + dr->api->end_doc(dr->handle); +} + +void print_get_colour(drawing *dr, int colour, int printing_in_colour, + int *hatch, float *r, float *g, float *b) +{ + assert(colour >= 0 && colour < dr->ncolours); + if (dr->colours[colour].hatch_when == 2 || + (dr->colours[colour].hatch_when == 1 && !printing_in_colour)) { + *hatch = dr->colours[colour].hatch; + } else { + *hatch = -1; + if (printing_in_colour) { + *r = dr->colours[colour].r; + *g = dr->colours[colour].g; + *b = dr->colours[colour].b; + } else { + *r = *g = *b = dr->colours[colour].grey; + } + } +} + +static int print_generic_colour(drawing *dr, float r, float g, float b, + float grey, int hatch, int hatch_when) +{ + if (dr->ncolours >= dr->coloursize) { + dr->coloursize = dr->ncolours + 16; + dr->colours = sresize(dr->colours, dr->coloursize, + struct print_colour); + } + dr->colours[dr->ncolours].hatch = hatch; + dr->colours[dr->ncolours].hatch_when = hatch_when; + dr->colours[dr->ncolours].r = r; + dr->colours[dr->ncolours].g = g; + dr->colours[dr->ncolours].b = b; + dr->colours[dr->ncolours].grey = grey; + return dr->ncolours++; +} + +int print_mono_colour(drawing *dr, int grey) +{ + return print_generic_colour(dr, grey, grey, grey, grey, -1, 0); +} + +int print_grey_colour(drawing *dr, float grey) +{ + return print_generic_colour(dr, grey, grey, grey, grey, -1, 0); +} + +int print_hatched_colour(drawing *dr, int hatch) +{ + return print_generic_colour(dr, 0, 0, 0, 0, hatch, 2); +} + +int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey) +{ + return print_generic_colour(dr, r, g, b, grey, -1, 0); +} + +int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey) +{ + return print_generic_colour(dr, r, g, b, grey, -1, 0); +} + +int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch) +{ + return print_generic_colour(dr, r, g, b, 0, hatch, 1); +} + +void print_line_width(drawing *dr, int width) +{ + /* + * I don't think it's entirely sensible to have line widths be + * entirely relative to the puzzle size; there is a point + * beyond which lines are just _stupidly_ thick. On the other + * hand, absolute line widths aren't particularly nice either + * because they start to feel a bit feeble at really large + * scales. + * + * My experimental answer is to scale line widths as the + * _square root_ of the main puzzle scale. Double the puzzle + * size, and the line width multiplies by 1.4. + */ + dr->api->line_width(dr->handle, (float)sqrt(dr->scale) * width); +} + +void print_line_dotted(drawing *dr, int dotted) +{ + dr->api->line_dotted(dr->handle, dotted); +} diff --git a/apps/plugins/puzzles/dsf.c b/apps/plugins/puzzles/dsf.c new file mode 100644 index 0000000000..1a2fa8c4be --- /dev/null +++ b/apps/plugins/puzzles/dsf.c @@ -0,0 +1,192 @@ +/* + * dsf.c: some functions to handle a disjoint set forest, + * which is a data structure useful in any solver which has to + * worry about avoiding closed loops. + */ + +#include "rbassert.h" +#include + +#include "puzzles.h" + +/*void print_dsf(int *dsf, int size) +{ + int *printed_elements = snewn(size, int); + int *equal_elements = snewn(size, int); + int *inverse_elements = snewn(size, int); + int printed_count = 0, equal_count, inverse_count; + int i, n, inverse; + + memset(printed_elements, -1, sizeof(int) * size); + + while (1) { + equal_count = 0; + inverse_count = 0; + for (i = 0; i < size; ++i) { + if (!memchr(printed_elements, i, sizeof(int) * size)) + break; + } + if (i == size) + goto done; + + i = dsf_canonify(dsf, i); + + for (n = 0; n < size; ++n) { + if (edsf_canonify(dsf, n, &inverse) == i) { + if (inverse) + inverse_elements[inverse_count++] = n; + else + equal_elements[equal_count++] = n; + } + } + + for (n = 0; n < equal_count; ++n) { + fprintf(stderr, "%d ", equal_elements[n]); + printed_elements[printed_count++] = equal_elements[n]; + } + if (inverse_count) { + fprintf(stderr, "!= "); + for (n = 0; n < inverse_count; ++n) { + fprintf(stderr, "%d ", inverse_elements[n]); + printed_elements[printed_count++] = inverse_elements[n]; + } + } + fprintf(stderr, "\n"); + } +done: + + sfree(printed_elements); + sfree(equal_elements); + sfree(inverse_elements); +}*/ + +void dsf_init(int *dsf, int size) +{ + int i; + + for (i = 0; i < size; i++) dsf[i] = 6; + /* Bottom bit of each element of this array stores whether that + * element is opposite to its parent, which starts off as + * false. Second bit of each element stores whether that element + * is the root of its tree or not. If it's not the root, the + * remaining 30 bits are the parent, otherwise the remaining 30 + * bits are the number of elements in the tree. */ +} + +int *snew_dsf(int size) +{ + int *ret; + + ret = snewn(size, int); + dsf_init(ret, size); + + /*print_dsf(ret, size); */ + + return ret; +} + +int dsf_canonify(int *dsf, int index) +{ + return edsf_canonify(dsf, index, NULL); +} + +void dsf_merge(int *dsf, int v1, int v2) +{ + edsf_merge(dsf, v1, v2, FALSE); +} + +int dsf_size(int *dsf, int index) { + return dsf[dsf_canonify(dsf, index)] >> 2; +} + +int edsf_canonify(int *dsf, int index, int *inverse_return) +{ + int start_index = index, canonical_index; + int inverse = 0; + +/* fprintf(stderr, "dsf = %p\n", dsf); */ +/* fprintf(stderr, "Canonify %2d\n", index); */ + + assert(index >= 0); + + /* Find the index of the canonical element of the 'equivalence class' of + * which start_index is a member, and figure out whether start_index is the + * same as or inverse to that. */ + while ((dsf[index] & 2) == 0) { + inverse ^= (dsf[index] & 1); + index = dsf[index] >> 2; +/* fprintf(stderr, "index = %2d, ", index); */ +/* fprintf(stderr, "inverse = %d\n", inverse); */ + } + canonical_index = index; + + if (inverse_return) + *inverse_return = inverse; + + /* Update every member of this 'equivalence class' to point directly at the + * canonical member. */ + index = start_index; + while (index != canonical_index) { + int nextindex = dsf[index] >> 2; + int nextinverse = inverse ^ (dsf[index] & 1); + dsf[index] = (canonical_index << 2) | inverse; + inverse = nextinverse; + index = nextindex; + } + + assert(inverse == 0); + +/* fprintf(stderr, "Return %2d\n", index); */ + + return index; +} + +void edsf_merge(int *dsf, int v1, int v2, int inverse) +{ + int i1, i2; + +/* fprintf(stderr, "dsf = %p\n", dsf); */ +/* fprintf(stderr, "Merge [%2d,%2d], %d\n", v1, v2, inverse); */ + + v1 = edsf_canonify(dsf, v1, &i1); + assert(dsf[v1] & 2); + inverse ^= i1; + v2 = edsf_canonify(dsf, v2, &i2); + assert(dsf[v2] & 2); + inverse ^= i2; + +/* fprintf(stderr, "Doing [%2d,%2d], %d\n", v1, v2, inverse); */ + + if (v1 == v2) + assert(!inverse); + else { + assert(inverse == 0 || inverse == 1); + /* + * We always make the smaller of v1 and v2 the new canonical + * element. This ensures that the canonical element of any + * class in this structure is always the first element in + * it. 'Keen' depends critically on this property. + * + * (Jonas Koelker previously had this code choosing which + * way round to connect the trees by examining the sizes of + * the classes being merged, so that the root of the + * larger-sized class became the new root. This gives better + * asymptotic performance, but I've changed it to do it this + * way because I like having a deterministic canonical + * element.) + */ + if (v1 > v2) { + int v3 = v1; + v1 = v2; + v2 = v3; + } + dsf[v1] += (dsf[v2] >> 2) << 2; + dsf[v2] = (v1 << 2) | !!inverse; + } + + v2 = edsf_canonify(dsf, v2, &i2); + assert(v2 == v1); + assert(i2 == inverse); + +/* fprintf(stderr, "dsf[%2d] = %2d\n", v2, dsf[v2]); */ +} diff --git a/apps/plugins/puzzles/emcc.c b/apps/plugins/puzzles/emcc.c new file mode 100644 index 0000000000..5ffcb0b793 --- /dev/null +++ b/apps/plugins/puzzles/emcc.c @@ -0,0 +1,867 @@ +/* + * emcc.c: the C component of an Emscripten-based web/Javascript front + * end for Puzzles. + * + * The Javascript parts of this system live in emcclib.js and + * emccpre.js. It also depends on being run in the context of a web + * page containing an appropriate collection of bits and pieces (a + * canvas, some buttons and links etc), which is generated for each + * puzzle by the script html/jspage.pl. + */ + +/* + * Further thoughts on possible enhancements: + * + * - I think it might be feasible to have these JS puzzles permit + * loading and saving games in disk files. Saving would be done by + * constructing a data: URI encapsulating the save file, and then + * telling the browser to visit that URI with the effect that it + * would naturally pop up a 'where would you like to save this' + * dialog box. Loading, more or less similarly, might be feasible + * by using the DOM File API to ask the user to select a file and + * permit us to see its contents. + * + * - I should think about whether these webified puzzles can support + * touchscreen-based tablet browsers (assuming there are any that + * can cope with the reasonably modern JS and run it fast enough to + * be worthwhile). + * + * - think about making use of localStorage. It might be useful to + * let the user save games into there as an alternative to disk + * files - disk files are all very well for getting the save right + * out of your browser to (e.g.) email to me as a bug report, but + * for just resuming a game you were in the middle of, you'd + * probably rather have a nice simple 'quick save' and 'quick load' + * button pair. Also, that might be a useful place to store + * preferences, if I ever get round to writing a preferences UI. + * + * - some CSS to make the button bar and configuration dialogs a + * little less ugly would probably not go amiss. + * + * - this is a downright silly idea, but it does occur to me that if + * I were to write a PDF output driver for the Puzzles printing + * API, then I might be able to implement a sort of 'printing' + * feature in this front end, using data: URIs again. (Ask the user + * exactly what they want printed, then construct an appropriate + * PDF and embed it in a gigantic data: URI. Then they can print + * that using whatever they normally use to print PDFs!) + */ + +#include "rbassert.h" +#include +#include +#include + +#include "puzzles.h" + +/* + * Extern references to Javascript functions provided in emcclib.js. + */ +extern void js_debug(const char *); +extern void js_error_box(const char *message); +extern void js_remove_type_dropdown(void); +extern void js_remove_solve_button(void); +extern void js_add_preset(const char *name); +extern int js_get_selected_preset(void); +extern void js_select_preset(int n); +extern void js_get_date_64(unsigned *p); +extern void js_update_permalinks(const char *desc, const char *seed); +extern void js_enable_undo_redo(int undo, int redo); +extern void js_activate_timer(); +extern void js_deactivate_timer(); +extern void js_canvas_start_draw(void); +extern void js_canvas_draw_update(int x, int y, int w, int h); +extern void js_canvas_end_draw(void); +extern void js_canvas_draw_rect(int x, int y, int w, int h, + const char *colour); +extern void js_canvas_clip_rect(int x, int y, int w, int h); +extern void js_canvas_unclip(void); +extern void js_canvas_draw_line(float x1, float y1, float x2, float y2, + int width, const char *colour); +extern void js_canvas_draw_poly(int *points, int npoints, + const char *fillcolour, + const char *outlinecolour); +extern void js_canvas_draw_circle(int x, int y, int r, + const char *fillcolour, + const char *outlinecolour); +extern int js_canvas_find_font_midpoint(int height, const char *fontptr); +extern void js_canvas_draw_text(int x, int y, int halign, + const char *colptr, const char *fontptr, + const char *text); +extern int js_canvas_new_blitter(int w, int h); +extern void js_canvas_free_blitter(int id); +extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); +extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h); +extern void js_canvas_make_statusbar(void); +extern void js_canvas_set_statusbar(const char *text); +extern void js_canvas_set_size(int w, int h); + +extern void js_dialog_init(const char *title); +extern void js_dialog_string(int i, const char *title, const char *initvalue); +extern void js_dialog_choices(int i, const char *title, const char *choicelist, + int initvalue); +extern void js_dialog_boolean(int i, const char *title, int initvalue); +extern void js_dialog_launch(void); +extern void js_dialog_cleanup(void); +extern void js_focus_canvas(void); + +/* + * Call JS to get the date, and use that to initialise our random + * number generator to invent the first game seed. + */ +void get_random_seed(void **randseed, int *randseedsize) +{ + unsigned *ret = snewn(2, unsigned); + js_get_date_64(ret); + *randseed = ret; + *randseedsize = 2*sizeof(unsigned); +} + +/* + * Fatal error, called in cases of complete despair such as when + * malloc() has returned NULL. + */ +void fatal(char *fmt, ...) +{ + char buf[512]; + va_list ap; + + strcpy(buf, "puzzle fatal error: "); + + va_start(ap, fmt); + vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap); + va_end(ap); + + js_error_box(buf); +} + +void debug_printf(char *fmt, ...) +{ + char buf[512]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + js_debug(buf); +} + +/* + * Helper function that makes it easy to test strings that might be + * NULL. + */ +int strnullcmp(const char *a, const char *b) +{ + if (a == NULL || b == NULL) + return a != NULL ? +1 : b != NULL ? -1 : 0; + return strcmp(a, b); +} + +/* + * HTMLish names for the colours allocated by the puzzle. + */ +char **colour_strings; +int ncolours; + +/* + * The global midend object. + */ +midend *me; + +/* ---------------------------------------------------------------------- + * Timing functions. + */ +int timer_active = FALSE; +void deactivate_timer(frontend *fe) +{ + js_deactivate_timer(); + timer_active = FALSE; +} +void activate_timer(frontend *fe) +{ + if (!timer_active) { + js_activate_timer(); + timer_active = TRUE; + } +} +void timer_callback(double tplus) +{ + if (timer_active) + midend_timer(me, tplus); +} + +/* ---------------------------------------------------------------------- + * Helper functions to resize the canvas, and variables to remember + * its size for other functions (e.g. trimming blitter rectangles). + */ +static int canvas_w, canvas_h; + +/* Called when we resize as a result of changing puzzle settings */ +static void resize(void) +{ + int w, h; + w = h = INT_MAX; + midend_size(me, &w, &h, FALSE); + js_canvas_set_size(w, h); + canvas_w = w; + canvas_h = h; +} + +/* Called from JS when the user uses the resize handle */ +void resize_puzzle(int w, int h) +{ + midend_size(me, &w, &h, TRUE); + if (canvas_w != w || canvas_h != h) { + js_canvas_set_size(w, h); + canvas_w = w; + canvas_h = h; + midend_force_redraw(me); + } +} + +/* Called from JS when the user uses the restore button */ +void restore_puzzle_size(int w, int h) +{ + midend_reset_tilesize(me); + resize(); + midend_force_redraw(me); +} + +/* + * HTML doesn't give us a default frontend colour of its own, so we + * just make up a lightish grey ourselves. + */ +void frontend_default_colour(frontend *fe, float *output) +{ + output[0] = output[1] = output[2] = 0.9F; +} + +/* + * Helper function called from all over the place to ensure the undo + * and redo buttons get properly enabled and disabled after every move + * or undo or new-game event. + */ +static void update_undo_redo(void) +{ + js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me)); +} + +/* + * Mouse event handlers called from JS. + */ +void mousedown(int x, int y, int button) +{ + button = (button == 0 ? LEFT_BUTTON : + button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON); + midend_process_key(me, x, y, button); + update_undo_redo(); +} + +void mouseup(int x, int y, int button) +{ + button = (button == 0 ? LEFT_RELEASE : + button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE); + midend_process_key(me, x, y, button); + update_undo_redo(); +} + +void mousemove(int x, int y, int buttons) +{ + int button = (buttons & 2 ? MIDDLE_DRAG : + buttons & 4 ? RIGHT_DRAG : LEFT_DRAG); + midend_process_key(me, x, y, button); + update_undo_redo(); +} + +/* + * Keyboard handler called from JS. + */ +void key(int keycode, int charcode, const char *key, const char *chr, + int shift, int ctrl) +{ + int keyevent = -1; + + if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Del") || + keycode == 8 || keycode == 46) { + keyevent = 127; /* Backspace / Delete */ + } else if (!strnullcmp(key, "Enter") || keycode == 13) { + keyevent = 13; /* return */ + } else if (!strnullcmp(key, "Left") || keycode == 37) { + keyevent = CURSOR_LEFT; + } else if (!strnullcmp(key, "Up") || keycode == 38) { + keyevent = CURSOR_UP; + } else if (!strnullcmp(key, "Right") || keycode == 39) { + keyevent = CURSOR_RIGHT; + } else if (!strnullcmp(key, "Down") || keycode == 40) { + keyevent = CURSOR_DOWN; + } else if (!strnullcmp(key, "End") || keycode == 35) { + /* + * We interpret Home, End, PgUp and PgDn as numeric keypad + * controls regardless of whether they're the ones on the + * numeric keypad (since we can't tell). The effect of + * this should only be that the non-numeric-pad versions + * of those keys generate directions in 8-way movement + * puzzles like Cube and Inertia. + */ + keyevent = MOD_NUM_KEYPAD | '1'; + } else if (!strnullcmp(key, "PageDown") || keycode==34) { + keyevent = MOD_NUM_KEYPAD | '3'; + } else if (!strnullcmp(key, "Home") || keycode==36) { + keyevent = MOD_NUM_KEYPAD | '7'; + } else if (!strnullcmp(key, "PageUp") || keycode==33) { + keyevent = MOD_NUM_KEYPAD | '9'; + } else if (chr && chr[0] && !chr[1]) { + keyevent = chr[0] & 0xFF; + } else if (keycode >= 96 && keycode < 106) { + keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96); + } else if (keycode >= 65 && keycode <= 90) { + keyevent = keycode + (shift ? 0 : 32); + } else if (keycode >= 48 && keycode <= 57) { + keyevent = keycode; + } else if (keycode == 32) { /* space / CURSOR_SELECT2 */ + keyevent = keycode; + } + + if (keyevent >= 0) { + if (shift && keyevent >= 0x100) + keyevent |= MOD_SHFT; + + if (ctrl) { + if (keyevent >= 0x100) + keyevent |= MOD_CTRL; + else + keyevent &= 0x1F; + } + + midend_process_key(me, 0, 0, keyevent); + update_undo_redo(); + } +} + +/* + * Helper function called from several places to update the permalinks + * whenever a new game is created. + */ +static void update_permalinks(void) +{ + char *desc, *seed; + desc = midend_get_game_id(me); + seed = midend_get_random_seed(me); + js_update_permalinks(desc, seed); + sfree(desc); + sfree(seed); +} + +/* + * Callback from the midend when the game ids change, so we can update + * the permalinks. + */ +static void ids_changed(void *ignored) +{ + update_permalinks(); +} + +/* ---------------------------------------------------------------------- + * Implementation of the drawing API by calling Javascript canvas + * drawing functions. (Well, half of it; the other half is on the JS + * side.) + */ +static void js_start_draw(void *handle) +{ + js_canvas_start_draw(); +} + +static void js_clip(void *handle, int x, int y, int w, int h) +{ + js_canvas_clip_rect(x, y, w, h); +} + +static void js_unclip(void *handle) +{ + js_canvas_unclip(); +} + +static void js_draw_text(void *handle, int x, int y, int fonttype, + int fontsize, int align, int colour, char *text) +{ + char fontstyle[80]; + int halign; + + sprintf(fontstyle, "%dpx %s", fontsize, + fonttype == FONT_FIXED ? "monospace" : "sans-serif"); + + if (align & ALIGN_VCENTRE) + y += js_canvas_find_font_midpoint(fontsize, fontstyle); + + if (align & ALIGN_HCENTRE) + halign = 1; + else if (align & ALIGN_HRIGHT) + halign = 2; + else + halign = 0; + + js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text); +} + +static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour) +{ + js_canvas_draw_rect(x, y, w, h, colour_strings[colour]); +} + +static void js_draw_line(void *handle, int x1, int y1, int x2, int y2, + int colour) +{ + js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]); +} + +static void js_draw_thick_line(void *handle, float thickness, + float x1, float y1, float x2, float y2, + int colour) +{ + js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]); +} + +static void js_draw_poly(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + js_canvas_draw_poly(coords, npoints, + fillcolour >= 0 ? colour_strings[fillcolour] : NULL, + colour_strings[outlinecolour]); +} + +static void js_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + js_canvas_draw_circle(cx, cy, radius, + fillcolour >= 0 ? colour_strings[fillcolour] : NULL, + colour_strings[outlinecolour]); +} + +struct blitter { + int id; /* allocated on the js side */ + int w, h; /* easier to retain here */ +}; + +static blitter *js_blitter_new(void *handle, int w, int h) +{ + blitter *bl = snew(blitter); + bl->w = w; + bl->h = h; + bl->id = js_canvas_new_blitter(w, h); + return bl; +} + +static void js_blitter_free(void *handle, blitter *bl) +{ + js_canvas_free_blitter(bl->id); + sfree(bl); +} + +static void trim_rect(int *x, int *y, int *w, int *h) +{ + int x0, x1, y0, y1; + + /* + * Reduce the size of the copied rectangle to stop it going + * outside the bounds of the canvas. + */ + + /* Transform from x,y,w,h form into coordinates of all edges */ + x0 = *x; + y0 = *y; + x1 = *x + *w; + y1 = *y + *h; + + /* Clip each coordinate at both extremes of the canvas */ + x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0); + x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1); + y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0); + y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1); + + /* Transform back into x,y,w,h to return */ + *x = x0; + *y = y0; + *w = x1 - x0; + *h = y1 - y0; +} + +static void js_blitter_save(void *handle, blitter *bl, int x, int y) +{ + int w = bl->w, h = bl->h; + trim_rect(&x, &y, &w, &h); + if (w > 0 && h > 0) + js_canvas_copy_to_blitter(bl->id, x, y, w, h); +} + +static void js_blitter_load(void *handle, blitter *bl, int x, int y) +{ + int w = bl->w, h = bl->h; + trim_rect(&x, &y, &w, &h); + if (w > 0 && h > 0) + js_canvas_copy_from_blitter(bl->id, x, y, w, h); +} + +static void js_draw_update(void *handle, int x, int y, int w, int h) +{ + trim_rect(&x, &y, &w, &h); + if (w > 0 && h > 0) + js_canvas_draw_update(x, y, w, h); +} + +static void js_end_draw(void *handle) +{ + js_canvas_end_draw(); +} + +static void js_status_bar(void *handle, char *text) +{ + js_canvas_set_statusbar(text); +} + +static char *js_text_fallback(void *handle, const char *const *strings, + int nstrings) +{ + return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */ +} + +const struct drawing_api js_drawing = { + js_draw_text, + js_draw_rect, + js_draw_line, + js_draw_poly, + js_draw_circle, + js_draw_update, + js_clip, + js_unclip, + js_start_draw, + js_end_draw, + js_status_bar, + js_blitter_new, + js_blitter_free, + js_blitter_save, + js_blitter_load, + NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ + NULL, NULL, /* line_width, line_dotted */ + js_text_fallback, + js_draw_thick_line, +}; + +/* ---------------------------------------------------------------------- + * Presets and game-configuration dialog support. + */ +static game_params **presets; +static int npresets; +int have_presets_dropdown; + +void select_appropriate_preset(void) +{ + if (have_presets_dropdown) { + int preset = midend_which_preset(me); + js_select_preset(preset < 0 ? -1 : preset); + } +} + +static config_item *cfg = NULL; +static int cfg_which; + +/* + * Set up a dialog box. This is pretty easy on the C side; most of the + * work is done in JS. + */ +static void cfg_start(int which) +{ + char *title; + int i; + + cfg = midend_get_config(me, which, &title); + cfg_which = which; + + js_dialog_init(title); + sfree(title); + + for (i = 0; cfg[i].type != C_END; i++) { + switch (cfg[i].type) { + case C_STRING: + js_dialog_string(i, cfg[i].name, cfg[i].sval); + break; + case C_BOOLEAN: + js_dialog_boolean(i, cfg[i].name, cfg[i].ival); + break; + case C_CHOICES: + js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival); + break; + } + } + + js_dialog_launch(); +} + +/* + * Callbacks from JS when the OK button is clicked, to return the + * final state of each control. + */ +void dlg_return_sval(int index, const char *val) +{ + sfree(cfg[index].sval); + cfg[index].sval = dupstr(val); +} +void dlg_return_ival(int index, int val) +{ + cfg[index].ival = val; +} + +/* + * Called when the user clicks OK or Cancel. use_results will be TRUE + * or FALSE respectively, in those cases. We terminate the dialog box, + * unless the user selected an invalid combination of parameters. + */ +static void cfg_end(int use_results) +{ + if (use_results) { + /* + * User hit OK. + */ + char *err = midend_set_config(me, cfg_which, cfg); + + if (err) { + /* + * The settings were unacceptable, so leave the config box + * open for the user to adjust them and try again. + */ + js_error_box(err); + } else { + /* + * New settings are fine; start a new game and close the + * dialog. + */ + select_appropriate_preset(); + midend_new_game(me); + resize(); + midend_redraw(me); + free_cfg(cfg); + js_dialog_cleanup(); + } + } else { + /* + * User hit Cancel. Close the dialog, but also we must still + * reselect the right element of the dropdown list. + * + * (Because: imagine you have a preset selected, and then you + * select Custom from the list, but change your mind and hit + * Esc. The Custom option will now still be selected in the + * list, whereas obviously it should show the preset you still + * _actually_ have selected. Worse still, it'll be the visible + * rather than invisible Custom option - see the comment in + * js_add_preset in emcclib.js - so you won't even be able to + * select Custom without a faffy workaround.) + */ + select_appropriate_preset(); + + free_cfg(cfg); + js_dialog_cleanup(); + } +} + +/* ---------------------------------------------------------------------- + * Called from JS when a command is given to the puzzle by clicking a + * button or control of some sort. + */ +void command(int n) +{ + switch (n) { + case 0: /* specific game ID */ + cfg_start(CFG_DESC); + break; + case 1: /* random game seed */ + cfg_start(CFG_SEED); + break; + case 2: /* game parameter dropdown changed */ + { + int i = js_get_selected_preset(); + if (i < 0) { + /* + * The user selected 'Custom', so launch the config + * box. + */ + if (thegame.can_configure) /* (double-check just in case) */ + cfg_start(CFG_SETTINGS); + } else { + /* + * The user selected a preset, so just switch straight + * to that. + */ + assert(i < npresets); + midend_set_params(me, presets[i]); + midend_new_game(me); + resize(); + midend_redraw(me); + update_undo_redo(); + js_focus_canvas(); + select_appropriate_preset(); /* sort out Custom/Customise */ + } + } + break; + case 3: /* OK clicked in a config box */ + cfg_end(TRUE); + update_undo_redo(); + break; + case 4: /* Cancel clicked in a config box */ + cfg_end(FALSE); + update_undo_redo(); + break; + case 5: /* New Game */ + midend_process_key(me, 0, 0, 'n'); + update_undo_redo(); + js_focus_canvas(); + break; + case 6: /* Restart */ + midend_restart_game(me); + update_undo_redo(); + js_focus_canvas(); + break; + case 7: /* Undo */ + midend_process_key(me, 0, 0, 'u'); + update_undo_redo(); + js_focus_canvas(); + break; + case 8: /* Redo */ + midend_process_key(me, 0, 0, 'r'); + update_undo_redo(); + js_focus_canvas(); + break; + case 9: /* Solve */ + if (thegame.can_solve) { + char *msg = midend_solve(me); + if (msg) + js_error_box(msg); + } + update_undo_redo(); + js_focus_canvas(); + break; + } +} + +/* ---------------------------------------------------------------------- + * Setup function called at page load time. It's called main() because + * that's the most convenient thing in Emscripten, but it's not main() + * in the usual sense of bounding the program's entire execution. + * Instead, this function returns once the initial puzzle is set up + * and working, and everything thereafter happens by means of JS event + * handlers sending us callbacks. + */ +int main(int argc, char **argv) +{ + char *param_err; + float *colours; + int i; + + /* + * Instantiate a midend. + */ + me = midend_new(NULL, &thegame, &js_drawing, NULL); + + /* + * Chuck in the HTML fragment ID if we have one (trimming the + * leading # off the front first). If that's invalid, we retain + * the error message and will display it at the end, after setting + * up a random puzzle as usual. + */ + if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0') + param_err = midend_game_id(me, argv[1] + 1); + else + param_err = NULL; + + /* + * Create either a random game or the specified one, and set the + * canvas size appropriately. + */ + midend_new_game(me); + resize(); + + /* + * Create a status bar, if needed. + */ + if (midend_wants_statusbar(me)) + js_canvas_make_statusbar(); + + /* + * Set up the game-type dropdown with presets and/or the Custom + * option. + */ + npresets = midend_num_presets(me); + if (npresets == 0) { + /* + * This puzzle doesn't have selectable game types at all. + * Completely remove the drop-down list from the page. + */ + js_remove_type_dropdown(); + have_presets_dropdown = FALSE; + } else { + presets = snewn(npresets, game_params *); + for (i = 0; i < npresets; i++) { + char *name; + midend_fetch_preset(me, i, &name, &presets[i]); + js_add_preset(name); + } + if (thegame.can_configure) + js_add_preset(NULL); /* the 'Custom' entry in the dropdown */ + + have_presets_dropdown = TRUE; + + /* + * Now ensure the appropriate element of the presets menu + * starts off selected, in case it isn't the first one in the + * list (e.g. Slant). + */ + select_appropriate_preset(); + } + + /* + * Remove the Solve button if the game doesn't support it. + */ + if (!thegame.can_solve) + js_remove_solve_button(); + + /* + * Retrieve the game's colours, and convert them into #abcdef type + * hex ID strings. + */ + colours = midend_colours(me, &ncolours); + colour_strings = snewn(ncolours, char *); + for (i = 0; i < ncolours; i++) { + char col[40]; + sprintf(col, "#%02x%02x%02x", + (unsigned)(0.5 + 255 * colours[i*3+0]), + (unsigned)(0.5 + 255 * colours[i*3+1]), + (unsigned)(0.5 + 255 * colours[i*3+2])); + colour_strings[i] = dupstr(col); + } + + /* + * Request notification when the game ids change (e.g. if the user + * presses 'n', and also when Mines supersedes its game + * description), so that we can proactively update the permalink. + */ + midend_request_id_changes(me, ids_changed, NULL); + + /* + * Draw the puzzle's initial state, and set up the permalinks and + * undo/redo greying out. + */ + midend_redraw(me); + update_permalinks(); + update_undo_redo(); + + /* + * If we were given an erroneous game ID in argv[1], now's the + * time to put up the error box about it, after we've fully set up + * a random puzzle. Then when the user clicks 'ok', we have a + * puzzle for them. + */ + if (param_err) + js_error_box(param_err); + + /* + * Done. Return to JS, and await callbacks! + */ + return 0; +} diff --git a/apps/plugins/puzzles/emcclib.js b/apps/plugins/puzzles/emcclib.js new file mode 100644 index 0000000000..385281ad0b --- /dev/null +++ b/apps/plugins/puzzles/emcclib.js @@ -0,0 +1,757 @@ +/* + * emcclib.js: one of the Javascript components of an Emscripten-based + * web/Javascript front end for Puzzles. + * + * The other parts of this system live in emcc.c and emccpre.js. It + * also depends on being run in the context of a web page containing + * an appropriate collection of bits and pieces (a canvas, some + * buttons and links etc), which is generated for each puzzle by the + * script html/jspage.pl. + * + * This file contains a set of Javascript functions which we insert + * into Emscripten's library object via the --js-library option; this + * allows us to provide JS code which can be called from the + * Emscripten-compiled C, mostly dealing with UI interaction of + * various kinds. + */ + +mergeInto(LibraryManager.library, { + /* + * void js_debug(const char *message); + * + * A function to write a diagnostic to the Javascript console. + * Unused in production, but handy in development. + */ + js_debug: function(ptr) { + console.log(Pointer_stringify(ptr)); + }, + + /* + * void js_error_box(const char *message); + * + * A wrapper around Javascript's alert(), so the C code can print + * simple error message boxes (e.g. when invalid data is entered + * in a configuration dialog). + */ + js_error_box: function(ptr) { + alert(Pointer_stringify(ptr)); + }, + + /* + * void js_remove_type_dropdown(void); + * + * Get rid of the drop-down list on the web page for selecting + * game presets. Called at setup time if the game back end + * provides neither presets nor configurability. + */ + js_remove_type_dropdown: function() { + document.getElementById("gametype").style.display = "none"; + }, + + /* + * void js_remove_solve_button(void); + * + * Get rid of the Solve button on the web page. Called at setup + * time if the game doesn't support an in-game solve function. + */ + js_remove_solve_button: function() { + document.getElementById("solve").style.display = "none"; + }, + + /* + * void js_add_preset(const char *name); + * + * Add a preset to the drop-down types menu. The provided text is + * the name of the preset. (The corresponding game_params stays on + * the C side and never comes out this far; we just pass a numeric + * index back to the C code when a selection is made.) + * + * The special 'Custom' preset is requested by passing NULL to + * this function, rather than the string "Custom", since in that + * case we need to do something special - see below. + */ + js_add_preset: function(ptr) { + var name = (ptr == 0 ? "Customise..." : Pointer_stringify(ptr)); + var value = gametypeoptions.length; + + var option = document.createElement("option"); + option.value = value; + option.appendChild(document.createTextNode(name)); + gametypeselector.appendChild(option); + gametypeoptions.push(option); + + if (ptr == 0) { + // The option we've just created is the one for inventing + // a new custom setup. + gametypenewcustom = option; + option.value = -1; + + // Now create another element called 'Custom', which will + // be auto-selected by us to indicate the custom settings + // you've previously selected. However, we don't add it to + // the game type selector; it will only appear when the + // user actually has custom settings selected. + option = document.createElement("option"); + option.value = -2; + option.appendChild(document.createTextNode("Custom")); + gametypethiscustom = option; + } + }, + + /* + * int js_get_selected_preset(void); + * + * Return the index of the currently selected value in the type + * dropdown. + */ + js_get_selected_preset: function() { + for (var i in gametypeoptions) { + if (gametypeoptions[i].selected) { + return gametypeoptions[i].value; + } + } + return 0; + }, + + /* + * void js_select_preset(int n); + * + * Cause a different value to be selected in the type dropdown + * (for when the user selects values from the Custom configurer + * which turn out to exactly match a preset). + */ + js_select_preset: function(n) { + if (gametypethiscustom !== null) { + // Fiddle with the Custom/Customise options. If we're + // about to select the Custom option, then it should be in + // the menu, and the other one should read "Re-customise"; + // if we're about to select another one, then the static + // Custom option should disappear and the other one should + // read "Customise". + + if (gametypethiscustom.parentNode == gametypeselector) + gametypeselector.removeChild(gametypethiscustom); + if (gametypenewcustom.parentNode == gametypeselector) + gametypeselector.removeChild(gametypenewcustom); + + if (n < 0) { + gametypeselector.appendChild(gametypethiscustom); + gametypenewcustom.lastChild.data = "Re-customise..."; + } else { + gametypenewcustom.lastChild.data = "Customise..."; + } + gametypeselector.appendChild(gametypenewcustom); + gametypenewcustom.selected = false; + } + + if (n < 0) { + gametypethiscustom.selected = true; + } else { + gametypeoptions[n].selected = true; + } + }, + + /* + * void js_get_date_64(unsigned *p); + * + * Return the current date, in milliseconds since the epoch + * (Javascript's native format), as a 64-bit integer. Used to + * invent an initial random seed for puzzle generation. + */ + js_get_date_64: function(ptr) { + var d = (new Date()).valueOf(); + setValue(ptr, d, 'i64'); + }, + + /* + * void js_update_permalinks(const char *desc, const char *seed); + * + * Update the permalinks on the web page for a new game + * description and optional random seed. desc can never be NULL, + * but seed might be (if the game was generated by entering a + * descriptive id by hand), in which case we suppress display of + * the random seed permalink. + */ + js_update_permalinks: function(desc, seed) { + desc = Pointer_stringify(desc); + permalink_desc.href = "#" + desc; + + if (seed == 0) { + permalink_seed.style.display = "none"; + } else { + seed = Pointer_stringify(seed); + permalink_seed.href = "#" + seed; + permalink_seed.style.display = "inline"; + } + }, + + /* + * void js_enable_undo_redo(int undo, int redo); + * + * Set the enabled/disabled states of the undo and redo buttons, + * after a move. + */ + js_enable_undo_redo: function(undo, redo) { + undo_button.disabled = (undo == 0); + redo_button.disabled = (redo == 0); + }, + + /* + * void js_activate_timer(); + * + * Start calling the C timer_callback() function every 20ms. + */ + js_activate_timer: function() { + if (timer === null) { + timer_reference_date = (new Date()).valueOf(); + timer = setInterval(function() { + var now = (new Date()).valueOf(); + timer_callback((now - timer_reference_date) / 1000.0); + timer_reference_date = now; + return true; + }, 20); + } + }, + + /* + * void js_deactivate_timer(); + * + * Stop calling the C timer_callback() function every 20ms. + */ + js_deactivate_timer: function() { + if (timer !== null) { + clearInterval(timer); + timer = null; + } + }, + + /* + * void js_canvas_start_draw(void); + * + * Prepare to do some drawing on the canvas. + */ + js_canvas_start_draw: function() { + ctx = offscreen_canvas.getContext('2d'); + update_xmin = update_xmax = update_ymin = update_ymax = undefined; + }, + + /* + * void js_canvas_draw_update(int x, int y, int w, int h); + * + * Mark a rectangle of the off-screen canvas as needing to be + * copied to the on-screen one. + */ + js_canvas_draw_update: function(x, y, w, h) { + /* + * Currently we do this in a really simple way, just by taking + * the smallest rectangle containing all updates so far. We + * could instead keep the data in a richer form (e.g. retain + * multiple smaller rectangles needing update, and only redraw + * the whole thing beyond a certain threshold) but this will + * do for now. + */ + if (update_xmin === undefined || update_xmin > x) update_xmin = x; + if (update_ymin === undefined || update_ymin > y) update_ymin = y; + if (update_xmax === undefined || update_xmax < x+w) update_xmax = x+w; + if (update_ymax === undefined || update_ymax < y+h) update_ymax = y+h; + }, + + /* + * void js_canvas_end_draw(void); + * + * Finish the drawing, by actually copying the newly drawn stuff + * to the on-screen canvas. + */ + js_canvas_end_draw: function() { + if (update_xmin !== undefined) { + var onscreen_ctx = onscreen_canvas.getContext('2d'); + onscreen_ctx.drawImage(offscreen_canvas, + update_xmin, update_ymin, + update_xmax - update_xmin, + update_ymax - update_ymin, + update_xmin, update_ymin, + update_xmax - update_xmin, + update_ymax - update_ymin); + } + ctx = null; + }, + + /* + * void js_canvas_draw_rect(int x, int y, int w, int h, + * const char *colour); + * + * Draw a rectangle. + */ + js_canvas_draw_rect: function(x, y, w, h, colptr) { + ctx.fillStyle = Pointer_stringify(colptr); + ctx.fillRect(x, y, w, h); + }, + + /* + * void js_canvas_clip_rect(int x, int y, int w, int h); + * + * Set a clipping rectangle. + */ + js_canvas_clip_rect: function(x, y, w, h) { + ctx.save(); + ctx.beginPath(); + ctx.rect(x, y, w, h); + ctx.clip(); + }, + + /* + * void js_canvas_unclip(void); + * + * Reset to no clipping. + */ + js_canvas_unclip: function() { + ctx.restore(); + }, + + /* + * void js_canvas_draw_line(float x1, float y1, float x2, float y2, + * int width, const char *colour); + * + * Draw a line. We must adjust the coordinates by 0.5 because + * Javascript's canvas coordinates appear to be pixel corners, + * whereas we want pixel centres. Also, we manually draw the pixel + * at each end of the line, which our clients will expect but + * Javascript won't reliably do by default (in common with other + * Postscriptish drawing frameworks). + */ + js_canvas_draw_line: function(x1, y1, x2, y2, width, colour) { + colour = Pointer_stringify(colour); + + ctx.beginPath(); + ctx.moveTo(x1 + 0.5, y1 + 0.5); + ctx.lineTo(x2 + 0.5, y2 + 0.5); + ctx.lineWidth = width; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeStyle = colour; + ctx.stroke(); + ctx.fillStyle = colour; + ctx.fillRect(x1, y1, 1, 1); + ctx.fillRect(x2, y2, 1, 1); + }, + + /* + * void js_canvas_draw_poly(int *points, int npoints, + * const char *fillcolour, + * const char *outlinecolour); + * + * Draw a polygon. + */ + js_canvas_draw_poly: function(pointptr, npoints, fill, outline) { + ctx.beginPath(); + ctx.moveTo(getValue(pointptr , 'i32') + 0.5, + getValue(pointptr+4, 'i32') + 0.5); + for (var i = 1; i < npoints; i++) + ctx.lineTo(getValue(pointptr+8*i , 'i32') + 0.5, + getValue(pointptr+8*i+4, 'i32') + 0.5); + ctx.closePath(); + if (fill != 0) { + ctx.fillStyle = Pointer_stringify(fill); + ctx.fill(); + } + ctx.lineWidth = '1'; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeStyle = Pointer_stringify(outline); + ctx.stroke(); + }, + + /* + * void js_canvas_draw_circle(int x, int y, int r, + * const char *fillcolour, + * const char *outlinecolour); + * + * Draw a circle. + */ + js_canvas_draw_circle: function(x, y, r, fill, outline) { + ctx.beginPath(); + ctx.arc(x + 0.5, y + 0.5, r, 0, 2*Math.PI); + if (fill != 0) { + ctx.fillStyle = Pointer_stringify(fill); + ctx.fill(); + } + ctx.lineWidth = '1'; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeStyle = Pointer_stringify(outline); + ctx.stroke(); + }, + + /* + * int js_canvas_find_font_midpoint(int height, const char *fontptr); + * + * Return the adjustment required for text displayed using + * ALIGN_VCENTRE. We want to place the midpoint between the + * baseline and the cap-height at the specified position; so this + * function returns the adjustment which, when added to the + * desired centre point, returns the y-coordinate at which you + * should put the baseline. + * + * There is no sensible method of querying this kind of font + * metric in Javascript, so instead we render a piece of test text + * to a throwaway offscreen canvas and then read the pixel data + * back out to find the highest and lowest pixels. That's good + * _enough_ (in that we only needed the answer to the nearest + * pixel anyway), but rather disgusting! + * + * Since this is a very expensive operation, we cache the results + * per (font,height) pair. + */ + js_canvas_find_font_midpoint: function(height, font) { + font = Pointer_stringify(font); + + // Reuse cached value if possible + if (midpoint_cache[font] !== undefined) + return midpoint_cache[font]; + + // Find the width of the string + var ctx1 = onscreen_canvas.getContext('2d'); + ctx1.font = font; + var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0; + + // Construct a test canvas of appropriate size, initialise it to + // black, and draw the string on it in white + var measure_canvas = document.createElement('canvas'); + var ctx2 = measure_canvas.getContext('2d'); + ctx2.canvas.width = width; + ctx2.canvas.height = 2*height; + ctx2.fillStyle = "#000000"; + ctx2.fillRect(0, 0, width, 2*height); + var baseline = (1.5*height) | 0; + ctx2.fillStyle = "#ffffff"; + ctx2.font = font; + ctx2.fillText(midpoint_test_str, 0, baseline); + + // Scan the contents of the test canvas to find the top and bottom + // set pixels. + var pixels = ctx2.getImageData(0, 0, width, 2*height).data; + var ymin = 2*height, ymax = -1; + for (var y = 0; y < 2*height; y++) { + for (var x = 0; x < width; x++) { + if (pixels[4*(y*width+x)] != 0) { + if (ymin > y) ymin = y; + if (ymax < y) ymax = y; + break; + } + } + } + + var ret = (baseline - (ymin + ymax) / 2) | 0; + midpoint_cache[font] = ret; + return ret; + }, + + /* + * void js_canvas_draw_text(int x, int y, int halign, + * const char *colptr, const char *fontptr, + * const char *text); + * + * Draw text. Vertical alignment has been taken care of on the C + * side, by optionally calling the above function. Horizontal + * alignment is handled here, since we can get the canvas draw + * function to do it for us with almost no extra effort. + */ + js_canvas_draw_text: function(x, y, halign, colptr, fontptr, text) { + ctx.font = Pointer_stringify(fontptr); + ctx.fillStyle = Pointer_stringify(colptr); + ctx.textAlign = (halign == 0 ? 'left' : + halign == 1 ? 'center' : 'right'); + ctx.textBaseline = 'alphabetic'; + ctx.fillText(Pointer_stringify(text), x, y); + }, + + /* + * int js_canvas_new_blitter(int w, int h); + * + * Create a new blitter object, which is just an offscreen canvas + * of the specified size. + */ + js_canvas_new_blitter: function(w, h) { + var id = blittercount++; + blitters[id] = document.createElement("canvas"); + blitters[id].width = w; + blitters[id].height = h; + return id; + }, + + /* + * void js_canvas_free_blitter(int id); + * + * Free a blitter (or rather, destroy our reference to it so JS + * can garbage-collect it, and also enforce that we don't + * accidentally use it again afterwards). + */ + js_canvas_free_blitter: function(id) { + blitters[id] = null; + }, + + /* + * void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); + * + * Copy from the puzzle image to a blitter. The size is passed to + * us, partly so we don't have to remember the size of each + * blitter, but mostly so that the C side can adjust the copy + * rectangle in the case where it partially overlaps the edge of + * the screen. + */ + js_canvas_copy_to_blitter: function(id, x, y, w, h) { + var blitter_ctx = blitters[id].getContext('2d'); + blitter_ctx.drawImage(offscreen_canvas, + x, y, w, h, + 0, 0, w, h); + }, + + /* + * void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h); + * + * Copy from a blitter back to the puzzle image. As above, the + * size of the copied rectangle is passed to us from the C side + * and may already have been modified. + */ + js_canvas_copy_from_blitter: function(id, x, y, w, h) { + ctx.drawImage(blitters[id], + 0, 0, w, h, + x, y, w, h); + }, + + /* + * void js_canvas_make_statusbar(void); + * + * Cause a status bar to exist. Called at setup time if the puzzle + * back end turns out to want one. + */ + js_canvas_make_statusbar: function() { + var statusholder = document.getElementById("statusbarholder"); + statusbar = document.createElement("div"); + statusbar.style.overflow = "hidden"; + statusbar.style.width = (onscreen_canvas.width - 4) + "px"; + statusholder.style.width = onscreen_canvas.width + "px"; + statusbar.style.height = "1.2em"; + statusbar.style.textAlign = "left"; + statusbar.style.background = "#d8d8d8"; + statusbar.style.borderLeft = '2px solid #c8c8c8'; + statusbar.style.borderTop = '2px solid #c8c8c8'; + statusbar.style.borderRight = '2px solid #e8e8e8'; + statusbar.style.borderBottom = '2px solid #e8e8e8'; + statusbar.appendChild(document.createTextNode(" ")); + statusholder.appendChild(statusbar); + }, + + /* + * void js_canvas_set_statusbar(const char *text); + * + * Set the text in the status bar. + */ + js_canvas_set_statusbar: function(ptr) { + var text = Pointer_stringify(ptr); + statusbar.replaceChild(document.createTextNode(text), + statusbar.lastChild); + }, + + /* + * void js_canvas_set_size(int w, int h); + * + * Set the size of the puzzle canvas. Called at setup, and every + * time the user picks new puzzle settings requiring a different + * size. + */ + js_canvas_set_size: function(w, h) { + onscreen_canvas.width = w; + offscreen_canvas.width = w; + if (statusbar !== null) { + statusbar.style.width = (w - 4) + "px"; + document.getElementById("statusbarholder").style.width = w + "px"; + } + resizable_div.style.width = w + "px"; + + onscreen_canvas.height = h; + offscreen_canvas.height = h; + }, + + /* + * void js_dialog_init(const char *title); + * + * Begin constructing a 'dialog box' which will be popped up in an + * overlay on top of the rest of the puzzle web page. + */ + js_dialog_init: function(titletext) { + // Create an overlay on the page which darkens everything + // beneath it. + dlg_dimmer = document.createElement("div"); + dlg_dimmer.style.width = "100%"; + dlg_dimmer.style.height = "100%"; + dlg_dimmer.style.background = '#000000'; + dlg_dimmer.style.position = 'fixed'; + dlg_dimmer.style.opacity = 0.3; + dlg_dimmer.style.top = dlg_dimmer.style.left = 0; + dlg_dimmer.style["z-index"] = 99; + + // Now create a form which sits on top of that in turn. + dlg_form = document.createElement("form"); + dlg_form.style.width = (window.innerWidth * 2 / 3) + "px"; + dlg_form.style.opacity = 1; + dlg_form.style.background = '#ffffff'; + dlg_form.style.color = '#000000'; + dlg_form.style.position = 'absolute'; + dlg_form.style.border = "2px solid black"; + dlg_form.style.padding = "20px"; + dlg_form.style.top = (window.innerHeight / 10) + "px"; + dlg_form.style.left = (window.innerWidth / 6) + "px"; + dlg_form.style["z-index"] = 100; + + var title = document.createElement("p"); + title.style.marginTop = "0px"; + title.appendChild(document.createTextNode + (Pointer_stringify(titletext))); + dlg_form.appendChild(title); + + dlg_return_funcs = []; + dlg_next_id = 0; + }, + + /* + * void js_dialog_string(int i, const char *title, const char *initvalue); + * + * Add a string control (that is, an edit box) to the dialog under + * construction. + */ + js_dialog_string: function(index, title, initialtext) { + dlg_form.appendChild(document.createTextNode(Pointer_stringify(title))); + var editbox = document.createElement("input"); + editbox.type = "text"; + editbox.value = Pointer_stringify(initialtext); + dlg_form.appendChild(editbox); + dlg_form.appendChild(document.createElement("br")); + + dlg_return_funcs.push(function() { + dlg_return_sval(index, editbox.value); + }); + }, + + /* + * void js_dialog_choices(int i, const char *title, const char *choicelist, + * int initvalue); + * + * Add a choices control (i.e. a drop-down list) to the dialog + * under construction. The 'choicelist' parameter is unchanged + * from the way the puzzle back end will have supplied it: i.e. + * it's still encoded as a single string whose first character + * gives the separator. + */ + js_dialog_choices: function(index, title, choicelist, initvalue) { + dlg_form.appendChild(document.createTextNode(Pointer_stringify(title))); + var dropdown = document.createElement("select"); + var choicestr = Pointer_stringify(choicelist); + var items = choicestr.slice(1).split(choicestr[0]); + var options = []; + for (var i in items) { + var option = document.createElement("option"); + option.value = i; + option.appendChild(document.createTextNode(items[i])); + if (i == initvalue) option.selected = true; + dropdown.appendChild(option); + options.push(option); + } + dlg_form.appendChild(dropdown); + dlg_form.appendChild(document.createElement("br")); + + dlg_return_funcs.push(function() { + var val = 0; + for (var i in options) { + if (options[i].selected) { + val = options[i].value; + break; + } + } + dlg_return_ival(index, val); + }); + }, + + /* + * void js_dialog_boolean(int i, const char *title, int initvalue); + * + * Add a boolean control (a checkbox) to the dialog under + * construction. Checkboxes are generally expected to be sensitive + * on their label text as well as the box itself, so for this + * control we create an actual label rather than merely a text + * node (and hence we must allocate an id to the checkbox so that + * the label can refer to it). + */ + js_dialog_boolean: function(index, title, initvalue) { + var checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = "cb" + String(dlg_next_id++); + checkbox.checked = (initvalue != 0); + dlg_form.appendChild(checkbox); + var checkboxlabel = document.createElement("label"); + checkboxlabel.setAttribute("for", checkbox.id); + checkboxlabel.textContent = Pointer_stringify(title); + dlg_form.appendChild(checkboxlabel); + dlg_form.appendChild(document.createElement("br")); + + dlg_return_funcs.push(function() { + dlg_return_ival(index, checkbox.checked ? 1 : 0); + }); + }, + + /* + * void js_dialog_launch(void); + * + * Finish constructing a dialog, and actually display it, dimming + * everything else on the page. + */ + js_dialog_launch: function() { + // Put in the OK and Cancel buttons at the bottom. + var button; + + button = document.createElement("input"); + button.type = "button"; + button.value = "OK"; + button.onclick = function(event) { + for (var i in dlg_return_funcs) + dlg_return_funcs[i](); + command(3); + } + dlg_form.appendChild(button); + + button = document.createElement("input"); + button.type = "button"; + button.value = "Cancel"; + button.onclick = function(event) { + command(4); + } + dlg_form.appendChild(button); + + document.body.appendChild(dlg_dimmer); + document.body.appendChild(dlg_form); + }, + + /* + * void js_dialog_cleanup(void); + * + * Stop displaying a dialog, and clean up the internal state + * associated with it. + */ + js_dialog_cleanup: function() { + document.body.removeChild(dlg_dimmer); + document.body.removeChild(dlg_form); + dlg_dimmer = dlg_form = null; + onscreen_canvas.focus(); + }, + + /* + * void js_focus_canvas(void); + * + * Return keyboard focus to the puzzle canvas. Called after a + * puzzle-control button is pressed, which tends to have the side + * effect of taking focus away from the canvas. + */ + js_focus_canvas: function() { + onscreen_canvas.focus(); + } +}); diff --git a/apps/plugins/puzzles/emccpre.js b/apps/plugins/puzzles/emccpre.js new file mode 100644 index 0000000000..ebf67d1fc6 --- /dev/null +++ b/apps/plugins/puzzles/emccpre.js @@ -0,0 +1,364 @@ +/* + * emccpre.js: one of the Javascript components of an Emscripten-based + * web/Javascript front end for Puzzles. + * + * The other parts of this system live in emcc.c and emcclib.js. It + * also depends on being run in the context of a web page containing + * an appropriate collection of bits and pieces (a canvas, some + * buttons and links etc), which is generated for each puzzle by the + * script html/jspage.pl. + * + * This file contains the Javascript code which is prefixed unmodified + * to Emscripten's output via the --pre-js option. It declares all our + * global variables, and provides the puzzle init function and a + * couple of other helper functions. + */ + +// To avoid flicker while doing complicated drawing, we use two +// canvases, the same size. One is actually on the web page, and the +// other is off-screen. We do all our drawing on the off-screen one +// first, and then copy rectangles of it to the on-screen canvas in +// response to draw_update() calls by the game backend. +var onscreen_canvas, offscreen_canvas; + +// A persistent drawing context for the offscreen canvas, to save +// constructing one per individual graphics operation. +var ctx; + +// Bounding rectangle for the copy to the onscreen canvas that will be +// done at drawing end time. Updated by js_canvas_draw_update and used +// by js_canvas_end_draw. +var update_xmin, update_xmax, update_ymin, update_ymax; + +// Module object for Emscripten. We fill in these parameters to ensure +// that Module.run() won't be called until we're ready (we want to do +// our own init stuff first), and that when main() returns nothing +// will get cleaned up so we remain able to call the puzzle's various +// callbacks. +var Module = { + 'noInitialRun': true, + 'noExitRuntime': true +}; + +// Variables used by js_canvas_find_font_midpoint(). +var midpoint_test_str = "ABCDEFGHIKLMNOPRSTUVWXYZ0123456789"; +var midpoint_cache = []; + +// Variables used by js_activate_timer() and js_deactivate_timer(). +var timer = null; +var timer_reference_date; + +// void timer_callback(double tplus); +// +// Called every 20ms while timing is active. +var timer_callback; + +// The status bar object, if we create one. +var statusbar = null; + +// Currently live blitters. We keep an integer id for each one on the +// JS side; the C side, which expects a blitter to look like a struct, +// simply defines the struct to contain that integer id. +var blittercount = 0; +var blitters = []; + +// State for the dialog-box mechanism. dlg_dimmer and dlg_form are the +// page-darkening overlay and the actual dialog box respectively; +// dlg_next_id is used to allocate each checkbox a unique id to use +// for linking its label to it (see js_dialog_boolean); +// dlg_return_funcs is a list of JS functions to be called when the OK +// button is pressed, to pass the results back to C. +var dlg_dimmer = null, dlg_form = null; +var dlg_next_id = 0; +var dlg_return_funcs = null; + +// void dlg_return_sval(int index, const char *val); +// void dlg_return_ival(int index, int val); +// +// C-side entry points called by functions in dlg_return_funcs, to +// pass back the final value in each dialog control. +var dlg_return_sval, dlg_return_ival; + +// The
Preset
", &escape($preset), "
+ + + + + + + +
+ + + +
+
+
+
+
+
+ +${instructions} + +${links} + +${footer} + + +EOF + + close $outpage; +} diff --git a/apps/plugins/puzzles/html/jspage.pl b/apps/plugins/puzzles/html/jspage.pl new file mode 100755 index 0000000000..19868bd948 --- /dev/null +++ b/apps/plugins/puzzles/html/jspage.pl @@ -0,0 +1,120 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; +my $footer = ""; +$footer .= $_ while <$footerfile>; +close $footerfile; + +for my $arg (@ARGV) { + $arg =~ /(.*\/)?([^\/]+)\.html$/ or die; + my $filename = $2; + open my $gamefile, "<", $arg or die "$arg: open: $!\n"; + my $unfinished = 0; + my $docname = $filename; + chomp(my $puzzlename = <$gamefile>); + while ($puzzlename =~ s/^([^:=]+)(=([^:]+))?://) { + if ($1 eq "unfinished") { + $unfinished = 1; + } elsif ($1 eq "docname") { + $docname = $3; + } else { + die "$arg: unknown keyword '$1'\n"; + } + } + my $instructions = ""; + $instructions .= $_ while <$gamefile>; + close $gamefile; + + open my $outpage, ">", "${filename}.html"; + + my $unfinishedtitlefragment = $unfinished ? "an unfinished puzzle " : ""; + my $unfinishedheading = $unfinished ? "

an unfinished puzzle

\n" : ""; + my $unfinishedpara; + my $links; + if ($unfinished) { + $unfinishedpara = < +You have found your way to a page containing an unfinished +puzzle in my collection, not linked from the main +puzzles page. Don't be surprised if things are hard to understand +or don't work as you expect. +EOF + $links = < +Back to main puzzles page (which does not link to this) +EOF + } else { + $unfinishedpara = ""; + $links = < +Full instructions +| +Back to main puzzles page +EOF + } + + print $outpage < + + + +${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection + + + +

${puzzlename}

+${unfinishedheading} +

from Simon Tatham's Portable Puzzle Collection

+ +${unfinishedpara} + +
+ +
+Sorry, this Javascript puzzle doesn't seem to work in your web +browser. Perhaps you have Javascript disabled, or perhaps your browser +doesn't provide a feature that the puzzle code requires (such as +typed arrays). +These puzzles have been successfully run in Firefox 19, Chrome 26, +Internet Explorer 10 and Safari 6. +
+
+ +${instructions} + +${links} + +${footer} + + +EOF + + close $outpage; +} diff --git a/apps/plugins/puzzles/html/keen.html b/apps/plugins/puzzles/html/keen.html new file mode 100644 index 0000000000..bd0eb3644d --- /dev/null +++ b/apps/plugins/puzzles/html/keen.html @@ -0,0 +1,15 @@ +Keen +

+Fill in the grid with digits from 1 to the grid size, so that every +digit appears exactly once in each row and column, and so that all +the arithmetic clues are satisfied (i.e. the clue number in each +thick box should be possible to construct from the digits in the box +using the specified arithmetic operation). +

+To place a number, click in a square to select it, then type the +number on the keyboard. To erase a number, click to select a square +and then press Backspace. +

+Right-click in a square and then type a number to add or remove the +number as a pencil mark, indicating numbers that you think +might go in that square. diff --git a/apps/plugins/puzzles/html/lightup.html b/apps/plugins/puzzles/html/lightup.html new file mode 100644 index 0000000000..2de2f91bb9 --- /dev/null +++ b/apps/plugins/puzzles/html/lightup.html @@ -0,0 +1,10 @@ +Light Up +

+Place light bulbs in the grid so as to light up all the blank +squares. A light illuminates its own square and all the squares in +the same row or column unless blocked by walls (black squares). +Lights may not illuminate each other. Each numbered square must be +orthogonally adjacent to exactly the given number of lights. +

+Click on a square to place or remove a light. Right-click to place a +dot indicating that you think there is no light in that square. diff --git a/apps/plugins/puzzles/html/loopy.html b/apps/plugins/puzzles/html/loopy.html new file mode 100644 index 0000000000..96f3a9d908 --- /dev/null +++ b/apps/plugins/puzzles/html/loopy.html @@ -0,0 +1,13 @@ +Loopy +

+Form a single closed loop out of the grid edges, in such a way that +every numbered square has exactly that many of its edges included in +the loop. +

+Click on a grid edge to mark it as part of the loop (black), and +again to return to marking it as undecided (yellow). Right-click on +a grid edge to mark it as definitely not part of the loop (faint +grey), and again to mark it as undecided again. +

+When you have mastered the square grid, look in the Type menu for +many other types of tiling! diff --git a/apps/plugins/puzzles/html/magnets.html b/apps/plugins/puzzles/html/magnets.html new file mode 100644 index 0000000000..2807569a6c --- /dev/null +++ b/apps/plugins/puzzles/html/magnets.html @@ -0,0 +1,17 @@ +Magnets +

+Fill each domino shape with either a magnet (consisting of a + and +− pole) or a neutral domino (green). +

+The number of + poles that in each row and column must match the +numbers along the top and left; the number of − poles must +match the numbers along the bottom and right. Two + poles may not be +orthogonally adjacent to each other, and similarly two − poles. +

+Left-click a domino to toggle it between being empty and being a +magnet (the + is placed in the end you click). Right-click to toggle +between empty, neutral, and a ?? mark indicating that you're sure +it's a magnet but don't yet know which way round it goes. +

+Left-click a clue to mark it as done (grey it out). To unmark a clue +as done, left-click it again. diff --git a/apps/plugins/puzzles/html/map.html b/apps/plugins/puzzles/html/map.html new file mode 100644 index 0000000000..5f81793054 --- /dev/null +++ b/apps/plugins/puzzles/html/map.html @@ -0,0 +1,15 @@ +Map +

+Colour the map with four colours, so that no two adjacent regions +have the same colour. (Regions touching at only one corner do not +count as adjacent.) There is a unique colouring consistent with the +coloured regions you are already given. +

+Drag from a coloured region to a blank one to colour the latter the +same colour as the former. Drag from outside the grid into a region +to erase its colour. (You cannot change the colours of the regions +you are given at the start of the game.) +

+Right-drag from a coloured region to a blank one to add dots marking +the latter region as possibly the same colour as the +former, or to remove those dots again. diff --git a/apps/plugins/puzzles/html/mines.html b/apps/plugins/puzzles/html/mines.html new file mode 100644 index 0000000000..d17d6ffa80 --- /dev/null +++ b/apps/plugins/puzzles/html/mines.html @@ -0,0 +1,18 @@ +Mines +

+Try to expose every square in the grid that is not one of the hidden +mines, without opening any square that is a mine. +

+Click in a square to open it. Every opened square are marked with +the number of mines in the surrounding 8 squares, if there are any; +if not, all the surrounding squares are automatically opened. +

+Right-click in a square to mark it with a flag if you think it is a +mine. If a numbered square has exactly the right number of flags +around it, you can click in it to open all the squares around it +that are not flagged. +

+The first square you open is guaranteed to be safe, and (by default) +you are guaranteed to be able to solve the whole grid by deduction +rather than guesswork. (Deductions may require you to think about +the total number of mines.) diff --git a/apps/plugins/puzzles/html/net.html b/apps/plugins/puzzles/html/net.html new file mode 100644 index 0000000000..08bffbac3e --- /dev/null +++ b/apps/plugins/puzzles/html/net.html @@ -0,0 +1,17 @@ +Net +

+Rotate the grid squares so that they all join up into a single +connected network with no loops. +

+Left-click in a square to rotate it anticlockwise. Right-click to +rotate it clockwise. Middle-click, or shift-left-click if you have +no middle mouse button, to lock a square once you think it is +correct (so you don't accidentally rotate it again); do the same +again to unlock it if you change your mind. +

+Squares connected to the middle square are lit up. Aim to light up +every square in the grid (not just the endpoint blobs). +

+When this gets too easy, select a 'wrapping' variant from the Type +menu to enable grid lines to run off one edge of the playing area +and come back on the opposite edge! diff --git a/apps/plugins/puzzles/html/netslide.html b/apps/plugins/puzzles/html/netslide.html new file mode 100644 index 0000000000..f1877417d4 --- /dev/null +++ b/apps/plugins/puzzles/html/netslide.html @@ -0,0 +1,14 @@ +Netslide +

+Slide the grid squares around so that they all join up into a single +connected network with no loops. +

+Click on the arrows at the edges of the grid to move a row or column +left, right, up or down. The square that falls off the end of the +row comes back on the other end. +

+Squares connected to the middle square are lit up. Aim to light up +every square in the grid (not just the endpoint blobs). +

+Connecting across a red barrier line is forbidden. On harder levels, +there are fewer barriers, which makes it harder rather than easier! diff --git a/apps/plugins/puzzles/html/palisade.html b/apps/plugins/puzzles/html/palisade.html new file mode 100644 index 0000000000..5b6b933104 --- /dev/null +++ b/apps/plugins/puzzles/html/palisade.html @@ -0,0 +1,11 @@ +Palisade +

+Draw lines along the grid edges, in such a way that the grid is +divided into connected regions, all of the size shown in the status +line. Also, each square containing a number should have that many of +its edges drawn in. +

+Click on a grid edge to mark it as a division between regions (black), +and again to return to marking it as undecided (yellow). Right-click +on a grid edge to mark it as definitely not part of the loop (faint +grey), and again to mark it as undecided again. diff --git a/apps/plugins/puzzles/html/pattern.html b/apps/plugins/puzzles/html/pattern.html new file mode 100644 index 0000000000..54e05d6416 --- /dev/null +++ b/apps/plugins/puzzles/html/pattern.html @@ -0,0 +1,12 @@ +Pattern +

+Fill in the grid with a pattern of black and white squares, so that +the numbers in each row and column match the lengths of consecutive +runs of black squares. +

+Left-click in a square to mark it black; right-click (or hold Ctrl +while left-clicking) to mark it white. Click and drag along a row or +column to mark multiple squares black or white at once. Middle-click +(or hold Shift while left-clicking) to return a square to grey +(meaning undecided): dragging like that can erase a whole rectangle, +not just a row or column. diff --git a/apps/plugins/puzzles/html/pearl.html b/apps/plugins/puzzles/html/pearl.html new file mode 100644 index 0000000000..2ca25a5ee0 --- /dev/null +++ b/apps/plugins/puzzles/html/pearl.html @@ -0,0 +1,13 @@ +Pearl +

+Draw a single closed loop by connecting together the centres of +adjacent grid squares, so that some squares end up as corners, some as +straights (horizontal or vertical), and some may be empty. Every +square containing a black circle must be a corner not connected +directly to another corner; every square containing a white circle +must be a straight which is connected to at least one corner. +

+Drag between squares to draw or undraw pieces of the loop. +Alternatively, left-click the edge between two squares to turn it on +or off. Right-click an edge to mark it with a cross indicating that +you are sure the loop does not go through it. diff --git a/apps/plugins/puzzles/html/pegs.html b/apps/plugins/puzzles/html/pegs.html new file mode 100644 index 0000000000..4a2378873e --- /dev/null +++ b/apps/plugins/puzzles/html/pegs.html @@ -0,0 +1,8 @@ +Pegs +

+Jump one peg over another to remove the one you jumped over. Try to +remove all but one peg. +

+Drag a peg into an empty space to make a move. The target space must +be exactly two holes away from the starting peg, in an orthogonal +direction, and there must be a peg in the hole in between. diff --git a/apps/plugins/puzzles/html/range.html b/apps/plugins/puzzles/html/range.html new file mode 100644 index 0000000000..bb5b59c4d2 --- /dev/null +++ b/apps/plugins/puzzles/html/range.html @@ -0,0 +1,21 @@ +Range +

+Colour some squares black, so as to meet the following conditions: +

    +
  • +No two black squares are orthogonally adjacent. +
  • +No group of white squares is separated from the rest of the grid by +black squares. +
  • +Each numbered cell can see precisely that many white squares in +total by looking in all four orthogonal directions, counting itself. +(Black squares block the view. So, for example, a 2 clue must be +adjacent to three black squares or grid edges, and in the fourth +direction there must be one white square and then a black one beyond +it.) +
+ +

+Left-click to colour a square black. Right-click to mark a square +with a dot, if you know it should not be black. diff --git a/apps/plugins/puzzles/html/rect.html b/apps/plugins/puzzles/html/rect.html new file mode 100644 index 0000000000..d23d827663 --- /dev/null +++ b/apps/plugins/puzzles/html/rect.html @@ -0,0 +1,10 @@ +docname=rectangles:Rectangles +

+Draw lines along the grid edges to divide the grid into rectangles, +so that each rectangle contains exactly one numbered square and its +area is equal to the number written in that square. +

+Click and drag from one grid corner to another, or from one square +centre to another, to draw a rectangle. You can also drag along a +grid line to just draw a line at a time, or just click on a single +grid edge to draw or erase it. diff --git a/apps/plugins/puzzles/html/samegame.html b/apps/plugins/puzzles/html/samegame.html new file mode 100644 index 0000000000..e6de095210 --- /dev/null +++ b/apps/plugins/puzzles/html/samegame.html @@ -0,0 +1,14 @@ +Same Game +

+Try to empty the playing area completely, by removing connected +groups of two or more squares of the same colour. Then try to score +as much as possible, by removing large groups at a time instead of +small ones. +

+Click on a coloured square to highlight the rest of its connected +group. The status line will print the number of squares selected, +and the score you would gain by removing them. Click again to remove +the group; other squares will fall down to fill the space, and if +you empty a whole column then the other columns will move up. You +cannot remove a single isolated square: try to avoid dead-end +positions where all remaining squares are isolated. diff --git a/apps/plugins/puzzles/html/signpost.html b/apps/plugins/puzzles/html/signpost.html new file mode 100644 index 0000000000..fa23e99de0 --- /dev/null +++ b/apps/plugins/puzzles/html/signpost.html @@ -0,0 +1,14 @@ +Signpost +

+Connect all the squares together into a sequence, so that every +square's arrow points towards the square that follows it (though the +next square can be any distance away in that direction). + +

+Left-drag from a square to the square that should follow it, or +right-drag from a square to the square that should precede it. + +

+Left-drag a square off the grid to break all links to it. Right-drag +a square off the grid to break all links to it and everything else +in its connected chain. diff --git a/apps/plugins/puzzles/html/singles.html b/apps/plugins/puzzles/html/singles.html new file mode 100644 index 0000000000..252bffb380 --- /dev/null +++ b/apps/plugins/puzzles/html/singles.html @@ -0,0 +1,11 @@ +Singles +

+Black out some of the squares, in such a way that: +

  • no number appears twice in any row or column +
  • no two black squares are adjacent +
  • the white squares form a single connected group (connections +along diagonals do not count).
+

+Click in a square to black it out, and again to uncover it. +Right-click in a square to mark it with a circle, indicating that +you're sure it should not be blacked out. diff --git a/apps/plugins/puzzles/html/sixteen.html b/apps/plugins/puzzles/html/sixteen.html new file mode 100644 index 0000000000..4530469fe6 --- /dev/null +++ b/apps/plugins/puzzles/html/sixteen.html @@ -0,0 +1,8 @@ +Sixteen +

+Slide the grid squares around so that the numbers end up in +consecutive order from the top left corner. +

+Click on the arrows at the edges of the grid to move a row or column +left, right, up or down. The square that falls off the end of the +row comes back on the other end. diff --git a/apps/plugins/puzzles/html/slant.html b/apps/plugins/puzzles/html/slant.html new file mode 100644 index 0000000000..d6d31aa302 --- /dev/null +++ b/apps/plugins/puzzles/html/slant.html @@ -0,0 +1,9 @@ +Slant +

+Fill in a diagonal line in every grid square so that there are no +loops in the grid, and so that every numbered point has that many +lines meeting at it. +

+Left-click in a square to mark it with a \; right-click +to mark it with a /. Keep clicking in a square to +cycle it between \, / and empty. diff --git a/apps/plugins/puzzles/html/solo.html b/apps/plugins/puzzles/html/solo.html new file mode 100644 index 0000000000..88ebd5cb29 --- /dev/null +++ b/apps/plugins/puzzles/html/solo.html @@ -0,0 +1,20 @@ +Solo +

+Fill in a number in every square so that every number appears +exactly once in each row, each column and each block marked by thick +lines. +

+To place a number, click in a square to select it, then type the +number on the keyboard. To erase a number, click to select a square +and then press Backspace. +

+Right-click in a square and then type a number to add or remove the +number as a pencil mark, indicating numbers that you think +might go in that square. +

+When you master the basic game, try Jigsaw mode (irregularly shaped +blocks), X mode (the two main diagonals of the grid must also +contain every number once), Killer mode (instead of single-cell +clues you are given regions of the grid each of which must add up to +a given total, again without reusing any digits), or all of those at +once! diff --git a/apps/plugins/puzzles/html/tents.html b/apps/plugins/puzzles/html/tents.html new file mode 100644 index 0000000000..e3f6d5f0ea --- /dev/null +++ b/apps/plugins/puzzles/html/tents.html @@ -0,0 +1,20 @@ +Tents +

+Place tents in the empty squares in such a way that: +

    +
  • no two tents are adjacent, even diagonally +
  • the number of tents in each row and column matches the numbers +around the edge of the grid +
  • it is possible to match tents to trees so that each tree is +orthogonally adjacent to its own tent (but may also be adjacent to +other tents). +
+

+Click in a square to place or remove a tent. Right-click to mark a +square as empty (not a tent). Right-click and drag along a row or +column to mark many squares at once as empty. +

+Warning '!' marks appear to indicate adjacent tents. Numbers round +the edge of the grid light up red to indicate they do not match the +number of tents in the row. Groups of tents light up red to indicate +that they have too few trees between them, and vice versa. diff --git a/apps/plugins/puzzles/html/towers.html b/apps/plugins/puzzles/html/towers.html new file mode 100644 index 0000000000..a710e0ab6e --- /dev/null +++ b/apps/plugins/puzzles/html/towers.html @@ -0,0 +1,22 @@ +Towers +

+Fill in the grid with towers whose heights range from 1 to the grid +size, so that every possible height appears exactly once in each row +and column, and so that each clue around the edge counts the number +of towers that are visible when looking into the grid from that +direction. (Taller towers hide shorter ones behind them. So the +sequence 2,1,4,3,5 would match a clue of 3 on the left, because the +1 is hidden behind the 2 and the 3 is hidden behind the 4. On the +right, it would match a clue of 1 because the 5 hides everything +else.) +

+To place a tower, click in a square to select it, then type the +desired height on the keyboard. To erase a tower, click to select a +square and then press Backspace. +

+Right-click in a square and then type a number to add or remove the +number as a pencil mark, indicating tower heights that you think +might go in that square. +

+Left-click on a clue to mark it as done (grey it out). To unmark a +clue as done, left-click on it again. \ No newline at end of file diff --git a/apps/plugins/puzzles/html/tracks.html b/apps/plugins/puzzles/html/tracks.html new file mode 100644 index 0000000000..afabed37ac --- /dev/null +++ b/apps/plugins/puzzles/html/tracks.html @@ -0,0 +1,19 @@ +Tracks +

+Complete the track from A to B so that the rows and +columns contain the same number of track segments as are indicated in the +clues to the top and right of the grid. There are only straight and +90-degree curved rail sections, and the track may not cross itself. +

+Left-click on an edge between two squares to add a track segment between +the two squares. Right-click on an edge to add a cross on the edge, +indicating no track is possible there. +

+Left-click in a square to add a colour indicator showing that you know the +square must contain a track, even if you don't know which edges it crosses +yet. Right-click in a square to add a cross indicating it contains no +track segment. +

+Left- or right-drag between squares to lay a straight line of is-track or +is-not-track indicators, useful for filling in rows or columns to match the +clue. diff --git a/apps/plugins/puzzles/html/twiddle.html b/apps/plugins/puzzles/html/twiddle.html new file mode 100644 index 0000000000..5f94e4e120 --- /dev/null +++ b/apps/plugins/puzzles/html/twiddle.html @@ -0,0 +1,15 @@ +Twiddle +

+Rotate square sections of the grid to arrange the squares into +numerical order starting from the top left. +

+In the basic game, you rotate a 2×2 square section. Left-click +in the centre of that section (i.e. on a corner point between four +squares) to rotate the whole section anticlockwise. Right-click to +rotate the section clockwise. +

+When you master the basic game, go to the Type menu to try it with +larger rotating groups (for a 3×3 group you must click in the +centre of a square to rotate the block around it). Or select the +'orientable' mode in which every square must end up the right way +round as well as in the right place. Or both! diff --git a/apps/plugins/puzzles/html/undead.html b/apps/plugins/puzzles/html/undead.html new file mode 100644 index 0000000000..c21374f6dc --- /dev/null +++ b/apps/plugins/puzzles/html/undead.html @@ -0,0 +1,22 @@ +Undead +

+Fill in every grid square which doesn't contain a mirror with either a +ghost, a vampire, or a zombie. The numbers round the grid edges show +how many monsters must be visible along your line of sight if you look +directly into the grid from that position, along a row or column. +Zombies are always visible; ghosts are only visible when reflected in +at least one mirror; vampires are only visible when not reflected in +any mirror. + +

+To place a monster, click in a square to select it, then type the +monster's letter on the keyboard: G for a ghost, V for a vampire or Z +for a zombie. To erase a monster, click to select a square and then +press Backspace. +

+Right-click in a square and then type a letter to add or remove the +monster as a pencil mark, indicating monsters that you think +might go in that square. +

+Left-click on a clue to mark it as done (grey it out). To unmark a +clue as done, left-click on it again. diff --git a/apps/plugins/puzzles/html/unequal.html b/apps/plugins/puzzles/html/unequal.html new file mode 100644 index 0000000000..085f82effc --- /dev/null +++ b/apps/plugins/puzzles/html/unequal.html @@ -0,0 +1,14 @@ +Unequal +

+Fill in the grid with numbers from 1 to the grid size, so that every +number appears exactly once in each row and column, and so that all +the < signs represent true inequalities (i.e. the +number at the pointed end is smaller than the number at the open end). +

+To place a number, click in a square to select it, then type the +number on the keyboard. To erase a number, click to select a square +and then press Backspace. +

+Right-click in a square and then type a number to add or remove the +number as a pencil mark, indicating numbers that you think +might go in that square. diff --git a/apps/plugins/puzzles/html/unruly.html b/apps/plugins/puzzles/html/unruly.html new file mode 100644 index 0000000000..2cd3fb25f5 --- /dev/null +++ b/apps/plugins/puzzles/html/unruly.html @@ -0,0 +1,11 @@ +Unruly +

+Colour every square either black or white, in such a way that: +

  • no three consecutive squares, horizontally or vertically, are +the same colour +
  • each row and column contains the same number of black and white +squares.
+

+Left-click in an empty square to turn it black, or right-click to turn +it white. Click again in an already-filled square to cycle it between +black and white and empty; middle-click to reset any square to empty. diff --git a/apps/plugins/puzzles/html/untangle.html b/apps/plugins/puzzles/html/untangle.html new file mode 100644 index 0000000000..7171a3d55d --- /dev/null +++ b/apps/plugins/puzzles/html/untangle.html @@ -0,0 +1,5 @@ +Untangle +

+Move the points around so that none of the lines cross. +

+Click on a point and drag it to move it. diff --git a/apps/plugins/puzzles/icons/Makefile b/apps/plugins/puzzles/icons/Makefile new file mode 100644 index 0000000000..00dae1f841 --- /dev/null +++ b/apps/plugins/puzzles/icons/Makefile @@ -0,0 +1,153 @@ +# Makefile for Puzzles icons. + +PUZZLES = blackbox bridges cube dominosa fifteen filling flip flood \ + galaxies guess inertia keen lightup loopy magnets map mines \ + net netslide palisade pattern pearl pegs range rect \ + samegame signpost singles sixteen slant solo tents towers \ + twiddle tracks undead unequal unruly untangle + +BASE = $(patsubst %,%-base.png,$(PUZZLES)) +WEB = $(patsubst %,%-web.png,$(PUZZLES)) + +IBASE = $(patsubst %,%-ibase.png,$(PUZZLES)) +IBASE4 = $(patsubst %,%-ibase4.png,$(PUZZLES)) +P48D24 = $(patsubst %,%-48d24.png,$(PUZZLES)) +P48D8 = $(patsubst %,%-48d8.png,$(PUZZLES)) +P48D4 = $(patsubst %,%-48d4.png,$(PUZZLES)) +P32D24 = $(patsubst %,%-32d24.png,$(PUZZLES)) +P32D8 = $(patsubst %,%-32d8.png,$(PUZZLES)) +P32D4 = $(patsubst %,%-32d4.png,$(PUZZLES)) +P16D24 = $(patsubst %,%-16d24.png,$(PUZZLES)) +P16D8 = $(patsubst %,%-16d8.png,$(PUZZLES)) +P16D4 = $(patsubst %,%-16d4.png,$(PUZZLES)) +ICONS = $(patsubst %,%.ico,$(PUZZLES)) +CICONS = $(patsubst %,%-icon.c,$(PUZZLES)) +RC = $(patsubst %,%.rc,$(PUZZLES)) + +BIN = ../ +PIC = ./ + +# Work around newer ImageMagick unilaterally distorting colours when +# converting to PNG. +CSP = -set colorspace RGB + +base: $(BASE) +web: $(WEB) +pngicons: $(P48D24) $(P32D24) $(P16D24) +winicons: $(ICONS) $(RC) +gtkicons: $(CICONS) +all: base web pngicons winicons gtkicons + +# Build the base puzzle screenshots from which all the other images +# are derived. Some of them involve showing a move animation +# part-way through. +fifteen-base.png : override REDO=0.3 +flip-base.png : override REDO=0.3 +netslide-base.png : override REDO=0.3 +sixteen-base.png : override REDO=0.3 +twiddle-base.png : override REDO=0.3 +$(BASE): %-base.png: $(BIN)% $(PIC)%.sav + $(PIC)screenshot.sh $(BIN)$* $(PIC)$*.sav $@ $(REDO) + +# Build the screenshots for the web, by scaling the original base +# images to a uniform size. +$(WEB): %-web.png: %-base.png + $(PIC)square.pl 150 5 $^ $@ + +# Build the base _icon_ images, by careful cropping of the base +# images: icons are very small so it's often necessary to zoom in +# on a smaller portion of the screenshot. +blackbox-ibase.png : override CROP=352x352 144x144+0+208 +bridges-ibase.png : override CROP=264x264 107x107+157+157 +dominosa-ibase.png : override CROP=304x272 152x152+152+0 +fifteen-ibase.png : override CROP=240x240 120x120+0+120 +filling-ibase.png : override CROP=256x256 133x133+14+78 +flip-ibase.png : override CROP=288x288 145x145+120+72 +galaxies-ibase.png : override CROP=288x288 165x165+0+0 +guess-ibase.png : override CROP=263x420 178x178+75+17 +inertia-ibase.png : override CROP=321x321 128x128+193+0 +keen-ibase.png : override CROP=288x288 96x96+24+120 +lightup-ibase.png : override CROP=256x256 112x112+144+0 +loopy-ibase.png : override CROP=257x257 113x113+0+0 +magnets-ibase.png : override CROP=264x232 96x96+36+100 +mines-ibase.png : override CROP=240x240 110x110+130+130 +net-ibase.png : override CROP=193x193 113x113+0+80 +netslide-ibase.png : override CROP=289x289 144x144+0+0 +palisade-ibase.png : override CROP=288x288 192x192+0+0 +pattern-ibase.png : override CROP=384x384 223x223+0+0 +pearl-ibase.png : override CROP=216x216 94x94+108+15 +pegs-ibase.png : override CROP=263x263 147x147+116+0 +range-ibase.png : override CROP=256x256 98x98+111+15 +rect-ibase.png : override CROP=205x205 115x115+90+0 +signpost-ibase.png : override CROP=240x240 98x98+23+23 +singles-ibase.png : override CROP=224x224 98x98+15+15 +sixteen-ibase.png : override CROP=288x288 144x144+144+144 +slant-ibase.png : override CROP=321x321 160x160+160+160 +solo-ibase.png : override CROP=481x481 145x145+24+24 +tents-ibase.png : override CROP=320x320 165x165+142+0 +towers-ibase.png : override CROP=300x300 102x102+151+6 +tracks-ibase.png : override CROP=246x246 118x118+6+6 +twiddle-ibase.png : override CROP=192x192 102x102+69+21 +undead-ibase.png : override CROP=416x480 192x192+16+80 +unequal-ibase.png : override CROP=208x208 104x104+104+104 +untangle-ibase.png : override CROP=320x320 164x164+3+116 +$(IBASE): %-ibase.png: %-base.png + $(PIC)crop.sh $^ $@ $(CROP) + +# Convert the full-size icon images to 4-bit colour, because that +# seems to work better than reducing it in 24 bits and then +# dithering. +$(IBASE4): %-ibase4.png: %-ibase.png + convert -colors 16 +dither $(CSP) -map $(PIC)win16pal.xpm $^ $@ + +# Build the 24-bit PNGs for the icons, at three sizes. +$(P48D24): %-48d24.png: %-ibase.png + $(PIC)square.pl 48 4 $^ $@ +$(P32D24): %-32d24.png: %-ibase.png + $(PIC)square.pl 32 2 $^ $@ +$(P16D24): %-16d24.png: %-ibase.png + $(PIC)square.pl 16 1 $^ $@ + +# The 8-bit icon PNGs are just custom-paletted quantisations of the +# 24-bit ones. +$(P48D8) $(P32D8) $(P16D8): %d8.png: %d24.png + convert -colors 256 $^ $@ + +# But the depth-4 images work better if we re-shrink from the +# ibase4 versions of the images, and then normalise the colours +# again afterwards. (They're still not very good, but my hope is +# that on most modern Windows machines this won't matter too +# much...) +$(P48D4): %-48d4.png: %-ibase4.png + $(PIC)square.pl 48 1 $^ $@-tmp2.png + convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@ + rm -f $@-tmp2.png +$(P32D4): %-32d4.png: %-ibase.png + $(PIC)square.pl 32 1 $^ $@-tmp2.png + convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@ + rm -f $@-tmp2.png +$(P16D4): %-16d4.png: %-ibase.png + $(PIC)square.pl 16 1 $^ $@-tmp2.png + convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@ + rm -f $@-tmp2.png + +# Build the actual Windows icons themselves, by feeding all those +# PNGs to my icon builder script. +$(ICONS): %.ico: %-48d24.png %-48d8.png %-48d4.png \ + %-32d24.png %-32d8.png %-32d4.png \ + %-16d24.png %-16d8.png %-16d4.png + $(PIC)icon.pl -24 $*-48d24.png $*-32d24.png $*-16d24.png \ + -8 $*-48d8.png $*-32d8.png $*-16d8.png \ + -4 $*-48d4.png $*-32d4.png $*-16d4.png > $@ + +# Build the .RC files which bind the icons into the applications. +$(RC): %.rc: + echo '#include "puzzles.rc2"' > $@ + echo '200 ICON "$*.ico"' >> $@ + +# Build the GTK icon source files. +$(CICONS): %-icon.c: %-16d24.png %-32d24.png %-48d24.png + $(PIC)cicon.pl $^ > $@ + +clean: + rm -f *.png *.ico *.rc *-icon.c diff --git a/apps/plugins/puzzles/icons/blackbox.sav b/apps/plugins/puzzles/icons/blackbox.sav new file mode 100644 index 0000000000..4483f3c81a --- /dev/null +++ b/apps/plugins/puzzles/icons/blackbox.sav @@ -0,0 +1,27 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :9:Black Box +PARAMS :8:w8h8m5M5 +CPARAMS :8:w8h8m5M5 +SEED :15:999785320716678 +DESC :24:c8b9f8528193756b9a2fd24d +UI :2:E0 +NSTATES :2:18 +STATEPOS:2:13 +MOVE :2:F2 +MOVE :2:F4 +MOVE :2:F5 +MOVE :3:F25 +MOVE :3:F26 +MOVE :3:F11 +MOVE :3:F12 +MOVE :3:F15 +MOVE :4:T7,7 +MOVE :4:T7,4 +MOVE :4:T1,7 +MOVE :2:F3 +MOVE :2:F9 +MOVE :3:F27 +MOVE :4:T5,7 +MOVE :4:T1,8 +MOVE :1:R diff --git a/apps/plugins/puzzles/icons/bridges.sav b/apps/plugins/puzzles/icons/bridges.sav new file mode 100644 index 0000000000..ddae7085cd --- /dev/null +++ b/apps/plugins/puzzles/icons/bridges.sav @@ -0,0 +1,72 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Bridges +PARAMS :15:10x10i30e10m2d2 +CPARAMS :15:10x10i30e10m2d2 +SEED :15:944199396008454 +DESC :49:a1a4a4b4k4b6b2a4b2b2b1e2e1a4b4c3j25d2a1f2c3a4d4c3 +AUXINFO :838:bd75eb5f7b129109b5cdcff0925c77ca5c0a135365002b93b44c5013c7a307b9504affcfb8ad934263196fc3e6d0b023abe48d254d46d29520e50a5e423c0fb1bc01ccc51cad61045c439e7c2bb8e5788bc7f3622aaa3a8125ebde11c9cd69b6f2393246fd094ad91e81ae58cd557b73bd1c9839cfad5835c8519e44298204eaca58dfd79289546959bfbabdc5f3cb7a27b8d3fb2d0b062bd5c2e469493c19f8c89989df73d8a3ab02d9afcbfedf245306d15881a01d153122f8374c7526abecc90919f99ff62e9789cabc402249af095ceb14c8c59c0d9ffbcdd731d50114e7c30c31ef0638f4d352abbfd04b4315d368d65bbfe005b6586245bc5244e5050098cf4c1b6986120f40d5ce038c10a3f309286f950cdc287e495aa13c70ab0c1f113a135556d7ce895fd8244afcbad43fe51f275837f223a1cb95151de8a158cb0add7fa8c9f1fa0e09a1ce842136c1679144cead56b164c4ef1a09ed36fd9704ba191b5957bc3d5bb97d8a1f7451d357a6638ac320b0beb0cd35aa404c8f1621c6d400960aa97bf6ce3a944339d7e401c4d98c31773b2a8881352d5653fdb5e8f7c04b +NSTATES :2:63 +STATEPOS:2:41 +MOVE :10:L8,0,5,0,1 +MOVE :10:L8,0,5,0,2 +MOVE :10:L8,0,8,2,1 +MOVE :10:L8,0,8,2,2 +MOVE :4:M8,0 +MOVE :10:L0,2,3,2,1 +MOVE :10:L0,2,3,2,2 +MOVE :10:L0,2,0,7,1 +MOVE :10:L0,2,0,7,2 +MOVE :4:M0,2 +MOVE :10:L1,0,3,0,1 +MOVE :4:M1,0 +MOVE :10:L3,0,5,0,1 +MOVE :10:L3,0,3,2,1 +MOVE :10:L1,3,1,5,1 +MOVE :10:L0,7,5,7,1 +MOVE :10:L0,7,0,9,1 +MOVE :10:L0,9,5,9,1 +MOVE :10:L0,9,5,9,2 +MOVE :10:L0,9,0,7,2 +MOVE :4:M0,9 +MOVE :4:M0,7 +MOVE :10:L4,8,8,8,1 +MOVE :10:L4,8,8,8,2 +MOVE :4:M4,8 +MOVE :10:L5,9,9,9,1 +MOVE :10:L5,9,9,9,2 +MOVE :4:M5,9 +MOVE :10:L9,9,9,6,1 +MOVE :4:M9,9 +MOVE :10:L8,8,8,5,1 +MOVE :4:M8,8 +MOVE :10:L9,6,9,4,1 +MOVE :4:M9,6 +MOVE :4:M9,4 +MOVE :10:L1,5,4,5,1 +MOVE :10:L1,5,4,5,2 +MOVE :10:L1,5,1,3,2 +MOVE :4:M1,3 +MOVE :4:M1,5 +MOVE :10:L3,4,3,2,1 +MOVE :10:L3,4,3,2,2 +MOVE :4:M3,4 +MOVE :10:L4,5,8,5,1 +MOVE :10:L7,7,5,7,1 +MOVE :4:M5,7 +MOVE :4:M7,7 +MOVE :10:L7,3,4,3,1 +MOVE :4:M7,3 +MOVE :10:L5,0,3,0,2 +MOVE :4:M5,0 +MOVE :4:M3,0 +MOVE :10:L3,2,6,2,1 +MOVE :4:M3,2 +MOVE :10:L6,2,8,2,1 +MOVE :4:M6,2 +MOVE :10:L8,2,8,5,1 +MOVE :4:M8,2 +MOVE :4:M8,5 +MOVE :10:L4,5,4,3,1 +MOVE :4:M4,3 +MOVE :4:M4,5 diff --git a/apps/plugins/puzzles/icons/cicon.pl b/apps/plugins/puzzles/icons/cicon.pl new file mode 100755 index 0000000000..3578bd33fe --- /dev/null +++ b/apps/plugins/puzzles/icons/cicon.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +# Given a list of input PNGs, create a C source file file +# containing a const array of XPMs, under the name `xpm_icon'. + +$k = 0; +@xpms = (); +foreach $f (@ARGV) { + # XPM format is generated directly by ImageMagick, so that's easy + # enough. We just have to adjust the declaration line so that it + # has the right name, linkage and storage class. + @lines = (); + open XPM, "convert $f xpm:- |"; + push @lines, $_ while ; + close XPM; + die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/; + $lines[1] = "static const char *const xpm_icon_$k"."[] = {\n"; + $k++; + push @xpms, @lines, "\n"; +} + +# Now output. +foreach $line (@xpms) { print $line; } +print "const char *const *const xpm_icons[] = {\n"; +for ($i = 0; $i < $k; $i++) { print " xpm_icon_$i,\n"; } +print "};\n"; +print "const int n_xpm_icons = $k;\n"; diff --git a/apps/plugins/puzzles/icons/crop.sh b/apps/plugins/puzzles/icons/crop.sh new file mode 100755 index 0000000000..0d15d3c9b9 --- /dev/null +++ b/apps/plugins/puzzles/icons/crop.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# Crop one image into another, after first checking that the source +# image has the expected size in pixels. +# +# This is used in the Puzzles icon build scripts to construct icons +# which are zoomed in on a particular sub-area of the puzzle's +# basic screenshot. This way I can define crop areas in pixels, +# while not having to worry too much that if I adjust the source +# puzzle so as to alter the layout the crop area might start +# hitting the wrong bit of picture. Most layout changes I can +# conveniently imagine will also alter the overall image size, so +# this script will give a build error and alert me to the fact that +# I need to fiddle with the icon makefile. + +infile="$1" +outfile="$2" +insize="$3" +crop="$4" + +# Special case: if no input size or crop parameter was specified at +# all, we just copy the input to the output file. + +if test $# -lt 3; then + cp "$infile" "$outfile" + exit 0 +fi + +# Check the input image size. +realsize=`identify -format %wx%h "$infile"` +if test "x$insize" != "x$realsize"; then + echo "crop.sh: '$infile' has wrong initial size: $realsize != $insize" >&2 + exit 1 +fi + +# And crop. +convert -crop "$crop" "$infile" "$outfile" diff --git a/apps/plugins/puzzles/icons/cube.sav b/apps/plugins/puzzles/icons/cube.sav new file mode 100644 index 0000000000..bb123f4e74 --- /dev/null +++ b/apps/plugins/puzzles/icons/cube.sav @@ -0,0 +1,22 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :4:Cube +PARAMS :4:c4x4 +CPARAMS :4:c4x4 +SEED :15:125146248070163 +DESC :6:C461,3 +NSTATES :2:14 +STATEPOS:1:5 +MOVE :1:D +MOVE :1:D +MOVE :1:D +MOVE :1:U +MOVE :1:L +MOVE :1:L +MOVE :1:D +MOVE :1:U +MOVE :1:D +MOVE :1:U +MOVE :1:U +MOVE :1:U +MOVE :1:L diff --git a/apps/plugins/puzzles/icons/dominosa.sav b/apps/plugins/puzzles/icons/dominosa.sav new file mode 100644 index 0000000000..5991f3e57e --- /dev/null +++ b/apps/plugins/puzzles/icons/dominosa.sav @@ -0,0 +1,53 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Dominosa +PARAMS :1:6 +CPARAMS :1:6 +DESC :56:55521461210004364611033535444421636022603153156422620503 +NSTATES :2:46 +STATEPOS:2:33 +MOVE :6:D18,19 +MOVE :6:E22,23 +MOVE :6:E22,30 +MOVE :5:E9,17 +MOVE :6:D38,46 +MOVE :6:E14,15 +MOVE :5:E6,14 +MOVE :6:E33,34 +MOVE :6:E34,42 +MOVE :6:E26,34 +MOVE :6:D34,35 +MOVE :6:E42,50 +MOVE :6:E16,24 +MOVE :4:D4,5 +MOVE :4:D6,7 +MOVE :6:D15,23 +MOVE :6:E17,25 +MOVE :6:D16,17 +MOVE :6:E11,12 +MOVE :6:D51,52 +MOVE :5:E3,11 +MOVE :6:D10,11 +MOVE :4:D2,3 +MOVE :6:E37,45 +MOVE :6:D49,50 +MOVE :6:D40,48 +MOVE :6:D25,26 +MOVE :6:D24,32 +MOVE :6:D33,41 +MOVE :6:D42,43 +MOVE :6:D27,28 +MOVE :6:E21,29 +MOVE :6:D31,39 +MOVE :6:D47,55 +MOVE :6:E13,21 +MOVE :6:E13,14 +MOVE :6:D12,13 +MOVE :6:D20,21 +MOVE :6:D14,22 +MOVE :6:D29,30 +MOVE :6:D36,37 +MOVE :6:D44,45 +MOVE :6:D53,54 +MOVE :4:D0,1 +MOVE :4:D8,9 diff --git a/apps/plugins/puzzles/icons/fifteen.sav b/apps/plugins/puzzles/icons/fifteen.sav new file mode 100644 index 0000000000..d81345a7d8 --- /dev/null +++ b/apps/plugins/puzzles/icons/fifteen.sav @@ -0,0 +1,74 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Fifteen +PARAMS :3:4x4 +CPARAMS :3:4x4 +SEED :15:307905346810973 +DESC :37:8,11,3,6,14,13,4,2,0,9,12,10,5,1,7,15 +NSTATES :2:66 +STATEPOS:2:47 +MOVE :4:M1,2 +MOVE :4:M1,3 +MOVE :4:M0,3 +MOVE :4:M0,1 +MOVE :4:M1,1 +MOVE :4:M1,2 +MOVE :4:M0,2 +MOVE :4:M0,0 +MOVE :4:M1,0 +MOVE :4:M1,1 +MOVE :4:M0,1 +MOVE :4:M0,0 +MOVE :4:M1,0 +MOVE :4:M3,0 +MOVE :4:M3,1 +MOVE :4:M1,1 +MOVE :4:M1,0 +MOVE :4:M3,0 +MOVE :4:M3,1 +MOVE :4:M1,1 +MOVE :4:M1,0 +MOVE :4:M2,0 +MOVE :4:M2,1 +MOVE :4:M1,1 +MOVE :4:M1,3 +MOVE :4:M0,3 +MOVE :4:M0,1 +MOVE :4:M1,1 +MOVE :4:M1,2 +MOVE :4:M0,2 +MOVE :4:M0,1 +MOVE :4:M2,1 +MOVE :4:M3,1 +MOVE :4:M3,2 +MOVE :4:M2,2 +MOVE :4:M2,3 +MOVE :4:M3,3 +MOVE :4:M3,1 +MOVE :4:M2,1 +MOVE :4:M2,2 +MOVE :4:M1,2 +MOVE :4:M1,3 +MOVE :4:M2,3 +MOVE :4:M2,2 +MOVE :4:M1,2 +MOVE :4:M0,2 +MOVE :4:M0,3 +MOVE :4:M1,3 +MOVE :4:M1,2 +MOVE :4:M2,2 +MOVE :4:M2,3 +MOVE :4:M0,3 +MOVE :4:M0,2 +MOVE :4:M1,2 +MOVE :4:M2,2 +MOVE :4:M2,3 +MOVE :4:M1,3 +MOVE :4:M1,2 +MOVE :4:M3,2 +MOVE :4:M3,3 +MOVE :4:M1,3 +MOVE :4:M1,2 +MOVE :4:M2,2 +MOVE :4:M2,3 +MOVE :4:M3,3 diff --git a/apps/plugins/puzzles/icons/filling.sav b/apps/plugins/puzzles/icons/filling.sav new file mode 100644 index 0000000000..caf0bb2d4c --- /dev/null +++ b/apps/plugins/puzzles/icons/filling.sav @@ -0,0 +1,38 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Filling +PARAMS :3:7x7 +CPARAMS :3:7x7 +SEED :15:279172739852696 +DESC :49:0000000031051240010004000001106171000400001013105 +NSTATES :2:30 +STATEPOS:2:13 +MOVE :4:38_3 +MOVE :4:39_3 +MOVE :4:36_4 +MOVE :4:43_4 +MOVE :4:35_4 +MOVE :4:47_5 +MOVE :4:40_5 +MOVE :4:34_5 +MOVE :4:41_5 +MOVE :4:25_7 +MOVE :4:23_6 +MOVE :4:16_6 +MOVE :4:18_7 +MOVE :4:19_7 +MOVE :4:20_7 +MOVE :4:26_7 +MOVE :4:24_7 +MOVE :4:29_6 +MOVE :4:22_6 +MOVE :4:15_6 +MOVE :3:7_4 +MOVE :3:0_4 +MOVE :3:1_3 +MOVE :3:2_3 +MOVE :3:6_2 +MOVE :3:5_5 +MOVE :3:4_5 +MOVE :3:3_5 +MOVE :4:10_5 diff --git a/apps/plugins/puzzles/icons/flip.sav b/apps/plugins/puzzles/icons/flip.sav new file mode 100644 index 0000000000..82b4c49357 --- /dev/null +++ b/apps/plugins/puzzles/icons/flip.sav @@ -0,0 +1,20 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :4:Flip +PARAMS :4:5x5c +CPARAMS :4:5x5c +SEED :15:158897339725978 +DESC :165:c400007100001c4000071000018400043100011c400047100011c400046100010c400047100011c4000471000118400043100011c400047100011c400046100010c000047000011c0000470000118,5c18b48 +NSTATES :2:12 +STATEPOS:1:4 +MOVE :4:M4,3 +MOVE :4:M3,0 +MOVE :4:M2,2 +MOVE :4:M3,2 +MOVE :4:M2,3 +MOVE :4:M0,2 +MOVE :4:M0,3 +MOVE :4:M1,4 +MOVE :4:M0,0 +MOVE :4:M1,0 +MOVE :4:M1,1 diff --git a/apps/plugins/puzzles/icons/flood.sav b/apps/plugins/puzzles/icons/flood.sav new file mode 100644 index 0000000000..ac4adf7020 --- /dev/null +++ b/apps/plugins/puzzles/icons/flood.sav @@ -0,0 +1,14 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Flood +PARAMS :7:6x6c6m5 +CPARAMS :7:6x6c6m5 +SEED :15:967543368167853 +DESC :39:032242034203340350204502505323231342,17 +NSTATES :1:6 +STATEPOS:1:6 +MOVE :2:M3 +MOVE :2:M2 +MOVE :2:M0 +MOVE :2:M5 +MOVE :2:M3 diff --git a/apps/plugins/puzzles/icons/galaxies.sav b/apps/plugins/puzzles/icons/galaxies.sav new file mode 100644 index 0000000000..42d18bc335 --- /dev/null +++ b/apps/plugins/puzzles/icons/galaxies.sav @@ -0,0 +1,51 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Galaxies +PARAMS :5:7x7dh +CPARAMS :5:7x7dh +SEED :15:483644862786314 +DESC :13:ikrqfedzljjsp +NSTATES :2:43 +STATEPOS:2:37 +MOVE :5:E12,9 +MOVE :5:E8,11 +MOVE :5:E5,12 +MOVE :5:E7,12 +MOVE :5:E4,13 +MOVE :5:E2,11 +MOVE :5:E3,10 +MOVE :4:E2,5 +MOVE :4:E4,5 +MOVE :4:E6,7 +MOVE :4:E8,1 +MOVE :5:E10,1 +MOVE :4:E9,2 +MOVE :4:E6,3 +MOVE :4:E7,4 +MOVE :5:E10,3 +MOVE :5:E10,5 +MOVE :5:E11,6 +MOVE :5:E13,6 +MOVE :5:E8,13 +MOVE :5:E12,7 +MOVE :6:E12,11 +MOVE :6:E13,12 +MOVE :4:E8,9 +MOVE :4:E7,8 +MOVE :4:E7,6 +MOVE :4:E9,6 +MOVE :4:E8,5 +MOVE :4:E9,4 +MOVE :4:E5,2 +MOVE :4:E4,1 +MOVE :4:E3,6 +MOVE :4:E2,7 +MOVE :4:E3,8 +MOVE :4:E3,4 +MOVE :4:E4,9 +MOVE :4:E2,9 +MOVE :5:E5,10 +MOVE :5:E6,11 +MOVE :4:E2,3 +MOVE :4:E2,1 +MOVE :5:E1,12 diff --git a/apps/plugins/puzzles/icons/guess.sav b/apps/plugins/puzzles/icons/guess.sav new file mode 100644 index 0000000000..69852bf769 --- /dev/null +++ b/apps/plugins/puzzles/icons/guess.sav @@ -0,0 +1,15 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Guess +PARAMS :9:c6p4g10Bm +CPARAMS :9:c6p4g10Bm +SEED :15:313100730915729 +DESC :8:b5f3faed +UI :7:0,0,0,0 +NSTATES :1:6 +STATEPOS:1:6 +MOVE :8:G1,1,2,2 +MOVE :8:G4,3,1,1 +MOVE :8:G5,5,1,1 +MOVE :8:G4,2,1,6 +MOVE :8:G2,3,1,6 diff --git a/apps/plugins/puzzles/icons/icon.pl b/apps/plugins/puzzles/icons/icon.pl new file mode 100755 index 0000000000..fcb1aa3e2c --- /dev/null +++ b/apps/plugins/puzzles/icons/icon.pl @@ -0,0 +1,270 @@ +#!/usr/bin/perl + +# Take a collection of input image files and convert them into a +# multi-resolution Windows .ICO icon file. +# +# The input images can be treated as having four different colour +# depths: +# +# - 24-bit true colour +# - 8-bit with custom palette +# - 4-bit using the Windows 16-colour palette (see comment below +# for details) +# - 1-bit using black and white only. +# +# The images can be supplied in any input format acceptable to +# ImageMagick, but their actual colour usage must already be +# appropriate for the specified mode; this script will not do any +# substantive conversion. So if an image intended to be used in 4- +# or 1-bit mode contains any colour not in the appropriate fixed +# palette, that's a fatal error; if an image to be used in 8-bit +# mode contains more than 256 distinct colours, that's also a fatal +# error. +# +# Command-line syntax is: +# +# icon.pl -depth imagefile [imagefile...] [-depth imagefile [imagefile...]] +# +# where `-depth' is one of `-24', `-8', `-4' or `-1', and tells the +# script how to treat all the image files given after that option +# until the next depth option. For example, you might execute +# +# icon.pl -24 48x48x24.png 32x32x24.png -8 32x32x8.png -1 monochrome.png +# +# to build an icon file containing two differently sized 24-bit +# images, one 8-bit image and one black and white image. +# +# Windows .ICO files support a 1-bit alpha channel on all these +# image types. That is, any pixel can be either opaque or fully +# transparent, but not partially transparent. The alpha channel is +# separate from the main image data, meaning that `transparent' is +# not required to take up a palette entry. (So an 8-bit image can +# have 256 distinct _opaque_ colours, plus transparent pixels as +# well.) If the input images have alpha channels, they will be used +# to determine which pixels of the icon are transparent, by simple +# quantisation half way up (e.g. in a PNG image with an 8-bit alpha +# channel, alpha values of 00-7F will be mapped to transparent +# pixels, and 80-FF will become opaque). + +# The Windows 16-colour palette consists of: +# - the eight corners of the colour cube (000000, 0000FF, 00FF00, +# 00FFFF, FF0000, FF00FF, FFFF00, FFFFFF) +# - dim versions of the seven non-black corners, at 128/255 of the +# brightness (000080, 008000, 008080, 800000, 800080, 808000, +# 808080) +# - light grey at 192/255 of full brightness (C0C0C0). +%win16pal = ( + "\x00\x00\x00\x00" => 0, + "\x00\x00\x80\x00" => 1, + "\x00\x80\x00\x00" => 2, + "\x00\x80\x80\x00" => 3, + "\x80\x00\x00\x00" => 4, + "\x80\x00\x80\x00" => 5, + "\x80\x80\x00\x00" => 6, + "\xC0\xC0\xC0\x00" => 7, + "\x80\x80\x80\x00" => 8, + "\x00\x00\xFF\x00" => 9, + "\x00\xFF\x00\x00" => 10, + "\x00\xFF\xFF\x00" => 11, + "\xFF\x00\x00\x00" => 12, + "\xFF\x00\xFF\x00" => 13, + "\xFF\xFF\x00\x00" => 14, + "\xFF\xFF\xFF\x00" => 15, +); +@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal; + +# The black and white palette consists of black (000000) and white +# (FFFFFF), obviously. +%win2pal = ( + "\x00\x00\x00\x00" => 0, + "\xFF\xFF\xFF\x00" => 1, +); +@win2pal = sort { $win16pal{$a} <=> $win2pal{$b} } keys %win2pal; + +@hdr = (); +@dat = (); + +$depth = undef; +foreach $_ (@ARGV) { + if (/^-(24|8|4|1)$/) { + $depth = $1; + } elsif (defined $depth) { + &readicon($_, $depth); + } else { + $usage = 1; + } +} +if ($usage || length @hdr == 0) { + print "usage: icon.pl ( -24 | -8 | -4 | -1 ) image [image...]\n"; + print " [ ( -24 | -8 | -4 | -1 ) image [image...] ...]\n"; + exit 0; +} + +# Now write out the output icon file. +print pack "vvv", 0, 1, scalar @hdr; # file-level header +$filepos = 6 + 16 * scalar @hdr; +for ($i = 0; $i < scalar @hdr; $i++) { + print $hdr[$i]; + print pack "V", $filepos; + $filepos += length($dat[$i]); +} +for ($i = 0; $i < scalar @hdr; $i++) { + print $dat[$i]; +} + +sub readicon { + my $filename = shift @_; + my $depth = shift @_; + my $pix; + my $i; + my %pal; + + # Determine the icon's width and height. + my $w = `identify -format %w $filename`; + my $h = `identify -format %h $filename`; + + # Read the file in as RGBA data. We flip vertically at this + # point, to avoid having to do it ourselves (.BMP and hence + # .ICO are bottom-up). + my $data = []; + open IDATA, "convert -set colorspace sRGB -flip -depth 8 $filename rgba:- |"; + push @$data, $rgb while (read IDATA,$rgb,4,0) == 4; + close IDATA; + # Check we have the right amount of data. + $xl = $w * $h; + $al = scalar @$data; + die "wrong amount of image data ($al, expected $xl) from $filename\n" + unless $al == $xl; + + # Build the alpha channel now, so we can exclude transparent + # pixels from the palette analysis. We replace transparent + # pixels with undef in the data array. + # + # We quantise the alpha channel half way up, so that alpha of + # 0x80 or more is taken to be fully opaque and 0x7F or less is + # fully transparent. Nasty, but the best we can do without + # dithering (and don't even suggest we do that!). + my $x; + my $y; + my $alpha = ""; + + for ($y = 0; $y < $h; $y++) { + my $currbyte = 0, $currbits = 0; + for ($x = 0; $x < (($w+31)|31)-31; $x++) { + $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF"); + my @rgba = unpack "CCCC", $pix; + $currbyte <<= 1; + $currbits++; + if ($rgba[3] < 0x80) { + if ($x < $w) { + $data->[$y*$w+$x] = undef; + } + $currbyte |= 1; # MS has the alpha channel inverted :-) + } else { + # Might as well flip RGBA into BGR0 while we're here. + if ($x < $w) { + $data->[$y*$w+$x] = pack "CCCC", + $rgba[2], $rgba[1], $rgba[0], 0; + } + } + if ($currbits >= 8) { + $alpha .= pack "C", $currbyte; + $currbits -= 8; + } + } + } + + # For an 8-bit image, check we have at most 256 distinct + # colours, and build the palette. + %pal = (); + if ($depth == 8) { + my $palindex = 0; + foreach $pix (@$data) { + next unless defined $pix; + $pal{$pix} = $palindex++ unless defined $pal{$pix}; + } + die "too many colours in 8-bit image $filename\n" unless $palindex <= 256; + } elsif ($depth == 4) { + %pal = %win16pal; + } elsif ($depth == 1) { + %pal = %win2pal; + } + + my $raster = ""; + if ($depth < 24) { + # For a non-24-bit image, flatten the image into one palette + # index per pixel. + $pad = 32 / $depth; # number of pixels to pad scanline to 4-byte align + $pmask = $pad-1; + for ($y = 0; $y < $h; $y++) { + my $currbyte = 0, $currbits = 0; + for ($x = 0; $x < (($w+$pmask)|$pmask)-$pmask; $x++) { + $currbyte <<= $depth; + $currbits += $depth; + if ($x < $w && defined ($pix = $data->[$y*$w+$x])) { + if (!defined $pal{$pix}) { + my $pixprintable = unpack "H*", $pix; + die "illegal colour value $pixprintable at pixel ($x,$y) in $filename\n"; + } + $currbyte |= $pal{$pix}; + } + if ($currbits >= 8) { + $raster .= pack "C", $currbyte; + $currbits -= 8; + } + } + } + } else { + # For a 24-bit image, reverse the order of the R,G,B values + # and stick a padding zero on the end. + # + # (In this loop we don't need to bother padding the + # scanline out to a multiple of four bytes, because every + # pixel takes four whole bytes anyway.) + for ($i = 0; $i < scalar @$data; $i++) { + if (defined $data->[$i]) { + $raster .= $data->[$i]; + } else { + $raster .= "\x00\x00\x00\x00"; + } + } + $depth = 32; # and adjust this + } + + # Prepare the icon data. First the header... + my $data = pack "VVVvvVVVVVV", + 40, # size of bitmap info header + $w, # icon width + $h*2, # icon height (x2 to indicate the subsequent alpha channel) + 1, # 1 plane (common to all MS image formats) + $depth, # bits per pixel + 0, # no compression + length $raster, # image size + 0, 0, 0, 0; # resolution, colours used, colours important (ignored) + # ... then the palette ... + if ($depth <= 8) { + my $ncols = (1 << $depth); + my $palette = "\x00\x00\x00\x00" x $ncols; + foreach $i (keys %pal) { + substr($palette, $pal{$i}*4, 4) = $i; + } + $data .= $palette; + } + # ... the raster data we already had ready ... + $data .= $raster; + # ... and the alpha channel we already had as well. + $data .= $alpha; + + # Prepare the header which will represent this image in the + # icon file. + my $header = pack "CCCCvvV", + $w, $h, # width and height (this time the real height) + 1 << $depth, # number of colours, if less than 256 + 0, # reserved + 1, # planes + $depth, # bits per pixel + length $data; # size of real icon data + + push @hdr, $header; + push @dat, $data; +} diff --git a/apps/plugins/puzzles/icons/inertia.sav b/apps/plugins/puzzles/icons/inertia.sav new file mode 100644 index 0000000000..a6d6faed1b --- /dev/null +++ b/apps/plugins/puzzles/icons/inertia.sav @@ -0,0 +1,30 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Inertia +PARAMS :3:8x8 +CPARAMS :3:8x8 +SEED :15:739970145068932 +DESC :64:sbgwsmswwgggwggmmbwgwbssbwbsbwbbwsSmgbbsbbmsgbmssgmggbmmwmbmmwsw +UI :2:D0 +NSTATES :2:21 +STATEPOS:1:5 +MOVE :1:6 +MOVE :1:5 +MOVE :1:3 +MOVE :1:1 +MOVE :1:6 +MOVE :1:0 +MOVE :1:5 +MOVE :1:7 +MOVE :1:5 +MOVE :1:6 +MOVE :1:1 +MOVE :1:3 +MOVE :1:4 +MOVE :1:2 +MOVE :1:6 +MOVE :1:5 +MOVE :1:3 +MOVE :1:1 +MOVE :1:5 +MOVE :1:3 diff --git a/apps/plugins/puzzles/icons/keen.sav b/apps/plugins/puzzles/icons/keen.sav new file mode 100644 index 0000000000..4adbd42d17 --- /dev/null +++ b/apps/plugins/puzzles/icons/keen.sav @@ -0,0 +1,62 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :4:Keen +PARAMS :3:5de +CPARAMS :3:5de +SEED :15:846699649745236 +DESC :48:a__a_3a_5a_a_a_3b_bac_,a5m15a7a10s2s2d2s3m40m2s2 +AUXINFO :52:6105af67c6ebc8de056b59ebc9a463aa54e75f647055c0a6c1bd +NSTATES :2:53 +STATEPOS:2:39 +MOVE :6:P0,4,2 +MOVE :6:P0,4,4 +MOVE :6:P0,4,5 +MOVE :6:P1,4,2 +MOVE :6:P1,4,4 +MOVE :6:P1,4,5 +MOVE :6:P1,3,2 +MOVE :6:P1,3,4 +MOVE :6:P1,3,5 +MOVE :6:R2,2,4 +MOVE :6:R1,2,2 +MOVE :6:P1,3,2 +MOVE :6:P1,4,2 +MOVE :6:R0,4,2 +MOVE :6:R2,3,2 +MOVE :6:R2,4,1 +MOVE :6:R1,4,4 +MOVE :6:R1,3,5 +MOVE :6:P3,4,3 +MOVE :6:P3,4,5 +MOVE :6:P4,4,3 +MOVE :6:P4,4,5 +MOVE :6:R4,4,5 +MOVE :6:R3,4,3 +MOVE :6:P3,1,2 +MOVE :6:P3,1,5 +MOVE :6:P3,0,2 +MOVE :6:P3,0,5 +MOVE :6:R3,2,1 +MOVE :6:R3,3,4 +MOVE :6:P2,0,3 +MOVE :6:P2,0,5 +MOVE :6:P2,1,3 +MOVE :6:P2,1,5 +MOVE :6:P0,1,1 +MOVE :6:P0,1,3 +MOVE :6:P1,1,1 +MOVE :6:P1,1,3 +MOVE :6:R2,0,3 +MOVE :6:R2,1,5 +MOVE :6:R3,0,5 +MOVE :6:R3,1,2 +MOVE :6:R4,1,4 +MOVE :6:R4,2,3 +MOVE :6:R4,0,2 +MOVE :6:R4,3,1 +MOVE :6:R0,2,5 +MOVE :6:R0,3,3 +MOVE :6:R1,1,3 +MOVE :6:R0,1,1 +MOVE :6:R1,0,1 +MOVE :6:R0,0,4 diff --git a/apps/plugins/puzzles/icons/lightup.sav b/apps/plugins/puzzles/icons/lightup.sav new file mode 100644 index 0000000000..21b59bdec4 --- /dev/null +++ b/apps/plugins/puzzles/icons/lightup.sav @@ -0,0 +1,24 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Light Up +PARAMS :10:7x7b20s4d1 +CPARAMS :10:7x7b25s4d1 +SEED :15:538922615851330 +DESC :25:b3gB0c0dBaBaBa1aBd1c01g0b +NSTATES :2:16 +STATEPOS:2:16 +MOVE :4:L1,0 +MOVE :4:L2,1 +MOVE :4:L3,0 +MOVE :4:L0,3 +MOVE :4:L6,1 +MOVE :4:L3,4 +MOVE :4:I6,5 +MOVE :4:I1,5 +MOVE :4:I2,6 +MOVE :4:I3,6 +MOVE :4:I4,5 +MOVE :4:I5,6 +MOVE :4:L5,5 +MOVE :4:I6,4 +MOVE :4:I4,2 diff --git a/apps/plugins/puzzles/icons/loopy.sav b/apps/plugins/puzzles/icons/loopy.sav new file mode 100644 index 0000000000..11611818af --- /dev/null +++ b/apps/plugins/puzzles/icons/loopy.sav @@ -0,0 +1,120 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Loopy +PARAMS :7:7x7t0de +CPARAMS :7:7x7t0de +DESC :31:02g222b3b2e2a2b322b2a2a3a2a1d1b +NSTATES :3:113 +STATEPOS:2:75 +MOVE :2:3n +MOVE :2:0n +MOVE :2:1n +MOVE :2:2n +MOVE :2:4n +MOVE :2:6y +MOVE :2:5y +MOVE :3:25n +MOVE :2:9n +MOVE :3:28y +MOVE :3:27y +MOVE :3:42n +MOVE :3:30n +MOVE :3:45y +MOVE :3:44y +MOVE :3:59n +MOVE :3:47n +MOVE :3:62y +MOVE :3:61y +MOVE :3:76n +MOVE :3:64n +MOVE :3:79y +MOVE :3:78y +MOVE :3:93n +MOVE :3:81n +MOVE :4:110y +MOVE :4:111y +MOVE :3:99y +MOVE :3:98y +MOVE :3:24n +MOVE :3:39y +MOVE :3:23y +MOVE :3:22y +MOVE :3:26n +MOVE :3:37n +MOVE :3:38y +MOVE :3:54n +MOVE :3:69y +MOVE :3:53y +MOVE :3:40y +MOVE :2:7y +MOVE :3:88y +MOVE :3:87y +MOVE :4:102n +MOVE :3:90n +MOVE :3:52n +MOVE :3:41y +MOVE :3:55n +MOVE :3:43n +MOVE :2:8n +MOVE :3:10y +MOVE :3:12y +MOVE :3:11n +MOVE :3:13y +MOVE :3:29n +MOVE :3:15y +MOVE :3:14n +MOVE :3:16y +MOVE :3:32y +MOVE :3:31n +MOVE :3:18y +MOVE :3:17n +MOVE :3:19y +MOVE :3:20y +MOVE :3:21n +MOVE :3:33y +MOVE :3:35y +MOVE :3:36n +MOVE :3:50y +MOVE :3:57y +MOVE :3:58y +MOVE :3:72n +MOVE :3:60n +MOVE :3:74y +MOVE :4:104y +MOVE :4:107n +MOVE :4:106n +MOVE :3:92n +MOVE :4:109n +MOVE :3:89y +MOVE :3:77n +MOVE :3:75n +MOVE :3:73y +MOVE :3:85n +MOVE :3:70n +MOVE :3:56y +MOVE :3:67n +MOVE :3:71y +MOVE :3:68y +MOVE :3:84n +MOVE :3:82n +MOVE :3:83y +MOVE :3:97n +MOVE :3:86y +MOVE :4:101y +MOVE :4:100n +MOVE :4:103y +MOVE :4:105y +MOVE :4:108y +MOVE :3:96n +MOVE :3:95y +MOVE :3:94y +MOVE :3:91y +MOVE :3:80y +MOVE :3:66n +MOVE :3:65y +MOVE :3:63y +MOVE :3:51n +MOVE :3:46n +MOVE :3:34y +MOVE :3:48n +MOVE :3:49y diff --git a/apps/plugins/puzzles/icons/magnets.sav b/apps/plugins/puzzles/icons/magnets.sav new file mode 100644 index 0000000000..3c317b70ce --- /dev/null +++ b/apps/plugins/puzzles/icons/magnets.sav @@ -0,0 +1,33 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Magnets +PARAMS :6:6x5dtS +CPARAMS :6:6x5dtS +SEED :15:705856238774945 +DESC :56:2.2..1,.3.2.,2.21..,2..0.,TLRTLRBLRBTTLRLRBBLRTTTTLRBBBB +AUXINFO :60:ebae280db3eec279c628b6cfe4aca5a03ba24d7eba91169f1bdf275fce3f +NSTATES :2:24 +STATEPOS:2:15 +MOVE :4:.1,3 +MOVE :4:.0,1 +MOVE :4:?0,1 +MOVE :4:.2,1 +MOVE :4:?2,1 +MOVE :4:.2,4 +MOVE :4:?2,4 +MOVE :4:+2,3 +MOVE :4:.3,3 +MOVE :4:.0,2 +MOVE :4:?0,2 +MOVE :4:+1,4 +MOVE :4:+0,2 +MOVE :4:+0,0 +MOVE :4:+1,1 +MOVE :4:.2,2 +MOVE :4:+2,0 +MOVE :4:+3,1 +MOVE :4:.4,0 +MOVE :4:+5,1 +MOVE :4:.5,3 +MOVE :4:+4,3 +MOVE :4:.4,2 diff --git a/apps/plugins/puzzles/icons/map.sav b/apps/plugins/puzzles/icons/map.sav new file mode 100644 index 0000000000..33863e1eeb --- /dev/null +++ b/apps/plugins/puzzles/icons/map.sav @@ -0,0 +1,27 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :3:Map +PARAMS :10:20x20n30dn +CPARAMS :10:20x20n30dn +SEED :15:794003990129265 +DESC :264:dcbakatgcaaedaccabfabadbaaaagaiaaaeaiadcaaaafabccbdcaaecabggedfaebqbadgbngcblabdaadaaaeagabaaaacaacacbcaebabaabaebaafaaakabdhcdanaaceagakacbbajaaadbacbaaccbcbicdafbadgbaccbkcdaafbacbcaaabcddacaaaaddbabcdbbacabbhagajabbobcdjaecaafabahaaaffead,23a01c3c1a3d2a20b01a3a +AUXINFO :282:738e7a68c5d32445002968f3726646962b3604ef27a3657e0fdc0fd8180d5b747febd4619487bbc8bec5a48c709b154eb8da39c9b49be1e312a381fc2394e53126714079bd82e8444dad92419429635d1c816c53774b8c77b4ce03884c94d12bfb757cd93b5600471cb9726b3f2afe74d9932abeaa2efd6a496cad793ce5b221f943d620e883794f9d56741908 +NSTATES :2:18 +STATEPOS:2:12 +MOVE :4:3:20 +MOVE :4:0:24 +MOVE :4:3:10 +MOVE :4:1:18 +MOVE :4:2:11 +MOVE :4:3:17 +MOVE :4:1:23 +MOVE :4:3:27 +MOVE :4:1:29 +MOVE :4:0:16 +MOVE :4:2:13 +MOVE :3:1:6 +MOVE :3:2:2 +MOVE :3:0:7 +MOVE :3:2:9 +MOVE :4:0:15 +MOVE :3:3:5 diff --git a/apps/plugins/puzzles/icons/mines.sav b/apps/plugins/puzzles/icons/mines.sav new file mode 100644 index 0000000000..a827541163 --- /dev/null +++ b/apps/plugins/puzzles/icons/mines.sav @@ -0,0 +1,67 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Mines +PARAMS :6:9x9n35 +CPARAMS :6:9x9n35 +SEED :15:698938038698621 +DESC :26:0,0,me0691ca8a278f3c371688 +PRIVDESC:22:me0691ca8a278f3c371688 +UI :3:D0C +TIME :7:75.2958 +NSTATES :2:56 +STATEPOS:2:41 +MOVE :4:O0,0 +MOVE :4:F1,2 +MOVE :4:F0,2 +MOVE :4:O2,2 +MOVE :4:F2,1 +MOVE :4:F3,1 +MOVE :4:F3,2 +MOVE :4:F3,3 +MOVE :4:F1,3 +MOVE :4:F2,3 +MOVE :4:C1,0 +MOVE :4:C2,0 +MOVE :4:C3,0 +MOVE :4:F5,0 +MOVE :4:F5,1 +MOVE :4:C4,1 +MOVE :4:O6,1 +MOVE :4:O6,2 +MOVE :4:O6,3 +MOVE :4:F7,1 +MOVE :4:O7,4 +MOVE :4:O5,4 +MOVE :4:F5,3 +MOVE :4:F5,5 +MOVE :4:F6,6 +MOVE :4:C6,5 +MOVE :4:F8,6 +MOVE :4:C6,3 +MOVE :4:F8,2 +MOVE :4:C7,2 +MOVE :4:F8,0 +MOVE :4:F7,0 +MOVE :4:F6,0 +MOVE :4:C4,2 +MOVE :4:F4,4 +MOVE :4:F4,5 +MOVE :4:F3,4 +MOVE :4:C5,6 +MOVE :4:F7,7 +MOVE :4:F8,7 +MOVE :4:F7,8 +MOVE :4:O4,8 +MOVE :4:F3,6 +MOVE :4:C4,6 +MOVE :4:F3,8 +MOVE :4:F5,8 +MOVE :4:C6,7 +MOVE :4:C3,7 +MOVE :4:F2,5 +MOVE :4:F2,4 +MOVE :4:F1,8 +MOVE :4:F1,7 +MOVE :4:C2,7 +MOVE :4:C2,6 +MOVE :4:C1,6 diff --git a/apps/plugins/puzzles/icons/net.sav b/apps/plugins/puzzles/icons/net.sav new file mode 100644 index 0000000000..06a5426280 --- /dev/null +++ b/apps/plugins/puzzles/icons/net.sav @@ -0,0 +1,53 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :3:Net +PARAMS :3:5x5 +CPARAMS :3:5x5 +DESC :25:1115337157375775157135131 +UI :9:O0,0;C2,2 +NSTATES :2:45 +STATEPOS:2:45 +MOVE :4:C0,0 +MOVE :4:L0,0 +MOVE :4:L0,1 +MOVE :4:C0,2 +MOVE :4:L0,2 +MOVE :4:A0,3 +MOVE :4:L0,3 +MOVE :4:L0,4 +MOVE :4:L1,4 +MOVE :4:A2,4 +MOVE :4:A2,4 +MOVE :4:L2,4 +MOVE :4:C1,0 +MOVE :4:L1,0 +MOVE :4:L3,0 +MOVE :4:L2,0 +MOVE :4:A4,0 +MOVE :4:A4,0 +MOVE :4:L4,0 +MOVE :4:A4,1 +MOVE :4:L4,1 +MOVE :4:L3,1 +MOVE :4:A3,2 +MOVE :4:A3,2 +MOVE :4:L3,2 +MOVE :4:L2,2 +MOVE :4:A2,1 +MOVE :4:A2,1 +MOVE :4:A1,1 +MOVE :4:A1,1 +MOVE :4:A1,1 +MOVE :4:A1,1 +MOVE :4:A1,1 +MOVE :4:A1,1 +MOVE :4:A1,2 +MOVE :4:A1,2 +MOVE :4:A1,3 +MOVE :4:C2,3 +MOVE :4:A3,3 +MOVE :4:A4,4 +MOVE :4:A4,4 +MOVE :4:A4,3 +MOVE :4:A4,2 +MOVE :4:A4,2 diff --git a/apps/plugins/puzzles/icons/netslide.sav b/apps/plugins/puzzles/icons/netslide.sav new file mode 100644 index 0000000000..f37178ee0c --- /dev/null +++ b/apps/plugins/puzzles/icons/netslide.sav @@ -0,0 +1,47 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Netslide +PARAMS :3:4x4 +CPARAMS :3:4x4 +SEED :15:344208514520242 +DESC :16:49b59aca247714b4 +AUXINFO :34:60d28a22f68cdb6078d67a4d6069b9ff54 +NSTATES :2:38 +STATEPOS:2:31 +MOVE :4:R0,1 +MOVE :4:C1,1 +MOVE :4:R1,1 +MOVE :4:R1,1 +MOVE :4:C3,1 +MOVE :4:C1,1 +MOVE :4:R1,1 +MOVE :4:R1,1 +MOVE :4:R3,1 +MOVE :4:R0,1 +MOVE :4:C1,1 +MOVE :5:R3,-1 +MOVE :5:R0,-1 +MOVE :4:R0,1 +MOVE :4:R0,1 +MOVE :4:C0,1 +MOVE :5:R0,-1 +MOVE :5:R0,-1 +MOVE :5:C1,-1 +MOVE :4:R1,1 +MOVE :4:C1,1 +MOVE :5:R1,-1 +MOVE :5:C0,-1 +MOVE :5:C0,-1 +MOVE :4:R3,1 +MOVE :4:C0,1 +MOVE :4:C0,1 +MOVE :5:R3,-1 +MOVE :4:C0,1 +MOVE :5:R3,-1 +MOVE :5:C1,-1 +MOVE :4:R0,1 +MOVE :4:C0,1 +MOVE :5:R0,-1 +MOVE :5:C0,-1 +MOVE :4:C1,1 +MOVE :4:R3,1 diff --git a/apps/plugins/puzzles/icons/palisade.sav b/apps/plugins/puzzles/icons/palisade.sav new file mode 100644 index 0000000000..a935e890bb --- /dev/null +++ b/apps/plugins/puzzles/icons/palisade.sav @@ -0,0 +1,50 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Palisade +PARAMS :5:5x5n5 +CPARAMS :5:5x5n5 +SEED :15:930059588777257 +DESC :13:2d23c33e2c1b2 +AUXINFO :52:14a191be1282597737537139d11d87fb4f21ad4a8f31e67b4441 +NSTATES :2:41 +STATEPOS:2:27 +MOVE :14:F0,1,16F0,0,64 +MOVE :15:F0,0,32F1,0,128 +MOVE :12:F1,1,8F0,1,2 +MOVE :12:F1,0,4F1,1,1 +MOVE :14:F0,2,16F0,1,64 +MOVE :12:F1,2,8F0,2,2 +MOVE :12:F0,3,1F0,2,4 +MOVE :15:F2,0,128F1,0,32 +MOVE :12:F2,0,4F2,1,1 +MOVE :12:F3,0,8F2,0,2 +MOVE :15:F1,4,128F0,4,32 +MOVE :14:F1,4,16F1,3,64 +MOVE :15:F2,4,128F1,4,32 +MOVE :14:F0,3,64F0,4,16 +MOVE :15:F1,3,128F0,3,32 +MOVE :12:F1,3,1F1,2,4 +MOVE :15:F4,4,128F3,4,32 +MOVE :14:F4,4,16F4,3,64 +MOVE :12:F3,4,8F2,4,2 +MOVE :12:F2,4,1F2,3,4 +MOVE :12:F2,3,8F1,3,2 +MOVE :14:F2,2,64F2,3,16 +MOVE :15:F2,3,32F3,3,128 +MOVE :12:F3,3,4F3,4,1 +MOVE :12:F4,3,8F3,3,2 +MOVE :14:F4,3,16F4,2,64 +MOVE :12:F1,2,1F1,1,4 +MOVE :15:F2,1,128F1,1,32 +MOVE :15:F2,2,128F1,2,32 +MOVE :12:F2,2,1F2,1,4 +MOVE :15:F3,2,128F2,2,32 +MOVE :14:F3,2,64F3,3,16 +MOVE :12:F4,2,8F3,2,2 +MOVE :12:F3,2,1F3,1,4 +MOVE :15:F2,1,32F3,1,128 +MOVE :14:F4,2,16F4,1,64 +MOVE :12:F4,1,8F3,1,2 +MOVE :14:F3,0,64F3,1,16 +MOVE :15:F4,0,128F3,0,32 +MOVE :12:F4,1,1F4,0,4 diff --git a/apps/plugins/puzzles/icons/pattern.sav b/apps/plugins/puzzles/icons/pattern.sav new file mode 100644 index 0000000000..97c2396052 --- /dev/null +++ b/apps/plugins/puzzles/icons/pattern.sav @@ -0,0 +1,29 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Pattern +PARAMS :5:10x10 +CPARAMS :5:10x10 +DESC :67:3.4/2.2/4.1/2.3/2.3.2/4/6/6/3.1/1/5/5/1.1/4/5/6/2.3/3.3/1.1.3/1.1.4 +NSTATES :2:22 +STATEPOS:2:22 +MOVE :8:F6,4,1,2 +MOVE :8:F7,4,1,2 +MOVE :8:F4,5,2,1 +MOVE :8:F5,4,1,1 +MOVE :8:E0,5,2,1 +MOVE :8:E0,4,3,1 +MOVE :8:F0,6,1,4 +MOVE :8:F0,1,1,2 +MOVE :8:F0,1,5,1 +MOVE :8:E1,2,1,1 +MOVE :8:F2,0,1,4 +MOVE :8:E3,2,1,1 +MOVE :8:F3,0,1,1 +MOVE :8:F4,0,1,1 +MOVE :8:F3,3,1,1 +MOVE :8:E6,3,4,1 +MOVE :8:F6,6,1,4 +MOVE :8:F7,6,1,4 +MOVE :8:E6,0,1,4 +MOVE :8:E7,0,1,3 +MOVE :8:E5,1,1,1 diff --git a/apps/plugins/puzzles/icons/pearl.sav b/apps/plugins/puzzles/icons/pearl.sav new file mode 100644 index 0000000000..730ca85149 --- /dev/null +++ b/apps/plugins/puzzles/icons/pearl.sav @@ -0,0 +1,23 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Pearl +PARAMS :5:6x6dt +CPARAMS :5:6x6dt +SEED :15:901944054393278 +DESC :17:BbBfWcWbWBaBeWgWa +AUXINFO :72:f8bbe71b9be753d5fa143df207d7797ba62a9b3996eb8b8889487e1a2bd659d91a5e73e1 +NSTATES :2:14 +STATEPOS:1:7 +MOVE :55:F4,2,0;F1,1,0;F4,1,0;F1,0,0;F8,0,0;F2,0,1;F8,0,1;F2,0,2 +MOVE :27:F1,0,3;F4,1,3;F1,1,3;F4,2,3 +MOVE :27:F8,3,0;F2,3,1;F8,3,1;F2,3,2 +MOVE :97:F2,4,2;F8,4,1;F2,4,1;F8,4,0;F1,4,0;F4,5,0;F8,5,0;F2,5,1;F8,5,1;F2,5,2;F8,5,2;F2,5,3;F4,5,3;F1,4,3 +MOVE :13:F4,4,2;F1,3,2 +MOVE :13:F4,3,0;F1,2,0 +MOVE :69:F2,2,3;F8,2,2;F2,2,2;F8,2,1;F4,2,1;F1,1,1;F8,1,1;F2,1,2;F4,1,2;F1,0,2 +MOVE :41:F8,0,3;F2,0,4;F8,0,4;F2,0,5;F1,0,5;F4,1,5 +MOVE :27:F1,1,4;F4,2,4;F1,2,4;F4,3,4 +MOVE :13:F8,1,4;F2,1,5 +MOVE :55:F1,3,5;F4,4,5;F1,4,5;F4,5,5;F2,5,5;F8,5,4;F4,5,4;F1,4,4 +MOVE :13:F2,3,5;F8,3,4 +MOVE :13:F2,4,4;F8,4,3 diff --git a/apps/plugins/puzzles/icons/pegs.sav b/apps/plugins/puzzles/icons/pegs.sav new file mode 100644 index 0000000000..22b8a0d82f --- /dev/null +++ b/apps/plugins/puzzles/icons/pegs.sav @@ -0,0 +1,16 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :4:Pegs +PARAMS :8:7x7cross +CPARAMS :8:7x7cross +SEED :15:103342250484448 +DESC :49:OOPPPOOOOPPPOOPPPPPPPPPPHPPPPPPPPPPOOPPPOOOOPPPOO +NSTATES :1:8 +STATEPOS:1:8 +MOVE :7:3,1-3,3 +MOVE :7:5,2-3,2 +MOVE :7:5,4-5,2 +MOVE :7:3,4-5,4 +MOVE :7:6,4-4,4 +MOVE :7:4,0-4,2 +MOVE :7:2,0-4,0 diff --git a/apps/plugins/puzzles/icons/range.sav b/apps/plugins/puzzles/icons/range.sav new file mode 100644 index 0000000000..708e7db248 --- /dev/null +++ b/apps/plugins/puzzles/icons/range.sav @@ -0,0 +1,36 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Range +PARAMS :3:7x7 +CPARAMS :3:7x7 +SEED :15:989032078841515 +DESC :22:d7b3e8e5c7a7c13e4e8b4d +UI :1:0 +NSTATES :2:27 +STATEPOS:2:27 +MOVE :5:W,4,2 +MOVE :5:W,4,3 +MOVE :5:W,4,4 +MOVE :5:W,4,5 +MOVE :5:W,4,6 +MOVE :5:W,4,0 +MOVE :5:W,3,1 +MOVE :5:W,2,1 +MOVE :5:W,1,1 +MOVE :5:W,0,1 +MOVE :5:W,6,1 +MOVE :5:W,5,1 +MOVE :5:W,5,5 +MOVE :5:W,1,5 +MOVE :5:B,5,2 +MOVE :5:W,5,3 +MOVE :5:W,6,3 +MOVE :5:W,3,6 +MOVE :5:W,2,6 +MOVE :5:B,3,5 +MOVE :5:W,2,4 +MOVE :5:W,2,2 +MOVE :5:B,2,3 +MOVE :5:W,1,3 +MOVE :5:W,3,3 +MOVE :5:W,0,5 diff --git a/apps/plugins/puzzles/icons/rect.sav b/apps/plugins/puzzles/icons/rect.sav new file mode 100644 index 0000000000..17264daeeb --- /dev/null +++ b/apps/plugins/puzzles/icons/rect.sav @@ -0,0 +1,17 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :10:Rectangles +PARAMS :3:7x7 +CPARAMS :3:7x7 +DESC :33:a3d2b2a3_2a4a8h2a3c4_2b2c3a3_3c3b +NSTATES :2:10 +STATEPOS:2:10 +MOVE :8:R0,6,3,1 +MOVE :8:R6,4,1,3 +MOVE :8:R3,6,3,1 +MOVE :8:R4,4,2,1 +MOVE :8:R3,5,3,1 +MOVE :8:R6,1,1,3 +MOVE :8:R5,0,2,1 +MOVE :8:R5,1,1,2 +MOVE :8:R4,3,2,1 diff --git a/apps/plugins/puzzles/icons/samegame.sav b/apps/plugins/puzzles/icons/samegame.sav new file mode 100644 index 0000000000..f92b52d1b6 --- /dev/null +++ b/apps/plugins/puzzles/icons/samegame.sav @@ -0,0 +1,34 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :9:Same Game +PARAMS :9:10x10c3s2 +CPARAMS :9:10x10c3s2 +SEED :15:785412408200083 +DESC :199:1,1,3,1,2,2,1,2,3,2,2,2,3,3,2,1,1,1,3,2,3,3,2,3,1,3,2,1,1,3,1,2,2,2,3,2,3,2,3,2,1,3,1,2,1,2,3,2,1,3,2,3,1,1,3,3,1,3,3,3,1,1,3,2,2,1,1,2,1,2,2,2,3,1,3,2,2,1,2,3,3,1,2,3,1,3,3,2,1,3,3,1,3,1,2,2,1,3,1,2 +NSTATES :2:26 +STATEPOS:2:13 +MOVE :6:M94,95 +MOVE :6:M83,84 +MOVE :9:M83,93,94 +MOVE :6:M93,94 +MOVE :6:M20,21 +MOVE :15:M20,21,22,31,32 +MOVE :6:M70,71 +MOVE :6:M80,90 +MOVE :9:M73,82,83 +MOVE :18:M72,73,74,82,83,92 +MOVE :12:M51,61,62,72 +MOVE :9:M35,36,46 +MOVE :12:M49,57,58,59 +MOVE :6:M59,69 +MOVE :9:M69,79,89 +MOVE :12:M78,79,89,99 +MOVE :24:M45,46,47,54,55,57,64,67 +MOVE :36:M36,46,55,56,57,66,67,68,77,78,88,98 +MOVE :9:M76,77,87 +MOVE :6:M97,98 +MOVE :6:M94,95 +MOVE :45:M50,60,61,70,71,81,82,83,84,85,90,91,92,93,94 +MOVE :12:M73,81,82,83 +MOVE :6:M92,93 +MOVE :9:M81,90,91 diff --git a/apps/plugins/puzzles/icons/screenshot.sh b/apps/plugins/puzzles/icons/screenshot.sh new file mode 100755 index 0000000000..0e2a06eea7 --- /dev/null +++ b/apps/plugins/puzzles/icons/screenshot.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# Generate a screenshot from a puzzle save file. Takes the +# following arguments, in order: +# +# - the name of the puzzle binary +# - the name of the save file +# - the name of the output image file +# - (optionally) the proportion of the next move to redo before +# taking the screenshot. +# +# This script requires access to an X server in order to run, but +# seems to work fine under xvfb-run if you haven't got a real one +# available (or if you don't want to use it for some reason). + +binary="$1" +save="$2" +image="$3" +if test "x$4" != "x"; then + redo="--redo $4" +else + redo= +fi + +"$binary" $redo --screenshot "$image" --load "$save" diff --git a/apps/plugins/puzzles/icons/signpost.sav b/apps/plugins/puzzles/icons/signpost.sav new file mode 100644 index 0000000000..9ad1958ddf --- /dev/null +++ b/apps/plugins/puzzles/icons/signpost.sav @@ -0,0 +1,23 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Signpost +PARAMS :4:4x4c +CPARAMS :4:4x4c +SEED :15:230468784719861 +DESC :19:1eceebecfbfhgcaa16a +NSTATES :2:15 +STATEPOS:2:11 +MOVE :8:L2,1-3,1 +MOVE :8:L0,1-1,0 +MOVE :8:L2,2-1,1 +MOVE :8:L1,2-0,3 +MOVE :8:L0,2-2,0 +MOVE :8:L1,3-1,2 +MOVE :8:L1,1-1,3 +MOVE :8:L1,0-3,0 +MOVE :8:L0,0-0,1 +MOVE :8:L3,0-3,2 +MOVE :8:L3,2-0,2 +MOVE :8:L3,1-2,2 +MOVE :8:L2,3-2,1 +MOVE :8:L2,0-2,3 diff --git a/apps/plugins/puzzles/icons/singles.sav b/apps/plugins/puzzles/icons/singles.sav new file mode 100644 index 0000000000..260fd1f2b3 --- /dev/null +++ b/apps/plugins/puzzles/icons/singles.sav @@ -0,0 +1,45 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Singles +PARAMS :5:6x6dk +CPARAMS :5:6x6dk +SEED :15:781273601054598 +DESC :36:361566412253452144234115163346553461 +NSTATES :2:37 +STATEPOS:2:22 +MOVE :4:B1,0 +MOVE :4:C0,0 +MOVE :4:C1,1 +MOVE :4:C2,0 +MOVE :4:C0,1 +MOVE :4:B0,2 +MOVE :4:C0,3 +MOVE :4:C1,2 +MOVE :4:C4,3 +MOVE :4:B3,3 +MOVE :4:C3,2 +MOVE :4:C2,3 +MOVE :4:C3,4 +MOVE :4:B2,4 +MOVE :4:C1,4 +MOVE :4:C2,5 +MOVE :4:B1,5 +MOVE :4:C0,5 +MOVE :4:C0,4 +MOVE :4:C1,3 +MOVE :4:C3,5 +MOVE :4:B5,4 +MOVE :4:C4,4 +MOVE :4:C5,5 +MOVE :4:C5,3 +MOVE :4:C4,5 +MOVE :4:B4,0 +MOVE :4:C3,0 +MOVE :4:C4,1 +MOVE :4:C5,0 +MOVE :4:C5,1 +MOVE :4:B4,2 +MOVE :4:C5,2 +MOVE :4:C3,1 +MOVE :4:B2,1 +MOVE :4:C2,2 diff --git a/apps/plugins/puzzles/icons/sixteen.sav b/apps/plugins/puzzles/icons/sixteen.sav new file mode 100644 index 0000000000..076b1fbd4d --- /dev/null +++ b/apps/plugins/puzzles/icons/sixteen.sav @@ -0,0 +1,39 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Sixteen +PARAMS :3:4x4 +CPARAMS :3:4x4 +SEED :15:601798566229573 +DESC :38:2,16,3,10,13,8,7,4,9,14,12,11,15,1,5,6 +NSTATES :2:31 +STATEPOS:2:24 +MOVE :5:C3,-1 +MOVE :4:R0,1 +MOVE :4:C1,1 +MOVE :5:R0,-1 +MOVE :5:C1,-1 +MOVE :5:C3,-1 +MOVE :4:R2,1 +MOVE :4:R2,1 +MOVE :4:C3,1 +MOVE :5:C2,-1 +MOVE :5:C2,-1 +MOVE :4:R1,1 +MOVE :4:R1,1 +MOVE :4:C2,1 +MOVE :4:C2,1 +MOVE :4:R3,1 +MOVE :4:R3,1 +MOVE :4:C1,1 +MOVE :5:R2,-1 +MOVE :5:R2,-1 +MOVE :5:C1,-1 +MOVE :4:R2,1 +MOVE :4:C0,1 +MOVE :4:R3,1 +MOVE :5:R2,-1 +MOVE :5:C1,-1 +MOVE :4:R2,1 +MOVE :4:C1,1 +MOVE :5:R3,-1 +MOVE :5:C0,-1 diff --git a/apps/plugins/puzzles/icons/slant.sav b/apps/plugins/puzzles/icons/slant.sav new file mode 100644 index 0000000000..02017e5d48 --- /dev/null +++ b/apps/plugins/puzzles/icons/slant.sav @@ -0,0 +1,51 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Slant +PARAMS :5:8x8de +CPARAMS :5:8x8de +DESC :47:a10h23a32a02b22e3a2c1g3a20d32a0c221a210i0a101b0 +NSTATES :2:44 +STATEPOS:2:44 +MOVE :4:/7,0 +MOVE :4:\7,1 +MOVE :4:\1,0 +MOVE :4:/2,0 +MOVE :4:\0,4 +MOVE :4:/0,5 +MOVE :4:\0,6 +MOVE :4:/0,7 +MOVE :4:\1,7 +MOVE :4:/7,7 +MOVE :4:/3,7 +MOVE :4:\4,7 +MOVE :4:\5,7 +MOVE :4:/2,7 +MOVE :4:/7,4 +MOVE :4:\7,5 +MOVE :4:\7,3 +MOVE :4:\7,2 +MOVE :4:/6,2 +MOVE :4:\6,3 +MOVE :4:\7,6 +MOVE :4:/3,0 +MOVE :4:/2,1 +MOVE :4:\3,1 +MOVE :4:/2,2 +MOVE :4:\3,2 +MOVE :4:/2,3 +MOVE :4:\3,3 +MOVE :4:\1,1 +MOVE :4:/0,1 +MOVE :4:\0,2 +MOVE :4:\1,2 +MOVE :4:\1,3 +MOVE :4:/0,3 +MOVE :4:\1,4 +MOVE :4:\0,0 +MOVE :4:\5,3 +MOVE :4:\6,4 +MOVE :4:/5,4 +MOVE :4:/5,5 +MOVE :4:\6,5 +MOVE :4:/4,5 +MOVE :4:\4,6 diff --git a/apps/plugins/puzzles/icons/solo.sav b/apps/plugins/puzzles/icons/solo.sav new file mode 100644 index 0000000000..385cc68fe5 --- /dev/null +++ b/apps/plugins/puzzles/icons/solo.sav @@ -0,0 +1,36 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :4:Solo +PARAMS :3:3x3 +CPARAMS :3:3x3 +DESC :73:a2e9a5b6a2b3_7a1_4a9_6a2b4_1a7_2b1_7e6_9b2_5a8_1b6a5_9a3_8a7_2b8a6b1a1e4a +NSTATES :2:29 +STATEPOS:2:29 +MOVE :6:R7,1,1 +MOVE :6:R4,6,1 +MOVE :6:R5,0,1 +MOVE :6:R0,0,4 +MOVE :6:R2,0,6 +MOVE :6:R1,2,3 +MOVE :6:R0,3,9 +MOVE :6:R1,3,5 +MOVE :6:R2,4,8 +MOVE :6:R0,5,3 +MOVE :6:R1,5,6 +MOVE :6:R1,6,4 +MOVE :6:R4,7,4 +MOVE :6:R7,6,2 +MOVE :6:R7,7,5 +MOVE :6:R8,8,6 +MOVE :6:R7,3,3 +MOVE :6:R8,3,7 +MOVE :6:R8,3,8 +MOVE :6:R6,4,5 +MOVE :6:R7,5,7 +MOVE :6:R8,5,4 +MOVE :6:R7,2,8 +MOVE :6:R8,0,5 +MOVE :6:R4,2,5 +MOVE :6:R4,3,6 +MOVE :6:R5,4,4 +MOVE :6:R4,5,9 diff --git a/apps/plugins/puzzles/icons/square.pl b/apps/plugins/puzzles/icons/square.pl new file mode 100755 index 0000000000..815b94b532 --- /dev/null +++ b/apps/plugins/puzzles/icons/square.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl + +# Read an input image, crop its border to a standard width, and +# convert it into a square output image. Parameters are: +# +# - the required total image size +# - the output border thickness +# - the input image file name +# - the output image file name. + +($osize, $oborder, $infile, $outfile) = @ARGV; + +# Determine the input image's size. +$ident = `identify -format "%w %h" $infile`; +$ident =~ /(\d+) (\d+)/ or die "unable to get size for $infile\n"; +($w, $h) = ($1, $2); + +# Read the input image data. +$data = []; +open IDATA, "convert -depth 8 $infile rgb:- |"; +push @$data, $rgb while (read IDATA,$rgb,3,0) == 3; +close IDATA; +# Check we have the right amount of data. +$xl = $w * $h; +$al = scalar @$data; +die "wrong amount of image data ($al, expected $xl) from $infile\n" + unless $al == $xl; + +# Find the background colour, by looking around the entire border +# and finding the most popular pixel colour. +for ($i = 0; $i < $w; $i++) { + $pcount{$data->[$i]}++; # top row + $pcount{$data->[($h-1)*$w+$i]}++; # bottom row +} +for ($i = 1; $i < $h-1; $i++) { + $pcount{$data->[$i*$w]}++; # left column + $pcount{$data->[$i*$w+$w-1]}++; # right column +} +@plist = sort { $pcount{$b} <=> $pcount{$a} } keys %pcount; +$back = $plist[0]; + +# Crop rows and columns off the image to find the central rectangle +# of non-background stuff. +$ystart = 0; +$ystart++ while $ystart < $h and scalar(grep { $_ ne $back } map { $data->[$ystart*$w+$_] } 0 .. ($w-1)) == 0; +$yend = $h-1; +$yend-- while $yend >= $ystart and scalar(grep { $_ ne $back } map { $data->[$yend*$w+$_] } 0 .. ($w-1)) == 0; +$xstart = 0; +$xstart++ while $xstart < $w and scalar(grep { $_ ne $back } map { $data->[$_*$w+$xstart] } 0 .. ($h-1)) == 0; +$xend = $w-1; +$xend-- while $xend >= $xstart and scalar(grep { $_ ne $back } map { $data->[$_*$w+$xend] } 0 .. ($h-1)) == 0; + +# Decide how much border we're going to put back on to make the +# image perfectly square. +$hexpand = ($yend-$ystart) - ($xend-$xstart); +if ($hexpand > 0) { + $left = int($hexpand / 2); + $xstart -= $left; + $xend += $hexpand - $left; +} elsif ($hexpand < 0) { + $vexpand = -$hexpand; + $top = int($vexpand / 2); + $ystart -= $top; + $yend += $vexpand - $top; +} +$ow = $xend - $xstart + 1; +$oh = $yend - $ystart + 1; +die "internal computation problem" if $ow != $oh; # should be square + +# And decide how much _more_ border goes on to add the bit around +# the edge. +$realow = int($ow * ($osize / ($osize - 2*$oborder))); +$extra = $realow - $ow; +$left = int($extra / 2); +$xstart -= $left; +$xend += $extra - $left; +$top = int($extra / 2); +$ystart -= $top; +$yend += $extra - $top; +$ow = $xend - $xstart + 1; +$oh = $yend - $ystart + 1; +die "internal computation problem" if $ow != $oh; # should be square + +# Now write out the resulting image, and resize it appropriately. +open IDATA, "| convert -size ${ow}x${oh} -depth 8 -resize ${osize}x${osize}! rgb:- $outfile"; +for ($y = $ystart; $y <= $yend; $y++) { + for ($x = $xstart; $x <= $xend; $x++) { + if ($x >= 0 && $x < $w && $y >= 0 && $y < $h) { + print IDATA $data->[$y*$w+$x]; + } else { + print IDATA $back; + } + } +} +close IDATA; diff --git a/apps/plugins/puzzles/icons/tents.sav b/apps/plugins/puzzles/icons/tents.sav new file mode 100644 index 0000000000..292c2d2d75 --- /dev/null +++ b/apps/plugins/puzzles/icons/tents.sav @@ -0,0 +1,32 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :5:Tents +PARAMS :5:8x8de +CPARAMS :5:8x8de +DESC :45:ea_ddidfaabkd,3,0,2,1,2,2,1,1,3,1,1,1,1,1,3,1 +NSTATES :2:25 +STATEPOS:2:25 +MOVE :9:N5,6;N6,6 +MOVE :14:N5,7;N6,7;N7,7 +MOVE :9:N0,7;N1,7 +MOVE :4:N1,6 +MOVE :14:N6,2;N6,3;N6,4 +MOVE :9:N7,2;N7,3 +MOVE :14:N1,0;N2,0;N3,0 +MOVE :4:N3,1 +MOVE :4:N0,3 +MOVE :4:N3,4 +MOVE :4:N5,4 +MOVE :4:T6,0 +MOVE :4:T4,0 +MOVE :4:T0,0 +MOVE :4:N1,1 +MOVE :4:N4,1 +MOVE :9:N6,1;N7,1 +MOVE :4:T2,1 +MOVE :9:N1,2;N3,2 +MOVE :4:T5,2 +MOVE :4:N4,2 +MOVE :4:N5,3 +MOVE :4:N0,2 +MOVE :9:N1,3;N1,5 diff --git a/apps/plugins/puzzles/icons/towers.sav b/apps/plugins/puzzles/icons/towers.sav new file mode 100644 index 0000000000..351d473a63 --- /dev/null +++ b/apps/plugins/puzzles/icons/towers.sav @@ -0,0 +1,26 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :6:Towers +PARAMS :3:4de +CPARAMS :3:4de +SEED :15:888431554483015 +DESC :31:2/2/1/3/2/2/3/1/3/1/2/2/2/3/2/1 +AUXINFO :34:297d7a2fcf9e14403a74c976fe0fefd306 +NSTATES :2:17 +STATEPOS:2:10 +MOVE :6:R2,0,4 +MOVE :6:R0,1,4 +MOVE :6:R3,3,4 +MOVE :6:R1,2,4 +MOVE :6:R0,3,3 +MOVE :6:R1,0,3 +MOVE :6:R3,2,3 +MOVE :6:R2,1,3 +MOVE :6:R3,0,2 +MOVE :6:R3,1,1 +MOVE :6:R1,1,2 +MOVE :6:R1,3,1 +MOVE :6:R2,3,2 +MOVE :6:R2,2,1 +MOVE :6:R0,2,2 +MOVE :6:R0,0,1 diff --git a/apps/plugins/puzzles/icons/tracks.sav b/apps/plugins/puzzles/icons/tracks.sav new file mode 100644 index 0000000000..ca30644506 --- /dev/null +++ b/apps/plugins/puzzles/icons/tracks.sav @@ -0,0 +1,31 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :12:Train Tracks +PARAMS :5:6x6dt +CPARAMS :5:6x6dt +SEED :15:145870397370785 +DESC :31:l6t9b,3,2,1,S4,5,4,2,6,S3,2,3,3 +NSTATES :2:23 +STATEPOS:2:20 +MOVE :5:TD0,0 +MOVE :5:TR0,0 +MOVE :5:TL5,5 +MOVE :5:TU5,5 +MOVE :29:TS1,1;TS2,1;TS3,1;TS4,1;TS5,1 +MOVE :29:NS2,0;NS2,2;NS2,3;NS2,4;NS2,5 +MOVE :5:TU1,1 +MOVE :17:NS0,3;NS0,4;NS0,5 +MOVE :23:NS1,2;NS1,3;NS1,4;NS1,5 +MOVE :5:TL2,1 +MOVE :5:TL3,1 +MOVE :5:TS4,4 +MOVE :5:TS3,4 +MOVE :17:NS3,0;NS4,0;NS5,0 +MOVE :5:TS4,2 +MOVE :5:TS4,3 +MOVE :5:TU3,4 +MOVE :5:TL4,4 +MOVE :5:TL5,1 +MOVE :5:TU5,2 +MOVE :5:NS5,3 +MOVE :5:NS3,2 diff --git a/apps/plugins/puzzles/icons/twiddle.sav b/apps/plugins/puzzles/icons/twiddle.sav new file mode 100644 index 0000000000..2863033f99 --- /dev/null +++ b/apps/plugins/puzzles/icons/twiddle.sav @@ -0,0 +1,35 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Twiddle +PARAMS :5:3x3n2 +CPARAMS :5:3x3n2 +SEED :15:635499951462226 +DESC :17:3,7,2,6,5,1,8,4,9 +NSTATES :2:27 +STATEPOS:2:22 +MOVE :7:M0,0,-1 +MOVE :7:M1,0,-1 +MOVE :6:M1,1,1 +MOVE :6:M0,1,1 +MOVE :6:M0,0,1 +MOVE :6:M0,0,1 +MOVE :7:M1,1,-1 +MOVE :7:M0,1,-1 +MOVE :7:M0,1,-1 +MOVE :7:M1,1,-1 +MOVE :6:M0,1,1 +MOVE :7:M0,1,-1 +MOVE :6:M1,1,1 +MOVE :6:M1,1,1 +MOVE :6:M0,1,1 +MOVE :6:M0,1,1 +MOVE :7:M0,1,-1 +MOVE :7:M1,1,-1 +MOVE :7:M0,1,-1 +MOVE :7:M1,1,-1 +MOVE :6:M0,1,1 +MOVE :7:M1,0,-1 +MOVE :7:M0,1,-1 +MOVE :6:M1,0,1 +MOVE :6:M1,1,1 +MOVE :6:M1,1,1 diff --git a/apps/plugins/puzzles/icons/undead.sav b/apps/plugins/puzzles/icons/undead.sav new file mode 100644 index 0000000000..3c314dde8a --- /dev/null +++ b/apps/plugins/puzzles/icons/undead.sav @@ -0,0 +1,14 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :6:Undead +PARAMS :6:4x4de2 +CPARAMS :6:4x4de2 +DESC :48:5,2,2,aRLgLLaLRL,2,0,1,2,1,1,2,5,0,0,0,2,1,3,1,1 +NSTATES :1:7 +STATEPOS:1:7 +MOVE :2:G0 +MOVE :2:V0 +MOVE :2:G2 +MOVE :2:G3 +MOVE :2:V3 +MOVE :2:Z3 diff --git a/apps/plugins/puzzles/icons/unequal.sav b/apps/plugins/puzzles/icons/unequal.sav new file mode 100644 index 0000000000..c414513a22 --- /dev/null +++ b/apps/plugins/puzzles/icons/unequal.sav @@ -0,0 +1,25 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :7:Unequal +PARAMS :3:4de +CPARAMS :3:4de +SEED :15:143029490219212 +DESC :37:0D,0,0L,0,0,0,0,0,0D,0U,0R,0,0,0,0,4, +AUXINFO :34:f51274dc41e0a39caa38942fc525ed0108 +NSTATES :2:16 +STATEPOS:1:6 +MOVE :6:R2,1,4 +MOVE :6:R1,2,4 +MOVE :6:R0,0,4 +MOVE :6:R2,3,1 +MOVE :6:R3,2,1 +MOVE :6:R0,2,3 +MOVE :6:R2,2,2 +MOVE :6:R0,3,2 +MOVE :6:R1,3,3 +MOVE :6:R0,1,1 +MOVE :6:R1,1,2 +MOVE :6:R3,1,3 +MOVE :6:R3,0,2 +MOVE :6:R2,0,3 +MOVE :6:R1,0,1 diff --git a/apps/plugins/puzzles/icons/unruly.sav b/apps/plugins/puzzles/icons/unruly.sav new file mode 100644 index 0000000000..0f7ca1b9dd --- /dev/null +++ b/apps/plugins/puzzles/icons/unruly.sav @@ -0,0 +1,22 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :6:Unruly +PARAMS :5:6x6de +CPARAMS :5:6x6de +DESC :10:faCADAJeBd +NSTATES :2:15 +STATEPOS:2:15 +MOVE :6:P0,1,2 +MOVE :6:P0,4,2 +MOVE :6:P0,3,3 +MOVE :6:P0,3,0 +MOVE :6:P0,5,1 +MOVE :6:P0,2,1 +MOVE :6:P1,4,0 +MOVE :6:P1,1,1 +MOVE :6:P1,5,2 +MOVE :6:P0,0,2 +MOVE :6:P1,0,3 +MOVE :6:P1,0,0 +MOVE :6:P1,0,4 +MOVE :6:P0,2,4 diff --git a/apps/plugins/puzzles/icons/untangle.sav b/apps/plugins/puzzles/icons/untangle.sav new file mode 100644 index 0000000000..016318a521 --- /dev/null +++ b/apps/plugins/puzzles/icons/untangle.sav @@ -0,0 +1,16 @@ +SAVEFILE:41:Simon Tatham's Portable Puzzle Collection +VERSION :1:1 +GAME :8:Untangle +PARAMS :2:10 +CPARAMS :2:10 +SEED :15:761628688787632 +DESC :63:0-1,0-5,0-8,0-9,1-4,1-8,2-6,2-7,3-5,3-6,3-9,4-5,4-7,5-7,6-7,8-9 +AUXINFO :182:01bee8258e3164fe966f294b2837b6584b965b8d8e97571ba48f26c9bc0a91ac4b49fb4652bfaa5c340c82c57afbaa4620f2f6d49d7a7b330a66594d2b88c499d57c4093379b7dc322f2afa1ebab81004585751c39c19f8f9930c4 +NSTATES :1:7 +STATEPOS:1:6 +MOVE :12:P8:168,16/64 +MOVE :12:P0:186,85/64 +MOVE :12:P2:47,254/64 +MOVE :13:P5:131,153/64 +MOVE :12:P3:75,126/64 +MOVE :12:P7:93,303/64 diff --git a/apps/plugins/puzzles/icons/win16pal.xpm b/apps/plugins/puzzles/icons/win16pal.xpm new file mode 100644 index 0000000000..66fd60a480 --- /dev/null +++ b/apps/plugins/puzzles/icons/win16pal.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char *win16pal[] = { +/* columns rows colors chars-per-pixel */ +"16 1 16 1", +" c #000000", +". c #800000", +"X c #008000", +"o c #808000", +"O c #000080", +"+ c #800080", +"@ c #008080", +"# c #C0C0C0", +"$ c #808080", +"% c #FF0000", +"& c #00FF00", +"* c #FFFF00", +"= c #0000FF", +"- c #FF00FF", +"; c #00FFFF", +": c #FFFFFF", +/* pixels */ +" .XoO+@#$%&*=-;:" +}; diff --git a/apps/plugins/puzzles/inertia.R b/apps/plugins/puzzles/inertia.R new file mode 100644 index 0000000000..e6e86beaec --- /dev/null +++ b/apps/plugins/puzzles/inertia.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +inertia : [X] GTK COMMON inertia inertia-icon|no-icon + +inertia : [G] WINDOWS COMMON inertia inertia.res|noicon.res + +ALL += inertia[COMBINED] + +!begin am gtk +GAMES += inertia +!end + +!begin >list.c + A(inertia) \ +!end + +!begin >gamedesc.txt +inertia:inertia.exe:Inertia:Gem-collecting puzzle:Collect all the gems without running into any of the mines. +!end diff --git a/apps/plugins/puzzles/inertia.c b/apps/plugins/puzzles/inertia.c new file mode 100644 index 0000000000..a0e1c45fb1 --- /dev/null +++ b/apps/plugins/puzzles/inertia.c @@ -0,0 +1,2249 @@ +/* + * inertia.c: Game involving navigating round a grid picking up + * gems. + * + * Game rules and basic generator design by Ben Olmstead. + * This re-implementation was written by Simon Tatham. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +/* Used in the game_state */ +#define BLANK 'b' +#define GEM 'g' +#define MINE 'm' +#define STOP 's' +#define WALL 'w' + +/* Used in the game IDs */ +#define START 'S' + +/* Used in the game generation */ +#define POSSGEM 'G' + +/* Used only in the game_drawstate*/ +#define UNDRAWN '?' + +#define DIRECTIONS 8 +#define DP1 (DIRECTIONS+1) +#define DX(dir) ( (dir) & 3 ? (((dir) & 7) > 4 ? -1 : +1) : 0 ) +#define DY(dir) ( DX((dir)+6) ) + +/* + * Lvalue macro which expects x and y to be in range. + */ +#define LV_AT(w, h, grid, x, y) ( (grid)[(y)*(w)+(x)] ) + +/* + * Rvalue macro which can cope with x and y being out of range. + */ +#define AT(w, h, grid, x, y) ( (x)<0 || (x)>=(w) || (y)<0 || (y)>=(h) ? \ + WALL : LV_AT(w, h, grid, x, y) ) + +enum { + COL_BACKGROUND, + COL_OUTLINE, + COL_HIGHLIGHT, + COL_LOWLIGHT, + COL_PLAYER, + COL_DEAD_PLAYER, + COL_MINE, + COL_GEM, + COL_WALL, + COL_HINT, + NCOLOURS +}; + +struct game_params { + int w, h; +}; + +typedef struct soln { + int refcount; + int len; + unsigned char *list; +} soln; + +struct game_state { + game_params p; + int px, py; + int gems; + char *grid; + int distance_moved; + int dead; + int cheated; + int solnpos; + soln *soln; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = 10; +#ifdef PORTRAIT_SCREEN + ret->h = 10; +#else + ret->h = 8; +#endif + return ret; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static const struct game_params inertia_presets[] = { +#ifdef PORTRAIT_SCREEN + { 10, 10 }, + { 12, 12 }, + { 16, 16 }, +#else + { 10, 8 }, + { 15, 12 }, + { 20, 16 }, +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params p, *ret; + char *retname; + char namebuf[80]; + + if (i < 0 || i >= lenof(inertia_presets)) + return FALSE; + + p = inertia_presets[i]; + ret = dup_params(&p); + sprintf(namebuf, "%dx%d", ret->w, ret->h); + retname = dupstr(namebuf); + + *params = ret; + *name = retname; + return TRUE; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + /* + * Avoid completely degenerate cases which only have one + * row/column. We probably could generate completable puzzles + * of that shape, but they'd be forced to be extremely boring + * and at large sizes would take a while to happen upon at + * random as well. + */ + if (params->w < 2 || params->h < 2) + return "Width and height must both be at least two"; + + /* + * The grid construction algorithm creates 1/5 as many gems as + * grid squares, and must create at least one gem to have an + * actual puzzle. However, an area-five grid is ruled out by + * the above constraint, so the practical minimum is six. + */ + if (params->w * params->h < 6) + return "Grid area must be at least six squares"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver used by grid generator. + */ + +struct solver_scratch { + unsigned char *reachable_from, *reachable_to; + int *positions; +}; + +static struct solver_scratch *new_scratch(int w, int h) +{ + struct solver_scratch *sc = snew(struct solver_scratch); + + sc->reachable_from = snewn(w * h * DIRECTIONS, unsigned char); + sc->reachable_to = snewn(w * h * DIRECTIONS, unsigned char); + sc->positions = snewn(w * h * DIRECTIONS, int); + + return sc; +} + +static void free_scratch(struct solver_scratch *sc) +{ + sfree(sc->reachable_from); + sfree(sc->reachable_to); + sfree(sc->positions); + sfree(sc); +} + +static int can_go(int w, int h, char *grid, + int x1, int y1, int dir1, int x2, int y2, int dir2) +{ + /* + * Returns TRUE if we can transition directly from (x1,y1) + * going in direction dir1, to (x2,y2) going in direction dir2. + */ + + /* + * If we're actually in the middle of an unoccupyable square, + * we cannot make any move. + */ + if (AT(w, h, grid, x1, y1) == WALL || + AT(w, h, grid, x1, y1) == MINE) + return FALSE; + + /* + * If a move is capable of stopping at x1,y1,dir1, and x2,y2 is + * the same coordinate as x1,y1, then we can make the + * transition (by stopping and changing direction). + * + * For this to be the case, we have to either have a wall + * beyond x1,y1,dir1, or have a stop on x1,y1. + */ + if (x2 == x1 && y2 == y1 && + (AT(w, h, grid, x1, y1) == STOP || + AT(w, h, grid, x1, y1) == START || + AT(w, h, grid, x1+DX(dir1), y1+DY(dir1)) == WALL)) + return TRUE; + + /* + * If a move is capable of continuing here, then x1,y1,dir1 can + * move one space further on. + */ + if (x2 == x1+DX(dir1) && y2 == y1+DY(dir1) && dir1 == dir2 && + (AT(w, h, grid, x2, y2) == BLANK || + AT(w, h, grid, x2, y2) == GEM || + AT(w, h, grid, x2, y2) == STOP || + AT(w, h, grid, x2, y2) == START)) + return TRUE; + + /* + * That's it. + */ + return FALSE; +} + +static int find_gem_candidates(int w, int h, char *grid, + struct solver_scratch *sc) +{ + int wh = w*h; + int head, tail; + int sx, sy, gx, gy, gd, pass, possgems; + + /* + * This function finds all the candidate gem squares, which are + * precisely those squares which can be picked up on a loop + * from the starting point back to the starting point. Doing + * this may involve passing through such a square in the middle + * of a move; so simple breadth-first search over the _squares_ + * of the grid isn't quite adequate, because it might be that + * we can only reach a gem from the start by moving over it in + * one direction, but can only return to the start if we were + * moving over it in another direction. + * + * Instead, we BFS over a space which mentions each grid square + * eight times - once for each direction. We also BFS twice: + * once to find out what square+direction pairs we can reach + * _from_ the start point, and once to find out what pairs we + * can reach the start point from. Then a square is reachable + * if any of the eight directions for that square has both + * flags set. + */ + + memset(sc->reachable_from, 0, wh * DIRECTIONS); + memset(sc->reachable_to, 0, wh * DIRECTIONS); + + /* + * Find the starting square. + */ + sx = -1; /* placate optimiser */ + for (sy = 0; sy < h; sy++) { + for (sx = 0; sx < w; sx++) + if (AT(w, h, grid, sx, sy) == START) + break; + if (sx < w) + break; + } + assert(sy < h); + + for (pass = 0; pass < 2; pass++) { + unsigned char *reachable = (pass == 0 ? sc->reachable_from : + sc->reachable_to); + int sign = (pass == 0 ? +1 : -1); + int dir; + +#ifdef SOLVER_DIAGNOSTICS + printf("starting pass %d\n", pass); +#endif + + /* + * `head' and `tail' are indices within sc->positions which + * track the list of board positions left to process. + */ + head = tail = 0; + for (dir = 0; dir < DIRECTIONS; dir++) { + int index = (sy*w+sx)*DIRECTIONS+dir; + sc->positions[tail++] = index; + reachable[index] = TRUE; +#ifdef SOLVER_DIAGNOSTICS + printf("starting point %d,%d,%d\n", sx, sy, dir); +#endif + } + + /* + * Now repeatedly pick an element off the list and process + * it. + */ + while (head < tail) { + int index = sc->positions[head++]; + int dir = index % DIRECTIONS; + int x = (index / DIRECTIONS) % w; + int y = index / (w * DIRECTIONS); + int n, x2, y2, d2, i2; + +#ifdef SOLVER_DIAGNOSTICS + printf("processing point %d,%d,%d\n", x, y, dir); +#endif + /* + * The places we attempt to switch to here are: + * - each possible direction change (all the other + * directions in this square) + * - one step further in the direction we're going (or + * one step back, if we're in the reachable_to pass). + */ + for (n = -1; n < DIRECTIONS; n++) { + if (n < 0) { + x2 = x + sign * DX(dir); + y2 = y + sign * DY(dir); + d2 = dir; + } else { + x2 = x; + y2 = y; + d2 = n; + } + i2 = (y2*w+x2)*DIRECTIONS+d2; + if (x2 >= 0 && x2 < w && + y2 >= 0 && y2 < h && + !reachable[i2]) { + int ok; +#ifdef SOLVER_DIAGNOSTICS + printf(" trying point %d,%d,%d", x2, y2, d2); +#endif + if (pass == 0) + ok = can_go(w, h, grid, x, y, dir, x2, y2, d2); + else + ok = can_go(w, h, grid, x2, y2, d2, x, y, dir); +#ifdef SOLVER_DIAGNOSTICS + printf(" - %sok\n", ok ? "" : "not "); +#endif + if (ok) { + sc->positions[tail++] = i2; + reachable[i2] = TRUE; + } + } + } + } + } + + /* + * And that should be it. Now all we have to do is find the + * squares for which there exists _some_ direction such that + * the square plus that direction form a tuple which is both + * reachable from the start and reachable to the start. + */ + possgems = 0; + for (gy = 0; gy < h; gy++) + for (gx = 0; gx < w; gx++) + if (AT(w, h, grid, gx, gy) == BLANK) { + for (gd = 0; gd < DIRECTIONS; gd++) { + int index = (gy*w+gx)*DIRECTIONS+gd; + if (sc->reachable_from[index] && sc->reachable_to[index]) { +#ifdef SOLVER_DIAGNOSTICS + printf("space at %d,%d is reachable via" + " direction %d\n", gx, gy, gd); +#endif + LV_AT(w, h, grid, gx, gy) = POSSGEM; + possgems++; + break; + } + } + } + + return possgems; +} + +/* ---------------------------------------------------------------------- + * Grid generation code. + */ + +static char *gengrid(int w, int h, random_state *rs) +{ + int wh = w*h; + char *grid = snewn(wh+1, char); + struct solver_scratch *sc = new_scratch(w, h); + int maxdist_threshold, tries; + + maxdist_threshold = 2; + tries = 0; + + while (1) { + int i, j; + int possgems; + int *dist, *list, head, tail, maxdist; + + /* + * We're going to fill the grid with the five basic piece + * types in about 1/5 proportion. For the moment, though, + * we leave out the gems, because we'll put those in + * _after_ we run the solver to tell us where the viable + * locations are. + */ + i = 0; + for (j = 0; j < wh/5; j++) + grid[i++] = WALL; + for (j = 0; j < wh/5; j++) + grid[i++] = STOP; + for (j = 0; j < wh/5; j++) + grid[i++] = MINE; + assert(i < wh); + grid[i++] = START; + while (i < wh) + grid[i++] = BLANK; + shuffle(grid, wh, sizeof(*grid), rs); + + /* + * Find the viable gem locations, and immediately give up + * and try again if there aren't enough of them. + */ + possgems = find_gem_candidates(w, h, grid, sc); + if (possgems < wh/5) + continue; + + /* + * We _could_ now select wh/5 of the POSSGEMs and set them + * to GEM, and have a viable level. However, there's a + * chance that a large chunk of the level will turn out to + * be unreachable, so first we test for that. + * + * We do this by finding the largest distance from any + * square to the nearest POSSGEM, by breadth-first search. + * If this is above a critical threshold, we abort and try + * again. + * + * (This search is purely geometric, without regard to + * walls and long ways round.) + */ + dist = sc->positions; + list = sc->positions + wh; + for (i = 0; i < wh; i++) + dist[i] = -1; + head = tail = 0; + for (i = 0; i < wh; i++) + if (grid[i] == POSSGEM) { + dist[i] = 0; + list[tail++] = i; + } + maxdist = 0; + while (head < tail) { + int pos, x, y, d; + + pos = list[head++]; + if (maxdist < dist[pos]) + maxdist = dist[pos]; + + x = pos % w; + y = pos / w; + + for (d = 0; d < DIRECTIONS; d++) { + int x2, y2, p2; + + x2 = x + DX(d); + y2 = y + DY(d); + + if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h) { + p2 = y2*w+x2; + if (dist[p2] < 0) { + dist[p2] = dist[pos] + 1; + list[tail++] = p2; + } + } + } + } + assert(head == wh && tail == wh); + + /* + * Now abandon this grid and go round again if maxdist is + * above the required threshold. + * + * We can safely start the threshold as low as 2. As we + * accumulate failed generation attempts, we gradually + * raise it as we get more desperate. + */ + if (maxdist > maxdist_threshold) { + tries++; + if (tries == 50) { + maxdist_threshold++; + tries = 0; + } + continue; + } + + /* + * Now our reachable squares are plausibly evenly + * distributed over the grid. I'm not actually going to + * _enforce_ that I place the gems in such a way as not to + * increase that maxdist value; I'm now just going to trust + * to the RNG to pick a sensible subset of the POSSGEMs. + */ + j = 0; + for (i = 0; i < wh; i++) + if (grid[i] == POSSGEM) + list[j++] = i; + shuffle(list, j, sizeof(*list), rs); + for (i = 0; i < j; i++) + grid[list[i]] = (i < wh/5 ? GEM : BLANK); + break; + } + + free_scratch(sc); + + grid[wh] = '\0'; + + return grid; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + return gengrid(params->w, params->h, rs); +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h, wh = w*h; + int starts = 0, gems = 0, i; + + for (i = 0; i < wh; i++) { + if (!desc[i]) + return "Not enough data to fill grid"; + if (desc[i] != WALL && desc[i] != START && desc[i] != STOP && + desc[i] != GEM && desc[i] != MINE && desc[i] != BLANK) + return "Unrecognised character in game description"; + if (desc[i] == START) + starts++; + if (desc[i] == GEM) + gems++; + } + if (desc[i]) + return "Too much data to fill grid"; + if (starts < 1) + return "No starting square specified"; + if (starts > 1) + return "More than one starting square specified"; + if (gems < 1) + return "No gems specified"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h, wh = w*h; + int i; + game_state *state = snew(game_state); + + state->p = *params; /* structure copy */ + + state->grid = snewn(wh, char); + assert(strlen(desc) == wh); + memcpy(state->grid, desc, wh); + + state->px = state->py = -1; + state->gems = 0; + for (i = 0; i < wh; i++) { + if (state->grid[i] == START) { + state->grid[i] = STOP; + state->px = i % w; + state->py = i / w; + } else if (state->grid[i] == GEM) { + state->gems++; + } + } + + assert(state->gems > 0); + assert(state->px >= 0 && state->py >= 0); + + state->distance_moved = 0; + state->dead = FALSE; + + state->cheated = FALSE; + state->solnpos = 0; + state->soln = NULL; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->p.w, h = state->p.h, wh = w*h; + game_state *ret = snew(game_state); + + ret->p = state->p; + ret->px = state->px; + ret->py = state->py; + ret->gems = state->gems; + ret->grid = snewn(wh, char); + ret->distance_moved = state->distance_moved; + ret->dead = FALSE; + memcpy(ret->grid, state->grid, wh); + ret->cheated = state->cheated; + ret->soln = state->soln; + if (ret->soln) + ret->soln->refcount++; + ret->solnpos = state->solnpos; + + return ret; +} + +static void free_game(game_state *state) +{ + if (state->soln && --state->soln->refcount == 0) { + sfree(state->soln->list); + sfree(state->soln); + } + sfree(state->grid); + sfree(state); +} + +/* + * Internal function used by solver. + */ +static int move_goes_to(int w, int h, char *grid, int x, int y, int d) +{ + int dr; + + /* + * See where we'd get to if we made this move. + */ + dr = -1; /* placate optimiser */ + while (1) { + if (AT(w, h, grid, x+DX(d), y+DY(d)) == WALL) { + dr = DIRECTIONS; /* hit a wall, so end up stationary */ + break; + } + x += DX(d); + y += DY(d); + if (AT(w, h, grid, x, y) == STOP) { + dr = DIRECTIONS; /* hit a stop, so end up stationary */ + break; + } + if (AT(w, h, grid, x, y) == GEM) { + dr = d; /* hit a gem, so we're still moving */ + break; + } + if (AT(w, h, grid, x, y) == MINE) + return -1; /* hit a mine, so move is invalid */ + } + assert(dr >= 0); + return (y*w+x)*DP1+dr; +} + +static int compare_integers(const void *av, const void *bv) +{ + const int *a = (const int *)av; + const int *b = (const int *)bv; + if (*a < *b) + return -1; + else if (*a > *b) + return +1; + else + return 0; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = currstate->p.w, h = currstate->p.h, wh = w*h; + int *nodes, *nodeindex, *edges, *backedges, *edgei, *backedgei, *circuit; + int nedges; + int *dist, *dist2, *list; + int *unvisited; + int circuitlen, circuitsize; + int head, tail, pass, i, j, n, x, y, d, dd; + char *err, *soln, *p; + + /* + * Before anything else, deal with the special case in which + * all the gems are already collected. + */ + for (i = 0; i < wh; i++) + if (currstate->grid[i] == GEM) + break; + if (i == wh) { + *error = "Game is already solved"; + return NULL; + } + + /* + * Solving Inertia is a question of first building up the graph + * of where you can get to from where, and secondly finding a + * tour of the graph which takes in every gem. + * + * This is of course a close cousin of the travelling salesman + * problem, which is NP-complete; so I rather doubt that any + * _optimal_ tour can be found in plausible time. Hence I'll + * restrict myself to merely finding a not-too-bad one. + * + * First construct the graph, by bfsing out move by move from + * the current player position. Graph vertices will be + * - every endpoint of a move (place the ball can be + * stationary) + * - every gem (place the ball can go through in motion). + * Vertices of this type have an associated direction, since + * if a gem can be collected by sliding through it in two + * different directions it doesn't follow that you can + * change direction at it. + * + * I'm going to refer to a non-directional vertex as + * (y*w+x)*DP1+DIRECTIONS, and a directional one as + * (y*w+x)*DP1+d. + */ + + /* + * nodeindex[] maps node codes as shown above to numeric + * indices in the nodes[] array. + */ + nodeindex = snewn(DP1*wh, int); + for (i = 0; i < DP1*wh; i++) + nodeindex[i] = -1; + + /* + * Do the bfs to find all the interesting graph nodes. + */ + nodes = snewn(DP1*wh, int); + head = tail = 0; + + nodes[tail] = (currstate->py * w + currstate->px) * DP1 + DIRECTIONS; + nodeindex[nodes[0]] = tail; + tail++; + + while (head < tail) { + int nc = nodes[head++], nnc; + + d = nc % DP1; + + /* + * Plot all possible moves from this node. If the node is + * directed, there's only one. + */ + for (dd = 0; dd < DIRECTIONS; dd++) { + x = nc / DP1; + y = x / w; + x %= w; + + if (d < DIRECTIONS && d != dd) + continue; + + nnc = move_goes_to(w, h, currstate->grid, x, y, dd); + if (nnc >= 0 && nnc != nc) { + if (nodeindex[nnc] < 0) { + nodes[tail] = nnc; + nodeindex[nnc] = tail; + tail++; + } + } + } + } + n = head; + + /* + * Now we know how many nodes we have, allocate the edge array + * and go through setting up the edges. + */ + edges = snewn(DIRECTIONS*n, int); + edgei = snewn(n+1, int); + nedges = 0; + + for (i = 0; i < n; i++) { + int nc = nodes[i]; + + edgei[i] = nedges; + + d = nc % DP1; + x = nc / DP1; + y = x / w; + x %= w; + + for (dd = 0; dd < DIRECTIONS; dd++) { + int nnc; + + if (d >= DIRECTIONS || d == dd) { + nnc = move_goes_to(w, h, currstate->grid, x, y, dd); + + if (nnc >= 0 && nnc != nc) + edges[nedges++] = nodeindex[nnc]; + } + } + } + edgei[n] = nedges; + + /* + * Now set up the backedges array. + */ + backedges = snewn(nedges, int); + backedgei = snewn(n+1, int); + for (i = j = 0; i < nedges; i++) { + while (j+1 < n && i >= edgei[j+1]) + j++; + backedges[i] = edges[i] * n + j; + } + qsort(backedges, nedges, sizeof(int), compare_integers); + backedgei[0] = 0; + for (i = j = 0; i < nedges; i++) { + int k = backedges[i] / n; + backedges[i] %= n; + while (j < k) + backedgei[++j] = i; + } + backedgei[n] = nedges; + + /* + * Set up the initial tour. At all times, our tour is a circuit + * of graph vertices (which may, and probably will often, + * repeat vertices). To begin with, it's got exactly one vertex + * in it, which is the player's current starting point. + */ + circuitsize = 256; + circuit = snewn(circuitsize, int); + circuitlen = 0; + circuit[circuitlen++] = 0; /* node index 0 is the starting posn */ + + /* + * Track which gems are as yet unvisited. + */ + unvisited = snewn(wh, int); + for (i = 0; i < wh; i++) + unvisited[i] = FALSE; + for (i = 0; i < wh; i++) + if (currstate->grid[i] == GEM) + unvisited[i] = TRUE; + + /* + * Allocate space for doing bfses inside the main loop. + */ + dist = snewn(n, int); + dist2 = snewn(n, int); + list = snewn(n, int); + + err = NULL; + soln = NULL; + + /* + * Now enter the main loop, in each iteration of which we + * extend the tour to take in an as yet uncollected gem. + */ + while (1) { + int target, n1, n2, bestdist, extralen, targetpos; + +#ifdef TSP_DIAGNOSTICS + printf("circuit is"); + for (i = 0; i < circuitlen; i++) { + int nc = nodes[circuit[i]]; + printf(" (%d,%d,%d)", nc/DP1%w, nc/(DP1*w), nc%DP1); + } + printf("\n"); + printf("moves are "); + x = nodes[circuit[0]] / DP1 % w; + y = nodes[circuit[0]] / DP1 / w; + for (i = 1; i < circuitlen; i++) { + int x2, y2, dx, dy; + if (nodes[circuit[i]] % DP1 != DIRECTIONS) + continue; + x2 = nodes[circuit[i]] / DP1 % w; + y2 = nodes[circuit[i]] / DP1 / w; + dx = (x2 > x ? +1 : x2 < x ? -1 : 0); + dy = (y2 > y ? +1 : y2 < y ? -1 : 0); + for (d = 0; d < DIRECTIONS; d++) + if (DX(d) == dx && DY(d) == dy) + printf("%c", "89632147"[d]); + x = x2; + y = y2; + } + printf("\n"); +#endif + + /* + * First, start a pair of bfses at _every_ vertex currently + * in the tour, and extend them outwards to find the + * nearest as yet unreached gem vertex. + * + * This is largely a heuristic: we could pick _any_ doubly + * reachable node here and still get a valid tour as + * output. I hope that picking a nearby one will result in + * generally good tours. + */ + for (pass = 0; pass < 2; pass++) { + int *ep = (pass == 0 ? edges : backedges); + int *ei = (pass == 0 ? edgei : backedgei); + int *dp = (pass == 0 ? dist : dist2); + head = tail = 0; + for (i = 0; i < n; i++) + dp[i] = -1; + for (i = 0; i < circuitlen; i++) { + int ni = circuit[i]; + if (dp[ni] < 0) { + dp[ni] = 0; + list[tail++] = ni; + } + } + while (head < tail) { + int ni = list[head++]; + for (i = ei[ni]; i < ei[ni+1]; i++) { + int ti = ep[i]; + if (ti >= 0 && dp[ti] < 0) { + dp[ti] = dp[ni] + 1; + list[tail++] = ti; + } + } + } + } + /* Now find the nearest unvisited gem. */ + bestdist = -1; + target = -1; + for (i = 0; i < n; i++) { + if (unvisited[nodes[i] / DP1] && + dist[i] >= 0 && dist2[i] >= 0) { + int thisdist = dist[i] + dist2[i]; + if (bestdist < 0 || bestdist > thisdist) { + bestdist = thisdist; + target = i; + } + } + } + + if (target < 0) { + /* + * If we get to here, we haven't found a gem we can get + * at all, which means we terminate this loop. + */ + break; + } + + /* + * Now we have a graph vertex at list[tail-1] which is an + * unvisited gem. We want to add that vertex to our tour. + * So we run two more breadth-first searches: one starting + * from that vertex and following forward edges, and + * another starting from the same vertex and following + * backward edges. This allows us to determine, for each + * node on the current tour, how quickly we can get both to + * and from the target vertex from that node. + */ +#ifdef TSP_DIAGNOSTICS + printf("target node is %d (%d,%d,%d)\n", target, nodes[target]/DP1%w, + nodes[target]/DP1/w, nodes[target]%DP1); +#endif + + for (pass = 0; pass < 2; pass++) { + int *ep = (pass == 0 ? edges : backedges); + int *ei = (pass == 0 ? edgei : backedgei); + int *dp = (pass == 0 ? dist : dist2); + + for (i = 0; i < n; i++) + dp[i] = -1; + head = tail = 0; + + dp[target] = 0; + list[tail++] = target; + + while (head < tail) { + int ni = list[head++]; + for (i = ei[ni]; i < ei[ni+1]; i++) { + int ti = ep[i]; + if (ti >= 0 && dp[ti] < 0) { + dp[ti] = dp[ni] + 1; +/*printf("pass %d: set dist of vertex %d to %d (via %d)\n", pass, ti, dp[ti], ni);*/ + list[tail++] = ti; + } + } + } + } + + /* + * Now for every node n, dist[n] gives the length of the + * shortest path from the target vertex to n, and dist2[n] + * gives the length of the shortest path from n to the + * target vertex. + * + * Our next step is to search linearly along the tour to + * find the optimum place to insert a trip to the target + * vertex and back. Our two options are either + * (a) to find two adjacent vertices A,B in the tour and + * replace the edge A->B with the path A->target->B + * (b) to find a single vertex X in the tour and replace + * it with the complete round trip X->target->X. + * We do whichever takes the fewest moves. + */ + n1 = n2 = -1; + bestdist = -1; + for (i = 0; i < circuitlen; i++) { + int thisdist; + + /* + * Try a round trip from vertex i. + */ + if (dist[circuit[i]] >= 0 && + dist2[circuit[i]] >= 0) { + thisdist = dist[circuit[i]] + dist2[circuit[i]]; + if (bestdist < 0 || thisdist < bestdist) { + bestdist = thisdist; + n1 = n2 = i; + } + } + + /* + * Try a trip from vertex i via target to vertex i+1. + */ + if (i+1 < circuitlen && + dist2[circuit[i]] >= 0 && + dist[circuit[i+1]] >= 0) { + thisdist = dist2[circuit[i]] + dist[circuit[i+1]]; + if (bestdist < 0 || thisdist < bestdist) { + bestdist = thisdist; + n1 = i; + n2 = i+1; + } + } + } + if (bestdist < 0) { + /* + * We couldn't find a round trip taking in this gem _at + * all_. Give up. + */ + err = "Unable to find a solution from this starting point"; + break; + } +#ifdef TSP_DIAGNOSTICS + printf("insertion point: n1=%d, n2=%d, dist=%d\n", n1, n2, bestdist); +#endif + +#ifdef TSP_DIAGNOSTICS + printf("circuit before lengthening is"); + for (i = 0; i < circuitlen; i++) { + printf(" %d", circuit[i]); + } + printf("\n"); +#endif + + /* + * Now actually lengthen the tour to take in this round + * trip. + */ + extralen = dist2[circuit[n1]] + dist[circuit[n2]]; + if (n1 != n2) + extralen--; + circuitlen += extralen; + if (circuitlen >= circuitsize) { + circuitsize = circuitlen + 256; + circuit = sresize(circuit, circuitsize, int); + } + memmove(circuit + n2 + extralen, circuit + n2, + (circuitlen - n2 - extralen) * sizeof(int)); + n2 += extralen; + +#ifdef TSP_DIAGNOSTICS + printf("circuit in middle of lengthening is"); + for (i = 0; i < circuitlen; i++) { + printf(" %d", circuit[i]); + } + printf("\n"); +#endif + + /* + * Find the shortest-path routes to and from the target, + * and write them into the circuit. + */ + targetpos = n1 + dist2[circuit[n1]]; + assert(targetpos - dist2[circuit[n1]] == n1); + assert(targetpos + dist[circuit[n2]] == n2); + for (pass = 0; pass < 2; pass++) { + int dir = (pass == 0 ? -1 : +1); + int *ep = (pass == 0 ? backedges : edges); + int *ei = (pass == 0 ? backedgei : edgei); + int *dp = (pass == 0 ? dist : dist2); + int nn = (pass == 0 ? n2 : n1); + int ni = circuit[nn], ti, dest = nn; + + while (1) { + circuit[dest] = ni; + if (dp[ni] == 0) + break; + dest += dir; + ti = -1; +/*printf("pass %d: looking at vertex %d\n", pass, ni);*/ + for (i = ei[ni]; i < ei[ni+1]; i++) { + ti = ep[i]; + if (ti >= 0 && dp[ti] == dp[ni] - 1) + break; + } + assert(i < ei[ni+1] && ti >= 0); + ni = ti; + } + } + +#ifdef TSP_DIAGNOSTICS + printf("circuit after lengthening is"); + for (i = 0; i < circuitlen; i++) { + printf(" %d", circuit[i]); + } + printf("\n"); +#endif + + /* + * Finally, mark all gems that the new piece of circuit + * passes through as visited. + */ + for (i = n1; i <= n2; i++) { + int pos = nodes[circuit[i]] / DP1; + assert(pos >= 0 && pos < wh); + unvisited[pos] = FALSE; + } + } + +#ifdef TSP_DIAGNOSTICS + printf("before reduction, moves are "); + x = nodes[circuit[0]] / DP1 % w; + y = nodes[circuit[0]] / DP1 / w; + for (i = 1; i < circuitlen; i++) { + int x2, y2, dx, dy; + if (nodes[circuit[i]] % DP1 != DIRECTIONS) + continue; + x2 = nodes[circuit[i]] / DP1 % w; + y2 = nodes[circuit[i]] / DP1 / w; + dx = (x2 > x ? +1 : x2 < x ? -1 : 0); + dy = (y2 > y ? +1 : y2 < y ? -1 : 0); + for (d = 0; d < DIRECTIONS; d++) + if (DX(d) == dx && DY(d) == dy) + printf("%c", "89632147"[d]); + x = x2; + y = y2; + } + printf("\n"); +#endif + + /* + * That's got a basic solution. Now optimise it by removing + * redundant sections of the circuit: it's entirely possible + * that a piece of circuit we carefully inserted at one stage + * to collect a gem has become pointless because the steps + * required to collect some _later_ gem necessarily passed + * through the same one. + * + * So first we go through and work out how many times each gem + * is collected. Then we look for maximal sections of circuit + * which are redundant in the sense that their removal would + * not reduce any gem's collection count to zero, and replace + * each one with a bfs-derived fastest path between their + * endpoints. + */ + while (1) { + int oldlen = circuitlen; + int dir; + + for (dir = +1; dir >= -1; dir -= 2) { + + for (i = 0; i < wh; i++) + unvisited[i] = 0; + for (i = 0; i < circuitlen; i++) { + int xy = nodes[circuit[i]] / DP1; + if (currstate->grid[xy] == GEM) + unvisited[xy]++; + } + + /* + * If there's any gem we didn't end up visiting at all, + * give up. + */ + for (i = 0; i < wh; i++) { + if (currstate->grid[i] == GEM && unvisited[i] == 0) { + err = "Unable to find a solution from this starting point"; + break; + } + } + if (i < wh) + break; + + for (i = j = (dir > 0 ? 0 : circuitlen-1); + i < circuitlen && i >= 0; + i += dir) { + int xy = nodes[circuit[i]] / DP1; + if (currstate->grid[xy] == GEM && unvisited[xy] > 1) { + unvisited[xy]--; + } else if (currstate->grid[xy] == GEM || i == circuitlen-1) { + /* + * circuit[i] collects a gem for the only time, + * or is the last node in the circuit. + * Therefore it cannot be removed; so we now + * want to replace the path from circuit[j] to + * circuit[i] with a bfs-shortest path. + */ + int p, q, k, dest, ni, ti, thisdist; + + /* + * Set up the upper and lower bounds of the + * reduced section. + */ + p = min(i, j); + q = max(i, j); + +#ifdef TSP_DIAGNOSTICS + printf("optimising section from %d - %d\n", p, q); +#endif + + for (k = 0; k < n; k++) + dist[k] = -1; + head = tail = 0; + + dist[circuit[p]] = 0; + list[tail++] = circuit[p]; + + while (head < tail && dist[circuit[q]] < 0) { + int ni = list[head++]; + for (k = edgei[ni]; k < edgei[ni+1]; k++) { + int ti = edges[k]; + if (ti >= 0 && dist[ti] < 0) { + dist[ti] = dist[ni] + 1; + list[tail++] = ti; + } + } + } + + thisdist = dist[circuit[q]]; + assert(thisdist >= 0 && thisdist <= q-p); + + memmove(circuit+p+thisdist, circuit+q, + (circuitlen - q) * sizeof(int)); + circuitlen -= q-p; + q = p + thisdist; + circuitlen += q-p; + + if (dir > 0) + i = q; /* resume loop from the right place */ + +#ifdef TSP_DIAGNOSTICS + printf("new section runs from %d - %d\n", p, q); +#endif + + dest = q; + assert(dest >= 0); + ni = circuit[q]; + + while (1) { + /* printf("dest=%d circuitlen=%d ni=%d dist[ni]=%d\n", dest, circuitlen, ni, dist[ni]); */ + circuit[dest] = ni; + if (dist[ni] == 0) + break; + dest--; + ti = -1; + for (k = backedgei[ni]; k < backedgei[ni+1]; k++) { + ti = backedges[k]; + if (ti >= 0 && dist[ti] == dist[ni] - 1) + break; + } + assert(k < backedgei[ni+1] && ti >= 0); + ni = ti; + } + + /* + * Now re-increment the visit counts for the + * new path. + */ + while (++p < q) { + int xy = nodes[circuit[p]] / DP1; + if (currstate->grid[xy] == GEM) + unvisited[xy]++; + } + + j = i; + +#ifdef TSP_DIAGNOSTICS + printf("during reduction, circuit is"); + for (k = 0; k < circuitlen; k++) { + int nc = nodes[circuit[k]]; + printf(" (%d,%d,%d)", nc/DP1%w, nc/(DP1*w), nc%DP1); + } + printf("\n"); + printf("moves are "); + x = nodes[circuit[0]] / DP1 % w; + y = nodes[circuit[0]] / DP1 / w; + for (k = 1; k < circuitlen; k++) { + int x2, y2, dx, dy; + if (nodes[circuit[k]] % DP1 != DIRECTIONS) + continue; + x2 = nodes[circuit[k]] / DP1 % w; + y2 = nodes[circuit[k]] / DP1 / w; + dx = (x2 > x ? +1 : x2 < x ? -1 : 0); + dy = (y2 > y ? +1 : y2 < y ? -1 : 0); + for (d = 0; d < DIRECTIONS; d++) + if (DX(d) == dx && DY(d) == dy) + printf("%c", "89632147"[d]); + x = x2; + y = y2; + } + printf("\n"); +#endif + } + } + +#ifdef TSP_DIAGNOSTICS + printf("after reduction, moves are "); + x = nodes[circuit[0]] / DP1 % w; + y = nodes[circuit[0]] / DP1 / w; + for (i = 1; i < circuitlen; i++) { + int x2, y2, dx, dy; + if (nodes[circuit[i]] % DP1 != DIRECTIONS) + continue; + x2 = nodes[circuit[i]] / DP1 % w; + y2 = nodes[circuit[i]] / DP1 / w; + dx = (x2 > x ? +1 : x2 < x ? -1 : 0); + dy = (y2 > y ? +1 : y2 < y ? -1 : 0); + for (d = 0; d < DIRECTIONS; d++) + if (DX(d) == dx && DY(d) == dy) + printf("%c", "89632147"[d]); + x = x2; + y = y2; + } + printf("\n"); +#endif + } + + /* + * If we've managed an entire reduction pass in each + * direction and not made the solution any shorter, we're + * _really_ done. + */ + if (circuitlen == oldlen) + break; + } + + /* + * Encode the solution as a move string. + */ + if (!err) { + soln = snewn(circuitlen+2, char); + p = soln; + *p++ = 'S'; + x = nodes[circuit[0]] / DP1 % w; + y = nodes[circuit[0]] / DP1 / w; + for (i = 1; i < circuitlen; i++) { + int x2, y2, dx, dy; + if (nodes[circuit[i]] % DP1 != DIRECTIONS) + continue; + x2 = nodes[circuit[i]] / DP1 % w; + y2 = nodes[circuit[i]] / DP1 / w; + dx = (x2 > x ? +1 : x2 < x ? -1 : 0); + dy = (y2 > y ? +1 : y2 < y ? -1 : 0); + for (d = 0; d < DIRECTIONS; d++) + if (DX(d) == dx && DY(d) == dy) { + *p++ = '0' + d; + break; + } + assert(d < DIRECTIONS); + x = x2; + y = y2; + } + *p++ = '\0'; + assert(p - soln < circuitlen+2); + } + + sfree(list); + sfree(dist); + sfree(dist2); + sfree(unvisited); + sfree(circuit); + sfree(backedgei); + sfree(backedges); + sfree(edgei); + sfree(edges); + sfree(nodeindex); + sfree(nodes); + + if (err) + *error = err; + + return soln; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->p.w, h = state->p.h, r, c; + int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh; + char *board = snewn(len + 1, char); + + sprintf(board, "%*s+\n", len - 2, ""); + + for (r = 0; r < h; ++r) { + for (c = 0; c < w; ++c) { + int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; + int i = r*w + c; + switch (state->grid[i]) { + case BLANK: break; + case GEM: board[center] = 'o'; break; + case MINE: board[center] = 'M'; break; + case STOP: board[center-1] = '('; board[center+1] = ')'; break; + case WALL: memset(board + center - 1, 'X', 3); + } + + if (r == state->py && c == state->px) { + if (!state->dead) board[center] = '@'; + else memcpy(board + center - 1, ":-(", 3); + } + board[cell] = '+'; + memset(board + cell + 1, '-', cw - 1); + for (i = 1; i < ch; ++i) board[cell + i*gw] = '|'; + } + for (c = 0; c < ch; ++c) { + board[(r*ch+c)*gw + gw - 2] = "|+"[!c]; + board[(r*ch+c)*gw + gw - 1] = '\n'; + } + } + memset(board + len - gw, '-', gw - 2); + for (c = 0; c < w; ++c) board[len - gw + cw*c] = '+'; + + return board; +} + +struct game_ui { + float anim_length; + int flashtype; + int deaths; + int just_made_move; + int just_died; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->anim_length = 0.0F; + ui->flashtype = 0; + ui->deaths = 0; + ui->just_made_move = FALSE; + ui->just_died = FALSE; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + char buf[80]; + /* + * The deaths counter needs preserving across a serialisation. + */ + sprintf(buf, "D%d", ui->deaths); + return dupstr(buf); +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + int p = 0; + sscanf(encoding, "D%d%n", &ui->deaths, &p); +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + /* + * Increment the deaths counter. We only do this if + * ui->just_made_move is set (redoing a suicide move doesn't + * kill you _again_), and also we only do it if the game wasn't + * already completed (once you're finished, you can play). + */ + if (!oldstate->dead && newstate->dead && ui->just_made_move && + oldstate->gems) { + ui->deaths++; + ui->just_died = TRUE; + } else { + ui->just_died = FALSE; + } + ui->just_made_move = FALSE; +} + +struct game_drawstate { + game_params p; + int tilesize; + int started; + unsigned short *grid; + blitter *player_background; + int player_bg_saved, pbgx, pbgy; +}; + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#ifdef SMALL_SCREEN +#define BORDER (TILESIZE / 4) +#else +#define BORDER (TILESIZE) +#endif +#define HIGHLIGHT_WIDTH (TILESIZE / 10) +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->p.w, h = state->p.h /*, wh = w*h */; + int dir; + char buf[80]; + + dir = -1; + + if (button == LEFT_BUTTON) { + /* + * Mouse-clicking near the target point (or, more + * accurately, in the appropriate octant) is an alternative + * way to input moves. + */ + + if (FROMCOORD(x) != state->px || FROMCOORD(y) != state->py) { + int dx, dy; + float angle; + + dx = FROMCOORD(x) - state->px; + dy = FROMCOORD(y) - state->py; + /* I pass dx,dy rather than dy,dx so that the octants + * end up the right way round. */ + angle = atan2(dx, -dy); + + angle = (angle + (PI/8)) / (PI/4); + assert(angle > -16.0F); + dir = (int)(angle + 16.0F) & 7; + } + } else if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8')) + dir = 0; + else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2')) + dir = 4; + else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4')) + dir = 6; + else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6')) + dir = 2; + else if (button == (MOD_NUM_KEYPAD | '7')) + dir = 7; + else if (button == (MOD_NUM_KEYPAD | '1')) + dir = 5; + else if (button == (MOD_NUM_KEYPAD | '9')) + dir = 1; + else if (button == (MOD_NUM_KEYPAD | '3')) + dir = 3; + else if (IS_CURSOR_SELECT(button) && + state->soln && state->solnpos < state->soln->len) + dir = state->soln->list[state->solnpos]; + + if (dir < 0) + return NULL; + + /* + * Reject the move if we can't make it at all due to a wall + * being in the way. + */ + if (AT(w, h, state->grid, state->px+DX(dir), state->py+DY(dir)) == WALL) + return NULL; + + /* + * Reject the move if we're dead! + */ + if (state->dead) + return NULL; + + /* + * Otherwise, we can make the move. All we need to specify is + * the direction. + */ + ui->just_made_move = TRUE; + sprintf(buf, "%d", dir); + return dupstr(buf); +} + +static void install_new_solution(game_state *ret, const char *move) +{ + int i; + soln *sol; + assert (*move == 'S'); + ++move; + + sol = snew(soln); + sol->len = strlen(move); + sol->list = snewn(sol->len, unsigned char); + for (i = 0; i < sol->len; ++i) sol->list[i] = move[i] - '0'; + + if (ret->soln && --ret->soln->refcount == 0) { + sfree(ret->soln->list); + sfree(ret->soln); + } + + ret->soln = sol; + sol->refcount = 1; + + ret->cheated = TRUE; + ret->solnpos = 0; +} + +static void discard_solution(game_state *ret) +{ + --ret->soln->refcount; + assert(ret->soln->refcount > 0); /* ret has a soln-pointing dup */ + ret->soln = NULL; + ret->solnpos = 0; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->p.w, h = state->p.h /*, wh = w*h */; + int dir; + game_state *ret; + + if (*move == 'S') { + /* + * This is a solve move, so we don't actually _change_ the + * grid but merely set up a stored solution path. + */ + ret = dup_game(state); + install_new_solution(ret, move); + return ret; + } + + dir = atoi(move); + if (dir < 0 || dir >= DIRECTIONS) + return NULL; /* huh? */ + + if (state->dead) + return NULL; + + if (AT(w, h, state->grid, state->px+DX(dir), state->py+DY(dir)) == WALL) + return NULL; /* wall in the way! */ + + /* + * Now make the move. + */ + ret = dup_game(state); + ret->distance_moved = 0; + while (1) { + ret->px += DX(dir); + ret->py += DY(dir); + ret->distance_moved++; + + if (AT(w, h, ret->grid, ret->px, ret->py) == GEM) { + LV_AT(w, h, ret->grid, ret->px, ret->py) = BLANK; + ret->gems--; + } + + if (AT(w, h, ret->grid, ret->px, ret->py) == MINE) { + ret->dead = TRUE; + break; + } + + if (AT(w, h, ret->grid, ret->px, ret->py) == STOP || + AT(w, h, ret->grid, ret->px+DX(dir), + ret->py+DY(dir)) == WALL) + break; + } + + if (ret->soln) { + if (ret->dead || ret->gems == 0) + discard_solution(ret); + else if (ret->soln->list[ret->solnpos] == dir) { + ++ret->solnpos; + assert(ret->solnpos < ret->soln->len); /* or gems == 0 */ + assert(!ret->dead); /* or not a solution */ + } else { + char *error = NULL, *soln = solve_game(NULL, ret, NULL, &error); + if (!error) { + install_new_solution(ret, soln); + sfree(soln); + } else discard_solution(ret); + } + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = 2 * BORDER + 1 + params->w * TILESIZE; + *y = 2 * BORDER + 1 + params->h * TILESIZE; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + + assert(!ds->player_background); /* set_size is never called twice */ + assert(!ds->player_bg_saved); + + ds->player_background = blitter_new(dr, TILESIZE, TILESIZE); +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + ret[COL_OUTLINE * 3 + 0] = 0.0F; + ret[COL_OUTLINE * 3 + 1] = 0.0F; + ret[COL_OUTLINE * 3 + 2] = 0.0F; + + ret[COL_PLAYER * 3 + 0] = 0.0F; + ret[COL_PLAYER * 3 + 1] = 1.0F; + ret[COL_PLAYER * 3 + 2] = 0.0F; + + ret[COL_DEAD_PLAYER * 3 + 0] = 1.0F; + ret[COL_DEAD_PLAYER * 3 + 1] = 0.0F; + ret[COL_DEAD_PLAYER * 3 + 2] = 0.0F; + + ret[COL_MINE * 3 + 0] = 0.0F; + ret[COL_MINE * 3 + 1] = 0.0F; + ret[COL_MINE * 3 + 2] = 0.0F; + + ret[COL_GEM * 3 + 0] = 0.6F; + ret[COL_GEM * 3 + 1] = 1.0F; + ret[COL_GEM * 3 + 2] = 1.0F; + + for (i = 0; i < 3; i++) { + ret[COL_WALL * 3 + i] = (3 * ret[COL_BACKGROUND * 3 + i] + + 1 * ret[COL_HIGHLIGHT * 3 + i]) / 4; + } + + ret[COL_HINT * 3 + 0] = 1.0F; + ret[COL_HINT * 3 + 1] = 1.0F; + ret[COL_HINT * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->p.w, h = state->p.h, wh = w*h; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + + /* We can't allocate the blitter rectangle for the player background + * until we know what size to make it. */ + ds->player_background = NULL; + ds->player_bg_saved = FALSE; + ds->pbgx = ds->pbgy = -1; + + ds->p = state->p; /* structure copy */ + ds->started = FALSE; + ds->grid = snewn(wh, unsigned short); + for (i = 0; i < wh; i++) + ds->grid[i] = UNDRAWN; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + if (ds->player_background) + blitter_free(dr, ds->player_background); + sfree(ds->grid); + sfree(ds); +} + +static void draw_player(drawing *dr, game_drawstate *ds, int x, int y, + int dead, int hintdir) +{ + if (dead) { + int coords[DIRECTIONS*4]; + int d; + + for (d = 0; d < DIRECTIONS; d++) { + float x1, y1, x2, y2, x3, y3, len; + + x1 = DX(d); + y1 = DY(d); + len = sqrt(x1*x1+y1*y1); x1 /= len; y1 /= len; + + x3 = DX(d+1); + y3 = DY(d+1); + len = sqrt(x3*x3+y3*y3); x3 /= len; y3 /= len; + + x2 = (x1+x3) / 4; + y2 = (y1+y3) / 4; + + coords[d*4+0] = x + TILESIZE/2 + (int)((TILESIZE*3/7) * x1); + coords[d*4+1] = y + TILESIZE/2 + (int)((TILESIZE*3/7) * y1); + coords[d*4+2] = x + TILESIZE/2 + (int)((TILESIZE*3/7) * x2); + coords[d*4+3] = y + TILESIZE/2 + (int)((TILESIZE*3/7) * y2); + } + draw_polygon(dr, coords, DIRECTIONS*2, COL_DEAD_PLAYER, COL_OUTLINE); + } else { + draw_circle(dr, x + TILESIZE/2, y + TILESIZE/2, + TILESIZE/3, COL_PLAYER, COL_OUTLINE); + } + + if (!dead && hintdir >= 0) { + float scale = (DX(hintdir) && DY(hintdir) ? 0.8F : 1.0F); + int ax = (TILESIZE*2/5) * scale * DX(hintdir); + int ay = (TILESIZE*2/5) * scale * DY(hintdir); + int px = -ay, py = ax; + int ox = x + TILESIZE/2, oy = y + TILESIZE/2; + int coords[14], *c; + + c = coords; + *c++ = ox + px/9; + *c++ = oy + py/9; + *c++ = ox + px/9 + ax*2/3; + *c++ = oy + py/9 + ay*2/3; + *c++ = ox + px/3 + ax*2/3; + *c++ = oy + py/3 + ay*2/3; + *c++ = ox + ax; + *c++ = oy + ay; + *c++ = ox - px/3 + ax*2/3; + *c++ = oy - py/3 + ay*2/3; + *c++ = ox - px/9 + ax*2/3; + *c++ = oy - py/9 + ay*2/3; + *c++ = ox - px/9; + *c++ = oy - py/9; + draw_polygon(dr, coords, 7, COL_HINT, COL_OUTLINE); + } + + draw_update(dr, x, y, TILESIZE, TILESIZE); +} + +#define FLASH_DEAD 0x100 +#define FLASH_WIN 0x200 +#define FLASH_MASK 0x300 + +static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v) +{ + int tx = COORD(x), ty = COORD(y); + int bg = (v & FLASH_DEAD ? COL_DEAD_PLAYER : + v & FLASH_WIN ? COL_HIGHLIGHT : COL_BACKGROUND); + + v &= ~FLASH_MASK; + + clip(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1); + draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, bg); + + if (v == WALL) { + int coords[6]; + + coords[0] = tx + TILESIZE; + coords[1] = ty + TILESIZE; + coords[2] = tx + TILESIZE; + coords[3] = ty + 1; + coords[4] = tx + 1; + coords[5] = ty + TILESIZE; + draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); + + coords[0] = tx + 1; + coords[1] = ty + 1; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + + draw_rect(dr, tx + 1 + HIGHLIGHT_WIDTH, ty + 1 + HIGHLIGHT_WIDTH, + TILESIZE - 2*HIGHLIGHT_WIDTH, + TILESIZE - 2*HIGHLIGHT_WIDTH, COL_WALL); + } else if (v == MINE) { + int cx = tx + TILESIZE / 2; + int cy = ty + TILESIZE / 2; + int r = TILESIZE / 2 - 3; + + draw_circle(dr, cx, cy, 5*r/6, COL_MINE, COL_MINE); + draw_rect(dr, cx - r/6, cy - r, 2*(r/6)+1, 2*r+1, COL_MINE); + draw_rect(dr, cx - r, cy - r/6, 2*r+1, 2*(r/6)+1, COL_MINE); + draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT); + } else if (v == STOP) { + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE*3/7, -1, COL_OUTLINE); + draw_rect(dr, tx + TILESIZE*3/7, ty+1, + TILESIZE - 2*(TILESIZE*3/7) + 1, TILESIZE-1, bg); + draw_rect(dr, tx+1, ty + TILESIZE*3/7, + TILESIZE-1, TILESIZE - 2*(TILESIZE*3/7) + 1, bg); + } else if (v == GEM) { + int coords[8]; + + coords[0] = tx+TILESIZE/2; + coords[1] = ty+TILESIZE/2-TILESIZE*5/14; + coords[2] = tx+TILESIZE/2-TILESIZE*5/14; + coords[3] = ty+TILESIZE/2; + coords[4] = tx+TILESIZE/2; + coords[5] = ty+TILESIZE/2+TILESIZE*5/14; + coords[6] = tx+TILESIZE/2+TILESIZE*5/14; + coords[7] = ty+TILESIZE/2; + + draw_polygon(dr, coords, 4, COL_GEM, COL_OUTLINE); + } + + unclip(dr); + draw_update(dr, tx, ty, TILESIZE, TILESIZE); +} + +#define BASE_ANIM_LENGTH 0.1F +#define FLASH_LENGTH 0.3F + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->p.w, h = state->p.h /*, wh = w*h */; + int x, y; + float ap; + int player_dist; + int flashtype; + int gems, deaths; + char status[256]; + + if (flashtime && + !((int)(flashtime * 3 / FLASH_LENGTH) % 2)) + flashtype = ui->flashtype; + else + flashtype = 0; + + /* + * Erase the player sprite. + */ + if (ds->player_bg_saved) { + assert(ds->player_background); + blitter_load(dr, ds->player_background, ds->pbgx, ds->pbgy); + draw_update(dr, ds->pbgx, ds->pbgy, TILESIZE, TILESIZE); + ds->player_bg_saved = FALSE; + } + + /* + * Initialise a fresh drawstate. + */ + if (!ds->started) { + int wid, ht; + + /* + * Blank out the window initially. + */ + game_compute_size(&ds->p, TILESIZE, &wid, &ht); + draw_rect(dr, 0, 0, wid, ht, COL_BACKGROUND); + draw_update(dr, 0, 0, wid, ht); + + /* + * Draw the grid lines. + */ + for (y = 0; y <= h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), + COL_LOWLIGHT); + for (x = 0; x <= w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), + COL_LOWLIGHT); + + ds->started = TRUE; + } + + /* + * If we're in the process of animating a move, let's start by + * working out how far the player has moved from their _older_ + * state. + */ + if (oldstate) { + ap = animtime / ui->anim_length; + player_dist = ap * (dir > 0 ? state : oldstate)->distance_moved; + } else { + player_dist = 0; + ap = 0.0F; + } + + /* + * Draw the grid contents. + * + * We count the gems as we go round this loop, for the purposes + * of the status bar. Of course we have a gems counter in the + * game_state already, but if we do the counting in this loop + * then it tracks gems being picked up in a sliding move, and + * updates one by one. + */ + gems = 0; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + unsigned short v = (unsigned char)state->grid[y*w+x]; + + /* + * Special case: if the player is in the process of + * moving over a gem, we draw the gem iff they haven't + * gone past it yet. + */ + if (oldstate && oldstate->grid[y*w+x] != state->grid[y*w+x]) { + /* + * Compute the distance from this square to the + * original player position. + */ + int dist = max(abs(x - oldstate->px), abs(y - oldstate->py)); + + /* + * If the player has reached here, use the new grid + * element. Otherwise use the old one. + */ + if (player_dist < dist) + v = oldstate->grid[y*w+x]; + else + v = state->grid[y*w+x]; + } + + /* + * Special case: erase the mine the dead player is + * sitting on. Only at the end of the move. + */ + if (v == MINE && !oldstate && state->dead && + x == state->px && y == state->py) + v = BLANK; + + if (v == GEM) + gems++; + + v |= flashtype; + + if (ds->grid[y*w+x] != v) { + draw_tile(dr, ds, x, y, v); + ds->grid[y*w+x] = v; + } + } + + /* + * Gem counter in the status bar. We replace it with + * `COMPLETED!' when it reaches zero ... or rather, when the + * _current state_'s gem counter is zero. (Thus, `Gems: 0' is + * shown between the collection of the last gem and the + * completion of the move animation that did it.) + */ + if (state->dead && (!oldstate || oldstate->dead)) { + sprintf(status, "DEAD!"); + } else if (state->gems || (oldstate && oldstate->gems)) { + if (state->cheated) + sprintf(status, "Auto-solver used. "); + else + *status = '\0'; + sprintf(status + strlen(status), "Gems: %d", gems); + } else if (state->cheated) { + sprintf(status, "Auto-solved."); + } else { + sprintf(status, "COMPLETED!"); + } + /* We subtract one from the visible death counter if we're still + * animating the move at the end of which the death took place. */ + deaths = ui->deaths; + if (oldstate && ui->just_died) { + assert(deaths > 0); + deaths--; + } + if (deaths) + sprintf(status + strlen(status), " Deaths: %d", deaths); + status_bar(dr, status); + + /* + * Draw the player sprite. + */ + assert(!ds->player_bg_saved); + assert(ds->player_background); + { + int ox, oy, nx, ny; + nx = COORD(state->px); + ny = COORD(state->py); + if (oldstate) { + ox = COORD(oldstate->px); + oy = COORD(oldstate->py); + } else { + ox = nx; + oy = ny; + } + ds->pbgx = ox + ap * (nx - ox); + ds->pbgy = oy + ap * (ny - oy); + } + blitter_save(dr, ds->player_background, ds->pbgx, ds->pbgy); + draw_player(dr, ds, ds->pbgx, ds->pbgy, + (state->dead && !oldstate), + (!oldstate && state->soln ? + state->soln->list[state->solnpos] : -1)); + ds->player_bg_saved = TRUE; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + int dist; + if (dir > 0) + dist = newstate->distance_moved; + else + dist = oldstate->distance_moved; + ui->anim_length = sqrt(dist) * BASE_ANIM_LENGTH; + return ui->anim_length; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->dead && newstate->dead) { + ui->flashtype = FLASH_DEAD; + return FLASH_LENGTH; + } else if (oldstate->gems && !newstate->gems) { + ui->flashtype = FLASH_WIN; + return FLASH_LENGTH; + } + return 0.0F; +} + +static int game_status(const game_state *state) +{ + /* + * We never report the game as lost, on the grounds that if the + * player has died they're quite likely to want to undo and carry + * on. + */ + return state->gems == 0 ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame inertia +#endif + +const struct game thegame = { + "Inertia", "games.inertia", "inertia", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/keen.R b/apps/plugins/puzzles/keen.R new file mode 100644 index 0000000000..77609bc7fa --- /dev/null +++ b/apps/plugins/puzzles/keen.R @@ -0,0 +1,25 @@ +# -*- makefile -*- + +KEEN_LATIN_EXTRA = tree234 maxflow dsf +KEEN_EXTRA = latin KEEN_LATIN_EXTRA + +keen : [X] GTK COMMON keen KEEN_EXTRA keen-icon|no-icon + +keen : [G] WINDOWS COMMON keen KEEN_EXTRA keen.res|noicon.res + +keensolver : [U] keen[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] KEEN_LATIN_EXTRA STANDALONE +keensolver : [C] keen[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] KEEN_LATIN_EXTRA STANDALONE + +ALL += keen[COMBINED] KEEN_EXTRA + +!begin am gtk +GAMES += keen +!end + +!begin >list.c + A(keen) \ +!end + +!begin >gamedesc.txt +keen:keen.exe:Keen:Arithmetic Latin square puzzle:Complete the latin square in accordance with the arithmetic clues. +!end diff --git a/apps/plugins/puzzles/keen.c b/apps/plugins/puzzles/keen.c new file mode 100644 index 0000000000..32d6288f88 --- /dev/null +++ b/apps/plugins/puzzles/keen.c @@ -0,0 +1,2479 @@ +/* + * keen.c: an implementation of the Times's 'KenKen' puzzle, and + * also of Nikoli's very similar 'Inshi No Heya' puzzle. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "latin.h" + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,solver_easy,e) \ + A(NORMAL,Normal,solver_normal,n) \ + A(HARD,Hard,solver_hard,h) \ + A(EXTREME,Extreme,NULL,x) \ + A(UNREASONABLE,Unreasonable,NULL,u) +#define ENUM(upper,title,func,lower) DIFF_ ## upper, +#define TITLE(upper,title,func,lower) #title, +#define ENCODE(upper,title,func,lower) #lower +#define CONFIG(upper,title,func,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const keen_diffnames[] = { DIFFLIST(TITLE) }; +static char const keen_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +/* + * Clue notation. Important here that ADD and MUL come before SUB + * and DIV, and that DIV comes last. + */ +#define C_ADD 0x00000000L +#define C_MUL 0x20000000L +#define C_SUB 0x40000000L +#define C_DIV 0x60000000L +#define CMASK 0x60000000L +#define CUNIT 0x20000000L + +/* + * Maximum size of any clue block. Very large ones are annoying in UI + * terms (if they're multiplicative you end up with too many digits to + * fit in the square) and also in solver terms (too many possibilities + * to iterate over). + */ +#define MAXBLK 6 + +enum { + COL_BACKGROUND, + COL_GRID, + COL_USER, + COL_HIGHLIGHT, + COL_ERROR, + COL_PENCIL, + NCOLOURS +}; + +struct game_params { + int w, diff, multiplication_only; +}; + +struct clues { + int refcount; + int w; + int *dsf; + long *clues; +}; + +struct game_state { + game_params par; + struct clues *clues; + digit *grid; + int *pencil; /* bitmaps using bits 1<<1..1<w = 6; + ret->diff = DIFF_NORMAL; + ret->multiplication_only = FALSE; + + return ret; +} + +const static struct game_params keen_presets[] = { + { 4, DIFF_EASY, FALSE }, + { 5, DIFF_EASY, FALSE }, + { 5, DIFF_EASY, TRUE }, + { 6, DIFF_EASY, FALSE }, + { 6, DIFF_NORMAL, FALSE }, + { 6, DIFF_NORMAL, TRUE }, + { 6, DIFF_HARD, FALSE }, + { 6, DIFF_EXTREME, FALSE }, + { 6, DIFF_UNREASONABLE, FALSE }, + { 9, DIFF_NORMAL, FALSE }, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(keen_presets)) + return FALSE; + + ret = snew(game_params); + *ret = keen_presets[i]; /* structure copy */ + + sprintf(buf, "%dx%d %s%s", ret->w, ret->w, keen_diffnames[ret->diff], + ret->multiplication_only ? ", multiplication only" : ""); + + *name = dupstr(buf); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + + if (*p == 'd') { + int i; + p++; + params->diff = DIFFCOUNT+1; /* ...which is invalid */ + if (*p) { + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == keen_diffchars[i]) + params->diff = i; + } + p++; + } + } + + if (*p == 'm') { + p++; + params->multiplication_only = TRUE; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[80]; + + sprintf(ret, "%d", params->w); + if (full) + sprintf(ret + strlen(ret), "d%c%s", keen_diffchars[params->diff], + params->multiplication_only ? "m" : ""); + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Grid size"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Difficulty"; + ret[1].type = C_CHOICES; + ret[1].sval = DIFFCONFIG; + ret[1].ival = params->diff; + + ret[2].name = "Multiplication only"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->multiplication_only; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->diff = cfg[1].ival; + ret->multiplication_only = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 3 || params->w > 9) + return "Grid size must be between 3 and 9"; + if (params->diff >= DIFFCOUNT) + return "Unknown difficulty rating"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +struct solver_ctx { + int w, diff; + int nboxes; + int *boxes, *boxlist, *whichbox; + long *clues; + digit *soln; + digit *dscratch; + int *iscratch; +}; + +static void solver_clue_candidate(struct solver_ctx *ctx, int diff, int box) +{ + int w = ctx->w; + int n = ctx->boxes[box+1] - ctx->boxes[box]; + int j; + + /* + * This function is called from the main clue-based solver + * routine when we discover a candidate layout for a given clue + * box consistent with everything we currently know about the + * digit constraints in that box. We expect to find the digits + * of the candidate layout in ctx->dscratch, and we update + * ctx->iscratch as appropriate. + * + * The contents of ctx->iscratch are completely different + * depending on whether diff == DIFF_HARD or not. This function + * uses iscratch completely differently between the two cases, and + * the code in solver_common() which consumes the result must + * likewise have an if statement with completely different + * branches for the two cases. + * + * In DIFF_EASY and DIFF_NORMAL modes, the valid entries in + * ctx->iscratch are 0,...,n-1, and each of those entries + * ctx->iscratch[i] gives a bitmap of the possible digits in the + * ith square of the clue box currently under consideration. So + * each entry of iscratch starts off as an empty bitmap, and we + * set bits in it as possible layouts for the clue box are + * considered (and the difference between DIFF_EASY and + * DIFF_NORMAL is just that in DIFF_EASY mode we deliberately set + * more bits than absolutely necessary, hence restricting our own + * knowledge). + * + * But in DIFF_HARD mode, the valid entries are 0,...,2*w-1 (at + * least outside *this* function - inside this function, we also + * use 2*w,...,4*w-1 as scratch space in the loop below); the + * first w of those give the possible digits in the intersection + * of the current clue box with each column of the puzzle, and the + * next w do the same for each row. In this mode, each iscratch + * entry starts off as a _full_ bitmap, and in this function we + * _clear_ bits for digits that are absent from a given row or + * column in each candidate layout, so that the only bits which + * remain set are those for digits which have to appear in a given + * row/column no matter how the clue box is laid out. + */ + if (diff == DIFF_EASY) { + unsigned mask = 0; + /* + * Easy-mode clue deductions: we do not record information + * about which squares take which values, so we amalgamate + * all the values in dscratch and OR them all into + * everywhere. + */ + for (j = 0; j < n; j++) + mask |= 1 << ctx->dscratch[j]; + for (j = 0; j < n; j++) + ctx->iscratch[j] |= mask; + } else if (diff == DIFF_NORMAL) { + /* + * Normal-mode deductions: we process the information in + * dscratch in the obvious way. + */ + for (j = 0; j < n; j++) + ctx->iscratch[j] |= 1 << ctx->dscratch[j]; + } else if (diff == DIFF_HARD) { + /* + * Hard-mode deductions: instead of ruling things out + * _inside_ the clue box, we look for numbers which occur in + * a given row or column in all candidate layouts, and rule + * them out of all squares in that row or column that + * _aren't_ part of this clue box. + */ + int *sq = ctx->boxlist + ctx->boxes[box]; + + for (j = 0; j < 2*w; j++) + ctx->iscratch[2*w+j] = 0; + for (j = 0; j < n; j++) { + int x = sq[j] / w, y = sq[j] % w; + ctx->iscratch[2*w+x] |= 1 << ctx->dscratch[j]; + ctx->iscratch[3*w+y] |= 1 << ctx->dscratch[j]; + } + for (j = 0; j < 2*w; j++) + ctx->iscratch[j] &= ctx->iscratch[2*w+j]; + } +} + +static int solver_common(struct latin_solver *solver, void *vctx, int diff) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int w = ctx->w; + int box, i, j, k; + int ret = 0, total; + + /* + * Iterate over each clue box and deduce what we can. + */ + for (box = 0; box < ctx->nboxes; box++) { + int *sq = ctx->boxlist + ctx->boxes[box]; + int n = ctx->boxes[box+1] - ctx->boxes[box]; + long value = ctx->clues[box] & ~CMASK; + long op = ctx->clues[box] & CMASK; + + /* + * Initialise ctx->iscratch for this clue box. At different + * difficulty levels we must initialise a different amount of + * it to different things; see the comments in + * solver_clue_candidate explaining what each version does. + */ + if (diff == DIFF_HARD) { + for (i = 0; i < 2*w; i++) + ctx->iscratch[i] = (1 << (w+1)) - (1 << 1); + } else { + for (i = 0; i < n; i++) + ctx->iscratch[i] = 0; + } + + switch (op) { + case C_SUB: + case C_DIV: + /* + * These two clue types must always apply to a box of + * area 2. Also, the two digits in these boxes can never + * be the same (because any domino must have its two + * squares in either the same row or the same column). + * So we simply iterate over all possibilities for the + * two squares (both ways round), rule out any which are + * inconsistent with the digit constraints we already + * have, and update the digit constraints with any new + * information thus garnered. + */ + assert(n == 2); + + for (i = 1; i <= w; i++) { + j = (op == C_SUB ? i + value : i * value); + if (j > w) break; + + /* (i,j) is a valid digit pair. Try it both ways round. */ + + if (solver->cube[sq[0]*w+i-1] && + solver->cube[sq[1]*w+j-1]) { + ctx->dscratch[0] = i; + ctx->dscratch[1] = j; + solver_clue_candidate(ctx, diff, box); + } + + if (solver->cube[sq[0]*w+j-1] && + solver->cube[sq[1]*w+i-1]) { + ctx->dscratch[0] = j; + ctx->dscratch[1] = i; + solver_clue_candidate(ctx, diff, box); + } + } + + break; + + case C_ADD: + case C_MUL: + /* + * For these clue types, I have no alternative but to go + * through all possible number combinations. + * + * Instead of a tedious physical recursion, I iterate in + * the scratch array through all possibilities. At any + * given moment, i indexes the element of the box that + * will next be incremented. + */ + i = 0; + ctx->dscratch[i] = 0; + total = value; /* start with the identity */ + while (1) { + if (i < n) { + /* + * Find the next valid value for cell i. + */ + for (j = ctx->dscratch[i] + 1; j <= w; j++) { + if (op == C_ADD ? (total < j) : (total % j != 0)) + continue; /* this one won't fit */ + if (!solver->cube[sq[i]*w+j-1]) + continue; /* this one is ruled out already */ + for (k = 0; k < i; k++) + if (ctx->dscratch[k] == j && + (sq[k] % w == sq[i] % w || + sq[k] / w == sq[i] / w)) + break; /* clashes with another row/col */ + if (k < i) + continue; + + /* Found one. */ + break; + } + + if (j > w) { + /* No valid values left; drop back. */ + i--; + if (i < 0) + break; /* overall iteration is finished */ + if (op == C_ADD) + total += ctx->dscratch[i]; + else + total *= ctx->dscratch[i]; + } else { + /* Got a valid value; store it and move on. */ + ctx->dscratch[i++] = j; + if (op == C_ADD) + total -= j; + else + total /= j; + ctx->dscratch[i] = 0; + } + } else { + if (total == (op == C_ADD ? 0 : 1)) + solver_clue_candidate(ctx, diff, box); + i--; + if (op == C_ADD) + total += ctx->dscratch[i]; + else + total *= ctx->dscratch[i]; + } + } + + break; + } + + /* + * Do deductions based on the information we've now + * accumulated in ctx->iscratch. See the comments above in + * solver_clue_candidate explaining what data is left in here, + * and how it differs between DIFF_HARD and lower difficulty + * levels (hence the big if statement here). + */ + if (diff < DIFF_HARD) { +#ifdef STANDALONE_SOLVER + char prefix[256]; + + if (solver_show_working) + sprintf(prefix, "%*susing clue at (%d,%d):\n", + solver_recurse_depth*4, "", + sq[0]/w+1, sq[0]%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ +#endif + + for (i = 0; i < n; i++) + for (j = 1; j <= w; j++) { + if (solver->cube[sq[i]*w+j-1] && + !(ctx->iscratch[i] & (1 << j))) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%s%*s ruling out %d at (%d,%d)\n", + prefix, solver_recurse_depth*4, "", + j, sq[i]/w+1, sq[i]%w+1); + prefix[0] = '\0'; + } +#endif + solver->cube[sq[i]*w+j-1] = 0; + ret = 1; + } + } + } else { +#ifdef STANDALONE_SOLVER + char prefix[256]; + + if (solver_show_working) + sprintf(prefix, "%*susing clue at (%d,%d):\n", + solver_recurse_depth*4, "", + sq[0]/w+1, sq[0]%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ +#endif + + for (i = 0; i < 2*w; i++) { + int start = (i < w ? i*w : i-w); + int step = (i < w ? 1 : w); + for (j = 1; j <= w; j++) if (ctx->iscratch[i] & (1 << j)) { +#ifdef STANDALONE_SOLVER + char prefix2[256]; + + if (solver_show_working) + sprintf(prefix2, "%*s this clue requires %d in" + " %s %d:\n", solver_recurse_depth*4, "", + j, i < w ? "column" : "row", i%w+1); + else + prefix2[0] = '\0'; /* placate optimiser */ +#endif + + for (k = 0; k < w; k++) { + int pos = start + k*step; + if (ctx->whichbox[pos] != box && + solver->cube[pos*w+j-1]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%s%s%*s ruling out %d at (%d,%d)\n", + prefix, prefix2, + solver_recurse_depth*4, "", + j, pos/w+1, pos%w+1); + prefix[0] = prefix2[0] = '\0'; + } +#endif + solver->cube[pos*w+j-1] = 0; + ret = 1; + } + } + } + } + + /* + * Once we find one block we can do something with in + * this way, revert to trying easier deductions, so as + * not to generate solver diagnostics that make the + * problem look harder than it is. (We have to do this + * for the Hard deductions but not the Easy/Normal ones, + * because only the Hard deductions are cross-box.) + */ + if (ret) + return ret; + } + } + + return ret; +} + +static int solver_easy(struct latin_solver *solver, void *vctx) +{ + /* + * Omit the EASY deductions when solving at NORMAL level, since + * the NORMAL deductions are a superset of them anyway and it + * saves on time and confusing solver diagnostics. + * + * Note that this breaks the natural semantics of the return + * value of latin_solver. Without this hack, you could determine + * a puzzle's difficulty in one go by trying to solve it at + * maximum difficulty and seeing what difficulty value was + * returned; but with this hack, solving an Easy puzzle on + * Normal difficulty will typically return Normal. Hence the + * uses of the solver to determine difficulty are all arranged + * so as to double-check by re-solving at the next difficulty + * level down and making sure it failed. + */ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + if (ctx->diff > DIFF_EASY) + return 0; + return solver_common(solver, vctx, DIFF_EASY); +} + +static int solver_normal(struct latin_solver *solver, void *vctx) +{ + return solver_common(solver, vctx, DIFF_NORMAL); +} + +static int solver_hard(struct latin_solver *solver, void *vctx) +{ + return solver_common(solver, vctx, DIFF_HARD); +} + +#define SOLVER(upper,title,func,lower) func, +static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) }; + +static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff) +{ + int a = w*w; + struct solver_ctx ctx; + int ret; + int i, j, n, m; + + ctx.w = w; + ctx.soln = soln; + ctx.diff = maxdiff; + + /* + * Transform the dsf-formatted clue list into one over which we + * can iterate more easily. + * + * Also transpose the x- and y-coordinates at this point, + * because the 'cube' array in the general Latin square solver + * puts x first (oops). + */ + for (ctx.nboxes = i = 0; i < a; i++) + if (dsf_canonify(dsf, i) == i) + ctx.nboxes++; + ctx.boxlist = snewn(a, int); + ctx.boxes = snewn(ctx.nboxes+1, int); + ctx.clues = snewn(ctx.nboxes, long); + ctx.whichbox = snewn(a, int); + for (n = m = i = 0; i < a; i++) + if (dsf_canonify(dsf, i) == i) { + ctx.clues[n] = clues[i]; + ctx.boxes[n] = m; + for (j = 0; j < a; j++) + if (dsf_canonify(dsf, j) == i) { + ctx.boxlist[m++] = (j % w) * w + (j / w); /* transpose */ + ctx.whichbox[ctx.boxlist[m-1]] = n; + } + n++; + } + assert(n == ctx.nboxes); + assert(m == a); + ctx.boxes[n] = m; + + ctx.dscratch = snewn(a+1, digit); + ctx.iscratch = snewn(max(a+1, 4*w), int); + + ret = latin_solver(soln, w, maxdiff, + DIFF_EASY, DIFF_HARD, DIFF_EXTREME, + DIFF_EXTREME, DIFF_UNREASONABLE, + keen_solvers, &ctx, NULL, NULL); + + sfree(ctx.dscratch); + sfree(ctx.iscratch); + sfree(ctx.whichbox); + sfree(ctx.boxlist); + sfree(ctx.boxes); + sfree(ctx.clues); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Grid generation. + */ + +static char *encode_block_structure(char *p, int w, int *dsf) +{ + int i, currrun = 0; + char *orig, *q, *r, c; + + orig = p; + + /* + * Encode the block structure. We do this by encoding the + * pattern of dividing lines: first we iterate over the w*(w-1) + * internal vertical grid lines in ordinary reading order, then + * over the w*(w-1) internal horizontal ones in transposed + * reading order. + * + * We encode the number of non-lines between the lines; _ means + * zero (two adjacent divisions), a means 1, ..., y means 25, + * and z means 25 non-lines _and no following line_ (so that za + * means 26, zb 27 etc). + */ + for (i = 0; i <= 2*w*(w-1); i++) { + int x, y, p0, p1, edge; + + if (i == 2*w*(w-1)) { + edge = TRUE; /* terminating virtual edge */ + } else { + if (i < w*(w-1)) { + y = i/(w-1); + x = i%(w-1); + p0 = y*w+x; + p1 = y*w+x+1; + } else { + x = i/(w-1) - w; + y = i%(w-1); + p0 = y*w+x; + p1 = (y+1)*w+x; + } + edge = (dsf_canonify(dsf, p0) != dsf_canonify(dsf, p1)); + } + + if (edge) { + while (currrun > 25) + *p++ = 'z', currrun -= 25; + if (currrun) + *p++ = 'a'-1 + currrun; + else + *p++ = '_'; + currrun = 0; + } else + currrun++; + } + + /* + * Now go through and compress the string by replacing runs of + * the same letter with a single copy of that letter followed by + * a repeat count, where that makes it shorter. (This puzzle + * seems to generate enough long strings of _ to make this a + * worthwhile step.) + */ + for (q = r = orig; r < p ;) { + *q++ = c = *r; + + for (i = 0; r+i < p && r[i] == c; i++); + r += i; + + if (i == 2) { + *q++ = c; + } else if (i > 2) { + q += sprintf(q, "%d", i); + } + } + + return q; +} + +static char *parse_block_structure(const char **p, int w, int *dsf) +{ + int a = w*w; + int pos = 0; + int repc = 0, repn = 0; + + dsf_init(dsf, a); + + while (**p && (repn > 0 || **p != ',')) { + int c, adv; + + if (repn > 0) { + repn--; + c = repc; + } else if (**p == '_' || (**p >= 'a' && **p <= 'z')) { + c = (**p == '_' ? 0 : **p - 'a' + 1); + (*p)++; + if (**p && isdigit((unsigned char)**p)) { + repc = c; + repn = atoi(*p)-1; + while (**p && isdigit((unsigned char)**p)) (*p)++; + } + } else + return "Invalid character in game description"; + + adv = (c != 25); /* 'z' is a special case */ + + while (c-- > 0) { + int p0, p1; + + /* + * Non-edge; merge the two dsf classes on either + * side of it. + */ + if (pos >= 2*w*(w-1)) + return "Too much data in block structure specification"; + if (pos < w*(w-1)) { + int y = pos/(w-1); + int x = pos%(w-1); + p0 = y*w+x; + p1 = y*w+x+1; + } else { + int x = pos/(w-1) - w; + int y = pos%(w-1); + p0 = y*w+x; + p1 = (y+1)*w+x; + } + dsf_merge(dsf, p0, p1); + + pos++; + } + if (adv) { + pos++; + if (pos > 2*w*(w-1)+1) + return "Too much data in block structure specification"; + } + } + + /* + * When desc is exhausted, we expect to have gone exactly + * one space _past_ the end of the grid, due to the dummy + * edge at the end. + */ + if (pos != 2*w*(w-1)+1) + return "Not enough data in block structure specification"; + + return NULL; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, a = w*w; + digit *grid, *soln; + int *order, *revorder, *singletons, *dsf; + long *clues, *cluevals; + int i, j, k, n, x, y, ret; + int diff = params->diff; + char *desc, *p; + + /* + * Difficulty exceptions: 3x3 puzzles at difficulty Hard or + * higher are currently not generable - the generator will spin + * forever looking for puzzles of the appropriate difficulty. We + * dial each of these down to the next lower difficulty. + * + * Remember to re-test this whenever a change is made to the + * solver logic! + * + * I tested it using the following shell command: + +for d in e n h x u; do + for i in {3..9}; do + echo ./keen --generate 1 ${i}d${d} + perl -e 'alarm 30; exec @ARGV' ./keen --generate 5 ${i}d${d} >/dev/null \ + || echo broken + done +done + + * Of course, it's better to do that after taking the exceptions + * _out_, so as to detect exceptions that should be removed as + * well as those which should be added. + */ + if (w == 3 && diff > DIFF_NORMAL) + diff = DIFF_NORMAL; + + grid = NULL; + + order = snewn(a, int); + revorder = snewn(a, int); + singletons = snewn(a, int); + dsf = snew_dsf(a); + clues = snewn(a, long); + cluevals = snewn(a, long); + soln = snewn(a, digit); + + while (1) { + /* + * First construct a latin square to be the solution. + */ + sfree(grid); + grid = latin_generate(w, rs); + + /* + * Divide the grid into arbitrarily sized blocks, but so as + * to arrange plenty of dominoes which can be SUB/DIV clues. + * We do this by first placing dominoes at random for a + * while, then tying the remaining singletons one by one + * into neighbouring blocks. + */ + for (i = 0; i < a; i++) + order[i] = i; + shuffle(order, a, sizeof(*order), rs); + for (i = 0; i < a; i++) + revorder[order[i]] = i; + + for (i = 0; i < a; i++) + singletons[i] = TRUE; + + dsf_init(dsf, a); + + /* Place dominoes. */ + for (i = 0; i < a; i++) { + if (singletons[i]) { + int best = -1; + + x = i % w; + y = i / w; + + if (x > 0 && singletons[i-1] && + (best == -1 || revorder[i-1] < revorder[best])) + best = i-1; + if (x+1 < w && singletons[i+1] && + (best == -1 || revorder[i+1] < revorder[best])) + best = i+1; + if (y > 0 && singletons[i-w] && + (best == -1 || revorder[i-w] < revorder[best])) + best = i-w; + if (y+1 < w && singletons[i+w] && + (best == -1 || revorder[i+w] < revorder[best])) + best = i+w; + + /* + * When we find a potential domino, we place it with + * probability 3/4, which seems to strike a decent + * balance between plenty of dominoes and leaving + * enough singletons to make interesting larger + * shapes. + */ + if (best >= 0 && random_upto(rs, 4)) { + singletons[i] = singletons[best] = FALSE; + dsf_merge(dsf, i, best); + } + } + } + + /* Fold in singletons. */ + for (i = 0; i < a; i++) { + if (singletons[i]) { + int best = -1; + + x = i % w; + y = i / w; + + if (x > 0 && dsf_size(dsf, i-1) < MAXBLK && + (best == -1 || revorder[i-1] < revorder[best])) + best = i-1; + if (x+1 < w && dsf_size(dsf, i+1) < MAXBLK && + (best == -1 || revorder[i+1] < revorder[best])) + best = i+1; + if (y > 0 && dsf_size(dsf, i-w) < MAXBLK && + (best == -1 || revorder[i-w] < revorder[best])) + best = i-w; + if (y+1 < w && dsf_size(dsf, i+w) < MAXBLK && + (best == -1 || revorder[i+w] < revorder[best])) + best = i+w; + + if (best >= 0) { + singletons[i] = singletons[best] = FALSE; + dsf_merge(dsf, i, best); + } + } + } + + /* Quit and start again if we have any singletons left over + * which we weren't able to do anything at all with. */ + for (i = 0; i < a; i++) + if (singletons[i]) + break; + if (i < a) + continue; + + /* + * Decide what would be acceptable clues for each block. + * + * Blocks larger than 2 have free choice of ADD or MUL; + * blocks of size 2 can be anything in principle (except + * that they can only be DIV if the two numbers have an + * integer quotient, of course), but we rule out (or try to + * avoid) some clues because they're of low quality. + * + * Hence, we iterate once over the grid, stopping at the + * canonical element of every >2 block and the _non_- + * canonical element of every 2-block; the latter means that + * we can make our decision about a 2-block in the knowledge + * of both numbers in it. + * + * We reuse the 'singletons' array (finished with in the + * above loop) to hold information about which blocks are + * suitable for what. + */ +#define F_ADD 0x01 +#define F_SUB 0x02 +#define F_MUL 0x04 +#define F_DIV 0x08 +#define BAD_SHIFT 4 + + for (i = 0; i < a; i++) { + singletons[i] = 0; + j = dsf_canonify(dsf, i); + k = dsf_size(dsf, j); + if (params->multiplication_only) + singletons[j] = F_MUL; + else if (j == i && k > 2) { + singletons[j] |= F_ADD | F_MUL; + } else if (j != i && k == 2) { + /* Fetch the two numbers and sort them into order. */ + int p = grid[j], q = grid[i], v; + if (p < q) { + int t = p; p = q; q = t; + } + + /* + * Addition clues are always allowed, but we try to + * avoid sums of 3, 4, (2w-1) and (2w-2) if we can, + * because they're too easy - they only leave one + * option for the pair of numbers involved. + */ + v = p + q; + if (v > 4 && v < 2*w-2) + singletons[j] |= F_ADD; + else + singletons[j] |= F_ADD << BAD_SHIFT; + + /* + * Multiplication clues: above Normal difficulty, we + * prefer (but don't absolutely insist on) clues of + * this type which leave multiple options open. + */ + v = p * q; + n = 0; + for (k = 1; k <= w; k++) + if (v % k == 0 && v / k <= w && v / k != k) + n++; + if (n <= 2 && diff > DIFF_NORMAL) + singletons[j] |= F_MUL << BAD_SHIFT; + else + singletons[j] |= F_MUL; + + /* + * Subtraction: we completely avoid a difference of + * w-1. + */ + v = p - q; + if (v < w-1) + singletons[j] |= F_SUB; + + /* + * Division: for a start, the quotient must be an + * integer or the clue type is impossible. Also, we + * never use quotients strictly greater than w/2, + * because they're not only too easy but also + * inelegant. + */ + if (p % q == 0 && 2 * (p / q) <= w) + singletons[j] |= F_DIV; + } + } + + /* + * Actually choose a clue for each block, trying to keep the + * numbers of each type even, and starting with the + * preferred candidates for each type where possible. + * + * I'm sure there should be a faster algorithm for doing + * this, but I can't be bothered: O(N^2) is good enough when + * N is at most the number of dominoes that fits into a 9x9 + * square. + */ + shuffle(order, a, sizeof(*order), rs); + for (i = 0; i < a; i++) + clues[i] = 0; + while (1) { + int done_something = FALSE; + + for (k = 0; k < 4; k++) { + long clue; + int good, bad; + switch (k) { + case 0: clue = C_DIV; good = F_DIV; break; + case 1: clue = C_SUB; good = F_SUB; break; + case 2: clue = C_MUL; good = F_MUL; break; + default /* case 3 */ : clue = C_ADD; good = F_ADD; break; + } + + for (i = 0; i < a; i++) { + j = order[i]; + if (singletons[j] & good) { + clues[j] = clue; + singletons[j] = 0; + break; + } + } + if (i == a) { + /* didn't find a nice one, use a nasty one */ + bad = good << BAD_SHIFT; + for (i = 0; i < a; i++) { + j = order[i]; + if (singletons[j] & bad) { + clues[j] = clue; + singletons[j] = 0; + break; + } + } + } + if (i < a) + done_something = TRUE; + } + + if (!done_something) + break; + } +#undef F_ADD +#undef F_SUB +#undef F_MUL +#undef F_DIV +#undef BAD_SHIFT + + /* + * Having chosen the clue types, calculate the clue values. + */ + for (i = 0; i < a; i++) { + j = dsf_canonify(dsf, i); + if (j == i) { + cluevals[j] = grid[i]; + } else { + switch (clues[j]) { + case C_ADD: + cluevals[j] += grid[i]; + break; + case C_MUL: + cluevals[j] *= grid[i]; + break; + case C_SUB: + cluevals[j] = abs(cluevals[j] - grid[i]); + break; + case C_DIV: + { + int d1 = cluevals[j], d2 = grid[i]; + if (d1 == 0 || d2 == 0) + cluevals[j] = 0; + else + cluevals[j] = d2/d1 + d1/d2;/* one is 0 :-) */ + } + break; + } + } + } + + for (i = 0; i < a; i++) { + j = dsf_canonify(dsf, i); + if (j == i) { + clues[j] |= cluevals[j]; + } + } + + /* + * See if the game can be solved at the specified difficulty + * level, but not at the one below. + */ + if (diff > 0) { + memset(soln, 0, a); + ret = solver(w, dsf, clues, soln, diff-1); + if (ret <= diff-1) + continue; + } + memset(soln, 0, a); + ret = solver(w, dsf, clues, soln, diff); + if (ret != diff) + continue; /* go round again */ + + /* + * I wondered if at this point it would be worth trying to + * merge adjacent blocks together, to make the puzzle + * gradually more difficult if it's currently easier than + * specced, increasing the chance of a given generation run + * being successful. + * + * It doesn't seem to be critical for the generation speed, + * though, so for the moment I'm leaving it out. + */ + + /* + * We've got a usable puzzle! + */ + break; + } + + /* + * Encode the puzzle description. + */ + desc = snewn(40*a, char); + p = desc; + p = encode_block_structure(p, w, dsf); + *p++ = ','; + for (i = 0; i < a; i++) { + j = dsf_canonify(dsf, i); + if (j == i) { + switch (clues[j] & CMASK) { + case C_ADD: *p++ = 'a'; break; + case C_SUB: *p++ = 's'; break; + case C_MUL: *p++ = 'm'; break; + case C_DIV: *p++ = 'd'; break; + } + p += sprintf(p, "%ld", clues[j] & ~CMASK); + } + } + *p++ = '\0'; + desc = sresize(desc, p - desc, char); + + /* + * Encode the solution. + */ + assert(memcmp(soln, grid, a) == 0); + *aux = snewn(a+2, char); + (*aux)[0] = 'S'; + for (i = 0; i < a; i++) + (*aux)[i+1] = '0' + soln[i]; + (*aux)[a+1] = '\0'; + + sfree(grid); + sfree(order); + sfree(revorder); + sfree(singletons); + sfree(dsf); + sfree(clues); + sfree(cluevals); + sfree(soln); + + return desc; +} + +/* ---------------------------------------------------------------------- + * Gameplay. + */ + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, a = w*w; + int *dsf; + char *ret; + const char *p = desc; + int i; + + /* + * Verify that the block structure makes sense. + */ + dsf = snew_dsf(a); + ret = parse_block_structure(&p, w, dsf); + if (ret) { + sfree(dsf); + return ret; + } + + if (*p != ',') + return "Expected ',' after block structure description"; + p++; + + /* + * Verify that the right number of clues are given, and that SUB + * and DIV clues don't apply to blocks of the wrong size. + */ + for (i = 0; i < a; i++) { + if (dsf_canonify(dsf, i) == i) { + if (*p == 'a' || *p == 'm') { + /* these clues need no validation */ + } else if (*p == 'd' || *p == 's') { + if (dsf_size(dsf, i) != 2) + return "Subtraction and division blocks must have area 2"; + } else if (!*p) { + return "Too few clues for block structure"; + } else { + return "Unrecognised clue type"; + } + p++; + while (*p && isdigit((unsigned char)*p)) p++; + } + } + if (*p) + return "Too many clues for block structure"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, a = w*w; + game_state *state = snew(game_state); + const char *p = desc; + int i; + + state->par = *params; /* structure copy */ + state->clues = snew(struct clues); + state->clues->refcount = 1; + state->clues->w = w; + state->clues->dsf = snew_dsf(a); + parse_block_structure(&p, w, state->clues->dsf); + + assert(*p == ','); + p++; + + state->clues->clues = snewn(a, long); + for (i = 0; i < a; i++) { + if (dsf_canonify(state->clues->dsf, i) == i) { + long clue = 0; + switch (*p) { + case 'a': + clue = C_ADD; + break; + case 'm': + clue = C_MUL; + break; + case 's': + clue = C_SUB; + assert(dsf_size(state->clues->dsf, i) == 2); + break; + case 'd': + clue = C_DIV; + assert(dsf_size(state->clues->dsf, i) == 2); + break; + default: + assert(!"Bad description in new_game"); + } + p++; + clue |= atol(p); + while (*p && isdigit((unsigned char)*p)) p++; + state->clues->clues[i] = clue; + } else + state->clues->clues[i] = 0; + } + + state->grid = snewn(a, digit); + state->pencil = snewn(a, int); + for (i = 0; i < a; i++) { + state->grid[i] = 0; + state->pencil[i] = 0; + } + + state->completed = state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->par.w, a = w*w; + game_state *ret = snew(game_state); + + ret->par = state->par; /* structure copy */ + + ret->clues = state->clues; + ret->clues->refcount++; + + ret->grid = snewn(a, digit); + ret->pencil = snewn(a, int); + memcpy(ret->grid, state->grid, a*sizeof(digit)); + memcpy(ret->pencil, state->pencil, a*sizeof(int)); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->pencil); + if (--state->clues->refcount <= 0) { + sfree(state->clues->dsf); + sfree(state->clues->clues); + sfree(state->clues); + } + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->par.w, a = w*w; + int i, ret; + digit *soln; + char *out; + + if (aux) + return dupstr(aux); + + soln = snewn(a, digit); + memset(soln, 0, a); + + ret = solver(w, state->clues->dsf, state->clues->clues, + soln, DIFFCOUNT-1); + + if (ret == diff_impossible) { + *error = "No solution exists for this puzzle"; + out = NULL; + } else if (ret == diff_ambiguous) { + *error = "Multiple solutions exist for this puzzle"; + out = NULL; + } else { + out = snewn(a+2, char); + out[0] = 'S'; + for (i = 0; i < a; i++) + out[i+1] = '0' + soln[i]; + out[a+1] = '\0'; + } + + sfree(soln); + return out; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +struct game_ui { + /* + * These are the coordinates of the currently highlighted + * square on the grid, if hshow = 1. + */ + int hx, hy; + /* + * This indicates whether the current highlight is a + * pencil-mark one or a real one. + */ + int hpencil; + /* + * This indicates whether or not we're showing the highlight + * (used to be hx = hy = -1); important so that when we're + * using the cursor keys it doesn't keep coming back at a + * fixed position. When hshow = 1, pressing a valid number + * or letter key or Space will enter that number or letter in the grid. + */ + int hshow; + /* + * This indicates whether we're using the highlight as a cursor; + * it means that it doesn't vanish on a keypress, and that it is + * allowed on immutable squares. + */ + int hcursor; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + int w = newstate->par.w; + /* + * We prevent pencil-mode highlighting of a filled square, unless + * we're using the cursor keys. So if the user has just filled in + * a square which we had a pencil-mode highlight in (by Undo, or + * by Redo, or by Solve), then we cancel the highlight. + */ + if (ui->hshow && ui->hpencil && !ui->hcursor && + newstate->grid[ui->hy * w + ui->hx] != 0) { + ui->hshow = 0; + } +} + +#define PREFERRED_TILESIZE 48 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE / 2) +#define GRIDEXTRA max((TILESIZE / 32),1) +#define COORD(x) ((x)*TILESIZE + BORDER) +#define FROMCOORD(x) (((x)+(TILESIZE-BORDER)) / TILESIZE - 1) + +#define FLASH_TIME 0.4F + +#define DF_PENCIL_SHIFT 16 +#define DF_ERR_LATIN 0x8000 +#define DF_ERR_CLUE 0x4000 +#define DF_HIGHLIGHT 0x2000 +#define DF_HIGHLIGHT_PENCIL 0x1000 +#define DF_DIGIT_MASK 0x000F + +struct game_drawstate { + int tilesize; + int started; + long *tiles; + long *errors; + char *minus_sign, *times_sign, *divide_sign; +}; + +static int check_errors(const game_state *state, long *errors) +{ + int w = state->par.w, a = w*w; + int i, j, x, y, errs = FALSE; + long *cluevals; + int *full; + + cluevals = snewn(a, long); + full = snewn(a, int); + + if (errors) + for (i = 0; i < a; i++) { + errors[i] = 0; + full[i] = TRUE; + } + + for (i = 0; i < a; i++) { + long clue; + + j = dsf_canonify(state->clues->dsf, i); + if (j == i) { + cluevals[i] = state->grid[i]; + } else { + clue = state->clues->clues[j] & CMASK; + + switch (clue) { + case C_ADD: + cluevals[j] += state->grid[i]; + break; + case C_MUL: + cluevals[j] *= state->grid[i]; + break; + case C_SUB: + cluevals[j] = abs(cluevals[j] - state->grid[i]); + break; + case C_DIV: + { + int d1 = min(cluevals[j], state->grid[i]); + int d2 = max(cluevals[j], state->grid[i]); + if (d1 == 0 || d2 % d1 != 0) + cluevals[j] = 0; + else + cluevals[j] = d2 / d1; + } + break; + } + } + + if (!state->grid[i]) + full[j] = FALSE; + } + + for (i = 0; i < a; i++) { + j = dsf_canonify(state->clues->dsf, i); + if (j == i) { + if ((state->clues->clues[j] & ~CMASK) != cluevals[i]) { + errs = TRUE; + if (errors && full[j]) + errors[j] |= DF_ERR_CLUE; + } + } + } + + sfree(cluevals); + sfree(full); + + for (y = 0; y < w; y++) { + int mask = 0, errmask = 0; + for (x = 0; x < w; x++) { + int bit = 1 << state->grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1 << (w+1)) - (1 << 1)) { + errs = TRUE; + errmask &= ~1; + if (errors) { + for (x = 0; x < w; x++) + if (errmask & (1 << state->grid[y*w+x])) + errors[y*w+x] |= DF_ERR_LATIN; + } + } + } + + for (x = 0; x < w; x++) { + int mask = 0, errmask = 0; + for (y = 0; y < w; y++) { + int bit = 1 << state->grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1 << (w+1)) - (1 << 1)) { + errs = TRUE; + errmask &= ~1; + if (errors) { + for (y = 0; y < w; y++) + if (errmask & (1 << state->grid[y*w+x])) + errors[y*w+x] |= DF_ERR_LATIN; + } + } + } + + return errs; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->par.w; + int tx, ty; + char buf[80]; + + button &= ~MOD_MASK; + + tx = FROMCOORD(x); + ty = FROMCOORD(y); + + if (tx >= 0 && tx < w && ty >= 0 && ty < w) { + if (button == LEFT_BUTTON) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil == 0) { + ui->hshow = 0; + } else { + ui->hx = tx; + ui->hy = ty; + ui->hshow = 1; + ui->hpencil = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + if (button == RIGHT_BUTTON) { + /* + * Pencil-mode highlighting for non filled squares. + */ + if (state->grid[ty*w+tx] == 0) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil) { + ui->hshow = 0; + } else { + ui->hpencil = 1; + ui->hx = tx; + ui->hy = ty; + ui->hshow = 1; + } + } else { + ui->hshow = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + } + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->hx, &ui->hy, w, w, 0); + ui->hshow = ui->hcursor = 1; + return ""; + } + if (ui->hshow && + (button == CURSOR_SELECT)) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + if (ui->hshow && + ((button >= '0' && button <= '9' && button - '0' <= w) || + button == CURSOR_SELECT2 || button == '\b')) { + int n = button - '0'; + if (button == CURSOR_SELECT2 || button == '\b') + n = 0; + + /* + * Can't make pencil marks in a filled square. This can only + * become highlighted if we're using cursor keys. + */ + if (ui->hpencil && state->grid[ui->hy*w+ui->hx]) + return NULL; + + sprintf(buf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); + + if (!ui->hcursor) ui->hshow = 0; + + return dupstr(buf); + } + + if (button == 'M' || button == 'm') + return dupstr("M"); + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int w = from->par.w, a = w*w; + game_state *ret; + int x, y, i, n; + + if (move[0] == 'S') { + ret = dup_game(from); + ret->completed = ret->cheated = TRUE; + + for (i = 0; i < a; i++) { + if (move[i+1] < '1' || move[i+1] > '0'+w) { + free_game(ret); + return NULL; + } + ret->grid[i] = move[i+1] - '0'; + ret->pencil[i] = 0; + } + + if (move[a+1] != '\0') { + free_game(ret); + return NULL; + } + + return ret; + } else if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) { + + ret = dup_game(from); + if (move[0] == 'P' && n > 0) { + ret->pencil[y*w+x] ^= 1 << n; + } else { + ret->grid[y*w+x] = n; + ret->pencil[y*w+x] = 0; + + if (!ret->completed && !check_errors(ret, NULL)) + ret->completed = TRUE; + } + return ret; + } else if (move[0] == 'M') { + /* + * Fill in absolutely all pencil marks everywhere. (I + * wouldn't use this for actual play, but it's a handy + * starting point when following through a set of + * diagnostics output by the standalone solver.) + */ + ret = dup_game(from); + for (i = 0; i < a; i++) { + if (!ret->grid[i]) + ret->pencil[i] = (1 << (w+1)) - (1 << 1); + } + return ret; + } else + return NULL; /* couldn't parse move string */ +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define SIZE(w) ((w) * TILESIZE + 2*BORDER) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = *y = SIZE(params->w); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_USER * 3 + 0] = 0.0F; + ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_USER * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + *ncolours = NCOLOURS; + return ret; +} + +static const char *const minus_signs[] = { "\xE2\x88\x92", "-" }; +static const char *const times_signs[] = { "\xC3\x97", "*" }; +static const char *const divide_signs[] = { "\xC3\xB7", "/" }; + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->par.w, a = w*w; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->started = FALSE; + ds->tiles = snewn(a, long); + for (i = 0; i < a; i++) + ds->tiles[i] = -1; + ds->errors = snewn(a, long); + ds->minus_sign = text_fallback(dr, minus_signs, lenof(minus_signs)); + ds->times_sign = text_fallback(dr, times_signs, lenof(times_signs)); + ds->divide_sign = text_fallback(dr, divide_signs, lenof(divide_signs)); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->tiles); + sfree(ds->errors); + sfree(ds->minus_sign); + sfree(ds->times_sign); + sfree(ds->divide_sign); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, + int x, int y, long tile, int only_one_op) +{ + int w = clues->w /* , a = w*w */; + int tx, ty, tw, th; + int cx, cy, cw, ch; + char str[64]; + + tx = BORDER + x * TILESIZE + 1 + GRIDEXTRA; + ty = BORDER + y * TILESIZE + 1 + GRIDEXTRA; + + cx = tx; + cy = ty; + cw = tw = TILESIZE-1-2*GRIDEXTRA; + ch = th = TILESIZE-1-2*GRIDEXTRA; + + if (x > 0 && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, y*w+x-1)) + cx -= GRIDEXTRA, cw += GRIDEXTRA; + if (x+1 < w && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, y*w+x+1)) + cw += GRIDEXTRA; + if (y > 0 && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, (y-1)*w+x)) + cy -= GRIDEXTRA, ch += GRIDEXTRA; + if (y+1 < w && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, (y+1)*w+x)) + ch += GRIDEXTRA; + + clip(dr, cx, cy, cw, ch); + + /* background needs erasing */ + draw_rect(dr, cx, cy, cw, ch, + (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND); + + /* pencil-mode highlight */ + if (tile & DF_HIGHLIGHT_PENCIL) { + int coords[6]; + coords[0] = cx; + coords[1] = cy; + coords[2] = cx+cw/2; + coords[3] = cy; + coords[4] = cx; + coords[5] = cy+ch/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + /* + * Draw the corners of thick lines in corner-adjacent squares, + * which jut into this square by one pixel. + */ + if (x > 0 && y > 0 && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y-1)*w+x-1)) + draw_rect(dr, tx-GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x+1 < w && y > 0 && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y-1)*w+x+1)) + draw_rect(dr, tx+TILESIZE-1-2*GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x > 0 && y+1 < w && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y+1)*w+x-1)) + draw_rect(dr, tx-GRIDEXTRA, ty+TILESIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x+1 < w && y+1 < w && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y+1)*w+x+1)) + draw_rect(dr, tx+TILESIZE-1-2*GRIDEXTRA, ty+TILESIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + + /* Draw the box clue. */ + if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) { + long clue = clues->clues[y*w+x]; + long cluetype = clue & CMASK, clueval = clue & ~CMASK; + int size = dsf_size(clues->dsf, y*w+x); + /* + * Special case of clue-drawing: a box with only one square + * is written as just the number, with no operation, because + * it doesn't matter whether the operation is ADD or MUL. + * The generation code above should never produce puzzles + * containing such a thing - I think they're inelegant - but + * it's possible to type in game IDs from elsewhere, so I + * want to display them right if so. + */ + sprintf (str, "%ld%s", clueval, + (size == 1 || only_one_op ? "" : + cluetype == C_ADD ? "+" : + cluetype == C_SUB ? ds->minus_sign : + cluetype == C_MUL ? ds->times_sign : + /* cluetype == C_DIV ? */ ds->divide_sign)); + draw_text(dr, tx + GRIDEXTRA * 2, ty + GRIDEXTRA * 2 + TILESIZE/4, + FONT_VARIABLE, TILESIZE/4, ALIGN_VNORMAL | ALIGN_HLEFT, + (tile & DF_ERR_CLUE ? COL_ERROR : COL_GRID), str); + } + + /* new number needs drawing? */ + if (tile & DF_DIGIT_MASK) { + str[1] = '\0'; + str[0] = (tile & DF_DIGIT_MASK) + '0'; + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, + (tile & DF_ERR_LATIN) ? COL_ERROR : COL_USER, str); + } else { + int i, j, npencil; + int pl, pr, pt, pb; + float bestsize; + int pw, ph, minph, pbest, fontsize; + + /* Count the pencil marks required. */ + for (i = 1, npencil = 0; i <= w; i++) + if (tile & (1L << (i + DF_PENCIL_SHIFT))) + npencil++; + if (npencil) { + + minph = 2; + + /* + * Determine the bounding rectangle within which we're going + * to put the pencil marks. + */ + /* Start with the whole square */ + pl = tx + GRIDEXTRA; + pr = pl + TILESIZE - GRIDEXTRA; + pt = ty + GRIDEXTRA; + pb = pt + TILESIZE - GRIDEXTRA; + if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) { + /* + * Make space for the clue text. + */ + pt += TILESIZE/4; + /* minph--; */ + } + + /* + * We arrange our pencil marks in a grid layout, with + * the number of rows and columns adjusted to allow the + * maximum font size. + * + * So now we work out what the grid size ought to be. + */ + bestsize = 0.0; + pbest = 0; + /* Minimum */ + for (pw = 3; pw < max(npencil,4); pw++) { + float fw, fh, fs; + + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + fw = (pr - pl) / (float)pw; + fh = (pb - pt) / (float)ph; + fs = min(fw, fh); + if (fs > bestsize) { + bestsize = fs; + pbest = pw; + } + } + assert(pbest > 0); + pw = pbest; + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + + /* + * Now we've got our grid dimensions, work out the pixel + * size of a grid element, and round it to the nearest + * pixel. (We don't want rounding errors to make the + * grid look uneven at low pixel sizes.) + */ + fontsize = min((pr - pl) / pw, (pb - pt) / ph); + + /* + * Centre the resulting figure in the square. + */ + pl = tx + (TILESIZE - fontsize * pw) / 2; + pt = ty + (TILESIZE - fontsize * ph) / 2; + + /* + * And move it down a bit if it's collided with some + * clue text. + */ + if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) { + pt = max(pt, ty + GRIDEXTRA * 3 + TILESIZE/4); + } + + /* + * Now actually draw the pencil marks. + */ + for (i = 1, j = 0; i <= w; i++) + if (tile & (1L << (i + DF_PENCIL_SHIFT))) { + int dx = j % pw, dy = j / pw; + + str[1] = '\0'; + str[0] = i + '0'; + draw_text(dr, pl + fontsize * (2*dx+1) / 2, + pt + fontsize * (2*dy+1) / 2, + FONT_VARIABLE, fontsize, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } + } + } + + unclip(dr); + + draw_update(dr, cx, cy, cw, ch); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->par.w /*, a = w*w */; + int x, y; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all + * games should start by drawing a big background-colour + * rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND); + + /* + * Big containing rectangle. + */ + draw_rect(dr, COORD(0) - GRIDEXTRA, COORD(0) - GRIDEXTRA, + w*TILESIZE+1+GRIDEXTRA*2, w*TILESIZE+1+GRIDEXTRA*2, + COL_GRID); + + draw_update(dr, 0, 0, SIZE(w), SIZE(w)); + + ds->started = TRUE; + } + + check_errors(state, ds->errors); + + for (y = 0; y < w; y++) { + for (x = 0; x < w; x++) { + long tile = 0L; + + if (state->grid[y*w+x]) + tile = state->grid[y*w+x]; + else + tile = (long)state->pencil[y*w+x] << DF_PENCIL_SHIFT; + + if (ui->hshow && ui->hx == x && ui->hy == y) + tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT); + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + tile |= DF_HIGHLIGHT; /* completion flash */ + + tile |= ds->errors[y*w+x]; + + if (ds->tiles[y*w+x] != tile) { + ds->tiles[y*w+x] = tile; + draw_tile(dr, ds, state->clues, x, y, tile, + state->par.multiplication_only); + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + if (state->completed) + return FALSE; + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * We use 9mm squares by default, like Solo. + */ + game_compute_size(params, 900, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +/* + * Subfunction to draw the thick lines between cells. In order to do + * this using the line-drawing rather than rectangle-drawing API (so + * as to get line thicknesses to scale correctly) and yet have + * correctly mitred joins between lines, we must do this by tracing + * the boundary of each sub-block and drawing it in one go as a + * single polygon. + */ +static void outline_block_structure(drawing *dr, game_drawstate *ds, + int w, int *dsf, int ink) +{ + int a = w*w; + int *coords; + int i, n; + int x, y, dx, dy, sx, sy, sdx, sdy; + + coords = snewn(4*a, int); + + /* + * Iterate over all the blocks. + */ + for (i = 0; i < a; i++) { + if (dsf_canonify(dsf, i) != i) + continue; + + /* + * For each block, we need a starting square within it which + * has a boundary at the left. Conveniently, we have one + * right here, by construction. + */ + x = i % w; + y = i / w; + dx = -1; + dy = 0; + + /* + * Now begin tracing round the perimeter. At all + * times, (x,y) describes some square within the + * block, and (x+dx,y+dy) is some adjacent square + * outside it; so the edge between those two squares + * is always an edge of the block. + */ + sx = x, sy = y, sdx = dx, sdy = dy; /* save starting position */ + n = 0; + do { + int cx, cy, tx, ty, nin; + + /* + * Advance to the next edge, by looking at the two + * squares beyond it. If they're both outside the block, + * we turn right (by leaving x,y the same and rotating + * dx,dy clockwise); if they're both inside, we turn + * left (by rotating dx,dy anticlockwise and contriving + * to leave x+dx,y+dy unchanged); if one of each, we go + * straight on (and may enforce by assertion that + * they're one of each the _right_ way round). + */ + nin = 0; + tx = x - dy + dx; + ty = y + dx + dy; + nin += (tx >= 0 && tx < w && ty >= 0 && ty < w && + dsf_canonify(dsf, ty*w+tx) == i); + tx = x - dy; + ty = y + dx; + nin += (tx >= 0 && tx < w && ty >= 0 && ty < w && + dsf_canonify(dsf, ty*w+tx) == i); + if (nin == 0) { + /* + * Turn right. + */ + int tmp; + tmp = dx; + dx = -dy; + dy = tmp; + } else if (nin == 2) { + /* + * Turn left. + */ + int tmp; + + x += dx; + y += dy; + + tmp = dx; + dx = dy; + dy = -tmp; + + x -= dx; + y -= dy; + } else { + /* + * Go straight on. + */ + x -= dy; + y += dx; + } + + /* + * Now enforce by assertion that we ended up + * somewhere sensible. + */ + assert(x >= 0 && x < w && y >= 0 && y < w && + dsf_canonify(dsf, y*w+x) == i); + assert(x+dx < 0 || x+dx >= w || y+dy < 0 || y+dy >= w || + dsf_canonify(dsf, (y+dy)*w+(x+dx)) != i); + + /* + * Record the point we just went past at one end of the + * edge. To do this, we translate (x,y) down and right + * by half a unit (so they're describing a point in the + * _centre_ of the square) and then translate back again + * in a manner rotated by dy and dx. + */ + assert(n < 2*w+2); + cx = ((2*x+1) + dy + dx) / 2; + cy = ((2*y+1) - dx + dy) / 2; + coords[2*n+0] = BORDER + cx * TILESIZE; + coords[2*n+1] = BORDER + cy * TILESIZE; + n++; + + } while (x != sx || y != sy || dx != sdx || dy != sdy); + + /* + * That's our polygon; now draw it. + */ + draw_polygon(dr, coords, n, -1, ink); + } + + sfree(coords); +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->par.w; + int ink = print_mono_colour(dr, 0); + int x, y; + char *minus_sign, *times_sign, *divide_sign; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + minus_sign = text_fallback(dr, minus_signs, lenof(minus_signs)); + times_sign = text_fallback(dr, times_signs, lenof(times_signs)); + divide_sign = text_fallback(dr, divide_signs, lenof(divide_signs)); + + /* + * Border. + */ + print_line_width(dr, 3 * TILESIZE / 40); + draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, w*TILESIZE, ink); + + /* + * Main grid. + */ + for (x = 1; x < w; x++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER+x*TILESIZE, BORDER, + BORDER+x*TILESIZE, BORDER+w*TILESIZE, ink); + } + for (y = 1; y < w; y++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER, BORDER+y*TILESIZE, + BORDER+w*TILESIZE, BORDER+y*TILESIZE, ink); + } + + /* + * Thick lines between cells. + */ + print_line_width(dr, 3 * TILESIZE / 40); + outline_block_structure(dr, ds, w, state->clues->dsf, ink); + + /* + * Clues. + */ + for (y = 0; y < w; y++) + for (x = 0; x < w; x++) + if (dsf_canonify(state->clues->dsf, y*w+x) == y*w+x) { + long clue = state->clues->clues[y*w+x]; + long cluetype = clue & CMASK, clueval = clue & ~CMASK; + int size = dsf_size(state->clues->dsf, y*w+x); + char str[64]; + + /* + * As in the drawing code, we omit the operator for + * blocks of area 1. + */ + sprintf (str, "%ld%s", clueval, + (size == 1 ? "" : + cluetype == C_ADD ? "+" : + cluetype == C_SUB ? minus_sign : + cluetype == C_MUL ? times_sign : + /* cluetype == C_DIV ? */ divide_sign)); + + draw_text(dr, + BORDER+x*TILESIZE + 5*TILESIZE/80, + BORDER+y*TILESIZE + 20*TILESIZE/80, + FONT_VARIABLE, TILESIZE/4, + ALIGN_VNORMAL | ALIGN_HLEFT, + ink, str); + } + + /* + * Numbers for the solution, if any. + */ + for (y = 0; y < w; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x]) { + char str[2]; + str[1] = '\0'; + str[0] = state->grid[y*w+x] + '0'; + draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2, + BORDER + y*TILESIZE + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } + + sfree(minus_sign); + sfree(times_sign); + sfree(divide_sign); +} + +#ifdef COMBINED +#define thegame keen +#endif + +const struct game thegame = { + "Keen", "games.keen", "keen", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff, really_show_working = FALSE; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_show_working = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + solver_show_working = FALSE; + for (diff = 0; diff < DIFFCOUNT; diff++) { + memset(s->grid, 0, p->w * p->w); + ret = solver(p->w, s->clues->dsf, s->clues->clues, + s->grid, diff); + if (ret <= diff) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == diff_impossible) + printf("Difficulty rating: impossible (no solution exists)\n"); + else + printf("Difficulty rating: %s\n", keen_diffnames[ret]); + } else { + solver_show_working = really_show_working; + memset(s->grid, 0, p->w * p->w); + ret = solver(p->w, s->clues->dsf, s->clues->clues, + s->grid, diff); + if (ret != diff) + printf("Puzzle is inconsistent\n"); + else { + /* + * We don't have a game_text_format for this game, + * so we have to output the solution manually. + */ + int x, y; + for (y = 0; y < p->w; y++) { + for (x = 0; x < p->w; x++) { + printf("%s%c", x>0?" ":"", '0' + s->grid[y*p->w+x]); + } + putchar('\n'); + } + } + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/keymaps.h b/apps/plugins/puzzles/keymaps.h new file mode 100644 index 0000000000..651ecca250 --- /dev/null +++ b/apps/plugins/puzzles/keymaps.h @@ -0,0 +1,206 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 Franklin Wei, Benjamin Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef _XWORLD_KEYMAPS_H +#define _XWORLD_KEYMAPS_H + +/* Handle the "nice" targets that have directional buttons with normal names */ +#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \ + (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \ + (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \ + (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \ + (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \ + (CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \ + (CONFIG_KEYPAD == SANSA_C200_PAD) || \ + (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \ + (CONFIG_KEYPAD == SANSA_E200_PAD) || \ + (CONFIG_KEYPAD == SANSA_FUZE_PAD) || \ + (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \ + (CONFIG_KEYPAD == GIGABEAT_PAD) || \ + (CONFIG_KEYPAD == GIGABEAT_S_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ + (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) || \ + (CONFIG_KEYPAD == CREATIVE_ZEN_PAD) || \ + (CONFIG_KEYPAD == SONY_NWZ_PAD) || \ + (CONFIG_KEYPAD == CREATIVEZVM_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) || \ + (CONFIG_KEYPAD == HM801_PAD) || \ + (CONFIG_KEYPAD == HM60X_PAD) +#define BTN_UP BUTTON_UP +#define BTN_DOWN BUTTON_DOWN +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT + +#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) +#define BTN_UP_LEFT BUTTON_BACK +#define BTN_UP_RIGHT BUTTON_PLAYPAUSE +#define BTN_DOWN_LEFT BUTTON_BOTTOMLEFT +#define BTN_DOWN_RIGHT BUTTON_BOTTOMRIGHT +#endif + +#if (CONFIG_KEYPAD == HM60X_PAD) +#define BTN_FIRE BUTTON_POWER +#define BTN_PAUSE BUTTON_SELECT +#endif + +#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \ + (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \ + (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \ + (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \ + (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \ + (CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \ + (CONFIG_KEYPAD == SANSA_C200_PAD) || \ + (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \ + (CONFIG_KEYPAD == ONDAVX747_PAD) +#define BTN_FIRE BUTTON_VOL_UP +#define BTN_PAUSE BUTTON_VOL_DOWN + +#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD) +#define BTN_FIRE BUTTON_HOME +#define BTN_PAUSE BUTTON_SELECT + +#elif (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) +#define BTN_FIRE BUTTON_FFWD +#define BTN_PAUSE BUTTON_REW + +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#define BTN_FIRE BUTTON_REC +#define BTN_PAUSE BUTTON_POWER + +#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD) +#define BTN_FIRE BUTTON_SELECT +#define BTN_PAUSE BUTTON_POWER + +#elif (CONFIG_KEYPAD == CREATIVE_ZEN_PAD) +#define BTN_FIRE BUTTON_SELECT +#define BTN_PAUSE BUTTON_BACK + +#elif (CONFIG_KEYPAD == CREATIVEZVM_PAD) +#define BTN_FIRE BUTTON_PLAY +#define BTN_PAUSE BUTTON_MENU + +#elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) +#define BTN_FIRE BUTTON_USER +#define BTN_PAUSE BUTTON_MENU + +#elif (CONFIG_KEYPAD == SONY_NWZ_PAD) +#define BTN_FIRE BUTTON_PLAY +#define BTN_PAUSE BUTTON_BACK + +#elif (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define BTN_FIRE BUTTON_REC +#define BTN_PAUSE BUTTON_MODE + +#elif (CONFIG_KEYPAD == HM801_PAD) +#define BTN_FIRE BUTTON_PREV +#define BTN_PAUSE BUTTON_NEXT + +#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ + (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define BTN_FIRE BUTTON_REC +#define BTN_PAUSE BUTTON_PLAY + +#elif (CONFIG_KEYPAD == GIGABEAT_PAD) || \ + (CONFIG_KEYPAD == GIGABEAT_S_PAD) +#define BTN_FIRE BUTTON_VOL_UP +#define BTN_PAUSE BUTTON_MENU +/* #if CONFIG_KEYPAD == PHILIPS_HDD1630_PAD */ +#endif + +/* ... and now for the bad ones that don't have + * standard names for the directional buttons */ +#elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD) +#define BTN_UP BUTTON_OK +#define BTN_DOWN BUTTON_CANCEL +#define BTN_LEFT BUTTON_MENU +#define BTN_RIGHT BUTTON_PLAY +#define BTN_FIRE BUTTON_POWER +#define BTN_PAUSE BUTTON_REC + +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) +#define BTN_UP BUTTON_SCROLL_UP +#define BTN_DOWN BUTTON_SCROLL_DOWN +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT +#define BTN_FIRE BUTTON_REW +#define BTN_PAUSE BUTTON_PLAY + +#elif (CONFIG_KEYPAD == MROBE500_PAD) +#define BTN_FIRE BUTTON_POWER + +#elif (CONFIG_KEYPAD == MROBE_REMOTE) +#define BTN_UP BUTTON_RC_PLAY +#define BTN_DOWN BUTTON_RC_DOWN +#define BTN_LEFT BUTTON_RC_REW +#define BTN_RIGHT BUTTON_RC_FF + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define BTN_UP BUTTON_MENU +#define BTN_DOWN BUTTON_PLAY +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT +#define BTN_FIRE BUTTON_SELECT +#define BTN_PAUSE (BUTTON_MENU | BUTTON_SELECT) + +#elif (CONFIG_KEYPAD == ONDAVX777_PAD) +#define BTN_FIRE BUTTON_POWER + +#elif (CONFIG_KEYPAD == COWON_D2_PAD) +#define BTN_FIRE BUTTON_PLUS +#define BTN_PAUSE BUTTON_MINUS + +#elif (CONFIG_KEYPAD == ONDAVX747_PAD) || \ + (CONFIG_KEYPAD == DX50_PAD) +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT +#define BTN_FIRE BUTTON_BOTTOMLEFT +#define BTN_PAUSE BUTTON_TOPLEFT + +#else +#error Unsupported keypad +#endif + +#ifdef HAVE_TOUCHSCREEN +#define BTN_UP BUTTON_TOPMIDDLE +#define BTN_DOWN BUTTON_BOTTOMMIDDLE +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT + +#if (CONFIG_KEYPAD == MROBE500_PAD) || \ + (CONFIG_KEYPAD == ONDAVX777_PAD) +#define BTN_PAUSE BUTTON_BOTTOMLEFT + +#elif (CONFIG_KEYPAD != COWON_D2_PAD) && \ + (CONFIG_KEYPAD != DX50_PAD) && \ + (CONFIG_KEYPAD != ONDAVX777_PAD) +#define BTN_FIRE BUTTON_BOTTOMLEFT +#define BTN_PAUSE BUTTON_TOPLEFT +#endif + +/* HAVE_TOUCHSCREEN */ +#endif + +/* _XWORLD_KEYMAPS_H */ +#endif diff --git a/apps/plugins/puzzles/latin.c b/apps/plugins/puzzles/latin.c new file mode 100644 index 0000000000..f7e97a7fa6 --- /dev/null +++ b/apps/plugins/puzzles/latin.c @@ -0,0 +1,1436 @@ +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" +#include "maxflow.h" + +#ifdef STANDALONE_LATIN_TEST +#define STANDALONE_SOLVER +#endif + +#include "latin.h" + +/* -------------------------------------------------------- + * Solver. + */ + +static int latin_solver_top(struct latin_solver *solver, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree); + +#ifdef STANDALONE_SOLVER +int solver_show_working, solver_recurse_depth; +#endif + +/* + * Function called when we are certain that a particular square has + * a particular number in it. The y-coordinate passed in here is + * transformed. + */ +void latin_solver_place(struct latin_solver *solver, int x, int y, int n) +{ + int i, o = solver->o; + + assert(n <= o); + assert(cube(x,y,n)); + + /* + * Rule out all other numbers in this square. + */ + for (i = 1; i <= o; i++) + if (i != n) + cube(x,y,i) = FALSE; + + /* + * Rule out this number in all other positions in the row. + */ + for (i = 0; i < o; i++) + if (i != y) + cube(x,i,n) = FALSE; + + /* + * Rule out this number in all other positions in the column. + */ + for (i = 0; i < o; i++) + if (i != x) + cube(i,y,n) = FALSE; + + /* + * Enter the number in the result grid. + */ + solver->grid[y*o+x] = n; + + /* + * Cross out this number from the list of numbers left to place + * in its row, its column and its block. + */ + solver->row[y*o+n-1] = solver->col[x*o+n-1] = TRUE; +} + +int latin_solver_elim(struct latin_solver *solver, int start, int step +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ) +{ + int o = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + int fpos, m, i; + + /* + * Count the number of set bits within this section of the + * cube. + */ + m = 0; + fpos = -1; + for (i = 0; i < o; i++) + if (solver->cube[start+i*step]) { + fpos = start+i*step; + m++; + } + + if (m == 1) { + int x, y, n; + assert(fpos >= 0); + + n = 1 + fpos % o; + y = fpos / o; + x = y / o; + y %= o; + + if (!solver->grid[y*o+x]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s placing %s at (%d,%d)\n", + solver_recurse_depth*4, "", names[n-1], + x+1, y+1); + } +#endif + latin_solver_place(solver, x, y, n); + return +1; + } + } else if (m == 0) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s no possibilities available\n", + solver_recurse_depth*4, ""); + } +#endif + return -1; + } + + return 0; +} + +struct latin_solver_scratch { + unsigned char *grid, *rowidx, *colidx, *set; + int *neighbours, *bfsqueue; +#ifdef STANDALONE_SOLVER + int *bfsprev; +#endif +}; + +int latin_solver_set(struct latin_solver *solver, + struct latin_solver_scratch *scratch, + int start, int step1, int step2 +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ) +{ + int o = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + int i, j, n, count; + unsigned char *grid = scratch->grid; + unsigned char *rowidx = scratch->rowidx; + unsigned char *colidx = scratch->colidx; + unsigned char *set = scratch->set; + + /* + * We are passed a o-by-o matrix of booleans. Our first job + * is to winnow it by finding any definite placements - i.e. + * any row with a solitary 1 - and discarding that row and the + * column containing the 1. + */ + memset(rowidx, TRUE, o); + memset(colidx, TRUE, o); + for (i = 0; i < o; i++) { + int count = 0, first = -1; + for (j = 0; j < o; j++) + if (solver->cube[start+i*step1+j*step2]) + first = j, count++; + + if (count == 0) return -1; + if (count == 1) + rowidx[i] = colidx[first] = FALSE; + } + + /* + * Convert each of rowidx/colidx from a list of 0s and 1s to a + * list of the indices of the 1s. + */ + for (i = j = 0; i < o; i++) + if (rowidx[i]) + rowidx[j++] = i; + n = j; + for (i = j = 0; i < o; i++) + if (colidx[i]) + colidx[j++] = i; + assert(n == j); + + /* + * And create the smaller matrix. + */ + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + grid[i*o+j] = solver->cube[start+rowidx[i]*step1+colidx[j]*step2]; + + /* + * Having done that, we now have a matrix in which every row + * has at least two 1s in. Now we search to see if we can find + * a rectangle of zeroes (in the set-theoretic sense of + * `rectangle', i.e. a subset of rows crossed with a subset of + * columns) whose width and height add up to n. + */ + + memset(set, 0, n); + count = 0; + while (1) { + /* + * We have a candidate set. If its size is <=1 or >=n-1 + * then we move on immediately. + */ + if (count > 1 && count < n-1) { + /* + * The number of rows we need is n-count. See if we can + * find that many rows which each have a zero in all + * the positions listed in `set'. + */ + int rows = 0; + for (i = 0; i < n; i++) { + int ok = TRUE; + for (j = 0; j < n; j++) + if (set[j] && grid[i*o+j]) { + ok = FALSE; + break; + } + if (ok) + rows++; + } + + /* + * We expect never to be able to get _more_ than + * n-count suitable rows: this would imply that (for + * example) there are four numbers which between them + * have at most three possible positions, and hence it + * indicates a faulty deduction before this point or + * even a bogus clue. + */ + if (rows > n - count) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, + ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s contradiction reached\n", + solver_recurse_depth*4, ""); + } +#endif + return -1; + } + + if (rows >= n - count) { + int progress = FALSE; + + /* + * We've got one! Now, for each row which _doesn't_ + * satisfy the criterion, eliminate all its set + * bits in the positions _not_ listed in `set'. + * Return +1 (meaning progress has been made) if we + * successfully eliminated anything at all. + * + * This involves referring back through + * rowidx/colidx in order to work out which actual + * positions in the cube to meddle with. + */ + for (i = 0; i < n; i++) { + int ok = TRUE; + for (j = 0; j < n; j++) + if (set[j] && grid[i*o+j]) { + ok = FALSE; + break; + } + if (!ok) { + for (j = 0; j < n; j++) + if (!set[j] && grid[i*o+j]) { + int fpos = (start+rowidx[i]*step1+ + colidx[j]*step2); +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int px, py, pn; + + if (!progress) { + va_list ap; + printf("%*s", solver_recurse_depth*4, + ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n"); + } + + pn = 1 + fpos % o; + py = fpos / o; + px = py / o; + py %= o; + + printf("%*s ruling out %s at (%d,%d)\n", + solver_recurse_depth*4, "", + names[pn-1], px+1, py+1); + } +#endif + progress = TRUE; + solver->cube[fpos] = FALSE; + } + } + } + + if (progress) { + return +1; + } + } + } + + /* + * Binary increment: change the rightmost 0 to a 1, and + * change all 1s to the right of it to 0s. + */ + i = n; + while (i > 0 && set[i-1]) + set[--i] = 0, count--; + if (i > 0) + set[--i] = 1, count++; + else + break; /* done */ + } + + return 0; +} + +/* + * Look for forcing chains. A forcing chain is a path of + * pairwise-exclusive squares (i.e. each pair of adjacent squares + * in the path are in the same row, column or block) with the + * following properties: + * + * (a) Each square on the path has precisely two possible numbers. + * + * (b) Each pair of squares which are adjacent on the path share + * at least one possible number in common. + * + * (c) Each square in the middle of the path shares _both_ of its + * numbers with at least one of its neighbours (not the same + * one with both neighbours). + * + * These together imply that at least one of the possible number + * choices at one end of the path forces _all_ the rest of the + * numbers along the path. In order to make real use of this, we + * need further properties: + * + * (c) Ruling out some number N from the square at one end + * of the path forces the square at the other end to + * take number N. + * + * (d) The two end squares are both in line with some third + * square. + * + * (e) That third square currently has N as a possibility. + * + * If we can find all of that lot, we can deduce that at least one + * of the two ends of the forcing chain has number N, and that + * therefore the mutually adjacent third square does not. + * + * To find forcing chains, we're going to start a bfs at each + * suitable square, once for each of its two possible numbers. + */ +int latin_solver_forcing(struct latin_solver *solver, + struct latin_solver_scratch *scratch) +{ + int o = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + int *bfsqueue = scratch->bfsqueue; +#ifdef STANDALONE_SOLVER + int *bfsprev = scratch->bfsprev; +#endif + unsigned char *number = scratch->grid; + int *neighbours = scratch->neighbours; + int x, y; + + for (y = 0; y < o; y++) + for (x = 0; x < o; x++) { + int count, t, n; + + /* + * If this square doesn't have exactly two candidate + * numbers, don't try it. + * + * In this loop we also sum the candidate numbers, + * which is a nasty hack to allow us to quickly find + * `the other one' (since we will shortly know there + * are exactly two). + */ + for (count = t = 0, n = 1; n <= o; n++) + if (cube(x, y, n)) + count++, t += n; + if (count != 2) + continue; + + /* + * Now attempt a bfs for each candidate. + */ + for (n = 1; n <= o; n++) + if (cube(x, y, n)) { + int orign, currn, head, tail; + + /* + * Begin a bfs. + */ + orign = n; + + memset(number, o+1, o*o); + head = tail = 0; + bfsqueue[tail++] = y*o+x; +#ifdef STANDALONE_SOLVER + bfsprev[y*o+x] = -1; +#endif + number[y*o+x] = t - n; + + while (head < tail) { + int xx, yy, nneighbours, xt, yt, i; + + xx = bfsqueue[head++]; + yy = xx / o; + xx %= o; + + currn = number[yy*o+xx]; + + /* + * Find neighbours of yy,xx. + */ + nneighbours = 0; + for (yt = 0; yt < o; yt++) + neighbours[nneighbours++] = yt*o+xx; + for (xt = 0; xt < o; xt++) + neighbours[nneighbours++] = yy*o+xt; + + /* + * Try visiting each of those neighbours. + */ + for (i = 0; i < nneighbours; i++) { + int cc, tt, nn; + + xt = neighbours[i] % o; + yt = neighbours[i] / o; + + /* + * We need this square to not be + * already visited, and to include + * currn as a possible number. + */ + if (number[yt*o+xt] <= o) + continue; + if (!cube(xt, yt, currn)) + continue; + + /* + * Don't visit _this_ square a second + * time! + */ + if (xt == xx && yt == yy) + continue; + + /* + * To continue with the bfs, we need + * this square to have exactly two + * possible numbers. + */ + for (cc = tt = 0, nn = 1; nn <= o; nn++) + if (cube(xt, yt, nn)) + cc++, tt += nn; + if (cc == 2) { + bfsqueue[tail++] = yt*o+xt; +#ifdef STANDALONE_SOLVER + bfsprev[yt*o+xt] = yy*o+xx; +#endif + number[yt*o+xt] = tt - currn; + } + + /* + * One other possibility is that this + * might be the square in which we can + * make a real deduction: if it's + * adjacent to x,y, and currn is equal + * to the original number we ruled out. + */ + if (currn == orign && + (xt == x || yt == y)) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *sep = ""; + int xl, yl; + printf("%*sforcing chain, %s at ends of ", + solver_recurse_depth*4, "", + names[orign-1]); + xl = xx; + yl = yy; + while (1) { + printf("%s(%d,%d)", sep, xl+1, + yl+1); + xl = bfsprev[yl*o+xl]; + if (xl < 0) + break; + yl = xl / o; + xl %= o; + sep = "-"; + } + printf("\n%*s ruling out %s at (%d,%d)\n", + solver_recurse_depth*4, "", + names[orign-1], + xt+1, yt+1); + } +#endif + cube(xt, yt, orign) = FALSE; + return 1; + } + } + } + } + } + + return 0; +} + +struct latin_solver_scratch *latin_solver_new_scratch(struct latin_solver *solver) +{ + struct latin_solver_scratch *scratch = snew(struct latin_solver_scratch); + int o = solver->o; + scratch->grid = snewn(o*o, unsigned char); + scratch->rowidx = snewn(o, unsigned char); + scratch->colidx = snewn(o, unsigned char); + scratch->set = snewn(o, unsigned char); + scratch->neighbours = snewn(3*o, int); + scratch->bfsqueue = snewn(o*o, int); +#ifdef STANDALONE_SOLVER + scratch->bfsprev = snewn(o*o, int); +#endif + return scratch; +} + +void latin_solver_free_scratch(struct latin_solver_scratch *scratch) +{ +#ifdef STANDALONE_SOLVER + sfree(scratch->bfsprev); +#endif + sfree(scratch->bfsqueue); + sfree(scratch->neighbours); + sfree(scratch->set); + sfree(scratch->colidx); + sfree(scratch->rowidx); + sfree(scratch->grid); + sfree(scratch); +} + +void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o) +{ + int x, y; + + solver->o = o; + solver->cube = snewn(o*o*o, unsigned char); + solver->grid = grid; /* write straight back to the input */ + memset(solver->cube, TRUE, o*o*o); + + solver->row = snewn(o*o, unsigned char); + solver->col = snewn(o*o, unsigned char); + memset(solver->row, FALSE, o*o); + memset(solver->col, FALSE, o*o); + + for (x = 0; x < o; x++) + for (y = 0; y < o; y++) + if (grid[y*o+x]) + latin_solver_place(solver, x, y, grid[y*o+x]); + +#ifdef STANDALONE_SOLVER + solver->names = NULL; +#endif +} + +void latin_solver_free(struct latin_solver *solver) +{ + sfree(solver->cube); + sfree(solver->row); + sfree(solver->col); +} + +int latin_solver_diff_simple(struct latin_solver *solver) +{ + int x, y, n, ret, o = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + + /* + * Row-wise positional elimination. + */ + for (y = 0; y < o; y++) + for (n = 1; n <= o; n++) + if (!solver->row[y*o+n-1]) { + ret = latin_solver_elim(solver, cubepos(0,y,n), o*o +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %s in row %d", names[n-1], + y+1 +#endif + ); + if (ret != 0) return ret; + } + /* + * Column-wise positional elimination. + */ + for (x = 0; x < o; x++) + for (n = 1; n <= o; n++) + if (!solver->col[x*o+n-1]) { + ret = latin_solver_elim(solver, cubepos(x,0,n), o +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %s in column %d", names[n-1], x+1 +#endif + ); + if (ret != 0) return ret; + } + + /* + * Numeric elimination. + */ + for (x = 0; x < o; x++) + for (y = 0; y < o; y++) + if (!solver->grid[y*o+x]) { + ret = latin_solver_elim(solver, cubepos(x,y,1), 1 +#ifdef STANDALONE_SOLVER + , "numeric elimination at (%d,%d)", + x+1, y+1 +#endif + ); + if (ret != 0) return ret; + } + return 0; +} + +int latin_solver_diff_set(struct latin_solver *solver, + struct latin_solver_scratch *scratch, + int extreme) +{ + int x, y, n, ret, o = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + + if (!extreme) { + /* + * Row-wise set elimination. + */ + for (y = 0; y < o; y++) { + ret = latin_solver_set(solver, scratch, cubepos(0,y,1), o*o, 1 +#ifdef STANDALONE_SOLVER + , "set elimination, row %d", y+1 +#endif + ); + if (ret != 0) return ret; + } + /* + * Column-wise set elimination. + */ + for (x = 0; x < o; x++) { + ret = latin_solver_set(solver, scratch, cubepos(x,0,1), o, 1 +#ifdef STANDALONE_SOLVER + , "set elimination, column %d", x+1 +#endif + ); + if (ret != 0) return ret; + } + } else { + /* + * Row-vs-column set elimination on a single number + * (much tricker for a human to do!) + */ + for (n = 1; n <= o; n++) { + ret = latin_solver_set(solver, scratch, cubepos(0,0,n), o*o, o +#ifdef STANDALONE_SOLVER + , "positional set elimination on %s", + names[n-1] +#endif + ); + if (ret != 0) return ret; + } + } + return 0; +} + +/* + * Returns: + * 0 for 'didn't do anything' implying it was already solved. + * -1 for 'impossible' (no solution) + * 1 for 'single solution' + * >1 for 'multiple solutions' (you don't get to know how many, and + * the first such solution found will be set. + * + * and this function may well assert if given an impossible board. + */ +static int latin_solver_recurse + (struct latin_solver *solver, int diff_simple, int diff_set_0, + int diff_set_1, int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree) +{ + int best, bestcount; + int o = solver->o, x, y, n; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + + best = -1; + bestcount = o+1; + + for (y = 0; y < o; y++) + for (x = 0; x < o; x++) + if (!solver->grid[y*o+x]) { + int count; + + /* + * An unfilled square. Count the number of + * possible digits in it. + */ + count = 0; + for (n = 1; n <= o; n++) + if (cube(x,y,n)) + count++; + + /* + * We should have found any impossibilities + * already, so this can safely be an assert. + */ + assert(count > 1); + + if (count < bestcount) { + bestcount = count; + best = y*o+x; + } + } + + if (best == -1) + /* we were complete already. */ + return 0; + else { + int i, j; + digit *list, *ingrid, *outgrid; + int diff = diff_impossible; /* no solution found yet */ + + /* + * Attempt recursion. + */ + y = best / o; + x = best % o; + + list = snewn(o, digit); + ingrid = snewn(o*o, digit); + outgrid = snewn(o*o, digit); + memcpy(ingrid, solver->grid, o*o); + + /* Make a list of the possible digits. */ + for (j = 0, n = 1; n <= o; n++) + if (cube(x,y,n)) + list[j++] = n; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *sep = ""; + printf("%*srecursing on (%d,%d) [", + solver_recurse_depth*4, "", x+1, y+1); + for (i = 0; i < j; i++) { + printf("%s%s", sep, names[list[i]-1]); + sep = " or "; + } + printf("]\n"); + } +#endif + + /* + * And step along the list, recursing back into the + * main solver at every stage. + */ + for (i = 0; i < j; i++) { + int ret; + void *newctx; + struct latin_solver subsolver; + + memcpy(outgrid, ingrid, o*o); + outgrid[y*o+x] = list[i]; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*sguessing %s at (%d,%d)\n", + solver_recurse_depth*4, "", names[list[i]-1], x+1, y+1); + solver_recurse_depth++; +#endif + + if (ctxnew) { + newctx = ctxnew(ctx); + } else { + newctx = ctx; + } + latin_solver_alloc(&subsolver, outgrid, o); +#ifdef STANDALONE_SOLVER + subsolver.names = solver->names; +#endif + ret = latin_solver_top(&subsolver, diff_recursive, + diff_simple, diff_set_0, diff_set_1, + diff_forcing, diff_recursive, + usersolvers, newctx, ctxnew, ctxfree); + latin_solver_free(&subsolver); + if (ctxnew) + ctxfree(newctx); + +#ifdef STANDALONE_SOLVER + solver_recurse_depth--; + if (solver_show_working) { + printf("%*sretracting %s at (%d,%d)\n", + solver_recurse_depth*4, "", names[list[i]-1], x+1, y+1); + } +#endif + /* we recurse as deep as we can, so we should never find + * find ourselves giving up on a puzzle without declaring it + * impossible. */ + assert(ret != diff_unfinished); + + /* + * If we have our first solution, copy it into the + * grid we will return. + */ + if (diff == diff_impossible && ret != diff_impossible) + memcpy(solver->grid, outgrid, o*o); + + if (ret == diff_ambiguous) + diff = diff_ambiguous; + else if (ret == diff_impossible) + /* do not change our return value */; + else { + /* the recursion turned up exactly one solution */ + if (diff == diff_impossible) + diff = diff_recursive; + else + diff = diff_ambiguous; + } + + /* + * As soon as we've found more than one solution, + * give up immediately. + */ + if (diff == diff_ambiguous) + break; + } + + sfree(outgrid); + sfree(ingrid); + sfree(list); + + if (diff == diff_impossible) + return -1; + else if (diff == diff_ambiguous) + return 2; + else { + assert(diff == diff_recursive); + return 1; + } + } +} + +static int latin_solver_top(struct latin_solver *solver, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree) +{ + struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver); + int ret, diff = diff_simple; + + assert(maxdiff <= diff_recursive); + /* + * Now loop over the grid repeatedly trying all permitted modes + * of reasoning. The loop terminates if we complete an + * iteration without making any progress; we then return + * failure or success depending on whether the grid is full or + * not. + */ + while (1) { + int i; + + cont: + + latin_solver_debug(solver->cube, solver->o); + + for (i = 0; i <= maxdiff; i++) { + if (usersolvers[i]) + ret = usersolvers[i](solver, ctx); + else + ret = 0; + if (ret == 0 && i == diff_simple) + ret = latin_solver_diff_simple(solver); + if (ret == 0 && i == diff_set_0) + ret = latin_solver_diff_set(solver, scratch, 0); + if (ret == 0 && i == diff_set_1) + ret = latin_solver_diff_set(solver, scratch, 1); + if (ret == 0 && i == diff_forcing) + ret = latin_solver_forcing(solver, scratch); + + if (ret < 0) { + diff = diff_impossible; + goto got_result; + } else if (ret > 0) { + diff = max(diff, i); + goto cont; + } + } + + /* + * If we reach here, we have made no deductions in this + * iteration, so the algorithm terminates. + */ + break; + } + + /* + * Last chance: if we haven't fully solved the puzzle yet, try + * recursing based on guesses for a particular square. We pick + * one of the most constrained empty squares we can find, which + * has the effect of pruning the search tree as much as + * possible. + */ + if (maxdiff == diff_recursive) { + int nsol = latin_solver_recurse(solver, + diff_simple, diff_set_0, diff_set_1, + diff_forcing, diff_recursive, + usersolvers, ctx, ctxnew, ctxfree); + if (nsol < 0) diff = diff_impossible; + else if (nsol == 1) diff = diff_recursive; + else if (nsol > 1) diff = diff_ambiguous; + /* if nsol == 0 then we were complete anyway + * (and thus don't need to change diff) */ + } else { + /* + * We're forbidden to use recursion, so we just see whether + * our grid is fully solved, and return diff_unfinished + * otherwise. + */ + int x, y, o = solver->o; + + for (y = 0; y < o; y++) + for (x = 0; x < o; x++) + if (!solver->grid[y*o+x]) + diff = diff_unfinished; + } + + got_result: + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s%s found\n", + solver_recurse_depth*4, "", + diff == diff_impossible ? "no solution (impossible)" : + diff == diff_unfinished ? "no solution (unfinished)" : + diff == diff_ambiguous ? "multiple solutions" : + "one solution"); +#endif + + latin_solver_free_scratch(scratch); + + return diff; +} + +int latin_solver_main(struct latin_solver *solver, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree) +{ + int diff; +#ifdef STANDALONE_SOLVER + int o = solver->o; + char *text = NULL, **names = NULL; +#endif + +#ifdef STANDALONE_SOLVER + if (!solver->names) { + char *p; + int i; + + text = snewn(40 * o, char); + p = text; + + solver->names = snewn(o, char *); + + for (i = 0; i < o; i++) { + solver->names[i] = p; + p += 1 + sprintf(p, "%d", i+1); + } + } +#endif + + diff = latin_solver_top(solver, maxdiff, + diff_simple, diff_set_0, diff_set_1, + diff_forcing, diff_recursive, + usersolvers, ctx, ctxnew, ctxfree); + +#ifdef STANDALONE_SOLVER + sfree(names); + sfree(text); +#endif + + return diff; +} + +int latin_solver(digit *grid, int o, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree) +{ + struct latin_solver solver; + int diff; + + latin_solver_alloc(&solver, grid, o); + diff = latin_solver_main(&solver, maxdiff, + diff_simple, diff_set_0, diff_set_1, + diff_forcing, diff_recursive, + usersolvers, ctx, ctxnew, ctxfree); + latin_solver_free(&solver); + return diff; +} + +void latin_solver_debug(unsigned char *cube, int o) +{ +#ifdef STANDALONE_SOLVER + if (solver_show_working > 1) { + struct latin_solver ls, *solver = &ls; + char *dbg; + int x, y, i, c = 0; + + ls.cube = cube; ls.o = o; /* for cube() to work */ + + dbg = snewn(3*o*o*o, char); + for (y = 0; y < o; y++) { + for (x = 0; x < o; x++) { + for (i = 1; i <= o; i++) { + if (cube(x,y,i)) + dbg[c++] = i + '0'; + else + dbg[c++] = '.'; + } + dbg[c++] = ' '; + } + dbg[c++] = '\n'; + } + dbg[c++] = '\n'; + dbg[c++] = '\0'; + + printf("%s", dbg); + sfree(dbg); + } +#endif +} + +void latin_debug(digit *sq, int o) +{ +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int x, y; + + for (y = 0; y < o; y++) { + for (x = 0; x < o; x++) { + printf("%2d ", sq[y*o+x]); + } + printf("\n"); + } + printf("\n"); + } +#endif +} + +/* -------------------------------------------------------- + * Generation. + */ + +digit *latin_generate(int o, random_state *rs) +{ + digit *sq; + int *edges, *backedges, *capacity, *flow; + void *scratch; + int ne, scratchsize; + int i, j, k; + digit *row, *col, *numinv, *num; + + /* + * To efficiently generate a latin square in such a way that + * all possible squares are possible outputs from the function, + * we make use of a theorem which states that any r x n latin + * rectangle, with r < n, can be extended into an (r+1) x n + * latin rectangle. In other words, we can reliably generate a + * latin square row by row, by at every stage writing down any + * row at all which doesn't conflict with previous rows, and + * the theorem guarantees that we will never have to backtrack. + * + * To find a viable row at each stage, we can make use of the + * support functions in maxflow.c. + */ + + sq = snewn(o*o, digit); + + /* + * In case this method of generation introduces a really subtle + * top-to-bottom directional bias, we'll generate the rows in + * random order. + */ + row = snewn(o, digit); + col = snewn(o, digit); + numinv = snewn(o, digit); + num = snewn(o, digit); + for (i = 0; i < o; i++) + row[i] = i; + shuffle(row, i, sizeof(*row), rs); + + /* + * Set up the infrastructure for the maxflow algorithm. + */ + scratchsize = maxflow_scratch_size(o * 2 + 2); + scratch = smalloc(scratchsize); + backedges = snewn(o*o + 2*o, int); + edges = snewn((o*o + 2*o) * 2, int); + capacity = snewn(o*o + 2*o, int); + flow = snewn(o*o + 2*o, int); + /* Set up the edge array, and the initial capacities. */ + ne = 0; + for (i = 0; i < o; i++) { + /* Each LHS vertex is connected to all RHS vertices. */ + for (j = 0; j < o; j++) { + edges[ne*2] = i; + edges[ne*2+1] = j+o; + /* capacity for this edge is set later on */ + ne++; + } + } + for (i = 0; i < o; i++) { + /* Each RHS vertex is connected to the distinguished sink vertex. */ + edges[ne*2] = i+o; + edges[ne*2+1] = o*2+1; + capacity[ne] = 1; + ne++; + } + for (i = 0; i < o; i++) { + /* And the distinguished source vertex connects to each LHS vertex. */ + edges[ne*2] = o*2; + edges[ne*2+1] = i; + capacity[ne] = 1; + ne++; + } + assert(ne == o*o + 2*o); + /* Now set up backedges. */ + maxflow_setup_backedges(ne, edges, backedges); + + /* + * Now generate each row of the latin square. + */ + for (i = 0; i < o; i++) { + /* + * To prevent maxflow from behaving deterministically, we + * separately permute the columns and the digits for the + * purposes of the algorithm, differently for every row. + */ + for (j = 0; j < o; j++) + col[j] = num[j] = j; + shuffle(col, j, sizeof(*col), rs); + shuffle(num, j, sizeof(*num), rs); + /* We need the num permutation in both forward and inverse forms. */ + for (j = 0; j < o; j++) + numinv[num[j]] = j; + + /* + * Set up the capacities for the maxflow run, by examining + * the existing latin square. + */ + for (j = 0; j < o*o; j++) + capacity[j] = 1; + for (j = 0; j < i; j++) + for (k = 0; k < o; k++) { + int n = num[sq[row[j]*o + col[k]] - 1]; + capacity[k*o+n] = 0; + } + + /* + * Run maxflow. + */ + j = maxflow_with_scratch(scratch, o*2+2, 2*o, 2*o+1, ne, + edges, backedges, capacity, flow, NULL); + assert(j == o); /* by the above theorem, this must have succeeded */ + + /* + * And examine the flow array to pick out the new row of + * the latin square. + */ + for (j = 0; j < o; j++) { + for (k = 0; k < o; k++) { + if (flow[j*o+k]) + break; + } + assert(k < o); + sq[row[i]*o + col[j]] = numinv[k] + 1; + } + } + + /* + * Done. Free our internal workspaces... + */ + sfree(flow); + sfree(capacity); + sfree(edges); + sfree(backedges); + sfree(scratch); + sfree(numinv); + sfree(num); + sfree(col); + sfree(row); + + /* + * ... and return our completed latin square. + */ + return sq; +} + +digit *latin_generate_rect(int w, int h, random_state *rs) +{ + int o = max(w, h), x, y; + digit *latin, *latin_rect; + + latin = latin_generate(o, rs); + latin_rect = snewn(w*h, digit); + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + latin_rect[y*w + x] = latin[y*o + x]; + } + } + + sfree(latin); + return latin_rect; +} + +/* -------------------------------------------------------- + * Checking. + */ + +typedef struct lcparams { + digit elt; + int count; +} lcparams; + +static int latin_check_cmp(void *v1, void *v2) +{ + lcparams *lc1 = (lcparams *)v1; + lcparams *lc2 = (lcparams *)v2; + + if (lc1->elt < lc2->elt) return -1; + if (lc1->elt > lc2->elt) return 1; + return 0; +} + +#define ELT(sq,x,y) (sq[((y)*order)+(x)]) + +/* returns non-zero if sq is not a latin square. */ +int latin_check(digit *sq, int order) +{ + tree234 *dict = newtree234(latin_check_cmp); + int c, r; + int ret = 0; + lcparams *lcp, lc, *aret; + + /* Use a tree234 as a simple hash table, go through the square + * adding elements as we go or incrementing their counts. */ + for (c = 0; c < order; c++) { + for (r = 0; r < order; r++) { + lc.elt = ELT(sq, c, r); lc.count = 0; + lcp = find234(dict, &lc, NULL); + if (!lcp) { + lcp = snew(lcparams); + lcp->elt = ELT(sq, c, r); + lcp->count = 1; + aret = add234(dict, lcp); + assert(aret == lcp); + } else { + lcp->count++; + } + } + } + + /* There should be precisely 'order' letters in the alphabet, + * each occurring 'order' times (making the OxO tree) */ + if (count234(dict) != order) ret = 1; + else { + for (c = 0; (lcp = index234(dict, c)) != NULL; c++) { + if (lcp->count != order) ret = 1; + } + } + for (c = 0; (lcp = index234(dict, c)) != NULL; c++) + sfree(lcp); + freetree234(dict); + + return ret; +} + + +/* -------------------------------------------------------- + * Testing (and printing). + */ + +#ifdef STANDALONE_LATIN_TEST + +#include +#include + +const char *quis; + +static void latin_print(digit *sq, int order) +{ + int x, y; + + for (y = 0; y < order; y++) { + for (x = 0; x < order; x++) { + printf("%2u ", ELT(sq, x, y)); + } + printf("\n"); + } + printf("\n"); +} + +static void gen(int order, random_state *rs, int debug) +{ + digit *sq; + + solver_show_working = debug; + + sq = latin_generate(order, rs); + latin_print(sq, order); + if (latin_check(sq, order)) { + fprintf(stderr, "Square is not a latin square!"); + exit(1); + } + + sfree(sq); +} + +void test_soak(int order, random_state *rs) +{ + digit *sq; + int n = 0; + time_t tt_start, tt_now, tt_last; + + solver_show_working = 0; + tt_now = tt_start = time(NULL); + + while(1) { + sq = latin_generate(order, rs); + sfree(sq); + n++; + + tt_last = time(NULL); + if (tt_last > tt_now) { + tt_now = tt_last; + printf("%d total, %3.1f/s\n", n, + (double)n / (double)(tt_now - tt_start)); + } + } +} + +void usage_exit(const char *msg) +{ + if (msg) + fprintf(stderr, "%s: %s\n", quis, msg); + fprintf(stderr, "Usage: %s [--seed SEED] --soak | [game_id [game_id ...]]\n", quis); + exit(1); +} + +int main(int argc, char *argv[]) +{ + int i, soak = 0; + random_state *rs; + time_t seed = time(NULL); + + quis = argv[0]; + while (--argc > 0) { + const char *p = *++argv; + if (!strcmp(p, "--soak")) + soak = 1; + else if (!strcmp(p, "--seed")) { + if (argc == 0) + usage_exit("--seed needs an argument"); + seed = (time_t)atoi(*++argv); + argc--; + } else if (*p == '-') + usage_exit("unrecognised option"); + else + break; /* finished options */ + } + + rs = random_new((void*)&seed, sizeof(time_t)); + + if (soak == 1) { + if (argc != 1) usage_exit("only one argument for --soak"); + test_soak(atoi(*argv), rs); + } else { + if (argc > 0) { + for (i = 0; i < argc; i++) { + gen(atoi(*argv++), rs, 1); + } + } else { + while (1) { + i = random_upto(rs, 20) + 1; + gen(i, rs, 0); + } + } + } + random_free(rs); + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/latin.h b/apps/plugins/puzzles/latin.h new file mode 100644 index 0000000000..4b09f16ce1 --- /dev/null +++ b/apps/plugins/puzzles/latin.h @@ -0,0 +1,122 @@ +#ifndef LATIN_H +#define LATIN_H + +#include "puzzles.h" + +typedef unsigned char digit; + +/* --- Solver structures, definitions --- */ + +#ifdef STANDALONE_SOLVER +extern int solver_show_working, solver_recurse_depth; +#endif + +struct latin_solver { + int o; /* order of latin square */ + unsigned char *cube; /* o^3, indexed by x, y, and digit: + TRUE in that position indicates a possibility */ + digit *grid; /* o^2, indexed by x and y: for final deductions */ + + unsigned char *row; /* o^2: row[y*cr+n-1] TRUE if n is in row y */ + unsigned char *col; /* o^2: col[x*cr+n-1] TRUE if n is in col x */ + +#ifdef STANDALONE_SOLVER + char **names; /* o: names[n-1] gives name of 'digit' n */ +#endif +}; +#define cubepos(x,y,n) (((x)*solver->o+(y))*solver->o+(n)-1) +#define cube(x,y,n) (solver->cube[cubepos(x,y,n)]) + +#define gridpos(x,y) ((y)*solver->o+(x)) +#define grid(x,y) (solver->grid[gridpos(x,y)]) + + +/* --- Solver individual strategies --- */ + +/* Place a value at a specific location. */ +void latin_solver_place(struct latin_solver *solver, int x, int y, int n); + +/* Positional elimination. */ +int latin_solver_elim(struct latin_solver *solver, int start, int step +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ); + +struct latin_solver_scratch; /* private to latin.c */ +/* Set elimination */ +int latin_solver_set(struct latin_solver *solver, + struct latin_solver_scratch *scratch, + int start, int step1, int step2 +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ); + +/* Forcing chains */ +int latin_solver_forcing(struct latin_solver *solver, + struct latin_solver_scratch *scratch); + + +/* --- Solver allocation --- */ + +/* Fills in (and allocates members for) a latin_solver struct. + * Will allocate members of snew, but not snew itself + * (allowing 'struct latin_solver' to be the first element in a larger + * struct, for example). */ +void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o); +void latin_solver_free(struct latin_solver *solver); + +/* Allocates scratch space (for _set and _forcing) */ +struct latin_solver_scratch * + latin_solver_new_scratch(struct latin_solver *solver); +void latin_solver_free_scratch(struct latin_solver_scratch *scratch); + + +/* --- Solver guts --- */ + +/* Looped positional elimination */ +int latin_solver_diff_simple(struct latin_solver *solver); + +/* Looped set elimination; *extreme is set if it used + * the more difficult single-number elimination. */ +int latin_solver_diff_set(struct latin_solver *solver, + struct latin_solver_scratch *scratch, + int extreme); + +typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx); +typedef void *(*ctxnew_t)(void *ctx); +typedef void (*ctxfree_t)(void *ctx); + +/* Individual puzzles should use their enumerations for their + * own difficulty levels, ensuring they don't clash with these. */ +enum { diff_impossible = 10, diff_ambiguous, diff_unfinished }; + +/* Externally callable function that allocates and frees a latin_solver */ +int latin_solver(digit *grid, int o, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree); + +/* Version you can call if you want to alloc and free latin_solver yourself */ +int latin_solver_main(struct latin_solver *solver, int maxdiff, + int diff_simple, int diff_set_0, int diff_set_1, + int diff_forcing, int diff_recursive, + usersolver_t const *usersolvers, void *ctx, + ctxnew_t ctxnew, ctxfree_t ctxfree); + +void latin_solver_debug(unsigned char *cube, int o); + +/* --- Generation and checking --- */ + +digit *latin_generate(int o, random_state *rs); + +/* The order of the latin rectangle is max(w,h). */ +digit *latin_generate_rect(int w, int h, random_state *rs); + +int latin_check(digit *sq, int order); /* !0 => not a latin square */ + +void latin_debug(digit *sq, int order); + +#endif diff --git a/apps/plugins/puzzles/laydomino.c b/apps/plugins/puzzles/laydomino.c new file mode 100644 index 0000000000..458027020b --- /dev/null +++ b/apps/plugins/puzzles/laydomino.c @@ -0,0 +1,291 @@ +/* + * laydomino.c: code for performing a domino (2x1 tile) layout of + * a given area of code. + */ + +#include +#include +#include "rbassert.h" + +#include "puzzles.h" + +/* + * This function returns an array size w x h representing a grid: + * each grid[i] = j, where j is the other end of a 2x1 domino. + * If w*h is odd, one square will remain referring to itself. + */ + +int *domino_layout(int w, int h, random_state *rs) +{ + int *grid, *grid2, *list; + int wh = w*h; + + /* + * Allocate space in which to lay the grid out. + */ + grid = snewn(wh, int); + grid2 = snewn(wh, int); + list = snewn(2*wh, int); + + domino_layout_prealloc(w, h, rs, grid, grid2, list); + + sfree(grid2); + sfree(list); + + return grid; +} + +/* + * As for domino_layout, but with preallocated buffers. + * grid and grid2 should be size w*h, and list size 2*w*h. + */ +void domino_layout_prealloc(int w, int h, random_state *rs, + int *grid, int *grid2, int *list) +{ + int i, j, k, m, wh = w*h, todo, done; + + /* + * To begin with, set grid[i] = i for all i to indicate + * that all squares are currently singletons. Later we'll + * set grid[i] to be the index of the other end of the + * domino on i. + */ + for (i = 0; i < wh; i++) + grid[i] = i; + + /* + * Now prepare a list of the possible domino locations. There + * are w*(h-1) possible vertical locations, and (w-1)*h + * horizontal ones, for a total of 2*wh - h - w. + * + * I'm going to denote the vertical domino placement with + * its top in square i as 2*i, and the horizontal one with + * its left half in square i as 2*i+1. + */ + k = 0; + for (j = 0; j < h-1; j++) + for (i = 0; i < w; i++) + list[k++] = 2 * (j*w+i); /* vertical positions */ + for (j = 0; j < h; j++) + for (i = 0; i < w-1; i++) + list[k++] = 2 * (j*w+i) + 1; /* horizontal positions */ + assert(k == 2*wh - h - w); + + /* + * Shuffle the list. + */ + shuffle(list, k, sizeof(*list), rs); + + /* + * Work down the shuffled list, placing a domino everywhere + * we can. + */ + for (i = 0; i < k; i++) { + int horiz, xy, xy2; + + horiz = list[i] % 2; + xy = list[i] / 2; + xy2 = xy + (horiz ? 1 : w); + + if (grid[xy] == xy && grid[xy2] == xy2) { + /* + * We can place this domino. Do so. + */ + grid[xy] = xy2; + grid[xy2] = xy; + } + } + +#ifdef GENERATION_DIAGNOSTICS + printf("generated initial layout\n"); +#endif + + /* + * Now we've placed as many dominoes as we can immediately + * manage. There will be squares remaining, but they'll be + * singletons. So loop round and deal with the singletons + * two by two. + */ + while (1) { +#ifdef GENERATION_DIAGNOSTICS + for (j = 0; j < h; j++) { + for (i = 0; i < w; i++) { + int xy = j*w+i; + int v = grid[xy]; + int c = (v == xy+1 ? '[' : v == xy-1 ? ']' : + v == xy+w ? 'n' : v == xy-w ? 'U' : '.'); + putchar(c); + } + putchar('\n'); + } + putchar('\n'); +#endif + + /* + * Our strategy is: + * + * First find a singleton square. + * + * Then breadth-first search out from the starting + * square. From that square (and any others we reach on + * the way), examine all four neighbours of the square. + * If one is an end of a domino, we move to the _other_ + * end of that domino before looking at neighbours + * again. When we encounter another singleton on this + * search, stop. + * + * This will give us a path of adjacent squares such + * that all but the two ends are covered in dominoes. + * So we can now shuffle every domino on the path up by + * one. + * + * (Chessboard colours are mathematically important + * here: we always end up pairing each singleton with a + * singleton of the other colour. However, we never + * have to track this manually, since it's + * automatically taken care of by the fact that we + * always make an even number of orthogonal moves.) + */ + k = 0; + for (j = 0; j < wh; j++) { + if (grid[j] == j) { + k++; + i = j; /* start BFS here. */ + } + } + if (k == (wh % 2)) + break; /* if area is even, we have no more singletons; + if area is odd, we have one singleton. + either way, we're done. */ + +#ifdef GENERATION_DIAGNOSTICS + printf("starting b.f.s. at singleton %d\n", i); +#endif + /* + * Set grid2 to -1 everywhere. It will hold our + * distance-from-start values, and also our + * backtracking data, during the b.f.s. + */ + for (j = 0; j < wh; j++) + grid2[j] = -1; + grid2[i] = 0; /* starting square has distance zero */ + + /* + * Start our to-do list of squares. It'll live in + * `list'; since the b.f.s can cover every square at + * most once there is no need for it to be circular. + * We'll just have two counters tracking the end of the + * list and the squares we've already dealt with. + */ + done = 0; + todo = 1; + list[0] = i; + + /* + * Now begin the b.f.s. loop. + */ + while (done < todo) { + int d[4], nd, x, y; + + i = list[done++]; + +#ifdef GENERATION_DIAGNOSTICS + printf("b.f.s. iteration from %d\n", i); +#endif + x = i % w; + y = i / w; + nd = 0; + if (x > 0) + d[nd++] = i - 1; + if (x+1 < w) + d[nd++] = i + 1; + if (y > 0) + d[nd++] = i - w; + if (y+1 < h) + d[nd++] = i + w; + /* + * To avoid directional bias, process the + * neighbours of this square in a random order. + */ + shuffle(d, nd, sizeof(*d), rs); + + for (j = 0; j < nd; j++) { + k = d[j]; + if (grid[k] == k) { +#ifdef GENERATION_DIAGNOSTICS + printf("found neighbouring singleton %d\n", k); +#endif + grid2[k] = i; + break; /* found a target singleton! */ + } + + /* + * We're moving through a domino here, so we + * have two entries in grid2 to fill with + * useful data. In grid[k] - the square + * adjacent to where we came from - I'm going + * to put the address _of_ the square we came + * from. In the other end of the domino - the + * square from which we will continue the + * search - I'm going to put the distance. + */ + m = grid[k]; + + if (grid2[m] < 0 || grid2[m] > grid2[i]+1) { +#ifdef GENERATION_DIAGNOSTICS + printf("found neighbouring domino %d/%d\n", k, m); +#endif + grid2[m] = grid2[i]+1; + grid2[k] = i; + /* + * And since we've now visited a new + * domino, add m to the to-do list. + */ + assert(todo < wh); + list[todo++] = m; + } + } + + if (j < nd) { + i = k; +#ifdef GENERATION_DIAGNOSTICS + printf("terminating b.f.s. loop, i = %d\n", i); +#endif + break; + } + + i = -1; /* just in case the loop terminates */ + } + + /* + * We expect this b.f.s. to have found us a target + * square. + */ + assert(i >= 0); + + /* + * Now we can follow the trail back to our starting + * singleton, re-laying dominoes as we go. + */ + while (1) { + j = grid2[i]; + assert(j >= 0 && j < wh); + k = grid[j]; + + grid[i] = j; + grid[j] = i; +#ifdef GENERATION_DIAGNOSTICS + printf("filling in domino %d/%d (next %d)\n", i, j, k); +#endif + if (j == k) + break; /* we've reached the other singleton */ + i = k; + } +#ifdef GENERATION_DIAGNOSTICS + printf("fixup path completed\n"); +#endif + } +} + +/* vim: set shiftwidth=4 :set textwidth=80: */ + diff --git a/apps/plugins/puzzles/lightup.R b/apps/plugins/puzzles/lightup.R new file mode 100644 index 0000000000..a474de815d --- /dev/null +++ b/apps/plugins/puzzles/lightup.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +LIGHTUP_EXTRA = combi + +lightup : [X] GTK COMMON lightup LIGHTUP_EXTRA lightup-icon|no-icon + +lightup : [G] WINDOWS COMMON lightup LIGHTUP_EXTRA lightup.res|noicon.res + +lightupsolver : [U] lightup[STANDALONE_SOLVER] LIGHTUP_EXTRA STANDALONE +lightupsolver : [C] lightup[STANDALONE_SOLVER] LIGHTUP_EXTRA STANDALONE + +ALL += lightup[COMBINED] LIGHTUP_EXTRA + +!begin am gtk +GAMES += lightup +!end + +!begin >list.c + A(lightup) \ +!end + +!begin >gamedesc.txt +lightup:lightup.exe:Light Up:Light-bulb placing puzzle:Place bulbs to light up all the squares. +!end diff --git a/apps/plugins/puzzles/lightup.c b/apps/plugins/puzzles/lightup.c new file mode 100644 index 0000000000..9ca37b19d2 --- /dev/null +++ b/apps/plugins/puzzles/lightup.c @@ -0,0 +1,2405 @@ +/* + * lightup.c: Implementation of the Nikoli game 'Light Up'. + * + * Possible future solver enhancements: + * + * - In a situation where two clues are diagonally adjacent, you can + * deduce bounds on the number of lights shared between them. For + * instance, suppose a 3 clue is diagonally adjacent to a 1 clue: + * of the two squares adjacent to both clues, at least one must be + * a light (or the 3 would be unsatisfiable) and yet at most one + * must be a light (or the 1 would be overcommitted), so in fact + * _exactly_ one must be a light, and hence the other two squares + * adjacent to the 3 must also be lights and the other two adjacent + * to the 1 must not. Likewise if the 3 is replaced with a 2 but + * one of its other two squares is known not to be a light, and so + * on. + * + * - In a situation where two clues are orthogonally separated (not + * necessarily directly adjacent), you may be able to deduce + * something about the squares that align with each other. For + * instance, suppose two clues are vertically adjacent. Consider + * the pair of squares A,B horizontally adjacent to the top clue, + * and the pair C,D horizontally adjacent to the bottom clue. + * Assuming no intervening obstacles, A and C align with each other + * and hence at most one of them can be a light, and B and D + * likewise, so we must have at most two lights between the four + * squares. So if the clues indicate that there are at _least_ two + * lights in those four squares because the top clue requires at + * least one of AB to be a light and the bottom one requires at + * least one of CD, then we can in fact deduce that there are + * _exactly_ two lights between the four squares, and fill in the + * other squares adjacent to each clue accordingly. For instance, + * if both clues are 3s, then we instantly deduce that all four of + * the squares _vertically_ adjacent to the two clues must be + * lights. (For that to happen, of course, there'd also have to be + * a black square in between the clues, so the two inner lights + * don't light each other.) + * + * - I haven't thought it through carefully, but there's always the + * possibility that both of the above deductions are special cases + * of some more general pattern which can be made computationally + * feasible... + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +/* + * In standalone solver mode, `verbose' is a variable which can be + * set by command-line option; in debugging mode it's simply always + * true. + */ +#if defined STANDALONE_SOLVER +#define SOLVER_DIAGNOSTICS +int verbose = 0; +#undef debug +#define debug(x) printf x +#elif defined SOLVER_DIAGNOSTICS +#define verbose 2 +#endif + +/* --- Constants, structure definitions, etc. --- */ + +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 2) +#define TILE_RADIUS (ds->crad) + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define FLASH_TIME 0.30F + +enum { + COL_BACKGROUND, + COL_GRID, + COL_BLACK, /* black */ + COL_LIGHT, /* white */ + COL_LIT, /* yellow */ + COL_ERROR, /* red */ + COL_CURSOR, + NCOLOURS +}; + +enum { SYMM_NONE, SYMM_REF2, SYMM_ROT2, SYMM_REF4, SYMM_ROT4, SYMM_MAX }; + +#define DIFFCOUNT 2 + +struct game_params { + int w, h; + int blackpc; /* %age of black squares */ + int symm; + int difficulty; /* 0 to DIFFCOUNT */ +}; + +#define F_BLACK 1 + +/* flags for black squares */ +#define F_NUMBERED 2 /* it has a number attached */ +#define F_NUMBERUSED 4 /* this number was useful for solving */ + +/* flags for non-black squares */ +#define F_IMPOSSIBLE 8 /* can't put a light here */ +#define F_LIGHT 16 + +#define F_MARK 32 + +struct game_state { + int w, h, nlights; + int *lights; /* For black squares, (optionally) the number + of surrounding lights. For non-black squares, + the number of times it's lit. size h*w*/ + unsigned int *flags; /* size h*w */ + int completed, used_solve; +}; + +#define GRID(gs,grid,x,y) (gs->grid[(y)*((gs)->w) + (x)]) + +/* A ll_data holds information about which lights would be lit by + * a particular grid location's light (or conversely, which locations + * could light a specific other location). */ +/* most things should consider this struct opaque. */ +typedef struct { + int ox,oy; + int minx, maxx, miny, maxy; + int include_origin; +} ll_data; + +/* Macro that executes 'block' once per light in lld, including + * the origin if include_origin is specified. 'block' can use + * lx and ly as the coords. */ +#define FOREACHLIT(lld,block) do { \ + int lx,ly; \ + ly = (lld)->oy; \ + for (lx = (lld)->minx; lx <= (lld)->maxx; lx++) { \ + if (lx == (lld)->ox) continue; \ + block \ + } \ + lx = (lld)->ox; \ + for (ly = (lld)->miny; ly <= (lld)->maxy; ly++) { \ + if (!(lld)->include_origin && ly == (lld)->oy) continue; \ + block \ + } \ +} while(0) + + +typedef struct { + struct { int x, y; unsigned int f; } points[4]; + int npoints; +} surrounds; + +/* Fills in (doesn't allocate) a surrounds structure with the grid locations + * around a given square, taking account of the edges. */ +static void get_surrounds(const game_state *state, int ox, int oy, + surrounds *s) +{ + assert(ox >= 0 && ox < state->w && oy >= 0 && oy < state->h); + s->npoints = 0; +#define ADDPOINT(cond,nx,ny) do {\ + if (cond) { \ + s->points[s->npoints].x = (nx); \ + s->points[s->npoints].y = (ny); \ + s->points[s->npoints].f = 0; \ + s->npoints++; \ + } } while(0) + ADDPOINT(ox > 0, ox-1, oy); + ADDPOINT(ox < (state->w-1), ox+1, oy); + ADDPOINT(oy > 0, ox, oy-1); + ADDPOINT(oy < (state->h-1), ox, oy+1); +} + +/* --- Game parameter functions --- */ + +#define DEFAULT_PRESET 0 + +const struct game_params lightup_presets[] = { + { 7, 7, 20, SYMM_ROT4, 0 }, + { 7, 7, 20, SYMM_ROT4, 1 }, + { 7, 7, 20, SYMM_ROT4, 2 }, + { 10, 10, 20, SYMM_ROT2, 0 }, + { 10, 10, 20, SYMM_ROT2, 1 }, +#ifdef SLOW_SYSTEM + { 12, 12, 20, SYMM_ROT2, 0 }, + { 12, 12, 20, SYMM_ROT2, 1 }, +#else + { 10, 10, 20, SYMM_ROT2, 2 }, + { 14, 14, 20, SYMM_ROT2, 0 }, + { 14, 14, 20, SYMM_ROT2, 1 }, + { 14, 14, 20, SYMM_ROT2, 2 } +#endif +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + *ret = lightup_presets[DEFAULT_PRESET]; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(lightup_presets)) + return FALSE; + + ret = default_params(); + *ret = lightup_presets[i]; + *params = ret; + + sprintf(buf, "%dx%d %s", + ret->w, ret->h, + ret->difficulty == 2 ? "hard" : + ret->difficulty == 1 ? "tricky" : "easy"); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +#define EATNUM(x) do { \ + (x) = atoi(string); \ + while (*string && isdigit((unsigned char)*string)) string++; \ +} while(0) + +static void decode_params(game_params *params, char const *string) +{ + EATNUM(params->w); + if (*string == 'x') { + string++; + EATNUM(params->h); + } + if (*string == 'b') { + string++; + EATNUM(params->blackpc); + } + if (*string == 's') { + string++; + EATNUM(params->symm); + } else { + /* cope with user input such as '18x10' by ensuring symmetry + * is not selected by default to be incompatible with dimensions */ + if (params->symm == SYMM_ROT4 && params->w != params->h) + params->symm = SYMM_ROT2; + } + params->difficulty = 0; + /* cope with old params */ + if (*string == 'r') { + params->difficulty = 2; + string++; + } + if (*string == 'd') { + string++; + EATNUM(params->difficulty); + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[80]; + + if (full) { + sprintf(buf, "%dx%db%ds%dd%d", + params->w, params->h, params->blackpc, + params->symm, + params->difficulty); + } else { + sprintf(buf, "%dx%d", params->w, params->h); + } + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(6, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "%age of black squares"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->blackpc); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "Symmetry"; + ret[3].type = C_CHOICES; + ret[3].sval = ":None" + ":2-way mirror:2-way rotational" + ":4-way mirror:4-way rotational"; + ret[3].ival = params->symm; + + ret[4].name = "Difficulty"; + ret[4].type = C_CHOICES; + ret[4].sval = ":Easy:Tricky:Hard"; + ret[4].ival = params->difficulty; + + ret[5].name = NULL; + ret[5].type = C_END; + ret[5].sval = NULL; + ret[5].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->blackpc = atoi(cfg[2].sval); + ret->symm = cfg[3].ival; + ret->difficulty = cfg[4].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 2 || params->h < 2) + return "Width and height must be at least 2"; + if (full) { + if (params->blackpc < 5 || params->blackpc > 100) + return "Percentage of black squares must be between 5% and 100%"; + if (params->w != params->h) { + if (params->symm == SYMM_ROT4) + return "4-fold symmetry is only available with square grids"; + } + if (params->symm < 0 || params->symm >= SYMM_MAX) + return "Unknown symmetry type"; + if (params->difficulty < 0 || params->difficulty > DIFFCOUNT) + return "Unknown difficulty level"; + } + return NULL; +} + +/* --- Game state construction/freeing helper functions --- */ + +static game_state *new_state(const game_params *params) +{ + game_state *ret = snew(game_state); + + ret->w = params->w; + ret->h = params->h; + ret->lights = snewn(ret->w * ret->h, int); + ret->nlights = 0; + memset(ret->lights, 0, ret->w * ret->h * sizeof(int)); + ret->flags = snewn(ret->w * ret->h, unsigned int); + memset(ret->flags, 0, ret->w * ret->h * sizeof(unsigned int)); + ret->completed = ret->used_solve = 0; + return ret; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + + ret->lights = snewn(ret->w * ret->h, int); + memcpy(ret->lights, state->lights, ret->w * ret->h * sizeof(int)); + ret->nlights = state->nlights; + + ret->flags = snewn(ret->w * ret->h, unsigned int); + memcpy(ret->flags, state->flags, ret->w * ret->h * sizeof(unsigned int)); + + ret->completed = state->completed; + ret->used_solve = state->used_solve; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->lights); + sfree(state->flags); + sfree(state); +} + +static void debug_state(game_state *state) +{ + int x, y; + char c = '?'; + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + c = '.'; + if (GRID(state, flags, x, y) & F_BLACK) { + if (GRID(state, flags, x, y) & F_NUMBERED) + c = GRID(state, lights, x, y) + '0'; + else + c = '#'; + } else { + if (GRID(state, flags, x, y) & F_LIGHT) + c = 'O'; + else if (GRID(state, flags, x, y) & F_IMPOSSIBLE) + c = 'X'; + } + debug(("%c", (int)c)); + } + debug((" ")); + for (x = 0; x < state->w; x++) { + if (GRID(state, flags, x, y) & F_BLACK) + c = '#'; + else { + c = (GRID(state, flags, x, y) & F_LIGHT) ? 'A' : 'a'; + c += GRID(state, lights, x, y); + } + debug(("%c", (int)c)); + } + debug(("\n")); + } +} + +/* --- Game completion test routines. --- */ + +/* These are split up because occasionally functions are only + * interested in one particular aspect. */ + +/* Returns non-zero if all grid spaces are lit. */ +static int grid_lit(game_state *state) +{ + int x, y; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (GRID(state,flags,x,y) & F_BLACK) continue; + if (GRID(state,lights,x,y) == 0) + return 0; + } + } + return 1; +} + +/* Returns non-zero if any lights are lit by other lights. */ +static int grid_overlap(game_state *state) +{ + int x, y; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (!(GRID(state, flags, x, y) & F_LIGHT)) continue; + if (GRID(state, lights, x, y) > 1) + return 1; + } + } + return 0; +} + +static int number_wrong(const game_state *state, int x, int y) +{ + surrounds s; + int i, n, empty, lights = GRID(state, lights, x, y); + + /* + * This function computes the display hint for a number: we + * turn the number red if it is definitely wrong. This means + * that either + * + * (a) it has too many lights around it, or + * (b) it would have too few lights around it even if all the + * plausible squares (not black, lit or F_IMPOSSIBLE) were + * filled with lights. + */ + + assert(GRID(state, flags, x, y) & F_NUMBERED); + get_surrounds(state, x, y, &s); + + empty = n = 0; + for (i = 0; i < s.npoints; i++) { + if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_LIGHT) { + n++; + continue; + } + if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_BLACK) + continue; + if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_IMPOSSIBLE) + continue; + if (GRID(state,lights,s.points[i].x,s.points[i].y)) + continue; + empty++; + } + return (n > lights || (n + empty < lights)); +} + +static int number_correct(game_state *state, int x, int y) +{ + surrounds s; + int n = 0, i, lights = GRID(state, lights, x, y); + + assert(GRID(state, flags, x, y) & F_NUMBERED); + get_surrounds(state, x, y, &s); + for (i = 0; i < s.npoints; i++) { + if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_LIGHT) + n++; + } + return (n == lights) ? 1 : 0; +} + +/* Returns non-zero if any numbers add up incorrectly. */ +static int grid_addsup(game_state *state) +{ + int x, y; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (!(GRID(state, flags, x, y) & F_NUMBERED)) continue; + if (!number_correct(state, x, y)) return 0; + } + } + return 1; +} + +static int grid_correct(game_state *state) +{ + if (grid_lit(state) && + !grid_overlap(state) && + grid_addsup(state)) return 1; + return 0; +} + +/* --- Board initial setup (blacks, lights, numbers) --- */ + +static void clean_board(game_state *state, int leave_blacks) +{ + int x,y; + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (leave_blacks) + GRID(state, flags, x, y) &= F_BLACK; + else + GRID(state, flags, x, y) = 0; + GRID(state, lights, x, y) = 0; + } + } + state->nlights = 0; +} + +static void set_blacks(game_state *state, const game_params *params, + random_state *rs) +{ + int x, y, degree = 0, rotate = 0, nblack; + int rh, rw, i; + int wodd = (state->w % 2) ? 1 : 0; + int hodd = (state->h % 2) ? 1 : 0; + int xs[4], ys[4]; + + switch (params->symm) { + case SYMM_NONE: degree = 1; rotate = 0; break; + case SYMM_ROT2: degree = 2; rotate = 1; break; + case SYMM_REF2: degree = 2; rotate = 0; break; + case SYMM_ROT4: degree = 4; rotate = 1; break; + case SYMM_REF4: degree = 4; rotate = 0; break; + default: assert(!"Unknown symmetry type"); + } + if (params->symm == SYMM_ROT4 && (state->h != state->w)) + assert(!"4-fold symmetry unavailable without square grid"); + + if (degree == 4) { + rw = state->w/2; + rh = state->h/2; + if (!rotate) rw += wodd; /* ... but see below. */ + rh += hodd; + } else if (degree == 2) { + rw = state->w; + rh = state->h/2; + rh += hodd; + } else { + rw = state->w; + rh = state->h; + } + + /* clear, then randomise, required region. */ + clean_board(state, 0); + nblack = (rw * rh * params->blackpc) / 100; + for (i = 0; i < nblack; i++) { + do { + x = random_upto(rs,rw); + y = random_upto(rs,rh); + } while (GRID(state,flags,x,y) & F_BLACK); + GRID(state, flags, x, y) |= F_BLACK; + } + + /* Copy required region. */ + if (params->symm == SYMM_NONE) return; + + for (x = 0; x < rw; x++) { + for (y = 0; y < rh; y++) { + if (degree == 4) { + xs[0] = x; + ys[0] = y; + xs[1] = state->w - 1 - (rotate ? y : x); + ys[1] = rotate ? x : y; + xs[2] = rotate ? (state->w - 1 - x) : x; + ys[2] = state->h - 1 - y; + xs[3] = rotate ? y : (state->w - 1 - x); + ys[3] = state->h - 1 - (rotate ? x : y); + } else { + xs[0] = x; + ys[0] = y; + xs[1] = rotate ? (state->w - 1 - x) : x; + ys[1] = state->h - 1 - y; + } + for (i = 1; i < degree; i++) { + GRID(state, flags, xs[i], ys[i]) = + GRID(state, flags, xs[0], ys[0]); + } + } + } + /* SYMM_ROT4 misses the middle square above; fix that here. */ + if (degree == 4 && rotate && wodd && + (random_upto(rs,100) <= (unsigned int)params->blackpc)) + GRID(state,flags, + state->w/2 + wodd - 1, state->h/2 + hodd - 1) |= F_BLACK; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) debug_state(state); +#endif +} + +/* Fills in (does not allocate) a ll_data with all the tiles that would + * be illuminated by a light at point (ox,oy). If origin=1 then the + * origin is included in this list. */ +static void list_lights(game_state *state, int ox, int oy, int origin, + ll_data *lld) +{ + int x,y; + + lld->ox = lld->minx = lld->maxx = ox; + lld->oy = lld->miny = lld->maxy = oy; + lld->include_origin = origin; + + y = oy; + for (x = ox-1; x >= 0; x--) { + if (GRID(state, flags, x, y) & F_BLACK) break; + if (x < lld->minx) lld->minx = x; + } + for (x = ox+1; x < state->w; x++) { + if (GRID(state, flags, x, y) & F_BLACK) break; + if (x > lld->maxx) lld->maxx = x; + } + + x = ox; + for (y = oy-1; y >= 0; y--) { + if (GRID(state, flags, x, y) & F_BLACK) break; + if (y < lld->miny) lld->miny = y; + } + for (y = oy+1; y < state->h; y++) { + if (GRID(state, flags, x, y) & F_BLACK) break; + if (y > lld->maxy) lld->maxy = y; + } +} + +/* Makes sure a light is the given state, editing the lights table to suit the + * new state if necessary. */ +static void set_light(game_state *state, int ox, int oy, int on) +{ + ll_data lld; + int diff = 0; + + assert(!(GRID(state,flags,ox,oy) & F_BLACK)); + + if (!on && GRID(state,flags,ox,oy) & F_LIGHT) { + diff = -1; + GRID(state,flags,ox,oy) &= ~F_LIGHT; + state->nlights--; + } else if (on && !(GRID(state,flags,ox,oy) & F_LIGHT)) { + diff = 1; + GRID(state,flags,ox,oy) |= F_LIGHT; + state->nlights++; + } + + if (diff != 0) { + list_lights(state,ox,oy,1,&lld); + FOREACHLIT(&lld, GRID(state,lights,lx,ly) += diff; ); + } +} + +/* Returns 1 if removing a light at (x,y) would cause a square to go dark. */ +static int check_dark(game_state *state, int x, int y) +{ + ll_data lld; + + list_lights(state, x, y, 1, &lld); + FOREACHLIT(&lld, if (GRID(state,lights,lx,ly) == 1) { return 1; } ); + return 0; +} + +/* Sets up an initial random correct position (i.e. every + * space lit, and no lights lit by other lights) by filling the + * grid with lights and then removing lights one by one at random. */ +static void place_lights(game_state *state, random_state *rs) +{ + int i, x, y, n, *numindices, wh = state->w*state->h; + ll_data lld; + + numindices = snewn(wh, int); + for (i = 0; i < wh; i++) numindices[i] = i; + shuffle(numindices, wh, sizeof(*numindices), rs); + + /* Place a light on all grid squares without lights. */ + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + GRID(state, flags, x, y) &= ~F_MARK; /* we use this later. */ + if (GRID(state, flags, x, y) & F_BLACK) continue; + set_light(state, x, y, 1); + } + } + + for (i = 0; i < wh; i++) { + y = numindices[i] / state->w; + x = numindices[i] % state->w; + if (!(GRID(state, flags, x, y) & F_LIGHT)) continue; + if (GRID(state, flags, x, y) & F_MARK) continue; + list_lights(state, x, y, 0, &lld); + + /* If we're not lighting any lights ourself, don't remove anything. */ + n = 0; + FOREACHLIT(&lld, if (GRID(state,flags,lx,ly) & F_LIGHT) { n += 1; } ); + if (n == 0) continue; /* [1] */ + + /* Check whether removing lights we're lighting would cause anything + * to go dark. */ + n = 0; + FOREACHLIT(&lld, if (GRID(state,flags,lx,ly) & F_LIGHT) { n += check_dark(state,lx,ly); } ); + if (n == 0) { + /* No, it wouldn't, so we can remove them all. */ + FOREACHLIT(&lld, set_light(state,lx,ly, 0); ); + GRID(state,flags,x,y) |= F_MARK; + } + + if (!grid_overlap(state)) { + sfree(numindices); + return; /* we're done. */ + } + assert(grid_lit(state)); + } + /* could get here if the line at [1] continue'd out of the loop. */ + if (grid_overlap(state)) { + debug_state(state); + assert(!"place_lights failed to resolve overlapping lights!"); + } + sfree(numindices); +} + +/* Fills in all black squares with numbers of adjacent lights. */ +static void place_numbers(game_state *state) +{ + int x, y, i, n; + surrounds s; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (!(GRID(state,flags,x,y) & F_BLACK)) continue; + get_surrounds(state, x, y, &s); + n = 0; + for (i = 0; i < s.npoints; i++) { + if (GRID(state,flags,s.points[i].x, s.points[i].y) & F_LIGHT) + n++; + } + GRID(state,flags,x,y) |= F_NUMBERED; + GRID(state,lights,x,y) = n; + } + } +} + +/* --- Actual solver, with helper subroutines. --- */ + +static void tsl_callback(game_state *state, + int lx, int ly, int *x, int *y, int *n) +{ + if (GRID(state,flags,lx,ly) & F_IMPOSSIBLE) return; + if (GRID(state,lights,lx,ly) > 0) return; + *x = lx; *y = ly; (*n)++; +} + +static int try_solve_light(game_state *state, int ox, int oy, + unsigned int flags, int lights) +{ + ll_data lld; + int sx = 0, sy = 0, n = 0; + + if (lights > 0) return 0; + if (flags & F_BLACK) return 0; + + /* We have an unlit square; count how many ways there are left to + * place a light that lights us (including this square); if only + * one, we must put a light there. Squares that could light us + * are, of course, the same as the squares we would light... */ + list_lights(state, ox, oy, 1, &lld); + FOREACHLIT(&lld, { tsl_callback(state, lx, ly, &sx, &sy, &n); }); + if (n == 1) { + set_light(state, sx, sy, 1); +#ifdef SOLVER_DIAGNOSTICS + debug(("(%d,%d) can only be lit from (%d,%d); setting to LIGHT\n", + ox,oy,sx,sy)); + if (verbose) debug_state(state); +#endif + return 1; + } + + return 0; +} + +static int could_place_light(unsigned int flags, int lights) +{ + if (flags & (F_BLACK | F_IMPOSSIBLE)) return 0; + return (lights > 0) ? 0 : 1; +} + +static int could_place_light_xy(game_state *state, int x, int y) +{ + int lights = GRID(state,lights,x,y); + unsigned int flags = GRID(state,flags,x,y); + return (could_place_light(flags, lights)) ? 1 : 0; +} + +/* For a given number square, determine whether we have enough info + * to unambiguously place its lights. */ +static int try_solve_number(game_state *state, int nx, int ny, + unsigned int nflags, int nlights) +{ + surrounds s; + int x, y, nl, ns, i, ret = 0, lights; + unsigned int flags; + + if (!(nflags & F_NUMBERED)) return 0; + nl = nlights; + get_surrounds(state,nx,ny,&s); + ns = s.npoints; + + /* nl is no. of lights we need to place, ns is no. of spaces we + * have to place them in. Try and narrow these down, and mark + * points we can ignore later. */ + for (i = 0; i < s.npoints; i++) { + x = s.points[i].x; y = s.points[i].y; + flags = GRID(state,flags,x,y); + lights = GRID(state,lights,x,y); + if (flags & F_LIGHT) { + /* light here already; one less light for one less place. */ + nl--; ns--; + s.points[i].f |= F_MARK; + } else if (!could_place_light(flags, lights)) { + ns--; + s.points[i].f |= F_MARK; + } + } + if (ns == 0) return 0; /* nowhere to put anything. */ + if (nl == 0) { + /* we have placed all lights we need to around here; all remaining + * surrounds are therefore IMPOSSIBLE. */ + GRID(state,flags,nx,ny) |= F_NUMBERUSED; + for (i = 0; i < s.npoints; i++) { + if (!(s.points[i].f & F_MARK)) { + GRID(state,flags,s.points[i].x,s.points[i].y) |= F_IMPOSSIBLE; + ret = 1; + } + } +#ifdef SOLVER_DIAGNOSTICS + printf("Clue at (%d,%d) full; setting unlit to IMPOSSIBLE.\n", + nx,ny); + if (verbose) debug_state(state); +#endif + } else if (nl == ns) { + /* we have as many lights to place as spaces; fill them all. */ + GRID(state,flags,nx,ny) |= F_NUMBERUSED; + for (i = 0; i < s.npoints; i++) { + if (!(s.points[i].f & F_MARK)) { + set_light(state, s.points[i].x,s.points[i].y, 1); + ret = 1; + } + } +#ifdef SOLVER_DIAGNOSTICS + printf("Clue at (%d,%d) trivial; setting unlit to LIGHT.\n", + nx,ny); + if (verbose) debug_state(state); +#endif + } + return ret; +} + +struct setscratch { + int x, y; + int n; +}; + +#define SCRATCHSZ (state->w+state->h) + +/* New solver algorithm: overlapping sets can add IMPOSSIBLE flags. + * Algorithm thanks to Simon: + * + * (a) Any square where you can place a light has a set of squares + * which would become non-lights as a result. (This includes + * squares lit by the first square, and can also include squares + * adjacent to the same clue square if the new light is the last + * one around that clue.) Call this MAKESDARK(x,y) with (x,y) being + * the square you place a light. + + * (b) Any unlit square has a set of squares on which you could place + * a light to illuminate it. (Possibly including itself, of + * course.) This set of squares has the property that _at least + * one_ of them must contain a light. Sets of this type also arise + * from clue squares. Call this MAKESLIGHT(x,y), again with (x,y) + * the square you would place a light. + + * (c) If there exists (dx,dy) and (lx,ly) such that MAKESDARK(dx,dy) is + * a superset of MAKESLIGHT(lx,ly), this implies that placing a light at + * (dx,dy) would either leave no remaining way to illuminate a certain + * square, or would leave no remaining way to fulfill a certain clue + * (at lx,ly). In either case, a light can be ruled out at that position. + * + * So, we construct all possible MAKESLIGHT sets, both from unlit squares + * and clue squares, and then we look for plausible MAKESDARK sets that include + * our (lx,ly) to see if we can find a (dx,dy) to rule out. By the time we have + * constructed the MAKESLIGHT set we don't care about (lx,ly), just the set + * members. + * + * Once we have such a set, Simon came up with a Cunning Plan to find + * the most sensible MAKESDARK candidate: + * + * (a) for each square S in your set X, find all the squares which _would_ + * rule it out. That means any square which would light S, plus + * any square adjacent to the same clue square as S (provided + * that clue square has only one remaining light to be placed). + * It's not hard to make this list. Don't do anything with this + * data at the moment except _count_ the squares. + + * (b) Find the square S_min in the original set which has the + * _smallest_ number of other squares which would rule it out. + + * (c) Find all the squares that rule out S_min (it's probably + * better to recompute this than to have stored it during step + * (a), since the CPU requirement is modest but the storage + * cost would get ugly.) For each of these squares, see if it + * rules out everything else in the set X. Any which does can + * be marked as not-a-light. + * + */ + +typedef void (*trl_cb)(game_state *state, int dx, int dy, + struct setscratch *scratch, int n, void *ctx); + +static void try_rule_out(game_state *state, int x, int y, + struct setscratch *scratch, int n, + trl_cb cb, void *ctx); + +static void trl_callback_search(game_state *state, int dx, int dy, + struct setscratch *scratch, int n, void *ignored) +{ + int i; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) debug(("discount cb: light at (%d,%d)\n", dx, dy)); +#endif + + for (i = 0; i < n; i++) { + if (dx == scratch[i].x && dy == scratch[i].y) { + scratch[i].n = 1; + return; + } + } +} + +static void trl_callback_discount(game_state *state, int dx, int dy, + struct setscratch *scratch, int n, void *ctx) +{ + int *didsth = (int *)ctx; + int i; + + if (GRID(state,flags,dx,dy) & F_IMPOSSIBLE) { +#ifdef SOLVER_DIAGNOSTICS + debug(("Square at (%d,%d) already impossible.\n", dx,dy)); +#endif + return; + } + + /* Check whether a light at (dx,dy) rules out everything + * in scratch, and mark (dx,dy) as IMPOSSIBLE if it does. + * We can use try_rule_out for this as well, as the set of + * squares which would rule out (x,y) is the same as the + * set of squares which (x,y) would rule out. */ + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) debug(("Checking whether light at (%d,%d) rules out everything in scratch.\n", dx, dy)); +#endif + + for (i = 0; i < n; i++) + scratch[i].n = 0; + try_rule_out(state, dx, dy, scratch, n, trl_callback_search, NULL); + for (i = 0; i < n; i++) { + if (scratch[i].n == 0) return; + } + /* The light ruled out everything in scratch. Yay. */ + GRID(state,flags,dx,dy) |= F_IMPOSSIBLE; +#ifdef SOLVER_DIAGNOSTICS + debug(("Set reduction discounted square at (%d,%d):\n", dx,dy)); + if (verbose) debug_state(state); +#endif + + *didsth = 1; +} + +static void trl_callback_incn(game_state *state, int dx, int dy, + struct setscratch *scratch, int n, void *ctx) +{ + struct setscratch *s = (struct setscratch *)ctx; + s->n++; +} + +static void try_rule_out(game_state *state, int x, int y, + struct setscratch *scratch, int n, + trl_cb cb, void *ctx) +{ + /* XXX Find all the squares which would rule out (x,y); anything + * that would light it as well as squares adjacent to same clues + * as X assuming that clue only has one remaining light. + * Call the callback with each square. */ + ll_data lld; + surrounds s, ss; + int i, j, curr_lights, tot_lights; + + /* Find all squares that would rule out a light at (x,y) and call trl_cb + * with them: anything that would light (x,y)... */ + + list_lights(state, x, y, 0, &lld); + FOREACHLIT(&lld, { if (could_place_light_xy(state, lx, ly)) { cb(state, lx, ly, scratch, n, ctx); } }); + + /* ... as well as any empty space (that isn't x,y) next to any clue square + * next to (x,y) that only has one light left to place. */ + + get_surrounds(state, x, y, &s); + for (i = 0; i < s.npoints; i++) { + if (!(GRID(state,flags,s.points[i].x,s.points[i].y) & F_NUMBERED)) + continue; + /* we have an adjacent clue square; find /its/ surrounds + * and count the remaining lights it needs. */ + get_surrounds(state,s.points[i].x,s.points[i].y,&ss); + curr_lights = 0; + for (j = 0; j < ss.npoints; j++) { + if (GRID(state,flags,ss.points[j].x,ss.points[j].y) & F_LIGHT) + curr_lights++; + } + tot_lights = GRID(state, lights, s.points[i].x, s.points[i].y); + /* We have a clue with tot_lights to fill, and curr_lights currently + * around it. If adding a light at (x,y) fills up the clue (i.e. + * curr_lights + 1 = tot_lights) then we need to discount all other + * unlit squares around the clue. */ + if ((curr_lights + 1) == tot_lights) { + for (j = 0; j < ss.npoints; j++) { + int lx = ss.points[j].x, ly = ss.points[j].y; + if (lx == x && ly == y) continue; + if (could_place_light_xy(state, lx, ly)) + cb(state, lx, ly, scratch, n, ctx); + } + } + } +} + +#ifdef SOLVER_DIAGNOSTICS +static void debug_scratch(const char *msg, struct setscratch *scratch, int n) +{ + int i; + debug(("%s scratch (%d elements):\n", msg, n)); + for (i = 0; i < n; i++) { + debug((" (%d,%d) n%d\n", scratch[i].x, scratch[i].y, scratch[i].n)); + } +} +#endif + +static int discount_set(game_state *state, + struct setscratch *scratch, int n) +{ + int i, besti, bestn, didsth = 0; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose > 1) debug_scratch("discount_set", scratch, n); +#endif + if (n == 0) return 0; + + for (i = 0; i < n; i++) { + try_rule_out(state, scratch[i].x, scratch[i].y, scratch, n, + trl_callback_incn, (void*)&(scratch[i])); + } +#ifdef SOLVER_DIAGNOSTICS + if (verbose > 1) debug_scratch("discount_set after count", scratch, n); +#endif + + besti = -1; bestn = SCRATCHSZ; + for (i = 0; i < n; i++) { + if (scratch[i].n < bestn) { + bestn = scratch[i].n; + besti = i; + } + } +#ifdef SOLVER_DIAGNOSTICS + if (verbose > 1) debug(("best square (%d,%d) with n%d.\n", + scratch[besti].x, scratch[besti].y, scratch[besti].n)); +#endif + try_rule_out(state, scratch[besti].x, scratch[besti].y, scratch, n, + trl_callback_discount, (void*)&didsth); +#ifdef SOLVER_DIAGNOSTICS + if (didsth) debug((" [from square (%d,%d)]\n", + scratch[besti].x, scratch[besti].y)); +#endif + + return didsth; +} + +static void discount_clear(game_state *state, struct setscratch *scratch, int *n) +{ + *n = 0; + memset(scratch, 0, SCRATCHSZ * sizeof(struct setscratch)); +} + +static void unlit_cb(game_state *state, int lx, int ly, + struct setscratch *scratch, int *n) +{ + if (could_place_light_xy(state, lx, ly)) { + scratch[*n].x = lx; scratch[*n].y = ly; (*n)++; + } +} + +/* Construct a MAKESLIGHT set from an unlit square. */ +static int discount_unlit(game_state *state, int x, int y, + struct setscratch *scratch) +{ + ll_data lld; + int n, didsth; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) debug(("Trying to discount for unlit square at (%d,%d).\n", x, y)); + if (verbose > 1) debug_state(state); +#endif + + discount_clear(state, scratch, &n); + + list_lights(state, x, y, 1, &lld); + FOREACHLIT(&lld, { unlit_cb(state, lx, ly, scratch, &n); }); + didsth = discount_set(state, scratch, n); +#ifdef SOLVER_DIAGNOSTICS + if (didsth) debug((" [from unlit square at (%d,%d)].\n", x, y)); +#endif + return didsth; + +} + +/* Construct a series of MAKESLIGHT sets from a clue square. + * for a clue square with N remaining spaces that must contain M lights, every + * subset of size N-M+1 of those N spaces forms such a set. + */ + +static int discount_clue(game_state *state, int x, int y, + struct setscratch *scratch) +{ + int slen, m = GRID(state, lights, x, y), n, i, didsth = 0, lights; + unsigned int flags; + surrounds s, sempty; + combi_ctx *combi; + + if (m == 0) return 0; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) debug(("Trying to discount for sets at clue (%d,%d).\n", x, y)); + if (verbose > 1) debug_state(state); +#endif + + /* m is no. of lights still to place; starts off at the clue value + * and decreases when we find a light already down. + * n is no. of spaces left; starts off at 0 and goes up when we find + * a plausible space. */ + + get_surrounds(state, x, y, &s); + memset(&sempty, 0, sizeof(surrounds)); + for (i = 0; i < s.npoints; i++) { + int lx = s.points[i].x, ly = s.points[i].y; + flags = GRID(state,flags,lx,ly); + lights = GRID(state,lights,lx,ly); + + if (flags & F_LIGHT) m--; + + if (could_place_light(flags, lights)) { + sempty.points[sempty.npoints].x = lx; + sempty.points[sempty.npoints].y = ly; + sempty.npoints++; + } + } + n = sempty.npoints; /* sempty is now a surrounds of only blank squares. */ + if (n == 0) return 0; /* clue is full already. */ + + if (m < 0 || m > n) return 0; /* become impossible. */ + + combi = new_combi(n - m + 1, n); + while (next_combi(combi)) { + discount_clear(state, scratch, &slen); + for (i = 0; i < combi->r; i++) { + scratch[slen].x = sempty.points[combi->a[i]].x; + scratch[slen].y = sempty.points[combi->a[i]].y; + slen++; + } + if (discount_set(state, scratch, slen)) didsth = 1; + } + free_combi(combi); +#ifdef SOLVER_DIAGNOSTICS + if (didsth) debug((" [from clue at (%d,%d)].\n", x, y)); +#endif + return didsth; +} + +#define F_SOLVE_FORCEUNIQUE 1 +#define F_SOLVE_DISCOUNTSETS 2 +#define F_SOLVE_ALLOWRECURSE 4 + +static unsigned int flags_from_difficulty(int difficulty) +{ + unsigned int sflags = F_SOLVE_FORCEUNIQUE; + assert(difficulty <= DIFFCOUNT); + if (difficulty >= 1) sflags |= F_SOLVE_DISCOUNTSETS; + if (difficulty >= 2) sflags |= F_SOLVE_ALLOWRECURSE; + return sflags; +} + +#define MAXRECURSE 5 + +static int solve_sub(game_state *state, + unsigned int solve_flags, int depth, + int *maxdepth) +{ + unsigned int flags; + int x, y, didstuff, ncanplace, lights; + int bestx, besty, n, bestn, copy_soluble, self_soluble, ret, maxrecurse = 0; + game_state *scopy; + ll_data lld; + struct setscratch *sscratch = NULL; + +#ifdef SOLVER_DIAGNOSTICS + printf("solve_sub: depth = %d\n", depth); +#endif + if (maxdepth && *maxdepth < depth) *maxdepth = depth; + if (solve_flags & F_SOLVE_ALLOWRECURSE) maxrecurse = MAXRECURSE; + + while (1) { + if (grid_overlap(state)) { + /* Our own solver, from scratch, should never cause this to happen + * (assuming a soluble grid). However, if we're trying to solve + * from a half-completed *incorrect* grid this might occur; we + * just return the 'no solutions' code in this case. */ + ret = 0; goto done; + } + + if (grid_correct(state)) { ret = 1; goto done; } + + ncanplace = 0; + didstuff = 0; + /* These 2 loops, and the functions they call, are the critical loops + * for timing; any optimisations should look here first. */ + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + flags = GRID(state,flags,x,y); + lights = GRID(state,lights,x,y); + ncanplace += could_place_light(flags, lights); + + if (try_solve_light(state, x, y, flags, lights)) didstuff = 1; + if (try_solve_number(state, x, y, flags, lights)) didstuff = 1; + } + } + if (didstuff) continue; + if (!ncanplace) { + /* nowhere to put a light, puzzle is unsoluble. */ + ret = 0; goto done; + } + + if (solve_flags & F_SOLVE_DISCOUNTSETS) { + if (!sscratch) sscratch = snewn(SCRATCHSZ, struct setscratch); + /* Try a more cunning (and more involved) way... more details above. */ + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + flags = GRID(state,flags,x,y); + lights = GRID(state,lights,x,y); + + if (!(flags & F_BLACK) && lights == 0) { + if (discount_unlit(state, x, y, sscratch)) { + didstuff = 1; + goto reduction_success; + } + } else if (flags & F_NUMBERED) { + if (discount_clue(state, x, y, sscratch)) { + didstuff = 1; + goto reduction_success; + } + } + } + } + } +reduction_success: + if (didstuff) continue; + + /* We now have to make a guess; we have places to put lights but + * no definite idea about where they can go. */ + if (depth >= maxrecurse) { + /* mustn't delve any deeper. */ + ret = -1; goto done; + } + /* Of all the squares that we could place a light, pick the one + * that would light the most currently unlit squares. */ + /* This heuristic was just plucked from the air; there may well be + * a more efficient way of choosing a square to flip to minimise + * recursion. */ + bestn = 0; + bestx = besty = -1; /* suyb */ + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + flags = GRID(state,flags,x,y); + lights = GRID(state,lights,x,y); + if (!could_place_light(flags, lights)) continue; + + n = 0; + list_lights(state, x, y, 1, &lld); + FOREACHLIT(&lld, { if (GRID(state,lights,lx,ly) == 0) n++; }); + if (n > bestn) { + bestn = n; bestx = x; besty = y; + } + } + } + assert(bestn > 0); + assert(bestx >= 0 && besty >= 0); + + /* Now we've chosen a plausible (x,y), try to solve it once as 'lit' + * and once as 'impossible'; we need to make one copy to do this. */ + + scopy = dup_game(state); +#ifdef SOLVER_DIAGNOSTICS + debug(("Recursing #1: trying (%d,%d) as IMPOSSIBLE\n", bestx, besty)); +#endif + GRID(state,flags,bestx,besty) |= F_IMPOSSIBLE; + self_soluble = solve_sub(state, solve_flags, depth+1, maxdepth); + + if (!(solve_flags & F_SOLVE_FORCEUNIQUE) && self_soluble > 0) { + /* we didn't care about finding all solutions, and we just + * found one; return with it immediately. */ + free_game(scopy); + ret = self_soluble; + goto done; + } + +#ifdef SOLVER_DIAGNOSTICS + debug(("Recursing #2: trying (%d,%d) as LIGHT\n", bestx, besty)); +#endif + set_light(scopy, bestx, besty, 1); + copy_soluble = solve_sub(scopy, solve_flags, depth+1, maxdepth); + + /* If we wanted a unique solution but we hit our recursion limit + * (on either branch) then we have to assume we didn't find possible + * extra solutions, and return 'not soluble'. */ + if ((solve_flags & F_SOLVE_FORCEUNIQUE) && + ((copy_soluble < 0) || (self_soluble < 0))) { + ret = -1; + /* Make sure that whether or not it was self or copy (or both) that + * were soluble, that we return a solved state in self. */ + } else if (copy_soluble <= 0) { + /* copy wasn't soluble; keep self state and return that result. */ + ret = self_soluble; + } else if (self_soluble <= 0) { + /* copy solved and we didn't, so copy in copy's (now solved) + * flags and light state. */ + memcpy(state->lights, scopy->lights, + scopy->w * scopy->h * sizeof(int)); + memcpy(state->flags, scopy->flags, + scopy->w * scopy->h * sizeof(unsigned int)); + ret = copy_soluble; + } else { + ret = copy_soluble + self_soluble; + } + free_game(scopy); + goto done; + } +done: + if (sscratch) sfree(sscratch); +#ifdef SOLVER_DIAGNOSTICS + if (ret < 0) + debug(("solve_sub: depth = %d returning, ran out of recursion.\n", + depth)); + else + debug(("solve_sub: depth = %d returning, %d solutions.\n", + depth, ret)); +#endif + return ret; +} + +/* Fills in the (possibly partially-complete) game_state as far as it can, + * returning the number of possible solutions. If it returns >0 then the + * game_state will be in a solved state, but you won't know which one. */ +static int dosolve(game_state *state, int solve_flags, int *maxdepth) +{ + int x, y, nsol; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + GRID(state,flags,x,y) &= ~F_NUMBERUSED; + } + } + nsol = solve_sub(state, solve_flags, 0, maxdepth); + return nsol; +} + +static int strip_unused_nums(game_state *state) +{ + int x,y,n=0; + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if ((GRID(state,flags,x,y) & F_NUMBERED) && + !(GRID(state,flags,x,y) & F_NUMBERUSED)) { + GRID(state,flags,x,y) &= ~F_NUMBERED; + GRID(state,lights,x,y) = 0; + n++; + } + } + } + debug(("Stripped %d unused numbers.\n", n)); + return n; +} + +static void unplace_lights(game_state *state) +{ + int x,y; + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + if (GRID(state,flags,x,y) & F_LIGHT) + set_light(state,x,y,0); + GRID(state,flags,x,y) &= ~F_IMPOSSIBLE; + GRID(state,flags,x,y) &= ~F_NUMBERUSED; + } + } +} + +static int puzzle_is_good(game_state *state, int difficulty) +{ + int nsol, mdepth = 0; + unsigned int sflags = flags_from_difficulty(difficulty); + + unplace_lights(state); + +#ifdef SOLVER_DIAGNOSTICS + debug(("Trying to solve with difficulty %d (0x%x):\n", + difficulty, sflags)); + if (verbose) debug_state(state); +#endif + + nsol = dosolve(state, sflags, &mdepth); + /* if we wanted an easy puzzle, make sure we didn't need recursion. */ + if (!(sflags & F_SOLVE_ALLOWRECURSE) && mdepth > 0) { + debug(("Ignoring recursive puzzle.\n")); + return 0; + } + + debug(("%d solutions found.\n", nsol)); + if (nsol <= 0) return 0; + if (nsol > 1) return 0; + return 1; +} + +/* --- New game creation and user input code. --- */ + +/* The basic algorithm here is to generate the most complex grid possible + * while honouring two restrictions: + * + * * we require a unique solution, and + * * either we require solubility with no recursion (!params->recurse) + * * or we require some recursion. (params->recurse). + * + * The solver helpfully keeps track of the numbers it needed to use to + * get its solution, so we use that to remove an initial set of numbers + * and check we still satsify our requirements (on uniqueness and + * non-recursiveness, if applicable; we don't check explicit recursiveness + * until the end). + * + * Then we try to remove all numbers in a random order, and see if we + * still satisfy requirements (putting them back if we didn't). + * + * Removing numbers will always, in general terms, make a puzzle require + * more recursion but it may also mean a puzzle becomes non-unique. + * + * Once we're done, if we wanted a recursive puzzle but the most difficult + * puzzle we could come up with was non-recursive, we give up and try a new + * grid. */ + +#define MAX_GRIDGEN_TRIES 20 + +static char *new_game_desc(const game_params *params_in, random_state *rs, + char **aux, int interactive) +{ + game_params params_copy = *params_in; /* structure copy */ + game_params *params = ¶ms_copy; + game_state *news = new_state(params), *copys; + int i, j, run, x, y, wh = params->w*params->h, num; + char *ret, *p; + int *numindices; + + /* Construct a shuffled list of grid positions; we only + * do this once, because if it gets used more than once it'll + * be on a different grid layout. */ + numindices = snewn(wh, int); + for (j = 0; j < wh; j++) numindices[j] = j; + shuffle(numindices, wh, sizeof(*numindices), rs); + + while (1) { + for (i = 0; i < MAX_GRIDGEN_TRIES; i++) { + set_blacks(news, params, rs); /* also cleans board. */ + + /* set up lights and then the numbers, and remove the lights */ + place_lights(news, rs); + debug(("Generating initial grid.\n")); + place_numbers(news); + if (!puzzle_is_good(news, params->difficulty)) continue; + + /* Take a copy, remove numbers we didn't use and check there's + * still a unique solution; if so, use the copy subsequently. */ + copys = dup_game(news); + strip_unused_nums(copys); + if (!puzzle_is_good(copys, params->difficulty)) { + debug(("Stripped grid is not good, reverting.\n")); + free_game(copys); + } else { + free_game(news); + news = copys; + } + + /* Go through grid removing numbers at random one-by-one and + * trying to solve again; if it ceases to be good put the number back. */ + for (j = 0; j < wh; j++) { + y = numindices[j] / params->w; + x = numindices[j] % params->w; + if (!(GRID(news, flags, x, y) & F_NUMBERED)) continue; + num = GRID(news, lights, x, y); + GRID(news, lights, x, y) = 0; + GRID(news, flags, x, y) &= ~F_NUMBERED; + if (!puzzle_is_good(news, params->difficulty)) { + GRID(news, lights, x, y) = num; + GRID(news, flags, x, y) |= F_NUMBERED; + } else + debug(("Removed (%d,%d) still soluble.\n", x, y)); + } + if (params->difficulty > 0) { + /* Was the maximally-difficult puzzle difficult enough? + * Check we can't solve it with a more simplistic solver. */ + if (puzzle_is_good(news, params->difficulty-1)) { + debug(("Maximally-hard puzzle still not hard enough, skipping.\n")); + continue; + } + } + + goto goodpuzzle; + } + /* Couldn't generate a good puzzle in however many goes. Ramp up the + * %age of black squares (if we didn't already have lots; in which case + * why couldn't we generate a puzzle?) and try again. */ + if (params->blackpc < 90) params->blackpc += 5; + debug(("New black layout %d%%.\n", params->blackpc)); + } +goodpuzzle: + /* Game is encoded as a long string one character per square; + * 'S' is a space + * 'B' is a black square with no number + * '0', '1', '2', '3', '4' is a black square with a number. */ + ret = snewn((params->w * params->h) + 1, char); + p = ret; + run = 0; + for (y = 0; y < params->h; y++) { + for (x = 0; x < params->w; x++) { + if (GRID(news,flags,x,y) & F_BLACK) { + if (run) { + *p++ = ('a'-1) + run; + run = 0; + } + if (GRID(news,flags,x,y) & F_NUMBERED) + *p++ = '0' + GRID(news,lights,x,y); + else + *p++ = 'B'; + } else { + if (run == 26) { + *p++ = ('a'-1) + run; + run = 0; + } + run++; + } + } + } + if (run) { + *p++ = ('a'-1) + run; + run = 0; + } + *p = '\0'; + assert(p - ret <= params->w * params->h); + free_game(news); + sfree(numindices); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int i; + for (i = 0; i < params->w*params->h; i++) { + if (*desc >= '0' && *desc <= '4') + /* OK */; + else if (*desc == 'B') + /* OK */; + else if (*desc >= 'a' && *desc <= 'z') + i += *desc - 'a'; /* and the i++ will add another one */ + else if (!*desc) + return "Game description shorter than expected"; + else + return "Game description contained unexpected character"; + desc++; + } + if (*desc || i > params->w*params->h) + return "Game description longer than expected"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *ret = new_state(params); + int x,y; + int run = 0; + + for (y = 0; y < params->h; y++) { + for (x = 0; x < params->w; x++) { + char c = '\0'; + + if (run == 0) { + c = *desc++; + assert(c != 'S'); + if (c >= 'a' && c <= 'z') + run = c - 'a' + 1; + } + + if (run > 0) { + c = 'S'; + run--; + } + + switch (c) { + case '0': case '1': case '2': case '3': case '4': + GRID(ret,flags,x,y) |= F_NUMBERED; + GRID(ret,lights,x,y) = (c - '0'); + /* run-on... */ + + case 'B': + GRID(ret,flags,x,y) |= F_BLACK; + break; + + case 'S': + /* empty square */ + break; + + default: + assert(!"Malformed desc."); + break; + } + } + } + if (*desc) assert(!"Over-long desc."); + + return ret; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved; + char *move = NULL, buf[80]; + int movelen, movesize, x, y, len; + unsigned int oldflags, solvedflags, sflags; + + /* We don't care here about non-unique puzzles; if the + * user entered one themself then I doubt they care. */ + + sflags = F_SOLVE_ALLOWRECURSE | F_SOLVE_DISCOUNTSETS; + + /* Try and solve from where we are now (for non-unique + * puzzles this may produce a different answer). */ + solved = dup_game(currstate); + if (dosolve(solved, sflags, NULL) > 0) goto solved; + free_game(solved); + + /* That didn't work; try solving from the clean puzzle. */ + solved = dup_game(state); + if (dosolve(solved, sflags, NULL) > 0) goto solved; + *error = "Unable to find a solution to this puzzle."; + goto done; + +solved: + movesize = 256; + move = snewn(movesize, char); + movelen = 0; + move[movelen++] = 'S'; + move[movelen] = '\0'; + for (x = 0; x < currstate->w; x++) { + for (y = 0; y < currstate->h; y++) { + len = 0; + oldflags = GRID(currstate, flags, x, y); + solvedflags = GRID(solved, flags, x, y); + if ((oldflags & F_LIGHT) != (solvedflags & F_LIGHT)) + len = sprintf(buf, ";L%d,%d", x, y); + else if ((oldflags & F_IMPOSSIBLE) != (solvedflags & F_IMPOSSIBLE)) + len = sprintf(buf, ";I%d,%d", x, y); + if (len) { + if (movelen + len >= movesize) { + movesize = movelen + len + 256; + move = sresize(move, movesize, char); + } + strcpy(move + movelen, buf); + movelen += len; + } + } + } + +done: + free_game(solved); + return move; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +/* 'borrowed' from slant.c, mainly. I could have printed it one + * character per cell (like debug_state) but that comes out tiny. + * 'L' is used for 'light here' because 'O' looks too much like '0' + * (black square with no surrounding lights). */ +static char *game_text_format(const game_state *state) +{ + int w = state->w, h = state->h, W = w+1, H = h+1; + int x, y, len, lights; + unsigned int flags; + char *ret, *p; + + len = (h+H) * (w+W+1) + 1; + ret = snewn(len, char); + p = ret; + + for (y = 0; y < H; y++) { + for (x = 0; x < W; x++) { + *p++ = '+'; + if (x < w) + *p++ = '-'; + } + *p++ = '\n'; + if (y < h) { + for (x = 0; x < W; x++) { + *p++ = '|'; + if (x < w) { + /* actual interesting bit. */ + flags = GRID(state, flags, x, y); + lights = GRID(state, lights, x, y); + if (flags & F_BLACK) { + if (flags & F_NUMBERED) + *p++ = '0' + lights; + else + *p++ = '#'; + } else { + if (flags & F_LIGHT) + *p++ = 'L'; + else if (flags & F_IMPOSSIBLE) + *p++ = 'x'; + else if (lights > 0) + *p++ = '.'; + else + *p++ = ' '; + } + } + } + *p++ = '\n'; + } + } + *p++ = '\0'; + + assert(p - ret == len); + return ret; +} + +struct game_ui { + int cur_x, cur_y, cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = ui->cur_y = ui->cur_visible = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + /* nothing to encode. */ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + /* nothing to decode. */ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (newstate->completed) + ui->cur_visible = 0; +} + +#define DF_BLACK 1 /* black square */ +#define DF_NUMBERED 2 /* black square with number */ +#define DF_LIT 4 /* display (white) square lit up */ +#define DF_LIGHT 8 /* display light in square */ +#define DF_OVERLAP 16 /* display light as overlapped */ +#define DF_CURSOR 32 /* display cursor */ +#define DF_NUMBERWRONG 64 /* display black numbered square as error. */ +#define DF_FLASH 128 /* background flash is on. */ +#define DF_IMPOSSIBLE 256 /* display non-light little square */ + +struct game_drawstate { + int tilesize, crad; + int w, h; + unsigned int *flags; /* width * height */ + int started; +}; + + +/* Believe it or not, this empty = "" hack is needed to get around a bug in + * the prc-tools gcc when optimisation is turned on; before, it produced: + lightup-sect.c: In function `interpret_move': + lightup-sect.c:1416: internal error--unrecognizable insn: + (insn 582 580 583 (set (reg:SI 134) + (pc)) -1 (nil) + (nil)) + */ +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + enum { NONE, FLIP_LIGHT, FLIP_IMPOSSIBLE } action = NONE; + int cx = -1, cy = -1; + unsigned int flags; + char buf[80], *nullret = NULL, *empty = "", c; + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + if (ui->cur_visible) + nullret = empty; + ui->cur_visible = 0; + cx = FROMCOORD(x); + cy = FROMCOORD(y); + action = (button == LEFT_BUTTON) ? FLIP_LIGHT : FLIP_IMPOSSIBLE; + } else if (IS_CURSOR_SELECT(button) || + button == 'i' || button == 'I' || + button == ' ' || button == '\r' || button == '\n') { + if (ui->cur_visible) { + /* Only allow cursor-effect operations if the cursor is visible + * (otherwise you have no idea which square it might be affecting) */ + cx = ui->cur_x; + cy = ui->cur_y; + action = (button == 'i' || button == 'I' || button == CURSOR_SELECT2) ? + FLIP_IMPOSSIBLE : FLIP_LIGHT; + } + ui->cur_visible = 1; + } else if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, 0); + ui->cur_visible = 1; + nullret = empty; + } else + return NULL; + + switch (action) { + case FLIP_LIGHT: + case FLIP_IMPOSSIBLE: + if (cx < 0 || cy < 0 || cx >= state->w || cy >= state->h) + return nullret; + flags = GRID(state, flags, cx, cy); + if (flags & F_BLACK) + return nullret; + if (action == FLIP_LIGHT) { +#ifdef STYLUS_BASED + if (flags & F_IMPOSSIBLE || flags & F_LIGHT) c = 'I'; else c = 'L'; +#else + if (flags & F_IMPOSSIBLE) return nullret; + c = 'L'; +#endif + } else { +#ifdef STYLUS_BASED + if (flags & F_IMPOSSIBLE || flags & F_LIGHT) c = 'L'; else c = 'I'; +#else + if (flags & F_LIGHT) return nullret; + c = 'I'; +#endif + } + sprintf(buf, "%c%d,%d", (int)c, cx, cy); + break; + + case NONE: + return nullret; + + default: + assert(!"Shouldn't get here!"); + } + return dupstr(buf); +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *ret = dup_game(state); + int x, y, n, flags; + char c; + + if (!*move) goto badmove; + + while (*move) { + c = *move; + if (c == 'S') { + ret->used_solve = TRUE; + move++; + } else if (c == 'L' || c == 'I') { + move++; + if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || + x < 0 || y < 0 || x >= ret->w || y >= ret->h) + goto badmove; + + flags = GRID(ret, flags, x, y); + if (flags & F_BLACK) goto badmove; + + /* LIGHT and IMPOSSIBLE are mutually exclusive. */ + if (c == 'L') { + GRID(ret, flags, x, y) &= ~F_IMPOSSIBLE; + set_light(ret, x, y, (flags & F_LIGHT) ? 0 : 1); + } else { + set_light(ret, x, y, 0); + GRID(ret, flags, x, y) ^= F_IMPOSSIBLE; + } + move += n; + } else goto badmove; + + if (*move == ';') + move++; + else if (*move) goto badmove; + } + if (grid_correct(ret)) ret->completed = 1; + return ret; + +badmove: + free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +/* XXX entirely cloned from fifteen.c; separate out? */ +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * params->w + 2 * BORDER; + *y = TILE_SIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + ds->crad = 3*(tilesize-1)/8; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + for (i = 0; i < 3; i++) { + ret[COL_BLACK * 3 + i] = 0.0F; + ret[COL_LIGHT * 3 + i] = 1.0F; + ret[COL_CURSOR * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 2.0F; + ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.5F; + + } + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.25F; + ret[COL_ERROR * 3 + 2] = 0.25F; + + ret[COL_LIT * 3 + 0] = 1.0F; + ret[COL_LIT * 3 + 1] = 1.0F; + ret[COL_LIT * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = ds->crad = 0; + ds->w = state->w; ds->h = state->h; + + ds->flags = snewn(ds->w*ds->h, unsigned int); + for (i = 0; i < ds->w*ds->h; i++) + ds->flags[i] = -1; + + ds->started = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->flags); + sfree(ds); +} + +/* At some stage we should put these into a real options struct. + * Note that tile_redraw has no #ifdeffery; it relies on tile_flags not + * to put those flags in. */ +#define HINT_LIGHTS +#define HINT_OVERLAPS +#define HINT_NUMBERS + +static unsigned int tile_flags(game_drawstate *ds, const game_state *state, + const game_ui *ui, int x, int y, int flashing) +{ + unsigned int flags = GRID(state, flags, x, y); + int lights = GRID(state, lights, x, y); + unsigned int ret = 0; + + if (flashing) ret |= DF_FLASH; + if (ui && ui->cur_visible && x == ui->cur_x && y == ui->cur_y) + ret |= DF_CURSOR; + + if (flags & F_BLACK) { + ret |= DF_BLACK; + if (flags & F_NUMBERED) { +#ifdef HINT_NUMBERS + if (number_wrong(state, x, y)) + ret |= DF_NUMBERWRONG; +#endif + ret |= DF_NUMBERED; + } + } else { +#ifdef HINT_LIGHTS + if (lights > 0) ret |= DF_LIT; +#endif + if (flags & F_LIGHT) { + ret |= DF_LIGHT; +#ifdef HINT_OVERLAPS + if (lights > 1) ret |= DF_OVERLAP; +#endif + } + if (flags & F_IMPOSSIBLE) ret |= DF_IMPOSSIBLE; + } + return ret; +} + +static void tile_redraw(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y) +{ + unsigned int ds_flags = GRID(ds, flags, x, y); + int dx = COORD(x), dy = COORD(y); + int lit = (ds_flags & DF_FLASH) ? COL_GRID : COL_LIT; + + if (ds_flags & DF_BLACK) { + draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BLACK); + if (ds_flags & DF_NUMBERED) { + int ccol = (ds_flags & DF_NUMBERWRONG) ? COL_ERROR : COL_LIGHT; + char str[32]; + + /* We know that this won't change over the course of the game + * so it's OK to ignore this when calculating whether or not + * to redraw the tile. */ + sprintf(str, "%d", GRID(state, lights, x, y)); + draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE*3/5, + ALIGN_VCENTRE | ALIGN_HCENTRE, ccol, str); + } + } else { + draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, + (ds_flags & DF_LIT) ? lit : COL_BACKGROUND); + draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID); + if (ds_flags & DF_LIGHT) { + int lcol = (ds_flags & DF_OVERLAP) ? COL_ERROR : COL_LIGHT; + draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS, + lcol, COL_BLACK); + } else if ((ds_flags & DF_IMPOSSIBLE)) { + static int draw_blobs_when_lit = -1; + if (draw_blobs_when_lit < 0) { + char *env = getenv("LIGHTUP_LIT_BLOBS"); + draw_blobs_when_lit = (!env || (env[0] == 'y' || + env[0] == 'Y')); + } + if (!(ds_flags & DF_LIT) || draw_blobs_when_lit) { + int rlen = TILE_SIZE / 4; + draw_rect(dr, dx + TILE_SIZE/2 - rlen/2, + dy + TILE_SIZE/2 - rlen/2, + rlen, rlen, COL_BLACK); + } + } + } + + if (ds_flags & DF_CURSOR) { + int coff = TILE_SIZE/8; + draw_rect_outline(dr, dx + coff, dy + coff, + TILE_SIZE - coff*2, TILE_SIZE - coff*2, COL_CURSOR); + } + + draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int flashing = FALSE; + int x,y; + + if (flashtime) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; + + if (!ds->started) { + draw_rect(dr, 0, 0, + TILE_SIZE * ds->w + 2 * BORDER, + TILE_SIZE * ds->h + 2 * BORDER, COL_BACKGROUND); + + draw_rect_outline(dr, COORD(0)-1, COORD(0)-1, + TILE_SIZE * ds->w + 2, + TILE_SIZE * ds->h + 2, + COL_GRID); + + draw_update(dr, 0, 0, + TILE_SIZE * ds->w + 2 * BORDER, + TILE_SIZE * ds->h + 2 * BORDER); + ds->started = 1; + } + + for (x = 0; x < ds->w; x++) { + for (y = 0; y < ds->h; y++) { + unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing); + if (ds_flags != GRID(ds, flags, x, y)) { + GRID(ds, flags, x, y) = ds_flags; + tile_redraw(dr, ds, state, x, y); + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->w, h = state->h; + int ink = print_mono_colour(dr, 0); + int paper = print_mono_colour(dr, 1); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILE_SIZE / 16); + draw_rect_outline(dr, COORD(0), COORD(0), + TILE_SIZE * w, TILE_SIZE * h, ink); + + /* + * Grid. + */ + print_line_width(dr, TILE_SIZE / 24); + for (x = 1; x < w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink); + for (y = 1; y < h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink); + + /* + * Grid contents. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + unsigned int ds_flags = tile_flags(ds, state, NULL, x, y, FALSE); + int dx = COORD(x), dy = COORD(y); + if (ds_flags & DF_BLACK) { + draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, ink); + if (ds_flags & DF_NUMBERED) { + char str[32]; + sprintf(str, "%d", GRID(state, lights, x, y)); + draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE*3/5, + ALIGN_VCENTRE | ALIGN_HCENTRE, paper, str); + } + } else if (ds_flags & DF_LIGHT) { + draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, + TILE_RADIUS, -1, ink); + } + } +} + +#ifdef COMBINED +#define thegame lightup +#endif + +const struct game thegame = { + "Light Up", "games.lightup", "lightup", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err, *result; + int nsol, diff, really_verbose = 0; + unsigned int sflags; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_verbose++; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + /* Run the solvers easiest to hardest until we find one that + * can solve our puzzle. If it's soluble we know that the + * hardest (recursive) solver will always find the solution. */ + nsol = sflags = 0; + for (diff = 0; diff <= DIFFCOUNT; diff++) { + printf("\nSolving with difficulty %d.\n", diff); + sflags = flags_from_difficulty(diff); + unplace_lights(s); + nsol = dosolve(s, sflags, NULL); + if (nsol == 1) break; + } + + printf("\n"); + if (nsol == 0) { + printf("Puzzle has no solution.\n"); + } else if (nsol < 0) { + printf("Unable to find a unique solution.\n"); + } else if (nsol > 1) { + printf("Puzzle has multiple solutions.\n"); + } else { + verbose = really_verbose; + unplace_lights(s); + printf("Puzzle has difficulty %d: solving...\n", diff); + dosolve(s, sflags, NULL); /* sflags from last successful solve */ + result = game_text_format(s); + printf("%s", result); + sfree(result); + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/list.c b/apps/plugins/puzzles/list.c new file mode 100644 index 0000000000..ec019c31b2 --- /dev/null +++ b/apps/plugins/puzzles/list.c @@ -0,0 +1,55 @@ +/* + * list.c: List of pointers to puzzle structures, for monolithic + * platforms. + * + * This file is automatically generated by mkfiles.pl. Do not edit + * it directly, or the changes will be lost next time mkfiles.pl runs. + * Instead, edit Recipe and/or its *.R subfiles. + */ +#include "puzzles.h" +#define GAMELIST(A) \ + A(blackbox) \ + A(bridges) \ + A(cube) \ + A(dominosa) \ + A(fifteen) \ + A(filling) \ + A(flip) \ + A(flood) \ + A(galaxies) \ + A(guess) \ + A(inertia) \ + A(keen) \ + A(lightup) \ + A(loopy) \ + A(magnets) \ + A(map) \ + A(mines) \ + A(net) \ + A(netslide) \ + A(palisade) \ + A(pattern) \ + A(pearl) \ + A(pegs) \ + A(range) \ + A(rect) \ + A(samegame) \ + A(signpost) \ + A(singles) \ + A(sixteen) \ + A(slant) \ + A(solo) \ + A(tents) \ + A(towers) \ + A(tracks) \ + A(twiddle) \ + A(undead) \ + A(unequal) \ + A(unruly) \ + A(untangle) \ + +#define DECL(x) extern const game x; +#define REF(x) &x, +GAMELIST(DECL) +const game *gamelist[] = { GAMELIST(REF) }; +const int gamecount = lenof(gamelist); diff --git a/apps/plugins/puzzles/loopgen.c b/apps/plugins/puzzles/loopgen.c new file mode 100644 index 0000000000..25b63906fa --- /dev/null +++ b/apps/plugins/puzzles/loopgen.c @@ -0,0 +1,536 @@ +/* + * loopgen.c: loop generation functions for grid.[ch]. + */ + +#include +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" +#include "grid.h" +#include "loopgen.h" + + +/* We're going to store lists of current candidate faces for colouring black + * or white. + * Each face gets a 'score', which tells us how adding that face right + * now would affect the curliness of the solution loop. We're trying to + * maximise that quantity so will bias our random selection of faces to + * colour those with high scores */ +struct face_score { + int white_score; + int black_score; + unsigned long random; + /* No need to store a grid_face* here. The 'face_scores' array will + * be a list of 'face_score' objects, one for each face of the grid, so + * the position (index) within the 'face_scores' array will determine + * which face corresponds to a particular face_score. + * Having a single 'face_scores' array for all faces simplifies memory + * management, and probably improves performance, because we don't have to + * malloc/free each individual face_score, and we don't have to maintain + * a mapping from grid_face* pointers to face_score* pointers. + */ +}; + +static int generic_sort_cmpfn(void *v1, void *v2, size_t offset) +{ + struct face_score *f1 = v1; + struct face_score *f2 = v2; + int r; + + r = *(int *)((char *)f2 + offset) - *(int *)((char *)f1 + offset); + if (r) { + return r; + } + + if (f1->random < f2->random) + return -1; + else if (f1->random > f2->random) + return 1; + + /* + * It's _just_ possible that two faces might have been given + * the same random value. In that situation, fall back to + * comparing based on the positions within the face_scores list. + * This introduces a tiny directional bias, but not a significant one. + */ + return f1 - f2; +} + +static int white_sort_cmpfn(void *v1, void *v2) +{ + return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,white_score)); +} + +static int black_sort_cmpfn(void *v1, void *v2) +{ + return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,black_score)); +} + +/* 'board' is an array of enum face_colour, indicating which faces are + * currently black/white/grey. 'colour' is FACE_WHITE or FACE_BLACK. + * Returns whether it's legal to colour the given face with this colour. */ +static int can_colour_face(grid *g, char* board, int face_index, + enum face_colour colour) +{ + int i, j; + grid_face *test_face = g->faces + face_index; + grid_face *starting_face, *current_face; + grid_dot *starting_dot; + int transitions; + int current_state, s; /* booleans: equal or not-equal to 'colour' */ + int found_same_coloured_neighbour = FALSE; + assert(board[face_index] != colour); + + /* Can only consider a face for colouring if it's adjacent to a face + * with the same colour. */ + for (i = 0; i < test_face->order; i++) { + grid_edge *e = test_face->edges[i]; + grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1; + if (FACE_COLOUR(f) == colour) { + found_same_coloured_neighbour = TRUE; + break; + } + } + if (!found_same_coloured_neighbour) + return FALSE; + + /* Need to avoid creating a loop of faces of this colour around some + * differently-coloured faces. + * Also need to avoid meeting a same-coloured face at a corner, with + * other-coloured faces in between. Here's a simple test that (I believe) + * takes care of both these conditions: + * + * Take the circular path formed by this face's edges, and inflate it + * slightly outwards. Imagine walking around this path and consider + * the faces that you visit in sequence. This will include all faces + * touching the given face, either along an edge or just at a corner. + * Count the number of 'colour'/not-'colour' transitions you encounter, as + * you walk along the complete loop. This will obviously turn out to be + * an even number. + * If 0, we're either in the middle of an "island" of this colour (should + * be impossible as we're not supposed to create black or white loops), + * or we're about to start a new island - also not allowed. + * If 4 or greater, there are too many separate coloured regions touching + * this face, and colouring it would create a loop or a corner-violation. + * The only allowed case is when the count is exactly 2. */ + + /* i points to a dot around the test face. + * j points to a face around the i^th dot. + * The current face will always be: + * test_face->dots[i]->faces[j] + * We assume dots go clockwise around the test face, + * and faces go clockwise around dots. */ + + /* + * The end condition is slightly fiddly. In sufficiently strange + * degenerate grids, our test face may be adjacent to the same + * other face multiple times (typically if it's the exterior + * face). Consider this, in particular: + * + * +--+ + * | | + * +--+--+ + * | | | + * +--+--+ + * + * The bottom left face there is adjacent to the exterior face + * twice, so we can't just terminate our iteration when we reach + * the same _face_ we started at. Furthermore, we can't + * condition on having the same (i,j) pair either, because + * several (i,j) pairs identify the bottom left contiguity with + * the exterior face! We canonicalise the (i,j) pair by taking + * one step around before we set the termination tracking. + */ + + i = j = 0; + current_face = test_face->dots[0]->faces[0]; + if (current_face == test_face) { + j = 1; + current_face = test_face->dots[0]->faces[1]; + } + transitions = 0; + current_state = (FACE_COLOUR(current_face) == colour); + starting_dot = NULL; + starting_face = NULL; + while (TRUE) { + /* Advance to next face. + * Need to loop here because it might take several goes to + * find it. */ + while (TRUE) { + j++; + if (j == test_face->dots[i]->order) + j = 0; + + if (test_face->dots[i]->faces[j] == test_face) { + /* Advance to next dot round test_face, then + * find current_face around new dot + * and advance to the next face clockwise */ + i++; + if (i == test_face->order) + i = 0; + for (j = 0; j < test_face->dots[i]->order; j++) { + if (test_face->dots[i]->faces[j] == current_face) + break; + } + /* Must actually find current_face around new dot, + * or else something's wrong with the grid. */ + assert(j != test_face->dots[i]->order); + /* Found, so advance to next face and try again */ + } else { + break; + } + } + /* (i,j) are now advanced to next face */ + current_face = test_face->dots[i]->faces[j]; + s = (FACE_COLOUR(current_face) == colour); + if (!starting_dot) { + starting_dot = test_face->dots[i]; + starting_face = current_face; + current_state = s; + } else { + if (s != current_state) { + ++transitions; + current_state = s; + if (transitions > 2) + break; + } + if (test_face->dots[i] == starting_dot && + current_face == starting_face) + break; + } + } + + return (transitions == 2) ? TRUE : FALSE; +} + +/* Count the number of neighbours of 'face', having colour 'colour' */ +static int face_num_neighbours(grid *g, char *board, grid_face *face, + enum face_colour colour) +{ + int colour_count = 0; + int i; + grid_face *f; + grid_edge *e; + for (i = 0; i < face->order; i++) { + e = face->edges[i]; + f = (e->face1 == face) ? e->face2 : e->face1; + if (FACE_COLOUR(f) == colour) + ++colour_count; + } + return colour_count; +} + +/* The 'score' of a face reflects its current desirability for selection + * as the next face to colour white or black. We want to encourage moving + * into grey areas and increasing loopiness, so we give scores according to + * how many of the face's neighbours are currently coloured the same as the + * proposed colour. */ +static int face_score(grid *g, char *board, grid_face *face, + enum face_colour colour) +{ + /* Simple formula: score = 0 - num. same-coloured neighbours, + * so a higher score means fewer same-coloured neighbours. */ + return -face_num_neighbours(g, board, face, colour); +} + +/* + * Generate a new complete random closed loop for the given grid. + * + * The method is to generate a WHITE/BLACK colouring of all the faces, + * such that the WHITE faces will define the inside of the path, and the + * BLACK faces define the outside. + * To do this, we initially colour all faces GREY. The infinite space outside + * the grid is coloured BLACK, and we choose a random face to colour WHITE. + * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY + * faces, until the grid is filled with BLACK/WHITE. As we grow the regions, + * we avoid creating loops of a single colour, to preserve the topological + * shape of the WHITE and BLACK regions. + * We also try to make the boundary as loopy and twisty as possible, to avoid + * generating paths that are uninteresting. + * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY + * face that can be coloured with that colour (without violating the + * topological shape of that region). It's not obvious, but I think this + * algorithm is guaranteed to terminate without leaving any GREY faces behind. + * Indeed, if there are any GREY faces at all, both the WHITE and BLACK + * regions can be grown. + * This is checked using assert()ions, and I haven't seen any failures yet. + * + * Hand-wavy proof: imagine what can go wrong... + * + * Could the white faces get completely cut off by the black faces, and still + * leave some grey faces remaining? + * No, because then the black faces would form a loop around both the white + * faces and the grey faces, which is disallowed because we continually + * maintain the correct topological shape of the black region. + * Similarly, the black faces can never get cut off by the white faces. That + * means both the WHITE and BLACK regions always have some room to grow into + * the GREY regions. + * Could it be that we can't colour some GREY face, because there are too many + * WHITE/BLACK transitions as we walk round the face? (see the + * can_colour_face() function for details) + * No. Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk + * around the face. The two WHITE faces would be connected by a WHITE path, + * and the BLACK faces would be connected by a BLACK path. These paths would + * have to cross, which is impossible. + * Another thing that could go wrong: perhaps we can't find any GREY face to + * colour WHITE, because it would create a loop-violation or a corner-violation + * with the other WHITE faces? + * This is a little bit tricky to prove impossible. Imagine you have such a + * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop + * or corner violation). + * That would cut all the non-white area into two blobs. One of those blobs + * must be free of BLACK faces (because the BLACK stuff is a connected blob). + * So we have a connected GREY area, completely surrounded by WHITE + * (including the GREY face we've tentatively coloured WHITE). + * A well-known result in graph theory says that you can always find a GREY + * face whose removal leaves the remaining GREY area connected. And it says + * there are at least two such faces, so we can always choose the one that + * isn't the "tentative" GREY face. Colouring that face WHITE leaves + * everything nice and connected, including that "tentative" GREY face which + * acts as a gateway to the rest of the non-WHITE grid. + */ +void generate_loop(grid *g, char *board, random_state *rs, + loopgen_bias_fn_t bias, void *biasctx) +{ + int i, j; + int num_faces = g->num_faces; + struct face_score *face_scores; /* Array of face_score objects */ + struct face_score *fs; /* Points somewhere in the above list */ + struct grid_face *cur_face; + tree234 *lightable_faces_sorted; + tree234 *darkable_faces_sorted; + int *face_list; + int do_random_pass; + + /* Make a board */ + memset(board, FACE_GREY, num_faces); + + /* Create and initialise the list of face_scores */ + face_scores = snewn(num_faces, struct face_score); + for (i = 0; i < num_faces; i++) { + face_scores[i].random = random_bits(rs, 31); + face_scores[i].black_score = face_scores[i].white_score = 0; + } + + /* Colour a random, finite face white. The infinite face is implicitly + * coloured black. Together, they will seed the random growth process + * for the black and white areas. */ + i = random_upto(rs, num_faces); + board[i] = FACE_WHITE; + + /* We need a way of favouring faces that will increase our loopiness. + * We do this by maintaining a list of all candidate faces sorted by + * their score and choose randomly from that with appropriate skew. + * In order to avoid consistently biasing towards particular faces, we + * need the sort order _within_ each group of scores to be completely + * random. But it would be abusing the hospitality of the tree234 data + * structure if our comparison function were nondeterministic :-). So with + * each face we associate a random number that does not change during a + * particular run of the generator, and use that as a secondary sort key. + * Yes, this means we will be biased towards particular random faces in + * any one run but that doesn't actually matter. */ + + lightable_faces_sorted = newtree234(white_sort_cmpfn); + darkable_faces_sorted = newtree234(black_sort_cmpfn); + + /* Initialise the lists of lightable and darkable faces. This is + * slightly different from the code inside the while-loop, because we need + * to check every face of the board (the grid structure does not keep a + * list of the infinite face's neighbours). */ + for (i = 0; i < num_faces; i++) { + grid_face *f = g->faces + i; + struct face_score *fs = face_scores + i; + if (board[i] != FACE_GREY) continue; + /* We need the full colourability check here, it's not enough simply + * to check neighbourhood. On some grids, a neighbour of the infinite + * face is not necessarily darkable. */ + if (can_colour_face(g, board, i, FACE_BLACK)) { + fs->black_score = face_score(g, board, f, FACE_BLACK); + add234(darkable_faces_sorted, fs); + } + if (can_colour_face(g, board, i, FACE_WHITE)) { + fs->white_score = face_score(g, board, f, FACE_WHITE); + add234(lightable_faces_sorted, fs); + } + } + + /* Colour faces one at a time until no more faces are colourable. */ + while (TRUE) + { + enum face_colour colour; + tree234 *faces_to_pick; + int c_lightable = count234(lightable_faces_sorted); + int c_darkable = count234(darkable_faces_sorted); + if (c_lightable == 0 && c_darkable == 0) { + /* No more faces we can use at all. */ + break; + } + assert(c_lightable != 0 && c_darkable != 0); + + /* Choose a colour, and colour the best available face + * with that colour. */ + colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK; + + if (colour == FACE_WHITE) + faces_to_pick = lightable_faces_sorted; + else + faces_to_pick = darkable_faces_sorted; + if (bias) { + /* + * Go through all the candidate faces and pick the one the + * bias function likes best, breaking ties using the + * ordering in our tree234 (which is why we replace only + * if score > bestscore, not >=). + */ + int j, k; + struct face_score *best = NULL; + int score, bestscore = 0; + + for (j = 0; + (fs = (struct face_score *)index234(faces_to_pick, j))!=NULL; + j++) { + + assert(fs); + k = fs - face_scores; + assert(board[k] == FACE_GREY); + board[k] = colour; + score = bias(biasctx, board, k); + board[k] = FACE_GREY; + bias(biasctx, board, k); /* let bias know we put it back */ + + if (!best || score > bestscore) { + bestscore = score; + best = fs; + } + } + fs = best; + } else { + fs = (struct face_score *)index234(faces_to_pick, 0); + } + assert(fs); + i = fs - face_scores; + assert(board[i] == FACE_GREY); + board[i] = colour; + if (bias) + bias(biasctx, board, i); /* notify bias function of the change */ + + /* Remove this newly-coloured face from the lists. These lists should + * only contain grey faces. */ + del234(lightable_faces_sorted, fs); + del234(darkable_faces_sorted, fs); + + /* Remember which face we've just coloured */ + cur_face = g->faces + i; + + /* The face we've just coloured potentially affects the colourability + * and the scores of any neighbouring faces (touching at a corner or + * edge). So the search needs to be conducted around all faces + * touching the one we've just lit. Iterate over its corners, then + * over each corner's faces. For each such face, we remove it from + * the lists, recalculate any scores, then add it back to the lists + * (depending on whether it is lightable, darkable or both). */ + for (i = 0; i < cur_face->order; i++) { + grid_dot *d = cur_face->dots[i]; + for (j = 0; j < d->order; j++) { + grid_face *f = d->faces[j]; + int fi; /* face index of f */ + + if (f == NULL) + continue; + if (f == cur_face) + continue; + + /* If the face is already coloured, it won't be on our + * lightable/darkable lists anyway, so we can skip it without + * bothering with the removal step. */ + if (FACE_COLOUR(f) != FACE_GREY) continue; + + /* Find the face index and face_score* corresponding to f */ + fi = f - g->faces; + fs = face_scores + fi; + + /* Remove from lightable list if it's in there. We do this, + * even if it is still lightable, because the score might + * be different, and we need to remove-then-add to maintain + * correct sort order. */ + del234(lightable_faces_sorted, fs); + if (can_colour_face(g, board, fi, FACE_WHITE)) { + fs->white_score = face_score(g, board, f, FACE_WHITE); + add234(lightable_faces_sorted, fs); + } + /* Do the same for darkable list. */ + del234(darkable_faces_sorted, fs); + if (can_colour_face(g, board, fi, FACE_BLACK)) { + fs->black_score = face_score(g, board, f, FACE_BLACK); + add234(darkable_faces_sorted, fs); + } + } + } + } + + /* Clean up */ + freetree234(lightable_faces_sorted); + freetree234(darkable_faces_sorted); + sfree(face_scores); + + /* The next step requires a shuffled list of all faces */ + face_list = snewn(num_faces, int); + for (i = 0; i < num_faces; ++i) { + face_list[i] = i; + } + shuffle(face_list, num_faces, sizeof(int), rs); + + /* The above loop-generation algorithm can often leave large clumps + * of faces of one colour. In extreme cases, the resulting path can be + * degenerate and not very satisfying to solve. + * This next step alleviates this problem: + * Go through the shuffled list, and flip the colour of any face we can + * legally flip, and which is adjacent to only one face of the opposite + * colour - this tends to grow 'tendrils' into any clumps. + * Repeat until we can find no more faces to flip. This will + * eventually terminate, because each flip increases the loop's + * perimeter, which cannot increase for ever. + * The resulting path will have maximal loopiness (in the sense that it + * cannot be improved "locally". Unfortunately, this allows a player to + * make some illicit deductions. To combat this (and make the path more + * interesting), we do one final pass making random flips. */ + + /* Set to TRUE for final pass */ + do_random_pass = FALSE; + + while (TRUE) { + /* Remember whether a flip occurred during this pass */ + int flipped = FALSE; + + for (i = 0; i < num_faces; ++i) { + int j = face_list[i]; + enum face_colour opp = + (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE; + if (can_colour_face(g, board, j, opp)) { + grid_face *face = g->faces +j; + if (do_random_pass) { + /* final random pass */ + if (!random_upto(rs, 10)) + board[j] = opp; + } else { + /* normal pass - flip when neighbour count is 1 */ + if (face_num_neighbours(g, board, face, opp) == 1) { + board[j] = opp; + flipped = TRUE; + } + } + } + } + + if (do_random_pass) break; + if (!flipped) do_random_pass = TRUE; + } + + sfree(face_list); +} diff --git a/apps/plugins/puzzles/loopgen.h b/apps/plugins/puzzles/loopgen.h new file mode 100644 index 0000000000..079c87c576 --- /dev/null +++ b/apps/plugins/puzzles/loopgen.h @@ -0,0 +1,35 @@ +/* + * loopgen.h: interface file for loop generation functions for grid.[ch]. + */ + +#ifndef _LOOPGEN_H +#define _LOOPGEN_H + +#include "puzzles.h" +#include "grid.h" + +enum face_colour { FACE_WHITE, FACE_GREY, FACE_BLACK }; + +/* face should be of type grid_face* here. */ +#define FACE_COLOUR(face) \ + ( (face) == NULL ? FACE_BLACK : \ + board[(face) - g->faces] ) + +typedef int (*loopgen_bias_fn_t)(void *ctx, char *board, int face); + +/* 'board' should be a char array whose length is the same as + * g->num_faces: this will be filled in with FACE_WHITE or FACE_BLACK + * after loop generation. + * + * If 'bias' is non-null, it should be a user-provided function which + * rates a half-finished board (i.e. may include some FACE_GREYs) for + * desirability; this will cause the loop generator to bias in favour + * of loops with a high return value from that function. The 'face' + * parameter to the bias function indicates which face of the grid has + * been modified since the last call; it is guaranteed that only one + * will have been (so that bias functions can work incrementally + * rather than re-scanning the whole grid on every call). */ +extern void generate_loop(grid *g, char *board, random_state *rs, + loopgen_bias_fn_t bias, void *biasctx); + +#endif diff --git a/apps/plugins/puzzles/loopy.R b/apps/plugins/puzzles/loopy.R new file mode 100644 index 0000000000..f44560095d --- /dev/null +++ b/apps/plugins/puzzles/loopy.R @@ -0,0 +1,31 @@ +# -*- makefile -*- + +LOOPY_EXTRA = tree234 dsf grid penrose loopgen + +loopy : [X] GTK COMMON loopy LOOPY_EXTRA loopy-icon|no-icon + +loopy : [G] WINDOWS COMMON loopy LOOPY_EXTRA loopy.res|noicon.res + +loopysolver : [U] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE m.lib +loopysolver : [C] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE + +#penrose : [U] penrose[TEST_PENROSE] STANDALONE m.lib +#penrose : [C] penrose[TEST_PENROSE] STANDALONE + +#test-basis : [U] penrose[TEST_VECTORS] tree234 STANDALONE m.lib +#test-basis : [C] penrose[TEST_VECTORS] tree234 STANDALONE + + +ALL += loopy[COMBINED] LOOPY_EXTRA + +!begin am gtk +GAMES += loopy +!end + +!begin >list.c + A(loopy) \ +!end + +!begin >gamedesc.txt +loopy:loopy.exe:Loopy:Loop-drawing puzzle:Draw a single closed loop, given clues about number of adjacent edges. +!end diff --git a/apps/plugins/puzzles/loopy.c b/apps/plugins/puzzles/loopy.c new file mode 100644 index 0000000000..a931b31c37 --- /dev/null +++ b/apps/plugins/puzzles/loopy.c @@ -0,0 +1,3688 @@ +/* + * loopy.c: + * + * An implementation of the Nikoli game 'Loop the loop'. + * (c) Mike Pinna, 2005, 2006 + * Substantially rewritten to allowing for more general types of grid. + * (c) Lambros Lambrou 2008 + * + * vim: set shiftwidth=4 :set textwidth=80: + */ + +/* + * Possible future solver enhancements: + * + * - There's an interesting deductive technique which makes use + * of topology rather than just graph theory. Each _face_ in + * the grid is either inside or outside the loop; you can tell + * that two faces are on the same side of the loop if they're + * separated by a LINE_NO (or, more generally, by a path + * crossing no LINE_UNKNOWNs and an even number of LINE_YESes), + * and on the opposite side of the loop if they're separated by + * a LINE_YES (or an odd number of LINE_YESes and no + * LINE_UNKNOWNs). Oh, and any face separated from the outside + * of the grid by a LINE_YES or a LINE_NO is on the inside or + * outside respectively. So if you can track this for all + * faces, you figure out the state of the line between a pair + * once their relative insideness is known. + * + The way I envisage this working is simply to keep an edsf + * of all _faces_, which indicates whether they're on + * opposite sides of the loop from one another. We also + * include a special entry in the edsf for the infinite + * exterior "face". + * + So, the simple way to do this is to just go through the + * edges: every time we see an edge in a state other than + * LINE_UNKNOWN which separates two faces that aren't in the + * same edsf class, we can rectify that by merging the + * classes. Then, conversely, an edge in LINE_UNKNOWN state + * which separates two faces that _are_ in the same edsf + * class can immediately have its state determined. + * + But you can go one better, if you're prepared to loop + * over all _pairs_ of edges. Suppose we have edges A and B, + * which respectively separate faces A1,A2 and B1,B2. + * Suppose that A,B are in the same edge-edsf class and that + * A1,B1 (wlog) are in the same face-edsf class; then we can + * immediately place A2,B2 into the same face-edsf class (as + * each other, not as A1 and A2) one way round or the other. + * And conversely again, if A1,B1 are in the same face-edsf + * class and so are A2,B2, then we can put A,B into the same + * face-edsf class. + * * Of course, this deduction requires a quadratic-time + * loop over all pairs of edges in the grid, so it should + * be reserved until there's nothing easier left to be + * done. + * + * - The generalised grid support has made me (SGT) notice a + * possible extension to the loop-avoidance code. When you have + * a path of connected edges such that no other edges at all + * are incident on any vertex in the middle of the path - or, + * alternatively, such that any such edges are already known to + * be LINE_NO - then you know those edges are either all + * LINE_YES or all LINE_NO. Hence you can mentally merge the + * entire path into a single long curly edge for the purposes + * of loop avoidance, and look directly at whether or not the + * extreme endpoints of the path are connected by some other + * route. I find this coming up fairly often when I play on the + * octagonal grid setting, so it might be worth implementing in + * the solver. + * + * - (Just a speed optimisation.) Consider some todo list queue where every + * time we modify something we mark it for consideration by other bits of + * the solver, to save iteration over things that have already been done. + */ + +#include +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" +#include "grid.h" +#include "loopgen.h" + +/* Debugging options */ + +/* +#define DEBUG_CACHES +#define SHOW_WORKING +#define DEBUG_DLINES +*/ + +/* ---------------------------------------------------------------------- + * Struct, enum and function declarations + */ + +enum { + COL_BACKGROUND, + COL_FOREGROUND, + COL_LINEUNKNOWN, + COL_HIGHLIGHT, + COL_MISTAKE, + COL_SATISFIED, + COL_FAINT, + NCOLOURS +}; + +struct game_state { + grid *game_grid; /* ref-counted (internally) */ + + /* Put -1 in a face that doesn't get a clue */ + signed char *clues; + + /* Array of line states, to store whether each line is + * YES, NO or UNKNOWN */ + char *lines; + + unsigned char *line_errors; + int exactly_one_loop; + + int solved; + int cheated; + + /* Used in game_text_format(), so that it knows what type of + * grid it's trying to render as ASCII text. */ + int grid_type; +}; + +enum solver_status { + SOLVER_SOLVED, /* This is the only solution the solver could find */ + SOLVER_MISTAKE, /* This is definitely not a solution */ + SOLVER_AMBIGUOUS, /* This _might_ be an ambiguous solution */ + SOLVER_INCOMPLETE /* This may be a partial solution */ +}; + +/* ------ Solver state ------ */ +typedef struct solver_state { + game_state *state; + enum solver_status solver_status; + /* NB looplen is the number of dots that are joined together at a point, ie a + * looplen of 1 means there are no lines to a particular dot */ + int *looplen; + + /* Difficulty level of solver. Used by solver functions that want to + * vary their behaviour depending on the requested difficulty level. */ + int diff; + + /* caches */ + char *dot_yes_count; + char *dot_no_count; + char *face_yes_count; + char *face_no_count; + char *dot_solved, *face_solved; + int *dotdsf; + + /* Information for Normal level deductions: + * For each dline, store a bitmask for whether we know: + * (bit 0) at least one is YES + * (bit 1) at most one is YES */ + char *dlines; + + /* Hard level information */ + int *linedsf; +} solver_state; + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ + +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(NORMAL,Normal,n) \ + A(TRICKY,Tricky,t) \ + A(HARD,Hard,h) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFF_MAX }; +static char const *const diffnames[] = { DIFFLIST(TITLE) }; +static char const diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +/* + * Solver routines, sorted roughly in order of computational cost. + * The solver will run the faster deductions first, and slower deductions are + * only invoked when the faster deductions are unable to make progress. + * Each function is associated with a difficulty level, so that the generated + * puzzles are solvable by applying only the functions with the chosen + * difficulty level or lower. + */ +#define SOLVERLIST(A) \ + A(trivial_deductions, DIFF_EASY) \ + A(dline_deductions, DIFF_NORMAL) \ + A(linedsf_deductions, DIFF_HARD) \ + A(loop_deductions, DIFF_EASY) +#define SOLVER_FN_DECL(fn,diff) static int fn(solver_state *); +#define SOLVER_FN(fn,diff) &fn, +#define SOLVER_DIFF(fn,diff) diff, +SOLVERLIST(SOLVER_FN_DECL) +static int (*(solver_fns[]))(solver_state *) = { SOLVERLIST(SOLVER_FN) }; +static int const solver_diffs[] = { SOLVERLIST(SOLVER_DIFF) }; +static const int NUM_SOLVERS = sizeof(solver_diffs)/sizeof(*solver_diffs); + +struct game_params { + int w, h; + int diff; + int type; +}; + +/* line_drawstate is the same as line_state, but with the extra ERROR + * possibility. The drawing code copies line_state to line_drawstate, + * except in the case that the line is an error. */ +enum line_state { LINE_YES, LINE_UNKNOWN, LINE_NO }; +enum line_drawstate { DS_LINE_YES, DS_LINE_UNKNOWN, + DS_LINE_NO, DS_LINE_ERROR }; + +#define OPP(line_state) \ + (2 - line_state) + + +struct game_drawstate { + int started; + int tilesize; + int flashing; + int *textx, *texty; + char *lines; + char *clue_error; + char *clue_satisfied; +}; + +static char *validate_desc(const game_params *params, const char *desc); +static int dot_order(const game_state* state, int i, char line_type); +static int face_order(const game_state* state, int i, char line_type); +static solver_state *solve_game_rec(const solver_state *sstate); + +#ifdef DEBUG_CACHES +static void check_caches(const solver_state* sstate); +#else +#define check_caches(s) +#endif + +/* ------- List of grid generators ------- */ +#define GRIDLIST(A) \ + A(Squares,GRID_SQUARE,3,3) \ + A(Triangular,GRID_TRIANGULAR,3,3) \ + A(Honeycomb,GRID_HONEYCOMB,3,3) \ + A(Snub-Square,GRID_SNUBSQUARE,3,3) \ + A(Cairo,GRID_CAIRO,3,4) \ + A(Great-Hexagonal,GRID_GREATHEXAGONAL,3,3) \ + A(Octagonal,GRID_OCTAGONAL,3,3) \ + A(Kites,GRID_KITE,3,3) \ + A(Floret,GRID_FLORET,1,2) \ + A(Dodecagonal,GRID_DODECAGONAL,2,2) \ + A(Great-Dodecagonal,GRID_GREATDODECAGONAL,2,2) \ + A(Penrose (kite/dart),GRID_PENROSE_P2,3,3) \ + A(Penrose (rhombs),GRID_PENROSE_P3,3,3) + +#define GRID_NAME(title,type,amin,omin) #title, +#define GRID_CONFIG(title,type,amin,omin) ":" #title +#define GRID_TYPE(title,type,amin,omin) type, +#define GRID_SIZES(title,type,amin,omin) \ + {amin, omin, \ + "Width and height for this grid type must both be at least " #amin, \ + "At least one of width and height for this grid type must be at least " #omin,}, +static char const *const gridnames[] = { GRIDLIST(GRID_NAME) }; +#define GRID_CONFIGS GRIDLIST(GRID_CONFIG) +static grid_type grid_types[] = { GRIDLIST(GRID_TYPE) }; +#define NUM_GRID_TYPES (sizeof(grid_types) / sizeof(grid_types[0])) +static const struct { + int amin, omin; + char *aerr, *oerr; +} grid_size_limits[] = { GRIDLIST(GRID_SIZES) }; + +/* Generates a (dynamically allocated) new grid, according to the + * type and size requested in params. Does nothing if the grid is already + * generated. */ +static grid *loopy_generate_grid(const game_params *params, + const char *grid_desc) +{ + return grid_new(grid_types[params->type], params->w, params->h, grid_desc); +} + +/* ---------------------------------------------------------------------- + * Preprocessor magic + */ + +/* General constants */ +#define PREFERRED_TILE_SIZE 32 +#define BORDER(tilesize) ((tilesize) / 2) +#define FLASH_TIME 0.5F + +#define BIT_SET(field, bit) ((field) & (1<<(bit))) + +#define SET_BIT(field, bit) (BIT_SET(field, bit) ? FALSE : \ + ((field) |= (1<<(bit)), TRUE)) + +#define CLEAR_BIT(field, bit) (BIT_SET(field, bit) ? \ + ((field) &= ~(1<<(bit)), TRUE) : FALSE) + +#define CLUE2CHAR(c) \ + ((c < 0) ? ' ' : c < 10 ? c + '0' : c - 10 + 'A') + +/* ---------------------------------------------------------------------- + * General struct manipulation and other straightforward code + */ + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->game_grid = state->game_grid; + ret->game_grid->refcount++; + + ret->solved = state->solved; + ret->cheated = state->cheated; + + ret->clues = snewn(state->game_grid->num_faces, signed char); + memcpy(ret->clues, state->clues, state->game_grid->num_faces); + + ret->lines = snewn(state->game_grid->num_edges, char); + memcpy(ret->lines, state->lines, state->game_grid->num_edges); + + ret->line_errors = snewn(state->game_grid->num_edges, unsigned char); + memcpy(ret->line_errors, state->line_errors, state->game_grid->num_edges); + ret->exactly_one_loop = state->exactly_one_loop; + + ret->grid_type = state->grid_type; + return ret; +} + +static void free_game(game_state *state) +{ + if (state) { + grid_free(state->game_grid); + sfree(state->clues); + sfree(state->lines); + sfree(state->line_errors); + sfree(state); + } +} + +static solver_state *new_solver_state(const game_state *state, int diff) { + int i; + int num_dots = state->game_grid->num_dots; + int num_faces = state->game_grid->num_faces; + int num_edges = state->game_grid->num_edges; + solver_state *ret = snew(solver_state); + + ret->state = dup_game(state); + + ret->solver_status = SOLVER_INCOMPLETE; + ret->diff = diff; + + ret->dotdsf = snew_dsf(num_dots); + ret->looplen = snewn(num_dots, int); + + for (i = 0; i < num_dots; i++) { + ret->looplen[i] = 1; + } + + ret->dot_solved = snewn(num_dots, char); + ret->face_solved = snewn(num_faces, char); + memset(ret->dot_solved, FALSE, num_dots); + memset(ret->face_solved, FALSE, num_faces); + + ret->dot_yes_count = snewn(num_dots, char); + memset(ret->dot_yes_count, 0, num_dots); + ret->dot_no_count = snewn(num_dots, char); + memset(ret->dot_no_count, 0, num_dots); + ret->face_yes_count = snewn(num_faces, char); + memset(ret->face_yes_count, 0, num_faces); + ret->face_no_count = snewn(num_faces, char); + memset(ret->face_no_count, 0, num_faces); + + if (diff < DIFF_NORMAL) { + ret->dlines = NULL; + } else { + ret->dlines = snewn(2*num_edges, char); + memset(ret->dlines, 0, 2*num_edges); + } + + if (diff < DIFF_HARD) { + ret->linedsf = NULL; + } else { + ret->linedsf = snew_dsf(state->game_grid->num_edges); + } + + return ret; +} + +static void free_solver_state(solver_state *sstate) { + if (sstate) { + free_game(sstate->state); + sfree(sstate->dotdsf); + sfree(sstate->looplen); + sfree(sstate->dot_solved); + sfree(sstate->face_solved); + sfree(sstate->dot_yes_count); + sfree(sstate->dot_no_count); + sfree(sstate->face_yes_count); + sfree(sstate->face_no_count); + + /* OK, because sfree(NULL) is a no-op */ + sfree(sstate->dlines); + sfree(sstate->linedsf); + + sfree(sstate); + } +} + +static solver_state *dup_solver_state(const solver_state *sstate) { + game_state *state = sstate->state; + int num_dots = state->game_grid->num_dots; + int num_faces = state->game_grid->num_faces; + int num_edges = state->game_grid->num_edges; + solver_state *ret = snew(solver_state); + + ret->state = state = dup_game(sstate->state); + + ret->solver_status = sstate->solver_status; + ret->diff = sstate->diff; + + ret->dotdsf = snewn(num_dots, int); + ret->looplen = snewn(num_dots, int); + memcpy(ret->dotdsf, sstate->dotdsf, + num_dots * sizeof(int)); + memcpy(ret->looplen, sstate->looplen, + num_dots * sizeof(int)); + + ret->dot_solved = snewn(num_dots, char); + ret->face_solved = snewn(num_faces, char); + memcpy(ret->dot_solved, sstate->dot_solved, num_dots); + memcpy(ret->face_solved, sstate->face_solved, num_faces); + + ret->dot_yes_count = snewn(num_dots, char); + memcpy(ret->dot_yes_count, sstate->dot_yes_count, num_dots); + ret->dot_no_count = snewn(num_dots, char); + memcpy(ret->dot_no_count, sstate->dot_no_count, num_dots); + + ret->face_yes_count = snewn(num_faces, char); + memcpy(ret->face_yes_count, sstate->face_yes_count, num_faces); + ret->face_no_count = snewn(num_faces, char); + memcpy(ret->face_no_count, sstate->face_no_count, num_faces); + + if (sstate->dlines) { + ret->dlines = snewn(2*num_edges, char); + memcpy(ret->dlines, sstate->dlines, + 2*num_edges); + } else { + ret->dlines = NULL; + } + + if (sstate->linedsf) { + ret->linedsf = snewn(num_edges, int); + memcpy(ret->linedsf, sstate->linedsf, + num_edges * sizeof(int)); + } else { + ret->linedsf = NULL; + } + + return ret; +} + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + +#ifdef SLOW_SYSTEM + ret->h = 7; + ret->w = 7; +#else + ret->h = 10; + ret->w = 10; +#endif + ret->diff = DIFF_EASY; + ret->type = 0; + + return ret; +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + + *ret = *params; /* structure copy */ + return ret; +} + +static const game_params presets[] = { +#ifdef SMALL_SCREEN + { 7, 7, DIFF_EASY, 0 }, + { 7, 7, DIFF_NORMAL, 0 }, + { 7, 7, DIFF_HARD, 0 }, + { 7, 7, DIFF_HARD, 1 }, + { 7, 7, DIFF_HARD, 2 }, + { 5, 5, DIFF_HARD, 3 }, + { 7, 7, DIFF_HARD, 4 }, + { 5, 4, DIFF_HARD, 5 }, + { 5, 5, DIFF_HARD, 6 }, + { 5, 5, DIFF_HARD, 7 }, + { 3, 3, DIFF_HARD, 8 }, + { 3, 3, DIFF_HARD, 9 }, + { 3, 3, DIFF_HARD, 10 }, + { 6, 6, DIFF_HARD, 11 }, + { 6, 6, DIFF_HARD, 12 }, +#else + { 7, 7, DIFF_EASY, 0 }, + { 10, 10, DIFF_EASY, 0 }, + { 7, 7, DIFF_NORMAL, 0 }, + { 10, 10, DIFF_NORMAL, 0 }, + { 7, 7, DIFF_HARD, 0 }, + { 10, 10, DIFF_HARD, 0 }, + { 10, 10, DIFF_HARD, 1 }, + { 12, 10, DIFF_HARD, 2 }, + { 7, 7, DIFF_HARD, 3 }, + { 9, 9, DIFF_HARD, 4 }, + { 5, 4, DIFF_HARD, 5 }, + { 7, 7, DIFF_HARD, 6 }, + { 5, 5, DIFF_HARD, 7 }, + { 5, 5, DIFF_HARD, 8 }, + { 5, 4, DIFF_HARD, 9 }, + { 5, 4, DIFF_HARD, 10 }, + { 10, 10, DIFF_HARD, 11 }, + { 10, 10, DIFF_HARD, 12 } +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *tmppar; + char buf[80]; + + if (i < 0 || i >= lenof(presets)) + return FALSE; + + tmppar = snew(game_params); + *tmppar = presets[i]; + *params = tmppar; + sprintf(buf, "%dx%d %s - %s", tmppar->h, tmppar->w, + gridnames[tmppar->type], diffnames[tmppar->diff]); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static void decode_params(game_params *params, char const *string) +{ + params->h = params->w = atoi(string); + params->diff = DIFF_EASY; + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 't') { + string++; + params->type = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFF_MAX; i++) + if (*string == diffchars[i]) + params->diff = i; + if (*string) string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char str[80]; + sprintf(str, "%dx%dt%d", params->w, params->h, params->type); + if (full) + sprintf(str + strlen(str), "d%c", diffchars[params->diff]); + return dupstr(str); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Grid type"; + ret[2].type = C_CHOICES; + ret[2].sval = GRID_CONFIGS; + ret[2].ival = params->type; + + ret[3].name = "Difficulty"; + ret[3].type = C_CHOICES; + ret[3].sval = DIFFCONFIG; + ret[3].ival = params->diff; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->type = cfg[2].ival; + ret->diff = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->type < 0 || params->type >= NUM_GRID_TYPES) + return "Illegal grid type"; + if (params->w < grid_size_limits[params->type].amin || + params->h < grid_size_limits[params->type].amin) + return grid_size_limits[params->type].aerr; + if (params->w < grid_size_limits[params->type].omin && + params->h < grid_size_limits[params->type].omin) + return grid_size_limits[params->type].oerr; + + /* + * This shouldn't be able to happen at all, since decode_params + * and custom_params will never generate anything that isn't + * within range. + */ + assert(params->diff < DIFF_MAX); + + return NULL; +} + +/* Returns a newly allocated string describing the current puzzle */ +static char *state_to_text(const game_state *state) +{ + grid *g = state->game_grid; + char *retval; + int num_faces = g->num_faces; + char *description = snewn(num_faces + 1, char); + char *dp = description; + int empty_count = 0; + int i; + + for (i = 0; i < num_faces; i++) { + if (state->clues[i] < 0) { + if (empty_count > 25) { + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); + empty_count = 0; + } + empty_count++; + } else { + if (empty_count) { + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); + empty_count = 0; + } + dp += sprintf(dp, "%c", (int)CLUE2CHAR(state->clues[i])); + } + } + + if (empty_count) + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); + + retval = dupstr(description); + sfree(description); + + return retval; +} + +#define GRID_DESC_SEP '_' + +/* Splits up a (optional) grid_desc from the game desc. Returns the + * grid_desc (which needs freeing) and updates the desc pointer to + * start of real desc, or returns NULL if no desc. */ +static char *extract_grid_desc(const char **desc) +{ + char *sep = strchr(*desc, GRID_DESC_SEP), *gd; + int gd_len; + + if (!sep) return NULL; + + gd_len = sep - (*desc); + gd = snewn(gd_len+1, char); + memcpy(gd, *desc, gd_len); + gd[gd_len] = '\0'; + + *desc = sep+1; + + return gd; +} + +/* We require that the params pass the test in validate_params and that the + * description fills the entire game area */ +static char *validate_desc(const game_params *params, const char *desc) +{ + int count = 0; + grid *g; + char *grid_desc, *ret; + + /* It's pretty inefficient to do this just for validation. All we need to + * know is the precise number of faces. */ + grid_desc = extract_grid_desc(&desc); + ret = grid_validate_desc(grid_types[params->type], params->w, params->h, grid_desc); + if (ret) return ret; + + g = loopy_generate_grid(params, grid_desc); + if (grid_desc) sfree(grid_desc); + + for (; *desc; ++desc) { + if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) { + count++; + continue; + } + if (*desc >= 'a') { + count += *desc - 'a' + 1; + continue; + } + return "Unknown character in description"; + } + + if (count < g->num_faces) + return "Description too short for board size"; + if (count > g->num_faces) + return "Description too long for board size"; + + grid_free(g); + + return NULL; +} + +/* Sums the lengths of the numbers in range [0,n) */ +/* See equivalent function in solo.c for justification of this. */ +static int len_0_to_n(int n) +{ + int len = 1; /* Counting 0 as a bit of a special case */ + int i; + + for (i = 1; i < n; i *= 10) { + len += max(n - i, 0); + } + + return len; +} + +static char *encode_solve_move(const game_state *state) +{ + int len; + char *ret, *p; + int i; + int num_edges = state->game_grid->num_edges; + + /* This is going to return a string representing the moves needed to set + * every line in a grid to be the same as the ones in 'state'. The exact + * length of this string is predictable. */ + + len = 1; /* Count the 'S' prefix */ + /* Numbers in all lines */ + len += len_0_to_n(num_edges); + /* For each line we also have a letter */ + len += num_edges; + + ret = snewn(len + 1, char); + p = ret; + + p += sprintf(p, "S"); + + for (i = 0; i < num_edges; i++) { + switch (state->lines[i]) { + case LINE_YES: + p += sprintf(p, "%dy", i); + break; + case LINE_NO: + p += sprintf(p, "%dn", i); + break; + } + } + + /* No point in doing sums like that if they're going to be wrong */ + assert(strlen(ret) <= (size_t)len); + return ret; +} + +static game_ui *new_ui(const game_state *state) +{ + return NULL; +} + +static void free_ui(game_ui *ui) +{ +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + int grid_width, grid_height, rendered_width, rendered_height; + int g_tilesize; + + grid_compute_size(grid_types[params->type], params->w, params->h, + &g_tilesize, &grid_width, &grid_height); + + /* multiply first to minimise rounding error on integer division */ + rendered_width = grid_width * tilesize / g_tilesize; + rendered_height = grid_height * tilesize / g_tilesize; + *x = rendered_width + 2 * BORDER(tilesize) + 1; + *y = rendered_height + 2 * BORDER(tilesize) + 1; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_FOREGROUND * 3 + 0] = 0.0F; + ret[COL_FOREGROUND * 3 + 1] = 0.0F; + ret[COL_FOREGROUND * 3 + 2] = 0.0F; + + /* + * We want COL_LINEUNKNOWN to be a yellow which is a bit darker + * than the background. (I previously set it to 0.8,0.8,0, but + * found that this went badly with the 0.8,0.8,0.8 favoured as a + * background by the Java frontend.) + */ + ret[COL_LINEUNKNOWN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F; + ret[COL_LINEUNKNOWN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F; + ret[COL_LINEUNKNOWN * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 1] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 2] = 1.0F; + + ret[COL_MISTAKE * 3 + 0] = 1.0F; + ret[COL_MISTAKE * 3 + 1] = 0.0F; + ret[COL_MISTAKE * 3 + 2] = 0.0F; + + ret[COL_SATISFIED * 3 + 0] = 0.0F; + ret[COL_SATISFIED * 3 + 1] = 0.0F; + ret[COL_SATISFIED * 3 + 2] = 0.0F; + + /* We want the faint lines to be a bit darker than the background. + * Except if the background is pretty dark already; then it ought to be a + * bit lighter. Oy vey. + */ + ret[COL_FAINT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F; + ret[COL_FAINT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F; + ret[COL_FAINT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.9F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int num_faces = state->game_grid->num_faces; + int num_edges = state->game_grid->num_edges; + int i; + + ds->tilesize = 0; + ds->started = 0; + ds->lines = snewn(num_edges, char); + ds->clue_error = snewn(num_faces, char); + ds->clue_satisfied = snewn(num_faces, char); + ds->textx = snewn(num_faces, int); + ds->texty = snewn(num_faces, int); + ds->flashing = 0; + + memset(ds->lines, LINE_UNKNOWN, num_edges); + memset(ds->clue_error, 0, num_faces); + memset(ds->clue_satisfied, 0, num_faces); + for (i = 0; i < num_faces; i++) + ds->textx[i] = ds->texty[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->textx); + sfree(ds->texty); + sfree(ds->clue_error); + sfree(ds->clue_satisfied); + sfree(ds->lines); + sfree(ds); +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + if (params->type != 0) + return FALSE; + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w, h, W, H; + int x, y, i; + int cell_size; + char *ret; + grid *g = state->game_grid; + grid_face *f; + + assert(state->grid_type == 0); + + /* Work out the basic size unit */ + f = g->faces; /* first face */ + assert(f->order == 4); + /* The dots are ordered clockwise, so the two opposite + * corners are guaranteed to span the square */ + cell_size = abs(f->dots[0]->x - f->dots[2]->x); + + w = (g->highest_x - g->lowest_x) / cell_size; + h = (g->highest_y - g->lowest_y) / cell_size; + + /* Create a blank "canvas" to "draw" on */ + W = 2 * w + 2; + H = 2 * h + 1; + ret = snewn(W * H + 1, char); + for (y = 0; y < H; y++) { + for (x = 0; x < W-1; x++) { + ret[y*W + x] = ' '; + } + ret[y*W + W-1] = '\n'; + } + ret[H*W] = '\0'; + + /* Fill in edge info */ + for (i = 0; i < g->num_edges; i++) { + grid_edge *e = g->edges + i; + /* Cell coordinates, from (0,0) to (w-1,h-1) */ + int x1 = (e->dot1->x - g->lowest_x) / cell_size; + int x2 = (e->dot2->x - g->lowest_x) / cell_size; + int y1 = (e->dot1->y - g->lowest_y) / cell_size; + int y2 = (e->dot2->y - g->lowest_y) / cell_size; + /* Midpoint, in canvas coordinates (canvas coordinates are just twice + * cell coordinates) */ + x = x1 + x2; + y = y1 + y2; + switch (state->lines[i]) { + case LINE_YES: + ret[y*W + x] = (y1 == y2) ? '-' : '|'; + break; + case LINE_NO: + ret[y*W + x] = 'x'; + break; + case LINE_UNKNOWN: + break; /* already a space */ + default: + assert(!"Illegal line state"); + } + } + + /* Fill in clues */ + for (i = 0; i < g->num_faces; i++) { + int x1, x2, y1, y2; + + f = g->faces + i; + assert(f->order == 4); + /* Cell coordinates, from (0,0) to (w-1,h-1) */ + x1 = (f->dots[0]->x - g->lowest_x) / cell_size; + x2 = (f->dots[2]->x - g->lowest_x) / cell_size; + y1 = (f->dots[0]->y - g->lowest_y) / cell_size; + y2 = (f->dots[2]->y - g->lowest_y) / cell_size; + /* Midpoint, in canvas coordinates */ + x = x1 + x2; + y = y1 + y2; + ret[y*W + x] = CLUE2CHAR(state->clues[i]); + } + return ret; +} + +/* ---------------------------------------------------------------------- + * Debug code + */ + +#ifdef DEBUG_CACHES +static void check_caches(const solver_state* sstate) +{ + int i; + const game_state *state = sstate->state; + const grid *g = state->game_grid; + + for (i = 0; i < g->num_dots; i++) { + assert(dot_order(state, i, LINE_YES) == sstate->dot_yes_count[i]); + assert(dot_order(state, i, LINE_NO) == sstate->dot_no_count[i]); + } + + for (i = 0; i < g->num_faces; i++) { + assert(face_order(state, i, LINE_YES) == sstate->face_yes_count[i]); + assert(face_order(state, i, LINE_NO) == sstate->face_no_count[i]); + } +} + +#if 0 +#define check_caches(s) \ + do { \ + fprintf(stderr, "check_caches at line %d\n", __LINE__); \ + check_caches(s); \ + } while (0) +#endif +#endif /* DEBUG_CACHES */ + +/* ---------------------------------------------------------------------- + * Solver utility functions + */ + +/* Sets the line (with index i) to the new state 'line_new', and updates + * the cached counts of any affected faces and dots. + * Returns TRUE if this actually changed the line's state. */ +static int solver_set_line(solver_state *sstate, int i, + enum line_state line_new +#ifdef SHOW_WORKING + , const char *reason +#endif + ) +{ + game_state *state = sstate->state; + grid *g; + grid_edge *e; + + assert(line_new != LINE_UNKNOWN); + + check_caches(sstate); + + if (state->lines[i] == line_new) { + return FALSE; /* nothing changed */ + } + state->lines[i] = line_new; + +#ifdef SHOW_WORKING + fprintf(stderr, "solver: set line [%d] to %s (%s)\n", + i, line_new == LINE_YES ? "YES" : "NO", + reason); +#endif + + g = state->game_grid; + e = g->edges + i; + + /* Update the cache for both dots and both faces affected by this. */ + if (line_new == LINE_YES) { + sstate->dot_yes_count[e->dot1 - g->dots]++; + sstate->dot_yes_count[e->dot2 - g->dots]++; + if (e->face1) { + sstate->face_yes_count[e->face1 - g->faces]++; + } + if (e->face2) { + sstate->face_yes_count[e->face2 - g->faces]++; + } + } else { + sstate->dot_no_count[e->dot1 - g->dots]++; + sstate->dot_no_count[e->dot2 - g->dots]++; + if (e->face1) { + sstate->face_no_count[e->face1 - g->faces]++; + } + if (e->face2) { + sstate->face_no_count[e->face2 - g->faces]++; + } + } + + check_caches(sstate); + return TRUE; +} + +#ifdef SHOW_WORKING +#define solver_set_line(a, b, c) \ + solver_set_line(a, b, c, __FUNCTION__) +#endif + +/* + * Merge two dots due to the existence of an edge between them. + * Updates the dsf tracking equivalence classes, and keeps track of + * the length of path each dot is currently a part of. + * Returns TRUE if the dots were already linked, ie if they are part of a + * closed loop, and false otherwise. + */ +static int merge_dots(solver_state *sstate, int edge_index) +{ + int i, j, len; + grid *g = sstate->state->game_grid; + grid_edge *e = g->edges + edge_index; + + i = e->dot1 - g->dots; + j = e->dot2 - g->dots; + + i = dsf_canonify(sstate->dotdsf, i); + j = dsf_canonify(sstate->dotdsf, j); + + if (i == j) { + return TRUE; + } else { + len = sstate->looplen[i] + sstate->looplen[j]; + dsf_merge(sstate->dotdsf, i, j); + i = dsf_canonify(sstate->dotdsf, i); + sstate->looplen[i] = len; + return FALSE; + } +} + +/* Merge two lines because the solver has deduced that they must be either + * identical or opposite. Returns TRUE if this is new information, otherwise + * FALSE. */ +static int merge_lines(solver_state *sstate, int i, int j, int inverse +#ifdef SHOW_WORKING + , const char *reason +#endif + ) +{ + int inv_tmp; + + assert(i < sstate->state->game_grid->num_edges); + assert(j < sstate->state->game_grid->num_edges); + + i = edsf_canonify(sstate->linedsf, i, &inv_tmp); + inverse ^= inv_tmp; + j = edsf_canonify(sstate->linedsf, j, &inv_tmp); + inverse ^= inv_tmp; + + edsf_merge(sstate->linedsf, i, j, inverse); + +#ifdef SHOW_WORKING + if (i != j) { + fprintf(stderr, "%s [%d] [%d] %s(%s)\n", + __FUNCTION__, i, j, + inverse ? "inverse " : "", reason); + } +#endif + return (i != j); +} + +#ifdef SHOW_WORKING +#define merge_lines(a, b, c, d) \ + merge_lines(a, b, c, d, __FUNCTION__) +#endif + +/* Count the number of lines of a particular type currently going into the + * given dot. */ +static int dot_order(const game_state* state, int dot, char line_type) +{ + int n = 0; + grid *g = state->game_grid; + grid_dot *d = g->dots + dot; + int i; + + for (i = 0; i < d->order; i++) { + grid_edge *e = d->edges[i]; + if (state->lines[e - g->edges] == line_type) + ++n; + } + return n; +} + +/* Count the number of lines of a particular type currently surrounding the + * given face */ +static int face_order(const game_state* state, int face, char line_type) +{ + int n = 0; + grid *g = state->game_grid; + grid_face *f = g->faces + face; + int i; + + for (i = 0; i < f->order; i++) { + grid_edge *e = f->edges[i]; + if (state->lines[e - g->edges] == line_type) + ++n; + } + return n; +} + +/* Set all lines bordering a dot of type old_type to type new_type + * Return value tells caller whether this function actually did anything */ +static int dot_setall(solver_state *sstate, int dot, + char old_type, char new_type) +{ + int retval = FALSE, r; + game_state *state = sstate->state; + grid *g; + grid_dot *d; + int i; + + if (old_type == new_type) + return FALSE; + + g = state->game_grid; + d = g->dots + dot; + + for (i = 0; i < d->order; i++) { + int line_index = d->edges[i] - g->edges; + if (state->lines[line_index] == old_type) { + r = solver_set_line(sstate, line_index, new_type); + assert(r == TRUE); + retval = TRUE; + } + } + return retval; +} + +/* Set all lines bordering a face of type old_type to type new_type */ +static int face_setall(solver_state *sstate, int face, + char old_type, char new_type) +{ + int retval = FALSE, r; + game_state *state = sstate->state; + grid *g; + grid_face *f; + int i; + + if (old_type == new_type) + return FALSE; + + g = state->game_grid; + f = g->faces + face; + + for (i = 0; i < f->order; i++) { + int line_index = f->edges[i] - g->edges; + if (state->lines[line_index] == old_type) { + r = solver_set_line(sstate, line_index, new_type); + assert(r == TRUE); + retval = TRUE; + } + } + return retval; +} + +/* ---------------------------------------------------------------------- + * Loop generation and clue removal + */ + +static void add_full_clues(game_state *state, random_state *rs) +{ + signed char *clues = state->clues; + grid *g = state->game_grid; + char *board = snewn(g->num_faces, char); + int i; + + generate_loop(g, board, rs, NULL, NULL); + + /* Fill out all the clues by initialising to 0, then iterating over + * all edges and incrementing each clue as we find edges that border + * between BLACK/WHITE faces. While we're at it, we verify that the + * algorithm does work, and there aren't any GREY faces still there. */ + memset(clues, 0, g->num_faces); + for (i = 0; i < g->num_edges; i++) { + grid_edge *e = g->edges + i; + grid_face *f1 = e->face1; + grid_face *f2 = e->face2; + enum face_colour c1 = FACE_COLOUR(f1); + enum face_colour c2 = FACE_COLOUR(f2); + assert(c1 != FACE_GREY); + assert(c2 != FACE_GREY); + if (c1 != c2) { + if (f1) clues[f1 - g->faces]++; + if (f2) clues[f2 - g->faces]++; + } + } + sfree(board); +} + + +static int game_has_unique_soln(const game_state *state, int diff) +{ + int ret; + solver_state *sstate_new; + solver_state *sstate = new_solver_state((game_state *)state, diff); + + sstate_new = solve_game_rec(sstate); + + assert(sstate_new->solver_status != SOLVER_MISTAKE); + ret = (sstate_new->solver_status == SOLVER_SOLVED); + + free_solver_state(sstate_new); + free_solver_state(sstate); + + return ret; +} + + +/* Remove clues one at a time at random. */ +static game_state *remove_clues(game_state *state, random_state *rs, + int diff) +{ + int *face_list; + int num_faces = state->game_grid->num_faces; + game_state *ret = dup_game(state), *saved_ret; + int n; + + /* We need to remove some clues. We'll do this by forming a list of all + * available clues, shuffling it, then going along one at a + * time clearing each clue in turn for which doing so doesn't render the + * board unsolvable. */ + face_list = snewn(num_faces, int); + for (n = 0; n < num_faces; ++n) { + face_list[n] = n; + } + + shuffle(face_list, num_faces, sizeof(int), rs); + + for (n = 0; n < num_faces; ++n) { + saved_ret = dup_game(ret); + ret->clues[face_list[n]] = -1; + + if (game_has_unique_soln(ret, diff)) { + free_game(saved_ret); + } else { + free_game(ret); + ret = saved_ret; + } + } + sfree(face_list); + + return ret; +} + + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + /* solution and description both use run-length encoding in obvious ways */ + char *retval, *game_desc, *grid_desc; + grid *g; + game_state *state = snew(game_state); + game_state *state_new; + + grid_desc = grid_new_desc(grid_types[params->type], params->w, params->h, rs); + state->game_grid = g = loopy_generate_grid(params, grid_desc); + + state->clues = snewn(g->num_faces, signed char); + state->lines = snewn(g->num_edges, char); + state->line_errors = snewn(g->num_edges, unsigned char); + state->exactly_one_loop = FALSE; + + state->grid_type = params->type; + + newboard_please: + + memset(state->lines, LINE_UNKNOWN, g->num_edges); + memset(state->line_errors, 0, g->num_edges); + + state->solved = state->cheated = FALSE; + + /* Get a new random solvable board with all its clues filled in. Yes, this + * can loop for ever if the params are suitably unfavourable, but + * preventing games smaller than 4x4 seems to stop this happening */ + do { + add_full_clues(state, rs); + } while (!game_has_unique_soln(state, params->diff)); + + state_new = remove_clues(state, rs, params->diff); + free_game(state); + state = state_new; + + + if (params->diff > 0 && game_has_unique_soln(state, params->diff-1)) { +#ifdef SHOW_WORKING + fprintf(stderr, "Rejecting board, it is too easy\n"); +#endif + goto newboard_please; + } + + game_desc = state_to_text(state); + + free_game(state); + + if (grid_desc) { + retval = snewn(strlen(grid_desc) + 1 + strlen(game_desc) + 1, char); + sprintf(retval, "%s%c%s", grid_desc, (int)GRID_DESC_SEP, game_desc); + sfree(grid_desc); + sfree(game_desc); + } else { + retval = game_desc; + } + + assert(!validate_desc(params, retval)); + + return retval; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int i; + game_state *state = snew(game_state); + int empties_to_make = 0; + int n,n2; + const char *dp; + char *grid_desc; + grid *g; + int num_faces, num_edges; + + grid_desc = extract_grid_desc(&desc); + state->game_grid = g = loopy_generate_grid(params, grid_desc); + if (grid_desc) sfree(grid_desc); + + dp = desc; + + num_faces = g->num_faces; + num_edges = g->num_edges; + + state->clues = snewn(num_faces, signed char); + state->lines = snewn(num_edges, char); + state->line_errors = snewn(num_edges, unsigned char); + state->exactly_one_loop = FALSE; + + state->solved = state->cheated = FALSE; + + state->grid_type = params->type; + + for (i = 0; i < num_faces; i++) { + if (empties_to_make) { + empties_to_make--; + state->clues[i] = -1; + continue; + } + + assert(*dp); + n = *dp - '0'; + n2 = *dp - 'A' + 10; + if (n >= 0 && n < 10) { + state->clues[i] = n; + } else if (n2 >= 10 && n2 < 36) { + state->clues[i] = n2; + } else { + n = *dp - 'a' + 1; + assert(n > 0); + state->clues[i] = -1; + empties_to_make = n - 1; + } + ++dp; + } + + memset(state->lines, LINE_UNKNOWN, num_edges); + memset(state->line_errors, 0, num_edges); + return state; +} + +/* Calculates the line_errors data, and checks if the current state is a + * solution */ +static int check_completion(game_state *state) +{ + grid *g = state->game_grid; + int i, ret; + int *dsf, *component_state; + int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize; + enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY }; + + memset(state->line_errors, 0, g->num_edges); + + /* + * Find loops in the grid, and determine whether the puzzle is + * solved. + * + * Loopy is a bit more complicated than most puzzles that care + * about loop detection. In most of them, loops are simply + * _forbidden_; so the obviously right way to do + * error-highlighting during play is to light up a graph edge red + * iff it is part of a loop, which is exactly what the centralised + * findloop.c makes easy. + * + * But Loopy is unusual in that you're _supposed_ to be making a + * loop - and yet _some_ loops are not the right loop. So we need + * to be more discriminating, by identifying loops one by one and + * then thinking about which ones to highlight, and so findloop.c + * isn't quite the right tool for the job in this case. + * + * Worse still, consider situations in which the grid contains a + * loop and also some non-loop edges: there are some cases like + * this in which the user's intuitive expectation would be to + * highlight the loop (if you're only about half way through the + * puzzle and have accidentally made a little loop in some corner + * of the grid), and others in which they'd be more likely to + * expect you to highlight the non-loop edges (if you've just + * closed off a whole loop that you thought was the entire + * solution, but forgot some disconnected edges in a corner + * somewhere). So while it's easy enough to check whether the + * solution is _right_, highlighting the wrong parts is a tricky + * problem for this puzzle! + * + * I'd quite like, in some situations, to identify the largest + * loop among the player's YES edges, and then light up everything + * other than that. But finding the longest cycle in a graph is an + * NP-complete problem (because, in particular, it must return a + * Hamilton cycle if one exists). + * + * However, I think we can make the problem tractable by + * exercising the Puzzles principle that it isn't absolutely + * necessary to highlight _all_ errors: the key point is that by + * the time the user has filled in the whole grid, they should + * either have seen a completion flash, or have _some_ error + * highlight showing them why the solution isn't right. So in + * principle it would be *just about* good enough to highlight + * just one error in the whole grid, if there was really no better + * way. But we'd like to highlight as many errors as possible. + * + * In this case, I think the simple approach is to make use of the + * fact that no vertex may have degree > 2, and that's really + * simple to detect. So the plan goes like this: + * + * - Form the dsf of connected components of the graph vertices. + * + * - Highlight an error at any vertex with degree > 2. (It so + * happens that we do this by lighting up all the edges + * incident to that vertex, but that's an output detail.) + * + * - Any component that contains such a vertex is now excluded + * from further consideration, because it already has a + * highlight. + * + * - The remaining components have no vertex with degree > 2, and + * hence they all consist of either a simple loop, or a simple + * path with two endpoints. + * + * - For these purposes, group together all the paths and imagine + * them to be a single component (because in most normal + * situations the player will gradually build up the solution + * _not_ all in one connected segment, but as lots of separate + * little path pieces that gradually connect to each other). + * + * - After doing that, if there is exactly one (sensible) + * component - be it a collection of paths or a loop - then + * highlight no further edge errors. (The former case is normal + * during play, and the latter is a potentially solved puzzle.) + * + * - Otherwise, find the largest of the sensible components, + * leave that one unhighlighted, and light the rest up in red. + */ + + dsf = snew_dsf(g->num_dots); + + /* Build the dsf. */ + for (i = 0; i < g->num_edges; i++) { + if (state->lines[i] == LINE_YES) { + grid_edge *e = g->edges + i; + int d1 = e->dot1 - g->dots, d2 = e->dot2 - g->dots; + dsf_merge(dsf, d1, d2); + } + } + + /* Initialise a state variable for each connected component. */ + component_state = snewn(g->num_dots, int); + for (i = 0; i < g->num_dots; i++) { + if (dsf_canonify(dsf, i) == i) + component_state[i] = COMP_LOOP; + else + component_state[i] = COMP_NONE; + } + + /* Check for dots with degree > 3. Here we also spot dots of + * degree 1 in which the user has marked all the non-edges as + * LINE_NO, because those are also clear vertex-level errors, so + * we give them the same treatment of excluding their connected + * component from the subsequent loop analysis. */ + for (i = 0; i < g->num_dots; i++) { + int comp = dsf_canonify(dsf, i); + int yes = dot_order(state, i, LINE_YES); + int unknown = dot_order(state, i, LINE_UNKNOWN); + if ((yes == 1 && unknown == 0) || (yes >= 3)) { + /* violation, so mark all YES edges as errors */ + grid_dot *d = g->dots + i; + int j; + for (j = 0; j < d->order; j++) { + int e = d->edges[j] - g->edges; + if (state->lines[e] == LINE_YES) + state->line_errors[e] = TRUE; + } + /* And mark this component as not worthy of further + * consideration. */ + component_state[comp] = COMP_SILLY; + + } else if (yes == 0) { + /* A completely isolated dot must also be excluded it from + * the subsequent loop highlighting pass, but we tag it + * with a different enum value to avoid it counting + * towards the components that inhibit returning a win + * status. */ + component_state[comp] = COMP_EMPTY; + } else if (yes == 1) { + /* A dot with degree 1 that didn't fall into the 'clearly + * erroneous' case above indicates that this connected + * component will be a path rather than a loop - unless + * something worse elsewhere in the component has + * classified it as silly. */ + if (component_state[comp] != COMP_SILLY) + component_state[comp] = COMP_PATH; + } + } + + /* Count up the components. Also, find the largest sensible + * component. (Tie-breaking condition is derived from the order of + * vertices in the grid data structure, which is fairly arbitrary + * but at least stays stable throughout the game.) */ + nsilly = nloop = npath = 0; + total_pathsize = 0; + largest_comp = largest_size = -1; + for (i = 0; i < g->num_dots; i++) { + if (component_state[i] == COMP_SILLY) { + nsilly++; + } else if (component_state[i] == COMP_PATH) { + total_pathsize += dsf_size(dsf, i); + npath = 1; + } else if (component_state[i] == COMP_LOOP) { + int this_size; + + nloop++; + + if ((this_size = dsf_size(dsf, i)) > largest_size) { + largest_comp = i; + largest_size = this_size; + } + } + } + if (largest_size < total_pathsize) { + largest_comp = -1; /* means the paths */ + largest_size = total_pathsize; + } + + if (nloop > 0 && nloop + npath > 1) { + /* + * If there are at least two sensible components including at + * least one loop, highlight all edges in every sensible + * component that is not the largest one. + */ + for (i = 0; i < g->num_edges; i++) { + if (state->lines[i] == LINE_YES) { + grid_edge *e = g->edges + i; + int d1 = e->dot1 - g->dots; /* either endpoint is good enough */ + int comp = dsf_canonify(dsf, d1); + if ((component_state[comp] == COMP_PATH && + -1 != largest_comp) || + (component_state[comp] == COMP_LOOP && + comp != largest_comp)) + state->line_errors[i] = TRUE; + } + } + } + + if (nloop == 1 && npath == 0 && nsilly == 0) { + /* + * If there is exactly one component and it is a loop, then + * the puzzle is potentially complete, so check the clues. + */ + ret = TRUE; + + for (i = 0; i < g->num_faces; i++) { + int c = state->clues[i]; + if (c >= 0 && face_order(state, i, LINE_YES) != c) { + ret = FALSE; + break; + } + } + + /* + * Also, whether or not the puzzle is actually complete, set + * the flag that says this game_state has exactly one loop and + * nothing else, which will be used to vary the semantics of + * clue highlighting at display time. + */ + state->exactly_one_loop = TRUE; + } else { + ret = FALSE; + state->exactly_one_loop = FALSE; + } + + sfree(component_state); + sfree(dsf); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Solver logic + * + * Our solver modes operate as follows. Each mode also uses the modes above it. + * + * Easy Mode + * Just implement the rules of the game. + * + * Normal and Tricky Modes + * For each (adjacent) pair of lines through each dot we store a bit for + * whether at least one of them is on and whether at most one is on. (If we + * know both or neither is on that's already stored more directly.) + * + * Advanced Mode + * Use edsf data structure to make equivalence classes of lines that are + * known identical to or opposite to one another. + */ + + +/* DLines: + * For general grids, we consider "dlines" to be pairs of lines joined + * at a dot. The lines must be adjacent around the dot, so we can think of + * a dline as being a dot+face combination. Or, a dot+edge combination where + * the second edge is taken to be the next clockwise edge from the dot. + * Original loopy code didn't have this extra restriction of the lines being + * adjacent. From my tests with square grids, this extra restriction seems to + * take little, if anything, away from the quality of the puzzles. + * A dline can be uniquely identified by an edge/dot combination, given that + * a dline-pair always goes clockwise around its common dot. The edge/dot + * combination can be represented by an edge/bool combination - if bool is + * TRUE, use edge->dot1 else use edge->dot2. So the total number of dlines is + * exactly twice the number of edges in the grid - although the dlines + * spanning the infinite face are not all that useful to the solver. + * Note that, by convention, a dline goes clockwise around its common dot, + * which means the dline goes anti-clockwise around its common face. + */ + +/* Helper functions for obtaining an index into an array of dlines, given + * various information. We assume the grid layout conventions about how + * the various lists are interleaved - see grid_make_consistent() for + * details. */ + +/* i points to the first edge of the dline pair, reading clockwise around + * the dot. */ +static int dline_index_from_dot(grid *g, grid_dot *d, int i) +{ + grid_edge *e = d->edges[i]; + int ret; +#ifdef DEBUG_DLINES + grid_edge *e2; + int i2 = i+1; + if (i2 == d->order) i2 = 0; + e2 = d->edges[i2]; +#endif + ret = 2 * (e - g->edges) + ((e->dot1 == d) ? 1 : 0); +#ifdef DEBUG_DLINES + printf("dline_index_from_dot: d=%d,i=%d, edges [%d,%d] - %d\n", + (int)(d - g->dots), i, (int)(e - g->edges), + (int)(e2 - g->edges), ret); +#endif + return ret; +} +/* i points to the second edge of the dline pair, reading clockwise around + * the face. That is, the edges of the dline, starting at edge{i}, read + * anti-clockwise around the face. By layout conventions, the common dot + * of the dline will be f->dots[i] */ +static int dline_index_from_face(grid *g, grid_face *f, int i) +{ + grid_edge *e = f->edges[i]; + grid_dot *d = f->dots[i]; + int ret; +#ifdef DEBUG_DLINES + grid_edge *e2; + int i2 = i - 1; + if (i2 < 0) i2 += f->order; + e2 = f->edges[i2]; +#endif + ret = 2 * (e - g->edges) + ((e->dot1 == d) ? 1 : 0); +#ifdef DEBUG_DLINES + printf("dline_index_from_face: f=%d,i=%d, edges [%d,%d] - %d\n", + (int)(f - g->faces), i, (int)(e - g->edges), + (int)(e2 - g->edges), ret); +#endif + return ret; +} +static int is_atleastone(const char *dline_array, int index) +{ + return BIT_SET(dline_array[index], 0); +} +static int set_atleastone(char *dline_array, int index) +{ + return SET_BIT(dline_array[index], 0); +} +static int is_atmostone(const char *dline_array, int index) +{ + return BIT_SET(dline_array[index], 1); +} +static int set_atmostone(char *dline_array, int index) +{ + return SET_BIT(dline_array[index], 1); +} + +static void array_setall(char *array, char from, char to, int len) +{ + char *p = array, *p_old = p; + int len_remaining = len; + + while ((p = memchr(p, from, len_remaining))) { + *p = to; + len_remaining -= p - p_old; + p_old = p; + } +} + +/* Helper, called when doing dline dot deductions, in the case where we + * have 4 UNKNOWNs, and two of them (adjacent) have *exactly* one YES between + * them (because of dline atmostone/atleastone). + * On entry, edge points to the first of these two UNKNOWNs. This function + * will find the opposite UNKNOWNS (if they are adjacent to one another) + * and set their corresponding dline to atleastone. (Setting atmostone + * already happens in earlier dline deductions) */ +static int dline_set_opp_atleastone(solver_state *sstate, + grid_dot *d, int edge) +{ + game_state *state = sstate->state; + grid *g = state->game_grid; + int N = d->order; + int opp, opp2; + for (opp = 0; opp < N; opp++) { + int opp_dline_index; + if (opp == edge || opp == edge+1 || opp == edge-1) + continue; + if (opp == 0 && edge == N-1) + continue; + if (opp == N-1 && edge == 0) + continue; + opp2 = opp + 1; + if (opp2 == N) opp2 = 0; + /* Check if opp, opp2 point to LINE_UNKNOWNs */ + if (state->lines[d->edges[opp] - g->edges] != LINE_UNKNOWN) + continue; + if (state->lines[d->edges[opp2] - g->edges] != LINE_UNKNOWN) + continue; + /* Found opposite UNKNOWNS and they're next to each other */ + opp_dline_index = dline_index_from_dot(g, d, opp); + return set_atleastone(sstate->dlines, opp_dline_index); + } + return FALSE; +} + + +/* Set pairs of lines around this face which are known to be identical, to + * the given line_state */ +static int face_setall_identical(solver_state *sstate, int face_index, + enum line_state line_new) +{ + /* can[dir] contains the canonical line associated with the line in + * direction dir from the square in question. Similarly inv[dir] is + * whether or not the line in question is inverse to its canonical + * element. */ + int retval = FALSE; + game_state *state = sstate->state; + grid *g = state->game_grid; + grid_face *f = g->faces + face_index; + int N = f->order; + int i, j; + int can1, can2, inv1, inv2; + + for (i = 0; i < N; i++) { + int line1_index = f->edges[i] - g->edges; + if (state->lines[line1_index] != LINE_UNKNOWN) + continue; + for (j = i + 1; j < N; j++) { + int line2_index = f->edges[j] - g->edges; + if (state->lines[line2_index] != LINE_UNKNOWN) + continue; + + /* Found two UNKNOWNS */ + can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1); + can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2); + if (can1 == can2 && inv1 == inv2) { + solver_set_line(sstate, line1_index, line_new); + solver_set_line(sstate, line2_index, line_new); + } + } + } + return retval; +} + +/* Given a dot or face, and a count of LINE_UNKNOWNs, find them and + * return the edge indices into e. */ +static void find_unknowns(game_state *state, + grid_edge **edge_list, /* Edge list to search (from a face or a dot) */ + int expected_count, /* Number of UNKNOWNs (comes from solver's cache) */ + int *e /* Returned edge indices */) +{ + int c = 0; + grid *g = state->game_grid; + while (c < expected_count) { + int line_index = *edge_list - g->edges; + if (state->lines[line_index] == LINE_UNKNOWN) { + e[c] = line_index; + c++; + } + ++edge_list; + } +} + +/* If we have a list of edges, and we know whether the number of YESs should + * be odd or even, and there are only a few UNKNOWNs, we can do some simple + * linedsf deductions. This can be used for both face and dot deductions. + * Returns the difficulty level of the next solver that should be used, + * or DIFF_MAX if no progress was made. */ +static int parity_deductions(solver_state *sstate, + grid_edge **edge_list, /* Edge list (from a face or a dot) */ + int total_parity, /* Expected number of YESs modulo 2 (either 0 or 1) */ + int unknown_count) +{ + game_state *state = sstate->state; + int diff = DIFF_MAX; + int *linedsf = sstate->linedsf; + + if (unknown_count == 2) { + /* Lines are known alike/opposite, depending on inv. */ + int e[2]; + find_unknowns(state, edge_list, 2, e); + if (merge_lines(sstate, e[0], e[1], total_parity)) + diff = min(diff, DIFF_HARD); + } else if (unknown_count == 3) { + int e[3]; + int can[3]; /* canonical edges */ + int inv[3]; /* whether can[x] is inverse to e[x] */ + find_unknowns(state, edge_list, 3, e); + can[0] = edsf_canonify(linedsf, e[0], inv); + can[1] = edsf_canonify(linedsf, e[1], inv+1); + can[2] = edsf_canonify(linedsf, e[2], inv+2); + if (can[0] == can[1]) { + if (solver_set_line(sstate, e[2], (total_parity^inv[0]^inv[1]) ? + LINE_YES : LINE_NO)) + diff = min(diff, DIFF_EASY); + } + if (can[0] == can[2]) { + if (solver_set_line(sstate, e[1], (total_parity^inv[0]^inv[2]) ? + LINE_YES : LINE_NO)) + diff = min(diff, DIFF_EASY); + } + if (can[1] == can[2]) { + if (solver_set_line(sstate, e[0], (total_parity^inv[1]^inv[2]) ? + LINE_YES : LINE_NO)) + diff = min(diff, DIFF_EASY); + } + } else if (unknown_count == 4) { + int e[4]; + int can[4]; /* canonical edges */ + int inv[4]; /* whether can[x] is inverse to e[x] */ + find_unknowns(state, edge_list, 4, e); + can[0] = edsf_canonify(linedsf, e[0], inv); + can[1] = edsf_canonify(linedsf, e[1], inv+1); + can[2] = edsf_canonify(linedsf, e[2], inv+2); + can[3] = edsf_canonify(linedsf, e[3], inv+3); + if (can[0] == can[1]) { + if (merge_lines(sstate, e[2], e[3], total_parity^inv[0]^inv[1])) + diff = min(diff, DIFF_HARD); + } else if (can[0] == can[2]) { + if (merge_lines(sstate, e[1], e[3], total_parity^inv[0]^inv[2])) + diff = min(diff, DIFF_HARD); + } else if (can[0] == can[3]) { + if (merge_lines(sstate, e[1], e[2], total_parity^inv[0]^inv[3])) + diff = min(diff, DIFF_HARD); + } else if (can[1] == can[2]) { + if (merge_lines(sstate, e[0], e[3], total_parity^inv[1]^inv[2])) + diff = min(diff, DIFF_HARD); + } else if (can[1] == can[3]) { + if (merge_lines(sstate, e[0], e[2], total_parity^inv[1]^inv[3])) + diff = min(diff, DIFF_HARD); + } else if (can[2] == can[3]) { + if (merge_lines(sstate, e[0], e[1], total_parity^inv[2]^inv[3])) + diff = min(diff, DIFF_HARD); + } + } + return diff; +} + + +/* + * These are the main solver functions. + * + * Their return values are diff values corresponding to the lowest mode solver + * that would notice the work that they have done. For example if the normal + * mode solver adds actual lines or crosses, it will return DIFF_EASY as the + * easy mode solver might be able to make progress using that. It doesn't make + * sense for one of them to return a diff value higher than that of the + * function itself. + * + * Each function returns the lowest value it can, as early as possible, in + * order to try and pass as much work as possible back to the lower level + * solvers which progress more quickly. + */ + +/* PROPOSED NEW DESIGN: + * We have a work queue consisting of 'events' notifying us that something has + * happened that a particular solver mode might be interested in. For example + * the hard mode solver might do something that helps the normal mode solver at + * dot [x,y] in which case it will enqueue an event recording this fact. Then + * we pull events off the work queue, and hand each in turn to the solver that + * is interested in them. If a solver reports that it failed we pass the same + * event on to progressively more advanced solvers and the loop detector. Once + * we've exhausted an event, or it has helped us progress, we drop it and + * continue to the next one. The events are sorted first in order of solver + * complexity (easy first) then order of insertion (oldest first). + * Once we run out of events we loop over each permitted solver in turn + * (easiest first) until either a deduction is made (and an event therefore + * emerges) or no further deductions can be made (in which case we've failed). + * + * QUESTIONS: + * * How do we 'loop over' a solver when both dots and squares are concerned. + * Answer: first all squares then all dots. + */ + +static int trivial_deductions(solver_state *sstate) +{ + int i, current_yes, current_no; + game_state *state = sstate->state; + grid *g = state->game_grid; + int diff = DIFF_MAX; + + /* Per-face deductions */ + for (i = 0; i < g->num_faces; i++) { + grid_face *f = g->faces + i; + + if (sstate->face_solved[i]) + continue; + + current_yes = sstate->face_yes_count[i]; + current_no = sstate->face_no_count[i]; + + if (current_yes + current_no == f->order) { + sstate->face_solved[i] = TRUE; + continue; + } + + if (state->clues[i] < 0) + continue; + + /* + * This code checks whether the numeric clue on a face is so + * large as to permit all its remaining LINE_UNKNOWNs to be + * filled in as LINE_YES, or alternatively so small as to + * permit them all to be filled in as LINE_NO. + */ + + if (state->clues[i] < current_yes) { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } + if (state->clues[i] == current_yes) { + if (face_setall(sstate, i, LINE_UNKNOWN, LINE_NO)) + diff = min(diff, DIFF_EASY); + sstate->face_solved[i] = TRUE; + continue; + } + + if (f->order - state->clues[i] < current_no) { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } + if (f->order - state->clues[i] == current_no) { + if (face_setall(sstate, i, LINE_UNKNOWN, LINE_YES)) + diff = min(diff, DIFF_EASY); + sstate->face_solved[i] = TRUE; + continue; + } + + if (f->order - state->clues[i] == current_no + 1 && + f->order - current_yes - current_no > 2) { + /* + * One small refinement to the above: we also look for any + * adjacent pair of LINE_UNKNOWNs around the face with + * some LINE_YES incident on it from elsewhere. If we find + * one, then we know that pair of LINE_UNKNOWNs can't + * _both_ be LINE_YES, and hence that pushes us one line + * closer to being able to determine all the rest. + */ + int j, k, e1, e2, e, d; + + for (j = 0; j < f->order; j++) { + e1 = f->edges[j] - g->edges; + e2 = f->edges[j+1 < f->order ? j+1 : 0] - g->edges; + + if (g->edges[e1].dot1 == g->edges[e2].dot1 || + g->edges[e1].dot1 == g->edges[e2].dot2) { + d = g->edges[e1].dot1 - g->dots; + } else { + assert(g->edges[e1].dot2 == g->edges[e2].dot1 || + g->edges[e1].dot2 == g->edges[e2].dot2); + d = g->edges[e1].dot2 - g->dots; + } + + if (state->lines[e1] == LINE_UNKNOWN && + state->lines[e2] == LINE_UNKNOWN) { + for (k = 0; k < g->dots[d].order; k++) { + int e = g->dots[d].edges[k] - g->edges; + if (state->lines[e] == LINE_YES) + goto found; /* multi-level break */ + } + } + } + continue; + + found: + /* + * If we get here, we've found such a pair of edges, and + * they're e1 and e2. + */ + for (j = 0; j < f->order; j++) { + e = f->edges[j] - g->edges; + if (state->lines[e] == LINE_UNKNOWN && e != e1 && e != e2) { + int r = solver_set_line(sstate, e, LINE_YES); + assert(r); + diff = min(diff, DIFF_EASY); + } + } + } + } + + check_caches(sstate); + + /* Per-dot deductions */ + for (i = 0; i < g->num_dots; i++) { + grid_dot *d = g->dots + i; + int yes, no, unknown; + + if (sstate->dot_solved[i]) + continue; + + yes = sstate->dot_yes_count[i]; + no = sstate->dot_no_count[i]; + unknown = d->order - yes - no; + + if (yes == 0) { + if (unknown == 0) { + sstate->dot_solved[i] = TRUE; + } else if (unknown == 1) { + dot_setall(sstate, i, LINE_UNKNOWN, LINE_NO); + diff = min(diff, DIFF_EASY); + sstate->dot_solved[i] = TRUE; + } + } else if (yes == 1) { + if (unknown == 0) { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } else if (unknown == 1) { + dot_setall(sstate, i, LINE_UNKNOWN, LINE_YES); + diff = min(diff, DIFF_EASY); + } + } else if (yes == 2) { + if (unknown > 0) { + dot_setall(sstate, i, LINE_UNKNOWN, LINE_NO); + diff = min(diff, DIFF_EASY); + } + sstate->dot_solved[i] = TRUE; + } else { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } + } + + check_caches(sstate); + + return diff; +} + +static int dline_deductions(solver_state *sstate) +{ + game_state *state = sstate->state; + grid *g = state->game_grid; + char *dlines = sstate->dlines; + int i; + int diff = DIFF_MAX; + + /* ------ Face deductions ------ */ + + /* Given a set of dline atmostone/atleastone constraints, need to figure + * out if we can deduce any further info. For more general faces than + * squares, this turns out to be a tricky problem. + * The approach taken here is to define (per face) NxN matrices: + * "maxs" and "mins". + * The entries maxs(j,k) and mins(j,k) define the upper and lower limits + * for the possible number of edges that are YES between positions j and k + * going clockwise around the face. Can think of j and k as marking dots + * around the face (recall the labelling scheme: edge0 joins dot0 to dot1, + * edge1 joins dot1 to dot2 etc). + * Trivially, mins(j,j) = maxs(j,j) = 0, and we don't even bother storing + * these. mins(j,j+1) and maxs(j,j+1) are determined by whether edge{j} + * is YES, NO or UNKNOWN. mins(j,j+2) and maxs(j,j+2) are related to + * the dline atmostone/atleastone status for edges j and j+1. + * + * Then we calculate the remaining entries recursively. We definitely + * know that + * mins(j,k) >= { mins(j,u) + mins(u,k) } for any u between j and k. + * This is because any valid placement of YESs between j and k must give + * a valid placement between j and u, and also between u and k. + * I believe it's sufficient to use just the two values of u: + * j+1 and j+2. Seems to work well in practice - the bounds we compute + * are rigorous, even if they might not be best-possible. + * + * Once we have maxs and mins calculated, we can make inferences about + * each dline{j,j+1} by looking at the possible complementary edge-counts + * mins(j+2,j) and maxs(j+2,j) and comparing these with the face clue. + * As well as dlines, we can make similar inferences about single edges. + * For example, consider a pentagon with clue 3, and we know at most one + * of (edge0, edge1) is YES, and at most one of (edge2, edge3) is YES. + * We could then deduce edge4 is YES, because maxs(0,4) would be 2, so + * that final edge would have to be YES to make the count up to 3. + */ + + /* Much quicker to allocate arrays on the stack than the heap, so + * define the largest possible face size, and base our array allocations + * on that. We check this with an assertion, in case someone decides to + * make a grid which has larger faces than this. Note, this algorithm + * could get quite expensive if there are many large faces. */ +#define MAX_FACE_SIZE 12 + + for (i = 0; i < g->num_faces; i++) { + int maxs[MAX_FACE_SIZE][MAX_FACE_SIZE]; + int mins[MAX_FACE_SIZE][MAX_FACE_SIZE]; + grid_face *f = g->faces + i; + int N = f->order; + int j,m; + int clue = state->clues[i]; + assert(N <= MAX_FACE_SIZE); + if (sstate->face_solved[i]) + continue; + if (clue < 0) continue; + + /* Calculate the (j,j+1) entries */ + for (j = 0; j < N; j++) { + int edge_index = f->edges[j] - g->edges; + int dline_index; + enum line_state line1 = state->lines[edge_index]; + enum line_state line2; + int tmp; + int k = j + 1; + if (k >= N) k = 0; + maxs[j][k] = (line1 == LINE_NO) ? 0 : 1; + mins[j][k] = (line1 == LINE_YES) ? 1 : 0; + /* Calculate the (j,j+2) entries */ + dline_index = dline_index_from_face(g, f, k); + edge_index = f->edges[k] - g->edges; + line2 = state->lines[edge_index]; + k++; + if (k >= N) k = 0; + + /* max */ + tmp = 2; + if (line1 == LINE_NO) tmp--; + if (line2 == LINE_NO) tmp--; + if (tmp == 2 && is_atmostone(dlines, dline_index)) + tmp = 1; + maxs[j][k] = tmp; + + /* min */ + tmp = 0; + if (line1 == LINE_YES) tmp++; + if (line2 == LINE_YES) tmp++; + if (tmp == 0 && is_atleastone(dlines, dline_index)) + tmp = 1; + mins[j][k] = tmp; + } + + /* Calculate the (j,j+m) entries for m between 3 and N-1 */ + for (m = 3; m < N; m++) { + for (j = 0; j < N; j++) { + int k = j + m; + int u = j + 1; + int v = j + 2; + int tmp; + if (k >= N) k -= N; + if (u >= N) u -= N; + if (v >= N) v -= N; + maxs[j][k] = maxs[j][u] + maxs[u][k]; + mins[j][k] = mins[j][u] + mins[u][k]; + tmp = maxs[j][v] + maxs[v][k]; + maxs[j][k] = min(maxs[j][k], tmp); + tmp = mins[j][v] + mins[v][k]; + mins[j][k] = max(mins[j][k], tmp); + } + } + + /* See if we can make any deductions */ + for (j = 0; j < N; j++) { + int k; + grid_edge *e = f->edges[j]; + int line_index = e - g->edges; + int dline_index; + + if (state->lines[line_index] != LINE_UNKNOWN) + continue; + k = j + 1; + if (k >= N) k = 0; + + /* minimum YESs in the complement of this edge */ + if (mins[k][j] > clue) { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } + if (mins[k][j] == clue) { + /* setting this edge to YES would make at least + * (clue+1) edges - contradiction */ + solver_set_line(sstate, line_index, LINE_NO); + diff = min(diff, DIFF_EASY); + } + if (maxs[k][j] < clue - 1) { + sstate->solver_status = SOLVER_MISTAKE; + return DIFF_EASY; + } + if (maxs[k][j] == clue - 1) { + /* Only way to satisfy the clue is to set edge{j} as YES */ + solver_set_line(sstate, line_index, LINE_YES); + diff = min(diff, DIFF_EASY); + } + + /* More advanced deduction that allows propagation along diagonal + * chains of faces connected by dots, for example, 3-2-...-2-3 + * in square grids. */ + if (sstate->diff >= DIFF_TRICKY) { + /* Now see if we can make dline deduction for edges{j,j+1} */ + e = f->edges[k]; + if (state->lines[e - g->edges] != LINE_UNKNOWN) + /* Only worth doing this for an UNKNOWN,UNKNOWN pair. + * Dlines where one of the edges is known, are handled in the + * dot-deductions */ + continue; + + dline_index = dline_index_from_face(g, f, k); + k++; + if (k >= N) k = 0; + + /* minimum YESs in the complement of this dline */ + if (mins[k][j] > clue - 2) { + /* Adding 2 YESs would break the clue */ + if (set_atmostone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + } + /* maximum YESs in the complement of this dline */ + if (maxs[k][j] < clue) { + /* Adding 2 NOs would mean not enough YESs */ + if (set_atleastone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + } + } + } + } + + if (diff < DIFF_NORMAL) + return diff; + + /* ------ Dot deductions ------ */ + + for (i = 0; i < g->num_dots; i++) { + grid_dot *d = g->dots + i; + int N = d->order; + int yes, no, unknown; + int j; + if (sstate->dot_solved[i]) + continue; + yes = sstate->dot_yes_count[i]; + no = sstate->dot_no_count[i]; + unknown = N - yes - no; + + for (j = 0; j < N; j++) { + int k; + int dline_index; + int line1_index, line2_index; + enum line_state line1, line2; + k = j + 1; + if (k >= N) k = 0; + dline_index = dline_index_from_dot(g, d, j); + line1_index = d->edges[j] - g->edges; + line2_index = d->edges[k] - g->edges; + line1 = state->lines[line1_index]; + line2 = state->lines[line2_index]; + + /* Infer dline state from line state */ + if (line1 == LINE_NO || line2 == LINE_NO) { + if (set_atmostone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + } + if (line1 == LINE_YES || line2 == LINE_YES) { + if (set_atleastone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + } + /* Infer line state from dline state */ + if (is_atmostone(dlines, dline_index)) { + if (line1 == LINE_YES && line2 == LINE_UNKNOWN) { + solver_set_line(sstate, line2_index, LINE_NO); + diff = min(diff, DIFF_EASY); + } + if (line2 == LINE_YES && line1 == LINE_UNKNOWN) { + solver_set_line(sstate, line1_index, LINE_NO); + diff = min(diff, DIFF_EASY); + } + } + if (is_atleastone(dlines, dline_index)) { + if (line1 == LINE_NO && line2 == LINE_UNKNOWN) { + solver_set_line(sstate, line2_index, LINE_YES); + diff = min(diff, DIFF_EASY); + } + if (line2 == LINE_NO && line1 == LINE_UNKNOWN) { + solver_set_line(sstate, line1_index, LINE_YES); + diff = min(diff, DIFF_EASY); + } + } + /* Deductions that depend on the numbers of lines. + * Only bother if both lines are UNKNOWN, otherwise the + * easy-mode solver (or deductions above) would have taken + * care of it. */ + if (line1 != LINE_UNKNOWN || line2 != LINE_UNKNOWN) + continue; + + if (yes == 0 && unknown == 2) { + /* Both these unknowns must be identical. If we know + * atmostone or atleastone, we can make progress. */ + if (is_atmostone(dlines, dline_index)) { + solver_set_line(sstate, line1_index, LINE_NO); + solver_set_line(sstate, line2_index, LINE_NO); + diff = min(diff, DIFF_EASY); + } + if (is_atleastone(dlines, dline_index)) { + solver_set_line(sstate, line1_index, LINE_YES); + solver_set_line(sstate, line2_index, LINE_YES); + diff = min(diff, DIFF_EASY); + } + } + if (yes == 1) { + if (set_atmostone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + if (unknown == 2) { + if (set_atleastone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + } + } + + /* More advanced deduction that allows propagation along diagonal + * chains of faces connected by dots, for example: 3-2-...-2-3 + * in square grids. */ + if (sstate->diff >= DIFF_TRICKY) { + /* If we have atleastone set for this dline, infer + * atmostone for each "opposite" dline (that is, each + * dline without edges in common with this one). + * Again, this test is only worth doing if both these + * lines are UNKNOWN. For if one of these lines were YES, + * the (yes == 1) test above would kick in instead. */ + if (is_atleastone(dlines, dline_index)) { + int opp; + for (opp = 0; opp < N; opp++) { + int opp_dline_index; + if (opp == j || opp == j+1 || opp == j-1) + continue; + if (j == 0 && opp == N-1) + continue; + if (j == N-1 && opp == 0) + continue; + opp_dline_index = dline_index_from_dot(g, d, opp); + if (set_atmostone(dlines, opp_dline_index)) + diff = min(diff, DIFF_NORMAL); + } + if (yes == 0 && is_atmostone(dlines, dline_index)) { + /* This dline has *exactly* one YES and there are no + * other YESs. This allows more deductions. */ + if (unknown == 3) { + /* Third unknown must be YES */ + for (opp = 0; opp < N; opp++) { + int opp_index; + if (opp == j || opp == k) + continue; + opp_index = d->edges[opp] - g->edges; + if (state->lines[opp_index] == LINE_UNKNOWN) { + solver_set_line(sstate, opp_index, + LINE_YES); + diff = min(diff, DIFF_EASY); + } + } + } else if (unknown == 4) { + /* Exactly one of opposite UNKNOWNS is YES. We've + * already set atmostone, so set atleastone as + * well. + */ + if (dline_set_opp_atleastone(sstate, d, j)) + diff = min(diff, DIFF_NORMAL); + } + } + } + } + } + } + return diff; +} + +static int linedsf_deductions(solver_state *sstate) +{ + game_state *state = sstate->state; + grid *g = state->game_grid; + char *dlines = sstate->dlines; + int i; + int diff = DIFF_MAX; + int diff_tmp; + + /* ------ Face deductions ------ */ + + /* A fully-general linedsf deduction seems overly complicated + * (I suspect the problem is NP-complete, though in practice it might just + * be doable because faces are limited in size). + * For simplicity, we only consider *pairs* of LINE_UNKNOWNS that are + * known to be identical. If setting them both to YES (or NO) would break + * the clue, set them to NO (or YES). */ + + for (i = 0; i < g->num_faces; i++) { + int N, yes, no, unknown; + int clue; + + if (sstate->face_solved[i]) + continue; + clue = state->clues[i]; + if (clue < 0) + continue; + + N = g->faces[i].order; + yes = sstate->face_yes_count[i]; + if (yes + 1 == clue) { + if (face_setall_identical(sstate, i, LINE_NO)) + diff = min(diff, DIFF_EASY); + } + no = sstate->face_no_count[i]; + if (no + 1 == N - clue) { + if (face_setall_identical(sstate, i, LINE_YES)) + diff = min(diff, DIFF_EASY); + } + + /* Reload YES count, it might have changed */ + yes = sstate->face_yes_count[i]; + unknown = N - no - yes; + + /* Deductions with small number of LINE_UNKNOWNs, based on overall + * parity of lines. */ + diff_tmp = parity_deductions(sstate, g->faces[i].edges, + (clue - yes) % 2, unknown); + diff = min(diff, diff_tmp); + } + + /* ------ Dot deductions ------ */ + for (i = 0; i < g->num_dots; i++) { + grid_dot *d = g->dots + i; + int N = d->order; + int j; + int yes, no, unknown; + /* Go through dlines, and do any dline<->linedsf deductions wherever + * we find two UNKNOWNS. */ + for (j = 0; j < N; j++) { + int dline_index = dline_index_from_dot(g, d, j); + int line1_index; + int line2_index; + int can1, can2, inv1, inv2; + int j2; + line1_index = d->edges[j] - g->edges; + if (state->lines[line1_index] != LINE_UNKNOWN) + continue; + j2 = j + 1; + if (j2 == N) j2 = 0; + line2_index = d->edges[j2] - g->edges; + if (state->lines[line2_index] != LINE_UNKNOWN) + continue; + /* Infer dline flags from linedsf */ + can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1); + can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2); + if (can1 == can2 && inv1 != inv2) { + /* These are opposites, so set dline atmostone/atleastone */ + if (set_atmostone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + if (set_atleastone(dlines, dline_index)) + diff = min(diff, DIFF_NORMAL); + continue; + } + /* Infer linedsf from dline flags */ + if (is_atmostone(dlines, dline_index) + && is_atleastone(dlines, dline_index)) { + if (merge_lines(sstate, line1_index, line2_index, 1)) + diff = min(diff, DIFF_HARD); + } + } + + /* Deductions with small number of LINE_UNKNOWNs, based on overall + * parity of lines. */ + yes = sstate->dot_yes_count[i]; + no = sstate->dot_no_count[i]; + unknown = N - yes - no; + diff_tmp = parity_deductions(sstate, d->edges, + yes % 2, unknown); + diff = min(diff, diff_tmp); + } + + /* ------ Edge dsf deductions ------ */ + + /* If the state of a line is known, deduce the state of its canonical line + * too, and vice versa. */ + for (i = 0; i < g->num_edges; i++) { + int can, inv; + enum line_state s; + can = edsf_canonify(sstate->linedsf, i, &inv); + if (can == i) + continue; + s = sstate->state->lines[can]; + if (s != LINE_UNKNOWN) { + if (solver_set_line(sstate, i, inv ? OPP(s) : s)) + diff = min(diff, DIFF_EASY); + } else { + s = sstate->state->lines[i]; + if (s != LINE_UNKNOWN) { + if (solver_set_line(sstate, can, inv ? OPP(s) : s)) + diff = min(diff, DIFF_EASY); + } + } + } + + return diff; +} + +static int loop_deductions(solver_state *sstate) +{ + int edgecount = 0, clues = 0, satclues = 0, sm1clues = 0; + game_state *state = sstate->state; + grid *g = state->game_grid; + int shortest_chainlen = g->num_dots; + int loop_found = FALSE; + int dots_connected; + int progress = FALSE; + int i; + + /* + * Go through the grid and update for all the new edges. + * Since merge_dots() is idempotent, the simplest way to + * do this is just to update for _all_ the edges. + * Also, while we're here, we count the edges. + */ + for (i = 0; i < g->num_edges; i++) { + if (state->lines[i] == LINE_YES) { + loop_found |= merge_dots(sstate, i); + edgecount++; + } + } + + /* + * Count the clues, count the satisfied clues, and count the + * satisfied-minus-one clues. + */ + for (i = 0; i < g->num_faces; i++) { + int c = state->clues[i]; + if (c >= 0) { + int o = sstate->face_yes_count[i]; + if (o == c) + satclues++; + else if (o == c-1) + sm1clues++; + clues++; + } + } + + for (i = 0; i < g->num_dots; ++i) { + dots_connected = + sstate->looplen[dsf_canonify(sstate->dotdsf, i)]; + if (dots_connected > 1) + shortest_chainlen = min(shortest_chainlen, dots_connected); + } + + assert(sstate->solver_status == SOLVER_INCOMPLETE); + + if (satclues == clues && shortest_chainlen == edgecount) { + sstate->solver_status = SOLVER_SOLVED; + /* This discovery clearly counts as progress, even if we haven't + * just added any lines or anything */ + progress = TRUE; + goto finished_loop_deductionsing; + } + + /* + * Now go through looking for LINE_UNKNOWN edges which + * connect two dots that are already in the same + * equivalence class. If we find one, test to see if the + * loop it would create is a solution. + */ + for (i = 0; i < g->num_edges; i++) { + grid_edge *e = g->edges + i; + int d1 = e->dot1 - g->dots; + int d2 = e->dot2 - g->dots; + int eqclass, val; + if (state->lines[i] != LINE_UNKNOWN) + continue; + + eqclass = dsf_canonify(sstate->dotdsf, d1); + if (eqclass != dsf_canonify(sstate->dotdsf, d2)) + continue; + + val = LINE_NO; /* loop is bad until proven otherwise */ + + /* + * This edge would form a loop. Next + * question: how long would the loop be? + * Would it equal the total number of edges + * (plus the one we'd be adding if we added + * it)? + */ + if (sstate->looplen[eqclass] == edgecount + 1) { + int sm1_nearby; + + /* + * This edge would form a loop which + * took in all the edges in the entire + * grid. So now we need to work out + * whether it would be a valid solution + * to the puzzle, which means we have to + * check if it satisfies all the clues. + * This means that every clue must be + * either satisfied or satisfied-minus- + * 1, and also that the number of + * satisfied-minus-1 clues must be at + * most two and they must lie on either + * side of this edge. + */ + sm1_nearby = 0; + if (e->face1) { + int f = e->face1 - g->faces; + int c = state->clues[f]; + if (c >= 0 && sstate->face_yes_count[f] == c - 1) + sm1_nearby++; + } + if (e->face2) { + int f = e->face2 - g->faces; + int c = state->clues[f]; + if (c >= 0 && sstate->face_yes_count[f] == c - 1) + sm1_nearby++; + } + if (sm1clues == sm1_nearby && + sm1clues + satclues == clues) { + val = LINE_YES; /* loop is good! */ + } + } + + /* + * Right. Now we know that adding this edge + * would form a loop, and we know whether + * that loop would be a viable solution or + * not. + * + * If adding this edge produces a solution, + * then we know we've found _a_ solution but + * we don't know that it's _the_ solution - + * if it were provably the solution then + * we'd have deduced this edge some time ago + * without the need to do loop detection. So + * in this state we return SOLVER_AMBIGUOUS, + * which has the effect that hitting Solve + * on a user-provided puzzle will fill in a + * solution but using the solver to + * construct new puzzles won't consider this + * a reasonable deduction for the user to + * make. + */ + progress = solver_set_line(sstate, i, val); + assert(progress == TRUE); + if (val == LINE_YES) { + sstate->solver_status = SOLVER_AMBIGUOUS; + goto finished_loop_deductionsing; + } + } + + finished_loop_deductionsing: + return progress ? DIFF_EASY : DIFF_MAX; +} + +/* This will return a dynamically allocated solver_state containing the (more) + * solved grid */ +static solver_state *solve_game_rec(const solver_state *sstate_start) +{ + solver_state *sstate; + + /* Index of the solver we should call next. */ + int i = 0; + + /* As a speed-optimisation, we avoid re-running solvers that we know + * won't make any progress. This happens when a high-difficulty + * solver makes a deduction that can only help other high-difficulty + * solvers. + * For example: if a new 'dline' flag is set by dline_deductions, the + * trivial_deductions solver cannot do anything with this information. + * If we've already run the trivial_deductions solver (because it's + * earlier in the list), there's no point running it again. + * + * Therefore: if a solver is earlier in the list than "threshold_index", + * we don't bother running it if it's difficulty level is less than + * "threshold_diff". + */ + int threshold_diff = 0; + int threshold_index = 0; + + sstate = dup_solver_state(sstate_start); + + check_caches(sstate); + + while (i < NUM_SOLVERS) { + if (sstate->solver_status == SOLVER_MISTAKE) + return sstate; + if (sstate->solver_status == SOLVER_SOLVED || + sstate->solver_status == SOLVER_AMBIGUOUS) { + /* solver finished */ + break; + } + + if ((solver_diffs[i] >= threshold_diff || i >= threshold_index) + && solver_diffs[i] <= sstate->diff) { + /* current_solver is eligible, so use it */ + int next_diff = solver_fns[i](sstate); + if (next_diff != DIFF_MAX) { + /* solver made progress, so use new thresholds and + * start again at top of list. */ + threshold_diff = next_diff; + threshold_index = i; + i = 0; + continue; + } + } + /* current_solver is ineligible, or failed to make progress, so + * go to the next solver in the list */ + i++; + } + + if (sstate->solver_status == SOLVER_SOLVED || + sstate->solver_status == SOLVER_AMBIGUOUS) { + /* s/LINE_UNKNOWN/LINE_NO/g */ + array_setall(sstate->state->lines, LINE_UNKNOWN, LINE_NO, + sstate->state->game_grid->num_edges); + return sstate; + } + + return sstate; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + char *soln = NULL; + solver_state *sstate, *new_sstate; + + sstate = new_solver_state(state, DIFF_MAX); + new_sstate = solve_game_rec(sstate); + + if (new_sstate->solver_status == SOLVER_SOLVED) { + soln = encode_solve_move(new_sstate->state); + } else if (new_sstate->solver_status == SOLVER_AMBIGUOUS) { + soln = encode_solve_move(new_sstate->state); + /**error = "Solver found ambiguous solutions"; */ + } else { + soln = encode_solve_move(new_sstate->state); + /**error = "Solver failed"; */ + } + + free_solver_state(new_sstate); + free_solver_state(sstate); + + return soln; +} + +/* ---------------------------------------------------------------------- + * Drawing and mouse-handling + */ + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + grid *g = state->game_grid; + grid_edge *e; + int i; + char *ret, buf[80]; + char button_char = ' '; + enum line_state old_state; + + button &= ~MOD_MASK; + + /* Convert mouse-click (x,y) to grid coordinates */ + x -= BORDER(ds->tilesize); + y -= BORDER(ds->tilesize); + x = x * g->tilesize / ds->tilesize; + y = y * g->tilesize / ds->tilesize; + x += g->lowest_x; + y += g->lowest_y; + + e = grid_nearest_edge(g, x, y); + if (e == NULL) + return NULL; + + i = e - g->edges; + + /* I think it's only possible to play this game with mouse clicks, sorry */ + /* Maybe will add mouse drag support some time */ + old_state = state->lines[i]; + + switch (button) { + case LEFT_BUTTON: + switch (old_state) { + case LINE_UNKNOWN: + button_char = 'y'; + break; + case LINE_YES: +#ifdef STYLUS_BASED + button_char = 'n'; + break; +#endif + case LINE_NO: + button_char = 'u'; + break; + } + break; + case MIDDLE_BUTTON: + button_char = 'u'; + break; + case RIGHT_BUTTON: + switch (old_state) { + case LINE_UNKNOWN: + button_char = 'n'; + break; + case LINE_NO: +#ifdef STYLUS_BASED + button_char = 'y'; + break; +#endif + case LINE_YES: + button_char = 'u'; + break; + } + break; + default: + return NULL; + } + + + sprintf(buf, "%d%c", i, (int)button_char); + ret = dupstr(buf); + + return ret; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int i; + game_state *newstate = dup_game(state); + + if (move[0] == 'S') { + move++; + newstate->cheated = TRUE; + } + + while (*move) { + i = atoi(move); + if (i < 0 || i >= newstate->game_grid->num_edges) + goto fail; + move += strspn(move, "1234567890"); + switch (*(move++)) { + case 'y': + newstate->lines[i] = LINE_YES; + break; + case 'n': + newstate->lines[i] = LINE_NO; + break; + case 'u': + newstate->lines[i] = LINE_UNKNOWN; + break; + default: + goto fail; + } + } + + /* + * Check for completion. + */ + if (check_completion(newstate)) + newstate->solved = TRUE; + + return newstate; + + fail: + free_game(newstate); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +/* Convert from grid coordinates to screen coordinates */ +static void grid_to_screen(const game_drawstate *ds, const grid *g, + int grid_x, int grid_y, int *x, int *y) +{ + *x = grid_x - g->lowest_x; + *y = grid_y - g->lowest_y; + *x = *x * ds->tilesize / g->tilesize; + *y = *y * ds->tilesize / g->tilesize; + *x += BORDER(ds->tilesize); + *y += BORDER(ds->tilesize); +} + +/* Returns (into x,y) position of centre of face for rendering the text clue. + */ +static void face_text_pos(const game_drawstate *ds, const grid *g, + grid_face *f, int *xret, int *yret) +{ + int faceindex = f - g->faces; + + /* + * Return the cached position for this face, if we've already + * worked it out. + */ + if (ds->textx[faceindex] >= 0) { + *xret = ds->textx[faceindex]; + *yret = ds->texty[faceindex]; + return; + } + + /* + * Otherwise, use the incentre computed by grid.c and convert it + * to screen coordinates. + */ + grid_find_incentre(f); + grid_to_screen(ds, g, f->ix, f->iy, + &ds->textx[faceindex], &ds->texty[faceindex]); + + *xret = ds->textx[faceindex]; + *yret = ds->texty[faceindex]; +} + +static void face_text_bbox(game_drawstate *ds, grid *g, grid_face *f, + int *x, int *y, int *w, int *h) +{ + int xx, yy; + face_text_pos(ds, g, f, &xx, &yy); + + /* There seems to be a certain amount of trial-and-error involved + * in working out the correct bounding-box for the text. */ + + *x = xx - ds->tilesize/4 - 1; + *y = yy - ds->tilesize/4 - 3; + *w = ds->tilesize/2 + 2; + *h = ds->tilesize/2 + 5; +} + +static void game_redraw_clue(drawing *dr, game_drawstate *ds, + const game_state *state, int i) +{ + grid *g = state->game_grid; + grid_face *f = g->faces + i; + int x, y; + char c[20]; + + sprintf(c, "%d", state->clues[i]); + + face_text_pos(ds, g, f, &x, &y); + draw_text(dr, x, y, + FONT_VARIABLE, ds->tilesize/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, + ds->clue_error[i] ? COL_MISTAKE : + ds->clue_satisfied[i] ? COL_SATISFIED : COL_FOREGROUND, c); +} + +static void edge_bbox(game_drawstate *ds, grid *g, grid_edge *e, + int *x, int *y, int *w, int *h) +{ + int x1 = e->dot1->x; + int y1 = e->dot1->y; + int x2 = e->dot2->x; + int y2 = e->dot2->y; + int xmin, xmax, ymin, ymax; + + grid_to_screen(ds, g, x1, y1, &x1, &y1); + grid_to_screen(ds, g, x2, y2, &x2, &y2); + /* Allow extra margin for dots, and thickness of lines */ + xmin = min(x1, x2) - 2; + xmax = max(x1, x2) + 2; + ymin = min(y1, y2) - 2; + ymax = max(y1, y2) + 2; + + *x = xmin; + *y = ymin; + *w = xmax - xmin + 1; + *h = ymax - ymin + 1; +} + +static void dot_bbox(game_drawstate *ds, grid *g, grid_dot *d, + int *x, int *y, int *w, int *h) +{ + int x1, y1; + + grid_to_screen(ds, g, d->x, d->y, &x1, &y1); + + *x = x1 - 2; + *y = y1 - 2; + *w = 5; + *h = 5; +} + +static const int loopy_line_redraw_phases[] = { + COL_FAINT, COL_LINEUNKNOWN, COL_FOREGROUND, COL_HIGHLIGHT, COL_MISTAKE +}; +#define NPHASES lenof(loopy_line_redraw_phases) + +static void game_redraw_line(drawing *dr, game_drawstate *ds, + const game_state *state, int i, int phase) +{ + grid *g = state->game_grid; + grid_edge *e = g->edges + i; + int x1, x2, y1, y2; + int line_colour; + + if (state->line_errors[i]) + line_colour = COL_MISTAKE; + else if (state->lines[i] == LINE_UNKNOWN) + line_colour = COL_LINEUNKNOWN; + else if (state->lines[i] == LINE_NO) + line_colour = COL_FAINT; + else if (ds->flashing) + line_colour = COL_HIGHLIGHT; + else + line_colour = COL_FOREGROUND; + if (line_colour != loopy_line_redraw_phases[phase]) + return; + + /* Convert from grid to screen coordinates */ + grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1); + grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2); + + if (line_colour == COL_FAINT) { + static int draw_faint_lines = -1; + if (draw_faint_lines < 0) { + char *env = getenv("LOOPY_FAINT_LINES"); + draw_faint_lines = (!env || (env[0] == 'y' || + env[0] == 'Y')); + } + if (draw_faint_lines) + draw_line(dr, x1, y1, x2, y2, line_colour); + } else { + draw_thick_line(dr, 3.0, + x1 + 0.5, y1 + 0.5, + x2 + 0.5, y2 + 0.5, + line_colour); + } +} + +static void game_redraw_dot(drawing *dr, game_drawstate *ds, + const game_state *state, int i) +{ + grid *g = state->game_grid; + grid_dot *d = g->dots + i; + int x, y; + + grid_to_screen(ds, g, d->x, d->y, &x, &y); + draw_circle(dr, x, y, 2, COL_FOREGROUND, COL_FOREGROUND); +} + +static int boxes_intersect(int x0, int y0, int w0, int h0, + int x1, int y1, int w1, int h1) +{ + /* + * Two intervals intersect iff neither is wholly on one side of + * the other. Two boxes intersect iff their horizontal and + * vertical intervals both intersect. + */ + return (x0 < x1+w1 && x1 < x0+w0 && y0 < y1+h1 && y1 < y0+h0); +} + +static void game_redraw_in_rect(drawing *dr, game_drawstate *ds, + const game_state *state, + int x, int y, int w, int h) +{ + grid *g = state->game_grid; + int i, phase; + int bx, by, bw, bh; + + clip(dr, x, y, w, h); + draw_rect(dr, x, y, w, h, COL_BACKGROUND); + + for (i = 0; i < g->num_faces; i++) { + if (state->clues[i] >= 0) { + face_text_bbox(ds, g, &g->faces[i], &bx, &by, &bw, &bh); + if (boxes_intersect(x, y, w, h, bx, by, bw, bh)) + game_redraw_clue(dr, ds, state, i); + } + } + for (phase = 0; phase < NPHASES; phase++) { + for (i = 0; i < g->num_edges; i++) { + edge_bbox(ds, g, &g->edges[i], &bx, &by, &bw, &bh); + if (boxes_intersect(x, y, w, h, bx, by, bw, bh)) + game_redraw_line(dr, ds, state, i, phase); + } + } + for (i = 0; i < g->num_dots; i++) { + dot_bbox(ds, g, &g->dots[i], &bx, &by, &bw, &bh); + if (boxes_intersect(x, y, w, h, bx, by, bw, bh)) + game_redraw_dot(dr, ds, state, i); + } + + unclip(dr); + draw_update(dr, x, y, w, h); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ +#define REDRAW_OBJECTS_LIMIT 16 /* Somewhat arbitrary tradeoff */ + + grid *g = state->game_grid; + int border = BORDER(ds->tilesize); + int i; + int flash_changed; + int redraw_everything = FALSE; + + int edges[REDRAW_OBJECTS_LIMIT], nedges = 0; + int faces[REDRAW_OBJECTS_LIMIT], nfaces = 0; + + /* Redrawing is somewhat involved. + * + * An update can theoretically affect an arbitrary number of edges + * (consider, for example, completing or breaking a cycle which doesn't + * satisfy all the clues -- we'll switch many edges between error and + * normal states). On the other hand, redrawing the whole grid takes a + * while, making the game feel sluggish, and many updates are actually + * quite well localized. + * + * This redraw algorithm attempts to cope with both situations gracefully + * and correctly. For localized changes, we set a clip rectangle, fill + * it with background, and then redraw (a plausible but conservative + * guess at) the objects which intersect the rectangle; if several + * objects need redrawing, we'll do them individually. However, if lots + * of objects are affected, we'll just redraw everything. + * + * The reason for all of this is that it's just not safe to do the redraw + * piecemeal. If you try to draw an antialiased diagonal line over + * itself, you get a slightly thicker antialiased diagonal line, which + * looks rather ugly after a while. + * + * So, we take two passes over the grid. The first attempts to work out + * what needs doing, and the second actually does it. + */ + + if (!ds->started) { + redraw_everything = TRUE; + /* + * But we must still go through the upcoming loops, so that we + * set up stuff in ds correctly for the initial redraw. + */ + } + + /* First, trundle through the faces. */ + for (i = 0; i < g->num_faces; i++) { + grid_face *f = g->faces + i; + int sides = f->order; + int yes_order, no_order; + int clue_mistake; + int clue_satisfied; + int n = state->clues[i]; + if (n < 0) + continue; + + yes_order = face_order(state, i, LINE_YES); + if (state->exactly_one_loop) { + /* + * Special case: if the set of LINE_YES edges in the grid + * consists of exactly one loop and nothing else, then we + * switch to treating LINE_UNKNOWN the same as LINE_NO for + * purposes of clue checking. + * + * This is because some people like to play Loopy without + * using the right-click, i.e. never setting anything to + * LINE_NO. Without this special case, if a person playing + * in that style fills in what they think is a correct + * solution loop but in fact it has an underfilled clue, + * then we will display no victory flash and also no error + * highlight explaining why not. With this special case, + * we light up underfilled clues at the instant the loop + * is closed. (Of course, *overfilled* clues are fine + * either way.) + * + * (It might still be considered unfortunate that we can't + * warn this style of player any earlier, if they make a + * mistake very near the beginning which doesn't show up + * until they close the last edge of the loop. One other + * thing we _could_ do here is to treat any LINE_UNKNOWN + * as LINE_NO if either of its endpoints has yes-degree 2, + * reflecting the fact that setting that line to YES would + * be an obvious error. But I don't think even that could + * catch _all_ clue errors in a timely manner; I think + * there are some that won't be displayed until the loop + * is filled in, even so, and there's no way to avoid that + * with complete reliability except to switch to being a + * player who sets things to LINE_NO.) + */ + no_order = sides - yes_order; + } else { + no_order = face_order(state, i, LINE_NO); + } + + clue_mistake = (yes_order > n || no_order > (sides-n)); + clue_satisfied = (yes_order == n && no_order == (sides-n)); + + if (clue_mistake != ds->clue_error[i] || + clue_satisfied != ds->clue_satisfied[i]) { + ds->clue_error[i] = clue_mistake; + ds->clue_satisfied[i] = clue_satisfied; + if (nfaces == REDRAW_OBJECTS_LIMIT) + redraw_everything = TRUE; + else + faces[nfaces++] = i; + } + } + + /* Work out what the flash state needs to be. */ + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) { + flash_changed = !ds->flashing; + ds->flashing = TRUE; + } else { + flash_changed = ds->flashing; + ds->flashing = FALSE; + } + + /* Now, trundle through the edges. */ + for (i = 0; i < g->num_edges; i++) { + char new_ds = + state->line_errors[i] ? DS_LINE_ERROR : state->lines[i]; + if (new_ds != ds->lines[i] || + (flash_changed && state->lines[i] == LINE_YES)) { + ds->lines[i] = new_ds; + if (nedges == REDRAW_OBJECTS_LIMIT) + redraw_everything = TRUE; + else + edges[nedges++] = i; + } + } + + /* Pass one is now done. Now we do the actual drawing. */ + if (redraw_everything) { + int grid_width = g->highest_x - g->lowest_x; + int grid_height = g->highest_y - g->lowest_y; + int w = grid_width * ds->tilesize / g->tilesize; + int h = grid_height * ds->tilesize / g->tilesize; + + game_redraw_in_rect(dr, ds, state, + 0, 0, w + 2*border + 1, h + 2*border + 1); + } else { + + /* Right. Now we roll up our sleeves. */ + + for (i = 0; i < nfaces; i++) { + grid_face *f = g->faces + faces[i]; + int x, y, w, h; + + face_text_bbox(ds, g, f, &x, &y, &w, &h); + game_redraw_in_rect(dr, ds, state, x, y, w, h); + } + + for (i = 0; i < nedges; i++) { + grid_edge *e = g->edges + edges[i]; + int x, y, w, h; + + edge_bbox(ds, g, e, &x, &y, &w, &h); + game_redraw_in_rect(dr, ds, state, x, y, w, h); + } + } + + ds->started = TRUE; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->solved && newstate->solved && + !oldstate->cheated && !newstate->cheated) { + return FLASH_TIME; + } + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->solved ? +1 : 0; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 7mm "squares" by default. + */ + game_compute_size(params, 700, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int ink = print_mono_colour(dr, 0); + int i; + game_drawstate ads, *ds = &ads; + grid *g = state->game_grid; + + ds->tilesize = tilesize; + ds->textx = snewn(g->num_faces, int); + ds->texty = snewn(g->num_faces, int); + for (i = 0; i < g->num_faces; i++) + ds->textx[i] = ds->texty[i] = -1; + + for (i = 0; i < g->num_dots; i++) { + int x, y; + grid_to_screen(ds, g, g->dots[i].x, g->dots[i].y, &x, &y); + draw_circle(dr, x, y, ds->tilesize / 15, ink, ink); + } + + /* + * Clues. + */ + for (i = 0; i < g->num_faces; i++) { + grid_face *f = g->faces + i; + int clue = state->clues[i]; + if (clue >= 0) { + char c[20]; + int x, y; + sprintf(c, "%d", state->clues[i]); + face_text_pos(ds, g, f, &x, &y); + draw_text(dr, x, y, + FONT_VARIABLE, ds->tilesize / 2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, c); + } + } + + /* + * Lines. + */ + for (i = 0; i < g->num_edges; i++) { + int thickness = (state->lines[i] == LINE_YES) ? 30 : 150; + grid_edge *e = g->edges + i; + int x1, y1, x2, y2; + grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1); + grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2); + if (state->lines[i] == LINE_YES) + { + /* (dx, dy) points from (x1, y1) to (x2, y2). + * The line is then "fattened" in a perpendicular + * direction to create a thin rectangle. */ + double d = sqrt(SQ((double)x1 - x2) + SQ((double)y1 - y2)); + double dx = (x2 - x1) / d; + double dy = (y2 - y1) / d; + int points[8]; + + dx = (dx * ds->tilesize) / thickness; + dy = (dy * ds->tilesize) / thickness; + points[0] = x1 + (int)dy; + points[1] = y1 - (int)dx; + points[2] = x1 - (int)dy; + points[3] = y1 + (int)dx; + points[4] = x2 - (int)dy; + points[5] = y2 + (int)dx; + points[6] = x2 + (int)dy; + points[7] = y2 - (int)dx; + draw_polygon(dr, points, 4, ink, ink); + } + else + { + /* Draw a dotted line */ + int divisions = 6; + int j; + for (j = 1; j < divisions; j++) { + /* Weighted average */ + int x = (x1 * (divisions -j) + x2 * j) / divisions; + int y = (y1 * (divisions -j) + y2 * j) / divisions; + draw_circle(dr, x, y, ds->tilesize / thickness, ink, ink); + } + } + } + + sfree(ds->textx); + sfree(ds->texty); +} + +#ifdef COMBINED +#define thegame loopy +#endif + +const struct game thegame = { + "Loopy", "games.loopy", "loopy", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + 1, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE /* wants_statusbar */, + FALSE, game_timing_state, + 0, /* mouse_priorities */ +}; + +#ifdef STANDALONE_SOLVER + +/* + * Half-hearted standalone solver. It can't output the solution to + * anything but a square puzzle, and it can't log the deductions + * it makes either. But it can solve square puzzles, and more + * importantly it can use its solver to grade the difficulty of + * any puzzle you give it. + */ + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff; +#if 0 /* verbose solver not supported here (yet) */ + int really_verbose = FALSE; +#endif + + while (--argc > 0) { + char *p = *++argv; +#if 0 /* verbose solver not supported here (yet) */ + if (!strcmp(p, "-v")) { + really_verbose = TRUE; + } else +#endif + if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + for (diff = 0; diff < DIFF_MAX; diff++) { + solver_state *sstate_new; + solver_state *sstate = new_solver_state((game_state *)s, diff); + + sstate_new = solve_game_rec(sstate); + + if (sstate_new->solver_status == SOLVER_MISTAKE) + ret = 0; + else if (sstate_new->solver_status == SOLVER_SOLVED) + ret = 1; + else + ret = 2; + + free_solver_state(sstate_new); + free_solver_state(sstate); + + if (ret < 2) + break; + } + + if (diff == DIFF_MAX) { + if (grade) + printf("Difficulty rating: harder than Hard, or ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == 0) + printf("Difficulty rating: impossible (no solution exists)\n"); + else if (ret == 1) + printf("Difficulty rating: %s\n", diffnames[diff]); + } else { + solver_state *sstate_new; + solver_state *sstate = new_solver_state((game_state *)s, diff); + + /* If we supported a verbose solver, we'd set verbosity here */ + + sstate_new = solve_game_rec(sstate); + + if (sstate_new->solver_status == SOLVER_MISTAKE) + printf("Puzzle is inconsistent\n"); + else { + assert(sstate_new->solver_status == SOLVER_SOLVED); + if (s->grid_type == 0) { + fputs(game_text_format(sstate_new->state), stdout); + } else { + printf("Unable to output non-square grids\n"); + } + } + + free_solver_state(sstate_new); + free_solver_state(sstate); + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/magnets.R b/apps/plugins/puzzles/magnets.R new file mode 100644 index 0000000000..e55e4746ee --- /dev/null +++ b/apps/plugins/puzzles/magnets.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +MAGNETS_EXTRA = laydomino + +magnets : [X] GTK COMMON magnets MAGNETS_EXTRA magnets-icon|no-icon + +magnets : [G] WINDOWS COMMON magnets MAGNETS_EXTRA magnets.res|noicon.res + +magnetssolver : [U] magnets[STANDALONE_SOLVER] MAGNETS_EXTRA STANDALONE m.lib +magnetssolver : [C] magnets[STANDALONE_SOLVER] MAGNETS_EXTRA STANDALONE + +ALL += magnets[COMBINED] MAGNETS_EXTRA + +!begin am gtk +GAMES += magnets +!end + +!begin >list.c + A(magnets) \ +!end + +!begin >gamedesc.txt +magnets:magnets.exe:Magnets:Magnet-placing puzzle:Place magnets to satisfy the clues and avoid like poles touching. +!end diff --git a/apps/plugins/puzzles/magnets.c b/apps/plugins/puzzles/magnets.c new file mode 100644 index 0000000000..e51ae103c5 --- /dev/null +++ b/apps/plugins/puzzles/magnets.c @@ -0,0 +1,2641 @@ +/* + * magnets.c: implementation of janko.at 'magnets puzzle' game. + * + * http://64.233.179.104/translate_c?hl=en&u=http://www.janko.at/Raetsel/Magnete/Beispiel.htm + * + * Puzzle definition is just the size, and then the list of + (across then + * down) and - (across then down) present, then domino edges. + * + * An example: + * + * + 2 0 1 + * +-----+ + * 1|+ -| |1 + * |-+-+ | + * 0|-|#| |1 + * | +-+-| + * 2|+|- +|1 + * +-----+ + * 1 2 0 - + * + * 3x3:201,102,120,111,LRTT*BBLR + * + * 'Zotmeister' examples: + * 5x5:.2..1,3..1.,.2..2,2..2.,LRLRTTLRTBBT*BTTBLRBBLRLR + * 9x9:3.51...33,.2..23.13,..33.33.2,12...5.3.,**TLRTLR*,*TBLRBTLR,TBLRLRBTT,BLRTLRTBB,LRTB*TBLR,LRBLRBLRT,TTTLRLRTB,BBBTLRTB*,*LRBLRB** + * + * Janko 6x6 with solution: + * 6x6:322223,323132,232223,232223,LRTLRTTTBLRBBBTTLRLRBBLRTTLRTTBBLRBB + * + * janko 8x8: + * 8x8:34131323,23131334,43122323,21332243,LRTLRLRT,LRBTTTTB,LRTBBBBT,TTBTLRTB,BBTBTTBT,TTBTBBTB,BBTBLRBT,LRBLRLRB + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#ifdef STANDALONE_SOLVER +int verbose = 0; +#endif + +enum { + COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT, + COL_TEXT, COL_ERROR, COL_CURSOR, COL_DONE, + COL_NEUTRAL, COL_NEGATIVE, COL_POSITIVE, COL_NOT, + NCOLOURS +}; + +/* Cell states. */ +enum { EMPTY = 0, NEUTRAL = EMPTY, POSITIVE = 1, NEGATIVE = 2 }; + +#if defined DEBUGGING || defined STANDALONE_SOLVER +static const char *cellnames[3] = { "neutral", "positive", "negative" }; +#define NAME(w) ( ((w) < 0 || (w) > 2) ? "(out of range)" : cellnames[(w)] ) +#endif + +#define GRID2CHAR(g) ( ((g) >= 0 && (g) <= 2) ? ".+-"[(g)] : '?' ) +#define CHAR2GRID(c) ( (c) == '+' ? POSITIVE : (c) == '-' ? NEGATIVE : NEUTRAL ) + +#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h) + +#define OPPOSITE(x) ( ((x)*2) % 3 ) /* 0 --> 0, + 1 --> 2, + 2 --> 4 --> 1 */ + +#define FLASH_TIME 0.7F + +/* Macro ickery copied from slant.c */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(TRICKY,Tricky,t) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const magnets_diffnames[] = { DIFFLIST(TITLE) "(count)" }; +static char const magnets_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + + +/* --------------------------------------------------------------- */ +/* Game parameter functions. */ + +struct game_params { + int w, h, diff, stripclues; +}; + +#define DEFAULT_PRESET 2 + +static const struct game_params magnets_presets[] = { + {6, 5, DIFF_EASY, 0}, + {6, 5, DIFF_TRICKY, 0}, + {6, 5, DIFF_TRICKY, 1}, + {8, 7, DIFF_EASY, 0}, + {8, 7, DIFF_TRICKY, 0}, + {8, 7, DIFF_TRICKY, 1}, + {10, 9, DIFF_TRICKY, 0}, + {10, 9, DIFF_TRICKY, 1} +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + *ret = magnets_presets[DEFAULT_PRESET]; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[64]; + + if (i < 0 || i >= lenof(magnets_presets)) return FALSE; + + ret = default_params(); + *ret = magnets_presets[i]; /* struct copy */ + *params = ret; + + sprintf(buf, "%dx%d %s%s", + magnets_presets[i].w, magnets_presets[i].h, + magnets_diffnames[magnets_presets[i].diff], + magnets_presets[i].stripclues ? ", strip clues" : ""); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + while (*string && isdigit((unsigned char) *string)) ++string; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + + ret->diff = DIFF_EASY; + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == magnets_diffchars[i]) + ret->diff = i; + if (*string) string++; + } + + ret->stripclues = 0; + if (*string == 'S') { + string++; + ret->stripclues = 1; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[256]; + sprintf(buf, "%dx%d", params->w, params->h); + if (full) + sprintf(buf + strlen(buf), "d%c%s", + magnets_diffchars[params->diff], + params->stripclues ? "S" : ""); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[64]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = "Strip clues"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = params->stripclues; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + ret->stripclues = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 2) return "Width must be at least one"; + if (params->h < 2) return "Height must be at least one"; + if (params->diff < 0 || params->diff >= DIFFCOUNT) + return "Unknown difficulty level"; + + return NULL; +} + +/* --------------------------------------------------------------- */ +/* Game state allocation, deallocation. */ + +struct game_common { + int *dominoes; /* size w*h, dominoes[i] points to other end of domino. */ + int *rowcount; /* size 3*h, array of [plus, minus, neutral] counts */ + int *colcount; /* size 3*w, ditto */ + int refcount; +}; + +#define GS_ERROR 1 +#define GS_SET 2 +#define GS_NOTPOSITIVE 4 +#define GS_NOTNEGATIVE 8 +#define GS_NOTNEUTRAL 16 +#define GS_MARK 32 + +#define GS_NOTMASK (GS_NOTPOSITIVE|GS_NOTNEGATIVE|GS_NOTNEUTRAL) + +#define NOTFLAG(w) ( (w) == NEUTRAL ? GS_NOTNEUTRAL : \ + (w) == POSITIVE ? GS_NOTPOSITIVE : \ + (w) == NEGATIVE ? GS_NOTNEGATIVE : \ + 0 ) + +#define POSSIBLE(f,w) (!(state->flags[(f)] & NOTFLAG(w))) + +struct game_state { + int w, h, wh; + int *grid; /* size w*h, for cell state (pos/neg) */ + unsigned int *flags; /* size w*h */ + int solved, completed, numbered; + unsigned char *counts_done; + + struct game_common *common; /* domino layout never changes. */ +}; + +static void clear_state(game_state *ret) +{ + int i; + + ret->solved = ret->completed = ret->numbered = 0; + + memset(ret->common->rowcount, 0, ret->h*3*sizeof(int)); + memset(ret->common->colcount, 0, ret->w*3*sizeof(int)); + memset(ret->counts_done, 0, (ret->h + ret->w) * 2 * sizeof(unsigned char)); + + for (i = 0; i < ret->wh; i++) { + ret->grid[i] = EMPTY; + ret->flags[i] = 0; + ret->common->dominoes[i] = i; + } +} + +static game_state *new_state(int w, int h) +{ + game_state *ret = snew(game_state); + + memset(ret, 0, sizeof(game_state)); + ret->w = w; + ret->h = h; + ret->wh = w*h; + + ret->grid = snewn(ret->wh, int); + ret->flags = snewn(ret->wh, unsigned int); + ret->counts_done = snewn((ret->h + ret->w) * 2, unsigned char); + + ret->common = snew(struct game_common); + ret->common->refcount = 1; + + ret->common->dominoes = snewn(ret->wh, int); + ret->common->rowcount = snewn(ret->h*3, int); + ret->common->colcount = snewn(ret->w*3, int); + + clear_state(ret); + + return ret; +} + +static game_state *dup_game(const game_state *src) +{ + game_state *dest = snew(game_state); + + dest->w = src->w; + dest->h = src->h; + dest->wh = src->wh; + + dest->solved = src->solved; + dest->completed = src->completed; + dest->numbered = src->numbered; + + dest->common = src->common; + dest->common->refcount++; + + dest->grid = snewn(dest->wh, int); + memcpy(dest->grid, src->grid, dest->wh*sizeof(int)); + + dest->counts_done = snewn((dest->h + dest->w) * 2, unsigned char); + memcpy(dest->counts_done, src->counts_done, + (dest->h + dest->w) * 2 * sizeof(unsigned char)); + + dest->flags = snewn(dest->wh, unsigned int); + memcpy(dest->flags, src->flags, dest->wh*sizeof(unsigned int)); + + return dest; +} + +static void free_game(game_state *state) +{ + state->common->refcount--; + if (state->common->refcount == 0) { + sfree(state->common->dominoes); + sfree(state->common->rowcount); + sfree(state->common->colcount); + sfree(state->common); + } + sfree(state->counts_done); + sfree(state->flags); + sfree(state->grid); + sfree(state); +} + +/* --------------------------------------------------------------- */ +/* Game generation and reading. */ + +/* For a game of size w*h the game description is: + * w-sized string of column + numbers (L-R), or '.' for none + * semicolon + * h-sized string of row + numbers (T-B), or '.' + * semicolon + * w-sized string of column - numbers (L-R), or '.' + * semicolon + * h-sized string of row - numbers (T-B), or '.' + * semicolon + * w*h-sized string of 'L', 'R', 'U', 'D' for domino associations, + * or '*' for a black singleton square. + * + * for a total length of 2w + 2h + wh + 4. + */ + +static char n2c(int num) { /* XXX cloned from singles.c */ + if (num == -1) + return '.'; + if (num < 10) + return '0' + num; + else if (num < 10+26) + return 'a' + num - 10; + else + return 'A' + num - 10 - 26; + return '?'; +} + +static int c2n(char c) { /* XXX cloned from singles.c */ + if (isdigit((unsigned char)c)) + return (int)(c - '0'); + else if (c >= 'a' && c <= 'z') + return (int)(c - 'a' + 10); + else if (c >= 'A' && c <= 'Z') + return (int)(c - 'A' + 10 + 26); + return -1; +} + +static const char *readrow(const char *desc, int n, int *array, int off, + const char **prob) +{ + int i, num; + char c; + + for (i = 0; i < n; i++) { + c = *desc++; + if (c == 0) goto badchar; + if (c == '.') + num = -1; + else { + num = c2n(c); + if (num < 0) goto badchar; + } + array[i*3+off] = num; + } + c = *desc++; + if (c != ',') goto badchar; + return desc; + +badchar: + *prob = (c == 0) ? + "Game description too short" : + "Game description contained unexpected characters"; + return NULL; +} + +static game_state *new_game_int(const game_params *params, const char *desc, + const char **prob) +{ + game_state *state = new_state(params->w, params->h); + int x, y, idx, *count; + char c; + + *prob = NULL; + + /* top row, left-to-right */ + desc = readrow(desc, state->w, state->common->colcount, POSITIVE, prob); + if (*prob) goto done; + + /* left column, top-to-bottom */ + desc = readrow(desc, state->h, state->common->rowcount, POSITIVE, prob); + if (*prob) goto done; + + /* bottom row, left-to-right */ + desc = readrow(desc, state->w, state->common->colcount, NEGATIVE, prob); + if (*prob) goto done; + + /* right column, top-to-bottom */ + desc = readrow(desc, state->h, state->common->rowcount, NEGATIVE, prob); + if (*prob) goto done; + + /* Add neutral counts (== size - pos - neg) to columns and rows. + * Any singleton cells will just be treated as permanently neutral. */ + count = state->common->colcount; + for (x = 0; x < state->w; x++) { + if (count[x*3+POSITIVE] < 0 || count[x*3+NEGATIVE] < 0) + count[x*3+NEUTRAL] = -1; + else { + count[x*3+NEUTRAL] = + state->h - count[x*3+POSITIVE] - count[x*3+NEGATIVE]; + if (count[x*3+NEUTRAL] < 0) { + *prob = "Column counts inconsistent"; + goto done; + } + } + } + count = state->common->rowcount; + for (y = 0; y < state->h; y++) { + if (count[y*3+POSITIVE] < 0 || count[y*3+NEGATIVE] < 0) + count[y*3+NEUTRAL] = -1; + else { + count[y*3+NEUTRAL] = + state->w - count[y*3+POSITIVE] - count[y*3+NEGATIVE]; + if (count[y*3+NEUTRAL] < 0) { + *prob = "Row counts inconsistent"; + goto done; + } + } + } + + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + idx = y*state->w + x; +nextchar: + c = *desc++; + + if (c == 'L') /* this square is LHS of a domino */ + state->common->dominoes[idx] = idx+1; + else if (c == 'R') /* ... RHS of a domino */ + state->common->dominoes[idx] = idx-1; + else if (c == 'T') /* ... top of a domino */ + state->common->dominoes[idx] = idx+state->w; + else if (c == 'B') /* ... bottom of a domino */ + state->common->dominoes[idx] = idx-state->w; + else if (c == '*') /* singleton */ + state->common->dominoes[idx] = idx; + else if (c == ',') /* spacer, ignore */ + goto nextchar; + else goto badchar; + } + } + + /* Check dominoes as input are sensibly consistent + * (i.e. each end points to the other) */ + for (idx = 0; idx < state->wh; idx++) { + if (state->common->dominoes[idx] < 0 || + state->common->dominoes[idx] > state->wh || + state->common->dominoes[state->common->dominoes[idx]] != idx) { + *prob = "Domino descriptions inconsistent"; + goto done; + } + if (state->common->dominoes[idx] == idx) { + state->grid[idx] = NEUTRAL; + state->flags[idx] |= GS_SET; + } + } + /* Success. */ + state->numbered = 1; + goto done; + +badchar: + *prob = (c == 0) ? + "Game description too short" : + "Game description contained unexpected characters"; + +done: + if (*prob) { + free_game(state); + return NULL; + } + return state; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + const char *prob; + game_state *st = new_game_int(params, desc, &prob); + if (!st) return (char*)prob; + free_game(st); + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + const char *prob; + game_state *st = new_game_int(params, desc, &prob); + assert(st); + return st; +} + +static char *generate_desc(game_state *new) +{ + int x, y, idx, other, w = new->w, h = new->h; + char *desc = snewn(new->wh + 2*(w + h) + 5, char), *p = desc; + + for (x = 0; x < w; x++) *p++ = n2c(new->common->colcount[x*3+POSITIVE]); + *p++ = ','; + for (y = 0; y < h; y++) *p++ = n2c(new->common->rowcount[y*3+POSITIVE]); + *p++ = ','; + + for (x = 0; x < w; x++) *p++ = n2c(new->common->colcount[x*3+NEGATIVE]); + *p++ = ','; + for (y = 0; y < h; y++) *p++ = n2c(new->common->rowcount[y*3+NEGATIVE]); + *p++ = ','; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + idx = y*w + x; + other = new->common->dominoes[idx]; + + if (other == idx) *p++ = '*'; + else if (other == idx+1) *p++ = 'L'; + else if (other == idx-1) *p++ = 'R'; + else if (other == idx+w) *p++ = 'T'; + else if (other == idx-w) *p++ = 'B'; + else assert(!"mad domino orientation"); + } + } + *p = '\0'; + + return desc; +} + +static void game_text_hborder(const game_state *state, char **p_r) +{ + char *p = *p_r; + int x; + + *p++ = ' '; + *p++ = '+'; + for (x = 0; x < state->w*2-1; x++) *p++ = '-'; + *p++ = '+'; + *p++ = '\n'; + + *p_r = p; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int len, x, y, i; + char *ret, *p; + + len = ((state->w*2)+4) * ((state->h*2)+4) + 2; + p = ret = snewn(len, char); + + /* top row: '+' then column totals for plus. */ + *p++ = '+'; + for (x = 0; x < state->w; x++) { + *p++ = ' '; + *p++ = n2c(state->common->colcount[x*3+POSITIVE]); + } + *p++ = '\n'; + + /* top border. */ + game_text_hborder(state, &p); + + for (y = 0; y < state->h; y++) { + *p++ = n2c(state->common->rowcount[y*3+POSITIVE]); + *p++ = '|'; + for (x = 0; x < state->w; x++) { + i = y*state->w+x; + *p++ = state->common->dominoes[i] == i ? '#' : + state->grid[i] == POSITIVE ? '+' : + state->grid[i] == NEGATIVE ? '-' : + state->flags[i] & GS_SET ? '*' : ' '; + if (x < (state->w-1)) + *p++ = state->common->dominoes[i] == i+1 ? ' ' : '|'; + } + *p++ = '|'; + *p++ = n2c(state->common->rowcount[y*3+NEGATIVE]); + *p++ = '\n'; + + if (y < (state->h-1)) { + *p++ = ' '; + *p++ = '|'; + for (x = 0; x < state->w; x++) { + i = y*state->w+x; + *p++ = state->common->dominoes[i] == i+state->w ? ' ' : '-'; + if (x < (state->w-1)) + *p++ = '+'; + } + *p++ = '|'; + *p++ = '\n'; + } + } + + /* bottom border. */ + game_text_hborder(state, &p); + + /* bottom row: column totals for minus then '-'. */ + *p++ = ' '; + for (x = 0; x < state->w; x++) { + *p++ = ' '; + *p++ = n2c(state->common->colcount[x*3+NEGATIVE]); + } + *p++ = ' '; + *p++ = '-'; + *p++ = '\n'; + *p++ = '\0'; + + return ret; +} + +static void game_debug(game_state *state, const char *desc) +{ + char *fmt = game_text_format(state); + debug(("%s:\n%s\n", desc, fmt)); + sfree(fmt); +} + +enum { ROW, COLUMN }; + +typedef struct rowcol { + int i, di, n, roworcol, num; + int *targets; + const char *name; +} rowcol; + +static rowcol mkrowcol(const game_state *state, int num, int roworcol) +{ + rowcol rc; + + rc.roworcol = roworcol; + rc.num = num; + + if (roworcol == ROW) { + rc.i = num * state->w; + rc.di = 1; + rc.n = state->w; + rc.targets = &(state->common->rowcount[num*3]); + rc.name = "row"; + } else if (roworcol == COLUMN) { + rc.i = num; + rc.di = state->w; + rc.n = state->h; + rc.targets = &(state->common->colcount[num*3]); + rc.name = "column"; + } else { + assert(!"unknown roworcol"); + } + return rc; +} + +static int count_rowcol(const game_state *state, int num, int roworcol, + int which) +{ + int i, count = 0; + rowcol rc = mkrowcol(state, num, roworcol); + + for (i = 0; i < rc.n; i++, rc.i += rc.di) { + if (which < 0) { + if (state->grid[rc.i] == EMPTY && + !(state->flags[rc.i] & GS_SET)) + count++; + } else if (state->grid[rc.i] == which) + count++; + } + return count; +} + +static void check_rowcol(game_state *state, int num, int roworcol, int which, + int *wrong, int *incomplete) +{ + int count, target = mkrowcol(state, num, roworcol).targets[which]; + + if (target == -1) return; /* no number to check against. */ + + count = count_rowcol(state, num, roworcol, which); + if (count < target) *incomplete = 1; + if (count > target) *wrong = 1; +} + +static int check_completion(game_state *state) +{ + int i, j, x, y, idx, w = state->w, h = state->h; + int which = POSITIVE, wrong = 0, incomplete = 0; + + /* Check row and column counts for magnets. */ + for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) { + for (i = 0; i < w; i++) + check_rowcol(state, i, COLUMN, which, &wrong, &incomplete); + + for (i = 0; i < h; i++) + check_rowcol(state, i, ROW, which, &wrong, &incomplete); + } + /* Check each domino has been filled, and that we don't have + * touching identical terminals. */ + for (i = 0; i < state->wh; i++) state->flags[i] &= ~GS_ERROR; + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + idx = y*w + x; + if (state->common->dominoes[idx] == idx) + continue; /* no domino here */ + + if (!(state->flags[idx] & GS_SET)) + incomplete = 1; + + which = state->grid[idx]; + if (which != NEUTRAL) { +#define CHECK(xx,yy) do { \ + if (INGRID(state,xx,yy) && \ + (state->grid[(yy)*w+(xx)] == which)) { \ + wrong = 1; \ + state->flags[(yy)*w+(xx)] |= GS_ERROR; \ + state->flags[y*w+x] |= GS_ERROR; \ + } \ +} while(0) + CHECK(x,y-1); + CHECK(x,y+1); + CHECK(x-1,y); + CHECK(x+1,y); +#undef CHECK + } + } + } + return wrong ? -1 : incomplete ? 0 : 1; +} + +static const int dx[4] = {-1, 1, 0, 0}; +static const int dy[4] = {0, 0, -1, 1}; + +static void solve_clearflags(game_state *state) +{ + int i; + + for (i = 0; i < state->wh; i++) { + state->flags[i] &= ~GS_NOTMASK; + if (state->common->dominoes[i] != i) + state->flags[i] &= ~GS_SET; + } +} + +/* Knowing a given cell cannot be a certain colour also tells us + * something about the other cell in that domino. */ +static int solve_unflag(game_state *state, int i, int which, + const char *why, rowcol *rc) +{ + int ii, ret = 0; +#if defined DEBUGGING || defined STANDALONE_SOLVER + int w = state->w; +#endif + + assert(i >= 0 && i < state->wh); + ii = state->common->dominoes[i]; + if (ii == i) return 0; + + if (rc) + debug(("solve_unflag: (%d,%d) for %s %d", i%w, i/w, rc->name, rc->num)); + + if ((state->flags[i] & GS_SET) && (state->grid[i] == which)) { + debug(("solve_unflag: (%d,%d) already %s, cannot unflag (for %s).", + i%w, i/w, NAME(which), why)); + return -1; + } + if ((state->flags[ii] & GS_SET) && (state->grid[ii] == OPPOSITE(which))) { + debug(("solve_unflag: (%d,%d) opposite already %s, cannot unflag (for %s).", + ii%w, ii/w, NAME(OPPOSITE(which)), why)); + return -1; + } + if (POSSIBLE(i, which)) { + state->flags[i] |= NOTFLAG(which); + ret++; + debug(("solve_unflag: (%d,%d) CANNOT be %s (%s)", + i%w, i/w, NAME(which), why)); + } + if (POSSIBLE(ii, OPPOSITE(which))) { + state->flags[ii] |= NOTFLAG(OPPOSITE(which)); + ret++; + debug(("solve_unflag: (%d,%d) CANNOT be %s (%s, other half)", + ii%w, ii/w, NAME(OPPOSITE(which)), why)); + } +#ifdef STANDALONE_SOLVER + if (verbose && ret) { + printf("(%d,%d)", i%w, i/w); + if (rc) printf(" in %s %d", rc->name, rc->num); + printf(" cannot be %s (%s); opposite (%d,%d) not %s.\n", + NAME(which), why, ii%w, ii/w, NAME(OPPOSITE(which))); + } +#endif + return ret; +} + +static int solve_unflag_surrounds(game_state *state, int i, int which) +{ + int x = i%state->w, y = i/state->w, xx, yy, j, ii; + + assert(INGRID(state, x, y)); + + for (j = 0; j < 4; j++) { + xx = x+dx[j]; yy = y+dy[j]; + if (!INGRID(state, xx, yy)) continue; + + ii = yy*state->w+xx; + if (solve_unflag(state, ii, which, "adjacent to set cell", NULL) < 0) + return -1; + } + return 0; +} + +/* Sets a cell to a particular colour, and also perform other + * housekeeping around that. */ +static int solve_set(game_state *state, int i, int which, + const char *why, rowcol *rc) +{ + int ii; +#if defined DEBUGGING || defined STANDALONE_SOLVER + int w = state->w; +#endif + + ii = state->common->dominoes[i]; + + if (state->flags[i] & GS_SET) { + if (state->grid[i] == which) { + return 0; /* was already set and held, do nothing. */ + } else { + debug(("solve_set: (%d,%d) is held and %s, cannot set to %s", + i%w, i/w, NAME(state->grid[i]), NAME(which))); + return -1; + } + } + if ((state->flags[ii] & GS_SET) && state->grid[ii] != OPPOSITE(which)) { + debug(("solve_set: (%d,%d) opposite is held and %s, cannot set to %s", + ii%w, ii/w, NAME(state->grid[ii]), NAME(OPPOSITE(which)))); + return -1; + } + if (!POSSIBLE(i, which)) { + debug(("solve_set: (%d,%d) NOT %s, cannot set.", i%w, i/w, NAME(which))); + return -1; + } + if (!POSSIBLE(ii, OPPOSITE(which))) { + debug(("solve_set: (%d,%d) NOT %s, cannot set (%d,%d).", + ii%w, ii/w, NAME(OPPOSITE(which)), i%w, i/w)); + return -1; + } + +#ifdef STANDALONE_SOLVER + if (verbose) { + printf("(%d,%d)", i%w, i/w); + if (rc) printf(" in %s %d", rc->name, rc->num); + printf(" set to %s (%s), opposite (%d,%d) set to %s.\n", + NAME(which), why, ii%w, ii/w, NAME(OPPOSITE(which))); + } +#endif + if (rc) + debug(("solve_set: (%d,%d) for %s %d", i%w, i/w, rc->name, rc->num)); + debug(("solve_set: (%d,%d) setting to %s (%s), surrounds first:", + i%w, i/w, NAME(which), why)); + + if (which != NEUTRAL) { + if (solve_unflag_surrounds(state, i, which) < 0) + return -1; + if (solve_unflag_surrounds(state, ii, OPPOSITE(which)) < 0) + return -1; + } + + state->grid[i] = which; + state->grid[ii] = OPPOSITE(which); + + state->flags[i] |= GS_SET; + state->flags[ii] |= GS_SET; + + debug(("solve_set: (%d,%d) set to %s (%s)", i%w, i/w, NAME(which), why)); + + return 1; +} + +/* counts should be int[4]. */ +static void solve_counts(game_state *state, rowcol rc, int *counts, int *unset) +{ + int i, j, which; + + assert(counts); + for (i = 0; i < 4; i++) { + counts[i] = 0; + if (unset) unset[i] = 0; + } + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) { + assert(state->grid[i] < 3); + counts[state->grid[i]]++; + } else if (unset) { + for (which = 0; which <= 2; which++) { + if (POSSIBLE(i, which)) + unset[which]++; + } + } + } +} + +static int solve_checkfull(game_state *state, rowcol rc, int *counts) +{ + int starti = rc.i, j, which, didsth = 0, target; + int unset[4]; + + assert(state->numbered); /* only useful (should only be called) if numbered. */ + + solve_counts(state, rc, counts, unset); + + for (which = 0; which <= 2; which++) { + target = rc.targets[which]; + if (target == -1) continue; + + /*debug(("%s %d for %s: target %d, count %d, unset %d", + rc.name, rc.num, NAME(which), + target, counts[which], unset[which]));*/ + + if (target < counts[which]) { + debug(("%s %d has too many (%d) %s squares (target %d), impossible!", + rc.name, rc.num, counts[which], NAME(which), target)); + return -1; + } + if (target == counts[which]) { + /* We have the correct no. of the colour in this row/column + * already; unflag all the rest. */ + for (rc.i = starti, j = 0; j < rc.n; rc.i += rc.di, j++) { + if (state->flags[rc.i] & GS_SET) continue; + if (!POSSIBLE(rc.i, which)) continue; + + if (solve_unflag(state, rc.i, which, "row/col full", &rc) < 0) + return -1; + didsth = 1; + } + } else if ((target - counts[which]) == unset[which]) { + /* We need all the remaining unset squares for this colour; + * set them all. */ + for (rc.i = starti, j = 0; j < rc.n; rc.i += rc.di, j++) { + if (state->flags[rc.i] & GS_SET) continue; + if (!POSSIBLE(rc.i, which)) continue; + + if (solve_set(state, rc.i, which, "row/col needs all unset", &rc) < 0) + return -1; + didsth = 1; + } + } + } + return didsth; +} + +static int solve_startflags(game_state *state) +{ + int x, y, i; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*state->w+x; + if (state->common->dominoes[i] == i) continue; + if (state->grid[i] != NEUTRAL || + state->flags[i] & GS_SET) { + if (solve_set(state, i, state->grid[i], "initial set-and-hold", NULL) < 0) + return -1; + } + } + } + return 0; +} + +typedef int (*rowcolfn)(game_state *state, rowcol rc, int *counts); + +static int solve_rowcols(game_state *state, rowcolfn fn) +{ + int x, y, didsth = 0, ret; + rowcol rc; + int counts[4]; + + for (x = 0; x < state->w; x++) { + rc = mkrowcol(state, x, COLUMN); + solve_counts(state, rc, counts, NULL); + + ret = fn(state, rc, counts); + if (ret < 0) return ret; + didsth += ret; + } + for (y = 0; y < state->h; y++) { + rc = mkrowcol(state, y, ROW); + solve_counts(state, rc, counts, NULL); + + ret = fn(state, rc, counts); + if (ret < 0) return ret; + didsth += ret; + } + return didsth; +} + +static int solve_force(game_state *state) +{ + int i, which, didsth = 0; + unsigned long f; + + for (i = 0; i < state->wh; i++) { + if (state->flags[i] & GS_SET) continue; + if (state->common->dominoes[i] == i) continue; + + f = state->flags[i] & GS_NOTMASK; + which = -1; + if (f == (GS_NOTPOSITIVE|GS_NOTNEGATIVE)) + which = NEUTRAL; + if (f == (GS_NOTPOSITIVE|GS_NOTNEUTRAL)) + which = NEGATIVE; + if (f == (GS_NOTNEGATIVE|GS_NOTNEUTRAL)) + which = POSITIVE; + if (which != -1) { + if (solve_set(state, i, which, "forced by flags", NULL) < 0) + return -1; + didsth = 1; + } + } + return didsth; +} + +static int solve_neither(game_state *state) +{ + int i, j, didsth = 0; + + for (i = 0; i < state->wh; i++) { + if (state->flags[i] & GS_SET) continue; + j = state->common->dominoes[i]; + if (i == j) continue; + + if (((state->flags[i] & GS_NOTPOSITIVE) && + (state->flags[j] & GS_NOTPOSITIVE)) || + ((state->flags[i] & GS_NOTNEGATIVE) && + (state->flags[j] & GS_NOTNEGATIVE))) { + if (solve_set(state, i, NEUTRAL, "neither tile magnet", NULL) < 0) + return -1; + didsth = 1; + } + } + return didsth; +} + +static int solve_advancedfull(game_state *state, rowcol rc, int *counts) +{ + int i, j, nfound = 0, clearpos = 0, clearneg = 0, ret = 0; + + /* For this row/col, look for a domino entirely within the row where + * both ends can only be + or - (but isn't held). + * The +/- counts can thus be decremented by 1 each, and the 'unset' + * count by 2. + * + * Once that's done for all such dominoes (and they're marked), try + * and made usual deductions about rest of the row based on new totals. */ + + if (rc.targets[POSITIVE] == -1 && rc.targets[NEGATIVE] == -1) + return 0; /* don't have a target for either colour, nothing to do. */ + if ((rc.targets[POSITIVE] >= 0 && counts[POSITIVE] == rc.targets[POSITIVE]) && + (rc.targets[NEGATIVE] >= 0 && counts[NEGATIVE] == rc.targets[NEGATIVE])) + return 0; /* both colours are full up already, nothing to do. */ + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) + state->flags[i] &= ~GS_MARK; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) continue; + + /* We're looking for a domino in our row/col, thus if + * dominoes[i] -> i+di we've found one. */ + if (state->common->dominoes[i] != i+rc.di) continue; + + /* We need both squares of this domino to be either + or - + * (i.e. both NOTNEUTRAL only). */ + if (((state->flags[i] & GS_NOTMASK) != GS_NOTNEUTRAL) || + ((state->flags[i+rc.di] & GS_NOTMASK) != GS_NOTNEUTRAL)) + continue; + + debug(("Domino in %s %d at (%d,%d) must be polarised.", + rc.name, rc.num, i%state->w, i/state->w)); + state->flags[i] |= GS_MARK; + state->flags[i+rc.di] |= GS_MARK; + nfound++; + } + if (nfound == 0) return 0; + + /* nfound is #dominoes we matched, which will all be marked. */ + counts[POSITIVE] += nfound; + counts[NEGATIVE] += nfound; + + if (rc.targets[POSITIVE] >= 0 && counts[POSITIVE] == rc.targets[POSITIVE]) { + debug(("%s %d has now filled POSITIVE:", rc.name, rc.num)); + clearpos = 1; + } + if (rc.targets[NEGATIVE] >= 0 && counts[NEGATIVE] == rc.targets[NEGATIVE]) { + debug(("%s %d has now filled NEGATIVE:", rc.name, rc.num)); + clearneg = 1; + } + + if (!clearpos && !clearneg) return 0; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) continue; + if (state->flags[i] & GS_MARK) continue; + + if (clearpos && !(state->flags[i] & GS_NOTPOSITIVE)) { + if (solve_unflag(state, i, POSITIVE, "row/col full (+ve) [tricky]", &rc) < 0) + return -1; + ret++; + } + if (clearneg && !(state->flags[i] & GS_NOTNEGATIVE)) { + if (solve_unflag(state, i, NEGATIVE, "row/col full (-ve) [tricky]", &rc) < 0) + return -1; + ret++; + } + } + + return ret; +} + +/* If we only have one neutral still to place on a row/column then no + dominoes entirely in that row/column can be neutral. */ +static int solve_nonneutral(game_state *state, rowcol rc, int *counts) +{ + int i, j, ret = 0; + + if (rc.targets[NEUTRAL] != counts[NEUTRAL]+1) + return 0; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) continue; + if (state->common->dominoes[i] != i+rc.di) continue; + + if (!(state->flags[i] & GS_NOTNEUTRAL)) { + if (solve_unflag(state, i, NEUTRAL, "single neutral in row/col [tricky]", &rc) < 0) + return -1; + ret++; + } + } + return ret; +} + +/* If we need to fill all unfilled cells with +-, and we need 1 more of + * one than the other, and we have a single odd-numbered region of unfilled + * cells, that odd-numbered region must start and end with the extra number. */ +static int solve_oddlength(game_state *state, rowcol rc, int *counts) +{ + int i, j, ret = 0, extra, tpos, tneg; + int start = -1, length = 0, inempty = 0, startodd = -1; + + /* need zero neutral cells still to find... */ + if (rc.targets[NEUTRAL] != counts[NEUTRAL]) + return 0; + + /* ...and #positive and #negative to differ by one. */ + tpos = rc.targets[POSITIVE] - counts[POSITIVE]; + tneg = rc.targets[NEGATIVE] - counts[NEGATIVE]; + if (tpos == tneg+1) + extra = POSITIVE; + else if (tneg == tpos+1) + extra = NEGATIVE; + else return 0; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) { + if (inempty) { + if (length % 2) { + /* we've just finished an odd-length section. */ + if (startodd != -1) goto twoodd; + startodd = start; + } + inempty = 0; + } + } else { + if (inempty) + length++; + else { + start = i; + length = 1; + inempty = 1; + } + } + } + if (inempty && (length % 2)) { + if (startodd != -1) goto twoodd; + startodd = start; + } + if (startodd != -1) + ret = solve_set(state, startodd, extra, "odd-length section start", &rc); + + return ret; + +twoodd: + debug(("%s %d has >1 odd-length sections, starting at %d,%d and %d,%d.", + rc.name, rc.num, + startodd%state->w, startodd/state->w, + start%state->w, start/state->w)); + return 0; +} + +/* Count the number of remaining empty dominoes in any row/col. + * If that number is equal to the #remaining positive, + * or to the #remaining negative, no empty cells can be neutral. */ +static int solve_countdominoes_neutral(game_state *state, rowcol rc, int *counts) +{ + int i, j, ndom = 0, nonn = 0, ret = 0; + + if ((rc.targets[POSITIVE] == -1) && (rc.targets[NEGATIVE] == -1)) + return 0; /* need at least one target to compare. */ + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) continue; + assert(state->grid[i] == EMPTY); + + /* Skip solo cells, or second cell in domino. */ + if ((state->common->dominoes[i] == i) || + (state->common->dominoes[i] == i-rc.di)) + continue; + + ndom++; + } + + if ((rc.targets[POSITIVE] != -1) && + (rc.targets[POSITIVE]-counts[POSITIVE] == ndom)) + nonn = 1; + if ((rc.targets[NEGATIVE] != -1) && + (rc.targets[NEGATIVE]-counts[NEGATIVE] == ndom)) + nonn = 1; + + if (!nonn) return 0; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (state->flags[i] & GS_SET) continue; + + if (!(state->flags[i] & GS_NOTNEUTRAL)) { + if (solve_unflag(state, i, NEUTRAL, "all dominoes +/- [tricky]", &rc) < 0) + return -1; + ret++; + } + } + return ret; +} + +static int solve_domino_count(game_state *state, rowcol rc, int i, int which) +{ + int nposs = 0; + + /* Skip solo cells or 2nd in domino. */ + if ((state->common->dominoes[i] == i) || + (state->common->dominoes[i] == i-rc.di)) + return 0; + + if (state->flags[i] & GS_SET) + return 0; + + if (POSSIBLE(i, which)) + nposs++; + + if (state->common->dominoes[i] == i+rc.di) { + /* second cell of domino is on our row: test that too. */ + if (POSSIBLE(i+rc.di, which)) + nposs++; + } + return nposs; +} + +/* Count number of dominoes we could put each of + and - into. If it is equal + * to the #left, any domino we can only put + or - in one cell of must have it. */ +static int solve_countdominoes_nonneutral(game_state *state, rowcol rc, int *counts) +{ + int which, w, i, j, ndom = 0, didsth = 0, toset; + + for (which = POSITIVE, w = 0; w < 2; which = OPPOSITE(which), w++) { + if (rc.targets[which] == -1) continue; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (solve_domino_count(state, rc, i, which) > 0) + ndom++; + } + + if ((rc.targets[which] - counts[which]) != ndom) + continue; + + for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) { + if (solve_domino_count(state, rc, i, which) == 1) { + if (POSSIBLE(i, which)) + toset = i; + else { + /* paranoia, should have been checked by solve_domino_count. */ + assert(state->common->dominoes[i] == i+rc.di); + assert(POSSIBLE(i+rc.di, which)); + toset = i+rc.di; + } + if (solve_set(state, toset, which, "all empty dominoes need +/- [tricky]", &rc) < 0) + return -1; + didsth++; + } + } + } + return didsth; +} + +/* danger, evil macro. can't use the do { ... } while(0) trick because + * the continue breaks. */ +#define SOLVE_FOR_ROWCOLS(fn) \ + ret = solve_rowcols(state, fn); \ + if (ret < 0) { debug(("%s said impossible, cannot solve", #fn)); return -1; } \ + if (ret > 0) continue + +static int solve_state(game_state *state, int diff) +{ + int ret; + + debug(("solve_state, difficulty %s", magnets_diffnames[diff])); + + solve_clearflags(state); + if (solve_startflags(state) < 0) return -1; + + while (1) { + ret = solve_force(state); + if (ret > 0) continue; + if (ret < 0) return -1; + + ret = solve_neither(state); + if (ret > 0) continue; + if (ret < 0) return -1; + + SOLVE_FOR_ROWCOLS(solve_checkfull); + SOLVE_FOR_ROWCOLS(solve_oddlength); + + if (diff < DIFF_TRICKY) break; + + SOLVE_FOR_ROWCOLS(solve_advancedfull); + SOLVE_FOR_ROWCOLS(solve_nonneutral); + SOLVE_FOR_ROWCOLS(solve_countdominoes_neutral); + SOLVE_FOR_ROWCOLS(solve_countdominoes_nonneutral); + + /* more ... */ + + break; + } + return check_completion(state); +} + + +static char *game_state_diff(const game_state *src, const game_state *dst, + int issolve) +{ + char *ret = NULL, buf[80], c; + int retlen = 0, x, y, i, k; + + assert(src->w == dst->w && src->h == dst->h); + + if (issolve) { + ret = sresize(ret, 3, char); + ret[0] = 'S'; ret[1] = ';'; ret[2] = '\0'; + retlen += 2; + } + for (x = 0; x < dst->w; x++) { + for (y = 0; y < dst->h; y++) { + i = y*dst->w+x; + + if (src->common->dominoes[i] == i) continue; + +#define APPEND do { \ + ret = sresize(ret, retlen + k + 1, char); \ + strcpy(ret + retlen, buf); \ + retlen += k; \ +} while(0) + + if ((src->grid[i] != dst->grid[i]) || + ((src->flags[i] & GS_SET) != (dst->flags[i] & GS_SET))) { + if (dst->grid[i] == EMPTY && !(dst->flags[i] & GS_SET)) + c = ' '; + else + c = GRID2CHAR(dst->grid[i]); + k = sprintf(buf, "%c%d,%d;", (int)c, x, y); + APPEND; + } + } + } + debug(("game_state_diff returns %s", ret)); + return ret; +} + +static void solve_from_aux(const game_state *state, const char *aux) +{ + int i; + assert(strlen(aux) == state->wh); + for (i = 0; i < state->wh; i++) { + state->grid[i] = CHAR2GRID(aux[i]); + state->flags[i] |= GS_SET; + } +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved = dup_game(currstate); + char *move = NULL; + int ret; + + if (aux && strlen(aux) == state->wh) { + solve_from_aux(solved, aux); + goto solved; + } + + if (solve_state(solved, DIFFCOUNT) > 0) goto solved; + free_game(solved); + + solved = dup_game(state); + ret = solve_state(solved, DIFFCOUNT); + if (ret > 0) goto solved; + free_game(solved); + + *error = (ret < 0) ? "Puzzle is impossible." : "Unable to solve puzzle."; + return NULL; + +solved: + move = game_state_diff(currstate, solved, 1); + free_game(solved); + return move; +} + +static int solve_unnumbered(game_state *state) +{ + int i, ret; + while (1) { + ret = solve_force(state); + if (ret > 0) continue; + if (ret < 0) return -1; + + ret = solve_neither(state); + if (ret > 0) continue; + if (ret < 0) return -1; + + break; + } + for (i = 0; i < state->wh; i++) { + if (!(state->flags[i] & GS_SET)) return 0; + } + return 1; +} + +static int lay_dominoes(game_state *state, random_state *rs, int *scratch) +{ + int n, i, ret = 0, nlaid = 0, n_initial_neutral; + + for (i = 0; i < state->wh; i++) { + scratch[i] = i; + state->grid[i] = EMPTY; + state->flags[i] = (state->common->dominoes[i] == i) ? GS_SET : 0; + } + shuffle(scratch, state->wh, sizeof(int), rs); + + n_initial_neutral = (state->wh > 100) ? 5 : (state->wh / 10); + + for (n = 0; n < state->wh; n++) { + /* Find a space ... */ + + i = scratch[n]; + if (state->flags[i] & GS_SET) continue; /* already laid here. */ + + /* ...and lay a domino if we can. */ + + debug(("Laying domino at i:%d, (%d,%d)\n", i, i%state->w, i/state->w)); + + /* The choice of which type of domino to lay here leads to subtle differences + * in the sorts of boards that get produced. Too much bias towards magnets + * leads to games that are too easy. + * + * Currently, it lays a small set of dominoes at random as neutral, and + * then lays the rest preferring to be magnets -- however, if the + * current layout is such that a magnet won't go there, then it lays + * another neutral. + * + * The number of initially neutral dominoes is limited as grids get bigger: + * too many neutral dominoes invariably ends up with insoluble puzzle at + * this size, and the positioning process means it'll always end up laying + * more than the initial 5 anyway. + */ + + /* We should always be able to lay a neutral anywhere. */ + assert(!(state->flags[i] & GS_NOTNEUTRAL)); + + if (n < n_initial_neutral) { + debug((" ...laying neutral\n")); + ret = solve_set(state, i, NEUTRAL, "layout initial neutral", NULL); + } else { + debug((" ... preferring magnet\n")); + if (!(state->flags[i] & GS_NOTPOSITIVE)) + ret = solve_set(state, i, POSITIVE, "layout", NULL); + else if (!(state->flags[i] & GS_NOTNEGATIVE)) + ret = solve_set(state, i, NEGATIVE, "layout", NULL); + else + ret = solve_set(state, i, NEUTRAL, "layout", NULL); + } + if (!ret) { + debug(("Unable to lay anything at (%d,%d), giving up.", + i%state->w, i/state->w)); + ret = -1; + break; + } + + nlaid++; + ret = solve_unnumbered(state); + if (ret == -1) + debug(("solve_unnumbered decided impossible.\n")); + if (ret != 0) + break; + } + + debug(("Laid %d dominoes, total %d dominoes.\n", nlaid, state->wh/2)); + game_debug(state, "Final layout"); + return ret; +} + +static void gen_game(game_state *new, random_state *rs) +{ + int ret, x, y, val; + int *scratch = snewn(new->wh, int); + +#ifdef STANDALONE_SOLVER + if (verbose) printf("Generating new game...\n"); +#endif + + clear_state(new); + sfree(new->common->dominoes); /* bit grotty. */ + new->common->dominoes = domino_layout(new->w, new->h, rs); + + do { + ret = lay_dominoes(new, rs, scratch); + } while(ret == -1); + + /* for each cell, update colcount/rowcount as appropriate. */ + memset(new->common->colcount, 0, new->w*3*sizeof(int)); + memset(new->common->rowcount, 0, new->h*3*sizeof(int)); + for (x = 0; x < new->w; x++) { + for (y = 0; y < new->h; y++) { + val = new->grid[y*new->w+x]; + new->common->colcount[x*3+val]++; + new->common->rowcount[y*3+val]++; + } + } + new->numbered = 1; + + sfree(scratch); +} + +static void generate_aux(game_state *new, char *aux) +{ + int i; + for (i = 0; i < new->wh; i++) + aux[i] = GRID2CHAR(new->grid[i]); + aux[new->wh] = '\0'; +} + +static int check_difficulty(const game_params *params, game_state *new, + random_state *rs) +{ + int *scratch, *grid_correct, slen, i; + + memset(new->grid, EMPTY, new->wh*sizeof(int)); + + if (params->diff > DIFF_EASY) { + /* If this is too easy, return. */ + if (solve_state(new, params->diff-1) > 0) { + debug(("Puzzle is too easy.")); + return -1; + } + } + if (solve_state(new, params->diff) <= 0) { + debug(("Puzzle is not soluble at requested difficulty.")); + return -1; + } + if (!params->stripclues) return 0; + + /* Copy the correct grid away. */ + grid_correct = snewn(new->wh, int); + memcpy(grid_correct, new->grid, new->wh*sizeof(int)); + + /* Create shuffled array of side-clue locations. */ + slen = new->w*2 + new->h*2; + scratch = snewn(slen, int); + for (i = 0; i < slen; i++) scratch[i] = i; + shuffle(scratch, slen, sizeof(int), rs); + + /* For each clue, check whether removing it makes the puzzle unsoluble; + * put it back if so. */ + for (i = 0; i < slen; i++) { + int num = scratch[i], which, roworcol, target, targetn, ret; + rowcol rc; + + /* work out which clue we meant. */ + if (num < new->w+new->h) { which = POSITIVE; } + else { which = NEGATIVE; num -= new->w+new->h; } + + if (num < new->w) { roworcol = COLUMN; } + else { roworcol = ROW; num -= new->w; } + + /* num is now the row/column index in question. */ + rc = mkrowcol(new, num, roworcol); + + /* Remove clue, storing original... */ + target = rc.targets[which]; + targetn = rc.targets[NEUTRAL]; + rc.targets[which] = -1; + rc.targets[NEUTRAL] = -1; + + /* ...and see if we can still solve it. */ + game_debug(new, "removed clue, new board:"); + memset(new->grid, EMPTY, new->wh * sizeof(int)); + ret = solve_state(new, params->diff); + assert(ret != -1); + + if (ret == 0 || + memcmp(new->grid, grid_correct, new->wh*sizeof(int)) != 0) { + /* We made it ambiguous: put clue back. */ + debug(("...now impossible/different, put clue back.")); + rc.targets[which] = target; + rc.targets[NEUTRAL] = targetn; + } + } + sfree(scratch); + sfree(grid_correct); + + return 0; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux_r, int interactive) +{ + game_state *new = new_state(params->w, params->h); + char *desc, *aux = snewn(new->wh+1, char); + + do { + gen_game(new, rs); + generate_aux(new, aux); + } while (check_difficulty(params, new, rs) < 0); + + /* now we're complete, generate the description string + * and an aux_info for the completed game. */ + desc = generate_desc(new); + + free_game(new); + + *aux_r = aux; + return desc; +} + +struct game_ui { + int cur_x, cur_y, cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = ui->cur_y = 0; + ui->cur_visible = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (!oldstate->completed && newstate->completed) + ui->cur_visible = 0; +} + +struct game_drawstate { + int tilesize, started, solved; + int w, h; + unsigned long *what; /* size w*h */ + unsigned long *colwhat, *rowwhat; /* size 3*w, 3*h */ +}; + +#define DS_WHICH_MASK 0xf + +#define DS_ERROR 0x10 +#define DS_CURSOR 0x20 +#define DS_SET 0x40 +#define DS_NOTPOS 0x80 +#define DS_NOTNEG 0x100 +#define DS_NOTNEU 0x200 +#define DS_FLASH 0x400 + +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 8) + +#define COORD(x) ( (x+1) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( (x - BORDER) / TILE_SIZE - 1 ) + +static int is_clue(const game_state *state, int x, int y) +{ + int h = state->h, w = state->w; + + if (((x == -1 || x == w) && y >= 0 && y < h) || + ((y == -1 || y == h) && x >= 0 && x < w)) + return TRUE; + + return FALSE; +} + +static int clue_index(const game_state *state, int x, int y) +{ + int h = state->h, w = state->w; + + if (y == -1) + return x; + else if (x == w) + return w + y; + else if (y == h) + return 2 * w + h - x - 1; + else if (x == -1) + return 2 * (w + h) - y - 1; + + return -1; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int gx = FROMCOORD(x), gy = FROMCOORD(y), idx, curr; + char *nullret = NULL, buf[80], movech; + enum { CYCLE_MAGNET, CYCLE_NEUTRAL } action; + + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, 0); + ui->cur_visible = 1; + return ""; + } else if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + action = (button == CURSOR_SELECT) ? CYCLE_MAGNET : CYCLE_NEUTRAL; + gx = ui->cur_x; + gy = ui->cur_y; + } else if (INGRID(state, gx, gy) && + (button == LEFT_BUTTON || button == RIGHT_BUTTON)) { + if (ui->cur_visible) { + ui->cur_visible = 0; + nullret = ""; + } + action = (button == LEFT_BUTTON) ? CYCLE_MAGNET : CYCLE_NEUTRAL; + } else if (button == LEFT_BUTTON && is_clue(state, gx, gy)) { + sprintf(buf, "D%d,%d", gx, gy); + return dupstr(buf); + } else + return NULL; + + idx = gy * state->w + gx; + if (state->common->dominoes[idx] == idx) return nullret; + curr = state->grid[idx]; + + if (action == CYCLE_MAGNET) { + /* ... empty --> positive --> negative --> empty ... */ + + if (state->grid[idx] == NEUTRAL && state->flags[idx] & GS_SET) + return nullret; /* can't cycle a magnet from a neutral. */ + movech = (curr == EMPTY) ? '+' : (curr == POSITIVE) ? '-' : ' '; + } else if (action == CYCLE_NEUTRAL) { + /* ... empty -> neutral -> !neutral --> empty ... */ + + if (state->grid[idx] != NEUTRAL) + return nullret; /* can't cycle through neutral from a magnet. */ + + /* All of these are grid == EMPTY == NEUTRAL; it twiddles + * combinations of flags. */ + if (state->flags[idx] & GS_SET) /* neutral */ + movech = '?'; + else if (state->flags[idx] & GS_NOTNEUTRAL) /* !neutral */ + movech = ' '; + else + movech = '.'; + } else { + assert(!"unknown action"); + movech = 0; /* placate optimiser */ + } + + sprintf(buf, "%c%d,%d", movech, gx, gy); + + return dupstr(buf); +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *ret = dup_game(state); + int x, y, n, idx, idx2; + char c; + + if (!*move) goto badmove; + while (*move) { + c = *move++; + if (c == 'S') { + ret->solved = TRUE; + n = 0; + } else if (c == '+' || c == '-' || + c == '.' || c == ' ' || c == '?') { + if ((sscanf(move, "%d,%d%n", &x, &y, &n) != 2) || + !INGRID(state, x, y)) goto badmove; + + idx = y*state->w + x; + idx2 = state->common->dominoes[idx]; + if (idx == idx2) goto badmove; + + ret->flags[idx] &= ~GS_NOTMASK; + ret->flags[idx2] &= ~GS_NOTMASK; + + if (c == ' ' || c == '?') { + ret->grid[idx] = EMPTY; + ret->grid[idx2] = EMPTY; + ret->flags[idx] &= ~GS_SET; + ret->flags[idx2] &= ~GS_SET; + if (c == '?') { + ret->flags[idx] |= GS_NOTNEUTRAL; + ret->flags[idx2] |= GS_NOTNEUTRAL; + } + } else { + ret->grid[idx] = CHAR2GRID(c); + ret->grid[idx2] = OPPOSITE(CHAR2GRID(c)); + ret->flags[idx] |= GS_SET; + ret->flags[idx2] |= GS_SET; + } + } else if (c == 'D' && sscanf(move, "%d,%d%n", &x, &y, &n) == 2 && + is_clue(ret, x, y)) { + ret->counts_done[clue_index(ret, x, y)] ^= 1; + } else + goto badmove; + + move += n; + if (*move == ';') move++; + else if (*move) goto badmove; + } + if (check_completion(ret) == 1) + ret->completed = 1; + + return ret; + +badmove: + free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * (params->w+2) + 2 * BORDER; + *y = TILE_SIZE * (params->h+2) + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) { + ret[COL_TEXT * 3 + i] = 0.0F; + ret[COL_NEGATIVE * 3 + i] = 0.0F; + ret[COL_CURSOR * 3 + i] = 0.9F; + ret[COL_DONE * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.5F; + } + + ret[COL_POSITIVE * 3 + 0] = 0.8F; + ret[COL_POSITIVE * 3 + 1] = 0.0F; + ret[COL_POSITIVE * 3 + 2] = 0.0F; + + ret[COL_NEUTRAL * 3 + 0] = 0.10F; + ret[COL_NEUTRAL * 3 + 1] = 0.60F; + ret[COL_NEUTRAL * 3 + 2] = 0.10F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_NOT * 3 + 0] = 0.2F; + ret[COL_NOT * 3 + 1] = 0.2F; + ret[COL_NOT * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = ds->started = ds->solved = 0; + ds->w = state->w; + ds->h = state->h; + + ds->what = snewn(state->wh, unsigned long); + memset(ds->what, 0, state->wh*sizeof(unsigned long)); + + ds->colwhat = snewn(state->w*3, unsigned long); + memset(ds->colwhat, 0, state->w*3*sizeof(unsigned long)); + ds->rowwhat = snewn(state->h*3, unsigned long); + memset(ds->rowwhat, 0, state->h*3*sizeof(unsigned long)); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->colwhat); + sfree(ds->rowwhat); + sfree(ds->what); + sfree(ds); +} + +static void draw_num(drawing *dr, game_drawstate *ds, int rowcol, int which, + int idx, int colbg, int col, int num) +{ + char buf[32]; + int cx, cy, tsz; + + if (num < 0) return; + + sprintf(buf, "%d", num); + tsz = (strlen(buf) == 1) ? (7*TILE_SIZE/10) : (9*TILE_SIZE/10)/strlen(buf); + + if (rowcol == ROW) { + cx = BORDER; + if (which == NEGATIVE) cx += TILE_SIZE * (ds->w+1); + cy = BORDER + TILE_SIZE * (idx+1); + } else { + cx = BORDER + TILE_SIZE * (idx+1); + cy = BORDER; + if (which == NEGATIVE) cy += TILE_SIZE * (ds->h+1); + } + + draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, colbg); + draw_text(dr, cx + TILE_SIZE/2, cy + TILE_SIZE/2, FONT_VARIABLE, tsz, + ALIGN_VCENTRE | ALIGN_HCENTRE, col, buf); + + draw_update(dr, cx, cy, TILE_SIZE, TILE_SIZE); +} + +static void draw_sym(drawing *dr, game_drawstate *ds, int x, int y, int which, int col) +{ + int cx = COORD(x), cy = COORD(y); + int ccx = cx + TILE_SIZE/2, ccy = cy + TILE_SIZE/2; + int roff = TILE_SIZE/4, rsz = 2*roff+1; + int soff = TILE_SIZE/16, ssz = 2*soff+1; + + if (which == POSITIVE || which == NEGATIVE) { + draw_rect(dr, ccx - roff, ccy - soff, rsz, ssz, col); + if (which == POSITIVE) + draw_rect(dr, ccx - soff, ccy - roff, ssz, rsz, col); + } else if (col == COL_NOT) { + /* not-a-neutral is a blue question mark. */ + char qu[2] = { '?', 0 }; + draw_text(dr, ccx, ccy, FONT_VARIABLE, 7*TILE_SIZE/10, + ALIGN_VCENTRE | ALIGN_HCENTRE, col, qu); + } else { + draw_line(dr, ccx - roff, ccy - roff, ccx + roff, ccy + roff, col); + draw_line(dr, ccx + roff, ccy - roff, ccx - roff, ccy + roff, col); + } +} + +enum { + TYPE_L, + TYPE_R, + TYPE_T, + TYPE_B, + TYPE_BLANK +}; + +/* NOT responsible for redrawing background or updating. */ +static void draw_tile_col(drawing *dr, game_drawstate *ds, int *dominoes, + int x, int y, int which, int bg, int fg, int perc) +{ + int cx = COORD(x), cy = COORD(y), i, other, type = TYPE_BLANK; + int gutter, radius, coffset; + + /* gutter is TSZ/16 for 100%, 8*TSZ/16 (TSZ/2) for 0% */ + gutter = (TILE_SIZE / 16) + ((100 - perc) * (7*TILE_SIZE / 16))/100; + radius = (perc * (TILE_SIZE / 8)) / 100; + coffset = gutter + radius; + + i = y*ds->w + x; + other = dominoes[i]; + + if (other == i) return; + else if (other == i+1) type = TYPE_L; + else if (other == i-1) type = TYPE_R; + else if (other == i+ds->w) type = TYPE_T; + else if (other == i-ds->w) type = TYPE_B; + else assert(!"mad domino orientation"); + + /* domino drawing shamelessly stolen from dominosa.c. */ + if (type == TYPE_L || type == TYPE_T) + draw_circle(dr, cx+coffset, cy+coffset, + radius, bg, bg); + if (type == TYPE_R || type == TYPE_T) + draw_circle(dr, cx+TILE_SIZE-1-coffset, cy+coffset, + radius, bg, bg); + if (type == TYPE_L || type == TYPE_B) + draw_circle(dr, cx+coffset, cy+TILE_SIZE-1-coffset, + radius, bg, bg); + if (type == TYPE_R || type == TYPE_B) + draw_circle(dr, cx+TILE_SIZE-1-coffset, + cy+TILE_SIZE-1-coffset, + radius, bg, bg); + + for (i = 0; i < 2; i++) { + int x1, y1, x2, y2; + + x1 = cx + (i ? gutter : coffset); + y1 = cy + (i ? coffset : gutter); + x2 = cx + TILE_SIZE-1 - (i ? gutter : coffset); + y2 = cy + TILE_SIZE-1 - (i ? coffset : gutter); + if (type == TYPE_L) + x2 = cx + TILE_SIZE; + else if (type == TYPE_R) + x1 = cx; + else if (type == TYPE_T) + y2 = cy + TILE_SIZE ; + else if (type == TYPE_B) + y1 = cy; + + draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg); + } + + if (fg != -1) draw_sym(dr, ds, x, y, which, fg); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, int *dominoes, + int x, int y, unsigned long flags) +{ + int cx = COORD(x), cy = COORD(y), bg, fg, perc = 100; + int which = flags & DS_WHICH_MASK; + + flags &= ~DS_WHICH_MASK; + + draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND); + + if (flags & DS_CURSOR) + bg = COL_CURSOR; /* off-white white for cursor */ + else if (which == POSITIVE) + bg = COL_POSITIVE; + else if (which == NEGATIVE) + bg = COL_NEGATIVE; + else if (flags & DS_SET) + bg = COL_NEUTRAL; /* green inner for neutral cells */ + else + bg = COL_LOWLIGHT; /* light grey for empty cells. */ + + if (which == EMPTY && !(flags & DS_SET)) { + int notwhich = -1; + fg = -1; /* don't draw cross unless actually set as neutral. */ + + if (flags & DS_NOTPOS) notwhich = POSITIVE; + if (flags & DS_NOTNEG) notwhich = NEGATIVE; + if (flags & DS_NOTNEU) notwhich = NEUTRAL; + if (notwhich != -1) { + which = notwhich; + fg = COL_NOT; + } + } else + fg = (flags & DS_ERROR) ? COL_ERROR : + (flags & DS_CURSOR) ? COL_TEXT : COL_BACKGROUND; + + draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND); + + if (flags & DS_FLASH) { + int bordercol = COL_HIGHLIGHT; + draw_tile_col(dr, ds, dominoes, x, y, which, bordercol, -1, perc); + perc = 3*perc/4; + } + draw_tile_col(dr, ds, dominoes, x, y, which, bg, fg, perc); + + draw_update(dr, cx, cy, TILE_SIZE, TILE_SIZE); +} + +static int get_count_color(const game_state *state, int rowcol, int which, + int index, int target) +{ + int idx; + int count = count_rowcol(state, index, rowcol, which); + + if ((count > target) || + (count < target && !count_rowcol(state, index, rowcol, -1))) { + return COL_ERROR; + } else if (rowcol == COLUMN) { + idx = clue_index(state, index, which == POSITIVE ? -1 : state->h); + } else { + idx = clue_index(state, which == POSITIVE ? -1 : state->w, index); + } + + if (state->counts_done[idx]) { + return COL_DONE; + } + + return COL_TEXT; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y, w = state->w, h = state->h, which, i, j, flash; + + flash = (int)(flashtime * 5 / FLASH_TIME) % 2; + + if (!ds->started) { + /* draw background, corner +-. */ + draw_rect(dr, 0, 0, + TILE_SIZE * (w+2) + 2 * BORDER, + TILE_SIZE * (h+2) + 2 * BORDER, + COL_BACKGROUND); + + draw_sym(dr, ds, -1, -1, POSITIVE, COL_TEXT); + draw_sym(dr, ds, state->w, state->h, NEGATIVE, COL_TEXT); + + draw_update(dr, 0, 0, + TILE_SIZE * (ds->w+2) + 2 * BORDER, + TILE_SIZE * (ds->h+2) + 2 * BORDER); + } + + /* Draw grid */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int idx = y*w+x; + unsigned long c = state->grid[idx]; + + if (state->flags[idx] & GS_ERROR) + c |= DS_ERROR; + if (state->flags[idx] & GS_SET) + c |= DS_SET; + + if (x == ui->cur_x && y == ui->cur_y && ui->cur_visible) + c |= DS_CURSOR; + + if (flash) + c |= DS_FLASH; + + if (state->flags[idx] & GS_NOTPOSITIVE) + c |= DS_NOTPOS; + if (state->flags[idx] & GS_NOTNEGATIVE) + c |= DS_NOTNEG; + if (state->flags[idx] & GS_NOTNEUTRAL) + c |= DS_NOTNEU; + + if (ds->what[idx] != c || !ds->started) { + draw_tile(dr, ds, state->common->dominoes, x, y, c); + ds->what[idx] = c; + } + } + } + /* Draw counts around side */ + for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) { + for (i = 0; i < w; i++) { + int index = i * 3 + which; + int target = state->common->colcount[index]; + int color = get_count_color(state, COLUMN, which, i, target); + + if (color != ds->colwhat[index] || !ds->started) { + draw_num(dr, ds, COLUMN, which, i, COL_BACKGROUND, color, target); + ds->colwhat[index] = color; + } + } + for (i = 0; i < h; i++) { + int index = i * 3 + which; + int target = state->common->rowcount[index]; + int color = get_count_color(state, ROW, which, i, target); + + if (color != ds->rowwhat[index] || !ds->started) { + draw_num(dr, ds, ROW, which, i, COL_BACKGROUND, color, target); + ds->rowwhat[index] = color; + } + } + } + + ds->started = 1; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->solved && !newstate->solved) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->w, h = state->h; + int ink = print_mono_colour(dr, 0); + int paper = print_mono_colour(dr, 1); + int x, y, which, i, j; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + ds->w = w; ds->h = h; + + /* Border. */ + print_line_width(dr, TILE_SIZE/12); + + /* Numbers and +/- for corners. */ + draw_sym(dr, ds, -1, -1, POSITIVE, ink); + draw_sym(dr, ds, state->w, state->h, NEGATIVE, ink); + for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) { + for (i = 0; i < w; i++) { + draw_num(dr, ds, COLUMN, which, i, paper, ink, + state->common->colcount[i*3+which]); + } + for (i = 0; i < h; i++) { + draw_num(dr, ds, ROW, which, i, paper, ink, + state->common->rowcount[i*3+which]); + } + } + + /* Dominoes. */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + i = y*state->w + x; + if (state->common->dominoes[i] == i+1 || + state->common->dominoes[i] == i+w) { + int dx = state->common->dominoes[i] == i+1 ? 2 : 1; + int dy = 3 - dx; + int xx, yy; + int cx = COORD(x), cy = COORD(y); + + print_line_width(dr, 0); + + /* Ink the domino */ + for (yy = 0; yy < 2; yy++) + for (xx = 0; xx < 2; xx++) + draw_circle(dr, + cx+xx*dx*TILE_SIZE+(1-2*xx)*3*TILE_SIZE/16, + cy+yy*dy*TILE_SIZE+(1-2*yy)*3*TILE_SIZE/16, + TILE_SIZE/8, ink, ink); + draw_rect(dr, cx + TILE_SIZE/16, cy + 3*TILE_SIZE/16, + dx*TILE_SIZE - 2*(TILE_SIZE/16), + dy*TILE_SIZE - 6*(TILE_SIZE/16), ink); + draw_rect(dr, cx + 3*TILE_SIZE/16, cy + TILE_SIZE/16, + dx*TILE_SIZE - 6*(TILE_SIZE/16), + dy*TILE_SIZE - 2*(TILE_SIZE/16), ink); + + /* Un-ink the domino interior */ + for (yy = 0; yy < 2; yy++) + for (xx = 0; xx < 2; xx++) + draw_circle(dr, + cx+xx*dx*TILE_SIZE+(1-2*xx)*3*TILE_SIZE/16, + cy+yy*dy*TILE_SIZE+(1-2*yy)*3*TILE_SIZE/16, + 3*TILE_SIZE/32, paper, paper); + draw_rect(dr, cx + 3*TILE_SIZE/32, cy + 3*TILE_SIZE/16, + dx*TILE_SIZE - 2*(3*TILE_SIZE/32), + dy*TILE_SIZE - 6*(TILE_SIZE/16), paper); + draw_rect(dr, cx + 3*TILE_SIZE/16, cy + 3*TILE_SIZE/32, + dx*TILE_SIZE - 6*(TILE_SIZE/16), + dy*TILE_SIZE - 2*(3*TILE_SIZE/32), paper); + } + } + } + + /* Grid symbols (solution). */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + i = y*state->w + x; + if ((state->grid[i] != NEUTRAL) || (state->flags[i] & GS_SET)) + draw_sym(dr, ds, x, y, state->grid[i], ink); + } + } +} + +#ifdef COMBINED +#define thegame magnets +#endif + +const struct game thegame = { + "Magnets", "games.magnets", "magnets", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include +#include + +const char *quis = NULL; +int csv = 0; + +void usage(FILE *out) { + fprintf(out, "usage: %s [-v] [--print] |\n", quis); +} + +void doprint(game_state *state) +{ + char *fmt = game_text_format(state); + printf("%s", fmt); + sfree(fmt); +} + +static void pnum(int n, int ntot, const char *desc) +{ + printf("%2.1f%% (%d) %s", (double)n*100.0 / (double)ntot, n, desc); +} + +static void start_soak(game_params *p, random_state *rs) +{ + time_t tt_start, tt_now, tt_last; + char *aux; + game_state *s, *s2; + int n = 0, nsolved = 0, nimpossible = 0, ntricky = 0, ret, i; + long nn, nn_total = 0, nn_solved = 0, nn_tricky = 0; + + tt_start = tt_now = time(NULL); + + if (csv) + printf("time, w, h, #generated, #solved, #tricky, #impossible, " + "#neutral, #neutral/solved, #neutral/tricky\n"); + else + printf("Soak-testing a %dx%d grid.\n", p->w, p->h); + + s = new_state(p->w, p->h); + aux = snewn(s->wh+1, char); + + while (1) { + gen_game(s, rs); + + nn = 0; + for (i = 0; i < s->wh; i++) { + if (s->grid[i] == NEUTRAL) nn++; + } + + generate_aux(s, aux); + memset(s->grid, EMPTY, s->wh * sizeof(int)); + s2 = dup_game(s); + + ret = solve_state(s, DIFFCOUNT); + + n++; + nn_total += nn; + if (ret > 0) { + nsolved++; + nn_solved += nn; + if (solve_state(s2, DIFF_EASY) <= 0) { + ntricky++; + nn_tricky += nn; + } + } else if (ret < 0) { + char *desc = generate_desc(s); + solve_from_aux(s, aux); + printf("Game considered impossible:\n %dx%d:%s\n", + p->w, p->h, desc); + sfree(desc); + doprint(s); + nimpossible++; + } + + free_game(s2); + + tt_last = time(NULL); + if (tt_last > tt_now) { + tt_now = tt_last; + if (csv) { + printf("%d,%d,%d, %d,%d,%d,%d, %ld,%ld,%ld\n", + (int)(tt_now - tt_start), p->w, p->h, + n, nsolved, ntricky, nimpossible, + nn_total, nn_solved, nn_tricky); + } else { + printf("%d total, %3.1f/s, ", + n, (double)n / ((double)tt_now - tt_start)); + pnum(nsolved, n, "solved"); printf(", "); + pnum(ntricky, n, "tricky"); + if (nimpossible > 0) + pnum(nimpossible, n, "impossible"); + printf("\n"); + + printf(" overall %3.1f%% neutral (%3.1f%% for solved, %3.1f%% for tricky)\n", + (double)(nn_total * 100) / (double)(p->w * p->h * n), + (double)(nn_solved * 100) / (double)(p->w * p->h * nsolved), + (double)(nn_tricky * 100) / (double)(p->w * p->h * ntricky)); + } + } + } + free_game(s); + sfree(aux); +} + +int main(int argc, const char *argv[]) +{ + int print = 0, soak = 0, solved = 0, ret; + char *id = NULL, *desc, *desc_gen = NULL, *err, *aux = NULL; + game_state *s = NULL; + game_params *p = NULL; + random_state *rs = NULL; + time_t seed = time(NULL); + + setvbuf(stdout, NULL, _IONBF, 0); + + quis = argv[0]; + while (--argc > 0) { + char *p = (char*)(*++argv); + if (!strcmp(p, "-v") || !strcmp(p, "--verbose")) { + verbose = 1; + } else if (!strcmp(p, "--csv")) { + csv = 1; + } else if (!strcmp(p, "-e") || !strcmp(p, "--seed")) { + seed = atoi(*++argv); + argc--; + } else if (!strcmp(p, "-p") || !strcmp(p, "--print")) { + print = 1; + } else if (!strcmp(p, "-s") || !strcmp(p, "--soak")) { + soak = 1; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + usage(stderr); + exit(1); + } else { + id = p; + } + } + + rs = random_new((void*)&seed, sizeof(time_t)); + + if (!id) { + fprintf(stderr, "usage: %s [-v] [--soak] | \n", argv[0]); + goto done; + } + desc = strchr(id, ':'); + if (desc) *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_params(p, 1); + if (err) { + fprintf(stderr, "%s: %s", argv[0], err); + goto done; + } + + if (soak) { + if (desc) { + fprintf(stderr, "%s: --soak needs parameters, not description.\n", quis); + goto done; + } + start_soak(p, rs); + goto done; + } + + if (!desc) + desc = desc_gen = new_game_desc(p, rs, &aux, 0); + + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\nDescription: %s\n", quis, err, desc); + goto done; + } + s = new_game(NULL, p, desc); + printf("%s:%s (seed %ld)\n", id, desc, (long)seed); + if (aux) { + /* We just generated this ourself. */ + if (verbose || print) { + doprint(s); + solve_from_aux(s, aux); + solved = 1; + } + } else { + doprint(s); + verbose = 1; + ret = solve_state(s, DIFFCOUNT); + if (ret < 0) printf("Puzzle is impossible.\n"); + else if (ret == 0) printf("Puzzle is ambiguous.\n"); + else printf("Puzzle was solved.\n"); + verbose = 0; + solved = 1; + } + if (solved) doprint(s); + +done: + if (desc_gen) sfree(desc_gen); + if (p) free_params(p); + if (s) free_game(s); + if (rs) random_free(rs); + if (aux) sfree(aux); + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/makedist.sh b/apps/plugins/puzzles/makedist.sh new file mode 100755 index 0000000000..22b4f5d0ae --- /dev/null +++ b/apps/plugins/puzzles/makedist.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# Build a Unix source distribution from the Puzzles SVN area. +# +# Pass a version number argument to have the archive tagged with that +# version number. Otherwise, the script will not version-tag the +# archive at all. + +version="$1" + +if test "x$version" != "x"; then + arcsuffix="-$version" + ver="-DVER=$version" +else + arcsuffix= + ver= +fi + +perl mkfiles.pl +./mkauto.sh + +mkdir tmp.$$ +mkdir tmp.$$/puzzles$arcsuffix +mkdir tmp.$$/puzzles$arcsuffix/icons + +# Build Windows Help and text versions of the manual for convenience. +halibut --winhelp=puzzles.hlp --text=puzzles.txt puzzles.but + +# Build a text version of the HACKING document. +halibut --text=HACKING devel.but + +for i in *.c *.m *.h *.R *.rc *.but *.plist *.icns LICENCE README Recipe \ + *.rc2 mkfiles.pl Makefile Makefile.* \ + HACKING puzzles.txt puzzles.hlp puzzles.cnt puzzles.chm \ + icons/Makefile icons/*.sav icons/*.pl icons/*.sh icons/win16pal.xpm \ + icons/*.png icons/*.ico icons/*.rc icons/*.c \ + configure.ac mkauto.sh aclocal.m4 \ + configure depcomp install-sh missing compile; do + case $i in + */*) ln -s ../../../$i tmp.$$/puzzles$arcsuffix/$i;; + *) ln -s ../../$i tmp.$$/puzzles$arcsuffix/$i;; + esac +done + +tar -C tmp.$$ -chzf - puzzles$arcsuffix > ../puzzles$arcsuffix.tar.gz + +rm -rf tmp.$$ diff --git a/apps/plugins/puzzles/malloc.c b/apps/plugins/puzzles/malloc.c new file mode 100644 index 0000000000..d9943c6b40 --- /dev/null +++ b/apps/plugins/puzzles/malloc.c @@ -0,0 +1,61 @@ +/* + * malloc.c: safe wrappers around malloc, realloc, free, strdup + */ + +#include +#include +#include "puzzles.h" + +/* + * smalloc should guarantee to return a useful pointer - Halibut + * can do nothing except die when it's out of memory anyway. + */ + +int allocs = 0; +int frees = 0; + +void *smalloc(size_t size) { + void *p; + p = malloc(size); + LOGF("allocs: %d", ++allocs); + if (!p) + fatal("out of memory"); + return p; +} + +/* + * sfree should guaranteeably deal gracefully with freeing NULL + */ +void sfree(void *p) { + if (p) { + ++frees; + LOGF("frees: %d, total outstanding: %d", frees, allocs - frees); + free(p); + } +} + +/* + * srealloc should guaranteeably be able to realloc NULL + */ +void *srealloc(void *p, size_t size) { + void *q; + if (p) { + q = realloc(p, size); + } else { + LOGF("allocs: %d", ++allocs); + q = malloc(size); + } + if (!q) + fatal("out of memory"); + return q; +} + +/* + * dupstr is like strdup, but with the never-return-NULL property + * of smalloc (and also reliably defined in all environments :-) + */ +char *dupstr(const char *s) { + char *r = smalloc(1+strlen(s)); + strcpy(r,s); + return r; +} diff --git a/apps/plugins/puzzles/map.R b/apps/plugins/puzzles/map.R new file mode 100644 index 0000000000..1e702aa19d --- /dev/null +++ b/apps/plugins/puzzles/map.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +MAP_EXTRA = dsf + +map : [X] GTK COMMON map MAP_EXTRA map-icon|no-icon + +map : [G] WINDOWS COMMON map MAP_EXTRA map.res|noicon.res + +mapsolver : [U] map[STANDALONE_SOLVER] MAP_EXTRA STANDALONE m.lib +mapsolver : [C] map[STANDALONE_SOLVER] MAP_EXTRA STANDALONE + +ALL += map[COMBINED] MAP_EXTRA + +!begin am gtk +GAMES += map +!end + +!begin >list.c + A(map) \ +!end + +!begin >gamedesc.txt +map:map.exe:Map:Map-colouring puzzle:Colour the map so that adjacent regions are never the same colour. +!end diff --git a/apps/plugins/puzzles/map.c b/apps/plugins/puzzles/map.c new file mode 100644 index 0000000000..6e9c12523a --- /dev/null +++ b/apps/plugins/puzzles/map.c @@ -0,0 +1,3340 @@ +/* + * map.c: Game involving four-colouring a map. + */ + +/* + * TODO: + * + * - clue marking + * - better four-colouring algorithm? + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +/* + * In standalone solver mode, `verbose' is a variable which can be + * set by command-line option; in debugging mode it's simply always + * true. + */ +#if defined STANDALONE_SOLVER +#define SOLVER_DIAGNOSTICS +int verbose = FALSE; +#elif defined SOLVER_DIAGNOSTICS +#define verbose TRUE +#endif + +/* + * I don't seriously anticipate wanting to change the number of + * colours used in this game, but it doesn't cost much to use a + * #define just in case :-) + */ +#define FOUR 4 +#define THREE (FOUR-1) +#define FIVE (FOUR+1) +#define SIX (FOUR+2) + +/* + * Ghastly run-time configuration option, just for Gareth (again). + */ +static int flash_type = -1; +static float flash_length; + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(NORMAL,Normal,n) \ + A(HARD,Hard,h) \ + A(RECURSE,Unreasonable,u) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const map_diffnames[] = { DIFFLIST(TITLE) }; +static char const map_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +enum { TE, BE, LE, RE }; /* top/bottom/left/right edges */ + +enum { + COL_BACKGROUND, + COL_GRID, + COL_0, COL_1, COL_2, COL_3, + COL_ERROR, COL_ERRTEXT, + NCOLOURS +}; + +struct game_params { + int w, h, n, diff; +}; + +struct map { + int refcount; + int *map; + int *graph; + int n; + int ngraph; + int *immutable; + int *edgex, *edgey; /* position of a point on each edge */ + int *regionx, *regiony; /* position of a point in each region */ +}; + +struct game_state { + game_params p; + struct map *map; + int *colouring, *pencil; + int completed, cheated; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + +#ifdef PORTRAIT_SCREEN + ret->w = 16; + ret->h = 18; +#else + ret->w = 20; + ret->h = 15; +#endif + ret->n = 30; + ret->diff = DIFF_NORMAL; + + return ret; +} + +static const struct game_params map_presets[] = { +#ifdef PORTRAIT_SCREEN + {16, 18, 30, DIFF_EASY}, + {16, 18, 30, DIFF_NORMAL}, + {16, 18, 30, DIFF_HARD}, + {16, 18, 30, DIFF_RECURSE}, + {25, 30, 75, DIFF_NORMAL}, + {25, 30, 75, DIFF_HARD}, +#else + {20, 15, 30, DIFF_EASY}, + {20, 15, 30, DIFF_NORMAL}, + {20, 15, 30, DIFF_HARD}, + {20, 15, 30, DIFF_RECURSE}, + {30, 25, 75, DIFF_NORMAL}, + {30, 25, 75, DIFF_HARD}, +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(map_presets)) + return FALSE; + + ret = snew(game_params); + *ret = map_presets[i]; + + sprintf(str, "%dx%d, %d regions, %s", ret->w, ret->h, ret->n, + map_diffnames[ret->diff]); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + params->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->h = params->w; + } + if (*p == 'n') { + p++; + params->n = atoi(p); + while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++; + } else { + params->n = params->w * params->h / 8; + } + if (*p == 'd') { + int i; + p++; + for (i = 0; i < DIFFCOUNT; i++) + if (*p == map_diffchars[i]) + params->diff = i; + if (*p) p++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[400]; + + sprintf(ret, "%dx%dn%d", params->w, params->h, params->n); + if (full) + sprintf(ret + strlen(ret), "d%c", map_diffchars[params->diff]); + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Regions"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->n); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "Difficulty"; + ret[3].type = C_CHOICES; + ret[3].sval = DIFFCONFIG; + ret[3].ival = params->diff; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->n = atoi(cfg[2].sval); + ret->diff = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 2 || params->h < 2) + return "Width and height must be at least two"; + if (params->n < 5) + return "Must have at least five regions"; + if (params->n > params->w * params->h) + return "Too many regions to fit in grid"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Cumulative frequency table functions. + */ + +/* + * Initialise a cumulative frequency table. (Hardly worth writing + * this function; all it does is to initialise everything in the + * array to zero.) + */ +static void cf_init(int *table, int n) +{ + int i; + + for (i = 0; i < n; i++) + table[i] = 0; +} + +/* + * Increment the count of symbol `sym' by `count'. + */ +static void cf_add(int *table, int n, int sym, int count) +{ + int bit; + + bit = 1; + while (sym != 0) { + if (sym & bit) { + table[sym] += count; + sym &= ~bit; + } + bit <<= 1; + } + + table[0] += count; +} + +/* + * Cumulative frequency lookup: return the total count of symbols + * with value less than `sym'. + */ +static int cf_clookup(int *table, int n, int sym) +{ + int bit, index, limit, count; + + if (sym == 0) + return 0; + + assert(0 < sym && sym <= n); + + count = table[0]; /* start with the whole table size */ + + bit = 1; + while (bit < n) + bit <<= 1; + + limit = n; + + while (bit > 0) { + /* + * Find the least number with its lowest set bit in this + * position which is greater than or equal to sym. + */ + index = ((sym + bit - 1) &~ (bit * 2 - 1)) + bit; + + if (index < limit) { + count -= table[index]; + limit = index; + } + + bit >>= 1; + } + + return count; +} + +/* + * Single frequency lookup: return the count of symbol `sym'. + */ +static int cf_slookup(int *table, int n, int sym) +{ + int count, bit; + + assert(0 <= sym && sym < n); + + count = table[sym]; + + for (bit = 1; sym+bit < n && !(sym & bit); bit <<= 1) + count -= table[sym+bit]; + + return count; +} + +/* + * Return the largest symbol index such that the cumulative + * frequency up to that symbol is less than _or equal to_ count. + */ +static int cf_whichsym(int *table, int n, int count) { + int bit, sym, top; + + assert(count >= 0 && count < table[0]); + + bit = 1; + while (bit < n) + bit <<= 1; + + sym = 0; + top = table[0]; + + while (bit > 0) { + if (sym+bit < n) { + if (count >= top - table[sym+bit]) + sym += bit; + else + top -= table[sym+bit]; + } + + bit >>= 1; + } + + return sym; +} + +/* ---------------------------------------------------------------------- + * Map generation. + * + * FIXME: this isn't entirely optimal at present, because it + * inherently prioritises growing the largest region since there + * are more squares adjacent to it. This acts as a destabilising + * influence leading to a few large regions and mostly small ones. + * It might be better to do it some other way. + */ + +#define WEIGHT_INCREASED 2 /* for increased perimeter */ +#define WEIGHT_DECREASED 4 /* for decreased perimeter */ +#define WEIGHT_UNCHANGED 3 /* for unchanged perimeter */ + +/* + * Look at a square and decide which colours can be extended into + * it. + * + * If called with index < 0, it adds together one of + * WEIGHT_INCREASED, WEIGHT_DECREASED or WEIGHT_UNCHANGED for each + * colour that has a valid extension (according to the effect that + * it would have on the perimeter of the region being extended) and + * returns the overall total. + * + * If called with index >= 0, it returns one of the possible + * colours depending on the value of index, in such a way that the + * number of possible inputs which would give rise to a given + * return value correspond to the weight of that value. + */ +static int extend_options(int w, int h, int n, int *map, + int x, int y, int index) +{ + int c, i, dx, dy; + int col[8]; + int total = 0; + + if (map[y*w+x] >= 0) { + assert(index < 0); + return 0; /* can't do this square at all */ + } + + /* + * Fetch the eight neighbours of this square, in order around + * the square. + */ + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) { + int index = (dy < 0 ? 6-dx : dy > 0 ? 2+dx : 2*(1+dx)); + if (x+dx >= 0 && x+dx < w && y+dy >= 0 && y+dy < h) + col[index] = map[(y+dy)*w+(x+dx)]; + else + col[index] = -1; + } + + /* + * Iterate over each colour that might be feasible. + * + * FIXME: this routine currently has O(n) running time. We + * could turn it into O(FOUR) by only bothering to iterate over + * the colours mentioned in the four neighbouring squares. + */ + + for (c = 0; c < n; c++) { + int count, neighbours, runs; + + /* + * One of the even indices of col (representing the + * orthogonal neighbours of this square) must be equal to + * c, or else this square is not adjacent to region c and + * obviously cannot become an extension of it at this time. + */ + neighbours = 0; + for (i = 0; i < 8; i += 2) + if (col[i] == c) + neighbours++; + if (!neighbours) + continue; + + /* + * Now we know this square is adjacent to region c. The + * next question is, would extending it cause the region to + * become non-simply-connected? If so, we mustn't do it. + * + * We determine this by looking around col to see if we can + * find more than one separate run of colour c. + */ + runs = 0; + for (i = 0; i < 8; i++) + if (col[i] == c && col[(i+1) & 7] != c) + runs++; + if (runs > 1) + continue; + + assert(runs == 1); + + /* + * This square is a possibility. Determine its effect on + * the region's perimeter (computed from the number of + * orthogonal neighbours - 1 means a perimeter increase, 3 + * a decrease, 2 no change; 4 is impossible because the + * region would already not be simply connected) and we're + * done. + */ + assert(neighbours > 0 && neighbours < 4); + count = (neighbours == 1 ? WEIGHT_INCREASED : + neighbours == 2 ? WEIGHT_UNCHANGED : WEIGHT_DECREASED); + + total += count; + if (index >= 0 && index < count) + return c; + else + index -= count; + } + + assert(index < 0); + + return total; +} + +static void genmap(int w, int h, int n, int *map, random_state *rs) +{ + int wh = w*h; + int x, y, i, k; + int *tmp; + + assert(n <= wh); + tmp = snewn(wh, int); + + /* + * Clear the map, and set up `tmp' as a list of grid indices. + */ + for (i = 0; i < wh; i++) { + map[i] = -1; + tmp[i] = i; + } + + /* + * Place the region seeds by selecting n members from `tmp'. + */ + k = wh; + for (i = 0; i < n; i++) { + int j = random_upto(rs, k); + map[tmp[j]] = i; + tmp[j] = tmp[--k]; + } + + /* + * Re-initialise `tmp' as a cumulative frequency table. This + * will store the number of possible region colours we can + * extend into each square. + */ + cf_init(tmp, wh); + + /* + * Go through the grid and set up the initial cumulative + * frequencies. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + cf_add(tmp, wh, y*w+x, + extend_options(w, h, n, map, x, y, -1)); + + /* + * Now repeatedly choose a square we can extend a region into, + * and do so. + */ + while (tmp[0] > 0) { + int k = random_upto(rs, tmp[0]); + int sq; + int colour; + int xx, yy; + + sq = cf_whichsym(tmp, wh, k); + k -= cf_clookup(tmp, wh, sq); + x = sq % w; + y = sq / w; + colour = extend_options(w, h, n, map, x, y, k); + + map[sq] = colour; + + /* + * Re-scan the nine cells around the one we've just + * modified. + */ + for (yy = max(y-1, 0); yy < min(y+2, h); yy++) + for (xx = max(x-1, 0); xx < min(x+2, w); xx++) { + cf_add(tmp, wh, yy*w+xx, + -cf_slookup(tmp, wh, yy*w+xx) + + extend_options(w, h, n, map, xx, yy, -1)); + } + } + + /* + * Finally, go through and normalise the region labels into + * order, meaning that indistinguishable maps are actually + * identical. + */ + for (i = 0; i < n; i++) + tmp[i] = -1; + k = 0; + for (i = 0; i < wh; i++) { + assert(map[i] >= 0); + if (tmp[map[i]] < 0) + tmp[map[i]] = k++; + map[i] = tmp[map[i]]; + } + + sfree(tmp); +} + +/* ---------------------------------------------------------------------- + * Functions to handle graphs. + */ + +/* + * Having got a map in a square grid, convert it into a graph + * representation. + */ +static int gengraph(int w, int h, int n, int *map, int *graph) +{ + int i, j, x, y; + + /* + * Start by setting the graph up as an adjacency matrix. We'll + * turn it into a list later. + */ + for (i = 0; i < n*n; i++) + graph[i] = 0; + + /* + * Iterate over the map looking for all adjacencies. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int v, vx, vy; + v = map[y*w+x]; + if (x+1 < w && (vx = map[y*w+(x+1)]) != v) + graph[v*n+vx] = graph[vx*n+v] = 1; + if (y+1 < h && (vy = map[(y+1)*w+x]) != v) + graph[v*n+vy] = graph[vy*n+v] = 1; + } + + /* + * Turn the matrix into a list. + */ + for (i = j = 0; i < n*n; i++) + if (graph[i]) + graph[j++] = i; + + return j; +} + +static int graph_edge_index(int *graph, int n, int ngraph, int i, int j) +{ + int v = i*n+j; + int top, bot, mid; + + bot = -1; + top = ngraph; + while (top - bot > 1) { + mid = (top + bot) / 2; + if (graph[mid] == v) + return mid; + else if (graph[mid] < v) + bot = mid; + else + top = mid; + } + return -1; +} + +#define graph_adjacent(graph, n, ngraph, i, j) \ + (graph_edge_index((graph), (n), (ngraph), (i), (j)) >= 0) + +static int graph_vertex_start(int *graph, int n, int ngraph, int i) +{ + int v = i*n; + int top, bot, mid; + + bot = -1; + top = ngraph; + while (top - bot > 1) { + mid = (top + bot) / 2; + if (graph[mid] < v) + bot = mid; + else + top = mid; + } + return top; +} + +/* ---------------------------------------------------------------------- + * Generate a four-colouring of a graph. + * + * FIXME: it would be nice if we could convert this recursion into + * pseudo-recursion using some sort of explicit stack array, for + * the sake of the Palm port and its limited stack. + */ + +static int fourcolour_recurse(int *graph, int n, int ngraph, + int *colouring, int *scratch, random_state *rs) +{ + int nfree, nvert, start, i, j, k, c, ci; + int cs[FOUR]; + + /* + * Find the smallest number of free colours in any uncoloured + * vertex, and count the number of such vertices. + */ + + nfree = FIVE; /* start off bigger than FOUR! */ + nvert = 0; + for (i = 0; i < n; i++) + if (colouring[i] < 0 && scratch[i*FIVE+FOUR] <= nfree) { + if (nfree > scratch[i*FIVE+FOUR]) { + nfree = scratch[i*FIVE+FOUR]; + nvert = 0; + } + nvert++; + } + + /* + * If there aren't any uncoloured vertices at all, we're done. + */ + if (nvert == 0) + return TRUE; /* we've got a colouring! */ + + /* + * Pick a random vertex in that set. + */ + j = random_upto(rs, nvert); + for (i = 0; i < n; i++) + if (colouring[i] < 0 && scratch[i*FIVE+FOUR] == nfree) + if (j-- == 0) + break; + assert(i < n); + start = graph_vertex_start(graph, n, ngraph, i); + + /* + * Loop over the possible colours for i, and recurse for each + * one. + */ + ci = 0; + for (c = 0; c < FOUR; c++) + if (scratch[i*FIVE+c] == 0) + cs[ci++] = c; + shuffle(cs, ci, sizeof(*cs), rs); + + while (ci-- > 0) { + c = cs[ci]; + + /* + * Fill in this colour. + */ + colouring[i] = c; + + /* + * Update the scratch space to reflect a new neighbour + * of this colour for each neighbour of vertex i. + */ + for (j = start; j < ngraph && graph[j] < n*(i+1); j++) { + k = graph[j] - i*n; + if (scratch[k*FIVE+c] == 0) + scratch[k*FIVE+FOUR]--; + scratch[k*FIVE+c]++; + } + + /* + * Recurse. + */ + if (fourcolour_recurse(graph, n, ngraph, colouring, scratch, rs)) + return TRUE; /* got one! */ + + /* + * If that didn't work, clean up and try again with a + * different colour. + */ + for (j = start; j < ngraph && graph[j] < n*(i+1); j++) { + k = graph[j] - i*n; + scratch[k*FIVE+c]--; + if (scratch[k*FIVE+c] == 0) + scratch[k*FIVE+FOUR]++; + } + colouring[i] = -1; + } + + /* + * If we reach here, we were unable to find a colouring at all. + * (This doesn't necessarily mean the Four Colour Theorem is + * violated; it might just mean we've gone down a dead end and + * need to back up and look somewhere else. It's only an FCT + * violation if we get all the way back up to the top level and + * still fail.) + */ + return FALSE; +} + +static void fourcolour(int *graph, int n, int ngraph, int *colouring, + random_state *rs) +{ + int *scratch; + int i; + + /* + * For each vertex and each colour, we store the number of + * neighbours that have that colour. Also, we store the number + * of free colours for the vertex. + */ + scratch = snewn(n * FIVE, int); + for (i = 0; i < n * FIVE; i++) + scratch[i] = (i % FIVE == FOUR ? FOUR : 0); + + /* + * Clear the colouring to start with. + */ + for (i = 0; i < n; i++) + colouring[i] = -1; + + i = fourcolour_recurse(graph, n, ngraph, colouring, scratch, rs); + assert(i); /* by the Four Colour Theorem :-) */ + + sfree(scratch); +} + +/* ---------------------------------------------------------------------- + * Non-recursive solver. + */ + +struct solver_scratch { + unsigned char *possible; /* bitmap of colours for each region */ + + int *graph; + int n; + int ngraph; + + int *bfsqueue; + int *bfscolour; +#ifdef SOLVER_DIAGNOSTICS + int *bfsprev; +#endif + + int depth; +}; + +static struct solver_scratch *new_scratch(int *graph, int n, int ngraph) +{ + struct solver_scratch *sc; + + sc = snew(struct solver_scratch); + sc->graph = graph; + sc->n = n; + sc->ngraph = ngraph; + sc->possible = snewn(n, unsigned char); + sc->depth = 0; + sc->bfsqueue = snewn(n, int); + sc->bfscolour = snewn(n, int); +#ifdef SOLVER_DIAGNOSTICS + sc->bfsprev = snewn(n, int); +#endif + + return sc; +} + +static void free_scratch(struct solver_scratch *sc) +{ + sfree(sc->possible); + sfree(sc->bfsqueue); + sfree(sc->bfscolour); +#ifdef SOLVER_DIAGNOSTICS + sfree(sc->bfsprev); +#endif + sfree(sc); +} + +/* + * Count the bits in a word. Only needs to cope with FOUR bits. + */ +static int bitcount(int word) +{ + assert(FOUR <= 4); /* or this needs changing */ + word = ((word & 0xA) >> 1) + (word & 0x5); + word = ((word & 0xC) >> 2) + (word & 0x3); + return word; +} + +#ifdef SOLVER_DIAGNOSTICS +static const char colnames[FOUR] = { 'R', 'Y', 'G', 'B' }; +#endif + +static int place_colour(struct solver_scratch *sc, + int *colouring, int index, int colour +#ifdef SOLVER_DIAGNOSTICS + , char *verb +#endif + ) +{ + int *graph = sc->graph, n = sc->n, ngraph = sc->ngraph; + int j, k; + + if (!(sc->possible[index] & (1 << colour))) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*scannot place %c in region %d\n", 2*sc->depth, "", + colnames[colour], index); +#endif + return FALSE; /* can't do it */ + } + + sc->possible[index] = 1 << colour; + colouring[index] = colour; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*s%s %c in region %d\n", 2*sc->depth, "", + verb, colnames[colour], index); +#endif + + /* + * Rule out this colour from all the region's neighbours. + */ + for (j = graph_vertex_start(graph, n, ngraph, index); + j < ngraph && graph[j] < n*(index+1); j++) { + k = graph[j] - index*n; +#ifdef SOLVER_DIAGNOSTICS + if (verbose && (sc->possible[k] & (1 << colour))) + printf("%*s ruling out %c in region %d\n", 2*sc->depth, "", + colnames[colour], k); +#endif + sc->possible[k] &= ~(1 << colour); + } + + return TRUE; +} + +#ifdef SOLVER_DIAGNOSTICS +static char *colourset(char *buf, int set) +{ + int i; + char *p = buf; + char *sep = ""; + + for (i = 0; i < FOUR; i++) + if (set & (1 << i)) { + p += sprintf(p, "%s%c", sep, colnames[i]); + sep = ","; + } + + return buf; +} +#endif + +/* + * Returns 0 for impossible, 1 for success, 2 for failure to + * converge (i.e. puzzle is either ambiguous or just too + * difficult). + */ +static int map_solver(struct solver_scratch *sc, + int *graph, int n, int ngraph, int *colouring, + int difficulty) +{ + int i; + + if (sc->depth == 0) { + /* + * Initialise scratch space. + */ + for (i = 0; i < n; i++) + sc->possible[i] = (1 << FOUR) - 1; + + /* + * Place clues. + */ + for (i = 0; i < n; i++) + if (colouring[i] >= 0) { + if (!place_colour(sc, colouring, i, colouring[i] +#ifdef SOLVER_DIAGNOSTICS + , "initial clue:" +#endif + )) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*sinitial clue set is inconsistent\n", + 2*sc->depth, ""); +#endif + return 0; /* the clues aren't even consistent! */ + } + } + } + + /* + * Now repeatedly loop until we find nothing further to do. + */ + while (1) { + int done_something = FALSE; + + if (difficulty < DIFF_EASY) + break; /* can't do anything at all! */ + + /* + * Simplest possible deduction: find a region with only one + * possible colour. + */ + for (i = 0; i < n; i++) if (colouring[i] < 0) { + int p = sc->possible[i]; + + if (p == 0) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*sregion %d has no possible colours left\n", + 2*sc->depth, "", i); +#endif + return 0; /* puzzle is inconsistent */ + } + + if ((p & (p-1)) == 0) { /* p is a power of two */ + int c, ret; + for (c = 0; c < FOUR; c++) + if (p == (1 << c)) + break; + assert(c < FOUR); + ret = place_colour(sc, colouring, i, c +#ifdef SOLVER_DIAGNOSTICS + , "placing" +#endif + ); + /* + * place_colour() can only fail if colour c was not + * even a _possibility_ for region i, and we're + * pretty sure it was because we checked before + * calling place_colour(). So we can safely assert + * here rather than having to return a nice + * friendly error code. + */ + assert(ret); + done_something = TRUE; + } + } + + if (done_something) + continue; + + if (difficulty < DIFF_NORMAL) + break; /* can't do anything harder */ + + /* + * Failing that, go up one level. Look for pairs of regions + * which (a) both have the same pair of possible colours, + * (b) are adjacent to one another, (c) are adjacent to the + * same region, and (d) that region still thinks it has one + * or both of those possible colours. + * + * Simplest way to do this is by going through the graph + * edge by edge, so that we start with property (b) and + * then look for (a) and finally (c) and (d). + */ + for (i = 0; i < ngraph; i++) { + int j1 = graph[i] / n, j2 = graph[i] % n; + int j, k, v, v2; +#ifdef SOLVER_DIAGNOSTICS + int started = FALSE; +#endif + + if (j1 > j2) + continue; /* done it already, other way round */ + + if (colouring[j1] >= 0 || colouring[j2] >= 0) + continue; /* they're not undecided */ + + if (sc->possible[j1] != sc->possible[j2]) + continue; /* they don't have the same possibles */ + + v = sc->possible[j1]; + /* + * See if v contains exactly two set bits. + */ + v2 = v & -v; /* find lowest set bit */ + v2 = v & ~v2; /* clear it */ + if (v2 == 0 || (v2 & (v2-1)) != 0) /* not power of 2 */ + continue; + + /* + * We've found regions j1 and j2 satisfying properties + * (a) and (b): they have two possible colours between + * them, and since they're adjacent to one another they + * must use _both_ those colours between them. + * Therefore, if they are both adjacent to any other + * region then that region cannot be either colour. + * + * Go through the neighbours of j1 and see if any are + * shared with j2. + */ + for (j = graph_vertex_start(graph, n, ngraph, j1); + j < ngraph && graph[j] < n*(j1+1); j++) { + k = graph[j] - j1*n; + if (graph_adjacent(graph, n, ngraph, k, j2) && + (sc->possible[k] & v)) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + char buf[80]; + if (!started) + printf("%*sadjacent regions %d,%d share colours" + " %s\n", 2*sc->depth, "", j1, j2, + colourset(buf, v)); + started = TRUE; + printf("%*s ruling out %s in region %d\n",2*sc->depth, + "", colourset(buf, sc->possible[k] & v), k); + } +#endif + sc->possible[k] &= ~v; + done_something = TRUE; + } + } + } + + if (done_something) + continue; + + if (difficulty < DIFF_HARD) + break; /* can't do anything harder */ + + /* + * Right; now we get creative. Now we're going to look for + * `forcing chains'. A forcing chain is a path through the + * graph with the following properties: + * + * (a) Each vertex on the path has precisely two possible + * colours. + * + * (b) Each pair of vertices which are adjacent on the + * path share at least one possible colour in common. + * + * (c) Each vertex in the middle of the path shares _both_ + * of its colours with at least one of its neighbours + * (not the same one with both neighbours). + * + * These together imply that at least one of the possible + * colour choices at one end of the path forces _all_ the + * rest of the colours along the path. In order to make + * real use of this, we need further properties: + * + * (c) Ruling out some colour C from the vertex at one end + * of the path forces the vertex at the other end to + * take colour C. + * + * (d) The two end vertices are mutually adjacent to some + * third vertex. + * + * (e) That third vertex currently has C as a possibility. + * + * If we can find all of that lot, we can deduce that at + * least one of the two ends of the forcing chain has + * colour C, and that therefore the mutually adjacent third + * vertex does not. + * + * To find forcing chains, we're going to start a bfs at + * each suitable vertex of the graph, once for each of its + * two possible colours. + */ + for (i = 0; i < n; i++) { + int c; + + if (colouring[i] >= 0 || bitcount(sc->possible[i]) != 2) + continue; + + for (c = 0; c < FOUR; c++) + if (sc->possible[i] & (1 << c)) { + int j, k, gi, origc, currc, head, tail; + /* + * Try a bfs from this vertex, ruling out + * colour c. + * + * Within this loop, we work in colour bitmaps + * rather than actual colours, because + * converting back and forth is a needless + * computational expense. + */ + + origc = 1 << c; + + for (j = 0; j < n; j++) { + sc->bfscolour[j] = -1; +#ifdef SOLVER_DIAGNOSTICS + sc->bfsprev[j] = -1; +#endif + } + head = tail = 0; + sc->bfsqueue[tail++] = i; + sc->bfscolour[i] = sc->possible[i] &~ origc; + + while (head < tail) { + j = sc->bfsqueue[head++]; + currc = sc->bfscolour[j]; + + /* + * Try neighbours of j. + */ + for (gi = graph_vertex_start(graph, n, ngraph, j); + gi < ngraph && graph[gi] < n*(j+1); gi++) { + k = graph[gi] - j*n; + + /* + * To continue with the bfs in vertex + * k, we need k to be + * (a) not already visited + * (b) have two possible colours + * (c) those colours include currc. + */ + + if (sc->bfscolour[k] < 0 && + colouring[k] < 0 && + bitcount(sc->possible[k]) == 2 && + (sc->possible[k] & currc)) { + sc->bfsqueue[tail++] = k; + sc->bfscolour[k] = + sc->possible[k] &~ currc; +#ifdef SOLVER_DIAGNOSTICS + sc->bfsprev[k] = j; +#endif + } + + /* + * One other possibility is that k + * might be the region in which we can + * make a real deduction: if it's + * adjacent to i, contains currc as a + * possibility, and currc is equal to + * the original colour we ruled out. + */ + if (currc == origc && + graph_adjacent(graph, n, ngraph, k, i) && + (sc->possible[k] & currc)) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + char buf[80], *sep = ""; + int r; + + printf("%*sforcing chain, colour %s, ", + 2*sc->depth, "", + colourset(buf, origc)); + for (r = j; r != -1; r = sc->bfsprev[r]) { + printf("%s%d", sep, r); + sep = "-"; + } + printf("\n%*s ruling out %s in region" + " %d\n", 2*sc->depth, "", + colourset(buf, origc), k); + } +#endif + sc->possible[k] &= ~origc; + done_something = TRUE; + } + } + } + + assert(tail <= n); + } + } + + if (!done_something) + break; + } + + /* + * See if we've got a complete solution, and return if so. + */ + for (i = 0; i < n; i++) + if (colouring[i] < 0) + break; + if (i == n) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*sone solution found\n", 2*sc->depth, ""); +#endif + return 1; /* success! */ + } + + /* + * If recursion is not permissible, we now give up. + */ + if (difficulty < DIFF_RECURSE) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*sunable to proceed further without recursion\n", + 2*sc->depth, ""); +#endif + return 2; /* unable to complete */ + } + + /* + * Now we've got to do something recursive. So first hunt for a + * currently-most-constrained region. + */ + { + int best, bestc; + struct solver_scratch *rsc; + int *subcolouring, *origcolouring; + int ret, subret; + int we_already_got_one; + + best = -1; + bestc = FIVE; + + for (i = 0; i < n; i++) if (colouring[i] < 0) { + int p = sc->possible[i]; + enum { compile_time_assertion = 1 / (FOUR <= 4) }; + int c; + + /* Count the set bits. */ + c = (p & 5) + ((p >> 1) & 5); + c = (c & 3) + ((c >> 2) & 3); + assert(c > 1); /* or colouring[i] would be >= 0 */ + + if (c < bestc) { + best = i; + bestc = c; + } + } + + assert(best >= 0); /* or we'd be solved already */ + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%*srecursing on region %d\n", 2*sc->depth, "", best); +#endif + + /* + * Now iterate over the possible colours for this region. + */ + rsc = new_scratch(graph, n, ngraph); + rsc->depth = sc->depth + 1; + origcolouring = snewn(n, int); + memcpy(origcolouring, colouring, n * sizeof(int)); + subcolouring = snewn(n, int); + we_already_got_one = FALSE; + ret = 0; + + for (i = 0; i < FOUR; i++) { + if (!(sc->possible[best] & (1 << i))) + continue; + + memcpy(rsc->possible, sc->possible, n); + memcpy(subcolouring, origcolouring, n * sizeof(int)); + + place_colour(rsc, subcolouring, best, i +#ifdef SOLVER_DIAGNOSTICS + , "trying" +#endif + ); + + subret = map_solver(rsc, graph, n, ngraph, + subcolouring, difficulty); + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + printf("%*sretracting %c in region %d; found %s\n", + 2*sc->depth, "", colnames[i], best, + subret == 0 ? "no solutions" : + subret == 1 ? "one solution" : "multiple solutions"); + } +#endif + + /* + * If this possibility turned up more than one valid + * solution, or if it turned up one and we already had + * one, we're definitely ambiguous. + */ + if (subret == 2 || (subret == 1 && we_already_got_one)) { + ret = 2; + break; + } + + /* + * If this possibility turned up one valid solution and + * it's the first we've seen, copy it into the output. + */ + if (subret == 1) { + memcpy(colouring, subcolouring, n * sizeof(int)); + we_already_got_one = TRUE; + ret = 1; + } + + /* + * Otherwise, this guess led to a contradiction, so we + * do nothing. + */ + } + + sfree(origcolouring); + sfree(subcolouring); + free_scratch(rsc); + +#ifdef SOLVER_DIAGNOSTICS + if (verbose && sc->depth == 0) { + printf("%*s%s found\n", + 2*sc->depth, "", + ret == 0 ? "no solutions" : + ret == 1 ? "one solution" : "multiple solutions"); + } +#endif + return ret; + } +} + +/* ---------------------------------------------------------------------- + * Game generation main function. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + struct solver_scratch *sc = NULL; + int *map, *graph, ngraph, *colouring, *colouring2, *regions; + int i, j, w, h, n, solveret, cfreq[FOUR]; + int wh; + int mindiff, tries; +#ifdef GENERATION_DIAGNOSTICS + int x, y; +#endif + char *ret, buf[80]; + int retlen, retsize; + + w = params->w; + h = params->h; + n = params->n; + wh = w*h; + + *aux = NULL; + + map = snewn(wh, int); + graph = snewn(n*n, int); + colouring = snewn(n, int); + colouring2 = snewn(n, int); + regions = snewn(n, int); + + /* + * This is the minimum difficulty below which we'll completely + * reject a map design. Normally we set this to one below the + * requested difficulty, ensuring that we have the right + * result. However, for particularly dense maps or maps with + * particularly few regions it might not be possible to get the + * desired difficulty, so we will eventually drop this down to + * -1 to indicate that any old map will do. + */ + mindiff = params->diff; + tries = 50; + + while (1) { + + /* + * Create the map. + */ + genmap(w, h, n, map, rs); + +#ifdef GENERATION_DIAGNOSTICS + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = map[y*w+x]; + if (v >= 62) + putchar('!'); + else if (v >= 36) + putchar('a' + v-36); + else if (v >= 10) + putchar('A' + v-10); + else + putchar('0' + v); + } + putchar('\n'); + } +#endif + + /* + * Convert the map into a graph. + */ + ngraph = gengraph(w, h, n, map, graph); + +#ifdef GENERATION_DIAGNOSTICS + for (i = 0; i < ngraph; i++) + printf("%d-%d\n", graph[i]/n, graph[i]%n); +#endif + + /* + * Colour the map. + */ + fourcolour(graph, n, ngraph, colouring, rs); + +#ifdef GENERATION_DIAGNOSTICS + for (i = 0; i < n; i++) + printf("%d: %d\n", i, colouring[i]); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = colouring[map[y*w+x]]; + if (v >= 36) + putchar('a' + v-36); + else if (v >= 10) + putchar('A' + v-10); + else + putchar('0' + v); + } + putchar('\n'); + } +#endif + + /* + * Encode the solution as an aux string. + */ + if (*aux) /* in case we've come round again */ + sfree(*aux); + retlen = retsize = 0; + ret = NULL; + for (i = 0; i < n; i++) { + int len; + + if (colouring[i] < 0) + continue; + + len = sprintf(buf, "%s%d:%d", i ? ";" : "S;", colouring[i], i); + if (retlen + len >= retsize) { + retsize = retlen + len + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += len; + } + *aux = ret; + + /* + * Remove the region colours one by one, keeping + * solubility. Also ensure that there always remains at + * least one region of every colour, so that the user can + * drag from somewhere. + */ + for (i = 0; i < FOUR; i++) + cfreq[i] = 0; + for (i = 0; i < n; i++) { + regions[i] = i; + cfreq[colouring[i]]++; + } + for (i = 0; i < FOUR; i++) + if (cfreq[i] == 0) + continue; + + shuffle(regions, n, sizeof(*regions), rs); + + if (sc) free_scratch(sc); + sc = new_scratch(graph, n, ngraph); + + for (i = 0; i < n; i++) { + j = regions[i]; + + if (cfreq[colouring[j]] == 1) + continue; /* can't remove last region of colour */ + + memcpy(colouring2, colouring, n*sizeof(int)); + colouring2[j] = -1; + solveret = map_solver(sc, graph, n, ngraph, colouring2, + params->diff); + assert(solveret >= 0); /* mustn't be impossible! */ + if (solveret == 1) { + cfreq[colouring[j]]--; + colouring[j] = -1; + } + } + +#ifdef GENERATION_DIAGNOSTICS + for (i = 0; i < n; i++) + if (colouring[i] >= 0) { + if (i >= 62) + putchar('!'); + else if (i >= 36) + putchar('a' + i-36); + else if (i >= 10) + putchar('A' + i-10); + else + putchar('0' + i); + printf(": %d\n", colouring[i]); + } +#endif + + /* + * Finally, check that the puzzle is _at least_ as hard as + * required, and indeed that it isn't already solved. + * (Calling map_solver with negative difficulty ensures the + * latter - if a solver which _does nothing_ can solve it, + * it's too easy!) + */ + memcpy(colouring2, colouring, n*sizeof(int)); + if (map_solver(sc, graph, n, ngraph, colouring2, + mindiff - 1) == 1) { + /* + * Drop minimum difficulty if necessary. + */ + if (mindiff > 0 && (n < 9 || n > 2*wh/3)) { + if (tries-- <= 0) + mindiff = 0; /* give up and go for Easy */ + } + continue; + } + + break; + } + + /* + * Encode as a game ID. We do this by: + * + * - first going along the horizontal edges row by row, and + * then the vertical edges column by column + * - encoding the lengths of runs of edges and runs of + * non-edges + * - the decoder will reconstitute the region boundaries from + * this and automatically number them the same way we did + * - then we encode the initial region colours in a Slant-like + * fashion (digits 0-3 interspersed with letters giving + * lengths of runs of empty spaces). + */ + retlen = retsize = 0; + ret = NULL; + + { + int run, pv; + + /* + * Start with a notional non-edge, so that there'll be an + * explicit `a' to distinguish the case where we start with + * an edge. + */ + run = 1; + pv = 0; + + for (i = 0; i < w*(h-1) + (w-1)*h; i++) { + int x, y, dx, dy, v; + + if (i < w*(h-1)) { + /* Horizontal edge. */ + y = i / w; + x = i % w; + dx = 0; + dy = 1; + } else { + /* Vertical edge. */ + x = (i - w*(h-1)) / h; + y = (i - w*(h-1)) % h; + dx = 1; + dy = 0; + } + + if (retlen + 10 >= retsize) { + retsize = retlen + 256; + ret = sresize(ret, retsize, char); + } + + v = (map[y*w+x] != map[(y+dy)*w+(x+dx)]); + + if (pv != v) { + ret[retlen++] = 'a'-1 + run; + run = 1; + pv = v; + } else { + /* + * 'z' is a special case in this encoding. Rather + * than meaning a run of 26 and a state switch, it + * means a run of 25 and _no_ state switch, because + * otherwise there'd be no way to encode runs of + * more than 26. + */ + if (run == 25) { + ret[retlen++] = 'z'; + run = 0; + } + run++; + } + } + + ret[retlen++] = 'a'-1 + run; + ret[retlen++] = ','; + + run = 0; + for (i = 0; i < n; i++) { + if (retlen + 10 >= retsize) { + retsize = retlen + 256; + ret = sresize(ret, retsize, char); + } + + if (colouring[i] < 0) { + /* + * In _this_ encoding, 'z' is a run of 26, since + * there's no implicit state switch after each run. + * Confusingly different, but more compact. + */ + if (run == 26) { + ret[retlen++] = 'z'; + run = 0; + } + run++; + } else { + if (run > 0) + ret[retlen++] = 'a'-1 + run; + ret[retlen++] = '0' + colouring[i]; + run = 0; + } + } + if (run > 0) + ret[retlen++] = 'a'-1 + run; + ret[retlen] = '\0'; + + assert(retlen < retsize); + } + + free_scratch(sc); + sfree(regions); + sfree(colouring2); + sfree(colouring); + sfree(graph); + sfree(map); + + return ret; +} + +static char *parse_edge_list(const game_params *params, const char **desc, + int *map) +{ + int w = params->w, h = params->h, wh = w*h, n = params->n; + int i, k, pos, state; + const char *p = *desc; + + dsf_init(map+wh, wh); + + pos = -1; + state = 0; + + /* + * Parse the game description to get the list of edges, and + * build up a disjoint set forest as we go (by identifying + * pairs of squares whenever the edge list shows a non-edge). + */ + while (*p && *p != ',') { + if (*p < 'a' || *p > 'z') + return "Unexpected character in edge list"; + if (*p == 'z') + k = 25; + else + k = *p - 'a' + 1; + while (k-- > 0) { + int x, y, dx, dy; + + if (pos < 0) { + pos++; + continue; + } else if (pos < w*(h-1)) { + /* Horizontal edge. */ + y = pos / w; + x = pos % w; + dx = 0; + dy = 1; + } else if (pos < 2*wh-w-h) { + /* Vertical edge. */ + x = (pos - w*(h-1)) / h; + y = (pos - w*(h-1)) % h; + dx = 1; + dy = 0; + } else + return "Too much data in edge list"; + if (!state) + dsf_merge(map+wh, y*w+x, (y+dy)*w+(x+dx)); + + pos++; + } + if (*p != 'z') + state = !state; + p++; + } + assert(pos <= 2*wh-w-h); + if (pos < 2*wh-w-h) + return "Too little data in edge list"; + + /* + * Now go through again and allocate region numbers. + */ + pos = 0; + for (i = 0; i < wh; i++) + map[i] = -1; + for (i = 0; i < wh; i++) { + k = dsf_canonify(map+wh, i); + if (map[k] < 0) + map[k] = pos++; + map[i] = map[k]; + } + if (pos != n) + return "Edge list defines the wrong number of regions"; + + *desc = p; + + return NULL; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h, wh = w*h, n = params->n; + int area; + int *map; + char *ret; + + map = snewn(2*wh, int); + ret = parse_edge_list(params, &desc, map); + sfree(map); + if (ret) + return ret; + + if (*desc != ',') + return "Expected comma before clue list"; + desc++; /* eat comma */ + + area = 0; + while (*desc) { + if (*desc >= '0' && *desc < '0'+FOUR) + area++; + else if (*desc >= 'a' && *desc <= 'z') + area += *desc - 'a' + 1; + else + return "Unexpected character in clue list"; + desc++; + } + if (area < n) + return "Too little data in clue list"; + else if (area > n) + return "Too much data in clue list"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h, wh = w*h, n = params->n; + int i, pos; + const char *p; + game_state *state = snew(game_state); + + state->p = *params; + state->colouring = snewn(n, int); + for (i = 0; i < n; i++) + state->colouring[i] = -1; + state->pencil = snewn(n, int); + for (i = 0; i < n; i++) + state->pencil[i] = 0; + + state->completed = state->cheated = FALSE; + + state->map = snew(struct map); + state->map->refcount = 1; + state->map->map = snewn(wh*4, int); + state->map->graph = snewn(n*n, int); + state->map->n = n; + state->map->immutable = snewn(n, int); + for (i = 0; i < n; i++) + state->map->immutable[i] = FALSE; + + p = desc; + + { + char *ret; + ret = parse_edge_list(params, &p, state->map->map); + assert(!ret); + } + + /* + * Set up the other three quadrants in `map'. + */ + for (i = wh; i < 4*wh; i++) + state->map->map[i] = state->map->map[i % wh]; + + assert(*p == ','); + p++; + + /* + * Now process the clue list. + */ + pos = 0; + while (*p) { + if (*p >= '0' && *p < '0'+FOUR) { + state->colouring[pos] = *p - '0'; + state->map->immutable[pos] = TRUE; + pos++; + } else { + assert(*p >= 'a' && *p <= 'z'); + pos += *p - 'a' + 1; + } + p++; + } + assert(pos == n); + + state->map->ngraph = gengraph(w, h, n, state->map->map, state->map->graph); + + /* + * Attempt to smooth out some of the more jagged region + * outlines by the judicious use of diagonally divided squares. + */ + { + random_state *rs = random_new(desc, strlen(desc)); + int *squares = snewn(wh, int); + int done_something; + + for (i = 0; i < wh; i++) + squares[i] = i; + shuffle(squares, wh, sizeof(*squares), rs); + + do { + done_something = FALSE; + for (i = 0; i < wh; i++) { + int y = squares[i] / w, x = squares[i] % w; + int c = state->map->map[y*w+x]; + int tc, bc, lc, rc; + + if (x == 0 || x == w-1 || y == 0 || y == h-1) + continue; + + if (state->map->map[TE * wh + y*w+x] != + state->map->map[BE * wh + y*w+x]) + continue; + + tc = state->map->map[BE * wh + (y-1)*w+x]; + bc = state->map->map[TE * wh + (y+1)*w+x]; + lc = state->map->map[RE * wh + y*w+(x-1)]; + rc = state->map->map[LE * wh + y*w+(x+1)]; + + /* + * If this square is adjacent on two sides to one + * region and on the other two sides to the other + * region, and is itself one of the two regions, we can + * adjust it so that it's a diagonal. + */ + if (tc != bc && (tc == c || bc == c)) { + if ((lc == tc && rc == bc) || + (lc == bc && rc == tc)) { + state->map->map[TE * wh + y*w+x] = tc; + state->map->map[BE * wh + y*w+x] = bc; + state->map->map[LE * wh + y*w+x] = lc; + state->map->map[RE * wh + y*w+x] = rc; + done_something = TRUE; + } + } + } + } while (done_something); + sfree(squares); + random_free(rs); + } + + /* + * Analyse the map to find a canonical line segment + * corresponding to each edge, and a canonical point + * corresponding to each region. The former are where we'll + * eventually put error markers; the latter are where we'll put + * per-region flags such as numbers (when in diagnostic mode). + */ + { + int *bestx, *besty, *an, pass; + float *ax, *ay, *best; + + ax = snewn(state->map->ngraph + n, float); + ay = snewn(state->map->ngraph + n, float); + an = snewn(state->map->ngraph + n, int); + bestx = snewn(state->map->ngraph + n, int); + besty = snewn(state->map->ngraph + n, int); + best = snewn(state->map->ngraph + n, float); + + for (i = 0; i < state->map->ngraph + n; i++) { + bestx[i] = besty[i] = -1; + best[i] = (float)(2*(w+h)+1); + ax[i] = ay[i] = 0.0F; + an[i] = 0; + } + + /* + * We make two passes over the map, finding all the line + * segments separating regions and all the suitable points + * within regions. In the first pass, we compute the + * _average_ x and y coordinate of all the points in a + * given class; in the second pass, for each such average + * point, we find the candidate closest to it and call that + * canonical. + * + * Line segments are considered to have coordinates in + * their centre. Thus, at least one coordinate for any line + * segment is always something-and-a-half; so we store our + * coordinates as twice their normal value. + */ + for (pass = 0; pass < 2; pass++) { + int x, y; + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int ex[4], ey[4], ea[4], eb[4], en = 0; + + /* + * Look for an edge to the right of this + * square, an edge below it, and an edge in the + * middle of it. Also look to see if the point + * at the bottom right of this square is on an + * edge (and isn't a place where more than two + * regions meet). + */ + if (x+1 < w) { + /* right edge */ + ea[en] = state->map->map[RE * wh + y*w+x]; + eb[en] = state->map->map[LE * wh + y*w+(x+1)]; + ex[en] = (x+1)*2; + ey[en] = y*2+1; + en++; + } + if (y+1 < h) { + /* bottom edge */ + ea[en] = state->map->map[BE * wh + y*w+x]; + eb[en] = state->map->map[TE * wh + (y+1)*w+x]; + ex[en] = x*2+1; + ey[en] = (y+1)*2; + en++; + } + /* diagonal edge */ + ea[en] = state->map->map[TE * wh + y*w+x]; + eb[en] = state->map->map[BE * wh + y*w+x]; + ex[en] = x*2+1; + ey[en] = y*2+1; + en++; + + if (x+1 < w && y+1 < h) { + /* bottom right corner */ + int oct[8], othercol, nchanges; + oct[0] = state->map->map[RE * wh + y*w+x]; + oct[1] = state->map->map[LE * wh + y*w+(x+1)]; + oct[2] = state->map->map[BE * wh + y*w+(x+1)]; + oct[3] = state->map->map[TE * wh + (y+1)*w+(x+1)]; + oct[4] = state->map->map[LE * wh + (y+1)*w+(x+1)]; + oct[5] = state->map->map[RE * wh + (y+1)*w+x]; + oct[6] = state->map->map[TE * wh + (y+1)*w+x]; + oct[7] = state->map->map[BE * wh + y*w+x]; + + othercol = -1; + nchanges = 0; + for (i = 0; i < 8; i++) { + if (oct[i] != oct[0]) { + if (othercol < 0) + othercol = oct[i]; + else if (othercol != oct[i]) + break; /* three colours at this point */ + } + if (oct[i] != oct[(i+1) & 7]) + nchanges++; + } + + /* + * Now if there are exactly two regions at + * this point (not one, and not three or + * more), and only two changes around the + * loop, then this is a valid place to put + * an error marker. + */ + if (i == 8 && othercol >= 0 && nchanges == 2) { + ea[en] = oct[0]; + eb[en] = othercol; + ex[en] = (x+1)*2; + ey[en] = (y+1)*2; + en++; + } + + /* + * If there's exactly _one_ region at this + * point, on the other hand, it's a valid + * place to put a region centre. + */ + if (othercol < 0) { + ea[en] = eb[en] = oct[0]; + ex[en] = (x+1)*2; + ey[en] = (y+1)*2; + en++; + } + } + + /* + * Now process the points we've found, one by + * one. + */ + for (i = 0; i < en; i++) { + int emin = min(ea[i], eb[i]); + int emax = max(ea[i], eb[i]); + int gindex; + + if (emin != emax) { + /* Graph edge */ + gindex = + graph_edge_index(state->map->graph, n, + state->map->ngraph, emin, + emax); + } else { + /* Region number */ + gindex = state->map->ngraph + emin; + } + + assert(gindex >= 0); + + if (pass == 0) { + /* + * In pass 0, accumulate the values + * we'll use to compute the average + * positions. + */ + ax[gindex] += ex[i]; + ay[gindex] += ey[i]; + an[gindex] += 1; + } else { + /* + * In pass 1, work out whether this + * point is closer to the average than + * the last one we've seen. + */ + float dx, dy, d; + + assert(an[gindex] > 0); + dx = ex[i] - ax[gindex]; + dy = ey[i] - ay[gindex]; + d = (float)sqrt(dx*dx + dy*dy); + if (d < best[gindex]) { + best[gindex] = d; + bestx[gindex] = ex[i]; + besty[gindex] = ey[i]; + } + } + } + } + + if (pass == 0) { + for (i = 0; i < state->map->ngraph + n; i++) + if (an[i] > 0) { + ax[i] /= an[i]; + ay[i] /= an[i]; + } + } + } + + state->map->edgex = snewn(state->map->ngraph, int); + state->map->edgey = snewn(state->map->ngraph, int); + memcpy(state->map->edgex, bestx, state->map->ngraph * sizeof(int)); + memcpy(state->map->edgey, besty, state->map->ngraph * sizeof(int)); + + state->map->regionx = snewn(n, int); + state->map->regiony = snewn(n, int); + memcpy(state->map->regionx, bestx + state->map->ngraph, n*sizeof(int)); + memcpy(state->map->regiony, besty + state->map->ngraph, n*sizeof(int)); + + for (i = 0; i < state->map->ngraph; i++) + if (state->map->edgex[i] < 0) { + /* Find the other representation of this edge. */ + int e = state->map->graph[i]; + int iprime = graph_edge_index(state->map->graph, n, + state->map->ngraph, e%n, e/n); + assert(state->map->edgex[iprime] >= 0); + state->map->edgex[i] = state->map->edgex[iprime]; + state->map->edgey[i] = state->map->edgey[iprime]; + } + + sfree(ax); + sfree(ay); + sfree(an); + sfree(best); + sfree(bestx); + sfree(besty); + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->p = state->p; + ret->colouring = snewn(state->p.n, int); + memcpy(ret->colouring, state->colouring, state->p.n * sizeof(int)); + ret->pencil = snewn(state->p.n, int); + memcpy(ret->pencil, state->pencil, state->p.n * sizeof(int)); + ret->map = state->map; + ret->map->refcount++; + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->map->refcount <= 0) { + sfree(state->map->map); + sfree(state->map->graph); + sfree(state->map->immutable); + sfree(state->map->edgex); + sfree(state->map->edgey); + sfree(state->map->regionx); + sfree(state->map->regiony); + sfree(state->map); + } + sfree(state->pencil); + sfree(state->colouring); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + if (!aux) { + /* + * Use the solver. + */ + int *colouring; + struct solver_scratch *sc; + int sret; + int i; + char *ret, buf[80]; + int retlen, retsize; + + colouring = snewn(state->map->n, int); + memcpy(colouring, state->colouring, state->map->n * sizeof(int)); + + sc = new_scratch(state->map->graph, state->map->n, state->map->ngraph); + sret = map_solver(sc, state->map->graph, state->map->n, + state->map->ngraph, colouring, DIFFCOUNT-1); + free_scratch(sc); + + if (sret != 1) { + sfree(colouring); + if (sret == 0) + *error = "Puzzle is inconsistent"; + else + *error = "Unable to find a unique solution for this puzzle"; + return NULL; + } + + retsize = 64; + ret = snewn(retsize, char); + strcpy(ret, "S"); + retlen = 1; + + for (i = 0; i < state->map->n; i++) { + int len; + + assert(colouring[i] >= 0); + if (colouring[i] == currstate->colouring[i]) + continue; + assert(!state->map->immutable[i]); + + len = sprintf(buf, ";%d:%d", colouring[i], i); + if (retlen + len >= retsize) { + retsize = retlen + len + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += len; + } + + sfree(colouring); + + return ret; + } + return dupstr(aux); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +struct game_ui { + /* + * drag_colour: + * + * - -2 means no drag currently active. + * - >=0 means we're dragging a solid colour. + * - -1 means we're dragging a blank space, and drag_pencil + * might or might not add some pencil-mark stipples to that. + */ + int drag_colour; + int drag_pencil; + int dragx, dragy; + int show_numbers; + + int cur_x, cur_y, cur_visible, cur_moved, cur_lastmove; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->dragx = ui->dragy = -1; + ui->drag_colour = -2; + ui->drag_pencil = 0; + ui->show_numbers = FALSE; + ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_moved = 0; + ui->cur_lastmove = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int tilesize; + unsigned long *drawn, *todraw; + int started; + int dragx, dragy, drag_visible; + blitter *bl; +}; + +/* Flags in `drawn'. */ +#define ERR_BASE 0x00800000L +#define ERR_MASK 0xFF800000L +#define PENCIL_T_BASE 0x00080000L +#define PENCIL_T_MASK 0x00780000L +#define PENCIL_B_BASE 0x00008000L +#define PENCIL_B_MASK 0x00078000L +#define PENCIL_MASK 0x007F8000L +#define SHOW_NUMBERS 0x00004000L + +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE) +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) + + /* + * EPSILON_FOO are epsilons added to absolute cursor position by + * cursor movement, such that in pathological cases (e.g. a very + * small diamond-shaped area) it's relatively easy to select the + * region you wanted. + */ + +#define EPSILON_X(button) (((button) == CURSOR_RIGHT) ? +1 : \ + ((button) == CURSOR_LEFT) ? -1 : 0) +#define EPSILON_Y(button) (((button) == CURSOR_DOWN) ? +1 : \ + ((button) == CURSOR_UP) ? -1 : 0) + + +static int region_from_coords(const game_state *state, + const game_drawstate *ds, int x, int y) +{ + int w = state->p.w, h = state->p.h, wh = w*h /*, n = state->p.n */; + int tx = FROMCOORD(x), ty = FROMCOORD(y); + int dx = x - COORD(tx), dy = y - COORD(ty); + int quadrant; + + if (tx < 0 || tx >= w || ty < 0 || ty >= h) + return -1; /* border */ + + quadrant = 2 * (dx > dy) + (TILESIZE - dx > dy); + quadrant = (quadrant == 0 ? BE : + quadrant == 1 ? LE : + quadrant == 2 ? RE : TE); + + return state->map->map[quadrant * wh + ty*w+tx]; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + char *bufp, buf[256]; + int alt_button; + + /* + * Enable or disable numeric labels on regions. + */ + if (button == 'l' || button == 'L') { + ui->show_numbers = !ui->show_numbers; + return ""; + } + + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, state->p.w, state->p.h, 0); + ui->cur_visible = 1; + ui->cur_moved = 1; + ui->cur_lastmove = button; + ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(button); + ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(button); + return ""; + } + if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) { + ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove); + ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove); + ui->cur_visible = 1; + return ""; + } + if (ui->drag_colour == -2) { /* not currently cursor-dragging, start. */ + int r = region_from_coords(state, ds, ui->dragx, ui->dragy); + if (r >= 0) { + ui->drag_colour = state->colouring[r]; + ui->drag_pencil = (ui->drag_colour >= 0) ? 0 : state->pencil[r]; + } else { + ui->drag_colour = -1; + ui->drag_pencil = 0; + } + ui->cur_moved = 0; + return ""; + } else { /* currently cursor-dragging; drop the colour in the new region. */ + x = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove); + y = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove); + alt_button = (button == CURSOR_SELECT2) ? 1 : 0; + /* Double-select removes current colour. */ + if (!ui->cur_moved) ui->drag_colour = -1; + goto drag_dropped; + } + } + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + int r = region_from_coords(state, ds, x, y); + + if (r >= 0) { + ui->drag_colour = state->colouring[r]; + ui->drag_pencil = state->pencil[r]; + if (ui->drag_colour >= 0) + ui->drag_pencil = 0; /* should be already, but double-check */ + } else { + ui->drag_colour = -1; + ui->drag_pencil = 0; + } + ui->dragx = x; + ui->dragy = y; + ui->cur_visible = 0; + return ""; + } + + if ((button == LEFT_DRAG || button == RIGHT_DRAG) && + ui->drag_colour > -2) { + ui->dragx = x; + ui->dragy = y; + return ""; + } + + if ((button == LEFT_RELEASE || button == RIGHT_RELEASE) && + ui->drag_colour > -2) { + alt_button = (button == RIGHT_RELEASE) ? 1 : 0; + goto drag_dropped; + } + + return NULL; + +drag_dropped: + { + int r = region_from_coords(state, ds, x, y); + int c = ui->drag_colour; + int p = ui->drag_pencil; + int oldp; + + /* + * Cancel the drag, whatever happens. + */ + ui->drag_colour = -2; + + if (r < 0) + return ""; /* drag into border; do nothing else */ + + if (state->map->immutable[r]) + return ""; /* can't change this region */ + + if (state->colouring[r] == c && state->pencil[r] == p) + return ""; /* don't _need_ to change this region */ + + if (alt_button) { + if (state->colouring[r] >= 0) { + /* Can't pencil on a coloured region */ + return ""; + } else if (c >= 0) { + /* Right-dragging from colour to blank toggles one pencil */ + p = state->pencil[r] ^ (1 << c); + c = -1; + } + /* Otherwise, right-dragging from blank to blank is equivalent + * to left-dragging. */ + } + + bufp = buf; + oldp = state->pencil[r]; + if (c != state->colouring[r]) { + bufp += sprintf(bufp, ";%c:%d", (int)(c < 0 ? 'C' : '0' + c), r); + if (c >= 0) + oldp = 0; + } + if (p != oldp) { + int i; + for (i = 0; i < FOUR; i++) + if ((oldp ^ p) & (1 << i)) + bufp += sprintf(bufp, ";p%c:%d", (int)('0' + i), r); + } + + return dupstr(buf+1); /* ignore first semicolon */ + } +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int n = state->p.n; + game_state *ret = dup_game(state); + int c, k, adv, i; + + while (*move) { + int pencil = FALSE; + + c = *move; + if (c == 'p') { + pencil = TRUE; + c = *++move; + } + if ((c == 'C' || (c >= '0' && c < '0'+FOUR)) && + sscanf(move+1, ":%d%n", &k, &adv) == 1 && + k >= 0 && k < state->p.n) { + move += 1 + adv; + if (pencil) { + if (ret->colouring[k] >= 0) { + free_game(ret); + return NULL; + } + if (c == 'C') + ret->pencil[k] = 0; + else + ret->pencil[k] ^= 1 << (c - '0'); + } else { + ret->colouring[k] = (c == 'C' ? -1 : c - '0'); + ret->pencil[k] = 0; + } + } else if (*move == 'S') { + move++; + ret->cheated = TRUE; + } else { + free_game(ret); + return NULL; + } + + if (*move && *move != ';') { + free_game(ret); + return NULL; + } + if (*move) + move++; + } + + /* + * Check for completion. + */ + if (!ret->completed) { + int ok = TRUE; + + for (i = 0; i < n; i++) + if (ret->colouring[i] < 0) { + ok = FALSE; + break; + } + + if (ok) { + for (i = 0; i < ret->map->ngraph; i++) { + int j = ret->map->graph[i] / n; + int k = ret->map->graph[i] % n; + if (ret->colouring[j] == ret->colouring[k]) { + ok = FALSE; + break; + } + } + } + + if (ok) + ret->completed = TRUE; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = params->w * TILESIZE + 2 * BORDER + 1; + *y = params->h * TILESIZE + 2 * BORDER + 1; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + + assert(!ds->bl); /* set_size is never called twice */ + ds->bl = blitter_new(dr, TILESIZE+3, TILESIZE+3); +} + +const float map_colours[FOUR][3] = { +#ifdef VIVID_COLOURS + /* Use more vivid colours (e.g. on the Pocket PC) */ + {0.75F, 0.25F, 0.25F}, + {0.3F, 0.7F, 0.3F}, + {0.3F, 0.3F, 0.7F}, + {0.85F, 0.85F, 0.1F}, +#else + {0.7F, 0.5F, 0.4F}, + {0.8F, 0.7F, 0.4F}, + {0.5F, 0.6F, 0.4F}, + {0.55F, 0.45F, 0.35F}, +#endif +}; +const int map_hatching[FOUR] = { + HATCH_VERT, HATCH_SLASH, HATCH_HORIZ, HATCH_BACKSLASH +}; + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + memcpy(ret + COL_0 * 3, map_colours[0], 3 * sizeof(float)); + memcpy(ret + COL_1 * 3, map_colours[1], 3 * sizeof(float)); + memcpy(ret + COL_2 * 3, map_colours[2], 3 * sizeof(float)); + memcpy(ret + COL_3 * 3, map_colours[3], 3 * sizeof(float)); + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_ERRTEXT * 3 + 0] = 1.0F; + ret[COL_ERRTEXT * 3 + 1] = 1.0F; + ret[COL_ERRTEXT * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->drawn = snewn(state->p.w * state->p.h, unsigned long); + for (i = 0; i < state->p.w * state->p.h; i++) + ds->drawn[i] = 0xFFFFL; + ds->todraw = snewn(state->p.w * state->p.h, unsigned long); + ds->started = FALSE; + ds->bl = NULL; + ds->drag_visible = FALSE; + ds->dragx = ds->dragy = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->drawn); + sfree(ds->todraw); + if (ds->bl) + blitter_free(dr, ds->bl); + sfree(ds); +} + +static void draw_error(drawing *dr, game_drawstate *ds, int x, int y) +{ + int coords[8]; + int yext, xext; + + /* + * Draw a diamond. + */ + coords[0] = x - TILESIZE*2/5; + coords[1] = y; + coords[2] = x; + coords[3] = y - TILESIZE*2/5; + coords[4] = x + TILESIZE*2/5; + coords[5] = y; + coords[6] = x; + coords[7] = y + TILESIZE*2/5; + draw_polygon(dr, coords, 4, COL_ERROR, COL_GRID); + + /* + * Draw an exclamation mark in the diamond. This turns out to + * look unpleasantly off-centre if done via draw_text, so I do + * it by hand on the basis that exclamation marks aren't that + * difficult to draw... + */ + xext = TILESIZE/16; + yext = TILESIZE*2/5 - (xext*2+2); + draw_rect(dr, x-xext, y-yext, xext*2+1, yext*2+1 - (xext*3), + COL_ERRTEXT); + draw_rect(dr, x-xext, y+yext-xext*2+1, xext*2+1, xext*2, COL_ERRTEXT); +} + +static void draw_square(drawing *dr, game_drawstate *ds, + const game_params *params, struct map *map, + int x, int y, unsigned long v) +{ + int w = params->w, h = params->h, wh = w*h; + int tv, bv, xo, yo, i, j, oldj; + unsigned long errs, pencil, show_numbers; + + errs = v & ERR_MASK; + v &= ~ERR_MASK; + pencil = v & PENCIL_MASK; + v &= ~PENCIL_MASK; + show_numbers = v & SHOW_NUMBERS; + v &= ~SHOW_NUMBERS; + tv = v / FIVE; + bv = v % FIVE; + + clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); + + /* + * Draw the region colour. + */ + draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE, + (tv == FOUR ? COL_BACKGROUND : COL_0 + tv)); + /* + * Draw the second region colour, if this is a diagonally + * divided square. + */ + if (map->map[TE * wh + y*w+x] != map->map[BE * wh + y*w+x]) { + int coords[6]; + coords[0] = COORD(x)-1; + coords[1] = COORD(y+1)+1; + if (map->map[LE * wh + y*w+x] == map->map[TE * wh + y*w+x]) + coords[2] = COORD(x+1)+1; + else + coords[2] = COORD(x)-1; + coords[3] = COORD(y)-1; + coords[4] = COORD(x+1)+1; + coords[5] = COORD(y+1)+1; + draw_polygon(dr, coords, 3, + (bv == FOUR ? COL_BACKGROUND : COL_0 + bv), COL_GRID); + } + + /* + * Draw `pencil marks'. Currently we arrange these in a square + * formation, which means we may be in trouble if the value of + * FOUR changes later... + */ + assert(FOUR == 4); + for (yo = 0; yo < 4; yo++) + for (xo = 0; xo < 4; xo++) { + int te = map->map[TE * wh + y*w+x]; + int e, ee, c; + + e = (yo < xo && yo < 3-xo ? TE : + yo > xo && yo > 3-xo ? BE : + xo < 2 ? LE : RE); + ee = map->map[e * wh + y*w+x]; + + if (xo != (yo * 2 + 1) % 5) + continue; + c = yo; + + if (!(pencil & ((ee == te ? PENCIL_T_BASE : PENCIL_B_BASE) << c))) + continue; + + if (yo == xo && + (map->map[TE * wh + y*w+x] != map->map[LE * wh + y*w+x])) + continue; /* avoid TL-BR diagonal line */ + if (yo == 3-xo && + (map->map[TE * wh + y*w+x] != map->map[RE * wh + y*w+x])) + continue; /* avoid BL-TR diagonal line */ + + draw_circle(dr, COORD(x) + (xo+1)*TILESIZE/5, + COORD(y) + (yo+1)*TILESIZE/5, + TILESIZE/7, COL_0 + c, COL_0 + c); + } + + /* + * Draw the grid lines, if required. + */ + if (x <= 0 || map->map[RE*wh+y*w+(x-1)] != map->map[LE*wh+y*w+x]) + draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE, COL_GRID); + if (y <= 0 || map->map[BE*wh+(y-1)*w+x] != map->map[TE*wh+y*w+x]) + draw_rect(dr, COORD(x), COORD(y), TILESIZE, 1, COL_GRID); + if (x <= 0 || y <= 0 || + map->map[RE*wh+(y-1)*w+(x-1)] != map->map[TE*wh+y*w+x] || + map->map[BE*wh+(y-1)*w+(x-1)] != map->map[LE*wh+y*w+x]) + draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID); + + /* + * Draw error markers. + */ + for (yo = 0; yo < 3; yo++) + for (xo = 0; xo < 3; xo++) + if (errs & (ERR_BASE << (yo*3+xo))) + draw_error(dr, ds, + (COORD(x)*2+TILESIZE*xo)/2, + (COORD(y)*2+TILESIZE*yo)/2); + + /* + * Draw region numbers, if desired. + */ + if (show_numbers) { + oldj = -1; + for (i = 0; i < 2; i++) { + j = map->map[(i?BE:TE)*wh+y*w+x]; + if (oldj == j) + continue; + oldj = j; + + xo = map->regionx[j] - 2*x; + yo = map->regiony[j] - 2*y; + if (xo >= 0 && xo <= 2 && yo >= 0 && yo <= 2) { + char buf[80]; + sprintf(buf, "%d", j); + draw_text(dr, (COORD(x)*2+TILESIZE*xo)/2, + (COORD(y)*2+TILESIZE*yo)/2, + FONT_VARIABLE, 3*TILESIZE/5, + ALIGN_HCENTRE|ALIGN_VCENTRE, + COL_GRID, buf); + } + } + } + + unclip(dr); + + draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->p.w, h = state->p.h, wh = w*h, n = state->p.n; + int x, y, i; + int flash; + + if (ds->drag_visible) { + blitter_load(dr, ds->bl, ds->dragx, ds->dragy); + draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3); + ds->drag_visible = FALSE; + } + + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + if (!ds->started) { + int ww, wh; + + game_compute_size(&state->p, TILESIZE, &ww, &wh); + draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND); + draw_rect(dr, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1, + COL_GRID); + + draw_update(dr, 0, 0, ww, wh); + ds->started = TRUE; + } + + if (flashtime) { + if (flash_type == 1) + flash = (int)(flashtime * FOUR / flash_length); + else + flash = 1 + (int)(flashtime * THREE / flash_length); + } else + flash = -1; + + /* + * Set up the `todraw' array. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int tv = state->colouring[state->map->map[TE * wh + y*w+x]]; + int bv = state->colouring[state->map->map[BE * wh + y*w+x]]; + unsigned long v; + + if (tv < 0) + tv = FOUR; + if (bv < 0) + bv = FOUR; + + if (flash >= 0) { + if (flash_type == 1) { + if (tv == flash) + tv = FOUR; + if (bv == flash) + bv = FOUR; + } else if (flash_type == 2) { + if (flash % 2) + tv = bv = FOUR; + } else { + if (tv != FOUR) + tv = (tv + flash) % FOUR; + if (bv != FOUR) + bv = (bv + flash) % FOUR; + } + } + + v = tv * FIVE + bv; + + /* + * Add pencil marks. + */ + for (i = 0; i < FOUR; i++) { + if (state->colouring[state->map->map[TE * wh + y*w+x]] < 0 && + (state->pencil[state->map->map[TE * wh + y*w+x]] & (1<colouring[state->map->map[BE * wh + y*w+x]] < 0 && + (state->pencil[state->map->map[BE * wh + y*w+x]] & (1<show_numbers) + v |= SHOW_NUMBERS; + + ds->todraw[y*w+x] = v; + } + + /* + * Add error markers to the `todraw' array. + */ + for (i = 0; i < state->map->ngraph; i++) { + int v1 = state->map->graph[i] / n; + int v2 = state->map->graph[i] % n; + int xo, yo; + + if (state->colouring[v1] < 0 || state->colouring[v2] < 0) + continue; + if (state->colouring[v1] != state->colouring[v2]) + continue; + + x = state->map->edgex[i]; + y = state->map->edgey[i]; + + xo = x % 2; x /= 2; + yo = y % 2; y /= 2; + + ds->todraw[y*w+x] |= ERR_BASE << (yo*3+xo); + if (xo == 0) { + assert(x > 0); + ds->todraw[y*w+(x-1)] |= ERR_BASE << (yo*3+2); + } + if (yo == 0) { + assert(y > 0); + ds->todraw[(y-1)*w+x] |= ERR_BASE << (2*3+xo); + } + if (xo == 0 && yo == 0) { + assert(x > 0 && y > 0); + ds->todraw[(y-1)*w+(x-1)] |= ERR_BASE << (2*3+2); + } + } + + /* + * Now actually draw everything. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + unsigned long v = ds->todraw[y*w+x]; + if (ds->drawn[y*w+x] != v) { + draw_square(dr, ds, &state->p, state->map, x, y, v); + ds->drawn[y*w+x] = v; + } + } + + /* + * Draw the dragged colour blob if any. + */ + if ((ui->drag_colour > -2) || ui->cur_visible) { + int bg, iscur = 0; + if (ui->drag_colour >= 0) + bg = COL_0 + ui->drag_colour; + else if (ui->drag_colour == -1) { + bg = COL_BACKGROUND; + } else { + int r = region_from_coords(state, ds, ui->dragx, ui->dragy); + int c = (r < 0) ? -1 : state->colouring[r]; + assert(ui->cur_visible); + /*bg = COL_GRID;*/ + bg = (c < 0) ? COL_BACKGROUND : COL_0 + c; + iscur = 1; + } + + ds->dragx = ui->dragx - TILESIZE/2 - 2; + ds->dragy = ui->dragy - TILESIZE/2 - 2; + blitter_save(dr, ds->bl, ds->dragx, ds->dragy); + draw_circle(dr, ui->dragx, ui->dragy, + iscur ? TILESIZE/4 : TILESIZE/2, bg, COL_GRID); + for (i = 0; i < FOUR; i++) + if (ui->drag_pencil & (1 << i)) + draw_circle(dr, ui->dragx + ((i*4+2)%10-3) * TILESIZE/10, + ui->dragy + (i*2-3) * TILESIZE/10, + TILESIZE/8, COL_0 + i, COL_0 + i); + draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3); + ds->drag_visible = TRUE; + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) { + if (flash_type < 0) { + char *env = getenv("MAP_ALTERNATIVE_FLASH"); + if (env) + flash_type = atoi(env); + else + flash_type = 0; + flash_length = (flash_type == 1 ? 0.50F : 0.30F); + } + return flash_length; + } else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 4mm squares by default, I think. Simplest way to + * compute this size is to compute the pixel puzzle size at a + * given tile size and then scale. + */ + game_compute_size(params, 400, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->p.w, h = state->p.h, wh = w*h, n = state->p.n; + int ink, c[FOUR], i; + int x, y, r; + int *coords, ncoords, coordsize; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + /* We can't call game_set_size() here because we don't want a blitter */ + ads.tilesize = tilesize; + + ink = print_mono_colour(dr, 0); + for (i = 0; i < FOUR; i++) + c[i] = print_rgb_hatched_colour(dr, map_colours[i][0], + map_colours[i][1], map_colours[i][2], + map_hatching[i]); + + coordsize = 0; + coords = NULL; + + print_line_width(dr, TILESIZE / 16); + + /* + * Draw a single filled polygon around each region. + */ + for (r = 0; r < n; r++) { + int octants[8], lastdir, d1, d2, ox, oy; + + /* + * Start by finding a point on the region boundary. Any + * point will do. To do this, we'll search for a square + * containing the region and then decide which corner of it + * to use. + */ + x = w; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (state->map->map[wh*0+y*w+x] == r || + state->map->map[wh*1+y*w+x] == r || + state->map->map[wh*2+y*w+x] == r || + state->map->map[wh*3+y*w+x] == r) + break; + } + if (x < w) + break; + } + assert(y < h && x < w); /* we must have found one somewhere */ + /* + * This is the first square in lexicographic order which + * contains part of this region. Therefore, one of the top + * two corners of the square must be what we're after. The + * only case in which it isn't the top left one is if the + * square is diagonally divided and the region is in the + * bottom right half. + */ + if (state->map->map[wh*TE+y*w+x] != r && + state->map->map[wh*LE+y*w+x] != r) + x++; /* could just as well have done y++ */ + + /* + * Now we have a point on the region boundary. Trace around + * the region until we come back to this point, + * accumulating coordinates for a polygon draw operation as + * we go. + */ + lastdir = -1; + ox = x; + oy = y; + ncoords = 0; + + do { + /* + * There are eight possible directions we could head in + * from here. We identify them by octant numbers, and + * we also use octant numbers to identify the spaces + * between them: + * + * 6 7 0 + * \ 7|0 / + * \ | / + * 6 \|/ 1 + * 5-----+-----1 + * 5 /|\ 2 + * / | \ + * / 4|3 \ + * 4 3 2 + */ + octants[0] = x0 ? state->map->map[wh*LE+(y-1)*w+x] : -1; + octants[1] = x0 ? state->map->map[wh*BE+(y-1)*w+x] : -1; + octants[2] = xmap->map[wh*TE+y*w+x] : -1; + octants[3] = xmap->map[wh*LE+y*w+x] : -1; + octants[4] = x>0 && ymap->map[wh*RE+y*w+(x-1)] : -1; + octants[5] = x>0 && ymap->map[wh*TE+y*w+(x-1)] : -1; + octants[6] = x>0 && y>0 ? state->map->map[wh*BE+(y-1)*w+(x-1)] :-1; + octants[7] = x>0 && y>0 ? state->map->map[wh*RE+(y-1)*w+(x-1)] :-1; + + d1 = d2 = -1; + for (i = 0; i < 8; i++) + if ((octants[i] == r) ^ (octants[(i+1)%8] == r)) { + assert(d2 == -1); + if (d1 == -1) + d1 = i; + else + d2 = i; + } + + assert(d1 != -1 && d2 != -1); + if (d1 == lastdir) + d1 = d2; + + /* + * Now we're heading in direction d1. Save the current + * coordinates. + */ + if (ncoords + 2 > coordsize) { + coordsize += 128; + coords = sresize(coords, coordsize, int); + } + coords[ncoords++] = COORD(x); + coords[ncoords++] = COORD(y); + + /* + * Compute the new coordinates. + */ + x += (d1 % 4 == 3 ? 0 : d1 < 4 ? +1 : -1); + y += (d1 % 4 == 1 ? 0 : d1 > 1 && d1 < 5 ? +1 : -1); + assert(x >= 0 && x <= w && y >= 0 && y <= h); + + lastdir = d1 ^ 4; + } while (x != ox || y != oy); + + draw_polygon(dr, coords, ncoords/2, + state->colouring[r] >= 0 ? + c[state->colouring[r]] : -1, ink); + } + sfree(coords); +} + +#ifdef COMBINED +#define thegame map +#endif + +const struct game thegame = { + "Map", "games.map", "map", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + 20, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, TRUE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff, really_verbose = FALSE; + struct solver_scratch *sc; + int i; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_verbose = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + sc = new_scratch(s->map->graph, s->map->n, s->map->ngraph); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + for (diff = 0; diff < DIFFCOUNT; diff++) { + for (i = 0; i < s->map->n; i++) + if (!s->map->immutable[i]) + s->colouring[i] = -1; + ret = map_solver(sc, s->map->graph, s->map->n, s->map->ngraph, + s->colouring, diff); + if (ret < 2) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: harder than Hard, or ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == 0) + printf("Difficulty rating: impossible (no solution exists)\n"); + else if (ret == 1) + printf("Difficulty rating: %s\n", map_diffnames[diff]); + } else { + verbose = really_verbose; + for (i = 0; i < s->map->n; i++) + if (!s->map->immutable[i]) + s->colouring[i] = -1; + ret = map_solver(sc, s->map->graph, s->map->n, s->map->ngraph, + s->colouring, diff); + if (ret == 0) + printf("Puzzle is inconsistent\n"); + else { + int col = 0; + + for (i = 0; i < s->map->n; i++) { + printf("%5d <- %c%c", i, colnames[s->colouring[i]], + (col < 6 && i+1 < s->map->n ? ' ' : '\n')); + if (++col == 7) + col = 0; + } + } + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/maxflow.c b/apps/plugins/puzzles/maxflow.c new file mode 100644 index 0000000000..3a25654ff0 --- /dev/null +++ b/apps/plugins/puzzles/maxflow.c @@ -0,0 +1,461 @@ +/* + * Edmonds-Karp algorithm for finding a maximum flow and minimum + * cut in a network. Almost identical to the Ford-Fulkerson + * algorithm, but apparently using breadth-first search to find the + * _shortest_ augmenting path is a good way to guarantee + * termination and ensure the time complexity is not dependent on + * the actual value of the maximum flow. I don't understand why + * that should be, but it's claimed on the Internet that it's been + * proved, and that's good enough for me. I prefer BFS to DFS + * anyway :-) + */ + +#include "rbassert.h" +#include +#include + +#include "maxflow.h" + +#include "puzzles.h" /* for snewn/sfree */ + +int maxflow_with_scratch(void *scratch, int nv, int source, int sink, + int ne, const int *edges, const int *backedges, + const int *capacity, int *flow, int *cut) +{ + int *todo = (int *)scratch; + int *prev = todo + nv; + int *firstedge = todo + 2*nv; + int *firstbackedge = todo + 3*nv; + int i, j, head, tail, from, to; + int totalflow; + + /* + * Scan the edges array to find the index of the first edge + * from each node. + */ + j = 0; + for (i = 0; i < ne; i++) + while (j <= edges[2*i]) + firstedge[j++] = i; + while (j < nv) + firstedge[j++] = ne; + assert(j == nv); + + /* + * Scan the backedges array to find the index of the first edge + * _to_ each node. + */ + j = 0; + for (i = 0; i < ne; i++) + while (j <= edges[2*backedges[i]+1]) + firstbackedge[j++] = i; + while (j < nv) + firstbackedge[j++] = ne; + assert(j == nv); + + /* + * Start the flow off at zero on every edge. + */ + for (i = 0; i < ne; i++) + flow[i] = 0; + totalflow = 0; + + /* + * Repeatedly look for an augmenting path, and follow it. + */ + while (1) { + + /* + * Set up the prev array. + */ + for (i = 0; i < nv; i++) + prev[i] = -1; + + /* + * Initialise the to-do list for BFS. + */ + head = tail = 0; + todo[tail++] = source; + + /* + * Now do the BFS loop. + */ + while (head < tail && prev[sink] <= 0) { + from = todo[head++]; + + /* + * Try all the forward edges out of node `from'. For a + * forward edge to be valid, it must have flow + * currently less than its capacity. + */ + for (i = firstedge[from]; i < ne && edges[2*i] == from; i++) { + to = edges[2*i+1]; + if (to == source || prev[to] >= 0) + continue; + if (capacity[i] >= 0 && flow[i] >= capacity[i]) + continue; + /* + * This is a valid augmenting edge. Visit node `to'. + */ + prev[to] = 2*i; + todo[tail++] = to; + } + + /* + * Try all the backward edges into node `from'. For a + * backward edge to be valid, it must have flow + * currently greater than zero. + */ + for (i = firstbackedge[from]; + j = backedges[i], i < ne && edges[2*j+1]==from; i++) { + to = edges[2*j]; + if (to == source || prev[to] >= 0) + continue; + if (flow[j] <= 0) + continue; + /* + * This is a valid augmenting edge. Visit node `to'. + */ + prev[to] = 2*j+1; + todo[tail++] = to; + } + } + + /* + * If prev[sink] is non-null, we have found an augmenting + * path. + */ + if (prev[sink] >= 0) { + int max; + + /* + * Work backwards along the path figuring out the + * maximum flow we can add. + */ + to = sink; + max = -1; + while (to != source) { + int spare; + + /* + * Find the edge we're currently moving along. + */ + i = prev[to]; + from = edges[i]; + assert(from != to); + + /* + * Determine the spare capacity of this edge. + */ + if (i & 1) + spare = flow[i / 2]; /* backward edge */ + else if (capacity[i / 2] >= 0) + spare = capacity[i / 2] - flow[i / 2]; /* forward edge */ + else + spare = -1; /* unlimited forward edge */ + + assert(spare != 0); + + if (max < 0 || (spare >= 0 && spare < max)) + max = spare; + + to = from; + } + /* + * Fail an assertion if max is still < 0, i.e. there is + * an entirely unlimited path from source to sink. Also + * max should not _be_ zero, because by construction + * this _should_ be an augmenting path. + */ + assert(max > 0); + + /* + * Now work backwards along the path again, this time + * actually adjusting the flow. + */ + to = sink; + while (to != source) { + /* + * Find the edge we're currently moving along. + */ + i = prev[to]; + from = edges[i]; + assert(from != to); + + /* + * Adjust the edge. + */ + if (i & 1) + flow[i / 2] -= max; /* backward edge */ + else + flow[i / 2] += max; /* forward edge */ + + to = from; + } + + /* + * And adjust the overall flow counter. + */ + totalflow += max; + + continue; + } + + /* + * If we reach here, we have failed to find an augmenting + * path, which means we're done. Output the `cut' array if + * required, and leave. + */ + if (cut) { + for (i = 0; i < nv; i++) { + if (i == source || prev[i] >= 0) + cut[i] = 0; + else + cut[i] = 1; + } + } + return totalflow; + } +} + +int maxflow_scratch_size(int nv) +{ + return (nv * 4) * sizeof(int); +} + +void maxflow_setup_backedges(int ne, const int *edges, int *backedges) +{ + int i, n; + + for (i = 0; i < ne; i++) + backedges[i] = i; + + /* + * We actually can't use the C qsort() function, because we'd + * need to pass `edges' as a context parameter to its + * comparator function. So instead I'm forced to implement my + * own sorting algorithm internally, which is a pest. I'll use + * heapsort, because I like it. + */ + +#define LESS(i,j) ( (edges[2*(i)+1] < edges[2*(j)+1]) || \ + (edges[2*(i)+1] == edges[2*(j)+1] && \ + edges[2*(i)] < edges[2*(j)]) ) +#define PARENT(n) ( ((n)-1)/2 ) +#define LCHILD(n) ( 2*(n)+1 ) +#define RCHILD(n) ( 2*(n)+2 ) +#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0) + + /* + * Phase 1: build the heap. We want the _largest_ element at + * the top. + */ + n = 0; + while (n < ne) { + n++; + + /* + * Swap element n with its parent repeatedly to preserve + * the heap property. + */ + i = n-1; + + while (i > 0) { + int p = PARENT(i); + + if (LESS(backedges[p], backedges[i])) { + SWAP(backedges[p], backedges[i]); + i = p; + } else + break; + } + } + + /* + * Phase 2: repeatedly remove the largest element and stick it + * at the top of the array. + */ + while (n > 0) { + /* + * The largest element is at position 0. Put it at the top, + * and swap the arbitrary element from that position into + * position 0. + */ + n--; + SWAP(backedges[0], backedges[n]); + + /* + * Now repeatedly move that arbitrary element down the heap + * by swapping it with the more suitable of its children. + */ + i = 0; + while (1) { + int lc, rc; + + lc = LCHILD(i); + rc = RCHILD(i); + + if (lc >= n) + break; /* we've hit bottom */ + + if (rc >= n) { + /* + * Special case: there is only one child to check. + */ + if (LESS(backedges[i], backedges[lc])) + SWAP(backedges[i], backedges[lc]); + + /* _Now_ we've hit bottom. */ + break; + } else { + /* + * The common case: there are two children and we + * must check them both. + */ + if (LESS(backedges[i], backedges[lc]) || + LESS(backedges[i], backedges[rc])) { + /* + * Pick the more appropriate child to swap with + * (i.e. the one which would want to be the + * parent if one were above the other - as one + * is about to be). + */ + if (LESS(backedges[lc], backedges[rc])) { + SWAP(backedges[i], backedges[rc]); + i = rc; + } else { + SWAP(backedges[i], backedges[lc]); + i = lc; + } + } else { + /* This element is in the right place; we're done. */ + break; + } + } + } + } + +#undef LESS +#undef PARENT +#undef LCHILD +#undef RCHILD +#undef SWAP + +} + +int maxflow(int nv, int source, int sink, + int ne, const int *edges, const int *capacity, + int *flow, int *cut) +{ + void *scratch; + int *backedges; + int size; + int ret; + + /* + * Allocate the space. + */ + size = ne * sizeof(int) + maxflow_scratch_size(nv); + backedges = smalloc(size); + if (!backedges) + return -1; + scratch = backedges + ne; + + /* + * Set up the backedges array. + */ + maxflow_setup_backedges(ne, edges, backedges); + + /* + * Call the main function. + */ + ret = maxflow_with_scratch(scratch, nv, source, sink, ne, edges, + backedges, capacity, flow, cut); + + /* + * Free the scratch space. + */ + sfree(backedges); + + /* + * And we're done. + */ + return ret; +} + +#ifdef TESTMODE + +#define MAXEDGES 256 +#define MAXVERTICES 128 +#define ADDEDGE(i,j) do{edges[ne*2] = (i); edges[ne*2+1] = (j); ne++;}while(0) + +int compare_edge(const void *av, const void *bv) +{ + const int *a = (const int *)av; + const int *b = (const int *)bv; + + if (a[0] < b[0]) + return -1; + else if (a[0] > b[0]) + return +1; + else if (a[1] < b[1]) + return -1; + else if (a[1] > b[1]) + return +1; + else + return 0; +} + +int main(void) +{ + int edges[MAXEDGES*2], ne, nv; + int capacity[MAXEDGES], flow[MAXEDGES], cut[MAXVERTICES]; + int source, sink, p, q, i, j, ret; + + /* + * Use this algorithm to find a maximal complete matching in a + * bipartite graph. + */ + ne = 0; + nv = 0; + source = nv++; + p = nv; + nv += 5; + q = nv; + nv += 5; + sink = nv++; + for (i = 0; i < 5; i++) { + capacity[ne] = 1; + ADDEDGE(source, p+i); + } + for (i = 0; i < 5; i++) { + capacity[ne] = 1; + ADDEDGE(q+i, sink); + } + j = ne; + capacity[ne] = 1; ADDEDGE(p+0,q+0); + capacity[ne] = 1; ADDEDGE(p+1,q+0); + capacity[ne] = 1; ADDEDGE(p+1,q+1); + capacity[ne] = 1; ADDEDGE(p+2,q+1); + capacity[ne] = 1; ADDEDGE(p+2,q+2); + capacity[ne] = 1; ADDEDGE(p+3,q+2); + capacity[ne] = 1; ADDEDGE(p+3,q+3); + capacity[ne] = 1; ADDEDGE(p+4,q+3); + /* capacity[ne] = 1; ADDEDGE(p+2,q+4); */ + qsort(edges, ne, 2*sizeof(int), compare_edge); + + ret = maxflow(nv, source, sink, ne, edges, capacity, flow, cut); + + printf("ret = %d\n", ret); + + for (i = 0; i < ne; i++) + printf("flow %d: %d -> %d\n", flow[i], edges[2*i], edges[2*i+1]); + + for (i = 0; i < nv; i++) + if (cut[i] == 0) + printf("difficult set includes %d\n", i); + + return 0; +} + +#endif diff --git a/apps/plugins/puzzles/maxflow.h b/apps/plugins/puzzles/maxflow.h new file mode 100644 index 0000000000..d490f45421 --- /dev/null +++ b/apps/plugins/puzzles/maxflow.h @@ -0,0 +1,95 @@ +/* + * Edmonds-Karp algorithm for finding a maximum flow and minimum + * cut in a network. Almost identical to the Ford-Fulkerson + * algorithm, but apparently using breadth-first search to find the + * _shortest_ augmenting path is a good way to guarantee + * termination and ensure the time complexity is not dependent on + * the actual value of the maximum flow. I don't understand why + * that should be, but it's claimed on the Internet that it's been + * proved, and that's good enough for me. I prefer BFS to DFS + * anyway :-) + */ + +#ifndef MAXFLOW_MAXFLOW_H +#define MAXFLOW_MAXFLOW_H + +/* + * The actual algorithm. + * + * Inputs: + * + * - `scratch' is previously allocated scratch space of a size + * previously determined by calling `maxflow_scratch_size'. + * + * - `nv' is the number of vertices. Vertices are assumed to be + * numbered from 0 to nv-1. + * + * - `source' and `sink' are the distinguished source and sink + * vertices. + * + * - `ne' is the number of edges in the graph. + * + * - `edges' is an array of 2*ne integers, giving a (source, dest) + * pair for each network edge. Edge pairs are expected to be + * sorted in lexicographic order. + * + * - `backedges' is an array of `ne' integers, each a distinct + * index into `edges'. The edges in `edges', if permuted as + * specified by this array, should end up sorted in the _other_ + * lexicographic order, i.e. dest taking priority over source. + * + * - `capacity' is an array of `ne' integers, giving a maximum + * flow capacity for each edge. A negative value is taken to + * indicate unlimited capacity on that edge, but note that there + * may not be any unlimited-capacity _path_ from source to sink + * or an assertion will be failed. + * + * Output: + * + * - `flow' must be non-NULL. It is an array of `ne' integers, + * each giving the final flow along each edge. + * + * - `cut' may be NULL. If non-NULL, it is an array of `nv' + * integers, which will be set to zero or one on output, in such + * a way that: + * + the set of zero vertices includes the source + * + the set of one vertices includes the sink + * + the maximum flow capacity between the zero and one vertex + * sets is achieved (i.e. all edges from a zero vertex to a + * one vertex are at full capacity, while all edges from a + * one vertex to a zero vertex have no flow at all). + * + * - the returned value from the function is the total flow + * achieved. + */ +int maxflow_with_scratch(void *scratch, int nv, int source, int sink, + int ne, const int *edges, const int *backedges, + const int *capacity, int *flow, int *cut); + +/* + * The above function expects its `scratch' and `backedges' + * parameters to have already been set up. This allows you to set + * them up once and use them in multiple invocates of the + * algorithm. Now I provide functions to actually do the setting + * up. + */ +int maxflow_scratch_size(int nv); +void maxflow_setup_backedges(int ne, const int *edges, int *backedges); + +/* + * Simplified version of the above function. All parameters are the + * same, except that `scratch' and `backedges' are constructed + * internally. This is the simplest way to call the algorithm as a + * one-off; however, if you need to call it multiple times on the + * same network, it is probably better to call the above version + * directly so that you only construct `scratch' and `backedges' + * once. + * + * Additional return value is now -1, meaning that scratch space + * could not be allocated. + */ +int maxflow(int nv, int source, int sink, + int ne, const int *edges, const int *capacity, + int *flow, int *cut); + +#endif /* MAXFLOW_MAXFLOW_H */ diff --git a/apps/plugins/puzzles/midend.c b/apps/plugins/puzzles/midend.c new file mode 100644 index 0000000000..e2938a45bc --- /dev/null +++ b/apps/plugins/puzzles/midend.c @@ -0,0 +1,2136 @@ +/* + * midend.c: general middle fragment sitting between the + * platform-specific front end and game-specific back end. + * Maintains a move list, takes care of Undo and Redo commands, and + * processes standard keystrokes for undo/redo/new/quit. + */ +#include "puzzles.h" + +#include "rbcompat.h" +#include "rbassert.h" + +enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */ + +enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */ + +#define special(type) ( (type) != MOVE ) + +struct midend_state_entry { + game_state *state; + char *movestr; + int movetype; +}; + +struct midend { + frontend *frontend; + random_state *random; + const game *ourgame; + + game_params **presets; + char **preset_names, **preset_encodings; + int npresets, presetsize; + + /* + * `desc' and `privdesc' deserve a comment. + * + * `desc' is the game description as presented to the user when + * they ask for Game -> Specific. `privdesc', if non-NULL, is a + * different game description used to reconstruct the initial + * game_state when de-serialising. If privdesc is NULL, `desc' + * is used for both. + * + * For almost all games, `privdesc' is NULL and never used. The + * exception (as usual) is Mines: the initial game state has no + * squares open at all, but after the first click `desc' is + * rewritten to describe a game state with an initial click and + * thus a bunch of squares open. If we used that desc to + * serialise and deserialise, then the initial game state after + * deserialisation would look unlike the initial game state + * beforehand, and worse still execute_move() might fail on the + * attempted first click. So `privdesc' is also used in this + * case, to provide a game description describing the same + * fixed mine layout _but_ no initial click. (These game IDs + * may also be typed directly into Mines if you like.) + */ + char *desc, *privdesc, *seedstr; + char *aux_info; + enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode; + + int nstates, statesize, statepos; + struct midend_state_entry *states; + + game_params *params, *curparams; + game_drawstate *drawstate; + game_ui *ui; + + game_state *oldstate; + float anim_time, anim_pos; + float flash_time, flash_pos; + int dir; + + int timing; + float elapsed; + char *laststatus; + + drawing *drawing; + + int pressed_mouse_button; + + int preferred_tilesize, tilesize, winwidth, winheight; + + void (*game_id_change_notify_function)(void *); + void *game_id_change_notify_ctx; +}; + +#define ensure(me) do { \ + if ((me)->nstates >= (me)->statesize) { \ + (me)->statesize = (me)->nstates + 128; \ + (me)->states = sresize((me)->states, (me)->statesize, \ + struct midend_state_entry); \ + } \ +} while (0) + +void midend_reset_tilesize(midend *me) +{ + me->preferred_tilesize = me->ourgame->preferred_tilesize; + { + /* + * Allow an environment-based override for the default tile + * size by defining a variable along the lines of + * `NET_TILESIZE=15'. + */ + + char buf[80], *e; + int j, k, ts; + + sprintf(buf, "%s_TILESIZE", me->ourgame->name); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0) + me->preferred_tilesize = ts; + } +} + +midend *midend_new(frontend *fe, const game *ourgame, + const drawing_api *drapi, void *drhandle) +{ + midend *me = snew(midend); + void *randseed; + int randseedsize; + + get_random_seed(&randseed, &randseedsize); + + me->frontend = fe; + me->ourgame = ourgame; + me->random = random_new(randseed, randseedsize); + me->nstates = me->statesize = me->statepos = 0; + me->states = NULL; + me->params = ourgame->default_params(); + me->game_id_change_notify_function = NULL; + me->game_id_change_notify_ctx = NULL; + + /* + * Allow environment-based changing of the default settings by + * defining a variable along the lines of `NET_DEFAULT=25x25w' + * in which the value is an encoded parameter string. + */ + { + char buf[80], *e; + int j, k; + sprintf(buf, "%s_DEFAULT", me->ourgame->name); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + if ((e = getenv(buf)) != NULL) + me->ourgame->decode_params(me->params, e); + } + me->curparams = NULL; + me->desc = me->privdesc = NULL; + me->seedstr = NULL; + me->aux_info = NULL; + me->genmode = GOT_NOTHING; + me->drawstate = NULL; + me->oldstate = NULL; + me->presets = NULL; + me->preset_names = NULL; + me->preset_encodings = NULL; + me->npresets = me->presetsize = 0; + me->anim_time = me->anim_pos = 0.0F; + me->flash_time = me->flash_pos = 0.0F; + me->dir = 0; + me->ui = NULL; + me->pressed_mouse_button = 0; + me->laststatus = NULL; + me->timing = FALSE; + me->elapsed = 0.0F; + me->tilesize = me->winwidth = me->winheight = 0; + if (drapi) + me->drawing = drawing_new(drapi, me, drhandle); + else + me->drawing = NULL; + + midend_reset_tilesize(me); + + sfree(randseed); + + return me; +} + +const game *midend_which_game(midend *me) +{ + return me->ourgame; +} + +static void midend_purge_states(midend *me) +{ + while (me->nstates > me->statepos) { + me->ourgame->free_game(me->states[--me->nstates].state); + if (me->states[me->nstates].movestr) + sfree(me->states[me->nstates].movestr); + } +} + +static void midend_free_game(midend *me) +{ + while (me->nstates > 0) { + me->nstates--; + me->ourgame->free_game(me->states[me->nstates].state); + sfree(me->states[me->nstates].movestr); + } + + if (me->drawstate) + me->ourgame->free_drawstate(me->drawing, me->drawstate); +} + +void midend_free(midend *me) +{ + int i; + + midend_free_game(me); + + if (me->drawing) + drawing_free(me->drawing); + random_free(me->random); + sfree(me->states); + sfree(me->desc); + sfree(me->privdesc); + sfree(me->seedstr); + sfree(me->aux_info); + me->ourgame->free_params(me->params); + if (me->npresets) { + for (i = 0; i < me->npresets; i++) { + sfree(me->presets[i]); + sfree(me->preset_names[i]); + sfree(me->preset_encodings[i]); + } + sfree(me->presets); + sfree(me->preset_names); + sfree(me->preset_encodings); + } + if (me->ui) + me->ourgame->free_ui(me->ui); + if (me->curparams) + me->ourgame->free_params(me->curparams); + sfree(me->laststatus); + sfree(me); +} + +static void midend_size_new_drawstate(midend *me) +{ + /* + * Don't even bother, if we haven't worked out our tile size + * anyway yet. + */ + if (me->tilesize > 0) { + me->ourgame->compute_size(me->params, me->tilesize, + &me->winwidth, &me->winheight); + me->ourgame->set_size(me->drawing, me->drawstate, + me->params, me->tilesize); + } +} + +void midend_size(midend *me, int *x, int *y, int user_size) +{ + int min, max; + int rx, ry; + + /* + * We can't set the size on the same drawstate twice. So if + * we've already sized one drawstate, we must throw it away and + * create a new one. + */ + if (me->drawstate && me->tilesize > 0) { + me->ourgame->free_drawstate(me->drawing, me->drawstate); + me->drawstate = me->ourgame->new_drawstate(me->drawing, + me->states[0].state); + } + + /* + * Find the tile size that best fits within the given space. If + * `user_size' is TRUE, we must actually find the _largest_ such + * tile size, in order to get as close to the user's explicit + * request as possible; otherwise, we bound above at the game's + * preferred tile size, so that the game gets what it wants + * provided that this doesn't break the constraint from the + * front-end (which is likely to be a screen size or similar). + */ + if (user_size) { + max = 1; + do { + max *= 2; + me->ourgame->compute_size(me->params, max, &rx, &ry); + } while (rx <= *x && ry <= *y); + } else + max = me->preferred_tilesize + 1; + min = 1; + + /* + * Now binary-search between min and max. We're looking for a + * boundary rather than a value: the point at which tile sizes + * stop fitting within the given dimensions. Thus, we stop when + * max and min differ by exactly 1. + */ + while (max - min > 1) { + int mid = (max + min) / 2; + me->ourgame->compute_size(me->params, mid, &rx, &ry); + if (rx <= *x && ry <= *y) + min = mid; + else + max = mid; + } + + /* + * Now `min' is a valid size, and `max' isn't. So use `min'. + */ + + me->tilesize = min; + if (user_size) + /* If the user requested a change in size, make it permanent. */ + me->preferred_tilesize = me->tilesize; + midend_size_new_drawstate(me); + *x = me->winwidth; + *y = me->winheight; +} + +int midend_tilesize(midend *me) { return me->tilesize; } + +void midend_set_params(midend *me, game_params *params) +{ + me->ourgame->free_params(me->params); + me->params = me->ourgame->dup_params(params); +} + +game_params *midend_get_params(midend *me) +{ + return me->ourgame->dup_params(me->params); +} + +static void midend_set_timer(midend *me) +{ + me->timing = (me->ourgame->is_timed && + me->ourgame->timing_state(me->states[me->statepos-1].state, + me->ui)); + if (me->timing || me->flash_time || me->anim_time) + activate_timer(me->frontend); + else + deactivate_timer(me->frontend); +} + +void midend_force_redraw(midend *me) +{ + if (me->drawstate) + me->ourgame->free_drawstate(me->drawing, me->drawstate); + me->drawstate = me->ourgame->new_drawstate(me->drawing, + me->states[0].state); + midend_size_new_drawstate(me); + midend_redraw(me); +} + +void midend_new_game(midend *me) +{ + midend_stop_anim(me); + midend_free_game(me); + + assert(me->nstates == 0); + + if (me->genmode == GOT_DESC) { + me->genmode = GOT_NOTHING; + } else { + random_state *rs; + + if (me->genmode == GOT_SEED) { + me->genmode = GOT_NOTHING; + } else { + /* + * Generate a new random seed. 15 digits comes to about + * 48 bits, which should be more than enough. + * + * I'll avoid putting a leading zero on the number, + * just in case it confuses anybody who thinks it's + * processed as an integer rather than a string. + */ + char newseed[16]; + int i; + newseed[15] = '\0'; + newseed[0] = '1' + (char)random_upto(me->random, 9); + for (i = 1; i < 15; i++) + newseed[i] = '0' + (char)random_upto(me->random, 10); + sfree(me->seedstr); + me->seedstr = dupstr(newseed); + + if (me->curparams) + me->ourgame->free_params(me->curparams); + me->curparams = me->ourgame->dup_params(me->params); + } + + sfree(me->desc); + sfree(me->privdesc); + sfree(me->aux_info); + me->aux_info = NULL; + + rs = random_new(me->seedstr, strlen(me->seedstr)); + /* + * If this midend has been instantiated without providing a + * drawing API, it is non-interactive. This means that it's + * being used for bulk game generation, and hence we should + * pass the non-interactive flag to new_desc. + */ + me->desc = me->ourgame->new_desc(me->curparams, rs, + &me->aux_info, (me->drawing != NULL)); + me->privdesc = NULL; + random_free(rs); + } + + ensure(me); + + /* + * It might seem a bit odd that we're using me->params to + * create the initial game state, rather than me->curparams + * which is better tailored to this specific game and which we + * always know. + * + * It's supposed to be an invariant in the midend that + * me->params and me->curparams differ in no aspect that is + * important after generation (i.e. after new_desc()). By + * deliberately passing the _less_ specific of these two + * parameter sets, we provoke play-time misbehaviour in the + * case where a game has failed to encode a play-time parameter + * in the non-full version of encode_params(). + */ + me->states[me->nstates].state = + me->ourgame->new_game(me, me->params, me->desc); + + /* + * As part of our commitment to self-testing, test the aux + * string to make sure nothing ghastly went wrong. + */ + if (me->ourgame->can_solve && me->aux_info) { + game_state *s; + char *msg, *movestr; + + msg = NULL; + movestr = me->ourgame->solve(me->states[0].state, + me->states[0].state, + me->aux_info, &msg); + assert(movestr && !msg); + s = me->ourgame->execute_move(me->states[0].state, movestr); + assert(s); + me->ourgame->free_game(s); + sfree(movestr); + } + + me->states[me->nstates].movestr = NULL; + me->states[me->nstates].movetype = NEWGAME; + me->nstates++; + me->statepos = 1; + me->drawstate = me->ourgame->new_drawstate(me->drawing, + me->states[0].state); + midend_size_new_drawstate(me); + me->elapsed = 0.0F; + me->flash_pos = me->flash_time = 0.0F; + me->anim_pos = me->anim_time = 0.0F; + if (me->ui) + me->ourgame->free_ui(me->ui); + me->ui = me->ourgame->new_ui(me->states[0].state); + midend_set_timer(me); + me->pressed_mouse_button = 0; + + if (me->game_id_change_notify_function) + me->game_id_change_notify_function(me->game_id_change_notify_ctx); +} + +int midend_can_undo(midend *me) +{ + return (me->statepos > 1); +} + +int midend_can_redo(midend *me) +{ + return (me->statepos < me->nstates); +} + +static int midend_undo(midend *me) +{ + if (me->statepos > 1) { + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-1].state, + me->states[me->statepos-2].state); + me->statepos--; + me->dir = -1; + return 1; + } else + return 0; +} + +static int midend_redo(midend *me) +{ + if (me->statepos < me->nstates) { + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-1].state, + me->states[me->statepos].state); + me->statepos++; + me->dir = +1; + return 1; + } else + return 0; +} + +static void midend_finish_move(midend *me) +{ + float flashtime; + + /* + * We do not flash if the later of the two states is special. + * This covers both forward Solve moves and backward (undone) + * Restart moves. + */ + if ((me->oldstate || me->statepos > 1) && + ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) || + (me->dir < 0 && me->statepos < me->nstates && + !special(me->states[me->statepos].movetype)))) { + flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate : + me->states[me->statepos-2].state, + me->states[me->statepos-1].state, + me->oldstate ? me->dir : +1, + me->ui); + if (flashtime > 0) { + me->flash_pos = 0.0F; + me->flash_time = flashtime; + } + } + + if (me->oldstate) + me->ourgame->free_game(me->oldstate); + me->oldstate = NULL; + me->anim_pos = me->anim_time = 0; + me->dir = 0; + + midend_set_timer(me); +} + +void midend_stop_anim(midend *me) +{ + if (me->oldstate || me->anim_time != 0) { + midend_finish_move(me); + midend_redraw(me); + } +} + +void midend_restart_game(midend *me) +{ + game_state *s; + + assert(me->statepos >= 1); + if (me->statepos == 1) + return; /* no point doing anything at all! */ + + /* + * During restart, we reconstruct the game from the (public) + * game description rather than from states[0], because that + * way Mines gets slightly more sensible behaviour (restart + * goes to _after_ the first click so you don't have to + * remember where you clicked). + */ + s = me->ourgame->new_game(me, me->params, me->desc); + + /* + * Now enter the restarted state as the next move. + */ + midend_stop_anim(me); + midend_purge_states(me); + ensure(me); + me->states[me->nstates].state = s; + me->states[me->nstates].movestr = dupstr(me->desc); + me->states[me->nstates].movetype = RESTART; + me->statepos = ++me->nstates; + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-2].state, + me->states[me->statepos-1].state); + me->flash_pos = me->flash_time = 0.0F; + midend_finish_move(me); + midend_redraw(me); + midend_set_timer(me); +} + +static int midend_really_process_key(midend *me, int x, int y, int button) +{ + game_state *oldstate = + me->ourgame->dup_game(me->states[me->statepos - 1].state); + int type = MOVE, gottype = FALSE, ret = 1; + float anim_time; + game_state *s; + char *movestr; + + movestr = + me->ourgame->interpret_move(me->states[me->statepos-1].state, + me->ui, me->drawstate, x, y, button); + + if (!movestr) { + if (button == 'n' || button == 'N' || button == '\x0E') { + midend_new_game(me); + midend_redraw(me); + goto done; /* never animate */ + } else if (button == 'u' || button == 'U' || + button == '\x1A' || button == '\x1F') { + midend_stop_anim(me); + type = me->states[me->statepos-1].movetype; + gottype = TRUE; + if (!midend_undo(me)) + goto done; + } else if (button == 'r' || button == 'R' || + button == '\x12' || button == '\x19') { + midend_stop_anim(me); + if (!midend_redo(me)) + goto done; + } else if (button == '\x13' && me->ourgame->can_solve) { + if (midend_solve(me)) + goto done; + } else if (button == 'q' || button == 'Q' || button == '\x11') { + ret = 0; + goto done; + } else + goto done; + } else { + if (!*movestr) + s = me->states[me->statepos-1].state; + else { + s = me->ourgame->execute_move(me->states[me->statepos-1].state, + movestr); + assert(s != NULL); + } + + if (s == me->states[me->statepos-1].state) { + /* + * make_move() is allowed to return its input state to + * indicate that although no move has been made, the UI + * state has been updated and a redraw is called for. + */ + midend_redraw(me); + midend_set_timer(me); + goto done; + } else if (s) { + midend_stop_anim(me); + midend_purge_states(me); + ensure(me); + assert(movestr != NULL); + me->states[me->nstates].state = s; + me->states[me->nstates].movestr = movestr; + me->states[me->nstates].movetype = MOVE; + me->statepos = ++me->nstates; + me->dir = +1; + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-2].state, + me->states[me->statepos-1].state); + } else { + goto done; + } + } + + if (!gottype) + type = me->states[me->statepos-1].movetype; + + /* + * See if this move requires an animation. + */ + if (special(type) && !(type == SOLVE && + (me->ourgame->flags & SOLVE_ANIMATES))) { + anim_time = 0; + } else { + anim_time = me->ourgame->anim_length(oldstate, + me->states[me->statepos-1].state, + me->dir, me->ui); + } + + me->oldstate = oldstate; oldstate = NULL; + if (anim_time > 0) { + me->anim_time = anim_time; + } else { + me->anim_time = 0.0; + midend_finish_move(me); + } + me->anim_pos = 0.0; + + midend_redraw(me); + + midend_set_timer(me); + + done: + if (oldstate) me->ourgame->free_game(oldstate); + return ret; +} + +int midend_process_key(midend *me, int x, int y, int button) +{ + int ret = 1; + + /* + * Harmonise mouse drag and release messages. + * + * Some front ends might accidentally switch from sending, say, + * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a + * drag. (This can happen on the Mac, for example, since + * RIGHT_DRAG is usually done using Command+drag, and if the + * user accidentally releases Command half way through the drag + * then there will be trouble.) + * + * It would be an O(number of front ends) annoyance to fix this + * in the front ends, but an O(number of back ends) annoyance + * to have each game capable of dealing with it. Therefore, we + * fix it _here_ in the common midend code so that it only has + * to be done once. + * + * The possible ways in which things can go screwy in the front + * end are: + * + * - in a system containing multiple physical buttons button + * presses can inadvertently overlap. We can see ABab (caps + * meaning button-down and lowercase meaning button-up) when + * the user had semantically intended AaBb. + * + * - in a system where one button is simulated by means of a + * modifier key and another button, buttons can mutate + * between press and release (possibly during drag). So we + * can see Ab instead of Aa. + * + * Definite requirements are: + * + * - button _presses_ must never be invented or destroyed. If + * the user presses two buttons in succession, the button + * presses must be transferred to the backend unchanged. So + * if we see AaBb , that's fine; if we see ABab (the button + * presses inadvertently overlapped) we must somehow + * `correct' it to AaBb. + * + * - every mouse action must end up looking like a press, zero + * or more drags, then a release. This allows back ends to + * make the _assumption_ that incoming mouse data will be + * sane in this regard, and not worry about the details. + * + * So my policy will be: + * + * - treat any button-up as a button-up for the currently + * pressed button, or ignore it if there is no currently + * pressed button. + * + * - treat any drag as a drag for the currently pressed + * button, or ignore it if there is no currently pressed + * button. + * + * - if we see a button-down while another button is currently + * pressed, invent a button-up for the first one and then + * pass the button-down through as before. + * + * 2005-05-31: An addendum to the above. Some games might want + * a `priority order' among buttons, such that if one button is + * pressed while another is down then a fixed one of the + * buttons takes priority no matter what order they're pressed + * in. Mines, in particular, wants to treat a left+right click + * like a left click for the benefit of users of other + * implementations. So the last of the above points is modified + * in the presence of an (optional) button priority order. + * + * A further addition: we translate certain keyboard presses to + * cursor key 'select' buttons, so that a) frontends don't have + * to translate these themselves (like they do for CURSOR_UP etc), + * and b) individual games don't have to hard-code button presses + * of '\n' etc for keyboard-based cursors. The choice of buttons + * here could eventually be controlled by a runtime configuration + * option. + */ + if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) { + if (me->pressed_mouse_button) { + if (IS_MOUSE_DRAG(button)) { + button = me->pressed_mouse_button + + (LEFT_DRAG - LEFT_BUTTON); + } else { + button = me->pressed_mouse_button + + (LEFT_RELEASE - LEFT_BUTTON); + } + } else + return ret; /* ignore it */ + } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) { + /* + * If the new button has lower priority than the old one, + * don't bother doing this. + */ + if (me->ourgame->flags & + BUTTON_BEATS(me->pressed_mouse_button, button)) + return ret; /* just ignore it */ + + /* + * Fabricate a button-up for the previously pressed button. + */ + ret = ret && midend_really_process_key + (me, x, y, (me->pressed_mouse_button + + (LEFT_RELEASE - LEFT_BUTTON))); + } + + /* + * Translate keyboard presses to cursor selection. + */ + if (button == '\n' || button == '\r') + button = CURSOR_SELECT; + if (button == ' ') + button = CURSOR_SELECT2; + + /* + * Normalise both backspace characters (8 and 127) to \b. Easier + * to do this once, here, than to require all front ends to + * carefully generate the same one - now each front end can + * generate whichever is easiest. + */ + if (button == '\177') + button = '\b'; + + /* + * Now send on the event we originally received. + */ + ret = ret && midend_really_process_key(me, x, y, button); + + /* + * And update the currently pressed button. + */ + if (IS_MOUSE_RELEASE(button)) + me->pressed_mouse_button = 0; + else if (IS_MOUSE_DOWN(button)) + me->pressed_mouse_button = button; + + return ret; +} + +void midend_redraw(midend *me) +{ + assert(me->drawing); + + if (me->statepos > 0 && me->drawstate) { + start_draw(me->drawing); + if (me->oldstate && me->anim_time > 0 && + me->anim_pos < me->anim_time) { + assert(me->dir != 0); + me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate, + me->states[me->statepos-1].state, me->dir, + me->ui, me->anim_pos, me->flash_pos); + } else { + me->ourgame->redraw(me->drawing, me->drawstate, NULL, + me->states[me->statepos-1].state, +1 /*shrug*/, + me->ui, 0.0, me->flash_pos); + } + end_draw(me->drawing); + } +} + +/* + * Nasty hacky function used to implement the --redo option in + * gtk.c. Only used for generating the puzzles' icons. + */ +void midend_freeze_timer(midend *me, float tprop) +{ + me->anim_pos = me->anim_time * tprop; + midend_redraw(me); + deactivate_timer(me->frontend); +} + +void midend_timer(midend *me, float tplus) +{ + int need_redraw = (me->anim_time > 0 || me->flash_time > 0); + + me->anim_pos += tplus; + if (me->anim_pos >= me->anim_time || + me->anim_time == 0 || !me->oldstate) { + if (me->anim_time > 0) + midend_finish_move(me); + } + + me->flash_pos += tplus; + if (me->flash_pos >= me->flash_time || me->flash_time == 0) { + me->flash_pos = me->flash_time = 0; + } + + if (need_redraw) + midend_redraw(me); + + if (me->timing) { + float oldelapsed = me->elapsed; + me->elapsed += tplus; + if ((int)oldelapsed != (int)me->elapsed) + status_bar(me->drawing, me->laststatus ? me->laststatus : ""); + } + + midend_set_timer(me); +} + +float *midend_colours(midend *me, int *ncolours) +{ + float *ret; + + ret = me->ourgame->colours(me->frontend, ncolours); + + { + int i; + + /* + * Allow environment-based overrides for the standard + * colours by defining variables along the lines of + * `NET_COLOUR_4=6000c0'. + */ + + for (i = 0; i < *ncolours; i++) { + char buf[80], *e; + unsigned int r, g, b; + int j, k; + + sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + if ((e = getenv(buf)) != NULL && + sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) { + ret[i*3 + 0] = r / 255.0F; + ret[i*3 + 1] = g / 255.0F; + ret[i*3 + 2] = b / 255.0F; + } + } + } + + return ret; +} + +int midend_num_presets(midend *me) +{ + if (!me->npresets) { + char *name; + game_params *preset; + + while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) { + if (me->presetsize <= me->npresets) { + me->presetsize = me->npresets + 10; + me->presets = sresize(me->presets, me->presetsize, + game_params *); + me->preset_names = sresize(me->preset_names, me->presetsize, + char *); + me->preset_encodings = sresize(me->preset_encodings, + me->presetsize, char *); + } + + me->presets[me->npresets] = preset; + me->preset_names[me->npresets] = name; + me->preset_encodings[me->npresets] = + me->ourgame->encode_params(preset, TRUE);; + me->npresets++; + } + } + + { + /* + * Allow environment-based extensions to the preset list by + * defining a variable along the lines of `SOLO_PRESETS=2x3 + * Advanced:2x3da'. Colon-separated list of items, + * alternating between textual titles in the menu and + * encoded parameter strings. + */ + char buf[80], *e, *p; + int j, k; + + sprintf(buf, "%s_PRESETS", me->ourgame->name); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + + if ((e = getenv(buf)) != NULL) { + p = e = dupstr(e); + + while (*p) { + char *name, *val; + game_params *preset; + + name = p; + while (*p && *p != ':') p++; + if (*p) *p++ = '\0'; + val = p; + while (*p && *p != ':') p++; + if (*p) *p++ = '\0'; + + preset = me->ourgame->default_params(); + me->ourgame->decode_params(preset, val); + + if (me->ourgame->validate_params(preset, TRUE)) { + /* Drop this one from the list. */ + me->ourgame->free_params(preset); + continue; + } + + if (me->presetsize <= me->npresets) { + me->presetsize = me->npresets + 10; + me->presets = sresize(me->presets, me->presetsize, + game_params *); + me->preset_names = sresize(me->preset_names, + me->presetsize, char *); + me->preset_encodings = sresize(me->preset_encodings, + me->presetsize, char *); + } + + me->presets[me->npresets] = preset; + me->preset_names[me->npresets] = dupstr(name); + me->preset_encodings[me->npresets] = + me->ourgame->encode_params(preset, TRUE); + me->npresets++; + } + sfree(e); + } + } + + return me->npresets; +} + +void midend_fetch_preset(midend *me, int n, + char **name, game_params **params) +{ + assert(n >= 0 && n < me->npresets); + *name = me->preset_names[n]; + *params = me->presets[n]; +} + +int midend_which_preset(midend *me) +{ + char *encoding = me->ourgame->encode_params(me->params, TRUE); + int i, ret; + + ret = -1; + for (i = 0; i < me->npresets; i++) + if (!strcmp(encoding, me->preset_encodings[i])) { + ret = i; + break; + } + + sfree(encoding); + return ret; +} + +int midend_wants_statusbar(midend *me) +{ + return me->ourgame->wants_statusbar; +} + +void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx) +{ + me->game_id_change_notify_function = notify; + me->game_id_change_notify_ctx = ctx; +} + +void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) +{ + sfree(me->desc); + sfree(me->privdesc); + me->desc = dupstr(desc); + me->privdesc = privdesc ? dupstr(privdesc) : NULL; + if (me->game_id_change_notify_function) + me->game_id_change_notify_function(me->game_id_change_notify_ctx); +} + +config_item *midend_get_config(midend *me, int which, char **wintitle) +{ + char *titlebuf, *parstr, *rest; + config_item *ret; + char sep; + + assert(wintitle); + titlebuf = snewn(40 + strlen(me->ourgame->name), char); + + switch (which) { + case CFG_SETTINGS: + sprintf(titlebuf, "%s configuration", me->ourgame->name); + *wintitle = titlebuf; + return me->ourgame->configure(me->params); + case CFG_SEED: + case CFG_DESC: + if (!me->curparams) { + sfree(titlebuf); + return NULL; + } + sprintf(titlebuf, "%s %s selection", me->ourgame->name, + which == CFG_SEED ? "random" : "game"); + *wintitle = titlebuf; + + ret = snewn(2, config_item); + + ret[0].type = C_STRING; + if (which == CFG_SEED) + ret[0].name = "Game random seed"; + else + ret[0].name = "Game ID"; + ret[0].ival = 0; + /* + * For CFG_DESC the text going in here will be a string + * encoding of the restricted parameters, plus a colon, + * plus the game description. For CFG_SEED it will be the + * full parameters, plus a hash, plus the random seed data. + * Either of these is a valid full game ID (although only + * the former is likely to persist across many code + * changes). + */ + parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED); + assert(parstr); + if (which == CFG_DESC) { + rest = me->desc ? me->desc : ""; + sep = ':'; + } else { + rest = me->seedstr ? me->seedstr : ""; + sep = '#'; + } + ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char); + sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest); + sfree(parstr); + + ret[1].type = C_END; + ret[1].name = ret[1].sval = NULL; + ret[1].ival = 0; + + return ret; + } + + assert(!"We shouldn't be here"); + return NULL; +} + +static char *midend_game_id_int(midend *me, char *id, int defmode) +{ + char *error, *par, *desc, *seed; + game_params *newcurparams, *newparams, *oldparams1, *oldparams2; + int free_params; + + seed = strchr(id, '#'); + desc = strchr(id, ':'); + + if (desc && (!seed || desc < seed)) { + /* + * We have a colon separating parameters from game + * description. So `par' now points to the parameters + * string, and `desc' to the description string. + */ + *desc++ = '\0'; + par = id; + seed = NULL; + } else if (seed && (!desc || seed < desc)) { + /* + * We have a hash separating parameters from random seed. + * So `par' now points to the parameters string, and `seed' + * to the seed string. + */ + *seed++ = '\0'; + par = id; + desc = NULL; + } else { + /* + * We only have one string. Depending on `defmode', we take + * it to be either parameters, seed or description. + */ + if (defmode == DEF_SEED) { + seed = id; + par = desc = NULL; + } else if (defmode == DEF_DESC) { + desc = id; + par = seed = NULL; + } else { + par = id; + seed = desc = NULL; + } + } + + /* + * We must be reasonably careful here not to modify anything in + * `me' until we have finished validating things. This function + * must either return an error and do nothing to the midend, or + * return success and do everything; nothing in between is + * acceptable. + */ + newcurparams = newparams = oldparams1 = oldparams2 = NULL; + + if (par) { + /* + * The params string may underspecify the game parameters, so + * we must first initialise newcurparams with a full set of + * params from somewhere else before we decode_params the + * input string over the top. + * + * But which set? It depends on what other data we have. + * + * If we've been given a _descriptive_ game id, then that may + * well underspecify by design, e.g. Solo game descriptions + * often start just '3x3:' without specifying one of Solo's + * difficulty settings, because it isn't necessary once a game + * has been generated (and you might not even know it, if + * you're manually transcribing a game description). In that + * situation, I've always felt that the best thing to set the + * difficulty to (for use if the user hits 'New Game' after + * pasting in that game id) is whatever it was previously set + * to. That is, we use whatever is already in me->params as + * the basis for our decoding of this input string. + * + * A random-seed based game id, however, should use the real, + * built-in default params, and not even check the + * _DEFAULT environment setting, because when people + * paste each other random seeds - whether it's two users + * arranging to generate the same game at the same time to + * race solving them, or a user sending a bug report upstream + * - the whole point is for the random game id to always be + * interpreted the same way, even if it does underspecify. + * + * A parameter string typed in on its own, with no seed _or_ + * description, gets treated the same way as a random seed, + * because again I think the most likely reason for doing that + * is to have a portable representation of a set of params. + */ + if (desc) { + newcurparams = me->ourgame->dup_params(me->params); + } else { + newcurparams = me->ourgame->default_params(); + } + me->ourgame->decode_params(newcurparams, par); + error = me->ourgame->validate_params(newcurparams, desc == NULL); + if (error) { + me->ourgame->free_params(newcurparams); + return error; + } + oldparams1 = me->curparams; + + /* + * Now filter only the persistent parts of this state into + * the long-term params structure, unless we've _only_ + * received a params string in which case the whole lot is + * persistent. + */ + oldparams2 = me->params; + if (seed || desc) { + char *tmpstr; + + newparams = me->ourgame->dup_params(me->params); + + tmpstr = me->ourgame->encode_params(newcurparams, FALSE); + me->ourgame->decode_params(newparams, tmpstr); + + sfree(tmpstr); + } else { + newparams = me->ourgame->dup_params(newcurparams); + } + free_params = TRUE; + } else { + newcurparams = me->curparams; + newparams = me->params; + free_params = FALSE; + } + + if (desc) { + error = me->ourgame->validate_desc(newparams, desc); + if (error) { + if (free_params) { + if (newcurparams) + me->ourgame->free_params(newcurparams); + if (newparams) + me->ourgame->free_params(newparams); + } + return error; + } + } + + /* + * Now we've got past all possible error points. Update the + * midend itself. + */ + me->params = newparams; + me->curparams = newcurparams; + if (oldparams1) + me->ourgame->free_params(oldparams1); + if (oldparams2) + me->ourgame->free_params(oldparams2); + + sfree(me->desc); + sfree(me->privdesc); + me->desc = me->privdesc = NULL; + sfree(me->seedstr); + me->seedstr = NULL; + + if (desc) { + me->desc = dupstr(desc); + me->genmode = GOT_DESC; + sfree(me->aux_info); + me->aux_info = NULL; + } + + if (seed) { + me->seedstr = dupstr(seed); + me->genmode = GOT_SEED; + } + + return NULL; +} + +char *midend_game_id(midend *me, char *id) +{ + return midend_game_id_int(me, id, DEF_PARAMS); +} + +char *midend_get_game_id(midend *me) +{ + char *parstr, *ret; + + parstr = me->ourgame->encode_params(me->curparams, FALSE); + assert(parstr); + assert(me->desc); + ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char); + sprintf(ret, "%s:%s", parstr, me->desc); + sfree(parstr); + return ret; +} + +char *midend_get_random_seed(midend *me) +{ + char *parstr, *ret; + + if (!me->seedstr) + return NULL; + + parstr = me->ourgame->encode_params(me->curparams, TRUE); + assert(parstr); + ret = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char); + sprintf(ret, "%s#%s", parstr, me->seedstr); + sfree(parstr); + return ret; +} + +char *midend_set_config(midend *me, int which, config_item *cfg) +{ + char *error; + game_params *params; + + switch (which) { + case CFG_SETTINGS: + params = me->ourgame->custom_params(cfg); + error = me->ourgame->validate_params(params, TRUE); + + if (error) { + me->ourgame->free_params(params); + return error; + } + + me->ourgame->free_params(me->params); + me->params = params; + break; + + case CFG_SEED: + case CFG_DESC: + error = midend_game_id_int(me, cfg[0].sval, + (which == CFG_SEED ? DEF_SEED : DEF_DESC)); + if (error) + return error; + break; + } + + return NULL; +} + +int midend_can_format_as_text_now(midend *me) +{ + if (me->ourgame->can_format_as_text_ever) + return me->ourgame->can_format_as_text_now(me->params); + else + return FALSE; +} + +char *midend_text_format(midend *me) +{ + if (me->ourgame->can_format_as_text_ever && me->statepos > 0 && + me->ourgame->can_format_as_text_now(me->params)) + return me->ourgame->text_format(me->states[me->statepos-1].state); + else + return NULL; +} + +char *midend_solve(midend *me) +{ + game_state *s; + char *msg, *movestr; + + if (!me->ourgame->can_solve) + return "This game does not support the Solve operation"; + + if (me->statepos < 1) + return "No game set up to solve"; /* _shouldn't_ happen! */ + + msg = NULL; + movestr = me->ourgame->solve(me->states[0].state, + me->states[me->statepos-1].state, + me->aux_info, &msg); + if (!movestr) { + if (!msg) + msg = "Solve operation failed"; /* _shouldn't_ happen, but can */ + return msg; + } + s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr); + assert(s); + + /* + * Now enter the solved state as the next move. + */ + midend_stop_anim(me); + midend_purge_states(me); + ensure(me); + me->states[me->nstates].state = s; + me->states[me->nstates].movestr = movestr; + me->states[me->nstates].movetype = SOLVE; + me->statepos = ++me->nstates; + if (me->ui) + me->ourgame->changed_state(me->ui, + me->states[me->statepos-2].state, + me->states[me->statepos-1].state); + me->dir = +1; + if (me->ourgame->flags & SOLVE_ANIMATES) { + me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state); + me->anim_time = + me->ourgame->anim_length(me->states[me->statepos-2].state, + me->states[me->statepos-1].state, + +1, me->ui); + me->anim_pos = 0.0; + } else { + me->anim_time = 0.0; + midend_finish_move(me); + } + if (me->drawing) + midend_redraw(me); + midend_set_timer(me); + return NULL; +} + +int midend_status(midend *me) +{ + /* + * We should probably never be called when the state stack has no + * states on it at all - ideally, midends should never be left in + * that state for long enough to get put down and forgotten about. + * But if we are, I think we return _true_ - pedantically speaking + * a midend in that state is 'vacuously solved', and more + * practically, a user whose midend has been left in that state + * probably _does_ want the 'new game' option to be prominent. + */ + if (me->statepos == 0) + return +1; + + return me->ourgame->status(me->states[me->statepos-1].state); +} + +char *midend_rewrite_statusbar(midend *me, char *text) +{ + /* + * An important special case is that we are occasionally called + * with our own laststatus, to update the timer. + */ + if (me->laststatus != text) { + sfree(me->laststatus); + me->laststatus = dupstr(text); + } + + if (me->ourgame->is_timed) { + char timebuf[100], *ret; + int min, sec; + + sec = (int)me->elapsed; + min = sec / 60; + sec %= 60; + sprintf(timebuf, "[%d:%02d] ", min, sec); + + ret = snewn(strlen(timebuf) + strlen(text) + 1, char); + strcpy(ret, timebuf); + strcat(ret, text); + return ret; + + } else { + return dupstr(text); + } +} + +#define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection" +#define SERIALISE_VERSION "1" + +/* rockbox kludge */ +static void copy_left_justified(char *buf, size_t sz, const char *str) +{ + memset(buf, ' ', sz - 1); + int len = strlen(str); + if(len <= sz - 1) + memcpy(buf, str, len); + else + fatal("overrun"); + buf[sz - 1] = 0; +} + +void midend_serialise(midend *me, + void (*write)(void *ctx, void *buf, int len), + void *wctx) +{ + int i; + + /* + * Each line of the save file contains three components. First + * exactly 8 characters of header word indicating what type of + * data is contained on the line; then a colon followed by a + * decimal integer giving the length of the main string on the + * line; then a colon followed by the string itself (exactly as + * many bytes as previously specified, no matter what they + * contain). Then a newline (of reasonably flexible form). + */ +#define wr(h,s) do { \ + char hbuf[80]; \ + char *str = (s); \ + char lbuf[9]; \ + copy_left_justified(lbuf, sizeof(lbuf), h); \ + sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \ + write(wctx, hbuf, strlen(hbuf)); \ + write(wctx, str, strlen(str)); \ + write(wctx, "\n", 1); \ +} while (0) + + /* + * Magic string identifying the file, and version number of the + * file format. + */ + wr("SAVEFILE", SERIALISE_MAGIC); + wr("VERSION", SERIALISE_VERSION); + + /* + * The game name. (Copied locally to avoid const annoyance.) + */ + { + char *s = dupstr(me->ourgame->name); + wr("GAME", s); + sfree(s); + } + + /* + * The current long-term parameters structure, in full. + */ + if (me->params) { + char *s = me->ourgame->encode_params(me->params, TRUE); + wr("PARAMS", s); + sfree(s); + } + + /* + * The current short-term parameters structure, in full. + */ + if (me->curparams) { + char *s = me->ourgame->encode_params(me->curparams, TRUE); + wr("CPARAMS", s); + sfree(s); + } + + /* + * The current game description, the privdesc, and the random seed. + */ + if (me->seedstr) + wr("SEED", me->seedstr); + if (me->desc) + wr("DESC", me->desc); + if (me->privdesc) + wr("PRIVDESC", me->privdesc); + + /* + * The game's aux_info. We obfuscate this to prevent spoilers + * (people are likely to run `head' or similar on a saved game + * file simply to find out what it is, and don't necessarily + * want to be told the answer to the puzzle!) + */ + if (me->aux_info) { + unsigned char *s1; + char *s2; + int len; + + len = strlen(me->aux_info); + s1 = snewn(len, unsigned char); + memcpy(s1, me->aux_info, len); + obfuscate_bitmap(s1, len*8, FALSE); + s2 = bin2hex(s1, len); + + wr("AUXINFO", s2); + + sfree(s2); + sfree(s1); + } + + /* + * Any required serialisation of the game_ui. + */ + if (me->ui) { + char *s = me->ourgame->encode_ui(me->ui); + if (s) { + wr("UI", s); + sfree(s); + } + } + + /* + * The game time, if it's a timed game. + */ + if (me->ourgame->is_timed) { + char buf[80]; + sprintf(buf, "%g", me->elapsed); + wr("TIME", buf); + } + + /* + * The length of, and position in, the states list. + */ + { + char buf[80]; + sprintf(buf, "%d", me->nstates); + wr("NSTATES", buf); + sprintf(buf, "%d", me->statepos); + wr("STATEPOS", buf); + } + + /* + * For each state after the initial one (which we know is + * constructed from either privdesc or desc), enough + * information for execute_move() to reconstruct it from the + * previous one. + */ + for (i = 1; i < me->nstates; i++) { + assert(me->states[i].movetype != NEWGAME); /* only state 0 */ + switch (me->states[i].movetype) { + case MOVE: + wr("MOVE", me->states[i].movestr); + break; + case SOLVE: + wr("SOLVE", me->states[i].movestr); + break; + case RESTART: + wr("RESTART", me->states[i].movestr); + break; + } + } + +#undef wr +} + +/* + * This function returns NULL on success, or an error message. + */ +char *midend_deserialise(midend *me, + int (*read)(void *ctx, void *buf, int len), + void *rctx) +{ + int nstates = 0, statepos = -1, gotstates = 0; + int started = FALSE; + int i; + + char *val = NULL; + /* Initially all errors give the same report */ + char *ret = "Data does not appear to be a saved game file"; + + /* + * We construct all the new state in local variables while we + * check its sanity. Only once we have finished reading the + * serialised data and detected no errors at all do we start + * modifying stuff in the midend passed in. + */ + char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL; + char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL; + float elapsed = 0.0F; + game_params *params = NULL, *cparams = NULL; + game_ui *ui = NULL; + struct midend_state_entry *states = NULL; + + /* + * Loop round and round reading one key/value pair at a time + * from the serialised stream, until we have enough game states + * to finish. + */ + while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { + char key[9], c; + int len; + + do { + if (!read(rctx, key, 1)) { + /* unexpected EOF */ + goto cleanup; + } + } while (key[0] == '\r' || key[0] == '\n'); + + if (!read(rctx, key+1, 8)) { + /* unexpected EOF */ + goto cleanup; + } + + if (key[8] != ':') { + if (started) + ret = "Data was incorrectly formatted for a saved game file"; + goto cleanup; + } + len = strcspn(key, ": "); + assert(len <= 8); + key[len] = '\0'; + + len = 0; + while (1) { + if (!read(rctx, &c, 1)) { + /* unexpected EOF */ + goto cleanup; + } + + if (c == ':') { + break; + } else if (c >= '0' && c <= '9') { + len = (len * 10) + (c - '0'); + } else { + if (started) + ret = "Data was incorrectly formatted for a" + " saved game file"; + goto cleanup; + } + } + + val = snewn(len+1, char); + if (!read(rctx, val, len)) { + if (started) + goto cleanup; + } + val[len] = '\0'; + + if (!started) { + if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) { + /* ret already has the right message in it */ + goto cleanup; + } + /* Now most errors are this one, unless otherwise specified */ + ret = "Saved data ended unexpectedly"; + started = TRUE; + } else { + if (!strcmp(key, "VERSION")) { + if (strcmp(val, SERIALISE_VERSION)) { + ret = "Cannot handle this version of the saved game" + " file format"; + goto cleanup; + } + } else if (!strcmp(key, "GAME")) { + if (strcmp(val, me->ourgame->name)) { + ret = "Save file is from a different game"; + goto cleanup; + } + } else if (!strcmp(key, "PARAMS")) { + sfree(parstr); + parstr = val; + val = NULL; + } else if (!strcmp(key, "CPARAMS")) { + sfree(cparstr); + cparstr = val; + val = NULL; + } else if (!strcmp(key, "SEED")) { + sfree(seed); + seed = val; + val = NULL; + } else if (!strcmp(key, "DESC")) { + sfree(desc); + desc = val; + val = NULL; + } else if (!strcmp(key, "PRIVDESC")) { + sfree(privdesc); + privdesc = val; + val = NULL; + } else if (!strcmp(key, "AUXINFO")) { + unsigned char *tmp; + int len = strlen(val) / 2; /* length in bytes */ + tmp = hex2bin(val, len); + obfuscate_bitmap(tmp, len*8, TRUE); + + sfree(auxinfo); + auxinfo = snewn(len + 1, char); + memcpy(auxinfo, tmp, len); + auxinfo[len] = '\0'; + sfree(tmp); + } else if (!strcmp(key, "UI")) { + sfree(uistr); + uistr = val; + val = NULL; + } else if (!strcmp(key, "TIME")) { + elapsed = (float)atof(val); + } else if (!strcmp(key, "NSTATES")) { + nstates = atoi(val); + if (nstates <= 0) { + ret = "Number of states in save file was negative"; + goto cleanup; + } + if (states) { + ret = "Two state counts provided in save file"; + goto cleanup; + } + states = snewn(nstates, struct midend_state_entry); + for (i = 0; i < nstates; i++) { + states[i].state = NULL; + states[i].movestr = NULL; + states[i].movetype = NEWGAME; + } + } else if (!strcmp(key, "STATEPOS")) { + statepos = atoi(val); + } else if (!strcmp(key, "MOVE")) { + gotstates++; + states[gotstates].movetype = MOVE; + states[gotstates].movestr = val; + val = NULL; + } else if (!strcmp(key, "SOLVE")) { + gotstates++; + states[gotstates].movetype = SOLVE; + states[gotstates].movestr = val; + val = NULL; + } else if (!strcmp(key, "RESTART")) { + gotstates++; + states[gotstates].movetype = RESTART; + states[gotstates].movestr = val; + val = NULL; + } + } + + sfree(val); + val = NULL; + } + + params = me->ourgame->default_params(); + me->ourgame->decode_params(params, parstr); + if (me->ourgame->validate_params(params, TRUE)) { + ret = "Long-term parameters in save file are invalid"; + goto cleanup; + } + cparams = me->ourgame->default_params(); + me->ourgame->decode_params(cparams, cparstr); + if (me->ourgame->validate_params(cparams, FALSE)) { + ret = "Short-term parameters in save file are invalid"; + goto cleanup; + } + if (seed && me->ourgame->validate_params(cparams, TRUE)) { + /* + * The seed's no use with this version, but we can perfectly + * well use the rest of the data. + */ + sfree(seed); + seed = NULL; + } + if (!desc) { + ret = "Game description in save file is missing"; + goto cleanup; + } else if (me->ourgame->validate_desc(params, desc)) { + ret = "Game description in save file is invalid"; + goto cleanup; + } + if (privdesc && me->ourgame->validate_desc(params, privdesc)) { + ret = "Game private description in save file is invalid"; + goto cleanup; + } + if (statepos < 0 || statepos >= nstates) { + ret = "Game position in save file is out of range"; + } + + states[0].state = me->ourgame->new_game(me, params, + privdesc ? privdesc : desc); + for (i = 1; i < nstates; i++) { + assert(states[i].movetype != NEWGAME); + switch (states[i].movetype) { + case MOVE: + case SOLVE: + states[i].state = me->ourgame->execute_move(states[i-1].state, + states[i].movestr); + if (states[i].state == NULL) { + ret = "Save file contained an invalid move"; + goto cleanup; + } + break; + case RESTART: + if (me->ourgame->validate_desc(params, states[i].movestr)) { + ret = "Save file contained an invalid restart move"; + goto cleanup; + } + states[i].state = me->ourgame->new_game(me, params, + states[i].movestr); + break; + } + } + + ui = me->ourgame->new_ui(states[0].state); + me->ourgame->decode_ui(ui, uistr); + + /* + * Now we've run out of possible error conditions, so we're + * ready to start overwriting the real data in the current + * midend. We'll do this by swapping things with the local + * variables, so that the same cleanup code will free the old + * stuff. + */ + { + char *tmp; + + tmp = me->desc; + me->desc = desc; + desc = tmp; + + tmp = me->privdesc; + me->privdesc = privdesc; + privdesc = tmp; + + tmp = me->seedstr; + me->seedstr = seed; + seed = tmp; + + tmp = me->aux_info; + me->aux_info = auxinfo; + auxinfo = tmp; + } + + me->genmode = GOT_NOTHING; + + me->statesize = nstates; + nstates = me->nstates; + me->nstates = me->statesize; + { + struct midend_state_entry *tmp; + tmp = me->states; + me->states = states; + states = tmp; + } + me->statepos = statepos; + + { + game_params *tmp; + + tmp = me->params; + me->params = params; + params = tmp; + + tmp = me->curparams; + me->curparams = cparams; + cparams = tmp; + } + + me->oldstate = NULL; + me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F; + me->dir = 0; + + { + game_ui *tmp; + + tmp = me->ui; + me->ui = ui; + ui = tmp; + } + + me->elapsed = elapsed; + me->pressed_mouse_button = 0; + + midend_set_timer(me); + + if (me->drawstate) + me->ourgame->free_drawstate(me->drawing, me->drawstate); + me->drawstate = + me->ourgame->new_drawstate(me->drawing, + me->states[me->statepos-1].state); + midend_size_new_drawstate(me); + + ret = NULL; /* success! */ + + cleanup: + sfree(val); + sfree(seed); + sfree(parstr); + sfree(cparstr); + sfree(desc); + sfree(privdesc); + sfree(auxinfo); + sfree(uistr); + if (params) + me->ourgame->free_params(params); + if (cparams) + me->ourgame->free_params(cparams); + if (ui) + me->ourgame->free_ui(ui); + if (states) { + int i; + + for (i = 0; i < nstates; i++) { + if (states[i].state) + me->ourgame->free_game(states[i].state); + sfree(states[i].movestr); + } + sfree(states); + } + + return ret; +} + +/* + * This function examines a saved game file just far enough to + * determine which game type it contains. It returns NULL on success + * and the game name string in 'name' (which will be dynamically + * allocated and should be caller-freed), or an error message on + * failure. + */ +char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), + void *rctx) +{ + int nstates = 0, statepos = -1, gotstates = 0; + int started = FALSE; + + char *val = NULL; + /* Initially all errors give the same report */ + char *ret = "Data does not appear to be a saved game file"; + + *name = NULL; + + /* + * Loop round and round reading one key/value pair at a time from + * the serialised stream, until we've found the game name. + */ + while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { + char key[9], c; + int len; + + do { + if (!read(rctx, key, 1)) { + /* unexpected EOF */ + goto cleanup; + } + } while (key[0] == '\r' || key[0] == '\n'); + + if (!read(rctx, key+1, 8)) { + /* unexpected EOF */ + goto cleanup; + } + + if (key[8] != ':') { + if (started) + ret = "Data was incorrectly formatted for a saved game file"; + goto cleanup; + } + len = strcspn(key, ": "); + assert(len <= 8); + key[len] = '\0'; + + len = 0; + while (1) { + if (!read(rctx, &c, 1)) { + /* unexpected EOF */ + goto cleanup; + } + + if (c == ':') { + break; + } else if (c >= '0' && c <= '9') { + len = (len * 10) + (c - '0'); + } else { + if (started) + ret = "Data was incorrectly formatted for a" + " saved game file"; + goto cleanup; + } + } + + val = snewn(len+1, char); + if (!read(rctx, val, len)) { + if (started) + goto cleanup; + } + val[len] = '\0'; + + if (!started) { + if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) { + /* ret already has the right message in it */ + goto cleanup; + } + /* Now most errors are this one, unless otherwise specified */ + ret = "Saved data ended unexpectedly"; + started = TRUE; + } else { + if (!strcmp(key, "VERSION")) { + if (strcmp(val, SERIALISE_VERSION)) { + ret = "Cannot handle this version of the saved game" + " file format"; + goto cleanup; + } + } else if (!strcmp(key, "GAME")) { + *name = dupstr(val); + ret = NULL; + goto cleanup; + } + } + + sfree(val); + val = NULL; + } + + cleanup: + sfree(val); + return ret; +} + +char *midend_print_puzzle(midend *me, document *doc, int with_soln) +{ + game_state *soln = NULL; + + if (me->statepos < 1) + return "No game set up to print";/* _shouldn't_ happen! */ + + if (with_soln) { + char *msg, *movestr; + + if (!me->ourgame->can_solve) + return "This game does not support the Solve operation"; + + msg = "Solve operation failed";/* game _should_ overwrite on error */ + movestr = me->ourgame->solve(me->states[0].state, + me->states[me->statepos-1].state, + me->aux_info, &msg); + if (!movestr) + return msg; + soln = me->ourgame->execute_move(me->states[me->statepos-1].state, + movestr); + assert(soln); + + sfree(movestr); + } else + soln = NULL; + + /* + * This call passes over ownership of the two game_states and + * the game_params. Hence we duplicate the ones we want to + * keep, and we don't have to bother freeing soln if it was + * non-NULL. + */ + document_add_puzzle(doc, me->ourgame, + me->ourgame->dup_params(me->curparams), + me->ourgame->dup_game(me->states[0].state), soln); + + return NULL; +} diff --git a/apps/plugins/puzzles/mines.R b/apps/plugins/puzzles/mines.R new file mode 100644 index 0000000000..275c76c7ad --- /dev/null +++ b/apps/plugins/puzzles/mines.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +MINES_EXTRA = tree234 + +mines : [X] GTK COMMON mines MINES_EXTRA mines-icon|no-icon + +mines : [G] WINDOWS COMMON mines MINES_EXTRA mines.res|noicon.res + +mineobfusc : [U] mines[STANDALONE_OBFUSCATOR] MINES_EXTRA STANDALONE +mineobfusc : [C] mines[STANDALONE_OBFUSCATOR] MINES_EXTRA STANDALONE + +ALL += mines[COMBINED] MINES_EXTRA + +!begin am gtk +GAMES += mines +!end + +!begin >list.c + A(mines) \ +!end + +!begin >gamedesc.txt +mines:mines.exe:Mines:Mine-finding puzzle:Find all the mines without treading on any of them. +!end diff --git a/apps/plugins/puzzles/mines.c b/apps/plugins/puzzles/mines.c new file mode 100644 index 0000000000..deb71aed6b --- /dev/null +++ b/apps/plugins/puzzles/mines.c @@ -0,0 +1,3250 @@ +/* + * mines.c: Minesweeper clone with sophisticated grid generation. + * + * Still TODO: + * + * - think about configurably supporting question marks. Once, + * that is, we've thought about configurability in general! + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "tree234.h" +#include "puzzles.h" + +enum { + COL_BACKGROUND, COL_BACKGROUND2, + COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, + COL_MINE, COL_BANG, COL_CROSS, COL_FLAG, COL_FLAGBASE, COL_QUERY, + COL_HIGHLIGHT, COL_LOWLIGHT, + COL_WRONGNUMBER, + COL_CURSOR, + NCOLOURS +}; + +#define PREFERRED_TILE_SIZE 20 +#define TILE_SIZE (ds->tilesize) +#ifdef SMALL_SCREEN +#define BORDER 8 +#else +#define BORDER (TILE_SIZE * 3 / 2) +#endif +#define HIGHLIGHT_WIDTH (TILE_SIZE / 10) +#define OUTER_HIGHLIGHT_WIDTH (BORDER / 10) +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define FLASH_FRAME 0.13F + +struct game_params { + int w, h, n; + int unique; +}; + +struct mine_layout { + /* + * This structure is shared between all the game_states for a + * given instance of the puzzle, so we reference-count it. + */ + int refcount; + char *mines; + /* + * If we haven't yet actually generated the mine layout, here's + * all the data we will need to do so. + */ + int n, unique; + random_state *rs; + midend *me; /* to give back the new game desc */ +}; + +struct game_state { + int w, h, n, dead, won; + int used_solve; + struct mine_layout *layout; /* real mine positions */ + signed char *grid; /* player knowledge */ + /* + * Each item in the `grid' array is one of the following values: + * + * - 0 to 8 mean the square is open and has a surrounding mine + * count. + * + * - -1 means the square is marked as a mine. + * + * - -2 means the square is unknown. + * + * - -3 means the square is marked with a question mark + * (FIXME: do we even want to bother with this?). + * + * - 64 means the square has had a mine revealed when the game + * was lost. + * + * - 65 means the square had a mine revealed and this was the + * one the player hits. + * + * - 66 means the square has a crossed-out mine because the + * player had incorrectly marked it. + */ +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 9; + ret->n = 10; + ret->unique = TRUE; + + return ret; +} + +static const struct game_params mines_presets[] = { + {9, 9, 10, TRUE}, + {9, 9, 35, TRUE}, + {16, 16, 40, TRUE}, + {16, 16, 99, TRUE}, +#ifndef SMALL_SCREEN + {30, 16, 99, TRUE}, + {30, 16, 170, TRUE}, +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(mines_presets)) + return FALSE; + + ret = snew(game_params); + *ret = mines_presets[i]; + + sprintf(str, "%dx%d, %d mines", ret->w, ret->h, ret->n); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + params->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->h = params->w; + } + if (*p == 'n') { + p++; + params->n = atoi(p); + while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++; + } else { + params->n = params->w * params->h / 10; + } + + while (*p) { + if (*p == 'a') { + p++; + params->unique = FALSE; + } else + p++; /* skip any other gunk */ + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[400]; + int len; + + len = sprintf(ret, "%dx%d", params->w, params->h); + /* + * Mine count is a generation-time parameter, since it can be + * deduced from the mine bitmap! + */ + if (full) + len += sprintf(ret+len, "n%d", params->n); + if (full && !params->unique) + ret[len++] = 'a'; + assert(len < lenof(ret)); + ret[len] = '\0'; + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Mines"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->n); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "Ensure solubility"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = params->unique; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->n = atoi(cfg[2].sval); + if (strchr(cfg[2].sval, '%')) + ret->n = ret->n * (ret->w * ret->h) / 100; + ret->unique = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + /* + * Lower limit on grid size: each dimension must be at least 3. + * 1 is theoretically workable if rather boring, but 2 is a + * real problem: there is often _no_ way to generate a uniquely + * solvable 2xn Mines grid. You either run into two mines + * blocking the way and no idea what's behind them, or one mine + * and no way to know which of the two rows it's in. If the + * mine count is even you can create a soluble grid by packing + * all the mines at one end (so what when you hit a two-mine + * wall there are only as many covered squares left as there + * are mines); but if it's odd, you are doomed, because you + * _have_ to have a gap somewhere which you can't determine the + * position of. + */ + if (full && params->unique && (params->w <= 2 || params->h <= 2)) + return "Width and height must both be greater than two"; + if (params->n > params->w * params->h - 9) + return "Too many mines for grid size"; + + /* + * FIXME: Need more constraints here. Not sure what the + * sensible limits for Minesweeper actually are. The limits + * probably ought to change, however, depending on uniqueness. + */ + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Minesweeper solver, used to ensure the generated grids are + * solvable without having to take risks. + */ + +/* + * Count the bits in a word. Only needs to cope with 16 bits. + */ +static int bitcount16(int inword) +{ + unsigned int word = inword; + + word = ((word & 0xAAAA) >> 1) + (word & 0x5555); + word = ((word & 0xCCCC) >> 2) + (word & 0x3333); + word = ((word & 0xF0F0) >> 4) + (word & 0x0F0F); + word = ((word & 0xFF00) >> 8) + (word & 0x00FF); + + return (int)word; +} + +/* + * We use a tree234 to store a large number of small localised + * sets, each with a mine count. We also keep some of those sets + * linked together into a to-do list. + */ +struct set { + short x, y, mask, mines; + int todo; + struct set *prev, *next; +}; + +static int setcmp(void *av, void *bv) +{ + struct set *a = (struct set *)av; + struct set *b = (struct set *)bv; + + if (a->y < b->y) + return -1; + else if (a->y > b->y) + return +1; + else if (a->x < b->x) + return -1; + else if (a->x > b->x) + return +1; + else if (a->mask < b->mask) + return -1; + else if (a->mask > b->mask) + return +1; + else + return 0; +} + +struct setstore { + tree234 *sets; + struct set *todo_head, *todo_tail; +}; + +static struct setstore *ss_new(void) +{ + struct setstore *ss = snew(struct setstore); + ss->sets = newtree234(setcmp); + ss->todo_head = ss->todo_tail = NULL; + return ss; +} + +/* + * Take two input sets, in the form (x,y,mask). Munge the first by + * taking either its intersection with the second or its difference + * with the second. Return the new mask part of the first set. + */ +static int setmunge(int x1, int y1, int mask1, int x2, int y2, int mask2, + int diff) +{ + /* + * Adjust the second set so that it has the same x,y + * coordinates as the first. + */ + if (abs(x2-x1) >= 3 || abs(y2-y1) >= 3) { + mask2 = 0; + } else { + while (x2 > x1) { + mask2 &= ~(4|32|256); + mask2 <<= 1; + x2--; + } + while (x2 < x1) { + mask2 &= ~(1|8|64); + mask2 >>= 1; + x2++; + } + while (y2 > y1) { + mask2 &= ~(64|128|256); + mask2 <<= 3; + y2--; + } + while (y2 < y1) { + mask2 &= ~(1|2|4); + mask2 >>= 3; + y2++; + } + } + + /* + * Invert the second set if `diff' is set (we're after A &~ B + * rather than A & B). + */ + if (diff) + mask2 ^= 511; + + /* + * Now all that's left is a logical AND. + */ + return mask1 & mask2; +} + +static void ss_add_todo(struct setstore *ss, struct set *s) +{ + if (s->todo) + return; /* already on it */ + +#ifdef SOLVER_DIAGNOSTICS + printf("adding set on todo list: %d,%d %03x %d\n", + s->x, s->y, s->mask, s->mines); +#endif + + s->prev = ss->todo_tail; + if (s->prev) + s->prev->next = s; + else + ss->todo_head = s; + ss->todo_tail = s; + s->next = NULL; + s->todo = TRUE; +} + +static void ss_add(struct setstore *ss, int x, int y, int mask, int mines) +{ + struct set *s; + + assert(mask != 0); + + /* + * Normalise so that x and y are genuinely the bounding + * rectangle. + */ + while (!(mask & (1|8|64))) + mask >>= 1, x++; + while (!(mask & (1|2|4))) + mask >>= 3, y++; + + /* + * Create a set structure and add it to the tree. + */ + s = snew(struct set); + s->x = x; + s->y = y; + s->mask = mask; + s->mines = mines; + s->todo = FALSE; + if (add234(ss->sets, s) != s) { + /* + * This set already existed! Free it and return. + */ + sfree(s); + return; + } + + /* + * We've added a new set to the tree, so put it on the todo + * list. + */ + ss_add_todo(ss, s); +} + +static void ss_remove(struct setstore *ss, struct set *s) +{ + struct set *next = s->next, *prev = s->prev; + +#ifdef SOLVER_DIAGNOSTICS + printf("removing set %d,%d %03x\n", s->x, s->y, s->mask); +#endif + /* + * Remove s from the todo list. + */ + if (prev) + prev->next = next; + else if (s == ss->todo_head) + ss->todo_head = next; + + if (next) + next->prev = prev; + else if (s == ss->todo_tail) + ss->todo_tail = prev; + + s->todo = FALSE; + + /* + * Remove s from the tree. + */ + del234(ss->sets, s); + + /* + * Destroy the actual set structure. + */ + sfree(s); +} + +/* + * Return a dynamically allocated list of all the sets which + * overlap a provided input set. + */ +static struct set **ss_overlap(struct setstore *ss, int x, int y, int mask) +{ + struct set **ret = NULL; + int nret = 0, retsize = 0; + int xx, yy; + + for (xx = x-3; xx < x+3; xx++) + for (yy = y-3; yy < y+3; yy++) { + struct set stmp, *s; + int pos; + + /* + * Find the first set with these top left coordinates. + */ + stmp.x = xx; + stmp.y = yy; + stmp.mask = 0; + + if (findrelpos234(ss->sets, &stmp, NULL, REL234_GE, &pos)) { + while ((s = index234(ss->sets, pos)) != NULL && + s->x == xx && s->y == yy) { + /* + * This set potentially overlaps the input one. + * Compute the intersection to see if they + * really overlap, and add it to the list if + * so. + */ + if (setmunge(x, y, mask, s->x, s->y, s->mask, FALSE)) { + /* + * There's an overlap. + */ + if (nret >= retsize) { + retsize = nret + 32; + ret = sresize(ret, retsize, struct set *); + } + ret[nret++] = s; + } + + pos++; + } + } + } + + ret = sresize(ret, nret+1, struct set *); + ret[nret] = NULL; + + return ret; +} + +/* + * Get an element from the head of the set todo list. + */ +static struct set *ss_todo(struct setstore *ss) +{ + if (ss->todo_head) { + struct set *ret = ss->todo_head; + ss->todo_head = ret->next; + if (ss->todo_head) + ss->todo_head->prev = NULL; + else + ss->todo_tail = NULL; + ret->next = ret->prev = NULL; + ret->todo = FALSE; + return ret; + } else { + return NULL; + } +} + +struct squaretodo { + int *next; + int head, tail; +}; + +static void std_add(struct squaretodo *std, int i) +{ + if (std->tail >= 0) + std->next[std->tail] = i; + else + std->head = i; + std->tail = i; + std->next[i] = -1; +} + +typedef int (*open_cb)(void *, int, int); + +static void known_squares(int w, int h, struct squaretodo *std, + signed char *grid, + open_cb open, void *openctx, + int x, int y, int mask, int mine) +{ + int xx, yy, bit; + + bit = 1; + + for (yy = 0; yy < 3; yy++) + for (xx = 0; xx < 3; xx++) { + if (mask & bit) { + int i = (y + yy) * w + (x + xx); + + /* + * It's possible that this square is _already_ + * known, in which case we don't try to add it to + * the list twice. + */ + if (grid[i] == -2) { + + if (mine) { + grid[i] = -1; /* and don't open it! */ + } else { + grid[i] = open(openctx, x + xx, y + yy); + assert(grid[i] != -1); /* *bang* */ + } + std_add(std, i); + + } + } + bit <<= 1; + } +} + +/* + * This is data returned from the `perturb' function. It details + * which squares have become mines and which have become clear. The + * solver is (of course) expected to honourably not use that + * knowledge directly, but to efficently adjust its internal data + * structures and proceed based on only the information it + * legitimately has. + */ +struct perturbation { + int x, y; + int delta; /* +1 == become a mine; -1 == cleared */ +}; +struct perturbations { + int n; + struct perturbation *changes; +}; + +/* + * Main solver entry point. You give it a grid of existing + * knowledge (-1 for a square known to be a mine, 0-8 for empty + * squares with a given number of neighbours, -2 for completely + * unknown), plus a function which you can call to open new squares + * once you're confident of them. It fills in as much more of the + * grid as it can. + * + * Return value is: + * + * - -1 means deduction stalled and nothing could be done + * - 0 means deduction succeeded fully + * - >0 means deduction succeeded but some number of perturbation + * steps were required; the exact return value is the number of + * perturb calls. + */ + +typedef struct perturbations *(*perturb_cb) (void *, signed char *, int, int, int); + +static int minesolve(int w, int h, int n, signed char *grid, + open_cb open, + perturb_cb perturb, + void *ctx, random_state *rs) +{ + struct setstore *ss = ss_new(); + struct set **list; + struct squaretodo astd, *std = &astd; + int x, y, i, j; + int nperturbs = 0; + + /* + * Set up a linked list of squares with known contents, so that + * we can process them one by one. + */ + std->next = snewn(w*h, int); + std->head = std->tail = -1; + + /* + * Initialise that list with all known squares in the input + * grid. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + i = y*w+x; + if (grid[i] != -2) + std_add(std, i); + } + } + + /* + * Main deductive loop. + */ + while (1) { + int done_something = FALSE; + struct set *s; + + /* + * If there are any known squares on the todo list, process + * them and construct a set for each. + */ + while (std->head != -1) { + i = std->head; +#ifdef SOLVER_DIAGNOSTICS + printf("known square at %d,%d [%d]\n", i%w, i/w, grid[i]); +#endif + std->head = std->next[i]; + if (std->head == -1) + std->tail = -1; + + x = i % w; + y = i / w; + + if (grid[i] >= 0) { + int dx, dy, mines, bit, val; +#ifdef SOLVER_DIAGNOSTICS + printf("creating set around this square\n"); +#endif + /* + * Empty square. Construct the set of non-known squares + * around this one, and determine its mine count. + */ + mines = grid[i]; + bit = 1; + val = 0; + for (dy = -1; dy <= +1; dy++) { + for (dx = -1; dx <= +1; dx++) { +#ifdef SOLVER_DIAGNOSTICS + printf("grid %d,%d = %d\n", x+dx, y+dy, grid[i+dy*w+dx]); +#endif + if (x+dx < 0 || x+dx >= w || y+dy < 0 || y+dy >= h) + /* ignore this one */; + else if (grid[i+dy*w+dx] == -1) + mines--; + else if (grid[i+dy*w+dx] == -2) + val |= bit; + bit <<= 1; + } + } + if (val) + ss_add(ss, x-1, y-1, val, mines); + } + + /* + * Now, whether the square is empty or full, we must + * find any set which contains it and replace it with + * one which does not. + */ + { +#ifdef SOLVER_DIAGNOSTICS + printf("finding sets containing known square %d,%d\n", x, y); +#endif + list = ss_overlap(ss, x, y, 1); + + for (j = 0; list[j]; j++) { + int newmask, newmines; + + s = list[j]; + + /* + * Compute the mask for this set minus the + * newly known square. + */ + newmask = setmunge(s->x, s->y, s->mask, x, y, 1, TRUE); + + /* + * Compute the new mine count. + */ + newmines = s->mines - (grid[i] == -1); + + /* + * Insert the new set into the collection, + * unless it's been whittled right down to + * nothing. + */ + if (newmask) + ss_add(ss, s->x, s->y, newmask, newmines); + + /* + * Destroy the old one; it is actually obsolete. + */ + ss_remove(ss, s); + } + + sfree(list); + } + + /* + * Marking a fresh square as known certainly counts as + * doing something. + */ + done_something = TRUE; + } + + /* + * Now pick a set off the to-do list and attempt deductions + * based on it. + */ + if ((s = ss_todo(ss)) != NULL) { + +#ifdef SOLVER_DIAGNOSTICS + printf("set to do: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines); +#endif + /* + * Firstly, see if this set has a mine count of zero or + * of its own cardinality. + */ + if (s->mines == 0 || s->mines == bitcount16(s->mask)) { + /* + * If so, we can immediately mark all the squares + * in the set as known. + */ +#ifdef SOLVER_DIAGNOSTICS + printf("easy\n"); +#endif + known_squares(w, h, std, grid, open, ctx, + s->x, s->y, s->mask, (s->mines != 0)); + + /* + * Having done that, we need do nothing further + * with this set; marking all the squares in it as + * known will eventually eliminate it, and will + * also permit further deductions about anything + * that overlaps it. + */ + continue; + } + + /* + * Failing that, we now search through all the sets + * which overlap this one. + */ + list = ss_overlap(ss, s->x, s->y, s->mask); + + for (j = 0; list[j]; j++) { + struct set *s2 = list[j]; + int swing, s2wing, swc, s2wc; + + /* + * Find the non-overlapping parts s2-s and s-s2, + * and their cardinalities. + * + * I'm going to refer to these parts as `wings' + * surrounding the central part common to both + * sets. The `s wing' is s-s2; the `s2 wing' is + * s2-s. + */ + swing = setmunge(s->x, s->y, s->mask, s2->x, s2->y, s2->mask, + TRUE); + s2wing = setmunge(s2->x, s2->y, s2->mask, s->x, s->y, s->mask, + TRUE); + swc = bitcount16(swing); + s2wc = bitcount16(s2wing); + + /* + * If one set has more mines than the other, and + * the number of extra mines is equal to the + * cardinality of that set's wing, then we can mark + * every square in the wing as a known mine, and + * every square in the other wing as known clear. + */ + if (swc == s->mines - s2->mines || + s2wc == s2->mines - s->mines) { + known_squares(w, h, std, grid, open, ctx, + s->x, s->y, swing, + (swc == s->mines - s2->mines)); + known_squares(w, h, std, grid, open, ctx, + s2->x, s2->y, s2wing, + (s2wc == s2->mines - s->mines)); + continue; + } + + /* + * Failing that, see if one set is a subset of the + * other. If so, we can divide up the mine count of + * the larger set between the smaller set and its + * complement, even if neither smaller set ends up + * being immediately clearable. + */ + if (swc == 0 && s2wc != 0) { + /* s is a subset of s2. */ + assert(s2->mines > s->mines); + ss_add(ss, s2->x, s2->y, s2wing, s2->mines - s->mines); + } else if (s2wc == 0 && swc != 0) { + /* s2 is a subset of s. */ + assert(s->mines > s2->mines); + ss_add(ss, s->x, s->y, swing, s->mines - s2->mines); + } + } + + sfree(list); + + /* + * In this situation we have definitely done + * _something_, even if it's only reducing the size of + * our to-do list. + */ + done_something = TRUE; + } else if (n >= 0) { + /* + * We have nothing left on our todo list, which means + * all localised deductions have failed. Our next step + * is to resort to global deduction based on the total + * mine count. This is computationally expensive + * compared to any of the above deductions, which is + * why we only ever do it when all else fails, so that + * hopefully it won't have to happen too often. + * + * If you pass n<0 into this solver, that informs it + * that you do not know the total mine count, so it + * won't even attempt these deductions. + */ + + int minesleft, squaresleft; + int nsets, setused[10], cursor; + + /* + * Start by scanning the current grid state to work out + * how many unknown squares we still have, and how many + * mines are to be placed in them. + */ + squaresleft = 0; + minesleft = n; + for (i = 0; i < w*h; i++) { + if (grid[i] == -1) + minesleft--; + else if (grid[i] == -2) + squaresleft++; + } + +#ifdef SOLVER_DIAGNOSTICS + printf("global deduction time: squaresleft=%d minesleft=%d\n", + squaresleft, minesleft); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = grid[y*w+x]; + if (v == -1) + putchar('*'); + else if (v == -2) + putchar('?'); + else if (v == 0) + putchar('-'); + else + putchar('0' + v); + } + putchar('\n'); + } +#endif + + /* + * If there _are_ no unknown squares, we have actually + * finished. + */ + if (squaresleft == 0) { + assert(minesleft == 0); + break; + } + + /* + * First really simple case: if there are no more mines + * left, or if there are exactly as many mines left as + * squares to play them in, then it's all easy. + */ + if (minesleft == 0 || minesleft == squaresleft) { + for (i = 0; i < w*h; i++) + if (grid[i] == -2) + known_squares(w, h, std, grid, open, ctx, + i % w, i / w, 1, minesleft != 0); + continue; /* now go back to main deductive loop */ + } + + /* + * Failing that, we have to do some _real_ work. + * Ideally what we do here is to try every single + * combination of the currently available sets, in an + * attempt to find a disjoint union (i.e. a set of + * squares with a known mine count between them) such + * that the remaining unknown squares _not_ contained + * in that union either contain no mines or are all + * mines. + * + * Actually enumerating all 2^n possibilities will get + * a bit slow for large n, so I artificially cap this + * recursion at n=10 to avoid too much pain. + */ + nsets = count234(ss->sets); + if (nsets <= lenof(setused)) { + /* + * Doing this with actual recursive function calls + * would get fiddly because a load of local + * variables from this function would have to be + * passed down through the recursion. So instead + * I'm going to use a virtual recursion within this + * function. The way this works is: + * + * - we have an array `setused', such that + * setused[n] is 0 or 1 depending on whether set + * n is currently in the union we are + * considering. + * + * - we have a value `cursor' which indicates how + * much of `setused' we have so far filled in. + * It's conceptually the recursion depth. + * + * We begin by setting `cursor' to zero. Then: + * + * - if cursor can advance, we advance it by one. + * We set the value in `setused' that it went + * past to 1 if that set is disjoint from + * anything else currently in `setused', or to 0 + * otherwise. + * + * - If cursor cannot advance because it has + * reached the end of the setused list, then we + * have a maximal disjoint union. Check to see + * whether its mine count has any useful + * properties. If so, mark all the squares not + * in the union as known and terminate. + * + * - If cursor has reached the end of setused and + * the algorithm _hasn't_ terminated, back + * cursor up to the nearest 1, turn it into a 0 + * and advance cursor just past it. + * + * - If we attempt to back up to the nearest 1 and + * there isn't one at all, then we have gone + * through all disjoint unions of sets in the + * list and none of them has been helpful, so we + * give up. + */ + struct set *sets[lenof(setused)]; + for (i = 0; i < nsets; i++) + sets[i] = index234(ss->sets, i); + + cursor = 0; + while (1) { + + if (cursor < nsets) { + int ok = TRUE; + + /* See if any existing set overlaps this one. */ + for (i = 0; i < cursor; i++) + if (setused[i] && + setmunge(sets[cursor]->x, + sets[cursor]->y, + sets[cursor]->mask, + sets[i]->x, sets[i]->y, sets[i]->mask, + FALSE)) { + ok = FALSE; + break; + } + + if (ok) { + /* + * We're adding this set to our union, + * so adjust minesleft and squaresleft + * appropriately. + */ + minesleft -= sets[cursor]->mines; + squaresleft -= bitcount16(sets[cursor]->mask); + } + + setused[cursor++] = ok; + } else { +#ifdef SOLVER_DIAGNOSTICS + printf("trying a set combination with %d %d\n", + squaresleft, minesleft); +#endif /* SOLVER_DIAGNOSTICS */ + + /* + * We've reached the end. See if we've got + * anything interesting. + */ + if (squaresleft > 0 && + (minesleft == 0 || minesleft == squaresleft)) { + /* + * We have! There is at least one + * square not contained within the set + * union we've just found, and we can + * deduce that either all such squares + * are mines or all are not (depending + * on whether minesleft==0). So now all + * we have to do is actually go through + * the grid, find those squares, and + * mark them. + */ + for (i = 0; i < w*h; i++) + if (grid[i] == -2) { + int outside = TRUE; + y = i / w; + x = i % w; + for (j = 0; j < nsets; j++) + if (setused[j] && + setmunge(sets[j]->x, sets[j]->y, + sets[j]->mask, x, y, 1, + FALSE)) { + outside = FALSE; + break; + } + if (outside) + known_squares(w, h, std, grid, + open, ctx, + x, y, 1, minesleft != 0); + } + + done_something = TRUE; + break; /* return to main deductive loop */ + } + + /* + * If we reach here, then this union hasn't + * done us any good, so move on to the + * next. Backtrack cursor to the nearest 1, + * change it to a 0 and continue. + */ + while (--cursor >= 0 && !setused[cursor]); + if (cursor >= 0) { + assert(setused[cursor]); + + /* + * We're removing this set from our + * union, so re-increment minesleft and + * squaresleft. + */ + minesleft += sets[cursor]->mines; + squaresleft += bitcount16(sets[cursor]->mask); + + setused[cursor++] = 0; + } else { + /* + * We've backtracked all the way to the + * start without finding a single 1, + * which means that our virtual + * recursion is complete and nothing + * helped. + */ + break; + } + } + + } + + } + } + + if (done_something) + continue; + +#ifdef SOLVER_DIAGNOSTICS + /* + * Dump the current known state of the grid. + */ + printf("solver ran out of steam, ret=%d, grid:\n", nperturbs); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = grid[y*w+x]; + if (v == -1) + putchar('*'); + else if (v == -2) + putchar('?'); + else if (v == 0) + putchar('-'); + else + putchar('0' + v); + } + putchar('\n'); + } + + { + struct set *s; + + for (i = 0; (s = index234(ss->sets, i)) != NULL; i++) + printf("remaining set: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines); + } +#endif + + /* + * Now we really are at our wits' end as far as solving + * this grid goes. Our only remaining option is to call + * a perturb function and ask it to modify the grid to + * make it easier. + */ + if (perturb) { + struct perturbations *ret; + struct set *s; + + nperturbs++; + + /* + * Choose a set at random from the current selection, + * and ask the perturb function to either fill or empty + * it. + * + * If we have no sets at all, we must give up. + */ + if (count234(ss->sets) == 0) { +#ifdef SOLVER_DIAGNOSTICS + printf("perturbing on entire unknown set\n"); +#endif + ret = perturb(ctx, grid, 0, 0, 0); + } else { + s = index234(ss->sets, random_upto(rs, count234(ss->sets))); +#ifdef SOLVER_DIAGNOSTICS + printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask); +#endif + ret = perturb(ctx, grid, s->x, s->y, s->mask); + } + + if (ret) { + assert(ret->n > 0); /* otherwise should have been NULL */ + + /* + * A number of squares have been fiddled with, and + * the returned structure tells us which. Adjust + * the mine count in any set which overlaps one of + * those squares, and put them back on the to-do + * list. Also, if the square itself is marked as a + * known non-mine, put it back on the squares-to-do + * list. + */ + for (i = 0; i < ret->n; i++) { +#ifdef SOLVER_DIAGNOSTICS + printf("perturbation %s mine at %d,%d\n", + ret->changes[i].delta > 0 ? "added" : "removed", + ret->changes[i].x, ret->changes[i].y); +#endif + + if (ret->changes[i].delta < 0 && + grid[ret->changes[i].y*w+ret->changes[i].x] != -2) { + std_add(std, ret->changes[i].y*w+ret->changes[i].x); + } + + list = ss_overlap(ss, + ret->changes[i].x, ret->changes[i].y, 1); + + for (j = 0; list[j]; j++) { + list[j]->mines += ret->changes[i].delta; + ss_add_todo(ss, list[j]); + } + + sfree(list); + } + + /* + * Now free the returned data. + */ + sfree(ret->changes); + sfree(ret); + +#ifdef SOLVER_DIAGNOSTICS + /* + * Dump the current known state of the grid. + */ + printf("state after perturbation:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = grid[y*w+x]; + if (v == -1) + putchar('*'); + else if (v == -2) + putchar('?'); + else if (v == 0) + putchar('-'); + else + putchar('0' + v); + } + putchar('\n'); + } + + { + struct set *s; + + for (i = 0; (s = index234(ss->sets, i)) != NULL; i++) + printf("remaining set: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines); + } +#endif + + /* + * And now we can go back round the deductive loop. + */ + continue; + } + } + + /* + * If we get here, even that didn't work (either we didn't + * have a perturb function or it returned failure), so we + * give up entirely. + */ + break; + } + + /* + * See if we've got any unknown squares left. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (grid[y*w+x] == -2) { + nperturbs = -1; /* failed to complete */ + break; + } + + /* + * Free the set list and square-todo list. + */ + { + struct set *s; + while ((s = delpos234(ss->sets, 0)) != NULL) + sfree(s); + freetree234(ss->sets); + sfree(ss); + sfree(std->next); + } + + return nperturbs; +} + +/* ---------------------------------------------------------------------- + * Grid generator which uses the above solver. + */ + +struct minectx { + char *grid; + int w, h; + int sx, sy; + int allow_big_perturbs; + random_state *rs; +}; + +static int mineopen(void *vctx, int x, int y) +{ + struct minectx *ctx = (struct minectx *)vctx; + int i, j, n; + + assert(x >= 0 && x < ctx->w && y >= 0 && y < ctx->h); + if (ctx->grid[y * ctx->w + x]) + return -1; /* *bang* */ + + n = 0; + for (i = -1; i <= +1; i++) { + if (x + i < 0 || x + i >= ctx->w) + continue; + for (j = -1; j <= +1; j++) { + if (y + j < 0 || y + j >= ctx->h) + continue; + if (i == 0 && j == 0) + continue; + if (ctx->grid[(y+j) * ctx->w + (x+i)]) + n++; + } + } + + return n; +} + +/* Structure used internally to mineperturb(). */ +struct square { + int x, y, type, random; +}; +static int squarecmp(const void *av, const void *bv) +{ + const struct square *a = (const struct square *)av; + const struct square *b = (const struct square *)bv; + if (a->type < b->type) + return -1; + else if (a->type > b->type) + return +1; + else if (a->random < b->random) + return -1; + else if (a->random > b->random) + return +1; + else if (a->y < b->y) + return -1; + else if (a->y > b->y) + return +1; + else if (a->x < b->x) + return -1; + else if (a->x > b->x) + return +1; + return 0; +} + +/* + * Normally this function is passed an (x,y,mask) set description. + * On occasions, though, there is no _localised_ set being used, + * and the set being perturbed is supposed to be the entirety of + * the unreachable area. This is signified by the special case + * mask==0: in this case, anything labelled -2 in the grid is part + * of the set. + * + * Allowing perturbation in this special case appears to make it + * guaranteeably possible to generate a workable grid for any mine + * density, but they tend to be a bit boring, with mines packed + * densely into far corners of the grid and the remainder being + * less dense than one might like. Therefore, to improve overall + * grid quality I disable this feature for the first few attempts, + * and fall back to it after no useful grid has been generated. + */ +static struct perturbations *mineperturb(void *vctx, signed char *grid, + int setx, int sety, int mask) +{ + struct minectx *ctx = (struct minectx *)vctx; + struct square *sqlist; + int x, y, dx, dy, i, n, nfull, nempty; + struct square **tofill, **toempty, **todo; + int ntofill, ntoempty, ntodo, dtodo, dset; + struct perturbations *ret; + int *setlist; + + if (!mask && !ctx->allow_big_perturbs) + return NULL; + + /* + * Make a list of all the squares in the grid which we can + * possibly use. This list should be in preference order, which + * means + * + * - first, unknown squares on the boundary of known space + * - next, unknown squares beyond that boundary + * - as a very last resort, known squares, but not within one + * square of the starting position. + * + * Each of these sections needs to be shuffled independently. + * We do this by preparing list of all squares and then sorting + * it with a random secondary key. + */ + sqlist = snewn(ctx->w * ctx->h, struct square); + n = 0; + for (y = 0; y < ctx->h; y++) + for (x = 0; x < ctx->w; x++) { + /* + * If this square is too near the starting position, + * don't put it on the list at all. + */ + if (abs(y - ctx->sy) <= 1 && abs(x - ctx->sx) <= 1) + continue; + + /* + * If this square is in the input set, also don't put + * it on the list! + */ + if ((mask == 0 && grid[y*ctx->w+x] == -2) || + (x >= setx && x < setx + 3 && + y >= sety && y < sety + 3 && + mask & (1 << ((y-sety)*3+(x-setx))))) + continue; + + sqlist[n].x = x; + sqlist[n].y = y; + + if (grid[y*ctx->w+x] != -2) { + sqlist[n].type = 3; /* known square */ + } else { + /* + * Unknown square. Examine everything around it and + * see if it borders on any known squares. If it + * does, it's class 1, otherwise it's 2. + */ + + sqlist[n].type = 2; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (x+dx >= 0 && x+dx < ctx->w && + y+dy >= 0 && y+dy < ctx->h && + grid[(y+dy)*ctx->w+(x+dx)] != -2) { + sqlist[n].type = 1; + break; + } + } + + /* + * Finally, a random number to cause qsort to + * shuffle within each group. + */ + sqlist[n].random = random_bits(ctx->rs, 31); + + n++; + } + + qsort(sqlist, n, sizeof(struct square), squarecmp); + + /* + * Now count up the number of full and empty squares in the set + * we've been provided. + */ + nfull = nempty = 0; + if (mask) { + for (dy = 0; dy < 3; dy++) + for (dx = 0; dx < 3; dx++) + if (mask & (1 << (dy*3+dx))) { + assert(setx+dx <= ctx->w); + assert(sety+dy <= ctx->h); + if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)]) + nfull++; + else + nempty++; + } + } else { + for (y = 0; y < ctx->h; y++) + for (x = 0; x < ctx->w; x++) + if (grid[y*ctx->w+x] == -2) { + if (ctx->grid[y*ctx->w+x]) + nfull++; + else + nempty++; + } + } + + /* + * Now go through our sorted list until we find either `nfull' + * empty squares, or `nempty' full squares; these will be + * swapped with the appropriate squares in the set to either + * fill or empty the set while keeping the same number of mines + * overall. + */ + ntofill = ntoempty = 0; + if (mask) { + tofill = snewn(9, struct square *); + toempty = snewn(9, struct square *); + } else { + tofill = snewn(ctx->w * ctx->h, struct square *); + toempty = snewn(ctx->w * ctx->h, struct square *); + } + for (i = 0; i < n; i++) { + struct square *sq = &sqlist[i]; + if (ctx->grid[sq->y * ctx->w + sq->x]) + toempty[ntoempty++] = sq; + else + tofill[ntofill++] = sq; + if (ntofill == nfull || ntoempty == nempty) + break; + } + + /* + * If we haven't found enough empty squares outside the set to + * empty it into _or_ enough full squares outside it to fill it + * up with, we'll have to settle for doing only a partial job. + * In this case we choose to always _fill_ the set (because + * this case will tend to crop up when we're working with very + * high mine densities and the only way to get a solvable grid + * is going to be to pack most of the mines solidly around the + * edges). So now our job is to make a list of the empty + * squares in the set, and shuffle that list so that we fill a + * random selection of them. + */ + if (ntofill != nfull && ntoempty != nempty) { + int k; + + assert(ntoempty != 0); + + setlist = snewn(ctx->w * ctx->h, int); + i = 0; + if (mask) { + for (dy = 0; dy < 3; dy++) + for (dx = 0; dx < 3; dx++) + if (mask & (1 << (dy*3+dx))) { + assert(setx+dx <= ctx->w); + assert(sety+dy <= ctx->h); + if (!ctx->grid[(sety+dy)*ctx->w+(setx+dx)]) + setlist[i++] = (sety+dy)*ctx->w+(setx+dx); + } + } else { + for (y = 0; y < ctx->h; y++) + for (x = 0; x < ctx->w; x++) + if (grid[y*ctx->w+x] == -2) { + if (!ctx->grid[y*ctx->w+x]) + setlist[i++] = y*ctx->w+x; + } + } + assert(i > ntoempty); + /* + * Now pick `ntoempty' items at random from the list. + */ + for (k = 0; k < ntoempty; k++) { + int index = k + random_upto(ctx->rs, i - k); + int tmp; + + tmp = setlist[k]; + setlist[k] = setlist[index]; + setlist[index] = tmp; + } + } else + setlist = NULL; + + /* + * Now we're pretty much there. We need to either + * (a) put a mine in each of the empty squares in the set, and + * take one out of each square in `toempty' + * (b) take a mine out of each of the full squares in the set, + * and put one in each square in `tofill' + * depending on which one we've found enough squares to do. + * + * So we start by constructing our list of changes to return to + * the solver, so that it can update its data structures + * efficiently rather than having to rescan the whole grid. + */ + ret = snew(struct perturbations); + if (ntofill == nfull) { + todo = tofill; + ntodo = ntofill; + dtodo = +1; + dset = -1; + sfree(toempty); + } else { + /* + * (We also fall into this case if we've constructed a + * setlist.) + */ + todo = toempty; + ntodo = ntoempty; + dtodo = -1; + dset = +1; + sfree(tofill); + } + ret->n = 2 * ntodo; + ret->changes = snewn(ret->n, struct perturbation); + for (i = 0; i < ntodo; i++) { + ret->changes[i].x = todo[i]->x; + ret->changes[i].y = todo[i]->y; + ret->changes[i].delta = dtodo; + } + /* now i == ntodo */ + if (setlist) { + int j; + assert(todo == toempty); + for (j = 0; j < ntoempty; j++) { + ret->changes[i].x = setlist[j] % ctx->w; + ret->changes[i].y = setlist[j] / ctx->w; + ret->changes[i].delta = dset; + i++; + } + sfree(setlist); + } else if (mask) { + for (dy = 0; dy < 3; dy++) + for (dx = 0; dx < 3; dx++) + if (mask & (1 << (dy*3+dx))) { + int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1); + if (dset == -currval) { + ret->changes[i].x = setx + dx; + ret->changes[i].y = sety + dy; + ret->changes[i].delta = dset; + i++; + } + } + } else { + for (y = 0; y < ctx->h; y++) + for (x = 0; x < ctx->w; x++) + if (grid[y*ctx->w+x] == -2) { + int currval = (ctx->grid[y*ctx->w+x] ? +1 : -1); + if (dset == -currval) { + ret->changes[i].x = x; + ret->changes[i].y = y; + ret->changes[i].delta = dset; + i++; + } + } + } + assert(i == ret->n); + + sfree(sqlist); + sfree(todo); + + /* + * Having set up the precise list of changes we're going to + * make, we now simply make them and return. + */ + for (i = 0; i < ret->n; i++) { + int delta; + + x = ret->changes[i].x; + y = ret->changes[i].y; + delta = ret->changes[i].delta; + + /* + * Check we're not trying to add an existing mine or remove + * an absent one. + */ + assert((delta < 0) ^ (ctx->grid[y*ctx->w+x] == 0)); + + /* + * Actually make the change. + */ + ctx->grid[y*ctx->w+x] = (delta > 0); + + /* + * Update any numbers already present in the grid. + */ + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (x+dx >= 0 && x+dx < ctx->w && + y+dy >= 0 && y+dy < ctx->h && + grid[(y+dy)*ctx->w+(x+dx)] != -2) { + if (dx == 0 && dy == 0) { + /* + * The square itself is marked as known in + * the grid. Mark it as a mine if it's a + * mine, or else work out its number. + */ + if (delta > 0) { + grid[y*ctx->w+x] = -1; + } else { + int dx2, dy2, minecount = 0; + for (dy2 = -1; dy2 <= +1; dy2++) + for (dx2 = -1; dx2 <= +1; dx2++) + if (x+dx2 >= 0 && x+dx2 < ctx->w && + y+dy2 >= 0 && y+dy2 < ctx->h && + ctx->grid[(y+dy2)*ctx->w+(x+dx2)]) + minecount++; + grid[y*ctx->w+x] = minecount; + } + } else { + if (grid[(y+dy)*ctx->w+(x+dx)] >= 0) + grid[(y+dy)*ctx->w+(x+dx)] += delta; + } + } + } + +#ifdef GENERATION_DIAGNOSTICS + { + int yy, xx; + printf("grid after perturbing:\n"); + for (yy = 0; yy < ctx->h; yy++) { + for (xx = 0; xx < ctx->w; xx++) { + int v = ctx->grid[yy*ctx->w+xx]; + if (yy == ctx->sy && xx == ctx->sx) { + assert(!v); + putchar('S'); + } else if (v) { + putchar('*'); + } else { + putchar('-'); + } + } + putchar('\n'); + } + printf("\n"); + } +#endif + + return ret; +} + +static char *minegen(int w, int h, int n, int x, int y, int unique, + random_state *rs) +{ + char *ret = snewn(w*h, char); + int success; + int ntries = 0; + + do { + success = FALSE; + ntries++; + + memset(ret, 0, w*h); + + /* + * Start by placing n mines, none of which is at x,y or within + * one square of it. + */ + { + int *tmp = snewn(w*h, int); + int i, j, k, nn; + + /* + * Write down the list of possible mine locations. + */ + k = 0; + for (i = 0; i < h; i++) + for (j = 0; j < w; j++) + if (abs(i - y) > 1 || abs(j - x) > 1) + tmp[k++] = i*w+j; + + /* + * Now pick n off the list at random. + */ + nn = n; + while (nn-- > 0) { + i = random_upto(rs, k); + ret[tmp[i]] = 1; + tmp[i] = tmp[--k]; + } + + sfree(tmp); + } + +#ifdef GENERATION_DIAGNOSTICS + { + int yy, xx; + printf("grid after initial generation:\n"); + for (yy = 0; yy < h; yy++) { + for (xx = 0; xx < w; xx++) { + int v = ret[yy*w+xx]; + if (yy == y && xx == x) { + assert(!v); + putchar('S'); + } else if (v) { + putchar('*'); + } else { + putchar('-'); + } + } + putchar('\n'); + } + printf("\n"); + } +#endif + + /* + * Now set up a results grid to run the solver in, and a + * context for the solver to open squares. Then run the solver + * repeatedly; if the number of perturb steps ever goes up or + * it ever returns -1, give up completely. + * + * We bypass this bit if we're not after a unique grid. + */ + if (unique) { + signed char *solvegrid = snewn(w*h, signed char); + struct minectx actx, *ctx = &actx; + int solveret, prevret = -2; + + ctx->grid = ret; + ctx->w = w; + ctx->h = h; + ctx->sx = x; + ctx->sy = y; + ctx->rs = rs; + ctx->allow_big_perturbs = (ntries > 100); + + while (1) { + memset(solvegrid, -2, w*h); + solvegrid[y*w+x] = mineopen(ctx, x, y); + assert(solvegrid[y*w+x] == 0); /* by deliberate arrangement */ + + solveret = + minesolve(w, h, n, solvegrid, mineopen, mineperturb, ctx, rs); + if (solveret < 0 || (prevret >= 0 && solveret >= prevret)) { + success = FALSE; + break; + } else if (solveret == 0) { + success = TRUE; + break; + } + } + + sfree(solvegrid); + } else { + success = TRUE; + } + + } while (!success); + + return ret; +} + +static char *describe_layout(char *grid, int area, int x, int y, + int obfuscate) +{ + char *ret, *p; + unsigned char *bmp; + int i; + + /* + * Set up the mine bitmap and obfuscate it. + */ + bmp = snewn((area + 7) / 8, unsigned char); + memset(bmp, 0, (area + 7) / 8); + for (i = 0; i < area; i++) { + if (grid[i]) + bmp[i / 8] |= 0x80 >> (i % 8); + } + if (obfuscate) + obfuscate_bitmap(bmp, area, FALSE); + + /* + * Now encode the resulting bitmap in hex. We can work to + * nibble rather than byte granularity, since the obfuscation + * function guarantees to return a bit string of the same + * length as its input. + */ + ret = snewn((area+3)/4 + 100, char); + p = ret + sprintf(ret, "%d,%d,%s", x, y, + obfuscate ? "m" : "u"); /* 'm' == masked */ + for (i = 0; i < (area+3)/4; i++) { + int v = bmp[i/2]; + if (i % 2 == 0) + v >>= 4; + *p++ = "0123456789abcdef"[v & 0xF]; + } + *p = '\0'; + + sfree(bmp); + + return ret; +} + +static char *new_mine_layout(int w, int h, int n, int x, int y, int unique, + random_state *rs, char **game_desc) +{ + char *grid; + +#ifdef TEST_OBFUSCATION + static int tested_obfuscation = FALSE; + if (!tested_obfuscation) { + /* + * A few simple test vectors for the obfuscator. + * + * First test: the 28-bit stream 1234567. This divides up + * into 1234 and 567[0]. The SHA of 56 70 30 (appending + * "0") is 15ce8ab946640340bbb99f3f48fd2c45d1a31d30. Thus, + * we XOR the 16-bit string 15CE into the input 1234 to get + * 07FA. Next, we SHA that with "0": the SHA of 07 FA 30 is + * 3370135c5e3da4fed937adc004a79533962b6391. So we XOR the + * 12-bit string 337 into the input 567 to get 650. Thus + * our output is 07FA650. + */ + { + unsigned char bmp1[] = "\x12\x34\x56\x70"; + obfuscate_bitmap(bmp1, 28, FALSE); + printf("test 1 encode: %s\n", + memcmp(bmp1, "\x07\xfa\x65\x00", 4) ? "failed" : "passed"); + obfuscate_bitmap(bmp1, 28, TRUE); + printf("test 1 decode: %s\n", + memcmp(bmp1, "\x12\x34\x56\x70", 4) ? "failed" : "passed"); + } + /* + * Second test: a long string to make sure we switch from + * one SHA to the next correctly. My input string this time + * is simply fifty bytes of zeroes. + */ + { + unsigned char bmp2[50]; + unsigned char bmp2a[50]; + memset(bmp2, 0, 50); + memset(bmp2a, 0, 50); + obfuscate_bitmap(bmp2, 50 * 8, FALSE); + /* + * SHA of twenty-five zero bytes plus "0" is + * b202c07b990c01f6ff2d544707f60e506019b671. SHA of + * twenty-five zero bytes plus "1" is + * fcb1d8b5a2f6b592fe6780b36aa9d65dd7aa6db9. Thus our + * first half becomes + * b202c07b990c01f6ff2d544707f60e506019b671fcb1d8b5a2. + * + * SHA of that lot plus "0" is + * 10b0af913db85d37ca27f52a9f78bba3a80030db. SHA of the + * same string plus "1" is + * 3d01d8df78e76d382b8106f480135a1bc751d725. So the + * second half becomes + * 10b0af913db85d37ca27f52a9f78bba3a80030db3d01d8df78. + */ + printf("test 2 encode: %s\n", + memcmp(bmp2, "\xb2\x02\xc0\x7b\x99\x0c\x01\xf6\xff\x2d\x54" + "\x47\x07\xf6\x0e\x50\x60\x19\xb6\x71\xfc\xb1\xd8" + "\xb5\xa2\x10\xb0\xaf\x91\x3d\xb8\x5d\x37\xca\x27" + "\xf5\x2a\x9f\x78\xbb\xa3\xa8\x00\x30\xdb\x3d\x01" + "\xd8\xdf\x78", 50) ? "failed" : "passed"); + obfuscate_bitmap(bmp2, 50 * 8, TRUE); + printf("test 2 decode: %s\n", + memcmp(bmp2, bmp2a, 50) ? "failed" : "passed"); + } + } +#endif + + grid = minegen(w, h, n, x, y, unique, rs); + + if (game_desc) + *game_desc = describe_layout(grid, w * h, x, y, TRUE); + + return grid; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + /* + * We generate the coordinates of an initial click even if they + * aren't actually used. This has the effect of harmonising the + * random number usage between interactive and batch use: if + * you use `mines --generate' with an explicit random seed, you + * should get exactly the same results as if you type the same + * random seed into the interactive game and click in the same + * initial location. (Of course you won't get the same grid if + * you click in a _different_ initial location, but there's + * nothing to be done about that.) + */ + int x = random_upto(rs, params->w); + int y = random_upto(rs, params->h); + + if (!interactive) { + /* + * For batch-generated grids, pre-open one square. + */ + char *grid; + char *desc; + + grid = new_mine_layout(params->w, params->h, params->n, + x, y, params->unique, rs, &desc); + sfree(grid); + return desc; + } else { + char *rsdesc, *desc; + + rsdesc = random_state_encode(rs); + desc = snewn(strlen(rsdesc) + 100, char); + sprintf(desc, "r%d,%c,%s", params->n, (char)(params->unique ? 'u' : 'a'), rsdesc); + sfree(rsdesc); + return desc; + } +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int wh = params->w * params->h; + int x, y; + + if (*desc == 'r') { + desc++; + if (!*desc || !isdigit((unsigned char)*desc)) + return "No initial mine count in game description"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over mine count */ + if (*desc != ',') + return "No ',' after initial x-coordinate in game description"; + desc++; + if (*desc != 'u' && *desc != 'a') + return "No uniqueness specifier in game description"; + desc++; + if (*desc != ',') + return "No ',' after uniqueness specifier in game description"; + /* now ignore the rest */ + } else { + if (*desc && isdigit((unsigned char)*desc)) { + x = atoi(desc); + if (x < 0 || x >= params->w) + return "Initial x-coordinate was out of range"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over x coordinate */ + if (*desc != ',') + return "No ',' after initial x-coordinate in game description"; + desc++; /* eat comma */ + if (!*desc || !isdigit((unsigned char)*desc)) + return "No initial y-coordinate in game description"; + y = atoi(desc); + if (y < 0 || y >= params->h) + return "Initial y-coordinate was out of range"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over y coordinate */ + if (*desc != ',') + return "No ',' after initial y-coordinate in game description"; + desc++; /* eat comma */ + } + /* eat `m' for `masked' or `u' for `unmasked', if present */ + if (*desc == 'm' || *desc == 'u') + desc++; + /* now just check length of remainder */ + if (strlen(desc) != (wh+3)/4) + return "Game description is wrong length"; + } + + return NULL; +} + +static int open_square(game_state *state, int x, int y) +{ + int w = state->w, h = state->h; + int xx, yy, nmines, ncovered; + + if (!state->layout->mines) { + /* + * We have a preliminary game in which the mine layout + * hasn't been generated yet. Generate it based on the + * initial click location. + */ + char *desc, *privdesc; + state->layout->mines = new_mine_layout(w, h, state->layout->n, + x, y, state->layout->unique, + state->layout->rs, + &desc); + /* + * Find the trailing substring of the game description + * corresponding to just the mine layout; we will use this + * as our second `private' game ID for serialisation. + */ + privdesc = desc; + while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++; + if (*privdesc == ',') privdesc++; + while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++; + if (*privdesc == ',') privdesc++; + assert(*privdesc == 'm'); + midend_supersede_game_desc(state->layout->me, desc, privdesc); + sfree(desc); + random_free(state->layout->rs); + state->layout->rs = NULL; + } + + if (state->layout->mines[y*w+x]) { + /* + * The player has landed on a mine. Bad luck. Expose the + * mine that killed them, but not the rest (in case they + * want to Undo and carry on playing). + */ + state->dead = TRUE; + state->grid[y*w+x] = 65; + return -1; + } + + /* + * Otherwise, the player has opened a safe square. Mark it to-do. + */ + state->grid[y*w+x] = -10; /* `todo' value internal to this func */ + + /* + * Now go through the grid finding all `todo' values and + * opening them. Every time one of them turns out to have no + * neighbouring mines, we add all its unopened neighbours to + * the list as well. + * + * FIXME: We really ought to be able to do this better than + * using repeated N^2 scans of the grid. + */ + while (1) { + int done_something = FALSE; + + for (yy = 0; yy < h; yy++) + for (xx = 0; xx < w; xx++) + if (state->grid[yy*w+xx] == -10) { + int dx, dy, v; + + assert(!state->layout->mines[yy*w+xx]); + + v = 0; + + for (dx = -1; dx <= +1; dx++) + for (dy = -1; dy <= +1; dy++) + if (xx+dx >= 0 && xx+dx < state->w && + yy+dy >= 0 && yy+dy < state->h && + state->layout->mines[(yy+dy)*w+(xx+dx)]) + v++; + + state->grid[yy*w+xx] = v; + + if (v == 0) { + for (dx = -1; dx <= +1; dx++) + for (dy = -1; dy <= +1; dy++) + if (xx+dx >= 0 && xx+dx < state->w && + yy+dy >= 0 && yy+dy < state->h && + state->grid[(yy+dy)*w+(xx+dx)] == -2) + state->grid[(yy+dy)*w+(xx+dx)] = -10; + } + + done_something = TRUE; + } + + if (!done_something) + break; + } + + /* + * Finally, scan the grid and see if exactly as many squares + * are still covered as there are mines. If so, set the `won' + * flag and fill in mine markers on all covered squares. + */ + nmines = ncovered = 0; + for (yy = 0; yy < h; yy++) + for (xx = 0; xx < w; xx++) { + if (state->grid[yy*w+xx] < 0) + ncovered++; + if (state->layout->mines[yy*w+xx]) + nmines++; + } + assert(ncovered >= nmines); + if (ncovered == nmines) { + for (yy = 0; yy < h; yy++) + for (xx = 0; xx < w; xx++) { + if (state->grid[yy*w+xx] < 0) + state->grid[yy*w+xx] = -1; + } + state->won = TRUE; + } + + return 0; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int i, wh, x, y, masked; + unsigned char *bmp; + + state->w = params->w; + state->h = params->h; + state->n = params->n; + state->dead = state->won = FALSE; + state->used_solve = FALSE; + + wh = state->w * state->h; + + state->layout = snew(struct mine_layout); + memset(state->layout, 0, sizeof(struct mine_layout)); + state->layout->refcount = 1; + + state->grid = snewn(wh, signed char); + memset(state->grid, -2, wh); + + if (*desc == 'r') { + desc++; + state->layout->n = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over mine count */ + if (*desc) desc++; /* eat comma */ + if (*desc == 'a') + state->layout->unique = FALSE; + else + state->layout->unique = TRUE; + desc++; + if (*desc) desc++; /* eat comma */ + + state->layout->mines = NULL; + state->layout->rs = random_state_decode(desc); + state->layout->me = me; + + } else { + state->layout->rs = NULL; + state->layout->me = NULL; + state->layout->mines = snewn(wh, char); + + if (*desc && isdigit((unsigned char)*desc)) { + x = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over x coordinate */ + if (*desc) desc++; /* eat comma */ + y = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over y coordinate */ + if (*desc) desc++; /* eat comma */ + } else { + x = y = -1; + } + + if (*desc == 'm') { + masked = TRUE; + desc++; + } else { + if (*desc == 'u') + desc++; + /* + * We permit game IDs to be entered by hand without the + * masking transformation. + */ + masked = FALSE; + } + + bmp = snewn((wh + 7) / 8, unsigned char); + memset(bmp, 0, (wh + 7) / 8); + for (i = 0; i < (wh+3)/4; i++) { + int c = desc[i]; + int v; + + assert(c != 0); /* validate_desc should have caught */ + if (c >= '0' && c <= '9') + v = c - '0'; + else if (c >= 'a' && c <= 'f') + v = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + v = c - 'A' + 10; + else + v = 0; + + bmp[i / 2] |= v << (4 * (1 - (i % 2))); + } + + if (masked) + obfuscate_bitmap(bmp, wh, TRUE); + + memset(state->layout->mines, 0, wh); + for (i = 0; i < wh; i++) { + if (bmp[i / 8] & (0x80 >> (i % 8))) + state->layout->mines[i] = 1; + } + + if (x >= 0 && y >= 0) + open_square(state, x, y); + sfree(bmp); + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + ret->n = state->n; + ret->dead = state->dead; + ret->won = state->won; + ret->used_solve = state->used_solve; + ret->layout = state->layout; + ret->layout->refcount++; + ret->grid = snewn(ret->w * ret->h, signed char); + memcpy(ret->grid, state->grid, ret->w * ret->h); + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->layout->refcount <= 0) { + sfree(state->layout->mines); + if (state->layout->rs) + random_free(state->layout->rs); + sfree(state->layout); + } + sfree(state->grid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + if (!state->layout->mines) { + *error = "Game has not been started yet"; + return NULL; + } + + return dupstr("S"); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret; + int x, y; + + ret = snewn((state->w + 1) * state->h + 1, char); + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + int v = state->grid[y*state->w+x]; + if (v == 0) + v = '-'; + else if (v >= 1 && v <= 8) + v = '0' + v; + else if (v == -1) + v = '*'; + else if (v == -2 || v == -3) + v = '?'; + else if (v >= 64) + v = '!'; + ret[y * (state->w+1) + x] = v; + } + ret[y * (state->w+1) + state->w] = '\n'; + } + ret[(state->w + 1) * state->h] = '\0'; + + return ret; +} + +struct game_ui { + int hx, hy, hradius; /* for mouse-down highlights */ + int validradius; + int flash_is_death; + int deaths, completed; + int cur_x, cur_y, cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->hx = ui->hy = -1; + ui->hradius = ui->validradius = 0; + ui->deaths = 0; + ui->completed = FALSE; + ui->flash_is_death = FALSE; /* *shrug* */ + ui->cur_x = ui->cur_y = ui->cur_visible = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + char buf[80]; + /* + * The deaths counter and completion status need preserving + * across a serialisation. + */ + sprintf(buf, "D%d", ui->deaths); + if (ui->completed) + strcat(buf, "C"); + return dupstr(buf); +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + int p= 0; + sscanf(encoding, "D%d%n", &ui->deaths, &p); + if (encoding[p] == 'C') + ui->completed = TRUE; +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (newstate->won) + ui->completed = TRUE; +} + +struct game_drawstate { + int w, h, started, tilesize, bg; + signed char *grid; + /* + * Items in this `grid' array have all the same values as in + * the game_state grid, and in addition: + * + * - -10 means the tile was drawn `specially' as a result of a + * flash, so it will always need redrawing. + * + * - -22 and -23 mean the tile is highlighted for a possible + * click. + */ + int cur_x, cur_y; /* -1, -1 for no cursor displayed. */ +}; + +static char *interpret_move(const game_state *from, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int cx, cy; + char buf[256]; + + if (from->dead || from->won) + return NULL; /* no further moves permitted */ + + cx = FROMCOORD(x); + cy = FROMCOORD(y); + + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0); + ui->cur_visible = 1; + return ""; + } + if (IS_CURSOR_SELECT(button)) { + int v = from->grid[ui->cur_y * from->w + ui->cur_x]; + + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + if (button == CURSOR_SELECT2) { + /* As for RIGHT_BUTTON; only works on covered square. */ + if (v != -2 && v != -1) + return NULL; + sprintf(buf, "F%d,%d", ui->cur_x, ui->cur_y); + return dupstr(buf); + } + /* Otherwise, treat as LEFT_BUTTON, for a single square. */ + if (v == -2 || v == -3) { + if (from->layout->mines && + from->layout->mines[ui->cur_y * from->w + ui->cur_x]) + ui->deaths++; + + sprintf(buf, "O%d,%d", ui->cur_x, ui->cur_y); + return dupstr(buf); + } + cx = ui->cur_x; cy = ui->cur_y; + ui->validradius = 1; + goto uncover; + } + + if (button == LEFT_BUTTON || button == LEFT_DRAG || + button == MIDDLE_BUTTON || button == MIDDLE_DRAG) { + if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h) + return NULL; + + /* + * Mouse-downs and mouse-drags just cause highlighting + * updates. + */ + ui->hx = cx; + ui->hy = cy; + ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0); + if (button == LEFT_BUTTON) + ui->validradius = ui->hradius; + else if (button == MIDDLE_BUTTON) + ui->validradius = 1; + ui->cur_visible = 0; + return ""; + } + + if (button == RIGHT_BUTTON) { + if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h) + return NULL; + + /* + * Right-clicking only works on a covered square, and it + * toggles between -1 (marked as mine) and -2 (not marked + * as mine). + * + * FIXME: question marks. + */ + if (from->grid[cy * from->w + cx] != -2 && + from->grid[cy * from->w + cx] != -1) + return NULL; + + sprintf(buf, "F%d,%d", cx, cy); + return dupstr(buf); + } + + if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) { + ui->hx = ui->hy = -1; + ui->hradius = 0; + + /* + * At this stage we must never return NULL: we have adjusted + * the ui, so at worst we return "". + */ + if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h) + return ""; + + /* + * Left-clicking on a covered square opens a tile. Not + * permitted if the tile is marked as a mine, for safety. + * (Unmark it and _then_ open it.) + */ + if (button == LEFT_RELEASE && + (from->grid[cy * from->w + cx] == -2 || + from->grid[cy * from->w + cx] == -3) && + ui->validradius == 0) { + /* Check if you've killed yourself. */ + if (from->layout->mines && from->layout->mines[cy * from->w + cx]) + ui->deaths++; + + sprintf(buf, "O%d,%d", cx, cy); + return dupstr(buf); + } + goto uncover; + } + return NULL; + +uncover: + { + /* + * Left-clicking or middle-clicking on an uncovered tile: + * first we check to see if the number of mine markers + * surrounding the tile is equal to its mine count, and if + * so then we open all other surrounding squares. + */ + if (from->grid[cy * from->w + cx] > 0 && ui->validradius == 1) { + int dy, dx, n; + + /* Count mine markers. */ + n = 0; + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (cx+dx >= 0 && cx+dx < from->w && + cy+dy >= 0 && cy+dy < from->h) { + if (from->grid[(cy+dy)*from->w+(cx+dx)] == -1) + n++; + } + + if (n == from->grid[cy * from->w + cx]) { + + /* + * Now see if any of the squares we're clearing + * contains a mine (which will happen iff you've + * incorrectly marked the mines around the clicked + * square). If so, we open _just_ those squares, to + * reveal as little additional information as we + * can. + */ + char *p = buf; + char *sep = ""; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (cx+dx >= 0 && cx+dx < from->w && + cy+dy >= 0 && cy+dy < from->h) { + if (from->grid[(cy+dy)*from->w+(cx+dx)] != -1 && + from->layout->mines && + from->layout->mines[(cy+dy)*from->w+(cx+dx)]) { + p += sprintf(p, "%sO%d,%d", sep, cx+dx, cy+dy); + sep = ";"; + } + } + + if (p > buf) { + ui->deaths++; + } else { + sprintf(buf, "C%d,%d", cx, cy); + } + + return dupstr(buf); + } + } + + return ""; + } +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int cy, cx; + game_state *ret; + + if (!strcmp(move, "S")) { + int yy, xx; + + ret = dup_game(from); + if (!ret->dead) { + /* + * If the player is still alive at the moment of pressing + * Solve, expose the entire grid as if it were a completed + * solution. + */ + for (yy = 0; yy < ret->h; yy++) + for (xx = 0; xx < ret->w; xx++) { + + if (ret->layout->mines[yy*ret->w+xx]) { + ret->grid[yy*ret->w+xx] = -1; + } else { + int dx, dy, v; + + v = 0; + + for (dx = -1; dx <= +1; dx++) + for (dy = -1; dy <= +1; dy++) + if (xx+dx >= 0 && xx+dx < ret->w && + yy+dy >= 0 && yy+dy < ret->h && + ret->layout->mines[(yy+dy)*ret->w+(xx+dx)]) + v++; + + ret->grid[yy*ret->w+xx] = v; + } + } + } else { + /* + * If the player pressed Solve _after dying_, show a full + * corrections grid in the style of standard Minesweeper. + * Players who don't like Mines's behaviour on death of + * only showing the mine that killed you (so that in case + * of a typo you can undo and carry on without the rest of + * the grid being spoiled) can use this to get the display + * that ordinary Minesweeper would have given them. + */ + for (yy = 0; yy < ret->h; yy++) + for (xx = 0; xx < ret->w; xx++) { + int pos = yy*ret->w+xx; + if ((ret->grid[pos] == -2 || ret->grid[pos] == -3) && + ret->layout->mines[pos]) { + ret->grid[pos] = 64; + } else if (ret->grid[pos] == -1 && + !ret->layout->mines[pos]) { + ret->grid[pos] = 66; + } + } + } + ret->used_solve = TRUE; + + return ret; + } else { + ret = dup_game(from); + + while (*move) { + if (move[0] == 'F' && + sscanf(move+1, "%d,%d", &cx, &cy) == 2 && + cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) { + ret->grid[cy * from->w + cx] ^= (-2 ^ -1); + } else if (move[0] == 'O' && + sscanf(move+1, "%d,%d", &cx, &cy) == 2 && + cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) { + open_square(ret, cx, cy); + } else if (move[0] == 'C' && + sscanf(move+1, "%d,%d", &cx, &cy) == 2 && + cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) { + int dx, dy; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (cx+dx >= 0 && cx+dx < ret->w && + cy+dy >= 0 && cy+dy < ret->h && + (ret->grid[(cy+dy)*ret->w+(cx+dx)] == -2 || + ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3)) + open_square(ret, cx+dx, cy+dy); + } else { + free_game(ret); + return NULL; + } + + while (*move && *move != ';') move++; + if (*move) move++; + } + + return ret; + } +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = BORDER * 2 + TILE_SIZE * params->w; + *y = BORDER * 2 + TILE_SIZE * params->h; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_BACKGROUND2 * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 19.0F / 20.0F; + ret[COL_BACKGROUND2 * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 19.0F / 20.0F; + ret[COL_BACKGROUND2 * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 19.0F / 20.0F; + + ret[COL_1 * 3 + 0] = 0.0F; + ret[COL_1 * 3 + 1] = 0.0F; + ret[COL_1 * 3 + 2] = 1.0F; + + ret[COL_2 * 3 + 0] = 0.0F; + ret[COL_2 * 3 + 1] = 0.5F; + ret[COL_2 * 3 + 2] = 0.0F; + + ret[COL_3 * 3 + 0] = 1.0F; + ret[COL_3 * 3 + 1] = 0.0F; + ret[COL_3 * 3 + 2] = 0.0F; + + ret[COL_4 * 3 + 0] = 0.0F; + ret[COL_4 * 3 + 1] = 0.0F; + ret[COL_4 * 3 + 2] = 0.5F; + + ret[COL_5 * 3 + 0] = 0.5F; + ret[COL_5 * 3 + 1] = 0.0F; + ret[COL_5 * 3 + 2] = 0.0F; + + ret[COL_6 * 3 + 0] = 0.0F; + ret[COL_6 * 3 + 1] = 0.5F; + ret[COL_6 * 3 + 2] = 0.5F; + + ret[COL_7 * 3 + 0] = 0.0F; + ret[COL_7 * 3 + 1] = 0.0F; + ret[COL_7 * 3 + 2] = 0.0F; + + ret[COL_8 * 3 + 0] = 0.5F; + ret[COL_8 * 3 + 1] = 0.5F; + ret[COL_8 * 3 + 2] = 0.5F; + + ret[COL_MINE * 3 + 0] = 0.0F; + ret[COL_MINE * 3 + 1] = 0.0F; + ret[COL_MINE * 3 + 2] = 0.0F; + + ret[COL_BANG * 3 + 0] = 1.0F; + ret[COL_BANG * 3 + 1] = 0.0F; + ret[COL_BANG * 3 + 2] = 0.0F; + + ret[COL_CROSS * 3 + 0] = 1.0F; + ret[COL_CROSS * 3 + 1] = 0.0F; + ret[COL_CROSS * 3 + 2] = 0.0F; + + ret[COL_FLAG * 3 + 0] = 1.0F; + ret[COL_FLAG * 3 + 1] = 0.0F; + ret[COL_FLAG * 3 + 2] = 0.0F; + + ret[COL_FLAGBASE * 3 + 0] = 0.0F; + ret[COL_FLAGBASE * 3 + 1] = 0.0F; + ret[COL_FLAGBASE * 3 + 2] = 0.0F; + + ret[COL_QUERY * 3 + 0] = 0.0F; + ret[COL_QUERY * 3 + 1] = 0.0F; + ret[COL_QUERY * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 1] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 2] = 1.0F; + + ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F; + ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F; + ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F; + + ret[COL_WRONGNUMBER * 3 + 0] = 1.0F; + ret[COL_WRONGNUMBER * 3 + 1] = 0.6F; + ret[COL_WRONGNUMBER * 3 + 2] = 0.6F; + + /* Red tinge to a light colour, for the cursor. */ + ret[COL_CURSOR * 3 + 0] = ret[COL_HIGHLIGHT * 3 + 0]; + ret[COL_CURSOR * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F; + ret[COL_CURSOR * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->w = state->w; + ds->h = state->h; + ds->started = FALSE; + ds->tilesize = 0; /* not decided yet */ + ds->grid = snewn(ds->w * ds->h, signed char); + ds->bg = -1; + ds->cur_x = ds->cur_y = -1; + + memset(ds->grid, -99, ds->w * ds->h); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, + int x, int y, int v, int bg) +{ + if (v < 0) { + int coords[12]; + int hl = 0; + + if (v == -22 || v == -23) { + v += 20; + + /* + * Omit the highlights in this case. + */ + draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, + bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg); + draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT); + draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT); + } else { + /* + * Draw highlights to indicate the square is covered. + */ + coords[0] = x + TILE_SIZE - 1; + coords[1] = y + TILE_SIZE - 1; + coords[2] = x + TILE_SIZE - 1; + coords[3] = y; + coords[4] = x; + coords[5] = y + TILE_SIZE - 1; + draw_polygon(dr, coords, 3, COL_LOWLIGHT ^ hl, COL_LOWLIGHT ^ hl); + + coords[0] = x; + coords[1] = y; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT ^ hl, + COL_HIGHLIGHT ^ hl); + + draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, + TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, + bg); + } + + if (v == -1) { + /* + * Draw a flag. + */ +#define SETCOORD(n, dx, dy) do { \ + coords[(n)*2+0] = x + (int)(TILE_SIZE * (dx)); \ + coords[(n)*2+1] = y + (int)(TILE_SIZE * (dy)); \ +} while (0) + SETCOORD(0, 0.6F, 0.35F); + SETCOORD(1, 0.6F, 0.7F); + SETCOORD(2, 0.8F, 0.8F); + SETCOORD(3, 0.25F, 0.8F); + SETCOORD(4, 0.55F, 0.7F); + SETCOORD(5, 0.55F, 0.35F); + draw_polygon(dr, coords, 6, COL_FLAGBASE, COL_FLAGBASE); + + SETCOORD(0, 0.6F, 0.2F); + SETCOORD(1, 0.6F, 0.5F); + SETCOORD(2, 0.2F, 0.35F); + draw_polygon(dr, coords, 3, COL_FLAG, COL_FLAG); +#undef SETCOORD + + } else if (v == -3) { + /* + * Draw a question mark. + */ + draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2, + FONT_VARIABLE, TILE_SIZE * 6 / 8, + ALIGN_VCENTRE | ALIGN_HCENTRE, + COL_QUERY, "?"); + } + } else { + /* + * Clear the square to the background colour, and draw thin + * grid lines along the top and left. + * + * Exception is that for value 65 (mine we've just trodden + * on), we clear the square to COL_BANG. + */ + if (v & 32) { + bg = COL_WRONGNUMBER; + v &= ~32; + } + draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, + (v == 65 ? COL_BANG : + bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg)); + draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT); + draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT); + + if (v > 0 && v <= 8) { + /* + * Mark a number. + */ + char str[2]; + str[0] = v + '0'; + str[1] = '\0'; + draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2, + FONT_VARIABLE, TILE_SIZE * 7 / 8, + ALIGN_VCENTRE | ALIGN_HCENTRE, + (COL_1 - 1) + v, str); + + } else if (v >= 64) { + /* + * Mark a mine. + */ + { + int cx = x + TILE_SIZE / 2; + int cy = y + TILE_SIZE / 2; + int r = TILE_SIZE / 2 - 3; + + draw_circle(dr, cx, cy, 5*r/6, COL_MINE, COL_MINE); + draw_rect(dr, cx - r/6, cy - r, 2*(r/6)+1, 2*r+1, COL_MINE); + draw_rect(dr, cx - r, cy - r/6, 2*r+1, 2*(r/6)+1, COL_MINE); + draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT); + } + + if (v == 66) { + /* + * Cross through the mine. + */ + int dx; + for (dx = -1; dx <= +1; dx++) { + draw_line(dr, x + 3 + dx, y + 2, + x + TILE_SIZE - 3 + dx, + y + TILE_SIZE - 2, COL_CROSS); + draw_line(dr, x + TILE_SIZE - 3 + dx, y + 2, + x + 3 + dx, y + TILE_SIZE - 2, + COL_CROSS); + } + } + } + } + + draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y; + int mines, markers, bg; + int cx = -1, cy = -1, cmoved; + + if (flashtime) { + int frame = (int)(flashtime / FLASH_FRAME); + if (frame % 2) + bg = (ui->flash_is_death ? COL_BACKGROUND : COL_LOWLIGHT); + else + bg = (ui->flash_is_death ? COL_BANG : COL_HIGHLIGHT); + } else + bg = COL_BACKGROUND; + + if (!ds->started) { + int coords[10]; + + draw_rect(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); + draw_update(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER); + + /* + * Recessed area containing the whole puzzle. + */ + coords[0] = COORD(state->w) + OUTER_HIGHLIGHT_WIDTH - 1; + coords[1] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1; + coords[2] = COORD(state->w) + OUTER_HIGHLIGHT_WIDTH - 1; + coords[3] = COORD(0) - OUTER_HIGHLIGHT_WIDTH; + coords[4] = coords[2] - TILE_SIZE; + coords[5] = coords[3] + TILE_SIZE; + coords[8] = COORD(0) - OUTER_HIGHLIGHT_WIDTH; + coords[9] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1; + coords[6] = coords[8] + TILE_SIZE; + coords[7] = coords[9] - TILE_SIZE; + draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); + + coords[1] = COORD(0) - OUTER_HIGHLIGHT_WIDTH; + coords[0] = COORD(0) - OUTER_HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); + + ds->started = TRUE; + } + + if (ui->cur_visible) cx = ui->cur_x; + if (ui->cur_visible) cy = ui->cur_y; + cmoved = (cx != ds->cur_x || cy != ds->cur_y); + + /* + * Now draw the tiles. Also in this loop, count up the number + * of mines and mine markers. + */ + mines = markers = 0; + for (y = 0; y < ds->h; y++) + for (x = 0; x < ds->w; x++) { + int v = state->grid[y*ds->w+x], cc = 0; + + if (v == -1) + markers++; + if (state->layout->mines && state->layout->mines[y*ds->w+x]) + mines++; + + if (v >= 0 && v <= 8) { + /* + * Count up the flags around this tile, and if + * there are too _many_, highlight the tile. + */ + int dx, dy, flags = 0; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) { + int nx = x+dx, ny = y+dy; + if (nx >= 0 && nx < ds->w && + ny >= 0 && ny < ds->h && + state->grid[ny*ds->w+nx] == -1) + flags++; + } + + if (flags > v) + v |= 32; + } + + if ((v == -2 || v == -3) && + (abs(x-ui->hx) <= ui->hradius && abs(y-ui->hy) <= ui->hradius)) + v -= 20; + + if (cmoved && /* if cursor has moved, force redraw of curr and prev pos */ + ((x == cx && y == cy) || (x == ds->cur_x && y == ds->cur_y))) + cc = 1; + + if (ds->grid[y*ds->w+x] != v || bg != ds->bg || cc) { + draw_tile(dr, ds, COORD(x), COORD(y), v, + (x == cx && y == cy) ? COL_CURSOR : bg); + ds->grid[y*ds->w+x] = v; + } + } + ds->bg = bg; + ds->cur_x = cx; ds->cur_y = cy; + + if (!state->layout->mines) + mines = state->layout->n; + + /* + * Update the status bar. + */ + { + char statusbar[512]; + if (state->dead) { + sprintf(statusbar, "DEAD!"); + } else if (state->won) { + if (state->used_solve) + sprintf(statusbar, "Auto-solved."); + else + sprintf(statusbar, "COMPLETED!"); + } else { + sprintf(statusbar, "Marked: %d / %d", markers, mines); + } + if (ui->deaths) + sprintf(statusbar + strlen(statusbar), + " Deaths: %d", ui->deaths); + status_bar(dr, statusbar); + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (oldstate->used_solve || newstate->used_solve) + return 0.0F; + + if (dir > 0 && !oldstate->dead && !oldstate->won) { + if (newstate->dead) { + ui->flash_is_death = TRUE; + return 3 * FLASH_FRAME; + } + if (newstate->won) { + ui->flash_is_death = FALSE; + return 2 * FLASH_FRAME; + } + } + return 0.0F; +} + +static int game_status(const game_state *state) +{ + /* + * We report the game as lost only if the player has used the + * Solve function to reveal all the mines. Otherwise, we assume + * they'll undo and continue play. + */ + return state->won ? (state->used_solve ? -1 : +1) : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + if (state->dead || state->won || ui->completed || !state->layout->mines) + return FALSE; + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame mines +#endif + +const struct game thegame = { + "Mines", "games.mines", "mines", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + TRUE, game_timing_state, + BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON) | REQUIRE_RBUTTON, +}; + +#ifdef STANDALONE_OBFUSCATOR + +/* + * Vaguely useful stand-alone program which translates between + * obfuscated and clear Mines game descriptions. Pass in a game + * description on the command line, and if it's clear it will be + * obfuscated and vice versa. The output text should also be a + * valid game ID describing the same game. Like this: + * + * $ ./mineobfusc 9x9:4,4,mb071b49fbd1cb6a0d5868 + * 9x9:4,4,004000007c00010022080 + * $ ./mineobfusc 9x9:4,4,004000007c00010022080 + * 9x9:4,4,mb071b49fbd1cb6a0d5868 + */ + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int y, x; + + while (--argc > 0) { + char *p = *++argv; + if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + x = atoi(desc); + while (*desc && *desc != ',') desc++; + if (*desc) desc++; + y = atoi(desc); + while (*desc && *desc != ',') desc++; + if (*desc) desc++; + + printf("%s:%s\n", id, describe_layout(s->layout->mines, + p->w * p->h, + x, y, + (*desc != 'm'))); + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/misc.c b/apps/plugins/puzzles/misc.c new file mode 100644 index 0000000000..caf52cd520 --- /dev/null +++ b/apps/plugins/puzzles/misc.c @@ -0,0 +1,363 @@ +/* + * misc.c: Miscellaneous helpful functions. + */ + +#include "rbassert.h" +#include +#include +#include + +#include "puzzles.h" + +void free_cfg(config_item *cfg) +{ + config_item *i; + + for (i = cfg; i->type != C_END; i++) + if (i->type == C_STRING) + sfree(i->sval); + sfree(cfg); +} + +/* + * The Mines (among others) game descriptions contain the location of every + * mine, and can therefore be used to cheat. + * + * It would be pointless to attempt to _prevent_ this form of + * cheating by encrypting the description, since Mines is + * open-source so anyone can find out the encryption key. However, + * I think it is worth doing a bit of gentle obfuscation to prevent + * _accidental_ spoilers: if you happened to note that the game ID + * starts with an F, for example, you might be unable to put the + * knowledge of those mines out of your mind while playing. So, + * just as discussions of film endings are rot13ed to avoid + * spoiling it for people who don't want to be told, we apply a + * keyless, reversible, but visually completely obfuscatory masking + * function to the mine bitmap. + */ +void obfuscate_bitmap(unsigned char *bmp, int bits, int decode) +{ + int bytes, firsthalf, secondhalf; + struct step { + unsigned char *seedstart; + int seedlen; + unsigned char *targetstart; + int targetlen; + } steps[2]; + int i, j; + + /* + * My obfuscation algorithm is similar in concept to the OAEP + * encoding used in some forms of RSA. Here's a specification + * of it: + * + * + We have a `masking function' which constructs a stream of + * pseudorandom bytes from a seed of some number of input + * bytes. + * + * + We pad out our input bit stream to a whole number of + * bytes by adding up to 7 zero bits on the end. (In fact + * the bitmap passed as input to this function will already + * have had this done in practice.) + * + * + We divide the _byte_ stream exactly in half, rounding the + * half-way position _down_. So an 81-bit input string, for + * example, rounds up to 88 bits or 11 bytes, and then + * dividing by two gives 5 bytes in the first half and 6 in + * the second half. + * + * + We generate a mask from the second half of the bytes, and + * XOR it over the first half. + * + * + We generate a mask from the (encoded) first half of the + * bytes, and XOR it over the second half. Any null bits at + * the end which were added as padding are cleared back to + * zero even if this operation would have made them nonzero. + * + * To de-obfuscate, the steps are precisely the same except + * that the final two are reversed. + * + * Finally, our masking function. Given an input seed string of + * bytes, the output mask consists of concatenating the SHA-1 + * hashes of the seed string and successive decimal integers, + * starting from 0. + */ + + bytes = (bits + 7) / 8; + firsthalf = bytes / 2; + secondhalf = bytes - firsthalf; + + steps[decode ? 1 : 0].seedstart = bmp + firsthalf; + steps[decode ? 1 : 0].seedlen = secondhalf; + steps[decode ? 1 : 0].targetstart = bmp; + steps[decode ? 1 : 0].targetlen = firsthalf; + + steps[decode ? 0 : 1].seedstart = bmp; + steps[decode ? 0 : 1].seedlen = firsthalf; + steps[decode ? 0 : 1].targetstart = bmp + firsthalf; + steps[decode ? 0 : 1].targetlen = secondhalf; + + for (i = 0; i < 2; i++) { + SHA_State base, final; + unsigned char digest[20]; + char numberbuf[80]; + int digestpos = 20, counter = 0; + + SHA_Init(&base); + SHA_Bytes(&base, steps[i].seedstart, steps[i].seedlen); + + for (j = 0; j < steps[i].targetlen; j++) { + if (digestpos >= 20) { + sprintf(numberbuf, "%d", counter++); + final = base; + SHA_Bytes(&final, numberbuf, strlen(numberbuf)); + SHA_Final(&final, digest); + digestpos = 0; + } + steps[i].targetstart[j] ^= digest[digestpos++]; + } + + /* + * Mask off the pad bits in the final byte after both steps. + */ + if (bits % 8) + bmp[bits / 8] &= 0xFF & (0xFF00 >> (bits % 8)); + } +} + +/* err, yeah, these two pretty much rely on unsigned char being 8 bits. + * Platforms where this is not the case probably have bigger problems + * than just making these two work, though... */ +char *bin2hex(const unsigned char *in, int inlen) +{ + char *ret = snewn(inlen*2 + 1, char), *p = ret; + int i; + + for (i = 0; i < inlen*2; i++) { + int v = in[i/2]; + if (i % 2 == 0) v >>= 4; + *p++ = "0123456789abcdef"[v & 0xF]; + } + *p = '\0'; + return ret; +} + +unsigned char *hex2bin(const char *in, int outlen) +{ + unsigned char *ret = snewn(outlen, unsigned char); + int i; + + memset(ret, 0, outlen*sizeof(unsigned char)); + for (i = 0; i < outlen*2; i++) { + int c = in[i]; + int v; + + assert(c != 0); + if (c >= '0' && c <= '9') + v = c - '0'; + else if (c >= 'a' && c <= 'f') + v = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + v = c - 'A' + 10; + else + v = 0; + + ret[i / 2] |= v << (4 * (1 - (i % 2))); + } + return ret; +} + +void game_mkhighlight_specific(frontend *fe, float *ret, + int background, int highlight, int lowlight) +{ + float max; + int i; + + /* + * Drop the background colour so that the highlight is + * noticeably brighter than it while still being under 1. + */ + max = ret[background*3]; + for (i = 1; i < 3; i++) + if (ret[background*3+i] > max) + max = ret[background*3+i]; + if (max * 1.2F > 1.0F) { + for (i = 0; i < 3; i++) + ret[background*3+i] /= (max * 1.2F); + } + + for (i = 0; i < 3; i++) { + if (highlight >= 0) + ret[highlight * 3 + i] = ret[background * 3 + i] * 1.2F; + if (lowlight >= 0) + ret[lowlight * 3 + i] = ret[background * 3 + i] * 0.8F; + } +} + +void game_mkhighlight(frontend *fe, float *ret, + int background, int highlight, int lowlight) +{ + frontend_default_colour(fe, &ret[background * 3]); + game_mkhighlight_specific(fe, ret, background, highlight, lowlight); +} + +static void memswap(void *av, void *bv, int size) +{ + char tmpbuf[512]; + char *a = av, *b = bv; + + while (size > 0) { + int thislen = min(size, sizeof(tmpbuf)); + memcpy(tmpbuf, a, thislen); + memcpy(a, b, thislen); + memcpy(b, tmpbuf, thislen); + a += thislen; + b += thislen; + size -= thislen; + } +} + +void shuffle(void *array, int nelts, int eltsize, random_state *rs) +{ + char *carray = (char *)array; + int i; + + for (i = nelts; i-- > 1 ;) { + int j = random_upto(rs, i+1); + if (j != i) + memswap(carray + eltsize * i, carray + eltsize * j, eltsize); + } +} + +void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour) +{ + int x0 = x, x1 = x+w-1, y0 = y, y1 = y+h-1; + int coords[8]; + + coords[0] = x0; + coords[1] = y0; + coords[2] = x0; + coords[3] = y1; + coords[4] = x1; + coords[5] = y1; + coords[6] = x1; + coords[7] = y0; + + draw_polygon(dr, coords, 4, -1, colour); +} + +void draw_rect_corners(drawing *dr, int cx, int cy, int r, int col) +{ + draw_line(dr, cx - r, cy - r, cx - r, cy - r/2, col); + draw_line(dr, cx - r, cy - r, cx - r/2, cy - r, col); + draw_line(dr, cx - r, cy + r, cx - r, cy + r/2, col); + draw_line(dr, cx - r, cy + r, cx - r/2, cy + r, col); + draw_line(dr, cx + r, cy - r, cx + r, cy - r/2, col); + draw_line(dr, cx + r, cy - r, cx + r/2, cy - r, col); + draw_line(dr, cx + r, cy + r, cx + r, cy + r/2, col); + draw_line(dr, cx + r, cy + r, cx + r/2, cy + r, col); +} + +void move_cursor(int button, int *x, int *y, int maxw, int maxh, int wrap) +{ + int dx = 0, dy = 0; + switch (button) { + case CURSOR_UP: dy = -1; break; + case CURSOR_DOWN: dy = 1; break; + case CURSOR_RIGHT: dx = 1; break; + case CURSOR_LEFT: dx = -1; break; + default: return; + } + if (wrap) { + *x = (*x + dx + maxw) % maxw; + *y = (*y + dy + maxh) % maxh; + } else { + *x = min(max(*x+dx, 0), maxw - 1); + *y = min(max(*y+dy, 0), maxh - 1); + } +} + +/* Used in netslide.c and sixteen.c for cursor movement around edge. */ + +int c2pos(int w, int h, int cx, int cy) +{ + if (cy == -1) + return cx; /* top row, 0 .. w-1 (->) */ + else if (cx == w) + return w + cy; /* R col, w .. w+h -1 (v) */ + else if (cy == h) + return w + h + (w-cx-1); /* bottom row, w+h .. w+h+w-1 (<-) */ + else if (cx == -1) + return w + h + w + (h-cy-1); /* L col, w+h+w .. w+h+w+h-1 (^) */ + + assert(!"invalid cursor pos!"); + return -1; /* not reached */ +} + +int c2diff(int w, int h, int cx, int cy, int button) +{ + int diff = 0; + + assert(IS_CURSOR_MOVE(button)); + + /* Obvious moves around edge. */ + if (cy == -1) + diff = (button == CURSOR_RIGHT) ? +1 : (button == CURSOR_LEFT) ? -1 : diff; + if (cy == h) + diff = (button == CURSOR_RIGHT) ? -1 : (button == CURSOR_LEFT) ? +1 : diff; + if (cx == -1) + diff = (button == CURSOR_UP) ? +1 : (button == CURSOR_DOWN) ? -1 : diff; + if (cx == w) + diff = (button == CURSOR_UP) ? -1 : (button == CURSOR_DOWN) ? +1 : diff; + + if (button == CURSOR_LEFT && cx == w && (cy == 0 || cy == h-1)) + diff = (cy == 0) ? -1 : +1; + if (button == CURSOR_RIGHT && cx == -1 && (cy == 0 || cy == h-1)) + diff = (cy == 0) ? +1 : -1; + if (button == CURSOR_DOWN && cy == -1 && (cx == 0 || cx == w-1)) + diff = (cx == 0) ? -1 : +1; + if (button == CURSOR_UP && cy == h && (cx == 0 || cx == w-1)) + diff = (cx == 0) ? +1 : -1; + + debug(("cx,cy = %d,%d; w%d h%d, diff = %d", cx, cy, w, h, diff)); + return diff; +} + +void pos2c(int w, int h, int pos, int *cx, int *cy) +{ + int max = w+h+w+h; + + pos = (pos + max) % max; + + if (pos < w) { + *cx = pos; *cy = -1; return; + } + pos -= w; + if (pos < h) { + *cx = w; *cy = pos; return; + } + pos -= h; + if (pos < w) { + *cx = w-pos-1; *cy = h; return; + } + pos -= w; + if (pos < h) { + *cx = -1; *cy = h-pos-1; return; + } + assert(!"invalid pos, huh?"); /* limited by % above! */ +} + +void draw_text_outline(drawing *dr, int x, int y, int fonttype, + int fontsize, int align, + int text_colour, int outline_colour, char *text) +{ + if (outline_colour > -1) { + draw_text(dr, x-1, y, fonttype, fontsize, align, outline_colour, text); + draw_text(dr, x+1, y, fonttype, fontsize, align, outline_colour, text); + draw_text(dr, x, y-1, fonttype, fontsize, align, outline_colour, text); + draw_text(dr, x, y+1, fonttype, fontsize, align, outline_colour, text); + } + draw_text(dr, x, y, fonttype, fontsize, align, text_colour, text); +} + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/mkauto.sh b/apps/plugins/puzzles/mkauto.sh new file mode 100755 index 0000000000..297212ad4d --- /dev/null +++ b/apps/plugins/puzzles/mkauto.sh @@ -0,0 +1,2 @@ +#! /bin/sh +autoreconf -i && rm -rf autom4te.cache diff --git a/apps/plugins/puzzles/mkfiles.pl b/apps/plugins/puzzles/mkfiles.pl new file mode 100755 index 0000000000..c1623dfd12 --- /dev/null +++ b/apps/plugins/puzzles/mkfiles.pl @@ -0,0 +1,1807 @@ +#!/usr/bin/env perl +# +# Cross-platform Makefile generator. +# +# Reads the file `Recipe' to determine the list of generated +# executables and their component objects. Then reads the source +# files to compute #include dependencies. Finally, writes out the +# various target Makefiles. + +# PuTTY specifics which could still do with removing: +# - Mac makefile is not portabilised at all. Include directories +# are hardwired, and also the libraries are fixed. This is +# mainly because I was too scared to go anywhere near it. +# - sbcsgen.pl is still run at startup. + +# Other things undone: +# - special-define objects (foo.o[PREPROCSYMBOL]) are not +# supported in the mac or vcproj makefiles. + +use warnings; +use IO::Handle; +use Cwd; +use File::Basename; + +while ($#ARGV >= 0) { + if ($ARGV[0] eq "-U") { + # Convenience for Unix users: -U means that after we finish what + # we're doing here, we also run mkauto.sh and then 'configure'. So + # it's a one-stop shop for regenerating the actual end-product + # Unix makefile. + # + # Arguments supplied after -U go to configure. + $do_unix = 1; + shift @ARGV; + @confargs = @ARGV; + @ARGV = (); + } else { + die "unrecognised command-line argument '$ARGV[0]'\n"; + } +} + +@filestack = (); +$in = new IO::Handle; +open $in, "Recipe" or do { + # We want to deal correctly with being run from one of the + # subdirs in the source tree. So if we can't find Recipe here, + # try one level up. + chdir ".."; + open $in, "Recipe" or die "unable to open Recipe file\n"; +}; +push @filestack, $in; + +# HACK: One of the source files in `charset' is auto-generated by +# sbcsgen.pl. We need to generate that _now_, before attempting +# dependency analysis. +eval 'chdir "charset"; require "sbcsgen.pl"; chdir ".."'; + +@srcdirs = ("./"); + +$divert = undef; # ref to array of refs of scalars in which text is + # currently being put +$help = ""; # list of newline-free lines of help text +$project_name = "project"; # this is a good enough default +%makefiles = (); # maps makefile types to output makefile pathnames +%makefile_extra = (); # maps makefile types to extra Makefile text +%programs = (); # maps prog name + type letter to listref of objects/resources +%groups = (); # maps group name to listref of objects/resources + +@allobjs = (); # all object file names + +readinput: while (1) { + $in = $filestack[$#filestack]; + while (not defined ($_ = <$in>)) { + close $filestack[$#filestack]; + pop @filestack; + last readinput if 0 == scalar @filestack; + $in = $filestack[$#filestack]; + } + chomp; + @_ = split; + + # If we're gathering help text, keep doing so. + if (defined $divert) { + if ((defined $_[0]) && $_[0] eq "!end") { + $divert = undef; + } else { + for my $ref (@$divert) { + ${$ref} .= "$_\n"; + } + } + next; + } + # Skip comments and blank lines. + next if /^\s*#/ or scalar @_ == 0; + + if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = [\$help]; next; } + if ($_[0] eq "!name") { $project_name = $_[1]; next; } + if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; } + if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;} + if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;} + if ($_[0] eq "!cflags" and &mfval($_[1])) { + ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line + $rest = 1 if $rest eq ""; + $cflags{$_[1]}->{$_[2]} = $rest; + next; + } + if ($_[0] eq "!begin") { + my @args = @_; + shift @args; + $divert = []; + for my $component (@args) { + if ($component =~ /^>(.*)/) { + push @$divert, \$auxfiles{$1}; + } elsif ($component =~ /^([^_]*)(_.*)?$/ and &mfval($1)) { + push @$divert, \$makefile_extra{$component}; + } + } + next; + } + if ($_[0] eq "!include") { + @newfiles = (); + for ($i = 1; $i <= $#_; $i++) { + push @newfiles, (sort glob $_[$i]); + } + for ($i = $#newfiles; $i >= 0; $i--) { + $file = $newfiles[$i]; + $f = new IO::Handle; + open $f, "<$file" or die "unable to open include file '$file'\n"; + push @filestack, $f; + } + next; + } + + # Now we have an ordinary line. See if it's an = line, a : line + # or a + line. + @objs = @_; + + if ($_[0] eq "+") { + $listref = $lastlistref; + $prog = undef; + die "$.: unexpected + line\n" if !defined $lastlistref; + } elsif ($_[1] eq "=") { + $groups{$_[0]} = []; + $listref = $groups{$_[0]}; + $prog = undef; + shift @objs; # eat the group name + } elsif ($_[1] eq "+=") { + $groups{$_[0]} = [] if !defined $groups{$_[0]}; + $listref = $groups{$_[0]}; + $prog = undef; + shift @objs; # eat the group name + } elsif ($_[1] eq ":") { + $listref = []; + $prog = $_[0]; + shift @objs; # eat the program name + } else { + die "$.: unrecognised line type: '$_'\n"; + } + shift @objs; # eat the +, the = or the : + + while (scalar @objs > 0) { + $i = shift @objs; + if ($groups{$i}) { + foreach $j (@{$groups{$i}}) { unshift @objs, $j; } + } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or + $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) { + $type = substr($i,1,(length $i)-2); + } else { + if ($i =~ /\?$/) { + # Object files with a trailing question mark are optional: + # the build can proceed fine without them, so we only use + # them if their primary source files are present. + $i =~ s/\?$//; + $i = undef unless defined &finddep($i); + } elsif ($i =~ /\|/) { + # Object file descriptions containing a vertical bar are + # lists of choices: we use the _first_ one whose primary + # source file is present. + @options = split /\|/, $i; + $j = undef; + foreach $k (@options) { + $j=$k, last if defined &finddep($k); + } + die "no alternative found for $i\n" unless defined $j; + $i = $j; + } + if (defined $i) { + push @$listref, $i; + push @allobjs, $i; + } + } + } + if ($prog and $type) { + die "multiple program entries for $prog [$type]\n" + if defined $programs{$prog . "," . $type}; + $programs{$prog . "," . $type} = $listref; + } + $lastlistref = $listref; +} + +foreach $aux (sort keys %auxfiles) { + open AUX, ">$aux"; + print AUX $auxfiles{$aux}; + close AUX; +} + +# Find object file names with predefines (in square brackets after +# the module name), and decide on actual object names for them. +foreach $i (@allobjs) { + if ($i !~ /\[/) { + $objname{$i} = $i; + $srcname{$i} = $i; + $usedobjname{$i} = 1; + } +} +foreach $i (@allobjs) { + if ($i =~ /^(.*)\[([^\]]*)/) { + $defs{$i} = [ split ",",$2 ]; + $srcname{$i} = $s = $1; + $index = 1; + while (1) { + $maxlen = length $s; + $maxlen = 8 if $maxlen < 8; + $chop = $maxlen - length $index; + $chop = length $s if $chop > length $s; + $chop = 0 if $chop < 0; + $name = substr($s, 0, $chop) . $index; + $index++, next if $usedobjname{$name}; + $objname{$i} = $name; + $usedobjname{$name} = 1; + last; + } + } +} + +# Now retrieve the complete list of objects and resource files, and +# construct dependency data for them. While we're here, expand the +# object list for each program, and complain if its type isn't set. +@prognames = sort keys %programs; +%depends = (); +@scanlist = (); +foreach $i (@prognames) { + ($prog, $type) = split ",", $i; + # Strip duplicate object names. + $prev = ''; + @list = grep { $status = ($prev ne $_); $prev=$_; $status } + sort @{$programs{$i}}; + $programs{$i} = [@list]; + foreach $jj (@list) { + $j = $srcname{$jj}; + $file = &finddep($j); + if (defined $file) { + $depends{$jj} = [$file]; + push @scanlist, $file; + } + } +} + +# Scan each file on @scanlist and find further inclusions. +# Inclusions are given by lines of the form `#include "otherfile"' +# (system headers are automatically ignored by this because they'll +# be given in angle brackets). Files included by this method are +# added back on to @scanlist to be scanned in turn (if not already +# done). +# +# Resource scripts (.rc) can also include a file by means of a line +# ending `ICON "filename"'. Files included by this method are not +# added to @scanlist because they can never include further files. +# +# In this pass we write out a hash %further which maps a source +# file name into a listref containing further source file names. + +%further = (); +while (scalar @scanlist > 0) { + $file = shift @scanlist; + next if defined $further{$file}; # skip if we've already done it + $further{$file} = []; + $dirfile = &findfile($file); + open IN, "$dirfile" or die "unable to open source file $file\n"; + while () { + chomp; + /^\s*#include\s+\"([^\"]+)\"/ and do { + push @{$further{$file}}, $1; + push @scanlist, $1; + next; + }; + /ICON\s+\"([^\"]+)\"\s*$/ and do { + push @{$further{$file}}, $1; + next; + } + } + close IN; +} + +# Now we're ready to generate the final dependencies section. For +# each key in %depends, we must expand the dependencies list by +# iteratively adding entries from %further. +foreach $i (keys %depends) { + %dep = (); + @scanlist = @{$depends{$i}}; + foreach $i (@scanlist) { $dep{$i} = 1; } + while (scalar @scanlist > 0) { + $file = shift @scanlist; + foreach $j (@{$further{$file}}) { + if (!$dep{$j}) { + $dep{$j} = 1; + push @{$depends{$i}}, $j; + push @scanlist, $j; + } + } + } +# printf "%s: %s\n", $i, join ' ',@{$depends{$i}}; +} + +# Validation of input. + +sub mfval($) { + my ($type) = @_; + # Returns true if the argument is a known makefile type. Otherwise, + # prints a warning and returns false; + if (grep { $type eq $_ } + ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc")) { + return 1; + } + warn "$.:unknown makefile type '$type'\n"; + return 0; +} + +# Utility routines while writing out the Makefiles. + +sub dirpfx { + my ($path) = shift @_; + my ($sep) = shift @_; + my $ret = ""; + my $i; + while (($i = index $path, $sep) >= 0) { + $path = substr $path, ($i + length $sep); + $ret .= "..$sep"; + } + return $ret; +} + +sub findfile { + my ($name) = @_; + my $dir; + my $i; + my $outdir = undef; + unless (defined $findfilecache{$name}) { + $i = 0; + foreach $dir (@srcdirs) { + $outdir = $dir, $i++ if -f "$dir$name"; + } + die "multiple instances of source file $name\n" if $i > 1; + $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef); + } + return $findfilecache{$name}; +} + +sub finddep { + my $j = shift @_; + my $file; + # Find the first dependency of an object. + + # Dependencies for "x" start with "x.c" or "x.m" (depending on + # which one exists). + # Dependencies for "x.res" start with "x.rc". + # Dependencies for "x.rsrc" start with "x.r". + # Both types of file are pushed on the list of files to scan. + # Libraries (.lib) don't have dependencies at all. + if ($j =~ /^(.*)\.res$/) { + $file = "$1.rc"; + } elsif ($j =~ /^(.*)\.rsrc$/) { + $file = "$1.r"; + } elsif ($j !~ /\./) { + $file = "$j.c"; + $file = "$j.m" unless &findfile($file); + } else { + # For everything else, we assume it's its own dependency. + $file = $j; + } + $file = undef unless &findfile($file); + return $file; +} + +sub objects { + my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_; + my @ret; + my ($i, $x, $y); + ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl); + @ret = (); + foreach $ii (@{$programs{$prog}}) { + $i = $objname{$ii}; + $x = ""; + if ($i =~ /^(.*)\.(res|rsrc)/) { + $y = $1; + ($x = $rtmpl) =~ s/X/$y/; + } elsif ($i =~ /^(.*)\.lib/) { + $y = $1; + ($x = $ltmpl) =~ s/X/$y/; + } elsif ($i !~ /\./) { + ($x = $otmpl) =~ s/X/$i/; + } + push @ret, $x if $x ne ""; + } + return join " ", @ret; +} + +sub special { + my ($prog, $suffix) = @_; + my @ret; + my ($i, $x, $y); + @ret = (); + foreach $ii (@{$programs{$prog}}) { + $i = $objname{$ii}; + if (substr($i, (length $i) - (length $suffix)) eq $suffix) { + push @ret, $i; + } + } + return join " ", @ret; +} + +sub splitline { + my ($line, $width, $splitchar) = @_; + my $result = ""; + my $len; + $len = (defined $width ? $width : 76); + $splitchar = (defined $splitchar ? $splitchar : '\\'); + while (length $line > $len) { + $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/; + $result .= $1; + $result .= " ${splitchar}\n\t\t" if $2 ne ''; + $line = $2; + $len = 60; + } + return $result . $line; +} + +sub deps { + my ($otmpl, $rtmpl, $prefix, $dirsep, $depchar, $splitchar) = @_; + my ($i, $x, $y); + my @deps; + my @ret; + @ret = (); + $depchar ||= ':'; + foreach $ii (sort keys %depends) { + $i = $objname{$ii}; + next if $specialobj{$mftyp}->{$i}; + if ($i =~ /^(.*)\.(res|rsrc)/) { + next if !defined $rtmpl; + $y = $1; + ($x = $rtmpl) =~ s/X/$y/; + } else { + ($x = $otmpl) =~ s/X/$i/; + } + @deps = @{$depends{$ii}}; + # Skip things which are their own dependency. + next if grep { $_ eq $i } @deps; + @deps = map { + $_ = &findfile($_); + s/\//$dirsep/g; + $_ = $prefix . $_; + } @deps; + push @ret, {obj => $x, deps => [@deps], defs => $defs{$ii}}; + } + return @ret; +} + +sub prognames { + my ($types) = @_; + my ($n, $prog, $type); + my @ret; + @ret = (); + foreach $n (@prognames) { + ($prog, $type) = split ",", $n; + push @ret, $n if index(":$types:", ":$type:") >= 0; + } + return @ret; +} + +sub progrealnames { + my ($types) = @_; + my ($n, $prog, $type); + my @ret; + @ret = (); + foreach $n (@prognames) { + ($prog, $type) = split ",", $n; + push @ret, $prog if index(":$types:", ":$type:") >= 0; + } + return @ret; +} + +sub manpages { + my ($types,$suffix) = @_; + + # assume that all UNIX programs have a man page + if($suffix eq "1" && $types =~ /:X:/) { + return map("$_.1", &progrealnames($types)); + } + return (); +} + +$orig_dir = cwd; + +# Now we're ready to output the actual Makefiles. + +if (defined $makefiles{'cygwin'}) { + $mftyp = 'cygwin'; + $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); + + ##-- CygWin makefile + open OUT, ">$makefiles{'cygwin'}"; select OUT; + print + "# Makefile for $project_name under cygwin.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # gcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# You can define this path to point at your tools if you need to\n". + "# TOOLPATH = c:\\cygwin\\bin\\ # or similar, if you're running Windows\n". + "# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/\n". + "CC = \$(TOOLPATH)gcc\n". + "RC = \$(TOOLPATH)windres\n". + "# Uncomment the following two lines to compile under Winelib\n". + "# CC = winegcc\n". + "# RC = wrc\n". + "# You may also need to tell windres where to find include files:\n". + "# RCINC = --include-dir c:\\cygwin\\include\\\n". + "\n". + &splitline("CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT". + " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP " . + (join " ", map {"-I$dirpfx$_"} @srcdirs)) . + "\n". + "LDFLAGS = -mno-cygwin -s\n". + &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1". + " --define WINVER=0x0400 --define MINGW32_FIX=1 " . + (join " ", map {"--include $dirpfx$_"} @srcdirs) )."\n". + "\n"; + print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); + print "\n\n"; + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.o", "X.res.o", undef); + print &splitline($prog . ".exe: " . $objstr), "\n"; + my $mw = $type eq "G" ? " -mwindows" : ""; + $libstr = &objects($p, undef, undef, "-lX"); + print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " . + "-Wl,-Map,$prog.map " . + $objstr . " $libstr", 69), "\n\n"; + } + foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + if ($d->{obj} =~ /\.res\.o$/) { + print "\t\$(RC) \$(FWHACK) \$(RCFL) \$(RCFLAGS) \$< \$\@\n"; + } else { + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS)" . + " \$(XFLAGS)$deflist -c \$< -o \$\@\n"; + } + } + print "\n"; + print $makefile_extra{'cygwin'} || ""; + print "\nclean:\n". + "\trm -f *.o *.exe *.res.o *.map\n". + "\n"; + select STDOUT; close OUT; + +} + +##-- Borland makefile +if (defined $makefiles{'borland'}) { + $mftyp = 'borland'; + $dirpfx = &dirpfx($makefiles{'borland'}, "\\"); + + %stdlibs = ( # Borland provides many Win32 API libraries intrinsically + "advapi32" => 1, + "comctl32" => 1, + "comdlg32" => 1, + "gdi32" => 1, + "imm32" => 1, + "shell32" => 1, + "user32" => 1, + "winmm" => 1, + "winspool" => 1, + "wsock32" => 1, + ); + open OUT, ">$makefiles{'borland'}"; select OUT; + print + "# Makefile for $project_name under Borland C.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # bcc32 command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# If you rename this file to `Makefile', you should change this line,\n". + "# so that the .rsp files still depend on the correct makefile.\n". + "MAKEFILE = Makefile.bor\n". + "\n". + "# C compilation flags\n". + "CFLAGS = -D_WINDOWS -DWINVER=0x0401\n". + "\n". + "# Get include directory for resource compiler\n". + "!if !\$d(BCB)\n". + "BCB = \$(MAKEDIR)\\..\n". + "!endif\n". + "\n"; + print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); + print "\n\n"; + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; + my $ap = ($type eq "G") ? "-aa" : "-ap"; + print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n"; + } + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + print $prog, ".rsp: \$(MAKEFILE)\n"; + $objstr = &objects($p, "X.obj", undef, undef); + @objlist = split " ", $objstr; + @objlines = (""); + foreach $i (@objlist) { + if (length($objlines[$#objlines] . " $i") > 50) { + push @objlines, ""; + } + $objlines[$#objlines] .= " $i"; + } + $c0w = ($type eq "G") ? "c0w32" : "c0x32"; + print "\techo $c0w + > $prog.rsp\n"; + for ($i=0; $i<=$#objlines; $i++) { + $plus = ($i < $#objlines ? " +" : ""); + print "\techo$objlines[$i]$plus >> $prog.rsp\n"; + } + print "\techo $prog.exe >> $prog.rsp\n"; + $objstr = &objects($p, "X.obj", "X.res", undef); + @libs = split " ", &objects($p, undef, undef, "X"); + @libs = grep { !$stdlibs{$_} } @libs; + unshift @libs, "cw32", "import32"; + $libstr = join ' ', @libs; + print "\techo nul,$libstr, >> $prog.rsp\n"; + print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n"; + print "\n"; + } + foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + if ($d->{obj} =~ /\.res$/) { + print &splitline("\tbrcc32 \$(FWHACK) \$(RCFL) " . + "-i \$(BCB)\\include -r -DNO_WINRESRC_H -DWIN32". + " -D_WIN32 -DWINVER=0x0401 \$*.rc",69)."\n"; + } else { + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)" . + " \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist ". + (join " ", map {"-I$dirpfx$_"} @srcdirs) . + " /o$d->{obj} /c ".$d->{deps}->[0],69)."\n"; + } + } + print "\n"; + print $makefile_extra{'borland'} || ""; + print "\nclean:\n". + "\t-del *.obj\n". + "\t-del *.exe\n". + "\t-del *.res\n". + "\t-del *.pch\n". + "\t-del *.aps\n". + "\t-del *.il*\n". + "\t-del *.pdb\n". + "\t-del *.rsp\n". + "\t-del *.tds\n". + "\t-del *.\$\$\$\$\$\$\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'vc'}) { + $mftyp = 'vc'; + $dirpfx = &dirpfx($makefiles{'vc'}, "\\"); + + ##-- Visual C++ makefile + open OUT, ">$makefiles{'vc'}"; select OUT; + print + "# Makefile for $project_name under Visual C.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + print $help; + print + "\n". + "# If you rename this file to `Makefile', you should change this line,\n". + "# so that the .rsp files still depend on the correct makefile.\n". + "MAKEFILE = Makefile.vc\n". + "\n". + "# C compilation flags\n". + "CFLAGS = /nologo /W3 /O1 /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 /I.\n". + "LFLAGS = /incremental:no /fixed\n". + "\n"; + print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); + print "\n\n"; + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; + print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n"; + } + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + print $prog, ".rsp: \$(MAKEFILE)\n"; + $objstr = &objects($p, "X.obj", "X.res", "X.lib"); + @objlist = split " ", $objstr; + @objlines = (""); + foreach $i (@objlist) { + if (length($objlines[$#objlines] . " $i") > 50) { + push @objlines, ""; + } + $objlines[$#objlines] .= " $i"; + } + $subsys = ($type eq "G") ? "windows" : "console"; + print "\techo /nologo /subsystem:$subsys > $prog.rsp\n"; + for ($i=0; $i<=$#objlines; $i++) { + print "\techo$objlines[$i] >> $prog.rsp\n"; + } + print "\n"; + } + foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + if ($d->{obj} =~ /\.res$/) { + print "\trc \$(FWHACK) \$(RCFL) -r -DWIN32 -D_WIN32 ". + "-DWINVER=0x0400 -fo".$d->{obj}." ".$d->{deps}->[0]."\n"; + } else { + $deflist = join "", map { " /D$_" } @{$d->{defs}}; + print "\tcl \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist". + " /c ".$d->{deps}->[0]." /Fo$d->{obj}\n"; + } + } + print "\n"; + print $makefile_extra{'vc'} || ""; + print "\nclean: tidy\n". + "\t-del *.exe\n\n". + "tidy:\n". + "\t-del *.obj\n". + "\t-del *.res\n". + "\t-del *.pch\n". + "\t-del *.aps\n". + "\t-del *.ilk\n". + "\t-del *.pdb\n". + "\t-del *.rsp\n". + "\t-del *.dsp\n". + "\t-del *.dsw\n". + "\t-del *.ncb\n". + "\t-del *.opt\n". + "\t-del *.plg\n". + "\t-del *.map\n". + "\t-del *.idb\n". + "\t-del debug.log\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'wce'}) { + $mftyp = 'wce'; + $dirpfx = &dirpfx($makefiles{'wce'}, "\\"); + + ##-- eMbedded Visual C PocketPC makefile + open OUT, ">$makefiles{'wce'}"; select OUT; + print + "# Makefile for $project_name on PocketPC using eMbedded Visual C.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + print $help; + print + "\n". + "# If you rename this file to `Makefile', you should change this line,\n". + "# so that the .rsp files still depend on the correct makefile.\n". + "MAKEFILE = Makefile.wce\n". + "\n". + "# This makefile expects the environment to have been set up by one\n". + "# of the PocketPC batch files wcearmv4.bat and wceemulator.bat. No\n". + "# other build targets are currently supported, because they would\n". + "# need a section in this if statement.\n". + "!if \"\$(TARGETCPU)\" == \"emulator\"\n". + "PLATFORM_DEFS=/D \"_i386_\" /D \"i_386_\" /D \"_X86_\" /D \"x86\"\n". + "CC=cl\n". + "BASELIBS=commctrl.lib coredll.lib corelibc.lib aygshell.lib\n". + "MACHINE=IX86\n". + "!else\n". + "PLATFORM_DEFS=/D \"ARM\" /D \"_ARM_\" /D \"ARMV4\"\n". + "CC=clarm\n". + "BASELIBS=commctrl.lib coredll.lib aygshell.lib\n". + "MACHINE=ARM\n". + "!endif\n". + "\n". + "# C compilation flags\n". + "CFLAGS = /nologo /W3 /O1 /MC /D _WIN32_WCE=420 /D \"WIN32_PLATFORM_PSPC=400\" /D UNDER_CE=420 \\\n". + " \$(PLATFORM_DEFS) \\\n". + " /D \"UNICODE\" /D \"_UNICODE\" /D \"NDEBUG\" /D \"NO_HTMLHELP\"\n". + "\n". + "LFLAGS = /nologo /incremental:no \\\n". + " /base:0x00010000 /stack:0x10000,0x1000 /entry:WinMainCRTStartup \\\n". + " /nodefaultlib:libc.lib /nodefaultlib:libcmt.lib /nodefaultlib:msvcrt.lib /nodefaultlib:OLDNAMES.lib \\\n". + " /subsystem:windowsce,4.20 /align:4096 /MACHINE:\$(MACHINE)\n". + "\n". + "RCFL = /d UNDER_CE=420 /d _WIN32_WCE=420 /d \"WIN32_PLATFORM_PSPC=400\" \\\n". + " \$(PLATFORM_DEFS) \\\n". + " /d \"NDEBUG\" /d \"UNICODE\" /d \"_UNICODE\"\n". + "\n"; + print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G")); + print "\n\n"; + foreach $p (&prognames("G")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; + print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n"; + } + foreach $p (&prognames("G")) { + ($prog, $type) = split ",", $p; + print $prog, ".rsp: \$(MAKEFILE)\n"; + $objstr = &objects($p, "X.obj", "X.res", undef); + @objlist = split " ", $objstr; + @objlines = (""); + foreach $i (@objlist) { + if (length($objlines[$#objlines] . " $i") > 50) { + push @objlines, ""; + } + $objlines[$#objlines] .= " $i"; + } + print "\techo \$(BASELIBS) > $prog.rsp\n"; + for ($i=0; $i<=$#objlines; $i++) { + print "\techo$objlines[$i] >> $prog.rsp\n"; + } + print "\n"; + } + foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + if ($d->{obj} =~ /\.res$/) { + print "\trc \$(FWHACK) \$(RCFL) -r -fo". + $d->{obj}." ".$d->{deps}->[0]."\n"; + } else { + $deflist = join "", map { " /D$_" } @{$d->{defs}}; + print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist". + " /c ".$d->{deps}->[0]." /Fo$d->{obj}\n"; + } + } + print "\n"; + print $makefile_extra{'wce'} || ""; + print "\nclean: tidy\n". + "\t-del *.exe\n\n". + "tidy:\n". + "\t-del *.obj\n". + "\t-del *.res\n". + "\t-del *.pch\n". + "\t-del *.aps\n". + "\t-del *.ilk\n". + "\t-del *.pdb\n". + "\t-del *.rsp\n". + "\t-del *.dsp\n". + "\t-del *.dsw\n". + "\t-del *.ncb\n". + "\t-del *.opt\n". + "\t-del *.plg\n". + "\t-del *.map\n". + "\t-del *.idb\n". + "\t-del debug.log\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'vcproj'}) { + $mftyp = 'vcproj'; + + ##-- MSVC 6 Workspace and projects + # + # Note: All files created in this section are written in binary + # mode, because although MSVC's command-line make can deal with + # LF-only line endings, MSVC project files really _need_ to be + # CRLF. Hence, in order for mkfiles.pl to generate usable project + # files even when run from Unix, I make sure all files are binary + # and explicitly write the CRLFs. + # + # Create directories if necessary + mkdir $makefiles{'vcproj'} + if(! -d $makefiles{'vcproj'}); + chdir $makefiles{'vcproj'}; + @deps = &deps("X.obj", "X.res", "", "\\"); + %all_object_deps = map {$_->{obj} => $_->{deps}} @deps; + # Create the project files + # Get names of all Windows projects (GUI and console) + my @prognames = &prognames("G:C"); + foreach $progname (@prognames) { + create_project(\%all_object_deps, $progname); + } + # Create the workspace file + open OUT, ">$project_name.dsw"; binmode OUT; select OUT; + print + "Microsoft Developer Studio Workspace File, Format Version 6.00\r\n". + "# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n". + "\r\n". + "###############################################################################\r\n". + "\r\n"; + # List projects + foreach $progname (@prognames) { + ($windows_project, $type) = split ",", $progname; + print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n"; + } + print + "\r\n". + "Package=<5>\r\n". + "{{{\r\n". + "}}}\r\n". + "\r\n". + "Package=<4>\r\n". + "{{{\r\n". + "}}}\r\n". + "\r\n". + "###############################################################################\r\n". + "\r\n". + "Global:\r\n". + "\r\n". + "Package=<5>\r\n". + "{{{\r\n". + "}}}\r\n". + "\r\n". + "Package=<3>\r\n". + "{{{\r\n". + "}}}\r\n". + "\r\n". + "###############################################################################\r\n". + "\r\n"; + select STDOUT; close OUT; + chdir $orig_dir; + + sub create_project { + my ($all_object_deps, $progname) = @_; + # Construct program's dependency info + %seen_objects = (); + %lib_files = (); + %source_files = (); + %header_files = (); + %resource_files = (); + @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib"); + foreach $object_file (@object_files) { + next if defined $seen_objects{$object_file}; + $seen_objects{$object_file} = 1; + if($object_file =~ /\.lib$/io) { + $lib_files{$object_file} = 1; + next; + } + $object_deps = $all_object_deps{$object_file}; + foreach $object_dep (@$object_deps) { + if($object_dep =~ /\.c$/io) { + $source_files{$object_dep} = 1; + next; + } + if($object_dep =~ /\.h$/io) { + $header_files{$object_dep} = 1; + next; + } + if($object_dep =~ /\.(rc|ico)$/io) { + $resource_files{$object_dep} = 1; + next; + } + } + } + $libs = join " ", sort keys %lib_files; + @source_files = sort keys %source_files; + @header_files = sort keys %header_files; + @resources = sort keys %resource_files; + ($windows_project, $type) = split ",", $progname; + mkdir $windows_project + if(! -d $windows_project); + chdir $windows_project; + $subsys = ($type eq "G") ? "windows" : "console"; + open OUT, ">$windows_project.dsp"; binmode OUT; select OUT; + print + "# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n". + "# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n". + "# ** DO NOT EDIT **\r\n". + "\r\n". + "# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n". + "\r\n". + "CFG=$windows_project - Win32 Debug\r\n". + "!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n". + "!MESSAGE use the Export Makefile command and run\r\n". + "!MESSAGE \r\n". + "!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n". + "!MESSAGE \r\n". + "!MESSAGE You can specify a configuration when running NMAKE\r\n". + "!MESSAGE by defining the macro CFG on the command line. For example:\r\n". + "!MESSAGE \r\n". + "!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n". + "!MESSAGE \r\n". + "!MESSAGE Possible choices for configuration are:\r\n". + "!MESSAGE \r\n". + "!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n". + "!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n". + "!MESSAGE \r\n". + "\r\n". + "# Begin Project\r\n". + "# PROP AllowPerConfigDependencies 0\r\n". + "# PROP Scc_ProjName \"\"\r\n". + "# PROP Scc_LocalPath \"\"\r\n". + "CPP=cl.exe\r\n". + "MTL=midl.exe\r\n". + "RSC=rc.exe\r\n". + "\r\n". + "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n". + "\r\n". + "# PROP BASE Use_MFC 0\r\n". + "# PROP BASE Use_Debug_Libraries 0\r\n". + "# PROP BASE Output_Dir \"Release\"\r\n". + "# PROP BASE Intermediate_Dir \"Release\"\r\n". + "# PROP BASE Target_Dir \"\"\r\n". + "# PROP Use_MFC 0\r\n". + "# PROP Use_Debug_Libraries 0\r\n". + "# PROP Output_Dir \"Release\"\r\n". + "# PROP Intermediate_Dir \"Release\"\r\n". + "# PROP Ignore_Export_Lib 0\r\n". + "# PROP Target_Dir \"\"\r\n". + "# ADD BASE CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n". + "# ADD CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n". + "# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n". + "# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n". + "# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n". + "# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n". + "BSC32=bscmake.exe\r\n". + "# ADD BASE BSC32 /nologo\r\n". + "# ADD BSC32 /nologo\r\n". + "LINK32=link.exe\r\n". + "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n". + "# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n". + "# SUBTRACT LINK32 /pdb:none\r\n". + "\r\n". + "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n". + "\r\n". + "# PROP BASE Use_MFC 0\r\n". + "# PROP BASE Use_Debug_Libraries 1\r\n". + "# PROP BASE Output_Dir \"Debug\"\r\n". + "# PROP BASE Intermediate_Dir \"Debug\"\r\n". + "# PROP BASE Target_Dir \"\"\r\n". + "# PROP Use_MFC 0\r\n". + "# PROP Use_Debug_Libraries 1\r\n". + "# PROP Output_Dir \"Debug\"\r\n". + "# PROP Intermediate_Dir \"Debug\"\r\n". + "# PROP Ignore_Export_Lib 0\r\n". + "# PROP Target_Dir \"\"\r\n". + "# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n". + "# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n". + "# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n". + "# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n". + "# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n". + "# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n". + "BSC32=bscmake.exe\r\n". + "# ADD BASE BSC32 /nologo\r\n". + "# ADD BSC32 /nologo\r\n". + "LINK32=link.exe\r\n". + "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n". + "# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n". + "# SUBTRACT LINK32 /pdb:none\r\n". + "\r\n". + "!ENDIF \r\n". + "\r\n". + "# Begin Target\r\n". + "\r\n". + "# Name \"$windows_project - Win32 Release\"\r\n". + "# Name \"$windows_project - Win32 Debug\"\r\n". + "# Begin Group \"Source Files\"\r\n". + "\r\n". + "# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n"; + foreach $source_file (@source_files) { + print + "# Begin Source File\r\n". + "\r\n". + "SOURCE=..\\..\\$source_file\r\n"; + if($source_file =~ /ssh\.c/io) { + # Disable 'Edit and continue' as Visual Studio can't handle the macros + print + "\r\n". + "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n". + "\r\n". + "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n". + "\r\n". + "# ADD CPP /Zi\r\n". + "\r\n". + "!ENDIF \r\n". + "\r\n"; + } + print "# End Source File\r\n"; + } + print + "# End Group\r\n". + "# Begin Group \"Header Files\"\r\n". + "\r\n". + "# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n"; + foreach $header_file (@header_files) { + print + "# Begin Source File\r\n". + "\r\n". + "SOURCE=..\\..\\$header_file\r\n". + "# End Source File\r\n"; + } + print + "# End Group\r\n". + "# Begin Group \"Resource Files\"\r\n". + "\r\n". + "# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n"; + foreach $resource_file (@resources) { + print + "# Begin Source File\r\n". + "\r\n". + "SOURCE=..\\..\\$resource_file\r\n". + "# End Source File\r\n"; + } + print + "# End Group\r\n". + "# End Target\r\n". + "# End Project\r\n"; + select STDOUT; close OUT; + chdir ".."; + } +} + +if (defined $makefiles{'gtk'}) { + $mftyp = 'gtk'; + $dirpfx = &dirpfx($makefiles{'gtk'}, "/"); + + ##-- X/GTK/Unix makefile + open OUT, ">$makefiles{'gtk'}"; select OUT; + print + "# Makefile for $project_name under X/GTK and Unix.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # gcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# You can define this path to point at your tools if you need to\n". + "# TOOLPATH = /opt/gcc/bin\n". + "CC := \$(TOOLPATH)\$(CC)\n". + "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n". + "# (depending on what works on your system) if you want to enforce\n". + "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'\n". + "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n". + "# to 1.2 if it isn't found.\n". + "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 \$\$0 2>/dev/null || gtk-config \$\$0'\n". + "\n". + &splitline("CFLAGS := -O2 -Wall -Werror -ansi -pedantic -g " . + (join " ", map {"-I$dirpfx$_"} @srcdirs) . + " `\$(GTK_CONFIG) --cflags` \$(CFLAGS)")."\n". + "XLIBS = `\$(GTK_CONFIG) --libs` -lm\n". + "ULIBS = -lm#\n". + "INSTALL=install\n", + "INSTALL_PROGRAM=\$(INSTALL)\n", + "INSTALL_DATA=\$(INSTALL)\n", + "prefix=/usr/local\n", + "exec_prefix=\$(prefix)\n", + "bindir=\$(exec_prefix)/bin\n", + "gamesdir=\$(exec_prefix)/games\n", + "mandir=\$(prefix)/man\n", + "man1dir=\$(mandir)/man1\n", + "\n"; + print &splitline("all:" . join "", map { " \$(BINPREFIX)$_" } + &progrealnames("X:U")); + print "\n\n"; + foreach $p (&prognames("X:U")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.o", undef, undef); + print &splitline("\$(BINPREFIX)" . $prog . ": " . $objstr), "\n"; + $libstr = &objects($p, undef, undef, "-lX"); + print &splitline("\t\$(CC) -o \$@ $objstr $libstr \$(XLFLAGS) \$(${type}LIBS)", 69), + "\n\n"; + } + foreach $d (&deps("X.o", undef, $dirpfx, "/")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" . + " -c \$< -o \$\@\n"; + } + print "\n"; + print $makefile_extra{'gtk'} || ""; + print "\nclean:\n". + "\trm -f *.o". (join "", map { " \$(BINPREFIX)$_" } &progrealnames("X:U")) . "\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'am'}) { + $mftyp = 'am'; + die "Makefile.am in a subdirectory is not supported\n" + if &dirpfx($makefiles{'am'}, "/") ne ""; + + ##-- Unix/autoconf Makefile.am + open OUT, ">$makefiles{'am'}"; select OUT; + print + "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n"; + + print $makefile_extra{'am_begin'} || ""; + + # All programs go in noinstprogs by default. If you want them + # installed anywhere else, you have to also add them to + # bin_PROGRAMS using '!begin am'. (Automake doesn't seem to mind + # having a program name in _both_ of bin_PROGRAMS and + # noinst_PROGRAMS.) + @noinstprogs = (); + foreach $p (&prognames("X:U")) { + ($prog, $type) = split ",", $p; + push @noinstprogs, $prog; + } + print &splitline(join " ", "noinst_PROGRAMS", "=", @noinstprogs), "\n"; + + %objtosrc = (); + %amspeciallibs = (); + %amlibobjname = (); + %allsources = (); + foreach $d (&deps("X", undef, "", "/", "am")) { + my $obj = $d->{obj}; + my $use_archive = 0; + + if (defined $d->{defs}) { + # This file needs to go in an archive, so that we can + # change the preprocess flags to include some -Ds + $use_archive = 1; + $archivecppflags{$obj} = [map { " -D$_" } @{$d->{defs}}]; + } + if (defined $cflags{'am'} && $cflags{'am'}->{$obj}) { + # This file needs to go in an archive, so that we can + # change the compile flags as specified in Recipe + $use_archive = 1; + $archivecflags{$obj} = [$cflags{'am'}->{$obj}]; + } + if ($use_archive) { + $amspeciallibs{$obj} = "lib${obj}.a"; + $amlibobjname{$obj} = "lib${obj}_a-" . + basename($d->{deps}->[0], ".c", ".m") . + ".\$(OBJEXT)"; + } + $objtosrc{$obj} = $d->{deps}; + map { $allsources{$_} = 1 } @{$d->{deps}}; + } + + # 2014-02-22: as of automake-1.14 we begin to get complained at if + # we don't use this option + print "AUTOMAKE_OPTIONS = subdir-objects\n\n"; + + # Complete list of source and header files. Not used by the + # auto-generated parts of this makefile, but Recipe might like to + # have it available as a variable so that mandatory-rebuild things + # (version.o) can conveniently be made to depend on it. + print &splitline(join " ", "allsources", "=", + sort {$a cmp $b} keys %allsources), "\n\n"; + + @amcppflags = map {"-I\$(srcdir)/$_"} @srcdirs; + print &splitline(join " ", "AM_CPPFLAGS", "=", @amcppflags, "\n"); + + @amcflags = ("\$(GTK_CFLAGS)", "\$(WARNINGOPTS)"); + print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n"; + + %amlibsused = (); + foreach $p (&prognames("X:U")) { + ($prog, $type) = split ",", $p; + @progsources = ("${prog}_SOURCES", "="); + %sourcefiles = (); + @ldadd = (); + $objstr = &objects($p, "X", undef, undef); + foreach $obj (split / /,$objstr) { + if ($amspeciallibs{$obj}) { + $amlibsused{$obj} = 1; + push @ldadd, $amlibobjname{$obj}; + } else { + map { $sourcefiles{$_} = 1 } @{$objtosrc{$obj}}; + } + } + push @progsources, sort { $a cmp $b } keys %sourcefiles; + print &splitline(join " ", @progsources), "\n"; + if ($type eq "X") { + push @ldadd, "\$(GTK_LIBS)"; + } + push @ldadd, "-lm"; + print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n"; + print "\n"; + } + + foreach $obj (sort { $a cmp $b } keys %amlibsused) { + print &splitline(join " ", "lib${obj}_a_SOURCES", "=", + @{$objtosrc{$obj}}), "\n"; + print &splitline(join " ", "lib${obj}_a_CPPFLAGS", "=", + @amcflags, @{$archivecppflags{$obj}}), "\n" + if $archivecppflags{$obj}; + print &splitline(join " ", "lib${obj}_a_CFLAGS", "=", + @amcflags, @{$archivecflags{$obj}}), "\n" + if $archivecflags{$obj}; + } + print &splitline(join " ", "noinst_LIBRARIES", "=", + sort { $a cmp $b } + map { $amspeciallibs{$_} } + keys %amlibsused), + "\n\n"; + + print $makefile_extra{'am'} || ""; + select STDOUT; close OUT; +} + +if (defined $makefiles{'mpw'}) { + $mftyp = 'mpw'; + ##-- MPW Makefile + open OUT, ">$makefiles{'mpw'}"; select OUT; + print + "# Makefile for $project_name under MPW.\n#\n". + "# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # MPW command line option is -d not /D + ($_ = $help) =~ s/=\/D/=-d /gs; + print $_; + print "\n\n". + "ROptions = `Echo \"{VER}\" | StreamEdit -e \"1,\$ replace /=(\xc5)\xa81\xb0/ 'STR=\xb6\xb6\xb6\xb6\xb6\"' \xa81 '\xb6\xb6\xb6\xb6\xb6\"'\"`". + "\n". + "C_68K = {C}\n". + "C_CFM68K = {C}\n". + "C_PPC = {PPCC}\n". + "C_Carbon = {PPCC}\n". + "\n". + "# -w 35 disables \"unused parameter\" warnings\n". + "COptions = -i : -i :: -i ::charset -w 35 -w err -proto strict -ansi on \xb6\n". + " -notOnce\n". + "COptions_68K = {COptions} -model far -opt time\n". + "# Enabling \"-opt space\" for CFM-68K gives me undefined references to\n". + "# _\$LDIVT and _\$LMODT.\n". + "COptions_CFM68K = {COptions} -model cfmSeg -opt time\n". + "COptions_PPC = {COptions} -opt size -traceback\n". + "COptions_Carbon = {COptions} -opt size -traceback -d TARGET_API_MAC_CARBON\n". + "\n". + "Link_68K = ILink\n". + "Link_CFM68K = ILink\n". + "Link_PPC = PPCLink\n". + "Link_Carbon = PPCLink\n". + "\n". + "LinkOptions = -c 'pTTY'\n". + "LinkOptions_68K = {LinkOptions} -br 68k -model far -compact\n". + "LinkOptions_CFM68K = {LinkOptions} -br 020 -model cfmseg -compact\n". + "LinkOptions_PPC = {LinkOptions}\n". + "LinkOptions_Carbon = -m __appstart -w {LinkOptions}\n". + "\n". + "Libs_68K = \"{CLibraries}StdCLib.far.o\" \xb6\n". + " \"{Libraries}MacRuntime.o\" \xb6\n". + " \"{Libraries}MathLib.far.o\" \xb6\n". + " \"{Libraries}IntEnv.far.o\" \xb6\n". + " \"{Libraries}Interface.o\" \xb6\n". + " \"{Libraries}Navigation.far.o\" \xb6\n". + " \"{Libraries}OpenTransport.o\" \xb6\n". + " \"{Libraries}OpenTransportApp.o\" \xb6\n". + " \"{Libraries}OpenTptInet.o\" \xb6\n". + " \"{Libraries}UnicodeConverterLib.far.o\"\n". + "\n". + "Libs_CFM = \"{SharedLibraries}InterfaceLib\" \xb6\n". + " \"{SharedLibraries}StdCLib\" \xb6\n". + " \"{SharedLibraries}AppearanceLib\" \xb6\n". + " -weaklib AppearanceLib \xb6\n". + " \"{SharedLibraries}NavigationLib\" \xb6\n". + " -weaklib NavigationLib \xb6\n". + " \"{SharedLibraries}TextCommon\" \xb6\n". + " -weaklib TextCommon \xb6\n". + " \"{SharedLibraries}UnicodeConverter\" \xb6\n". + " -weaklib UnicodeConverter\n". + "\n". + "Libs_CFM68K = {Libs_CFM} \xb6\n". + " \"{CFM68KLibraries}NuMacRuntime.o\"\n". + "\n". + "Libs_PPC = {Libs_CFM} \xb6\n". + " \"{SharedLibraries}ControlsLib\" \xb6\n". + " -weaklib ControlsLib \xb6\n". + " \"{SharedLibraries}WindowsLib\" \xb6\n". + " -weaklib WindowsLib \xb6\n". + " \"{SharedLibraries}OpenTransportLib\" \xb6\n". + " -weaklib OTClientLib \xb6\n". + " -weaklib OTClientUtilLib \xb6\n". + " \"{SharedLibraries}OpenTptInternetLib\" \xb6\n". + " -weaklib OTInetClientLib \xb6\n". + " \"{PPCLibraries}StdCRuntime.o\" \xb6\n". + " \"{PPCLibraries}PPCCRuntime.o\" \xb6\n". + " \"{PPCLibraries}CarbonAccessors.o\" \xb6\n". + " \"{PPCLibraries}OpenTransportAppPPC.o\" \xb6\n". + " \"{PPCLibraries}OpenTptInetPPC.o\"\n". + "\n". + "Libs_Carbon = \"{PPCLibraries}CarbonStdCLib.o\" \xb6\n". + " \"{PPCLibraries}StdCRuntime.o\" \xb6\n". + " \"{PPCLibraries}PPCCRuntime.o\" \xb6\n". + " \"{SharedLibraries}CarbonLib\" \xb6\n". + " \"{SharedLibraries}StdCLib\"\n". + "\n"; + print &splitline("all \xc4 " . join(" ", &progrealnames("M")), undef, "\xb6"); + print "\n\n"; + foreach $p (&prognames("M")) { + ($prog, $type) = split ",", $p; + + print &splitline("$prog \xc4 $prog.68k $prog.ppc $prog.carbon", + undef, "\xb6"), "\n\n"; + + $rsrc = &objects($p, "", "X.rsrc", undef); + + foreach $arch (qw(68K CFM68K PPC Carbon)) { + $objstr = &objects($p, "X.\L$arch\E.o", "", undef); + print &splitline("$prog.\L$arch\E \xc4 $objstr $rsrc", undef, "\xb6"); + print "\n"; + print &splitline("\tDuplicate -y $rsrc {Targ}", 69, "\xb6"), "\n"; + print &splitline("\t{Link_$arch} -o {Targ} -fragname $prog " . + "{LinkOptions_$arch} " . + $objstr . " {Libs_$arch}", 69, "\xb6"), "\n"; + print &splitline("\tSetFile -a BMi {Targ}", 69, "\xb6"), "\n\n"; + } + + } + foreach $d (&deps("", "X.rsrc", "::", ":")) { + next unless $d->{obj}; + print &splitline(sprintf("%s \xc4 %s", $d->{obj}, join " ", @{$d->{deps}}), + undef, "\xb6"), "\n"; + print "\tRez ", $d->{deps}->[0], " -o {Targ} {ROptions}\n\n"; + } + foreach $arch (qw(68K CFM68K)) { + foreach $d (&deps("X.\L$arch\E.o", "", "::", ":")) { + next unless $d->{obj}; + print &splitline(sprintf("%s \xc4 %s", $d->{obj}, + join " ", @{$d->{deps}}), + undef, "\xb6"), "\n"; + print "\t{C_$arch} ", $d->{deps}->[0], + " -o {Targ} {COptions_$arch}\n\n"; + } + } + foreach $arch (qw(PPC Carbon)) { + foreach $d (&deps("X.\L$arch\E.o", "", "::", ":")) { + next unless $d->{obj}; + print &splitline(sprintf("%s \xc4 %s", $d->{obj}, + join " ", @{$d->{deps}}), + undef, "\xb6"), "\n"; + # The odd stuff here seems to stop afpd getting confused. + print "\techo -n > {Targ}\n"; + print "\tsetfile -t XCOF {Targ}\n"; + print "\t{C_$arch} ", $d->{deps}->[0], + " -o {Targ} {COptions_$arch}\n\n"; + } + } + select STDOUT; close OUT; +} + +if (defined $makefiles{'lcc'}) { + $mftyp = 'lcc'; + $dirpfx = &dirpfx($makefiles{'lcc'}, "\\"); + + ##-- lcc makefile + open OUT, ">$makefiles{'lcc'}"; select OUT; + print + "# Makefile for $project_name under lcc.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # lcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# If you rename this file to `Makefile', you should change this line,\n". + "# so that the .rsp files still depend on the correct makefile.\n". + "MAKEFILE = Makefile.lcc\n". + "\n". + "# C compilation flags\n". + "CFLAGS = -D_WINDOWS " . + (join " ", map {"-I$dirpfx$_"} @srcdirs) . + "\n". + "\n". + "# Get include directory for resource compiler\n". + "\n"; + print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); + print "\n\n"; + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr ), "\n"; + $subsystemtype = undef; + if ($type eq "G") { $subsystemtype = "-subsystem windows"; } + my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib"; + print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss"); + print "\n\n"; + } + + foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + if ($d->{obj} =~ /\.res$/) { + print &splitline("\tlrc \$(FWHACK) \$(RCFL) -r \$*.rc",69)."\n"; + } else { + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print &splitline("\tlcc -O -p6 \$(COMPAT) \$(FWHACK) \$(CFLAGS)". + " \$(XFLAGS)$deflist ".$d->{deps}->[0]." -o \$\@",69)."\n"; + } + } + print "\n"; + print $makefile_extra{'lcc'} || ""; + print "\nclean:\n". + "\t-del *.obj\n". + "\t-del *.exe\n". + "\t-del *.res\n"; + + select STDOUT; close OUT; +} + +if (defined $makefiles{'nestedvm'}) { + $mftyp = 'nestedvm'; + $dirpfx = &dirpfx($makefiles{'nestedvm'}, "/"); + + ##-- NestedVM makefile + open OUT, ">$makefiles{'nestedvm'}"; select OUT; + print + "# Makefile for $project_name under NestedVM.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # gcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# This path points at the nestedvm root directory\n". + "NESTEDVM = /opt/nestedvm\n". + "# You can define this path to point at your tools if you need to\n". + "TOOLPATH = \$(NESTEDVM)/upstream/install/bin\n". + "CC = \$(TOOLPATH)/mips-unknown-elf-gcc\n". + "\n". + &splitline("CFLAGS = -O2 -Wall -Werror -DSLOW_SYSTEM -g " . + (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". + "\n"; + print &splitline("all:" . join "", map { " $_.jar" } &progrealnames("X")); + print "\n\n"; + foreach $p (&prognames("X")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.o", undef, undef); + $objstr =~ s/gtk\.o/nestedvm\.o/g; + print &splitline($prog . ".mips: " . $objstr), "\n"; + $libstr = &objects($p, undef, undef, "-lX"); + print &splitline("\t\$(CC) \$(${type}LDFLAGS) -o \$@ " . + $objstr . " $libstr -lm", 69), "\n\n"; + } + foreach $d (&deps("X.o", undef, $dirpfx, "/")) { + $oobjs = $d->{obj}; + $ddeps= join " ", @{$d->{deps}}; + $oobjs =~ s/gtk/nestedvm/g; + $ddeps =~ s/gtk/nestedvm/g; + print &splitline(sprintf("%s: %s", $oobjs, $ddeps)), + "\n"; + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" . + " -c \$< -o \$\@\n"; + } + print "\n"; + print $makefile_extra{'nestedvm'} || ""; + print "\nclean:\n". + "\trm -rf *.o *.mips *.class *.html *.jar org applet.manifest\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'osx'}) { + $mftyp = 'osx'; + $dirpfx = &dirpfx($makefiles{'osx'}, "/"); + @osxarchs = ('i386'); + + ##-- Mac OS X makefile + open OUT, ">$makefiles{'osx'}"; select OUT; + print + "# Makefile for $project_name under Mac OS X.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # gcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "CC = \$(TOOLPATH)gcc\n". + "LIPO = \$(TOOLPATH)lipo\n". + "\n". + &splitline("CFLAGS = -O2 -Wall -Werror -g " . + (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". + "LDFLAGS = -framework Cocoa\n". + &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) . + "\n"; + print $makefile_extra{'osx'} || ""; + print "\n". + ".SUFFIXES: .o .c .m\n". + "\n"; + print "\n\n"; + foreach $p (&prognames("MX")) { + ($prog, $type) = split ",", $p; + $icon = &special($p, ".icns"); + $infoplist = &special($p, "info.plist"); + print "${prog}.app:\n\tmkdir -p \$\@\n"; + print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n"; + print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n"; + $targets = "${prog}.app/Contents/MacOS/$prog"; + if (defined $icon) { + print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n"; + print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n"; + $targets .= " ${prog}.app/Contents/Resources/${prog}.icns"; + } + if (defined $infoplist) { + print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n"; + $targets .= " ${prog}.app/Contents/Info.plist"; + } + $targets .= " \$(${prog}_extra)"; + print &splitline("${prog}: $targets", 69) . "\n\n"; + $libstr = &objects($p, undef, undef, "-lX"); + $archbins = ""; + foreach $arch (@osxarchs) { + $objstr = &objects($p, "X.${arch}.o", undef, undef); + print &splitline("${prog}.${arch}.bin: " . $objstr), "\n"; + print &splitline("\t\$(CC) -arch ${arch} -mmacosx-version-min=10.4 \$(LDFLAGS) -o \$@ " . + $objstr . " $libstr", 69), "\n\n"; + $archbins .= " ${prog}.${arch}.bin"; + } + print &splitline("${prog}.app/Contents/MacOS/$prog: ". + "${prog}.app/Contents/MacOS" . $archbins), "\n"; + print &splitline("\t\$(LIPO) -create $archbins -output \$@", 69), "\n\n"; + } + foreach $p (&prognames("U")) { + ($prog, $type) = split ",", $p; + $libstr = &objects($p, undef, undef, "-lX"); + $archbins = ""; + foreach $arch (@osxarchs) { + $objstr = &objects($p, "X.${arch}.o", undef, undef); + print &splitline("${prog}.${arch}: " . $objstr), "\n"; + print &splitline("\t\$(CC) -arch ${arch} -mmacosx-version-min=10.4 \$(ULDFLAGS) -o \$@ " . + $objstr . " $libstr", 69), "\n\n"; + $archbins .= " ${prog}.${arch}"; + } + print &splitline("${prog}:" . $archbins), "\n"; + print &splitline("\t\$(LIPO) -create $archbins -output \$@", 69), "\n\n"; + } + foreach $arch (@osxarchs) { + foreach $d (&deps("X.${arch}.o", undef, $dirpfx, "/")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + if ($d->{deps}->[0] =~ /\.m$/) { + print "\t\$(CC) -arch $arch -mmacosx-version-min=10.4 -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS)". + " \$(XFLAGS)$deflist -c \$< -o \$\@\n"; + } else { + print "\t\$(CC) -arch $arch -mmacosx-version-min=10.4 \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" . + " -c \$< -o \$\@\n"; + } + } + } + print "\nclean:\n". + "\trm -f *.o *.dmg". (join "", map { my $a=$_; (" $a", map { " ${a}.$_" } @osxarchs) } &progrealnames("U")) . "\n". + "\trm -rf *.app\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'gnustep'}) { + $mftyp = 'gnustep'; + $dirpfx = &dirpfx($makefiles{'gnustep'}, "/"); + + ##-- GNUstep makefile (use with 'gs_make -f Makefile.gnustep') + + # This is a pretty evil way to do things. In an ideal world, I'd + # use the approved GNUstep makefile mechanism which just defines a + # variable or two saying what source files go into what binary and + # then includes application.make. Unfortunately, that has the + # automake-ish limitation that it doesn't let you choose different + # command lines for each object, so I can't arrange for all those + # files with -DTHIS and -DTHAT to Just Work. + # + # A simple if ugly fix would be to have mkfiles.pl construct a + # directory full of stub C files of the form '#define thing', + # '#include "real_source_file"', and then reference those in this + # makefile. That would also make it easy to build a proper + # automake makefile. + open OUT, ">$makefiles{'gnustep'}"; select OUT; + print + "# Makefile for $project_name under GNUstep.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # gcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "NEEDS_GUI=yes\n". + "include \$(GNUSTEP_MAKEFILES)/common.make\n". + "include \$(GNUSTEP_MAKEFILES)/rules.make\n". + "include \$(GNUSTEP_MAKEFILES)/Instance/rules.make\n". + "\n". + &splitline("all::" . join "", map { " $_" } &progrealnames("MX:U")) . + "\n"; + print $makefile_extra{'gnustep'} || ""; + print "\n". + ".SUFFIXES: .o .c .m\n". + "\n"; + print "\n\n"; + foreach $p (&prognames("MX")) { + ($prog, $type) = split ",", $p; + $icon = &special($p, ".icns"); + $infoplist = &special($p, "info.plist"); + print "${prog}.app:\n\tmkdir -p \$\@\n"; + $targets = "${prog}.app ${prog}.app/$prog"; + if (defined $icon) { + print "${prog}.app/Resources: ${prog}.app\n\tmkdir -p \$\@\n"; + print "${prog}.app/Resources/${prog}.icns: ${prog}.app/Resources $icon\n\tcp $icon \$\@\n"; + $targets .= " ${prog}.app/Resources/${prog}.icns"; + } + if (defined $infoplist) { + print "${prog}.app/Info.plist: ${prog}.app $infoplist\n\tcp $infoplist \$\@\n"; + $targets .= " ${prog}.app/Info.plist"; + } + $targets .= " \$(${prog}_extra)"; + print &splitline("${prog}: $targets", 69) . "\n\n"; + $libstr = &objects($p, undef, undef, "-lX"); + $objstr = &objects($p, "X.o", undef, undef); + print &splitline("${prog}.app/$prog: " . $objstr), "\n"; + print &splitline("\t\$(CC) \$(ALL_LDFLAGS) -o \$@ " . $objstr . " \$(ALL_LIB_DIRS) $libstr \$(ALL_LIBS)", 69), "\n\n"; + } + foreach $p (&prognames("U")) { + ($prog, $type) = split ",", $p; + $libstr = &objects($p, undef, undef, "-lX"); + $objstr = &objects($p, "X.o", undef, undef); + print &splitline("${prog}: " . $objstr), "\n"; + print &splitline("\t\$(CC) \$(ULDFLAGS) -o \$@ " . $objstr . " $libstr", 69), "\n\n"; + } + foreach $d (&deps("X.o", undef, $dirpfx, "/")) { + print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), + "\n"; + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + if ($d->{deps}->[0] =~ /\.m$/) { + print "\t\$(CC) -DGNUSTEP \$(ALL_OBJCFLAGS) \$(COMPAT) \$(FWHACK) \$(OBJCFLAGS)". + " \$(XFLAGS)$deflist -c \$< -o \$\@\n"; + } else { + print "\t\$(CC) \$(ALL_CFLAGS) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" . + " -c \$< -o \$\@\n"; + } + } + print "\nclean::\n". + "\trm -f *.o ". (join " ", &progrealnames("U")) . "\n". + "\trm -rf *.app\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'emcc'}) { + $mftyp = 'emcc'; + $dirpfx = &dirpfx($makefiles{'emcc'}, "/"); + + ##-- Makefile for building Javascript puzzles via Emscripten + + open OUT, ">$makefiles{'emcc'}"; select OUT; + print + "# Makefile for $project_name using Emscripten. Requires GNU make.\n". + "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; + # emcc command line option is -D not /D + ($_ = $help) =~ s/=\/D/=-D/gs; + print $_; + print + "\n". + "# This can be set on the command line to point at the emcc command,\n". + "# if it is not on your PATH.\n". + "EMCC = emcc\n". + "\n". + &splitline("CFLAGS = -DSLOW_SYSTEM " . + (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". + "\n"; + $output_js_files = join "", map { " \$(OUTPREFIX)$_.js" } &progrealnames("X"); + print &splitline("all:" . $output_js_files); + print "\n\n"; + foreach $p (&prognames("X")) { + ($prog, $type) = split ",", $p; + $objstr = &objects($p, "X.o", undef, undef); + $objstr =~ s/gtk\.o/emcc\.o/g; + print &splitline("\$(OUTPREFIX)" . $prog . ".js: " . $objstr . " emccpre.js emcclib.js emccx.json"), "\n"; + print "\t\$(EMCC) -o \$(OUTPREFIX)".$prog.".js ". + "-O2 ". + "-s ASM_JS=1 ". + "--pre-js emccpre.js ". + "--js-library emcclib.js ". + "-s EXPORTED_FUNCTIONS=\"`sed 's://.*::' emccx.json | tr -d ' \\n'`\" " . $objstr . "\n\n"; + } + foreach $d (&deps("X.o", undef, $dirpfx, "/")) { + $oobjs = $d->{obj}; + $ddeps= join " ", @{$d->{deps}}; + $oobjs =~ s/gtk/emcc/g; + $ddeps =~ s/gtk/emcc/g; + print &splitline(sprintf("%s: %s", $oobjs, $ddeps)), + "\n"; + $deflist = join "", map { " -D$_" } @{$d->{defs}}; + print "\t\$(EMCC) \$(CFLAGS) \$(XFLAGS)$deflist" . + " -c \$< -o \$\@\n"; + } + print "\n"; + print $makefile_extra{'emcc'} || ""; + print "\nclean:\n". + "\trm -rf *.o $output_js_files\n"; + select STDOUT; close OUT; +} + +# All done, so do the Unix postprocessing if asked to. + +if ($do_unix) { + chdir $orig_dir; + system "./mkauto.sh"; + die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0; + system "./configure", @confargs; + die "mkfiles.pl: configure returned $?\n" if $? > 0; +} diff --git a/apps/plugins/puzzles/nestedvm.c b/apps/plugins/puzzles/nestedvm.c new file mode 100644 index 0000000000..c61afecf6a --- /dev/null +++ b/apps/plugins/puzzles/nestedvm.c @@ -0,0 +1,432 @@ +/* + * nestedvm.c: NestedVM front end for my puzzle collection. + */ + +#include +#include "rbassert.h" +#include +#include +#include +#include +#include + +#include + +#include "puzzles.h" + +extern void _pause(); +extern int _call_java(int cmd, int arg1, int arg2, int arg3); + +void fatal(char *fmt, ...) +{ + va_list ap; + fprintf(stderr, "fatal error: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +struct frontend { + // TODO kill unneeded members! + midend *me; + int timer_active; + struct timeval last_time; + config_item *cfg; + int cfg_which, cfgret; + int ox, oy, w, h; +}; + +static frontend *_fe; + +void get_random_seed(void **randseed, int *randseedsize) +{ + struct timeval *tvp = snew(struct timeval); + gettimeofday(tvp, NULL); + *randseed = (void *)tvp; + *randseedsize = sizeof(struct timeval); +} + +void frontend_default_colour(frontend *fe, float *output) +{ + output[0] = output[1]= output[2] = 0.8f; +} + +void nestedvm_status_bar(void *handle, char *text) +{ + _call_java(4,0,(int)text,0); +} + +void nestedvm_start_draw(void *handle) +{ + frontend *fe = (frontend *)handle; + _call_java(5, 0, fe->w, fe->h); + _call_java(4, 1, fe->ox, fe->oy); +} + +void nestedvm_clip(void *handle, int x, int y, int w, int h) +{ + frontend *fe = (frontend *)handle; + _call_java(5, w, h, 0); + _call_java(4, 3, x + fe->ox, y + fe->oy); +} + +void nestedvm_unclip(void *handle) +{ + frontend *fe = (frontend *)handle; + _call_java(4, 4, fe->ox, fe->oy); +} + +void nestedvm_draw_text(void *handle, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text) +{ + frontend *fe = (frontend *)handle; + _call_java(5, x + fe->ox, y + fe->oy, + (fonttype == FONT_FIXED ? 0x10 : 0x0) | align); + _call_java(7, fontsize, colour, (int)text); +} + +void nestedvm_draw_rect(void *handle, int x, int y, int w, int h, int colour) +{ + frontend *fe = (frontend *)handle; + _call_java(5, w, h, colour); + _call_java(4, 5, x + fe->ox, y + fe->oy); +} + +void nestedvm_draw_line(void *handle, int x1, int y1, int x2, int y2, + int colour) +{ + frontend *fe = (frontend *)handle; + _call_java(5, x2 + fe->ox, y2 + fe->oy, colour); + _call_java(4, 6, x1 + fe->ox, y1 + fe->oy); +} + +void nestedvm_draw_poly(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + int i; + _call_java(4, 7, npoints, 0); + for (i = 0; i < npoints; i++) { + _call_java(6, i, coords[i*2] + fe->ox, coords[i*2+1] + fe->oy); + } + _call_java(4, 8, outlinecolour, fillcolour); +} + +void nestedvm_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + _call_java(5, cx+fe->ox, cy+fe->oy, radius); + _call_java(4, 9, outlinecolour, fillcolour); +} + +struct blitter { + int handle, w, h, x, y; +}; + +blitter *nestedvm_blitter_new(void *handle, int w, int h) +{ + blitter *bl = snew(blitter); + bl->handle = -1; + bl->w = w; + bl->h = h; + return bl; +} + +void nestedvm_blitter_free(void *handle, blitter *bl) +{ + if (bl->handle != -1) + _call_java(4, 11, bl->handle, 0); + sfree(bl); +} + +void nestedvm_blitter_save(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + if (bl->handle == -1) + bl->handle = _call_java(4,10,bl->w, bl->h); + bl->x = x; + bl->y = y; + _call_java(8, bl->handle, x + fe->ox, y + fe->oy); +} + +void nestedvm_blitter_load(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + assert(bl->handle != -1); + if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { + x = bl->x; + y = bl->y; + } + _call_java(9, bl->handle, x + fe->ox, y + fe->oy); +} + +void nestedvm_end_draw(void *handle) +{ + _call_java(4,2,0,0); +} + +char *nestedvm_text_fallback(void *handle, const char *const *strings, + int nstrings) +{ + /* + * We assume Java can cope with any UTF-8 likely to be emitted + * by a puzzle. + */ + return dupstr(strings[0]); +} + +const struct drawing_api nestedvm_drawing = { + nestedvm_draw_text, + nestedvm_draw_rect, + nestedvm_draw_line, + nestedvm_draw_poly, + nestedvm_draw_circle, + NULL, // draw_update, + nestedvm_clip, + nestedvm_unclip, + nestedvm_start_draw, + nestedvm_end_draw, + nestedvm_status_bar, + nestedvm_blitter_new, + nestedvm_blitter_free, + nestedvm_blitter_save, + nestedvm_blitter_load, + NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ + NULL, NULL, /* line_width, line_dotted */ + nestedvm_text_fallback, +}; + +int jcallback_key_event(int x, int y, int keyval) +{ + frontend *fe = (frontend *)_fe; + if (fe->ox == -1) + return 1; + if (keyval >= 0 && + !midend_process_key(fe->me, x - fe->ox, y - fe->oy, keyval)) + return 42; + return 1; +} + +int jcallback_resize(int width, int height) +{ + frontend *fe = (frontend *)_fe; + int x, y; + x = width; + y = height; + midend_size(fe->me, &x, &y, TRUE); + fe->ox = (width - x) / 2; + fe->oy = (height - y) / 2; + fe->w = x; + fe->h = y; + midend_force_redraw(fe->me); + return 0; +} + +int jcallback_timer_func() +{ + frontend *fe = (frontend *)_fe; + if (fe->timer_active) { + struct timeval now; + float elapsed; + gettimeofday(&now, NULL); + elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F + + (now.tv_sec - fe->last_time.tv_sec)); + midend_timer(fe->me, elapsed); /* may clear timer_active */ + fe->last_time = now; + } + return fe->timer_active; +} + +void deactivate_timer(frontend *fe) +{ + if (fe->timer_active) + _call_java(4, 13, 0, 0); + fe->timer_active = FALSE; +} + +void activate_timer(frontend *fe) +{ + if (!fe->timer_active) { + _call_java(4, 12, 0, 0); + gettimeofday(&fe->last_time, NULL); + } + fe->timer_active = TRUE; +} + +void jcallback_config_ok() +{ + frontend *fe = (frontend *)_fe; + char *err; + + err = midend_set_config(fe->me, fe->cfg_which, fe->cfg); + + if (err) + _call_java(2, (int) "Error", (int)err, 1); + else { + fe->cfgret = TRUE; + } +} + +void jcallback_config_set_string(int item_ptr, int char_ptr) { + config_item *i = (config_item *)item_ptr; + char* newval = (char*) char_ptr; + sfree(i->sval); + i->sval = dupstr(newval); + free(newval); +} + +void jcallback_config_set_boolean(int item_ptr, int selected) { + config_item *i = (config_item *)item_ptr; + i->ival = selected != 0 ? TRUE : FALSE; +} + +void jcallback_config_set_choice(int item_ptr, int selected) { + config_item *i = (config_item *)item_ptr; + i->ival = selected; +} + +static int get_config(frontend *fe, int which) +{ + char *title; + config_item *i; + fe->cfg = midend_get_config(fe->me, which, &title); + fe->cfg_which = which; + fe->cfgret = FALSE; + _call_java(10, (int)title, 0, 0); + for (i = fe->cfg; i->type != C_END; i++) { + _call_java(5, (int)i, i->type, (int)i->name); + _call_java(11, (int)i->sval, i->ival, 0); + } + _call_java(12,0,0,0); + free_cfg(fe->cfg); + return fe->cfgret; +} + +int jcallback_menu_key_event(int key) +{ + frontend *fe = (frontend *)_fe; + if (!midend_process_key(fe->me, 0, 0, key)) + return 42; + return 0; +} + +static void resize_fe(frontend *fe) +{ + int x, y; + + x = INT_MAX; + y = INT_MAX; + midend_size(fe->me, &x, &y, FALSE); + _call_java(3, x, y, 0); +} + +int jcallback_preset_event(int ptr_game_params) +{ + frontend *fe = (frontend *)_fe; + game_params *params = + (game_params *)ptr_game_params; + + midend_set_params(fe->me, params); + midend_new_game(fe->me); + resize_fe(fe); + _call_java(13, midend_which_preset(fe->me), 0, 0); + return 0; +} + +int jcallback_solve_event() +{ + frontend *fe = (frontend *)_fe; + char *msg; + + msg = midend_solve(fe->me); + + if (msg) + _call_java(2, (int) "Error", (int)msg, 1); + return 0; +} + +int jcallback_restart_event() +{ + frontend *fe = (frontend *)_fe; + + midend_restart_game(fe->me); + return 0; +} + +int jcallback_config_event(int which) +{ + frontend *fe = (frontend *)_fe; + _call_java(13, midend_which_preset(fe->me), 0, 0); + if (!get_config(fe, which)) + return 0; + midend_new_game(fe->me); + resize_fe(fe); + _call_java(13, midend_which_preset(fe->me), 0, 0); + return 0; +} + +int jcallback_about_event() +{ + char titlebuf[256]; + char textbuf[1024]; + + sprintf(titlebuf, "About %.200s", thegame.name); + sprintf(textbuf, + "%.200s\n\n" + "from Simon Tatham's Portable Puzzle Collection\n\n" + "%.500s", thegame.name, ver); + _call_java(2, (int)&titlebuf, (int)&textbuf, 0); + return 0; +} + +int main(int argc, char **argv) +{ + int i, n; + float* colours; + + _fe = snew(frontend); + _fe->timer_active = FALSE; + _fe->me = midend_new(_fe, &thegame, &nestedvm_drawing, _fe); + if (argc > 1) + midend_game_id(_fe->me, argv[1]); /* ignore failure */ + midend_new_game(_fe->me); + + if ((n = midend_num_presets(_fe->me)) > 0) { + int i; + for (i = 0; i < n; i++) { + char *name; + game_params *params; + midend_fetch_preset(_fe->me, i, &name, ¶ms); + _call_java(1, (int)name, (int)params, 0); + } + } + + colours = midend_colours(_fe->me, &n); + _fe->ox = -1; + + _call_java(0, (int)thegame.name, + (thegame.can_configure ? 1 : 0) | + (midend_wants_statusbar(_fe->me) ? 2 : 0) | + (thegame.can_solve ? 4 : 0), n); + for (i = 0; i < n; i++) { + _call_java(1024+ i, + (int)(colours[i*3] * 0xFF), + (int)(colours[i*3+1] * 0xFF), + (int)(colours[i*3+2] * 0xFF)); + } + resize_fe(_fe); + + _call_java(13, midend_which_preset(_fe->me), 0, 0); + + // Now pause the vm. The VM will be call()ed when + // an input event occurs. + _pause(); + + // shut down when the VM is resumed. + deactivate_timer(_fe); + midend_free(_fe->me); + return 0; +} diff --git a/apps/plugins/puzzles/net.R b/apps/plugins/puzzles/net.R new file mode 100644 index 0000000000..8e98216e03 --- /dev/null +++ b/apps/plugins/puzzles/net.R @@ -0,0 +1,23 @@ +# -*- makefile -*- + +NET_EXTRA = tree234 dsf findloop + +net : [X] GTK COMMON net NET_EXTRA net-icon|no-icon + +# The Windows Net shouldn't be called `net.exe' since Windows +# already has a reasonably important utility program by that name! +netgame : [G] WINDOWS COMMON net NET_EXTRA net.res|noicon.res + +ALL += net[COMBINED] NET_EXTRA + +!begin am gtk +GAMES += net +!end + +!begin >list.c + A(net) \ +!end + +!begin >gamedesc.txt +net:netgame.exe:Net:Network jigsaw puzzle:Rotate each tile to reassemble the network. +!end diff --git a/apps/plugins/puzzles/net.c b/apps/plugins/puzzles/net.c new file mode 100644 index 0000000000..18ba7760d5 --- /dev/null +++ b/apps/plugins/puzzles/net.c @@ -0,0 +1,3210 @@ +/* + * net.c: Net game. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +/* + * The standard user interface for Net simply has left- and + * right-button mouse clicks in a square rotate it one way or the + * other. We also provide, by #ifdef, a separate interface based on + * rotational dragging motions. I initially developed this for the + * Mac on the basis that it might work better than the click + * interface with only one mouse button available, but in fact + * found it to be quite strange and unintuitive. Apparently it + * works better on stylus-driven platforms such as Palm and + * PocketPC, though, so we enable it by default there. + */ +#ifdef STYLUS_BASED +#define USE_DRAGGING +#endif + +#define MATMUL(xr,yr,m,x,y) do { \ + float rx, ry, xx = (x), yy = (y), *mat = (m); \ + rx = mat[0] * xx + mat[2] * yy; \ + ry = mat[1] * xx + mat[3] * yy; \ + (xr) = rx; (yr) = ry; \ +} while (0) + +/* Direction and other bitfields */ +#define R 0x01 +#define U 0x02 +#define L 0x04 +#define D 0x08 +#define LOCKED 0x10 +#define ACTIVE 0x20 +#define RLOOP (R << 6) +#define ULOOP (U << 6) +#define LLOOP (L << 6) +#define DLOOP (D << 6) +#define LOOP(dir) ((dir) << 6) + +/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */ +#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) ) +#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) ) +#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) ) +#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \ + ((n)&3) == 1 ? A(x) : \ + ((n)&3) == 2 ? F(x) : C(x) ) + +/* X and Y displacements */ +#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 ) +#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 ) + +/* Bit count */ +#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \ + (((x) & 0x02) >> 1) + ((x) & 0x01) ) + +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) +#define TILE_BORDER 1 +#ifdef SMALL_SCREEN +#define WINDOW_OFFSET 4 +#else +#define WINDOW_OFFSET 16 +#endif + +#define ROTATE_TIME 0.13F +#define FLASH_FRAME 0.07F + +/* Transform physical coords to game coords using game_drawstate ds */ +#define GX(x) (((x) + ds->org_x) % ds->width) +#define GY(y) (((y) + ds->org_y) % ds->height) +/* ...and game coords to physical coords */ +#define RX(x) (((x) + ds->width - ds->org_x) % ds->width) +#define RY(y) (((y) + ds->height - ds->org_y) % ds->height) + +enum { + COL_BACKGROUND, + COL_LOCKED, + COL_BORDER, + COL_WIRE, + COL_ENDPOINT, + COL_POWERED, + COL_BARRIER, + COL_LOOP, + NCOLOURS +}; + +struct game_params { + int width; + int height; + int wrapping; + int unique; + float barrier_probability; +}; + +struct game_state { + int width, height, wrapping, completed; + int last_rotate_x, last_rotate_y, last_rotate_dir; + int used_solve; + unsigned char *tiles; + unsigned char *barriers; +}; + +#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \ + ( (x2) = ((x1) + width + X((dir))) % width, \ + (y2) = ((y1) + height + Y((dir))) % height) + +#define OFFSET(x2,y2,x1,y1,dir,state) \ + OFFSETWH(x2,y2,x1,y1,dir,(state)->width,(state)->height) + +#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] ) +#define tile(state, x, y) index(state, (state)->tiles, x, y) +#define barrier(state, x, y) index(state, (state)->barriers, x, y) + +struct xyd { + int x, y, direction; +}; + +static int xyd_cmp(const void *av, const void *bv) { + const struct xyd *a = (const struct xyd *)av; + const struct xyd *b = (const struct xyd *)bv; + if (a->x < b->x) + return -1; + if (a->x > b->x) + return +1; + if (a->y < b->y) + return -1; + if (a->y > b->y) + return +1; + if (a->direction < b->direction) + return -1; + if (a->direction > b->direction) + return +1; + return 0; +} + +static int xyd_cmp_nc(void *av, void *bv) { return xyd_cmp(av, bv); } + +static struct xyd *new_xyd(int x, int y, int direction) +{ + struct xyd *xyd = snew(struct xyd); + xyd->x = x; + xyd->y = y; + xyd->direction = direction; + return xyd; +} + +/* ---------------------------------------------------------------------- + * Manage game parameters. + */ +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->width = 5; + ret->height = 5; + ret->wrapping = FALSE; + ret->unique = TRUE; + ret->barrier_probability = 0.0; + + return ret; +} + +static const struct game_params net_presets[] = { + {5, 5, FALSE, TRUE, 0.0}, + {7, 7, FALSE, TRUE, 0.0}, + {9, 9, FALSE, TRUE, 0.0}, + {11, 11, FALSE, TRUE, 0.0}, +#ifndef SMALL_SCREEN + {13, 11, FALSE, TRUE, 0.0}, +#endif + {5, 5, TRUE, TRUE, 0.0}, + {7, 7, TRUE, TRUE, 0.0}, + {9, 9, TRUE, TRUE, 0.0}, + {11, 11, TRUE, TRUE, 0.0}, +#ifndef SMALL_SCREEN + {13, 11, TRUE, TRUE, 0.0}, +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(net_presets)) + return FALSE; + + ret = snew(game_params); + *ret = net_presets[i]; + + sprintf(str, "%dx%d%s", ret->width, ret->height, + ret->wrapping ? " wrapping" : ""); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + char const *p = string; + + ret->width = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + ret->height = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + ret->height = ret->width; + } + + while (*p) { + if (*p == 'w') { + p++; + ret->wrapping = TRUE; + } else if (*p == 'b') { + p++; + ret->barrier_probability = (float)atof(p); + while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++; + } else if (*p == 'a') { + p++; + ret->unique = FALSE; + } else + p++; /* skip any other gunk */ + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[400]; + int len; + + len = sprintf(ret, "%dx%d", params->width, params->height); + if (params->wrapping) + ret[len++] = 'w'; + if (full && params->barrier_probability) + len += sprintf(ret+len, "b%g", params->barrier_probability); + if (full && !params->unique) + ret[len++] = 'a'; + assert(len < lenof(ret)); + ret[len] = '\0'; + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(6, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->width); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->height); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Walls wrap around"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->wrapping; + + ret[3].name = "Barrier probability"; + ret[3].type = C_STRING; + sprintf(buf, "%g", params->barrier_probability); + ret[3].sval = dupstr(buf); + ret[3].ival = 0; + + ret[4].name = "Ensure unique solution"; + ret[4].type = C_BOOLEAN; + ret[4].sval = NULL; + ret[4].ival = params->unique; + + ret[5].name = NULL; + ret[5].type = C_END; + ret[5].sval = NULL; + ret[5].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->width = atoi(cfg[0].sval); + ret->height = atoi(cfg[1].sval); + ret->wrapping = cfg[2].ival; + ret->barrier_probability = (float)atof(cfg[3].sval); + ret->unique = cfg[4].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->width <= 0 || params->height <= 0) + return "Width and height must both be greater than zero"; + if (params->width <= 1 && params->height <= 1) + return "At least one of width and height must be greater than one"; + if (params->barrier_probability < 0) + return "Barrier probability may not be negative"; + if (params->barrier_probability > 1) + return "Barrier probability may not be greater than 1"; + + /* + * Specifying either grid dimension as 2 in a wrapping puzzle + * makes it actually impossible to ensure a unique puzzle + * solution. + * + * Proof: + * + * Without loss of generality, let us assume the puzzle _width_ + * is 2, so we can conveniently discuss rows without having to + * say `rows/columns' all the time. (The height may be 2 as + * well, but that doesn't matter.) + * + * In each row, there are two edges between tiles: the inner + * edge (running down the centre of the grid) and the outer + * edge (the identified left and right edges of the grid). + * + * Lemma: In any valid 2xn puzzle there must be at least one + * row in which _exactly one_ of the inner edge and outer edge + * is connected. + * + * Proof: No row can have _both_ inner and outer edges + * connected, because this would yield a loop. So the only + * other way to falsify the lemma is for every row to have + * _neither_ the inner nor outer edge connected. But this + * means there is no connection at all between the left and + * right columns of the puzzle, so there are two disjoint + * subgraphs, which is also disallowed. [] + * + * Given such a row, it is always possible to make the + * disconnected edge connected and the connected edge + * disconnected without changing the state of any other edge. + * (This is easily seen by case analysis on the various tiles: + * left-pointing and right-pointing endpoints can be exchanged, + * likewise T-pieces, and a corner piece can select its + * horizontal connectivity independently of its vertical.) This + * yields a distinct valid solution. + * + * Thus, for _every_ row in which exactly one of the inner and + * outer edge is connected, there are two valid states for that + * row, and hence the total number of solutions of the puzzle + * is at least 2^(number of such rows), and in particular is at + * least 2 since there must be at least one such row. [] + */ + if (full && params->unique && params->wrapping && + (params->width == 2 || params->height == 2)) + return "No wrapping puzzle with a width or height of 2 can have" + " a unique solution"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver used to assure solution uniqueness during generation. + */ + +/* + * Test cases I used while debugging all this were + * + * ./net --generate 1 13x11w#12300 + * which expands under the non-unique grid generation rules to + * 13x11w:5eaade1bd222664436d5e2965c12656b1129dd825219e3274d558d5eb2dab5da18898e571d5a2987be79746bd95726c597447d6da96188c513add829da7681da954db113d3cd244 + * and has two ambiguous areas. + * + * An even better one is + * 13x11w#507896411361192 + * which expands to + * 13x11w:b7125b1aec598eb31bd58d82572bc11494e5dee4e8db2bdd29b88d41a16bdd996d2996ddec8c83741a1e8674e78328ba71737b8894a9271b1cd1399453d1952e43951d9b712822e + * and has an ambiguous area _and_ a situation where loop avoidance + * is a necessary deductive technique. + * + * Then there's + * 48x25w#820543338195187 + * becoming + * 48x25w:255989d14cdd185deaa753a93821a12edc1ab97943ac127e2685d7b8b3c48861b2192416139212b316eddd35de43714ebc7628d753db32e596284d9ec52c5a7dc1b4c811a655117d16dc28921b2b4161352cab1d89d18bc836b8b891d55ea4622a1251861b5bc9a8aa3e5bcd745c95229ca6c3b5e21d5832d397e917325793d7eb442dc351b2db2a52ba8e1651642275842d8871d5534aabc6d5b741aaa2d48ed2a7dbbb3151ddb49d5b9a7ed1ab98ee75d613d656dbba347bc514c84556b43a9bc65a3256ead792488b862a9d2a8a39b4255a4949ed7dbd79443292521265896b4399c95ede89d7c8c797a6a57791a849adea489359a158aa12e5dacce862b8333b7ebea7d344d1a3c53198864b73a9dedde7b663abb1b539e1e8853b1b7edb14a2a17ebaae4dbe63598a2e7e9a2dbdad415bc1d8cb88cbab5a8c82925732cd282e641ea3bd7d2c6e776de9117a26be86deb7c82c89524b122cb9397cd1acd2284e744ea62b9279bae85479ababe315c3ac29c431333395b24e6a1e3c43a2da42d4dce84aadd5b154aea555eaddcbd6e527d228c19388d9b424d94214555a7edbdeebe569d4a56dc51a86bd9963e377bb74752bd5eaa5761ba545e297b62a1bda46ab4aee423ad6c661311783cc18786d4289236563cb4a75ec67d481c14814994464cd1b87396dee63e5ab6e952cc584baa1d4c47cb557ec84dbb63d487c8728118673a166846dd3a4ebc23d6cb9c5827d96b4556e91899db32b517eda815ae271a8911bd745447121dc8d321557bc2a435ebec1bbac35b1a291669451174e6aa2218a4a9c5a6ca31ebc45d84e3a82c121e9ced7d55e9a + * which has a spot (far right) where slightly more complex loop + * avoidance is required. + */ + +struct todo { + unsigned char *marked; + int *buffer; + int buflen; + int head, tail; +}; + +static struct todo *todo_new(int maxsize) +{ + struct todo *todo = snew(struct todo); + todo->marked = snewn(maxsize, unsigned char); + memset(todo->marked, 0, maxsize); + todo->buflen = maxsize + 1; + todo->buffer = snewn(todo->buflen, int); + todo->head = todo->tail = 0; + return todo; +} + +static void todo_free(struct todo *todo) +{ + sfree(todo->marked); + sfree(todo->buffer); + sfree(todo); +} + +static void todo_add(struct todo *todo, int index) +{ + if (todo->marked[index]) + return; /* already on the list */ + todo->marked[index] = TRUE; + todo->buffer[todo->tail++] = index; + if (todo->tail == todo->buflen) + todo->tail = 0; +} + +static int todo_get(struct todo *todo) { + int ret; + + if (todo->head == todo->tail) + return -1; /* list is empty */ + ret = todo->buffer[todo->head++]; + if (todo->head == todo->buflen) + todo->head = 0; + todo->marked[ret] = FALSE; + + return ret; +} + +static int net_solver(int w, int h, unsigned char *tiles, + unsigned char *barriers, int wrapping) +{ + unsigned char *tilestate; + unsigned char *edgestate; + int *deadends; + int *equivalence; + struct todo *todo; + int i, j, x, y; + int area; + int done_something; + + /* + * Set up the solver's data structures. + */ + + /* + * tilestate stores the possible orientations of each tile. + * There are up to four of these, so we'll index the array in + * fours. tilestate[(y * w + x) * 4] and its three successive + * members give the possible orientations, clearing to 255 from + * the end as things are ruled out. + * + * In this loop we also count up the area of the grid (which is + * not _necessarily_ equal to w*h, because there might be one + * or more blank squares present. This will never happen in a + * grid generated _by_ this program, but it's worth keeping the + * solver as general as possible.) + */ + tilestate = snewn(w * h * 4, unsigned char); + area = 0; + for (i = 0; i < w*h; i++) { + tilestate[i * 4] = tiles[i] & 0xF; + for (j = 1; j < 4; j++) { + if (tilestate[i * 4 + j - 1] == 255 || + A(tilestate[i * 4 + j - 1]) == tilestate[i * 4]) + tilestate[i * 4 + j] = 255; + else + tilestate[i * 4 + j] = A(tilestate[i * 4 + j - 1]); + } + if (tiles[i] != 0) + area++; + } + + /* + * edgestate stores the known state of each edge. It is 0 for + * unknown, 1 for open (connected) and 2 for closed (not + * connected). + * + * In principle we need only worry about each edge once each, + * but in fact it's easier to track each edge twice so that we + * can reference it from either side conveniently. Also I'm + * going to allocate _five_ bytes per tile, rather than the + * obvious four, so that I can index edgestate[(y*w+x) * 5 + d] + * where d is 1,2,4,8 and they never overlap. + */ + edgestate = snewn((w * h - 1) * 5 + 9, unsigned char); + memset(edgestate, 0, (w * h - 1) * 5 + 9); + + /* + * deadends tracks which edges have dead ends on them. It is + * indexed by tile and direction: deadends[(y*w+x) * 5 + d] + * tells you whether heading out of tile (x,y) in direction d + * can reach a limited amount of the grid. Values are area+1 + * (no dead end known) or less than that (can reach _at most_ + * this many other tiles by heading this way out of this tile). + */ + deadends = snewn((w * h - 1) * 5 + 9, int); + for (i = 0; i < (w * h - 1) * 5 + 9; i++) + deadends[i] = area+1; + + /* + * equivalence tracks which sets of tiles are known to be + * connected to one another, so we can avoid creating loops by + * linking together tiles which are already linked through + * another route. + * + * This is a disjoint set forest structure: equivalence[i] + * contains the index of another member of the equivalence + * class containing i, or contains i itself for precisely one + * member in each such class. To find a representative member + * of the equivalence class containing i, you keep replacing i + * with equivalence[i] until it stops changing; then you go + * _back_ along the same path and point everything on it + * directly at the representative member so as to speed up + * future searches. Then you test equivalence between tiles by + * finding the representative of each tile and seeing if + * they're the same; and you create new equivalence (merge + * classes) by finding the representative of each tile and + * setting equivalence[one]=the_other. + */ + equivalence = snew_dsf(w * h); + + /* + * On a non-wrapping grid, we instantly know that all the edges + * round the edge are closed. + */ + if (!wrapping) { + for (i = 0; i < w; i++) { + edgestate[i * 5 + 2] = edgestate[((h-1) * w + i) * 5 + 8] = 2; + } + for (i = 0; i < h; i++) { + edgestate[(i * w + w-1) * 5 + 1] = edgestate[(i * w) * 5 + 4] = 2; + } + } + + /* + * If we have barriers available, we can mark those edges as + * closed too. + */ + if (barriers) { + for (y = 0; y < h; y++) for (x = 0; x < w; x++) { + int d; + for (d = 1; d <= 8; d += d) { + if (barriers[y*w+x] & d) { + int x2, y2; + /* + * In principle the barrier list should already + * contain each barrier from each side, but + * let's not take chances with our internal + * consistency. + */ + OFFSETWH(x2, y2, x, y, d, w, h); + edgestate[(y*w+x) * 5 + d] = 2; + edgestate[(y2*w+x2) * 5 + F(d)] = 2; + } + } + } + } + + /* + * Since most deductions made by this solver are local (the + * exception is loop avoidance, where joining two tiles + * together on one side of the grid can theoretically permit a + * fresh deduction on the other), we can address the scaling + * problem inherent in iterating repeatedly over the entire + * grid by instead working with a to-do list. + */ + todo = todo_new(w * h); + + /* + * Main deductive loop. + */ + done_something = TRUE; /* prevent instant termination! */ + while (1) { + int index; + + /* + * Take a tile index off the todo list and process it. + */ + index = todo_get(todo); + if (index == -1) { + /* + * If we have run out of immediate things to do, we + * have no choice but to scan the whole grid for + * longer-range things we've missed. Hence, I now add + * every square on the grid back on to the to-do list. + * I also set `done_something' to FALSE at this point; + * if we later come back here and find it still FALSE, + * we will know we've scanned the entire grid without + * finding anything new to do, and we can terminate. + */ + if (!done_something) + break; + for (i = 0; i < w*h; i++) + todo_add(todo, i); + done_something = FALSE; + + index = todo_get(todo); + } + + y = index / w; + x = index % w; + { + int d, ourclass = dsf_canonify(equivalence, y*w+x); + int deadendmax[9]; + + deadendmax[1] = deadendmax[2] = deadendmax[4] = deadendmax[8] = 0; + + for (i = j = 0; i < 4 && tilestate[(y*w+x) * 4 + i] != 255; i++) { + int valid; + int nnondeadends, nondeadends[4], deadendtotal; + int nequiv, equiv[5]; + int val = tilestate[(y*w+x) * 4 + i]; + + valid = TRUE; + nnondeadends = deadendtotal = 0; + equiv[0] = ourclass; + nequiv = 1; + for (d = 1; d <= 8; d += d) { + /* + * Immediately rule out this orientation if it + * conflicts with any known edge. + */ + if ((edgestate[(y*w+x) * 5 + d] == 1 && !(val & d)) || + (edgestate[(y*w+x) * 5 + d] == 2 && (val & d))) + valid = FALSE; + + if (val & d) { + /* + * Count up the dead-end statistics. + */ + if (deadends[(y*w+x) * 5 + d] <= area) { + deadendtotal += deadends[(y*w+x) * 5 + d]; + } else { + nondeadends[nnondeadends++] = d; + } + + /* + * Ensure we aren't linking to any tiles, + * through edges not already known to be + * open, which create a loop. + */ + if (edgestate[(y*w+x) * 5 + d] == 0) { + int c, k, x2, y2; + + OFFSETWH(x2, y2, x, y, d, w, h); + c = dsf_canonify(equivalence, y2*w+x2); + for (k = 0; k < nequiv; k++) + if (c == equiv[k]) + break; + if (k == nequiv) + equiv[nequiv++] = c; + else + valid = FALSE; + } + } + } + + if (nnondeadends == 0) { + /* + * If this orientation links together dead-ends + * with a total area of less than the entire + * grid, it is invalid. + * + * (We add 1 to deadendtotal because of the + * tile itself, of course; one tile linking + * dead ends of size 2 and 3 forms a subnetwork + * with a total area of 6, not 5.) + */ + if (deadendtotal > 0 && deadendtotal+1 < area) + valid = FALSE; + } else if (nnondeadends == 1) { + /* + * If this orientation links together one or + * more dead-ends with precisely one + * non-dead-end, then we may have to mark that + * non-dead-end as a dead end going the other + * way. However, it depends on whether all + * other orientations share the same property. + */ + deadendtotal++; + if (deadendmax[nondeadends[0]] < deadendtotal) + deadendmax[nondeadends[0]] = deadendtotal; + } else { + /* + * If this orientation links together two or + * more non-dead-ends, then we can rule out the + * possibility of putting in new dead-end + * markings in those directions. + */ + int k; + for (k = 0; k < nnondeadends; k++) + deadendmax[nondeadends[k]] = area+1; + } + + if (valid) + tilestate[(y*w+x) * 4 + j++] = val; +#ifdef SOLVER_DIAGNOSTICS + else + printf("ruling out orientation %x at %d,%d\n", val, x, y); +#endif + } + + assert(j > 0); /* we can't lose _all_ possibilities! */ + + if (j < i) { + done_something = TRUE; + + /* + * We have ruled out at least one tile orientation. + * Make sure the rest are blanked. + */ + while (j < 4) + tilestate[(y*w+x) * 4 + j++] = 255; + } + + /* + * Now go through the tile orientations again and see + * if we've deduced anything new about any edges. + */ + { + int a, o; + a = 0xF; o = 0; + + for (i = 0; i < 4 && tilestate[(y*w+x) * 4 + i] != 255; i++) { + a &= tilestate[(y*w+x) * 4 + i]; + o |= tilestate[(y*w+x) * 4 + i]; + } + for (d = 1; d <= 8; d += d) + if (edgestate[(y*w+x) * 5 + d] == 0) { + int x2, y2, d2; + OFFSETWH(x2, y2, x, y, d, w, h); + d2 = F(d); + if (a & d) { + /* This edge is open in all orientations. */ +#ifdef SOLVER_DIAGNOSTICS + printf("marking edge %d,%d:%d open\n", x, y, d); +#endif + edgestate[(y*w+x) * 5 + d] = 1; + edgestate[(y2*w+x2) * 5 + d2] = 1; + dsf_merge(equivalence, y*w+x, y2*w+x2); + done_something = TRUE; + todo_add(todo, y2*w+x2); + } else if (!(o & d)) { + /* This edge is closed in all orientations. */ +#ifdef SOLVER_DIAGNOSTICS + printf("marking edge %d,%d:%d closed\n", x, y, d); +#endif + edgestate[(y*w+x) * 5 + d] = 2; + edgestate[(y2*w+x2) * 5 + d2] = 2; + done_something = TRUE; + todo_add(todo, y2*w+x2); + } + } + + } + + /* + * Now check the dead-end markers and see if any of + * them has lowered from the real ones. + */ + for (d = 1; d <= 8; d += d) { + int x2, y2, d2; + OFFSETWH(x2, y2, x, y, d, w, h); + d2 = F(d); + if (deadendmax[d] > 0 && + deadends[(y2*w+x2) * 5 + d2] > deadendmax[d]) { +#ifdef SOLVER_DIAGNOSTICS + printf("setting dead end value %d,%d:%d to %d\n", + x2, y2, d2, deadendmax[d]); +#endif + deadends[(y2*w+x2) * 5 + d2] = deadendmax[d]; + done_something = TRUE; + todo_add(todo, y2*w+x2); + } + } + + } + } + + /* + * Mark all completely determined tiles as locked. + */ + j = TRUE; + for (i = 0; i < w*h; i++) { + if (tilestate[i * 4 + 1] == 255) { + assert(tilestate[i * 4 + 0] != 255); + tiles[i] = tilestate[i * 4] | LOCKED; + } else { + tiles[i] &= ~LOCKED; + j = FALSE; + } + } + + /* + * Free up working space. + */ + todo_free(todo); + sfree(tilestate); + sfree(edgestate); + sfree(deadends); + sfree(equivalence); + + return j; +} + +/* ---------------------------------------------------------------------- + * Randomly select a new game description. + */ + +/* + * Function to randomly perturb an ambiguous section in a grid, to + * attempt to ensure unique solvability. + */ +static void perturb(int w, int h, unsigned char *tiles, int wrapping, + random_state *rs, int startx, int starty, int startd) +{ + struct xyd *perimeter, *perim2, *loop[2], looppos[2]; + int nperim, perimsize, nloop[2], loopsize[2]; + int x, y, d, i; + + /* + * We know that the tile at (startx,starty) is part of an + * ambiguous section, and we also know that its neighbour in + * direction startd is fully specified. We begin by tracing all + * the way round the ambiguous area. + */ + nperim = perimsize = 0; + perimeter = NULL; + x = startx; + y = starty; + d = startd; +#ifdef PERTURB_DIAGNOSTICS + printf("perturb %d,%d:%d\n", x, y, d); +#endif + do { + int x2, y2, d2; + + if (nperim >= perimsize) { + perimsize = perimsize * 3 / 2 + 32; + perimeter = sresize(perimeter, perimsize, struct xyd); + } + perimeter[nperim].x = x; + perimeter[nperim].y = y; + perimeter[nperim].direction = d; + nperim++; +#ifdef PERTURB_DIAGNOSTICS + printf("perimeter: %d,%d:%d\n", x, y, d); +#endif + + /* + * First, see if we can simply turn left from where we are + * and find another locked square. + */ + d2 = A(d); + OFFSETWH(x2, y2, x, y, d2, w, h); + if ((!wrapping && (abs(x2-x) > 1 || abs(y2-y) > 1)) || + (tiles[y2*w+x2] & LOCKED)) { + d = d2; + } else { + /* + * Failing that, step left into the new square and look + * in front of us. + */ + x = x2; + y = y2; + OFFSETWH(x2, y2, x, y, d, w, h); + if ((wrapping || (abs(x2-x) <= 1 && abs(y2-y) <= 1)) && + !(tiles[y2*w+x2] & LOCKED)) { + /* + * And failing _that_, we're going to have to step + * forward into _that_ square and look right at the + * same locked square as we started with. + */ + x = x2; + y = y2; + d = C(d); + } + } + + } while (x != startx || y != starty || d != startd); + + /* + * Our technique for perturbing this ambiguous area is to + * search round its edge for a join we can make: that is, an + * edge on the perimeter which is (a) not currently connected, + * and (b) connecting it would not yield a full cross on either + * side. Then we make that join, search round the network to + * find the loop thus constructed, and sever the loop at a + * randomly selected other point. + */ + perim2 = snewn(nperim, struct xyd); + memcpy(perim2, perimeter, nperim * sizeof(struct xyd)); + /* Shuffle the perimeter, so as to search it without directional bias. */ + shuffle(perim2, nperim, sizeof(*perim2), rs); + for (i = 0; i < nperim; i++) { + int x2, y2; + + x = perim2[i].x; + y = perim2[i].y; + d = perim2[i].direction; + + OFFSETWH(x2, y2, x, y, d, w, h); + if (!wrapping && (abs(x2-x) > 1 || abs(y2-y) > 1)) + continue; /* can't link across non-wrapping border */ + if (tiles[y*w+x] & d) + continue; /* already linked in this direction! */ + if (((tiles[y*w+x] | d) & 15) == 15) + continue; /* can't turn this tile into a cross */ + if (((tiles[y2*w+x2] | F(d)) & 15) == 15) + continue; /* can't turn other tile into a cross */ + + /* + * We've found the point at which we're going to make a new + * link. + */ +#ifdef PERTURB_DIAGNOSTICS + printf("linking %d,%d:%d\n", x, y, d); +#endif + tiles[y*w+x] |= d; + tiles[y2*w+x2] |= F(d); + + break; + } + sfree(perim2); + + if (i == nperim) { + sfree(perimeter); + return; /* nothing we can do! */ + } + + /* + * Now we've constructed a new link, we need to find the entire + * loop of which it is a part. + * + * In principle, this involves doing a complete search round + * the network. However, I anticipate that in the vast majority + * of cases the loop will be quite small, so what I'm going to + * do is make _two_ searches round the network in parallel, one + * keeping its metaphorical hand on the left-hand wall while + * the other keeps its hand on the right. As soon as one of + * them gets back to its starting point, I abandon the other. + */ + for (i = 0; i < 2; i++) { + loopsize[i] = nloop[i] = 0; + loop[i] = NULL; + looppos[i].x = x; + looppos[i].y = y; + looppos[i].direction = d; + } + while (1) { + for (i = 0; i < 2; i++) { + int x2, y2, j; + + x = looppos[i].x; + y = looppos[i].y; + d = looppos[i].direction; + + OFFSETWH(x2, y2, x, y, d, w, h); + + /* + * Add this path segment to the loop, unless it exactly + * reverses the previous one on the loop in which case + * we take it away again. + */ +#ifdef PERTURB_DIAGNOSTICS + printf("looppos[%d] = %d,%d:%d\n", i, x, y, d); +#endif + if (nloop[i] > 0 && + loop[i][nloop[i]-1].x == x2 && + loop[i][nloop[i]-1].y == y2 && + loop[i][nloop[i]-1].direction == F(d)) { +#ifdef PERTURB_DIAGNOSTICS + printf("removing path segment %d,%d:%d from loop[%d]\n", + x2, y2, F(d), i); +#endif + nloop[i]--; + } else { + if (nloop[i] >= loopsize[i]) { + loopsize[i] = loopsize[i] * 3 / 2 + 32; + loop[i] = sresize(loop[i], loopsize[i], struct xyd); + } +#ifdef PERTURB_DIAGNOSTICS + printf("adding path segment %d,%d:%d to loop[%d]\n", + x, y, d, i); +#endif + loop[i][nloop[i]++] = looppos[i]; + } + +#ifdef PERTURB_DIAGNOSTICS + printf("tile at new location is %x\n", tiles[y2*w+x2] & 0xF); +#endif + d = F(d); + for (j = 0; j < 4; j++) { + if (i == 0) + d = A(d); + else + d = C(d); +#ifdef PERTURB_DIAGNOSTICS + printf("trying dir %d\n", d); +#endif + if (tiles[y2*w+x2] & d) { + looppos[i].x = x2; + looppos[i].y = y2; + looppos[i].direction = d; + break; + } + } + + assert(j < 4); + assert(nloop[i] > 0); + + if (looppos[i].x == loop[i][0].x && + looppos[i].y == loop[i][0].y && + looppos[i].direction == loop[i][0].direction) { +#ifdef PERTURB_DIAGNOSTICS + printf("loop %d finished tracking\n", i); +#endif + + /* + * Having found our loop, we now sever it at a + * randomly chosen point - absolutely any will do - + * which is not the one we joined it at to begin + * with. Conveniently, the one we joined it at is + * loop[i][0], so we just avoid that one. + */ + j = random_upto(rs, nloop[i]-1) + 1; + x = loop[i][j].x; + y = loop[i][j].y; + d = loop[i][j].direction; + OFFSETWH(x2, y2, x, y, d, w, h); + tiles[y*w+x] &= ~d; + tiles[y2*w+x2] &= ~F(d); + + break; + } + } + if (i < 2) + break; + } + sfree(loop[0]); + sfree(loop[1]); + + /* + * Finally, we must mark the entire disputed section as locked, + * to prevent the perturb function being called on it multiple + * times. + * + * To do this, we _sort_ the perimeter of the area. The + * existing xyd_cmp function will arrange things into columns + * for us, in such a way that each column has the edges in + * vertical order. Then we can work down each column and fill + * in all the squares between an up edge and a down edge. + */ + qsort(perimeter, nperim, sizeof(struct xyd), xyd_cmp); + x = y = -1; + for (i = 0; i <= nperim; i++) { + if (i == nperim || perimeter[i].x > x) { + /* + * Fill in everything from the last Up edge to the + * bottom of the grid, if necessary. + */ + if (x != -1) { + while (y < h) { +#ifdef PERTURB_DIAGNOSTICS + printf("resolved: locking tile %d,%d\n", x, y); +#endif + tiles[y * w + x] |= LOCKED; + y++; + } + x = y = -1; + } + + if (i == nperim) + break; + + x = perimeter[i].x; + y = 0; + } + + if (perimeter[i].direction == U) { + x = perimeter[i].x; + y = perimeter[i].y; + } else if (perimeter[i].direction == D) { + /* + * Fill in everything from the last Up edge to here. + */ + assert(x == perimeter[i].x && y <= perimeter[i].y); + while (y <= perimeter[i].y) { +#ifdef PERTURB_DIAGNOSTICS + printf("resolved: locking tile %d,%d\n", x, y); +#endif + tiles[y * w + x] |= LOCKED; + y++; + } + x = y = -1; + } + } + + sfree(perimeter); +} + +static int *compute_loops_inner(int w, int h, int wrapping, + const unsigned char *tiles, + const unsigned char *barriers); + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + tree234 *possibilities, *barriertree; + int w, h, x, y, cx, cy, nbarriers; + unsigned char *tiles, *barriers; + char *desc, *p; + + w = params->width; + h = params->height; + + cx = w / 2; + cy = h / 2; + + tiles = snewn(w * h, unsigned char); + barriers = snewn(w * h, unsigned char); + + begin_generation: + + memset(tiles, 0, w * h); + memset(barriers, 0, w * h); + + /* + * Construct the unshuffled grid. + * + * To do this, we simply start at the centre point, repeatedly + * choose a random possibility out of the available ways to + * extend a used square into an unused one, and do it. After + * extending the third line out of a square, we remove the + * fourth from the possibilities list to avoid any full-cross + * squares (which would make the game too easy because they + * only have one orientation). + * + * The slightly worrying thing is the avoidance of full-cross + * squares. Can this cause our unsophisticated construction + * algorithm to paint itself into a corner, by getting into a + * situation where there are some unreached squares and the + * only way to reach any of them is to extend a T-piece into a + * full cross? + * + * Answer: no it can't, and here's a proof. + * + * Any contiguous group of such unreachable squares must be + * surrounded on _all_ sides by T-pieces pointing away from the + * group. (If not, then there is a square which can be extended + * into one of the `unreachable' ones, and so it wasn't + * unreachable after all.) In particular, this implies that + * each contiguous group of unreachable squares must be + * rectangular in shape (any deviation from that yields a + * non-T-piece next to an `unreachable' square). + * + * So we have a rectangle of unreachable squares, with T-pieces + * forming a solid border around the rectangle. The corners of + * that border must be connected (since every tile connects all + * the lines arriving in it), and therefore the border must + * form a closed loop around the rectangle. + * + * But this can't have happened in the first place, since we + * _know_ we've avoided creating closed loops! Hence, no such + * situation can ever arise, and the naive grid construction + * algorithm will guaranteeably result in a complete grid + * containing no unreached squares, no full crosses _and_ no + * closed loops. [] + */ + possibilities = newtree234(xyd_cmp_nc); + + if (cx+1 < w) + add234(possibilities, new_xyd(cx, cy, R)); + if (cy-1 >= 0) + add234(possibilities, new_xyd(cx, cy, U)); + if (cx-1 >= 0) + add234(possibilities, new_xyd(cx, cy, L)); + if (cy+1 < h) + add234(possibilities, new_xyd(cx, cy, D)); + + while (count234(possibilities) > 0) { + int i; + struct xyd *xyd; + int x1, y1, d1, x2, y2, d2, d; + + /* + * Extract a randomly chosen possibility from the list. + */ + i = random_upto(rs, count234(possibilities)); + xyd = delpos234(possibilities, i); + x1 = xyd->x; + y1 = xyd->y; + d1 = xyd->direction; + sfree(xyd); + + OFFSET(x2, y2, x1, y1, d1, params); + d2 = F(d1); +#ifdef GENERATION_DIAGNOSTICS + printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n", + x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]); +#endif + + /* + * Make the connection. (We should be moving to an as yet + * unused tile.) + */ + index(params, tiles, x1, y1) |= d1; + assert(index(params, tiles, x2, y2) == 0); + index(params, tiles, x2, y2) |= d2; + + /* + * If we have created a T-piece, remove its last + * possibility. + */ + if (COUNT(index(params, tiles, x1, y1)) == 3) { + struct xyd xyd1, *xydp; + + xyd1.x = x1; + xyd1.y = y1; + xyd1.direction = 0x0F ^ index(params, tiles, x1, y1); + + xydp = find234(possibilities, &xyd1, NULL); + + if (xydp) { +#ifdef GENERATION_DIAGNOSTICS + printf("T-piece; removing (%d,%d,%c)\n", + xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); +#endif + del234(possibilities, xydp); + sfree(xydp); + } + } + + /* + * Remove all other possibilities that were pointing at the + * tile we've just moved into. + */ + for (d = 1; d < 0x10; d <<= 1) { + int x3, y3, d3; + struct xyd xyd1, *xydp; + + OFFSET(x3, y3, x2, y2, d, params); + d3 = F(d); + + xyd1.x = x3; + xyd1.y = y3; + xyd1.direction = d3; + + xydp = find234(possibilities, &xyd1, NULL); + + if (xydp) { +#ifdef GENERATION_DIAGNOSTICS + printf("Loop avoidance; removing (%d,%d,%c)\n", + xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); +#endif + del234(possibilities, xydp); + sfree(xydp); + } + } + + /* + * Add new possibilities to the list for moving _out_ of + * the tile we have just moved into. + */ + for (d = 1; d < 0x10; d <<= 1) { + int x3, y3; + + if (d == d2) + continue; /* we've got this one already */ + + if (!params->wrapping) { + if (d == U && y2 == 0) + continue; + if (d == D && y2 == h-1) + continue; + if (d == L && x2 == 0) + continue; + if (d == R && x2 == w-1) + continue; + } + + OFFSET(x3, y3, x2, y2, d, params); + + if (index(params, tiles, x3, y3)) + continue; /* this would create a loop */ + +#ifdef GENERATION_DIAGNOSTICS + printf("New frontier; adding (%d,%d,%c)\n", + x2, y2, "0RU3L567D9abcdef"[d]); +#endif + add234(possibilities, new_xyd(x2, y2, d)); + } + } + /* Having done that, we should have no possibilities remaining. */ + assert(count234(possibilities) == 0); + freetree234(possibilities); + + if (params->unique) { + int prevn = -1; + + /* + * Run the solver to check unique solubility. + */ + while (!net_solver(w, h, tiles, NULL, params->wrapping)) { + int n = 0; + + /* + * We expect (in most cases) that most of the grid will + * be uniquely specified already, and the remaining + * ambiguous sections will be small and separate. So + * our strategy is to find each individual such + * section, and perform a perturbation on the network + * in that area. + */ + for (y = 0; y < h; y++) for (x = 0; x < w; x++) { + if (x+1 < w && ((tiles[y*w+x] ^ tiles[y*w+x+1]) & LOCKED)) { + n++; + if (tiles[y*w+x] & LOCKED) + perturb(w, h, tiles, params->wrapping, rs, x+1, y, L); + else + perturb(w, h, tiles, params->wrapping, rs, x, y, R); + } + if (y+1 < h && ((tiles[y*w+x] ^ tiles[(y+1)*w+x]) & LOCKED)) { + n++; + if (tiles[y*w+x] & LOCKED) + perturb(w, h, tiles, params->wrapping, rs, x, y+1, U); + else + perturb(w, h, tiles, params->wrapping, rs, x, y, D); + } + } + + /* + * Now n counts the number of ambiguous sections we + * have fiddled with. If we haven't managed to decrease + * it from the last time we ran the solver, give up and + * regenerate the entire grid. + */ + if (prevn != -1 && prevn <= n) + goto begin_generation; /* (sorry) */ + + prevn = n; + } + + /* + * The solver will have left a lot of LOCKED bits lying + * around in the tiles array. Remove them. + */ + for (x = 0; x < w*h; x++) + tiles[x] &= ~LOCKED; + } + + /* + * Now compute a list of the possible barrier locations. + */ + barriertree = newtree234(xyd_cmp_nc); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + + if (!(index(params, tiles, x, y) & R) && + (params->wrapping || x < w-1)) + add234(barriertree, new_xyd(x, y, R)); + if (!(index(params, tiles, x, y) & D) && + (params->wrapping || y < h-1)) + add234(barriertree, new_xyd(x, y, D)); + } + } + + /* + * Save the unshuffled grid in aux. + */ + { + char *solution; + int i; + + solution = snewn(w * h + 1, char); + for (i = 0; i < w * h; i++) + solution[i] = "0123456789abcdef"[tiles[i] & 0xF]; + solution[w*h] = '\0'; + + *aux = solution; + } + + /* + * Now shuffle the grid. + * + * In order to avoid accidentally generating an already-solved + * grid, we will reshuffle as necessary to ensure that at least + * one edge has a mismatched connection. + * + * This can always be done, since validate_params() enforces a + * grid area of at least 2 and our generator never creates + * either type of rotationally invariant tile (cross and + * blank). Hence there must be at least one edge separating + * distinct tiles, and it must be possible to find orientations + * of those tiles such that one tile is trying to connect + * through that edge and the other is not. + * + * (We could be more subtle, and allow the shuffle to generate + * a grid in which all tiles match up locally and the only + * criterion preventing the grid from being already solved is + * connectedness. However, that would take more effort, and + * it's easier to simply make sure every grid is _obviously_ + * not solved.) + * + * We also require that our shuffle produces no loops in the + * initial grid state, because it's a bit rude to light up a 'HEY, + * YOU DID SOMETHING WRONG!' indicator when the user hasn't even + * had a chance to do _anything_ yet. This also is possible just + * by retrying the whole shuffle on failure, because it's clear + * that at least one non-solved shuffle with no loops must exist. + * (Proof: take the _solved_ state of the puzzle, and rotate one + * endpoint.) + */ + while (1) { + int mismatches, prev_loopsquares, this_loopsquares, i; + int *loops; + + shuffle: + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int orig = index(params, tiles, x, y); + int rot = random_upto(rs, 4); + index(params, tiles, x, y) = ROT(orig, rot); + } + } + + /* + * Check for loops, and try to fix them by reshuffling just + * the squares involved. + */ + prev_loopsquares = w*h+1; + while (1) { + loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL); + this_loopsquares = 0; + for (i = 0; i < w*h; i++) { + if (loops[i]) { + int orig = tiles[i]; + int rot = random_upto(rs, 4); + tiles[i] = ROT(orig, rot); + this_loopsquares++; + } + } + sfree(loops); + if (this_loopsquares > prev_loopsquares) { + /* + * We're increasing rather than reducing the number of + * loops. Give up and go back to the full shuffle. + */ + goto shuffle; + } + if (this_loopsquares == 0) + break; + prev_loopsquares = this_loopsquares; + } + + mismatches = 0; + /* + * I can't even be bothered to check for mismatches across + * a wrapping edge, so I'm just going to enforce that there + * must be a mismatch across a non-wrapping edge, which is + * still always possible. + */ + for (y = 0; y < h; y++) for (x = 0; x < w; x++) { + if (x+1 < w && ((ROT(index(params, tiles, x, y), 2) ^ + index(params, tiles, x+1, y)) & L)) + mismatches++; + if (y+1 < h && ((ROT(index(params, tiles, x, y), 2) ^ + index(params, tiles, x, y+1)) & U)) + mismatches++; + } + + if (mismatches == 0) + continue; + + /* OK. */ + break; + } + + /* + * And now choose barrier locations. (We carefully do this + * _after_ shuffling, so that changing the barrier rate in the + * params while keeping the random seed the same will give the + * same shuffled grid and _only_ change the barrier locations. + * Also the way we choose barrier locations, by repeatedly + * choosing one possibility from the list until we have enough, + * is designed to ensure that raising the barrier rate while + * keeping the seed the same will provide a superset of the + * previous barrier set - i.e. if you ask for 10 barriers, and + * then decide that's still too hard and ask for 20, you'll get + * the original 10 plus 10 more, rather than getting 20 new + * ones and the chance of remembering your first 10.) + */ + nbarriers = (int)(params->barrier_probability * count234(barriertree)); + assert(nbarriers >= 0 && nbarriers <= count234(barriertree)); + + while (nbarriers > 0) { + int i; + struct xyd *xyd; + int x1, y1, d1, x2, y2, d2; + + /* + * Extract a randomly chosen barrier from the list. + */ + i = random_upto(rs, count234(barriertree)); + xyd = delpos234(barriertree, i); + + assert(xyd != NULL); + + x1 = xyd->x; + y1 = xyd->y; + d1 = xyd->direction; + sfree(xyd); + + OFFSET(x2, y2, x1, y1, d1, params); + d2 = F(d1); + + index(params, barriers, x1, y1) |= d1; + index(params, barriers, x2, y2) |= d2; + + nbarriers--; + } + + /* + * Clean up the rest of the barrier list. + */ + { + struct xyd *xyd; + + while ( (xyd = delpos234(barriertree, 0)) != NULL) + sfree(xyd); + + freetree234(barriertree); + } + + /* + * Finally, encode the grid into a string game description. + * + * My syntax is extremely simple: each square is encoded as a + * hex digit in which bit 0 means a connection on the right, + * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same + * encoding as used internally). Each digit is followed by + * optional barrier indicators: `v' means a vertical barrier to + * the right of it, and `h' means a horizontal barrier below + * it. + */ + desc = snewn(w * h * 3 + 1, char); + p = desc; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + *p++ = "0123456789abcdef"[index(params, tiles, x, y)]; + if ((params->wrapping || x < w-1) && + (index(params, barriers, x, y) & R)) + *p++ = 'v'; + if ((params->wrapping || y < h-1) && + (index(params, barriers, x, y) & D)) + *p++ = 'h'; + } + } + assert(p - desc <= w*h*3); + *p = '\0'; + + sfree(tiles); + sfree(barriers); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->width, h = params->height; + int i; + + for (i = 0; i < w*h; i++) { + if (*desc >= '0' && *desc <= '9') + /* OK */; + else if (*desc >= 'a' && *desc <= 'f') + /* OK */; + else if (*desc >= 'A' && *desc <= 'F') + /* OK */; + else if (!*desc) + return "Game description shorter than expected"; + else + return "Game description contained unexpected character"; + desc++; + while (*desc == 'h' || *desc == 'v') + desc++; + } + if (*desc) + return "Game description longer than expected"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Construct an initial game state, given a description and parameters. + */ + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state; + int w, h, x, y; + + assert(params->width > 0 && params->height > 0); + assert(params->width > 1 || params->height > 1); + + /* + * Create a blank game state. + */ + state = snew(game_state); + w = state->width = params->width; + h = state->height = params->height; + state->wrapping = params->wrapping; + state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0; + state->completed = state->used_solve = FALSE; + state->tiles = snewn(state->width * state->height, unsigned char); + memset(state->tiles, 0, state->width * state->height); + state->barriers = snewn(state->width * state->height, unsigned char); + memset(state->barriers, 0, state->width * state->height); + + /* + * Parse the game description into the grid. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (*desc >= '0' && *desc <= '9') + tile(state, x, y) = *desc - '0'; + else if (*desc >= 'a' && *desc <= 'f') + tile(state, x, y) = *desc - 'a' + 10; + else if (*desc >= 'A' && *desc <= 'F') + tile(state, x, y) = *desc - 'A' + 10; + if (*desc) + desc++; + while (*desc == 'h' || *desc == 'v') { + int x2, y2, d1, d2; + if (*desc == 'v') + d1 = R; + else + d1 = D; + + OFFSET(x2, y2, x, y, d1, state); + d2 = F(d1); + + barrier(state, x, y) |= d1; + barrier(state, x2, y2) |= d2; + + desc++; + } + } + } + + /* + * Set up border barriers if this is a non-wrapping game. + */ + if (!state->wrapping) { + for (x = 0; x < state->width; x++) { + barrier(state, x, 0) |= U; + barrier(state, x, state->height-1) |= D; + } + for (y = 0; y < state->height; y++) { + barrier(state, 0, y) |= L; + barrier(state, state->width-1, y) |= R; + } + } else { + /* + * We check whether this is de-facto a non-wrapping game + * despite the parameters, in case we were passed the + * description of a non-wrapping game. This is so that we + * can change some aspects of the UI behaviour. + */ + state->wrapping = FALSE; + for (x = 0; x < state->width; x++) + if (!(barrier(state, x, 0) & U) || + !(barrier(state, x, state->height-1) & D)) + state->wrapping = TRUE; + for (y = 0; y < state->height; y++) + if (!(barrier(state, 0, y) & L) || + !(barrier(state, state->width-1, y) & R)) + state->wrapping = TRUE; + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret; + + ret = snew(game_state); + ret->width = state->width; + ret->height = state->height; + ret->wrapping = state->wrapping; + ret->completed = state->completed; + ret->used_solve = state->used_solve; + ret->last_rotate_dir = state->last_rotate_dir; + ret->last_rotate_x = state->last_rotate_x; + ret->last_rotate_y = state->last_rotate_y; + ret->tiles = snewn(state->width * state->height, unsigned char); + memcpy(ret->tiles, state->tiles, state->width * state->height); + ret->barriers = snewn(state->width * state->height, unsigned char); + memcpy(ret->barriers, state->barriers, state->width * state->height); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->tiles); + sfree(state->barriers); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + unsigned char *tiles; + char *ret; + int retlen, retsize; + int i; + + tiles = snewn(state->width * state->height, unsigned char); + + if (!aux) { + /* + * Run the internal solver on the provided grid. This might + * not yield a complete solution. + */ + memcpy(tiles, state->tiles, state->width * state->height); + net_solver(state->width, state->height, tiles, + state->barriers, state->wrapping); + } else { + for (i = 0; i < state->width * state->height; i++) { + int c = aux[i]; + + if (c >= '0' && c <= '9') + tiles[i] = c - '0'; + else if (c >= 'a' && c <= 'f') + tiles[i] = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + tiles[i] = c - 'A' + 10; + + tiles[i] |= LOCKED; + } + } + + /* + * Now construct a string which can be passed to execute_move() + * to transform the current grid into the solved one. + */ + retsize = 256; + ret = snewn(retsize, char); + retlen = 0; + ret[retlen++] = 'S'; + + for (i = 0; i < state->width * state->height; i++) { + int from = currstate->tiles[i], to = tiles[i]; + int ft = from & (R|L|U|D), tt = to & (R|L|U|D); + int x = i % state->width, y = i / state->width; + int chr = '\0'; + char buf[80], *p = buf; + + if (from == to) + continue; /* nothing needs doing at all */ + + /* + * To transform this tile into the desired tile: first + * unlock the tile if it's locked, then rotate it if + * necessary, then lock it if necessary. + */ + if (from & LOCKED) + p += sprintf(p, ";L%d,%d", x, y); + + if (tt == A(ft)) + chr = 'A'; + else if (tt == C(ft)) + chr = 'C'; + else if (tt == F(ft)) + chr = 'F'; + else { + assert(tt == ft); + chr = '\0'; + } + if (chr) + p += sprintf(p, ";%c%d,%d", chr, x, y); + + if (to & LOCKED) + p += sprintf(p, ";L%d,%d", x, y); + + if (p > buf) { + if (retlen + (p - buf) >= retsize) { + retsize = retlen + (p - buf) + 512; + ret = sresize(ret, retsize, char); + } + memcpy(ret+retlen, buf, p - buf); + retlen += p - buf; + } + } + + assert(retlen < retsize); + ret[retlen] = '\0'; + ret = sresize(ret, retlen+1, char); + + sfree(tiles); + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +/* ---------------------------------------------------------------------- + * Utility routine. + */ + +/* + * Compute which squares are reachable from the centre square, as a + * quick visual aid to determining how close the game is to + * completion. This is also a simple way to tell if the game _is_ + * completed - just call this function and see whether every square + * is marked active. + */ +static unsigned char *compute_active(const game_state *state, int cx, int cy) +{ + unsigned char *active; + tree234 *todo; + struct xyd *xyd; + + active = snewn(state->width * state->height, unsigned char); + memset(active, 0, state->width * state->height); + + /* + * We only store (x,y) pairs in todo, but it's easier to reuse + * xyd_cmp and just store direction 0 every time. + */ + todo = newtree234(xyd_cmp_nc); + index(state, active, cx, cy) = ACTIVE; + add234(todo, new_xyd(cx, cy, 0)); + + while ( (xyd = delpos234(todo, 0)) != NULL) { + int x1, y1, d1, x2, y2, d2; + + x1 = xyd->x; + y1 = xyd->y; + sfree(xyd); + + for (d1 = 1; d1 < 0x10; d1 <<= 1) { + OFFSET(x2, y2, x1, y1, d1, state); + d2 = F(d1); + + /* + * If the next tile in this direction is connected to + * us, and there isn't a barrier in the way, and it + * isn't already marked active, then mark it active and + * add it to the to-examine list. + */ + if ((tile(state, x1, y1) & d1) && + (tile(state, x2, y2) & d2) && + !(barrier(state, x1, y1) & d1) && + !index(state, active, x2, y2)) { + index(state, active, x2, y2) = ACTIVE; + add234(todo, new_xyd(x2, y2, 0)); + } + } + } + /* Now we expect the todo list to have shrunk to zero size. */ + assert(count234(todo) == 0); + freetree234(todo); + + return active; +} + +struct net_neighbour_ctx { + int w, h; + const unsigned char *tiles, *barriers; + int i, n, neighbours[4]; +}; +static int net_neighbour(int vertex, void *vctx) +{ + struct net_neighbour_ctx *ctx = (struct net_neighbour_ctx *)vctx; + + if (vertex >= 0) { + int x = vertex % ctx->w, y = vertex / ctx->w; + int tile, dir, x1, y1, v1; + + ctx->i = ctx->n = 0; + + tile = ctx->tiles[vertex]; + if (ctx->barriers) + tile &= ~ctx->barriers[vertex]; + + for (dir = 1; dir < 0x10; dir <<= 1) { + if (!(tile & dir)) + continue; + OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h); + v1 = y1 * ctx->w + x1; + if (ctx->tiles[v1] & F(dir)) + ctx->neighbours[ctx->n++] = v1; + } + } + + if (ctx->i < ctx->n) + return ctx->neighbours[ctx->i++]; + else + return -1; +} + +static int *compute_loops_inner(int w, int h, int wrapping, + const unsigned char *tiles, + const unsigned char *barriers) +{ + struct net_neighbour_ctx ctx; + struct findloopstate *fls; + int *loops; + int x, y; + + fls = findloop_new_state(w*h); + ctx.w = w; + ctx.h = h; + ctx.tiles = tiles; + ctx.barriers = barriers; + findloop_run(fls, w*h, net_neighbour, &ctx); + + loops = snewn(w*h, int); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int x1, y1, dir; + int flags = 0; + + for (dir = 1; dir < 0x10; dir <<= 1) { + if ((tiles[y*w+x] & dir) && + !(barriers && (barriers[y*w+x] & dir))) { + OFFSETWH(x1, y1, x, y, dir, w, h); + if ((tiles[y1*w+x1] & F(dir)) && + findloop_is_loop_edge(fls, y*w+x, y1*w+x1)) + flags |= LOOP(dir); + } + } + loops[y*w+x] = flags; + } + } + + findloop_free_state(fls); + return loops; +} + +static int *compute_loops(const game_state *state) +{ + return compute_loops_inner(state->width, state->height, state->wrapping, + state->tiles, state->barriers); +} + +struct game_ui { + int org_x, org_y; /* origin */ + int cx, cy; /* source tile (game coordinates) */ + int cur_x, cur_y; + int cur_visible; + random_state *rs; /* used for jumbling */ +#ifdef USE_DRAGGING + int dragtilex, dragtiley, dragstartx, dragstarty, dragged; +#endif +}; + +static game_ui *new_ui(const game_state *state) +{ + void *seed; + int seedsize; + game_ui *ui = snew(game_ui); + ui->org_x = ui->org_y = 0; + ui->cur_x = ui->cx = state->width / 2; + ui->cur_y = ui->cy = state->height / 2; + ui->cur_visible = FALSE; + get_random_seed(&seed, &seedsize); + ui->rs = random_new(seed, seedsize); + sfree(seed); + + return ui; +} + +static void free_ui(game_ui *ui) +{ + random_free(ui->rs); + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + char buf[120]; + /* + * We preserve the origin and centre-point coordinates over a + * serialise. + */ + sprintf(buf, "O%d,%d;C%d,%d", ui->org_x, ui->org_y, ui->cx, ui->cy); + return dupstr(buf); +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + sscanf(encoding, "O%d,%d;C%d,%d", + &ui->org_x, &ui->org_y, &ui->cx, &ui->cy); +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int width, height; + int org_x, org_y; + int tilesize; + int *visible; +}; + +/* ---------------------------------------------------------------------- + * Process a move. + */ +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + char *nullret; + int tx = -1, ty = -1, dir = 0; + int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL; + enum { + NONE, ROTATE_LEFT, ROTATE_180, ROTATE_RIGHT, TOGGLE_LOCK, JUMBLE, + MOVE_ORIGIN, MOVE_SOURCE, MOVE_ORIGIN_AND_SOURCE, MOVE_CURSOR + } action; + + button &= ~MOD_MASK; + nullret = NULL; + action = NONE; + + if (button == LEFT_BUTTON || + button == MIDDLE_BUTTON || +#ifdef USE_DRAGGING + button == LEFT_DRAG || + button == LEFT_RELEASE || + button == RIGHT_DRAG || + button == RIGHT_RELEASE || +#endif + button == RIGHT_BUTTON) { + + if (ui->cur_visible) { + ui->cur_visible = FALSE; + nullret = ""; + } + + /* + * The button must have been clicked on a valid tile. + */ + x -= WINDOW_OFFSET + TILE_BORDER; + y -= WINDOW_OFFSET + TILE_BORDER; + if (x < 0 || y < 0) + return nullret; + tx = x / TILE_SIZE; + ty = y / TILE_SIZE; + if (tx >= state->width || ty >= state->height) + return nullret; + /* Transform from physical to game coords */ + tx = (tx + ui->org_x) % state->width; + ty = (ty + ui->org_y) % state->height; + if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER || + y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) + return nullret; + +#ifdef USE_DRAGGING + + if (button == MIDDLE_BUTTON +#ifdef STYLUS_BASED + || button == RIGHT_BUTTON /* with a stylus, `right-click' locks */ +#endif + ) { + /* + * Middle button never drags: it only toggles the lock. + */ + action = TOGGLE_LOCK; + } else if (button == LEFT_BUTTON +#ifndef STYLUS_BASED + || button == RIGHT_BUTTON /* (see above) */ +#endif + ) { + /* + * Otherwise, we note down the start point for a drag. + */ + ui->dragtilex = tx; + ui->dragtiley = ty; + ui->dragstartx = x % TILE_SIZE; + ui->dragstarty = y % TILE_SIZE; + ui->dragged = FALSE; + return nullret; /* no actual action */ + } else if (button == LEFT_DRAG +#ifndef STYLUS_BASED + || button == RIGHT_DRAG +#endif + ) { + /* + * Find the new drag point and see if it necessitates a + * rotation. + */ + int x0,y0, xA,yA, xC,yC, xF,yF; + int mx, my; + int d0, dA, dC, dF, dmin; + + tx = ui->dragtilex; + ty = ui->dragtiley; + + mx = x - (ui->dragtilex * TILE_SIZE); + my = y - (ui->dragtiley * TILE_SIZE); + + x0 = ui->dragstartx; + y0 = ui->dragstarty; + xA = ui->dragstarty; + yA = TILE_SIZE-1 - ui->dragstartx; + xF = TILE_SIZE-1 - ui->dragstartx; + yF = TILE_SIZE-1 - ui->dragstarty; + xC = TILE_SIZE-1 - ui->dragstarty; + yC = ui->dragstartx; + + d0 = (mx-x0)*(mx-x0) + (my-y0)*(my-y0); + dA = (mx-xA)*(mx-xA) + (my-yA)*(my-yA); + dF = (mx-xF)*(mx-xF) + (my-yF)*(my-yF); + dC = (mx-xC)*(mx-xC) + (my-yC)*(my-yC); + + dmin = min(min(d0,dA),min(dF,dC)); + + if (d0 == dmin) { + return nullret; + } else if (dF == dmin) { + action = ROTATE_180; + ui->dragstartx = xF; + ui->dragstarty = yF; + ui->dragged = TRUE; + } else if (dA == dmin) { + action = ROTATE_LEFT; + ui->dragstartx = xA; + ui->dragstarty = yA; + ui->dragged = TRUE; + } else /* dC == dmin */ { + action = ROTATE_RIGHT; + ui->dragstartx = xC; + ui->dragstarty = yC; + ui->dragged = TRUE; + } + } else if (button == LEFT_RELEASE +#ifndef STYLUS_BASED + || button == RIGHT_RELEASE +#endif + ) { + if (!ui->dragged) { + /* + * There was a click but no perceptible drag: + * revert to single-click behaviour. + */ + tx = ui->dragtilex; + ty = ui->dragtiley; + + if (button == LEFT_RELEASE) + action = ROTATE_LEFT; + else + action = ROTATE_RIGHT; + } else + return nullret; /* no action */ + } + +#else /* USE_DRAGGING */ + + action = (button == LEFT_BUTTON ? ROTATE_LEFT : + button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK); + +#endif /* USE_DRAGGING */ + + } else if (IS_CURSOR_MOVE(button)) { + switch (button) { + case CURSOR_UP: dir = U; break; + case CURSOR_DOWN: dir = D; break; + case CURSOR_LEFT: dir = L; break; + case CURSOR_RIGHT: dir = R; break; + default: return nullret; + } + if (shift && ctrl) action = MOVE_ORIGIN_AND_SOURCE; + else if (shift) action = MOVE_ORIGIN; + else if (ctrl) action = MOVE_SOURCE; + else action = MOVE_CURSOR; + } else if (button == 'a' || button == 's' || button == 'd' || + button == 'A' || button == 'S' || button == 'D' || + button == 'f' || button == 'F' || + IS_CURSOR_SELECT(button)) { + tx = ui->cur_x; + ty = ui->cur_y; + if (button == 'a' || button == 'A' || button == CURSOR_SELECT) + action = ROTATE_LEFT; + else if (button == 's' || button == 'S' || button == CURSOR_SELECT2) + action = TOGGLE_LOCK; + else if (button == 'd' || button == 'D') + action = ROTATE_RIGHT; + else if (button == 'f' || button == 'F') + action = ROTATE_180; + ui->cur_visible = TRUE; + } else if (button == 'j' || button == 'J') { + /* XXX should we have some mouse control for this? */ + action = JUMBLE; + } else + return nullret; + + /* + * The middle button locks or unlocks a tile. (A locked tile + * cannot be turned, and is visually marked as being locked. + * This is a convenience for the player, so that once they are + * sure which way round a tile goes, they can lock it and thus + * avoid forgetting later on that they'd already done that one; + * and the locking also prevents them turning the tile by + * accident. If they change their mind, another middle click + * unlocks it.) + */ + if (action == TOGGLE_LOCK) { + char buf[80]; + sprintf(buf, "L%d,%d", tx, ty); + return dupstr(buf); + } else if (action == ROTATE_LEFT || action == ROTATE_RIGHT || + action == ROTATE_180) { + char buf[80]; + + /* + * The left and right buttons have no effect if clicked on a + * locked tile. + */ + if (tile(state, tx, ty) & LOCKED) + return nullret; + + /* + * Otherwise, turn the tile one way or the other. Left button + * turns anticlockwise; right button turns clockwise. + */ + sprintf(buf, "%c%d,%d", (int)(action == ROTATE_LEFT ? 'A' : + action == ROTATE_RIGHT ? 'C' : 'F'), tx, ty); + return dupstr(buf); + } else if (action == JUMBLE) { + /* + * Jumble all unlocked tiles to random orientations. + */ + + int jx, jy, maxlen; + char *ret, *p; + + /* + * Maximum string length assumes no int can be converted to + * decimal and take more than 11 digits! + */ + maxlen = state->width * state->height * 25 + 3; + + ret = snewn(maxlen, char); + p = ret; + *p++ = 'J'; + + for (jy = 0; jy < state->height; jy++) { + for (jx = 0; jx < state->width; jx++) { + if (!(tile(state, jx, jy) & LOCKED)) { + int rot = random_upto(ui->rs, 4); + if (rot) { + p += sprintf(p, ";%c%d,%d", "AFC"[rot-1], jx, jy); + } + } + } + } + *p++ = '\0'; + assert(p - ret < maxlen); + ret = sresize(ret, p - ret, char); + + return ret; + } else if (action == MOVE_ORIGIN || action == MOVE_SOURCE || + action == MOVE_ORIGIN_AND_SOURCE || action == MOVE_CURSOR) { + assert(dir != 0); + if (action == MOVE_ORIGIN || action == MOVE_ORIGIN_AND_SOURCE) { + if (state->wrapping) { + OFFSET(ui->org_x, ui->org_y, ui->org_x, ui->org_y, dir, state); + } else return nullret; /* disallowed for non-wrapping grids */ + } + if (action == MOVE_SOURCE || action == MOVE_ORIGIN_AND_SOURCE) { + OFFSET(ui->cx, ui->cy, ui->cx, ui->cy, dir, state); + } + if (action == MOVE_CURSOR) { + OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state); + ui->cur_visible = TRUE; + } + return ""; + } else { + return NULL; + } +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + game_state *ret; + int tx = -1, ty = -1, n, noanim, orig; + + ret = dup_game(from); + + if (move[0] == 'J' || move[0] == 'S') { + if (move[0] == 'S') + ret->used_solve = TRUE; + + move++; + if (*move == ';') + move++; + noanim = TRUE; + } else + noanim = FALSE; + + ret->last_rotate_dir = 0; /* suppress animation */ + ret->last_rotate_x = ret->last_rotate_y = 0; + + while (*move) { + if ((move[0] == 'A' || move[0] == 'C' || + move[0] == 'F' || move[0] == 'L') && + sscanf(move+1, "%d,%d%n", &tx, &ty, &n) >= 2 && + tx >= 0 && tx < from->width && ty >= 0 && ty < from->height) { + orig = tile(ret, tx, ty); + if (move[0] == 'A') { + tile(ret, tx, ty) = A(orig); + if (!noanim) + ret->last_rotate_dir = +1; + } else if (move[0] == 'F') { + tile(ret, tx, ty) = F(orig); + if (!noanim) + ret->last_rotate_dir = +2; /* + for sake of argument */ + } else if (move[0] == 'C') { + tile(ret, tx, ty) = C(orig); + if (!noanim) + ret->last_rotate_dir = -1; + } else { + assert(move[0] == 'L'); + tile(ret, tx, ty) ^= LOCKED; + } + + move += 1 + n; + if (*move == ';') move++; + } else { + free_game(ret); + return NULL; + } + } + if (!noanim) { + if (tx == -1 || ty == -1) { free_game(ret); return NULL; } + ret->last_rotate_x = tx; + ret->last_rotate_y = ty; + } + + /* + * Check whether the game has been completed. + * + * For this purpose it doesn't matter where the source square + * is, because we can start from anywhere and correctly + * determine whether the game is completed. + */ + { + unsigned char *active = compute_active(ret, 0, 0); + int x1, y1; + int complete = TRUE; + + for (x1 = 0; x1 < ret->width; x1++) + for (y1 = 0; y1 < ret->height; y1++) + if ((tile(ret, x1, y1) & 0xF) && !index(ret, active, x1, y1)) { + complete = FALSE; + goto break_label; /* break out of two loops at once */ + } + break_label: + + sfree(active); + + if (complete) + ret->completed = TRUE; + } + + return ret; +} + + +/* ---------------------------------------------------------------------- + * Routines for drawing the game position on the screen. + */ + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + game_drawstate *ds = snew(game_drawstate); + int i; + + ds->started = FALSE; + ds->width = state->width; + ds->height = state->height; + ds->org_x = ds->org_y = -1; + ds->visible = snewn(state->width * state->height, int); + ds->tilesize = 0; /* undecided yet */ + for (i = 0; i < state->width * state->height; i++) + ds->visible[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER; + *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret; + + ret = snewn(NCOLOURS * 3, float); + *ncolours = NCOLOURS; + + /* + * Basic background colour is whatever the front end thinks is + * a sensible default. + */ + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + /* + * Wires are black. + */ + ret[COL_WIRE * 3 + 0] = 0.0F; + ret[COL_WIRE * 3 + 1] = 0.0F; + ret[COL_WIRE * 3 + 2] = 0.0F; + + /* + * Powered wires and powered endpoints are cyan. + */ + ret[COL_POWERED * 3 + 0] = 0.0F; + ret[COL_POWERED * 3 + 1] = 1.0F; + ret[COL_POWERED * 3 + 2] = 1.0F; + + /* + * Barriers are red. + */ + ret[COL_BARRIER * 3 + 0] = 1.0F; + ret[COL_BARRIER * 3 + 1] = 0.0F; + ret[COL_BARRIER * 3 + 2] = 0.0F; + + /* + * Highlighted loops are red as well. + */ + ret[COL_LOOP * 3 + 0] = 1.0F; + ret[COL_LOOP * 3 + 1] = 0.0F; + ret[COL_LOOP * 3 + 2] = 0.0F; + + /* + * Unpowered endpoints are blue. + */ + ret[COL_ENDPOINT * 3 + 0] = 0.0F; + ret[COL_ENDPOINT * 3 + 1] = 0.0F; + ret[COL_ENDPOINT * 3 + 2] = 1.0F; + + /* + * Tile borders are a darker grey than the background. + */ + ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2]; + + /* + * Locked tiles are a grey in between those two. + */ + ret[COL_LOCKED * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_LOCKED * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_LOCKED * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2]; + + return ret; +} + +static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, + int colour) +{ + draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); + draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); + draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE); + draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE); + draw_line(dr, x1, y1, x2, y2, colour); +} + +static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2, + int colour) +{ + int mx = (x1 < x2 ? x1 : x2); + int my = (y1 < y2 ? y1 : y2); + int dx = (x2 + x1 - 2*mx + 1); + int dy = (y2 + y1 - 2*my + 1); + + draw_rect(dr, mx, my, dx, dy, colour); +} + +/* + * draw_barrier_corner() and draw_barrier() are passed physical coords + */ +static void draw_barrier_corner(drawing *dr, game_drawstate *ds, + int x, int y, int dx, int dy, int phase) +{ + int bx = WINDOW_OFFSET + TILE_SIZE * x; + int by = WINDOW_OFFSET + TILE_SIZE * y; + int x1, y1; + + x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); + y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); + + if (phase == 0) { + draw_rect_coords(dr, bx+x1+dx, by+y1, + bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy, + COL_WIRE); + draw_rect_coords(dr, bx+x1, by+y1+dy, + bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy, + COL_WIRE); + } else { + draw_rect_coords(dr, bx+x1, by+y1, + bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy, + COL_BARRIER); + } +} + +static void draw_barrier(drawing *dr, game_drawstate *ds, + int x, int y, int dir, int phase) +{ + int bx = WINDOW_OFFSET + TILE_SIZE * x; + int by = WINDOW_OFFSET + TILE_SIZE * y; + int x1, y1, w, h; + + x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0); + y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0); + w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); + h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); + + if (phase == 0) { + draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE); + } else { + draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER); + } +} + +/* + * draw_tile() is passed physical coordinates + */ +static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds, + int x, int y, int tile, int src, float angle, int cursor) +{ + int bx = WINDOW_OFFSET + TILE_SIZE * x; + int by = WINDOW_OFFSET + TILE_SIZE * y; + float matrix[4]; + float cx, cy, ex, ey, tx, ty; + int dir, col, phase; + + /* + * When we draw a single tile, we must draw everything up to + * and including the borders around the tile. This means that + * if the neighbouring tiles have connections to those borders, + * we must draw those connections on the borders themselves. + */ + + clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); + + /* + * So. First blank the tile out completely: draw a big + * rectangle in border colour, and a smaller rectangle in + * background colour to fill it in. + */ + draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER, + COL_BORDER); + draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER, + TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER, + tile & LOCKED ? COL_LOCKED : COL_BACKGROUND); + + /* + * Draw an inset outline rectangle as a cursor, in whichever of + * COL_LOCKED and COL_BACKGROUND we aren't currently drawing + * in. + */ + if (cursor) { + draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, + bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, + tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); + draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8, + bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, + tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); + draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8, + bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, + tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); + draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, + bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8, + tile & LOCKED ? COL_BACKGROUND : COL_LOCKED); + } + + /* + * Set up the rotation matrix. + */ + matrix[0] = (float)cos(angle * PI / 180.0); + matrix[1] = (float)-sin(angle * PI / 180.0); + matrix[2] = (float)sin(angle * PI / 180.0); + matrix[3] = (float)cos(angle * PI / 180.0); + + /* + * Draw the wires. + */ + cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F; + col = (tile & ACTIVE ? COL_POWERED : COL_WIRE); + for (dir = 1; dir < 0x10; dir <<= 1) { + if (tile & dir) { + ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); + ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); + MATMUL(tx, ty, matrix, ex, ey); + draw_filled_line(dr, bx+(int)cx, by+(int)cy, + bx+(int)(cx+tx), by+(int)(cy+ty), + COL_WIRE); + } + } + for (dir = 1; dir < 0x10; dir <<= 1) { + if (tile & dir) { + ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); + ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); + MATMUL(tx, ty, matrix, ex, ey); + draw_line(dr, bx+(int)cx, by+(int)cy, + bx+(int)(cx+tx), by+(int)(cy+ty), + (tile & LOOP(dir)) ? COL_LOOP : col); + } + } + /* If we've drawn any loop-highlighted arms, make sure the centre + * point is loop-coloured rather than a later arm overwriting it. */ + if (tile & (RLOOP | ULOOP | LLOOP | DLOOP)) + draw_rect(dr, bx+(int)cx, by+(int)cy, 1, 1, COL_LOOP); + + /* + * Draw the box in the middle. We do this in blue if the tile + * is an unpowered endpoint, in cyan if the tile is a powered + * endpoint, in black if the tile is the centrepiece, and + * otherwise not at all. + */ + col = -1; + if (src) + col = COL_WIRE; + else if (COUNT(tile) == 1) { + col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); + } + if (col >= 0) { + int i, points[8]; + + points[0] = +1; points[1] = +1; + points[2] = +1; points[3] = -1; + points[4] = -1; points[5] = -1; + points[6] = -1; points[7] = +1; + + for (i = 0; i < 8; i += 2) { + ex = (TILE_SIZE * 0.24F) * points[i]; + ey = (TILE_SIZE * 0.24F) * points[i+1]; + MATMUL(tx, ty, matrix, ex, ey); + points[i] = bx+(int)(cx+tx); + points[i+1] = by+(int)(cy+ty); + } + + draw_polygon(dr, points, 4, col, COL_WIRE); + } + + /* + * Draw the points on the border if other tiles are connected + * to us. + */ + for (dir = 1; dir < 0x10; dir <<= 1) { + int dx, dy, px, py, lx, ly, vx, vy, ox, oy; + + dx = X(dir); + dy = Y(dir); + + ox = x + dx; + oy = y + dy; + + if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height) + continue; + + if (!(tile(state, GX(ox), GY(oy)) & F(dir))) + continue; + + px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx); + py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy); + lx = dx * (TILE_BORDER-1); + ly = dy * (TILE_BORDER-1); + vx = (dy ? 1 : 0); + vy = (dx ? 1 : 0); + + if (angle == 0.0 && (tile & dir)) { + /* + * If we are fully connected to the other tile, we must + * draw right across the tile border. (We can use our + * own ACTIVE state to determine what colour to do this + * in: if we are fully connected to the other tile then + * the two ACTIVE states will be the same.) + */ + draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE); + draw_rect_coords(dr, px, py, px+lx, py+ly, + ((tile & LOOP(dir)) ? COL_LOOP : + (tile & ACTIVE) ? COL_POWERED : + COL_WIRE)); + } else { + /* + * The other tile extends into our border, but isn't + * actually connected to us. Just draw a single black + * dot. + */ + draw_rect_coords(dr, px, py, px, py, COL_WIRE); + } + } + + /* + * Draw barrier corners, and then barriers. + */ + for (phase = 0; phase < 2; phase++) { + for (dir = 1; dir < 0x10; dir <<= 1) { + int x1, y1, corner = FALSE; + /* + * If at least one barrier terminates at the corner + * between dir and A(dir), draw a barrier corner. + */ + if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) { + corner = TRUE; + } else { + /* + * Only count barriers terminating at this corner + * if they're physically next to the corner. (That + * is, if they've wrapped round from the far side + * of the screen, they don't count.) + */ + x1 = x + X(dir); + y1 = y + Y(dir); + if (x1 >= 0 && x1 < state->width && + y1 >= 0 && y1 < state->height && + (barrier(state, GX(x1), GY(y1)) & A(dir))) { + corner = TRUE; + } else { + x1 = x + X(A(dir)); + y1 = y + Y(A(dir)); + if (x1 >= 0 && x1 < state->width && + y1 >= 0 && y1 < state->height && + (barrier(state, GX(x1), GY(y1)) & dir)) + corner = TRUE; + } + } + + if (corner) { + /* + * At least one barrier terminates here. Draw a + * corner. + */ + draw_barrier_corner(dr, ds, x, y, + X(dir)+X(A(dir)), Y(dir)+Y(A(dir)), + phase); + } + } + + for (dir = 1; dir < 0x10; dir <<= 1) + if (barrier(state, GX(x), GY(y)) & dir) + draw_barrier(dr, ds, x, y, dir, phase); + } + + unclip(dr); + + draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float t, float ft) +{ + int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE; + unsigned char *active; + int *loops; + float angle = 0.0; + + /* + * Clear the screen, and draw the exterior barrier lines, if + * this is our first call or if the origin has changed. + */ + if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) { + int phase; + + ds->started = TRUE; + + draw_rect(dr, 0, 0, + WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, + WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, + COL_BACKGROUND); + + ds->org_x = ui->org_x; + ds->org_y = ui->org_y; + moved_origin = TRUE; + + draw_update(dr, 0, 0, + WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER, + WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER); + + for (phase = 0; phase < 2; phase++) { + + for (x = 0; x < ds->width; x++) { + if (x+1 < ds->width) { + if (barrier(state, GX(x), GY(0)) & R) + draw_barrier_corner(dr, ds, x, -1, +1, +1, phase); + if (barrier(state, GX(x), GY(ds->height-1)) & R) + draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase); + } + if (barrier(state, GX(x), GY(0)) & U) { + draw_barrier_corner(dr, ds, x, -1, -1, +1, phase); + draw_barrier_corner(dr, ds, x, -1, +1, +1, phase); + draw_barrier(dr, ds, x, -1, D, phase); + } + if (barrier(state, GX(x), GY(ds->height-1)) & D) { + draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase); + draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase); + draw_barrier(dr, ds, x, ds->height, U, phase); + } + } + + for (y = 0; y < ds->height; y++) { + if (y+1 < ds->height) { + if (barrier(state, GX(0), GY(y)) & D) + draw_barrier_corner(dr, ds, -1, y, +1, +1, phase); + if (barrier(state, GX(ds->width-1), GY(y)) & D) + draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase); + } + if (barrier(state, GX(0), GY(y)) & L) { + draw_barrier_corner(dr, ds, -1, y, +1, -1, phase); + draw_barrier_corner(dr, ds, -1, y, +1, +1, phase); + draw_barrier(dr, ds, -1, y, R, phase); + } + if (barrier(state, GX(ds->width-1), GY(y)) & R) { + draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase); + draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase); + draw_barrier(dr, ds, ds->width, y, L, phase); + } + } + } + } + + tx = ty = -1; + last_rotate_dir = dir==-1 ? oldstate->last_rotate_dir : + state->last_rotate_dir; + if (oldstate && (t < ROTATE_TIME) && last_rotate_dir) { + /* + * We're animating a single tile rotation. Find the turning + * tile. + */ + tx = (dir==-1 ? oldstate->last_rotate_x : state->last_rotate_x); + ty = (dir==-1 ? oldstate->last_rotate_y : state->last_rotate_y); + angle = last_rotate_dir * dir * 90.0F * (t / ROTATE_TIME); + state = oldstate; + } + + frame = -1; + if (ft > 0) { + /* + * We're animating a completion flash. Find which frame + * we're at. + */ + frame = (int)(ft / FLASH_FRAME); + } + + /* + * Draw any tile which differs from the way it was last drawn. + */ + active = compute_active(state, ui->cx, ui->cy); + loops = compute_loops(state); + + for (x = 0; x < ds->width; x++) + for (y = 0; y < ds->height; y++) { + int c = tile(state, GX(x), GY(y)) | + index(state, active, GX(x), GY(y)) | + index(state, loops, GX(x), GY(y)); + int is_src = GX(x) == ui->cx && GY(y) == ui->cy; + int is_anim = GX(x) == tx && GY(y) == ty; + int is_cursor = ui->cur_visible && + GX(x) == ui->cur_x && GY(y) == ui->cur_y; + + /* + * In a completion flash, we adjust the LOCKED bit + * depending on our distance from the centre point and + * the frame number. + */ + if (frame >= 0) { + int rcx = RX(ui->cx), rcy = RY(ui->cy); + int xdist, ydist, dist; + xdist = (x < rcx ? rcx - x : x - rcx); + ydist = (y < rcy ? rcy - y : y - rcy); + dist = (xdist > ydist ? xdist : ydist); + + if (frame >= dist && frame < dist+4) { + int lock = (frame - dist) & 1; + lock = lock ? LOCKED : 0; + c = (c &~ LOCKED) | lock; + } + } + + if (moved_origin || + index(state, ds->visible, x, y) != c || + index(state, ds->visible, x, y) == -1 || + is_src || is_anim || is_cursor) { + draw_tile(dr, state, ds, x, y, c, + is_src, (is_anim ? angle : 0.0F), is_cursor); + if (is_src || is_anim || is_cursor) + index(state, ds->visible, x, y) = -1; + else + index(state, ds->visible, x, y) = c; + } + } + + /* + * Update the status bar. + */ + { + char statusbuf[256]; + int i, n, n2, a; + + n = state->width * state->height; + for (i = a = n2 = 0; i < n; i++) { + if (active[i]) + a++; + if (state->tiles[i] & 0xF) + n2++; + } + + sprintf(statusbuf, "%sActive: %d/%d", + (state->used_solve ? "Auto-solved. " : + state->completed ? "COMPLETED! " : ""), a, n2); + + status_bar(dr, statusbuf); + } + + sfree(active); + sfree(loops); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + int last_rotate_dir; + + /* + * Don't animate if last_rotate_dir is zero. + */ + last_rotate_dir = dir==-1 ? oldstate->last_rotate_dir : + newstate->last_rotate_dir; + if (last_rotate_dir) + return ROTATE_TIME; + + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + /* + * If the game has just been completed, we display a completion + * flash. + */ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) { + int size = 0; + if (size < newstate->width) + size = newstate->width; + if (size < newstate->height) + size = newstate->height; + return FLASH_FRAME * (size+4); + } + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 8mm squares by default. + */ + game_compute_size(params, 800, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y, + int topleft, int v, int drawlines, int ink) +{ + int tx, ty, cx, cy, r, br, k, thick; + + tx = WINDOW_OFFSET + TILE_SIZE * x; + ty = WINDOW_OFFSET + TILE_SIZE * y; + + /* + * Find our centre point. + */ + if (topleft) { + cx = tx + (v & L ? TILE_SIZE / 4 : TILE_SIZE / 6); + cy = ty + (v & U ? TILE_SIZE / 4 : TILE_SIZE / 6); + r = TILE_SIZE / 8; + br = TILE_SIZE / 32; + } else { + cx = tx + TILE_SIZE / 2; + cy = ty + TILE_SIZE / 2; + r = TILE_SIZE / 2; + br = TILE_SIZE / 8; + } + thick = r / 20; + + /* + * Draw the square block if we have an endpoint. + */ + if (v == 1 || v == 2 || v == 4 || v == 8) + draw_rect(dr, cx - br, cy - br, br*2, br*2, ink); + + /* + * Draw each radial line. + */ + if (drawlines) { + for (k = 1; k < 16; k *= 2) + if (v & k) { + int x1 = min(cx, cx + (r-thick) * X(k)); + int x2 = max(cx, cx + (r-thick) * X(k)); + int y1 = min(cy, cy + (r-thick) * Y(k)); + int y2 = max(cy, cy + (r-thick) * Y(k)); + draw_rect(dr, x1 - thick, y1 - thick, + (x2 - x1) + 2*thick, (y2 - y1) + 2*thick, ink); + } + } +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->width, h = state->height; + int ink = print_mono_colour(dr, 0); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILE_SIZE / (state->wrapping ? 128 : 12)); + draw_rect_outline(dr, WINDOW_OFFSET, WINDOW_OFFSET, + TILE_SIZE * w, TILE_SIZE * h, ink); + + /* + * Grid. + */ + print_line_width(dr, TILE_SIZE / 128); + for (x = 1; x < w; x++) + draw_line(dr, WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET, + WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET + TILE_SIZE * h, + ink); + for (y = 1; y < h; y++) + draw_line(dr, WINDOW_OFFSET, WINDOW_OFFSET + TILE_SIZE * y, + WINDOW_OFFSET + TILE_SIZE * w, WINDOW_OFFSET + TILE_SIZE * y, + ink); + + /* + * Barriers. + */ + for (y = 0; y <= h; y++) + for (x = 0; x <= w; x++) { + int b = barrier(state, x % w, y % h); + if (x < w && (b & U)) + draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24, + WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24, + TILE_SIZE + TILE_SIZE/24 * 2, TILE_SIZE/24 * 2, ink); + if (y < h && (b & L)) + draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24, + WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24, + TILE_SIZE/24 * 2, TILE_SIZE + TILE_SIZE/24 * 2, ink); + } + + /* + * Grid contents. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int vx, v = tile(state, x, y); + int locked = v & LOCKED; + + v &= 0xF; + + /* + * Rotate into a standard orientation for the top left + * corner diagram. + */ + vx = v; + while (vx != 0 && vx != 15 && vx != 1 && vx != 9 && vx != 13 && + vx != 5) + vx = A(vx); + + /* + * Draw the top left corner diagram. + */ + draw_diagram(dr, ds, x, y, TRUE, vx, TRUE, ink); + + /* + * Draw the real solution diagram, if we're doing so. + */ + draw_diagram(dr, ds, x, y, FALSE, v, locked, ink); + } +} + +#ifdef COMBINED +#define thegame net +#endif + +const struct game thegame = { + "Net", "games.net", "net", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/netslide.R b/apps/plugins/puzzles/netslide.R new file mode 100644 index 0000000000..ecfe7c3df8 --- /dev/null +++ b/apps/plugins/puzzles/netslide.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +NETSLIDE_EXTRA = tree234 + +netslide : [X] GTK COMMON netslide NETSLIDE_EXTRA netslide-icon|no-icon + +netslide : [G] WINDOWS COMMON netslide NETSLIDE_EXTRA netslide.res|noicon.res + +ALL += netslide[COMBINED] NETSLIDE_EXTRA + +!begin am gtk +GAMES += netslide +!end + +!begin >list.c + A(netslide) \ +!end + +!begin >gamedesc.txt +netslide:netslide.exe:Netslide:Toroidal sliding network puzzle:Slide a row at a time to reassemble the network. +!end diff --git a/apps/plugins/puzzles/netslide.c b/apps/plugins/puzzles/netslide.c new file mode 100644 index 0000000000..e299b96eb3 --- /dev/null +++ b/apps/plugins/puzzles/netslide.c @@ -0,0 +1,1893 @@ +/* + * netslide.c: cross between Net and Sixteen, courtesy of Richard + * Boulton. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +#define MATMUL(xr,yr,m,x,y) do { \ + float rx, ry, xx = (x), yy = (y), *mat = (m); \ + rx = mat[0] * xx + mat[2] * yy; \ + ry = mat[1] * xx + mat[3] * yy; \ + (xr) = rx; (yr) = ry; \ +} while (0) + +/* Direction and other bitfields */ +#define R 0x01 +#define U 0x02 +#define L 0x04 +#define D 0x08 +#define FLASHING 0x10 +#define ACTIVE 0x20 +/* Corner flags go in the barriers array */ +#define RU 0x10 +#define UL 0x20 +#define LD 0x40 +#define DR 0x80 + +/* Get tile at given coordinate */ +#define T(state, x, y) ( (y) * (state)->width + (x) ) + +/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */ +#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) ) +#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) ) +#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) ) +#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \ + ((n)&3) == 1 ? A(x) : \ + ((n)&3) == 2 ? F(x) : C(x) ) + +/* X and Y displacements */ +#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 ) +#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 ) + +/* Bit count */ +#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \ + (((x) & 0x02) >> 1) + ((x) & 0x01) ) + +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) +#define BORDER TILE_SIZE +#define TILE_BORDER 1 +#define WINDOW_OFFSET 0 + +#define ANIM_TIME 0.13F +#define FLASH_FRAME 0.07F + +enum { + COL_BACKGROUND, + COL_FLASHING, + COL_BORDER, + COL_WIRE, + COL_ENDPOINT, + COL_POWERED, + COL_BARRIER, + COL_LOWLIGHT, + COL_TEXT, + NCOLOURS +}; + +struct game_params { + int width; + int height; + int wrapping; + float barrier_probability; + int movetarget; +}; + +struct game_state { + int width, height, cx, cy, wrapping, completed; + int used_solve; + int move_count, movetarget; + + /* position (row or col number, starting at 0) of last move. */ + int last_move_row, last_move_col; + + /* direction of last move: +1 or -1 */ + int last_move_dir; + + unsigned char *tiles; + unsigned char *barriers; +}; + +#define OFFSET(x2,y2,x1,y1,dir,state) \ + ( (x2) = ((x1) + (state)->width + X((dir))) % (state)->width, \ + (y2) = ((y1) + (state)->height + Y((dir))) % (state)->height) + +#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] ) +#define tile(state, x, y) index(state, (state)->tiles, x, y) +#define barrier(state, x, y) index(state, (state)->barriers, x, y) + +struct xyd { + int x, y, direction; +}; + +static int xyd_cmp(void *av, void *bv) { + struct xyd *a = (struct xyd *)av; + struct xyd *b = (struct xyd *)bv; + if (a->x < b->x) + return -1; + if (a->x > b->x) + return +1; + if (a->y < b->y) + return -1; + if (a->y > b->y) + return +1; + if (a->direction < b->direction) + return -1; + if (a->direction > b->direction) + return +1; + return 0; +} + +static struct xyd *new_xyd(int x, int y, int direction) +{ + struct xyd *xyd = snew(struct xyd); + xyd->x = x; + xyd->y = y; + xyd->direction = direction; + return xyd; +} + +static void slide_col(game_state *state, int dir, int col); +static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col); +static void slide_row(game_state *state, int dir, int row); +static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row); + +/* ---------------------------------------------------------------------- + * Manage game parameters. + */ +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->width = 3; + ret->height = 3; + ret->wrapping = FALSE; + ret->barrier_probability = 1.0; + ret->movetarget = 0; + + return ret; +} + +static const struct { int x, y, wrap, bprob; const char* desc; } +netslide_presets[] = { + {3, 3, FALSE, 1, " easy"}, + {3, 3, FALSE, 0, " medium"}, + {3, 3, TRUE, 0, " hard"}, + {4, 4, FALSE, 1, " easy"}, + {4, 4, FALSE, 0, " medium"}, + {4, 4, TRUE, 0, " hard"}, + {5, 5, FALSE, 1, " easy"}, + {5, 5, FALSE, 0, " medium"}, + {5, 5, TRUE, 0, " hard"}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(netslide_presets)) + return FALSE; + + ret = snew(game_params); + ret->width = netslide_presets[i].x; + ret->height = netslide_presets[i].y; + ret->wrapping = netslide_presets[i].wrap; + ret->barrier_probability = (float)netslide_presets[i].bprob; + ret->movetarget = 0; + + sprintf(str, "%dx%d%s", ret->width, ret->height, netslide_presets[i].desc); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + char const *p = string; + + ret->wrapping = FALSE; + ret->barrier_probability = 0.0; + ret->movetarget = 0; + + ret->width = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + ret->height = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if ( (ret->wrapping = (*p == 'w')) != 0 ) + p++; + if (*p == 'b') { + ret->barrier_probability = (float)atof(++p); + while (*p && (isdigit((unsigned char)*p) || *p == '.')) p++; + } + if (*p == 'm') { + ret->movetarget = atoi(++p); + } + } else { + ret->height = ret->width; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[400]; + int len; + + len = sprintf(ret, "%dx%d", params->width, params->height); + if (params->wrapping) + ret[len++] = 'w'; + if (full && params->barrier_probability) + len += sprintf(ret+len, "b%g", params->barrier_probability); + /* Shuffle limit is part of the limited parameters, because we have to + * provide the target move count. */ + if (params->movetarget) + len += sprintf(ret+len, "m%d", params->movetarget); + assert(len < lenof(ret)); + ret[len] = '\0'; + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(6, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->width); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->height); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Walls wrap around"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->wrapping; + + ret[3].name = "Barrier probability"; + ret[3].type = C_STRING; + sprintf(buf, "%g", params->barrier_probability); + ret[3].sval = dupstr(buf); + ret[3].ival = 0; + + ret[4].name = "Number of shuffling moves"; + ret[4].type = C_STRING; + sprintf(buf, "%d", params->movetarget); + ret[4].sval = dupstr(buf); + ret[4].ival = 0; + + ret[5].name = NULL; + ret[5].type = C_END; + ret[5].sval = NULL; + ret[5].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->width = atoi(cfg[0].sval); + ret->height = atoi(cfg[1].sval); + ret->wrapping = cfg[2].ival; + ret->barrier_probability = (float)atof(cfg[3].sval); + ret->movetarget = atoi(cfg[4].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->width <= 1 || params->height <= 1) + return "Width and height must both be greater than one"; + if (params->barrier_probability < 0) + return "Barrier probability may not be negative"; + if (params->barrier_probability > 1) + return "Barrier probability may not be greater than 1"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Randomly select a new game description. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + tree234 *possibilities, *barriertree; + int w, h, x, y, cx, cy, nbarriers; + unsigned char *tiles, *barriers; + char *desc, *p; + + w = params->width; + h = params->height; + + tiles = snewn(w * h, unsigned char); + memset(tiles, 0, w * h); + barriers = snewn(w * h, unsigned char); + memset(barriers, 0, w * h); + + cx = w / 2; + cy = h / 2; + + /* + * Construct the unshuffled grid. + * + * To do this, we simply start at the centre point, repeatedly + * choose a random possibility out of the available ways to + * extend a used square into an unused one, and do it. After + * extending the third line out of a square, we remove the + * fourth from the possibilities list to avoid any full-cross + * squares (which would make the game too easy because they + * only have one orientation). + * + * The slightly worrying thing is the avoidance of full-cross + * squares. Can this cause our unsophisticated construction + * algorithm to paint itself into a corner, by getting into a + * situation where there are some unreached squares and the + * only way to reach any of them is to extend a T-piece into a + * full cross? + * + * Answer: no it can't, and here's a proof. + * + * Any contiguous group of such unreachable squares must be + * surrounded on _all_ sides by T-pieces pointing away from the + * group. (If not, then there is a square which can be extended + * into one of the `unreachable' ones, and so it wasn't + * unreachable after all.) In particular, this implies that + * each contiguous group of unreachable squares must be + * rectangular in shape (any deviation from that yields a + * non-T-piece next to an `unreachable' square). + * + * So we have a rectangle of unreachable squares, with T-pieces + * forming a solid border around the rectangle. The corners of + * that border must be connected (since every tile connects all + * the lines arriving in it), and therefore the border must + * form a closed loop around the rectangle. + * + * But this can't have happened in the first place, since we + * _know_ we've avoided creating closed loops! Hence, no such + * situation can ever arise, and the naive grid construction + * algorithm will guaranteeably result in a complete grid + * containing no unreached squares, no full crosses _and_ no + * closed loops. [] + */ + possibilities = newtree234(xyd_cmp); + + if (cx+1 < w) + add234(possibilities, new_xyd(cx, cy, R)); + if (cy-1 >= 0) + add234(possibilities, new_xyd(cx, cy, U)); + if (cx-1 >= 0) + add234(possibilities, new_xyd(cx, cy, L)); + if (cy+1 < h) + add234(possibilities, new_xyd(cx, cy, D)); + + while (count234(possibilities) > 0) { + int i; + struct xyd *xyd; + int x1, y1, d1, x2, y2, d2, d; + + /* + * Extract a randomly chosen possibility from the list. + */ + i = random_upto(rs, count234(possibilities)); + xyd = delpos234(possibilities, i); + x1 = xyd->x; + y1 = xyd->y; + d1 = xyd->direction; + sfree(xyd); + + OFFSET(x2, y2, x1, y1, d1, params); + d2 = F(d1); +#ifdef GENERATION_DIAGNOSTICS + printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n", + x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]); +#endif + + /* + * Make the connection. (We should be moving to an as yet + * unused tile.) + */ + index(params, tiles, x1, y1) |= d1; + assert(index(params, tiles, x2, y2) == 0); + index(params, tiles, x2, y2) |= d2; + + /* + * If we have created a T-piece, remove its last + * possibility. + */ + if (COUNT(index(params, tiles, x1, y1)) == 3) { + struct xyd xyd1, *xydp; + + xyd1.x = x1; + xyd1.y = y1; + xyd1.direction = 0x0F ^ index(params, tiles, x1, y1); + + xydp = find234(possibilities, &xyd1, NULL); + + if (xydp) { +#ifdef GENERATION_DIAGNOSTICS + printf("T-piece; removing (%d,%d,%c)\n", + xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); +#endif + del234(possibilities, xydp); + sfree(xydp); + } + } + + /* + * Remove all other possibilities that were pointing at the + * tile we've just moved into. + */ + for (d = 1; d < 0x10; d <<= 1) { + int x3, y3, d3; + struct xyd xyd1, *xydp; + + OFFSET(x3, y3, x2, y2, d, params); + d3 = F(d); + + xyd1.x = x3; + xyd1.y = y3; + xyd1.direction = d3; + + xydp = find234(possibilities, &xyd1, NULL); + + if (xydp) { +#ifdef GENERATION_DIAGNOSTICS + printf("Loop avoidance; removing (%d,%d,%c)\n", + xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); +#endif + del234(possibilities, xydp); + sfree(xydp); + } + } + + /* + * Add new possibilities to the list for moving _out_ of + * the tile we have just moved into. + */ + for (d = 1; d < 0x10; d <<= 1) { + int x3, y3; + + if (d == d2) + continue; /* we've got this one already */ + + if (!params->wrapping) { + if (d == U && y2 == 0) + continue; + if (d == D && y2 == h-1) + continue; + if (d == L && x2 == 0) + continue; + if (d == R && x2 == w-1) + continue; + } + + OFFSET(x3, y3, x2, y2, d, params); + + if (index(params, tiles, x3, y3)) + continue; /* this would create a loop */ + +#ifdef GENERATION_DIAGNOSTICS + printf("New frontier; adding (%d,%d,%c)\n", + x2, y2, "0RU3L567D9abcdef"[d]); +#endif + add234(possibilities, new_xyd(x2, y2, d)); + } + } + /* Having done that, we should have no possibilities remaining. */ + assert(count234(possibilities) == 0); + freetree234(possibilities); + + /* + * Now compute a list of the possible barrier locations. + */ + barriertree = newtree234(xyd_cmp); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + + if (!(index(params, tiles, x, y) & R) && + (params->wrapping || x < w-1)) + add234(barriertree, new_xyd(x, y, R)); + if (!(index(params, tiles, x, y) & D) && + (params->wrapping || y < h-1)) + add234(barriertree, new_xyd(x, y, D)); + } + } + + /* + * Save the unshuffled grid in aux. + */ + { + char *solution; + int i; + + /* + * String format is exactly the same as a solve move, so we + * can just dupstr this in solve_game(). + */ + + solution = snewn(w * h + 2, char); + solution[0] = 'S'; + for (i = 0; i < w * h; i++) + solution[i+1] = "0123456789abcdef"[tiles[i] & 0xF]; + solution[w*h+1] = '\0'; + + *aux = solution; + } + + /* + * Now shuffle the grid. + * FIXME - this simply does a set of random moves to shuffle the pieces, + * although we make a token effort to avoid boring cases by avoiding moves + * that directly undo the previous one, or that repeat so often as to + * turn into fewer moves. + * + * A better way would be to number all the pieces, generate a placement + * for all the numbers as for "sixteen", observing parity constraints if + * neccessary, and then place the pieces according to their numbering. + * BUT - I'm not sure if this will work, since we disallow movement of + * the middle row and column. + */ + { + int i; + int cols = w - 1; + int rows = h - 1; + int moves = params->movetarget; + int prevdir = -1, prevrowcol = -1, nrepeats = 0; + if (!moves) moves = cols * rows * 2; + for (i = 0; i < moves; /* incremented conditionally */) { + /* Choose a direction: 0,1,2,3 = up, right, down, left. */ + int dir = random_upto(rs, 4); + int rowcol; + if (dir % 2 == 0) { + int col = random_upto(rs, cols); + if (col >= cx) col += 1; /* avoid centre */ + if (col == prevrowcol) { + if (dir == 2-prevdir) + continue; /* undoes last move */ + else if (dir == prevdir && (nrepeats+1)*2 > h) + continue; /* makes fewer moves */ + } + slide_col_int(w, h, tiles, 1 - dir, col); + rowcol = col; + } else { + int row = random_upto(rs, rows); + if (row >= cy) row += 1; /* avoid centre */ + if (row == prevrowcol) { + if (dir == 4-prevdir) + continue; /* undoes last move */ + else if (dir == prevdir && (nrepeats+1)*2 > w) + continue; /* makes fewer moves */ + } + slide_row_int(w, h, tiles, 2 - dir, row); + rowcol = row; + } + if (dir == prevdir && rowcol == prevrowcol) + nrepeats++; + else + nrepeats = 1; + prevdir = dir; + prevrowcol = rowcol; + i++; /* if we got here, the move was accepted */ + } + } + + /* + * And now choose barrier locations. (We carefully do this + * _after_ shuffling, so that changing the barrier rate in the + * params while keeping the random seed the same will give the + * same shuffled grid and _only_ change the barrier locations. + * Also the way we choose barrier locations, by repeatedly + * choosing one possibility from the list until we have enough, + * is designed to ensure that raising the barrier rate while + * keeping the seed the same will provide a superset of the + * previous barrier set - i.e. if you ask for 10 barriers, and + * then decide that's still too hard and ask for 20, you'll get + * the original 10 plus 10 more, rather than getting 20 new + * ones and the chance of remembering your first 10.) + */ + nbarriers = (int)(params->barrier_probability * count234(barriertree)); + assert(nbarriers >= 0 && nbarriers <= count234(barriertree)); + + while (nbarriers > 0) { + int i; + struct xyd *xyd; + int x1, y1, d1, x2, y2, d2; + + /* + * Extract a randomly chosen barrier from the list. + */ + i = random_upto(rs, count234(barriertree)); + xyd = delpos234(barriertree, i); + + assert(xyd != NULL); + + x1 = xyd->x; + y1 = xyd->y; + d1 = xyd->direction; + sfree(xyd); + + OFFSET(x2, y2, x1, y1, d1, params); + d2 = F(d1); + + index(params, barriers, x1, y1) |= d1; + index(params, barriers, x2, y2) |= d2; + + nbarriers--; + } + + /* + * Clean up the rest of the barrier list. + */ + { + struct xyd *xyd; + + while ( (xyd = delpos234(barriertree, 0)) != NULL) + sfree(xyd); + + freetree234(barriertree); + } + + /* + * Finally, encode the grid into a string game description. + * + * My syntax is extremely simple: each square is encoded as a + * hex digit in which bit 0 means a connection on the right, + * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same + * encoding as used internally). Each digit is followed by + * optional barrier indicators: `v' means a vertical barrier to + * the right of it, and `h' means a horizontal barrier below + * it. + */ + desc = snewn(w * h * 3 + 1, char); + p = desc; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + *p++ = "0123456789abcdef"[index(params, tiles, x, y)]; + if ((params->wrapping || x < w-1) && + (index(params, barriers, x, y) & R)) + *p++ = 'v'; + if ((params->wrapping || y < h-1) && + (index(params, barriers, x, y) & D)) + *p++ = 'h'; + } + } + assert(p - desc <= w*h*3); + *p = '\0'; + + sfree(tiles); + sfree(barriers); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->width, h = params->height; + int i; + + for (i = 0; i < w*h; i++) { + if (*desc >= '0' && *desc <= '9') + /* OK */; + else if (*desc >= 'a' && *desc <= 'f') + /* OK */; + else if (*desc >= 'A' && *desc <= 'F') + /* OK */; + else if (!*desc) + return "Game description shorter than expected"; + else + return "Game description contained unexpected character"; + desc++; + while (*desc == 'h' || *desc == 'v') + desc++; + } + if (*desc) + return "Game description longer than expected"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Construct an initial game state, given a description and parameters. + */ + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state; + int w, h, x, y; + + assert(params->width > 0 && params->height > 0); + assert(params->width > 1 || params->height > 1); + + /* + * Create a blank game state. + */ + state = snew(game_state); + w = state->width = params->width; + h = state->height = params->height; + state->cx = state->width / 2; + state->cy = state->height / 2; + state->wrapping = params->wrapping; + state->movetarget = params->movetarget; + state->completed = 0; + state->used_solve = FALSE; + state->move_count = 0; + state->last_move_row = -1; + state->last_move_col = -1; + state->last_move_dir = 0; + state->tiles = snewn(state->width * state->height, unsigned char); + memset(state->tiles, 0, state->width * state->height); + state->barriers = snewn(state->width * state->height, unsigned char); + memset(state->barriers, 0, state->width * state->height); + + + /* + * Parse the game description into the grid. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (*desc >= '0' && *desc <= '9') + tile(state, x, y) = *desc - '0'; + else if (*desc >= 'a' && *desc <= 'f') + tile(state, x, y) = *desc - 'a' + 10; + else if (*desc >= 'A' && *desc <= 'F') + tile(state, x, y) = *desc - 'A' + 10; + if (*desc) + desc++; + while (*desc == 'h' || *desc == 'v') { + int x2, y2, d1, d2; + if (*desc == 'v') + d1 = R; + else + d1 = D; + + OFFSET(x2, y2, x, y, d1, state); + d2 = F(d1); + + barrier(state, x, y) |= d1; + barrier(state, x2, y2) |= d2; + + desc++; + } + } + } + + /* + * Set up border barriers if this is a non-wrapping game. + */ + if (!state->wrapping) { + for (x = 0; x < state->width; x++) { + barrier(state, x, 0) |= U; + barrier(state, x, state->height-1) |= D; + } + for (y = 0; y < state->height; y++) { + barrier(state, 0, y) |= L; + barrier(state, state->width-1, y) |= R; + } + } + + /* + * Set up the barrier corner flags, for drawing barriers + * prettily when they meet. + */ + for (y = 0; y < state->height; y++) { + for (x = 0; x < state->width; x++) { + int dir; + + for (dir = 1; dir < 0x10; dir <<= 1) { + int dir2 = A(dir); + int x1, y1, x2, y2, x3, y3; + int corner = FALSE; + + if (!(barrier(state, x, y) & dir)) + continue; + + if (barrier(state, x, y) & dir2) + corner = TRUE; + + x1 = x + X(dir), y1 = y + Y(dir); + if (x1 >= 0 && x1 < state->width && + y1 >= 0 && y1 < state->height && + (barrier(state, x1, y1) & dir2)) + corner = TRUE; + + x2 = x + X(dir2), y2 = y + Y(dir2); + if (x2 >= 0 && x2 < state->width && + y2 >= 0 && y2 < state->height && + (barrier(state, x2, y2) & dir)) + corner = TRUE; + + if (corner) { + barrier(state, x, y) |= (dir << 4); + if (x1 >= 0 && x1 < state->width && + y1 >= 0 && y1 < state->height) + barrier(state, x1, y1) |= (A(dir) << 4); + if (x2 >= 0 && x2 < state->width && + y2 >= 0 && y2 < state->height) + barrier(state, x2, y2) |= (C(dir) << 4); + x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2); + if (x3 >= 0 && x3 < state->width && + y3 >= 0 && y3 < state->height) + barrier(state, x3, y3) |= (F(dir) << 4); + } + } + } + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret; + + ret = snew(game_state); + ret->width = state->width; + ret->height = state->height; + ret->cx = state->cx; + ret->cy = state->cy; + ret->wrapping = state->wrapping; + ret->movetarget = state->movetarget; + ret->completed = state->completed; + ret->used_solve = state->used_solve; + ret->move_count = state->move_count; + ret->last_move_row = state->last_move_row; + ret->last_move_col = state->last_move_col; + ret->last_move_dir = state->last_move_dir; + ret->tiles = snewn(state->width * state->height, unsigned char); + memcpy(ret->tiles, state->tiles, state->width * state->height); + ret->barriers = snewn(state->width * state->height, unsigned char); + memcpy(ret->barriers, state->barriers, state->width * state->height); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->tiles); + sfree(state->barriers); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + if (!aux) { + *error = "Solution not known for this puzzle"; + return NULL; + } + + return dupstr(aux); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +/* ---------------------------------------------------------------------- + * Utility routine. + */ + +/* + * Compute which squares are reachable from the centre square, as a + * quick visual aid to determining how close the game is to + * completion. This is also a simple way to tell if the game _is_ + * completed - just call this function and see whether every square + * is marked active. + * + * squares in the moving_row and moving_col are always inactive - this + * is so that "current" doesn't appear to jump across moving lines. + */ +static unsigned char *compute_active(const game_state *state, + int moving_row, int moving_col) +{ + unsigned char *active; + tree234 *todo; + struct xyd *xyd; + + active = snewn(state->width * state->height, unsigned char); + memset(active, 0, state->width * state->height); + + /* + * We only store (x,y) pairs in todo, but it's easier to reuse + * xyd_cmp and just store direction 0 every time. + */ + todo = newtree234(xyd_cmp); + index(state, active, state->cx, state->cy) = ACTIVE; + add234(todo, new_xyd(state->cx, state->cy, 0)); + + while ( (xyd = delpos234(todo, 0)) != NULL) { + int x1, y1, d1, x2, y2, d2; + + x1 = xyd->x; + y1 = xyd->y; + sfree(xyd); + + for (d1 = 1; d1 < 0x10; d1 <<= 1) { + OFFSET(x2, y2, x1, y1, d1, state); + d2 = F(d1); + + /* + * If the next tile in this direction is connected to + * us, and there isn't a barrier in the way, and it + * isn't already marked active, then mark it active and + * add it to the to-examine list. + */ + if ((x2 != moving_col && y2 != moving_row) && + (tile(state, x1, y1) & d1) && + (tile(state, x2, y2) & d2) && + !(barrier(state, x1, y1) & d1) && + !index(state, active, x2, y2)) { + index(state, active, x2, y2) = ACTIVE; + add234(todo, new_xyd(x2, y2, 0)); + } + } + } + /* Now we expect the todo list to have shrunk to zero size. */ + assert(count234(todo) == 0); + freetree234(todo); + + return active; +} + +struct game_ui { + int cur_x, cur_y; + int cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = 0; + ui->cur_y = -1; + ui->cur_visible = FALSE; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +/* ---------------------------------------------------------------------- + * Process a move. + */ + +static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row) +{ + int x = dir > 0 ? -1 : w; + int tx = x + dir; + int n = w - 1; + unsigned char endtile = tiles[row * w + tx]; + do { + x = tx; + tx = (x + dir + w) % w; + tiles[row * w + x] = tiles[row * w + tx]; + } while (--n > 0); + tiles[row * w + tx] = endtile; +} + +static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col) +{ + int y = dir > 0 ? -1 : h; + int ty = y + dir; + int n = h - 1; + unsigned char endtile = tiles[ty * w + col]; + do { + y = ty; + ty = (y + dir + h) % h; + tiles[y * w + col] = tiles[ty * w + col]; + } while (--n > 0); + tiles[ty * w + col] = endtile; +} + +static void slide_row(game_state *state, int dir, int row) +{ + slide_row_int(state->width, state->height, state->tiles, dir, row); +} + +static void slide_col(game_state *state, int dir, int col) +{ + slide_col_int(state->width, state->height, state->tiles, dir, col); +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int width, height; + int tilesize; + unsigned char *visible; + int cur_x, cur_y; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int cx, cy; + int dx, dy; + char buf[80]; + + button &= ~MOD_MASK; + + if (IS_CURSOR_MOVE(button)) { + int cpos, diff = 0; + cpos = c2pos(state->width, state->height, ui->cur_x, ui->cur_y); + diff = c2diff(state->width, state->height, ui->cur_x, ui->cur_y, button); + + if (diff != 0) { + do { /* we might have to do this more than once to skip missing arrows */ + cpos += diff; + pos2c(state->width, state->height, cpos, &ui->cur_x, &ui->cur_y); + } while (ui->cur_x == state->cx || ui->cur_y == state->cy); + } + + ui->cur_visible = 1; + return ""; + } + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + cx = (x - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2; + cy = (y - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2; + ui->cur_visible = 0; + } else if (IS_CURSOR_SELECT(button)) { + if (ui->cur_visible) { + cx = ui->cur_x; + cy = ui->cur_y; + } else { + /* 'click' when cursor is invisible just makes cursor visible. */ + ui->cur_visible = 1; + return ""; + } + } else + return NULL; + + if (cy >= 0 && cy < state->height && cy != state->cy) + { + if (cx == -1) dx = +1; + else if (cx == state->width) dx = -1; + else return NULL; + dy = 0; + } + else if (cx >= 0 && cx < state->width && cx != state->cx) + { + if (cy == -1) dy = +1; + else if (cy == state->height) dy = -1; + else return NULL; + dx = 0; + } + else + return NULL; + + /* reverse direction if right hand button is pressed */ + if (button == RIGHT_BUTTON) + { + dx = -dx; + dy = -dy; + } + + if (dx == 0) + sprintf(buf, "C%d,%d", cx, dy); + else + sprintf(buf, "R%d,%d", cy, dx); + return dupstr(buf); +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + game_state *ret; + int c, d, col; + + if ((move[0] == 'C' || move[0] == 'R') && + sscanf(move+1, "%d,%d", &c, &d) == 2 && + c >= 0 && c < (move[0] == 'C' ? from->width : from->height)) { + col = (move[0] == 'C'); + } else if (move[0] == 'S' && + strlen(move) == from->width * from->height + 1) { + int i; + ret = dup_game(from); + ret->used_solve = TRUE; + ret->completed = ret->move_count = 1; + + for (i = 0; i < from->width * from->height; i++) { + c = move[i+1]; + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else { + free_game(ret); + return NULL; + } + ret->tiles[i] = c; + } + return ret; + } else + return NULL; /* can't parse move string */ + + ret = dup_game(from); + + if (col) + slide_col(ret, d, c); + else + slide_row(ret, d, c); + + ret->move_count++; + ret->last_move_row = col ? -1 : c; + ret->last_move_col = col ? c : -1; + ret->last_move_dir = d; + + /* + * See if the game has been completed. + */ + if (!ret->completed) { + unsigned char *active = compute_active(ret, -1, -1); + int x1, y1; + int complete = TRUE; + + for (x1 = 0; x1 < ret->width; x1++) + for (y1 = 0; y1 < ret->height; y1++) + if (!index(ret, active, x1, y1)) { + complete = FALSE; + goto break_label; /* break out of two loops at once */ + } + break_label: + + sfree(active); + + if (complete) + ret->completed = ret->move_count; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Routines for drawing the game position on the screen. + */ + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + game_drawstate *ds = snew(game_drawstate); + + ds->started = FALSE; + ds->width = state->width; + ds->height = state->height; + ds->visible = snewn(state->width * state->height, unsigned char); + ds->tilesize = 0; /* not decided yet */ + memset(ds->visible, 0xFF, state->width * state->height); + ds->cur_x = ds->cur_y = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER; + *y = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret; + + ret = snewn(NCOLOURS * 3, float); + *ncolours = NCOLOURS; + + /* + * Basic background colour is whatever the front end thinks is + * a sensible default. + */ + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + /* + * Wires are black. + */ + ret[COL_WIRE * 3 + 0] = 0.0F; + ret[COL_WIRE * 3 + 1] = 0.0F; + ret[COL_WIRE * 3 + 2] = 0.0F; + + /* + * Powered wires and powered endpoints are cyan. + */ + ret[COL_POWERED * 3 + 0] = 0.0F; + ret[COL_POWERED * 3 + 1] = 1.0F; + ret[COL_POWERED * 3 + 2] = 1.0F; + + /* + * Barriers are red. + */ + ret[COL_BARRIER * 3 + 0] = 1.0F; + ret[COL_BARRIER * 3 + 1] = 0.0F; + ret[COL_BARRIER * 3 + 2] = 0.0F; + + /* + * Unpowered endpoints are blue. + */ + ret[COL_ENDPOINT * 3 + 0] = 0.0F; + ret[COL_ENDPOINT * 3 + 1] = 0.0F; + ret[COL_ENDPOINT * 3 + 2] = 1.0F; + + /* + * Tile borders are a darker grey than the background. + */ + ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2]; + + /* + * Flashing tiles are a grey in between those two. + */ + ret[COL_FLASHING * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_FLASHING * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_FLASHING * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; + ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.8F; + ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.8F; + ret[COL_TEXT * 3 + 0] = 0.0; + ret[COL_TEXT * 3 + 1] = 0.0; + ret[COL_TEXT * 3 + 2] = 0.0; + + return ret; +} + +static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, + int colour) +{ + draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); + draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); + draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE); + draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE); + draw_line(dr, x1, y1, x2, y2, colour); +} + +static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2, + int colour) +{ + int mx = (x1 < x2 ? x1 : x2); + int my = (y1 < y2 ? y1 : y2); + int dx = (x2 + x1 - 2*mx + 1); + int dy = (y2 + y1 - 2*my + 1); + + draw_rect(dr, mx, my, dx, dy, colour); +} + +static void draw_barrier_corner(drawing *dr, game_drawstate *ds, + int x, int y, int dir, int phase) +{ + int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x; + int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y; + int x1, y1, dx, dy, dir2; + + dir >>= 4; + + dir2 = A(dir); + dx = X(dir) + X(dir2); + dy = Y(dir) + Y(dir2); + x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); + y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0); + + if (phase == 0) { + draw_rect_coords(dr, bx+x1, by+y1, + bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy, + COL_WIRE); + draw_rect_coords(dr, bx+x1, by+y1, + bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy, + COL_WIRE); + } else { + draw_rect_coords(dr, bx+x1, by+y1, + bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy, + COL_BARRIER); + } +} + +static void draw_barrier(drawing *dr, game_drawstate *ds, + int x, int y, int dir, int phase) +{ + int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x; + int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y; + int x1, y1, w, h; + + x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0); + y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0); + w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); + h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); + + if (phase == 0) { + draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE); + } else { + draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER); + } +} + +static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, + int x, int y, int tile, float xshift, float yshift) +{ + int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x + (int)(xshift * TILE_SIZE); + int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y + (int)(yshift * TILE_SIZE); + float cx, cy, ex, ey; + int dir, col; + + /* + * When we draw a single tile, we must draw everything up to + * and including the borders around the tile. This means that + * if the neighbouring tiles have connections to those borders, + * we must draw those connections on the borders themselves. + * + * This would be terribly fiddly if we ever had to draw a tile + * while its neighbour was in mid-rotate, because we'd have to + * arrange to _know_ that the neighbour was being rotated and + * hence had an anomalous effect on the redraw of this tile. + * Fortunately, the drawing algorithm avoids ever calling us in + * this circumstance: we're either drawing lots of straight + * tiles at game start or after a move is complete, or we're + * repeatedly drawing only the rotating tile. So no problem. + */ + + /* + * So. First blank the tile out completely: draw a big + * rectangle in border colour, and a smaller rectangle in + * background colour to fill it in. + */ + draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER, + COL_BORDER); + draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER, + TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER, + tile & FLASHING ? COL_FLASHING : COL_BACKGROUND); + + /* + * Draw the wires. + */ + cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F; + col = (tile & ACTIVE ? COL_POWERED : COL_WIRE); + for (dir = 1; dir < 0x10; dir <<= 1) { + if (tile & dir) { + ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); + ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); + draw_filled_line(dr, bx+(int)cx, by+(int)cy, + bx+(int)(cx+ex), by+(int)(cy+ey), + COL_WIRE); + } + } + for (dir = 1; dir < 0x10; dir <<= 1) { + if (tile & dir) { + ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); + ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); + draw_line(dr, bx+(int)cx, by+(int)cy, + bx+(int)(cx+ex), by+(int)(cy+ey), col); + } + } + + /* + * Draw the box in the middle. We do this in blue if the tile + * is an unpowered endpoint, in cyan if the tile is a powered + * endpoint, in black if the tile is the centrepiece, and + * otherwise not at all. + */ + col = -1; + if (x == state->cx && y == state->cy) + col = COL_WIRE; + else if (COUNT(tile) == 1) { + col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); + } + if (col >= 0) { + int i, points[8]; + + points[0] = +1; points[1] = +1; + points[2] = +1; points[3] = -1; + points[4] = -1; points[5] = -1; + points[6] = -1; points[7] = +1; + + for (i = 0; i < 8; i += 2) { + ex = (TILE_SIZE * 0.24F) * points[i]; + ey = (TILE_SIZE * 0.24F) * points[i+1]; + points[i] = bx+(int)(cx+ex); + points[i+1] = by+(int)(cy+ey); + } + + draw_polygon(dr, points, 4, col, COL_WIRE); + } + + /* + * Draw the points on the border if other tiles are connected + * to us. + */ + for (dir = 1; dir < 0x10; dir <<= 1) { + int dx, dy, px, py, lx, ly, vx, vy, ox, oy; + + dx = X(dir); + dy = Y(dir); + + ox = x + dx; + oy = y + dy; + + if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height) + continue; + + if (!(tile(state, ox, oy) & F(dir))) + continue; + + px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx); + py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy); + lx = dx * (TILE_BORDER-1); + ly = dy * (TILE_BORDER-1); + vx = (dy ? 1 : 0); + vy = (dx ? 1 : 0); + + if (xshift == 0.0 && yshift == 0.0 && (tile & dir)) { + /* + * If we are fully connected to the other tile, we must + * draw right across the tile border. (We can use our + * own ACTIVE state to determine what colour to do this + * in: if we are fully connected to the other tile then + * the two ACTIVE states will be the same.) + */ + draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE); + draw_rect_coords(dr, px, py, px+lx, py+ly, + (tile & ACTIVE) ? COL_POWERED : COL_WIRE); + } else { + /* + * The other tile extends into our border, but isn't + * actually connected to us. Just draw a single black + * dot. + */ + draw_rect_coords(dr, px, py, px, py, COL_WIRE); + } + } + + draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); +} + +static void draw_tile_barriers(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y) +{ + int phase; + int dir; + int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x; + int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y; + /* + * Draw barrier corners, and then barriers. + */ + for (phase = 0; phase < 2; phase++) { + for (dir = 1; dir < 0x10; dir <<= 1) + if (barrier(state, x, y) & (dir << 4)) + draw_barrier_corner(dr, ds, x, y, dir << 4, phase); + for (dir = 1; dir < 0x10; dir <<= 1) + if (barrier(state, x, y) & dir) + draw_barrier(dr, ds, x, y, dir, phase); + } + + draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); +} + +static void draw_arrow(drawing *dr, game_drawstate *ds, + int x, int y, int xdx, int xdy, int cur) +{ + int coords[14]; + int ydy = -xdx, ydx = xdy; + + x = x * TILE_SIZE + BORDER + WINDOW_OFFSET; + y = y * TILE_SIZE + BORDER + WINDOW_OFFSET; + +#define POINT(n, xx, yy) ( \ + coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \ + coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy) + + POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4); /* top of arrow */ + POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2); /* right corner */ + POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2); /* right concave */ + POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom right */ + POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom left */ + POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2); /* left concave */ + POINT(6, TILE_SIZE / 4, TILE_SIZE / 2); /* left corner */ + + draw_polygon(dr, coords, 7, cur ? COL_POWERED : COL_LOWLIGHT, COL_TEXT); +} + +static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds, + int cur_x, int cur_y, int cur) +{ + if (cur_x == -1 && cur_y == -1) + return; /* 'no cursur here */ + else if (cur_x == -1) /* LH column. */ + draw_arrow(dr, ds, 0, cur_y+1, 0, -1, cur); + else if (cur_x == ds->width) /* RH column */ + draw_arrow(dr, ds, ds->width, cur_y, 0, +1, cur); + else if (cur_y == -1) /* Top row */ + draw_arrow(dr, ds, cur_x, 0, +1, 0, cur); + else if (cur_y == ds->height) /* Bottom row */ + draw_arrow(dr, ds, cur_x+1, ds->height, -1, 0, cur); + else + assert(!"Invalid cursor position"); + + draw_update(dr, + cur_x * TILE_SIZE + BORDER + WINDOW_OFFSET, + cur_y * TILE_SIZE + BORDER + WINDOW_OFFSET, + TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float t, float ft) +{ + int x, y, frame; + unsigned char *active; + float xshift = 0.0; + float yshift = 0.0; + int cur_x = -1, cur_y = -1; + + /* + * Clear the screen and draw the exterior barrier lines if this + * is our first call. + */ + if (!ds->started) { + int phase; + + ds->started = TRUE; + + draw_rect(dr, 0, 0, + BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, + BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, + COL_BACKGROUND); + draw_update(dr, 0, 0, + BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER, + BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER); + + for (phase = 0; phase < 2; phase++) { + + for (x = 0; x < ds->width; x++) { + if (barrier(state, x, 0) & UL) + draw_barrier_corner(dr, ds, x, -1, LD, phase); + if (barrier(state, x, 0) & RU) + draw_barrier_corner(dr, ds, x, -1, DR, phase); + if (barrier(state, x, 0) & U) + draw_barrier(dr, ds, x, -1, D, phase); + if (barrier(state, x, ds->height-1) & DR) + draw_barrier_corner(dr, ds, x, ds->height, RU, phase); + if (barrier(state, x, ds->height-1) & LD) + draw_barrier_corner(dr, ds, x, ds->height, UL, phase); + if (barrier(state, x, ds->height-1) & D) + draw_barrier(dr, ds, x, ds->height, U, phase); + } + + for (y = 0; y < ds->height; y++) { + if (barrier(state, 0, y) & UL) + draw_barrier_corner(dr, ds, -1, y, RU, phase); + if (barrier(state, 0, y) & LD) + draw_barrier_corner(dr, ds, -1, y, DR, phase); + if (barrier(state, 0, y) & L) + draw_barrier(dr, ds, -1, y, R, phase); + if (barrier(state, ds->width-1, y) & RU) + draw_barrier_corner(dr, ds, ds->width, y, UL, phase); + if (barrier(state, ds->width-1, y) & DR) + draw_barrier_corner(dr, ds, ds->width, y, LD, phase); + if (barrier(state, ds->width-1, y) & R) + draw_barrier(dr, ds, ds->width, y, L, phase); + } + } + + /* + * Arrows for making moves. + */ + for (x = 0; x < ds->width; x++) { + if (x == state->cx) continue; + draw_arrow(dr, ds, x, 0, +1, 0, 0); + draw_arrow(dr, ds, x+1, ds->height, -1, 0, 0); + } + for (y = 0; y < ds->height; y++) { + if (y == state->cy) continue; + draw_arrow(dr, ds, ds->width, y, 0, +1, 0); + draw_arrow(dr, ds, 0, y+1, 0, -1, 0); + } + } + if (ui->cur_visible) { + cur_x = ui->cur_x; cur_y = ui->cur_y; + } + if (cur_x != ds->cur_x || cur_y != ds->cur_y) { + /* Cursor has changed; redraw two (prev and curr) arrows. */ + assert(cur_x != state->cx && cur_y != state->cy); + + draw_arrow_for_cursor(dr, ds, cur_x, cur_y, 1); + draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, 0); + ds->cur_x = cur_x; ds->cur_y = cur_y; + } + + /* Check if this is an undo. If so, we will need to run any animation + * backwards. + */ + if (oldstate && oldstate->move_count > state->move_count) { + const game_state * tmpstate = state; + state = oldstate; + oldstate = tmpstate; + t = ANIM_TIME - t; + } + + if (oldstate && (t < ANIM_TIME)) { + /* + * We're animating a slide, of row/column number + * state->last_move_pos, in direction + * state->last_move_dir + */ + xshift = state->last_move_row == -1 ? 0.0F : + (1 - t / ANIM_TIME) * state->last_move_dir; + yshift = state->last_move_col == -1 ? 0.0F : + (1 - t / ANIM_TIME) * state->last_move_dir; + } + + frame = -1; + if (ft > 0) { + /* + * We're animating a completion flash. Find which frame + * we're at. + */ + frame = (int)(ft / FLASH_FRAME); + } + + /* + * Draw any tile which differs from the way it was last drawn. + */ + if (xshift != 0.0 || yshift != 0.0) { + active = compute_active(state, + state->last_move_row, state->last_move_col); + } else { + active = compute_active(state, -1, -1); + } + + clip(dr, + BORDER + WINDOW_OFFSET, BORDER + WINDOW_OFFSET, + TILE_SIZE * state->width + TILE_BORDER, + TILE_SIZE * state->height + TILE_BORDER); + + for (x = 0; x < ds->width; x++) + for (y = 0; y < ds->height; y++) { + unsigned char c = tile(state, x, y) | index(state, active, x, y); + + /* + * In a completion flash, we adjust the FLASHING bit + * depending on our distance from the centre point and + * the frame number. + */ + if (frame >= 0) { + int xdist, ydist, dist; + xdist = (x < state->cx ? state->cx - x : x - state->cx); + ydist = (y < state->cy ? state->cy - y : y - state->cy); + dist = (xdist > ydist ? xdist : ydist); + + if (frame >= dist && frame < dist+4) { + int flash = (frame - dist) & 1; + flash = flash ? FLASHING : 0; + c = (c &~ FLASHING) | flash; + } + } + + if (index(state, ds->visible, x, y) != c || + index(state, ds->visible, x, y) == 0xFF || + (x == state->last_move_col || y == state->last_move_row)) + { + float xs = (y == state->last_move_row ? xshift : (float)0.0); + float ys = (x == state->last_move_col ? yshift : (float)0.0); + + draw_tile(dr, ds, state, x, y, c, xs, ys); + if (xs < 0 && x == 0) + draw_tile(dr, ds, state, state->width, y, c, xs, ys); + else if (xs > 0 && x == state->width - 1) + draw_tile(dr, ds, state, -1, y, c, xs, ys); + else if (ys < 0 && y == 0) + draw_tile(dr, ds, state, x, state->height, c, xs, ys); + else if (ys > 0 && y == state->height - 1) + draw_tile(dr, ds, state, x, -1, c, xs, ys); + + if (x == state->last_move_col || y == state->last_move_row) + index(state, ds->visible, x, y) = 0xFF; + else + index(state, ds->visible, x, y) = c; + } + } + + for (x = 0; x < ds->width; x++) + for (y = 0; y < ds->height; y++) + draw_tile_barriers(dr, ds, state, x, y); + + unclip(dr); + + /* + * Update the status bar. + */ + { + char statusbuf[256]; + int i, n, a; + + n = state->width * state->height; + for (i = a = 0; i < n; i++) + if (active[i]) + a++; + + if (state->used_solve) + sprintf(statusbuf, "Moves since auto-solve: %d", + state->move_count - state->completed); + else + sprintf(statusbuf, "%sMoves: %d", + (state->completed ? "COMPLETED! " : ""), + (state->completed ? state->completed : state->move_count)); + + if (state->movetarget) + sprintf(statusbuf + strlen(statusbuf), " (target %d)", + state->movetarget); + + sprintf(statusbuf + strlen(statusbuf), " Active: %d/%d", a, n); + + status_bar(dr, statusbuf); + } + + sfree(active); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return ANIM_TIME; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + /* + * If the game has just been completed, we display a completion + * flash. + */ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) { + int size; + size = 0; + if (size < newstate->cx+1) + size = newstate->cx+1; + if (size < newstate->cy+1) + size = newstate->cy+1; + if (size < newstate->width - newstate->cx) + size = newstate->width - newstate->cx; + if (size < newstate->height - newstate->cy) + size = newstate->height - newstate->cy; + return FLASH_FRAME * (size+4); + } + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return FALSE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame netslide +#endif + +const struct game thegame = { + "Netslide", "games.netslide", "netslide", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/no-icon.c b/apps/plugins/puzzles/no-icon.c new file mode 100644 index 0000000000..114b2c57c7 --- /dev/null +++ b/apps/plugins/puzzles/no-icon.c @@ -0,0 +1,8 @@ + +/* + * Dummy source file which replaces the files generated in the + * `icons' subdirectory, when they're absent. + */ + +const char *const *const xpm_icons[] = { 0 }; +const int n_xpm_icons = 0; diff --git a/apps/plugins/puzzles/noicon.rc b/apps/plugins/puzzles/noicon.rc new file mode 100644 index 0000000000..1de605d605 --- /dev/null +++ b/apps/plugins/puzzles/noicon.rc @@ -0,0 +1,11 @@ +/* Puzzle resource file without an icon, used in the absence of icons/foo.rc */ + +#include "puzzles.rc2" + +/* XXX this probably isn't the right test, but it'll do. */ +#ifdef MINGW32_FIX +/* XXX The MinGW toolchain (specifically, windres) doesn't like a resource + * file with no resources. Give it a dummy one. + * This can go if/when VERSIONINFO resources are added. */ +200 RCDATA { 0 } +#endif diff --git a/apps/plugins/puzzles/nullfe.c b/apps/plugins/puzzles/nullfe.c new file mode 100644 index 0000000000..4c9975b90e --- /dev/null +++ b/apps/plugins/puzzles/nullfe.c @@ -0,0 +1,69 @@ +/* + * nullfe.c: Null front-end code containing a bunch of boring stub + * functions. Used to ensure successful linking when building the + * various stand-alone solver binaries. + */ + +#include + +#include "puzzles.h" + +void frontend_default_colour(frontend *fe, float *output) {} +void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text) {} +void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {} +void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {} +void draw_thick_line(drawing *dr, float thickness, + float x1, float y1, float x2, float y2, int colour) {} +void draw_polygon(drawing *dr, int *coords, int npoints, + int fillcolour, int outlinecolour) {} +void draw_circle(drawing *dr, int cx, int cy, int radius, + int fillcolour, int outlinecolour) {} +char *text_fallback(drawing *dr, const char *const *strings, int nstrings) +{ return dupstr(strings[0]); } +void clip(drawing *dr, int x, int y, int w, int h) {} +void unclip(drawing *dr) {} +void start_draw(drawing *dr) {} +void draw_update(drawing *dr, int x, int y, int w, int h) {} +void end_draw(drawing *dr) {} +blitter *blitter_new(drawing *dr, int w, int h) {return NULL;} +void blitter_free(drawing *dr, blitter *bl) {} +void blitter_save(drawing *dr, blitter *bl, int x, int y) {} +void blitter_load(drawing *dr, blitter *bl, int x, int y) {} +int print_mono_colour(drawing *dr, int grey) { return 0; } +int print_grey_colour(drawing *dr, float grey) { return 0; } +int print_hatched_colour(drawing *dr, int hatch) { return 0; } +int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey) +{ return 0; } +int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey) +{ return 0; } +int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch) +{ return 0; } +void print_line_width(drawing *dr, int width) {} +void print_line_dotted(drawing *dr, int dotted) {} +void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) {} +void status_bar(drawing *dr, char *text) {} + +void fatal(char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "fatal error: "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); + exit(1); +} + +#ifdef DEBUGGING +void debug_printf(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} +#endif diff --git a/apps/plugins/puzzles/nullgame.R b/apps/plugins/puzzles/nullgame.R new file mode 100644 index 0000000000..41bdb85d57 --- /dev/null +++ b/apps/plugins/puzzles/nullgame.R @@ -0,0 +1,12 @@ +# -*- makefile -*- + +# The `nullgame' source file is a largely blank one, which contains +# all the correct function definitions to compile and link, but +# which defines the null game in which nothing is ever drawn and +# there are no valid moves. Its main purpose is to act as a +# template for writing new game definition source files. I include +# it in the Makefile because it will be worse than useless if it +# ever fails to compile, so it's important that it should actually +# be built on a regular basis. +nullgame : [X] GTK COMMON nullgame nullgame-icon|no-icon +nullgame : [G] WINDOWS COMMON nullgame nullgame.res|noicon.res diff --git a/apps/plugins/puzzles/nullgame.c b/apps/plugins/puzzles/nullgame.c new file mode 100644 index 0000000000..6e14c3237c --- /dev/null +++ b/apps/plugins/puzzles/nullgame.c @@ -0,0 +1,303 @@ +/* + * nullgame.c [FIXME]: Template defining the null game (in which no + * moves are permitted and nothing is ever drawn). This file exists + * solely as a basis for constructing new game definitions - it + * helps to have something which will compile from the word go and + * merely doesn't _do_ very much yet. + * + * Parts labelled FIXME actually want _removing_ (e.g. the dummy + * field in each of the required data structures, and this entire + * comment itself) when converting this source file into one + * describing a real game. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + NCOLOURS +}; + +struct game_params { + int FIXME; +}; + +struct game_state { + int FIXME; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->FIXME = 0; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + return FALSE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ +} + +static char *encode_params(const game_params *params, int full) +{ + return dupstr("FIXME"); +} + +static config_item *game_configure(const game_params *params) +{ + return NULL; +} + +static game_params *custom_params(const config_item *cfg) +{ + return NULL; +} + +static char *validate_params(const game_params *params, int full) +{ + return NULL; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + return dupstr("FIXME"); +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + + state->FIXME = 0; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->FIXME = state->FIXME; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return NULL; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +static game_ui *new_ui(const game_state *state) +{ + return NULL; +} + +static void free_ui(game_ui *ui) +{ +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int tilesize; + int FIXME; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = *y = 10 * tilesize; /* FIXME */ +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; + ds->FIXME = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND); + draw_update(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame nullgame +#endif + +const struct game thegame = { + "Null Game", NULL, NULL, + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + FALSE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + FALSE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + 20 /* FIXME */, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/obfusc.c b/apps/plugins/puzzles/obfusc.c new file mode 100644 index 0000000000..e95fa3f397 --- /dev/null +++ b/apps/plugins/puzzles/obfusc.c @@ -0,0 +1,126 @@ +/* + * Stand-alone tool to access the Puzzles obfuscation algorithm. + * + * To deobfuscate, use "obfusc -d": + * + * obfusc -d reads binary data from stdin, writes to stdout + * obfusc -d works on the given hex string instead of stdin + * obfusc -d -h writes a hex string instead of binary to stdout + * + * To obfuscate, "obfusc -e": + * + * obfusc -e reads binary from stdin, writes hex to stdout + * obfusc -e works on the given hex string instead of stdin + * obfusc -e -b writes binary instead of text to stdout + * + * The default output format is hex for -e and binary for -d + * because that's the way obfuscation is generally used in + * Puzzles. Either of -b and -h can always be specified to set it + * explicitly. + * + * Data read from standard input is assumed always to be binary; + * data provided on the command line is taken to be hex. + */ + +#include +#include +#include +#include +#include + +#include "puzzles.h" + +int main(int argc, char **argv) +{ + enum { BINARY, DEFAULT, HEX } outputmode = DEFAULT; + char *inhex = NULL; + unsigned char *data; + int datalen; + int decode = -1; + int doing_opts = TRUE; + + while (--argc > 0) { + char *p = *++argv; + + if (doing_opts && *p == '-') { + if (!strcmp(p, "--")) { + doing_opts = 0; + continue; + } + p++; + while (*p) { + switch (*p) { + case 'e': + decode = 0; + break; + case 'd': + decode = 1; + break; + case 'b': + outputmode = BINARY; + break; + case 'h': + outputmode = HEX; + break; + default: + return 1; + } + p++; + } + } else { + if (!inhex) { + inhex = p; + } else { + return 1; + } + } + } + + if (decode < 0) { + return 0; + } + + if (outputmode == DEFAULT) + outputmode = (decode ? BINARY : HEX); + + if (inhex) { + datalen = strlen(inhex) / 2; + data = hex2bin(inhex, datalen); + } else { + int datasize = 4096; + datalen = 0; + data = snewn(datasize, unsigned char); + while (1) { + int ret = fread(data + datalen, 1, datasize - datalen, stdin); + if (ret < 0) { + fprintf(stderr, "obfusc: read: %s\n", strerror(errno)); + return 1; + } else if (ret == 0) { + break; + } else { + datalen += ret; + if (datasize - datalen < 4096) { + datasize = datalen * 5 / 4 + 4096; + data = sresize(data, datasize, unsigned char); + } + } + } + } + + obfuscate_bitmap(data, datalen * 8, decode); + + if (outputmode == BINARY) { + int ret = fwrite(data, 1, datalen, stdout); + if (ret < 0) { + fprintf(stderr, "obfusc: write: %s\n", strerror(errno)); + return 1; + } + } else { + int i; + for (i = 0; i < datalen; i++) + printf("%02x", data[i]); + printf("\n"); + } + + return 0; +} diff --git a/apps/plugins/puzzles/osx-help.but b/apps/plugins/puzzles/osx-help.but new file mode 100644 index 0000000000..fa45996aee --- /dev/null +++ b/apps/plugins/puzzles/osx-help.but @@ -0,0 +1,14 @@ +\# Additional Halibut fragment to set up the HTML output +\# appropriately for MacOS online help. + +\cfg{html-head-end}{ + +} diff --git a/apps/plugins/puzzles/osx-info.plist b/apps/plugins/puzzles/osx-info.plist new file mode 100644 index 0000000000..9f4aef8e53 --- /dev/null +++ b/apps/plugins/puzzles/osx-info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleIconFile + Puzzles.icns + CFBundleHelpBookFolder + Help + CFBundleHelpBookName + Puzzles Help + CFBundleName + Puzzles + CFBundleDisplayName + Puzzles + CFBundleExecutable + Puzzles + CFBundleVersion + Unidentified build + CFBundleShortVersionString + Unidentified build + CFBundleDevelopmentRegion + en + CFBundleIdentifier + uk.org.greenend.chiark.sgtatham.puzzles + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSHumanReadableCopyright + This software is copyright (c) 2004-2014 Simon Tatham + + diff --git a/apps/plugins/puzzles/osx.icns b/apps/plugins/puzzles/osx.icns new file mode 100644 index 0000000000000000000000000000000000000000..b4346a0da15a60eccd8c7112317afeb24cb5e4e8 GIT binary patch literal 48589 zcmeHv3w%`NweL4GnIz;TL0$$t)W$~@lwwbj2n5jnF6yn;!79?)8x>1HGb9+E2Ev~A z&P*OK!6XP1LP&-L!w^Ye2$0O)^T-3VT2s9~$nn>6>*1$(ZkyAd`?L0%xodrU?|CFM zNznG3-|5|iA(`20t#7UW_pNX3@4r{rw0?6jA>D6nT3?(^2ze;@E&psP{=vP3C`a}# zUa;Vv1q<$8u=0^dR;_q&`ML#!BoReIM&0H9;~O5_zEpTm$g{6_TJ9%5U3qs>QhJi= z8Mc$@JhtiH?aS$P(isfYpRUvY%#w2#Au2-J{;$V@D{821>KgP zKU%e{Fc@szy0dUshrYgI>wkaw-lrYEx_r)c@{NKu;In z-POED$E5D^Kh?^ezi{E4``Ao9QLSCmKBW6V`CINg20~KB(C@kPdkYrac5_zNgJS6E zHT}H}*5kIDB0_gKgRjkr2))6y-|`Pq=zhoPy3#`Z%~I(8i`|EGJJ^2^Loa{L-cY_{ zhi#q|`l-63)|X!>$`V6wKj}JoqNTO+heQhfj2}Wjef*ZchtTV-+qPP64uyXBLX)ku z?)L9VvwqTbrlaFv?{i}4?PvX7um9A=Cxp=5JzWgH+}9R(LYVcl+<887*3WVm=&F!r z-OZiqzHs4UEBB-@?HvOj|4|oIz5O`O`;<@K+55B2g@s!R<`yr}zC?&3`CBH@e|hzX zAAAgU6B&n11-Kigw2>U^64iZ+e|W!ssqubQ&c-D-=#mxFr|6Osrza=hvf0(aoK|(1 z88gEs829EZNzxILrYAGf3CY^l14--mSCu{c=;M#?HQv7@Sw|G<3LQz$AY{%qPoS6Q zy_MGQG9*E3ioTk1=Z_v(>*2i>);r+pX1FREPER3Z>hsRy_npOylInC_x?)xalRh_P z@wz9UtL3>>XT9DE_Gim(dT6)d{v}h0LP0W=jAAC)9Sr{K2mbD5ZlABhTv_^q{Wg7g zwKG4e}C@hx}c(Mqi)Nbybbxq^%u`C@96Q^jF#<-tZ;StZ-XNv zLFam1!O~|73U(ahJuA=oU|=f@440p{@^8QY{h!))?PZwKZ90Z+?C5x4kH!4#&wrtr zm-qZLckTVnZ{Bgs*JxNlze)W&x>hM(){>GAZO zzu4E)aWdfZ_yc}_AkcrL?z@CEU+QGeD%@uocjD=8-?=why(T_6;5gfQGdcE-&*V?? zc}}0IZS;bbG>+%Fz~T1()|;CKeY(ILwBmJny{2P6yu@=H??2q`z5L^*ce+iU+b_P( zb7r^%7w`)-z5Nd#d%MTu;e5P@^U&*!J`+C+FAY50^mY%&bvj&}ciYUYw;QZ7XE~2x_L$GW-@s92dXeMKRQecCB2{)?GWE^6eDn<* z^PO%`;lU-UXVKf3vK!bkwyeyy#T5W|VO&nq*epe|2m*eE2#kyyc zxqNHH$kBONoWHYlQ}oqv9^#f!PLEg*;Sa9=_eBJD1sIVGOU(OVMdq zZQZ?Rcj@lZQtgW`y|mQ?s?llaxZp17=qa2ln4F``kRMgTmv==R&H%~Dt4LczXc`*%-#!ABzGz|}rO2cDv8vZrY zP;qA_qgKjk2ub>%oQ7q3Q^`x1-?7uM$^z+cvKA}Hrr{gA&UyKR2Wl!x8=Lmu7$Xg< z8k?IB)z>?A)_nAvWi$=bL)DO`q0X1$_xIJ;bq1dsRSet3EbKUb@wsp@JklJQg}tZl zi!O#97o6R~#qbiA7_k_7;aEq@R?K3^aZpQR7elyo%4_6qj8zO7KJgTkU-*h$45@C+ zV)(s_ufxSR0)C(H)n6Z@B6{nbjmHd)DNgf=a5xp8Mrf zDFyWGlJNii+2z0d;Y$ucM&Mf@uxb59IJ+yqH52$PT#!}&4iMP15xI|<-HM#0#$}47 z@K4gxWy_W>%~^84c{y2WS&{HS@zUF8>SrrwWzR9rPQLY~*|X*@e5&+-p;~!hUxB4q zwRP39Sw^CmsxlHXjUiJLvNtsM^#o!-Hss%pFuf^MZs?-{xXWX>OoUpLv#?fi=1?vZ{rmIKN0)f7*XQHCzV1?{ zET!_PxmLm6bO1YD39D+gE~x(J5B=xQcj?Ygy>Moi(O_WLh@F>~fD-Au)d4CaQEsfpha9Q=NpF{pTBy`eC@=;zO^e}1F3w4(O4 ztvBprY83Y8nHN(xuew_#>0@2P*FO9F z+UI}h*kd$j7Te4wwz9FU(_MRucH6e%qKBEsl$wPZ%#(>vJ#^=by+a=|AE`eWI$L|_ zutYdY<5?-sJ9+SAe+hZ1BS!INabP4+i(+eBglNa^p>e92{(E zZZuFf;<6*XQX zKVLFe?FjIF%{SFv3BV0Iy=V8q(<8s1KL(?B6xI6SZl2*49B1HDdaLXGJa>6z?G?`8 zN#MAi9**%OcQ*jnb-K95ujdG_=hDH`UbM>fEwBBU2YQTyqY=c?4zR`*Ov=+IirVgdci1%Mqw9&kh}<022gJY>H|FAzr6M%K$PL8K^k}br^3JxI$8tIJ?cK^X0Hbv$hmmb!+C{(Xof?Qi-U^*aJT|E|9YS- zfi3lPAK?(JS|4ZRr{V#NN3f*mbsiPpw$7{bOyxY?M?n%If@?UZ40i+kD?*D1puaQt z4kx7`+ED}hs$;K0tO%R;OulDGhr=)JD%V(*xt+cEw8x(zL-2|C~^@2J-uti zv`UgZzrDAA`Kthu9%29sKy-3F5VN;q*UHQ)k~+Wr&E>U2paozs9s;kF9uJuB0Rt;D z_mR2t-4E3L%IR!wayB=^(#FN@Y(C!W?>)Esfz16x^@DpFua$q1eC>b#{`bTG^r~iN zH3^qtvXoh-yzkbj)kO8Phypwf3h?Z#TUX^36&AwNtj&c55B@Gf zqGnrFgjB2|yrWSUOl&QV6pK}acQk-l%hoYsql$2ZSY#2NJE{opI2`=?>tGX(I#Cri zJYT%wg`($m80`dAxXfzZyisp0iv%Wi6|UIwqS0Em?Rfb zoObNy%J31(j-tY%mkpblB1PdwW>d<>Z94>$qwDZyW6jZHZ1Jm1 zgW_-jvo&@5h5}JAT!*Jd)!|FWin^HIsaS`tl`o4C#jV4EqebvsS*XM1TV67jl~w(O z#HqvQ_&{S}i&Teo8=rrrwAA=8iC2f~pWP(Z;k3tB=05)9lj|0daq6&XJAg0L;l11Q z7Tz&`!A&G~9X`{Bb-4Siml5l5uXmqmVcLzUBu*VZJ*o}|s=s+1PK&6+jjuvcpF|z5 zmFuuvhvhmf*I_R`49j&G$&UY=Iy?i9!*|^^Ke`SV#;?QEg!8aehmSTi{Eg~xVJEN# z7xplg>DIzcdW)sv(edhVQCDvuz?t?{RoQnGl~qNtG( z2y1L@JJDX6K3*N(QBiH*Uj=s=mNI6;j{ZxRPu($o9p)LI!s}uDcLn$=UCkfA`oja0 ztHWu5>ZA4hz7DRL?0I3{szE@3D3jeX@h^_^Drj_H0kp&0^pt0c^D%CsN$Z77et?j!SJ#E0MM$!U;KWl zcAOJ2^e|ZuS6Ax}sH&?Ez_*mTqS_b-Vig{U;ehNLtZw|^*QR%MzfODitzFKDV=0pU-g6K!70~@ouPyAw8(=o=NtT&Kh(Tc9!&e_vKf$ zZJBZhl7R}Gj%F4`y%Zeaja;gc*IRGyUNlwk0m7~covAb$GqB+DW z$=K_;aGi~RJrk}Q>@J2Q0~X<>sb-167|T{BjAgJpt2xyG$E{;QtSXk+EMZ79m@~l3 z6wfumoU0OKjN{2bcs9{BBU?cj7?+7VuHA`aR)IZ1u7bo3rNr(;A2Do(Qw(U(_;|vI zv7S{J;)&6?qoIs%5;3wn_tCMH0~{Tkz;p5#Q;0YL29bEivilmrNo2uEysBpt3~bbL zI7C=FaWUgOM0B8R=9C)NppO~g6j*DxR#+#URffQv#56^hr8u`HZW3~U1Imr#ov_j= zWG)mHe4Su;FpF{w$SXL&WxN@tS;SJ?(P2S0uGZv2205JcDlAkCUt?n#Vvey8*^!jt zfvZD9g9DHv19}*nE^hE50VbI>m{}z}_hE>2^hA#&4-a#Kq68X_FuIZ&d$ zk2@Axhvfy67PV!CR{J3C=pm=au5B6|m>r-N?O@SJv1LdsjKwv8PVA@dPOx0q{B=3m znJp2vj^~hE6MZG;YO?3eo|c}hY6&Onh$=^`&1bYE-=bCI=j*g22VVJlZE{ITUe2QI ztWl}coB#)(I8UR)ZKqx9I*g5D9jXAG}SLjv)0QSbq5?c#dZQBlpw{R%CTlb=SsM5DhQ4cs!I5l z2;cGyEI^0wMZOYBG_0L|ricC6;b2?@eVd<4Y`e)1ZEL~k+Cge7_T_K}Jqa#c5!T;i}h zV0P>d#!kf7Ecl=#-{r!O>0hu1IIg3W1=hTVWZQC7WFA>mmXik7v|7jT&>(&>Lu61l zq#IPh_Qh5^R0gfvUFJ}(b2u#ag#6tAwH`)c;bo@pXU%1^63H4;qID132Z>$pB#sKF z%1|;ez&aJKd5Ln90qFoprY+^JpniFcP-L4xZS6hZ*k3k1QGlI@k2oa!; zo=adAFx{^YE3U$NOmML!c^Y&U%R7VvB}nar>sN>CI0dYJm@AHR*g>+zzIy|eL^Eg= zFd}7KBDf%|4O|FxdF&g}OYuP$;Y5Q$!%Be_4sMvZL^nsR<7$KX3YkVPs^O)lenWcL z5q&C(@d@`6idm{%G;#(W$A6E+-SISP}5-YXVm;{u|oVUeb^o(8NOz@(5OfGvzw zJ0WH@#0#SsB>O_3(YWH2!X(j&ij2&RtC+4fK-iV4foteP^bDgxe4&BJ$*IDX%4t%8 z^}($ptQ0vvI5NJ3TpAu0Mo_!edDLf$6eKDD3Q~ZqlvQnjkeIAj@k#}!%p)$l;G3|H zzyM+?xrduLxERhPc!M~mE}|txj1za~(A)|6C^#s=hVYEqEYN2wr*x1Knv=p>mKImY z&0(B(fDzo3rW0=R5tT$b@3>#l@*+VJUXA5|q%BAECy*58O-ER6TnVJyWpk*nYc*b93t>IwK?TJl&?5+zWw3aW~o#Z-Vb zHAP6&+^oA(+X%^nJ($?p;b8>2jIMdR9i~I_HFthi+DQ<@g#(jD9YBC_`5`D+wV=cZ z4uEi~iwY;>Zpp$u0?dTq1MW%a3*D0<6;6Qy)G}%r9j---xW@EcDg^?q5~4saq&$P$ zQHW)qP_<~qinQ2{7{CnzHHl6h6s9}uq)xOaK?+q4;DqHYj2jlzi-J<p-iM_GkHZj5>k6O*KmWjd0k7p~K13Xy&KNG&Dh5k76C`{3es)><`apzc+(?b7=yKss}Ym8 zXD~&p2}*Xy$I9in7b?+6Xr3L{n$|> zM4~ABv67aE=L9fcxV^-LKEfj2h(HMy#RNh*I*fRf{n+6nmI6jBQ1&P$aHXQzaVUF~ zjatCtQTAiUpNO)LqY;y`7Zw*j%?j^@q4**h`?yA9F!m@r7GiNWq8a=6i$x&po%pz1 zvK7tP3y;^2L-7$abrOs{+ztc}qe)3+aj}JNUtpxPM1U@`mMSnrv z%!v#>8bI<{;P3^+CuHzZDHhn1;PCt44mKisfy9@h_|adD#s88BEWQ#Ld@FGIs&7c+ z4~sOu5-5CYK0~JbRe1bQMIK)X9KLlecsEHR|I;WUUkS9kbv0^#Qzl=vjueEbd_7Y6 z5aDE*{J~L7{$M^b`QXm@ME>9yB7aaI@=T?nF#@LqoNb)1xrYtqwkGU%I z_>&-jWgb8D8y+%`AG`Y=iIpfCl>SDH%;S%u-Nbbi!ODgnIf%U^LhrrAQ-z6Kna8I+ zZ@@@KlaZql6@J@5=J7{&-s6grxcBg?njDW0eFSP?+tu)7K8I@j&G>vAsaEFnX=i=h zj{FE#JDQiDuqB`F(lVz%QD;7I`n}{iTeUJHm|?9>%*gnrz4<`uze%>(>Xq5SY-@d5 zcJ^PVJ0Dp6cZtr{s>})ISX&cwawglK|0&UZsQB_-!-vWbKB%^xQLYKDv7Sj>v*w$( z=tmIy2?U7!Q>-_wV2%2lQTytAp!RRGSzYq|sSsyhGq9%4-VUHj-t3iVn?fs13u8*2}Q4`z!P-~oq zeH7P;;_yJ9_A5d^*@P|osK-b&!R0D@XE!a51-NaXi zF)wkB$9C-t9tz#oI1E0Uk60>n`9y{}hVG{ZBD=z+2LqVk*wGr*wNIrd-L+4p8^v&7 zPYeAvNW89nsvynGWV-gL0>*qilorCyi(3oe0N#)4+NTaKyVu>dPt|Z!vQnQW)wLgr3pUuOUnar?!K8#3X^{_i z?T-p1x@(_ayxy*TsuWoYaQQ$Wlj+(A-AkGYyY_K}u7}*iU$z95ohg%Q+eej8yo+sL zwo;V|`u3^xr)L(QIRVn@c%A#ujV=gYJ2R_Oki_*Pb+9AmkTsFb`zfOQ@R^JzNnyeU z{!~fn%ccx*m^fYhX(7pAXNow0lzOom`P0MlLBV!YO3ZftkQ{^PiB808=?@9(B7`MS za8HNZu(56ZAuYYAl?K31vb8^?YY=slA&xQK{UIr%D3u6OVuSx5|Mc(w=gU8T^_Q +#include +#include +#import +#include "puzzles.h" + +/* ---------------------------------------------------------------------- + * Global variables. + */ + +/* + * The `Type' menu. We frob this dynamically to allow the user to + * choose a preset set of settings from the current game. + */ +NSMenu *typemenu; + +/* + * Forward reference. + */ +extern const struct drawing_api osx_drawing; + +/* + * The NSApplication shared instance, which I'll want to refer to from + * a few places here and there. + */ +NSApplication *app; + +/* ---------------------------------------------------------------------- + * Miscellaneous support routines that aren't part of any object or + * clearly defined subsystem. + */ + +void fatal(char *fmt, ...) +{ + va_list ap; + char errorbuf[2048]; + NSAlert *alert; + + va_start(ap, fmt); + vsnprintf(errorbuf, lenof(errorbuf), fmt, ap); + va_end(ap); + + alert = [NSAlert alloc]; + /* + * We may have come here because we ran out of memory, in which + * case it's entirely likely that that alloc will fail, so we + * should have a fallback of some sort. + */ + if (!alert) { + fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf); + } else { + alert = [[alert init] autorelease]; + [alert addButtonWithTitle:@"Oh dear"]; + [alert setInformativeText:[NSString stringWithUTF8String:errorbuf]]; + [alert runModal]; + } + exit(1); +} + +void frontend_default_colour(frontend *fe, float *output) +{ + /* FIXME: Is there a system default we can tap into for this? */ + output[0] = output[1] = output[2] = 0.8F; +} + +void get_random_seed(void **randseed, int *randseedsize) +{ + time_t *tp = snew(time_t); + time(tp); + *randseed = (void *)tp; + *randseedsize = sizeof(time_t); +} + +static void savefile_write(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + fwrite(buf, 1, len, fp); +} + +static int savefile_read(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + int ret; + + ret = fread(buf, 1, len, fp); + return (ret == len); +} + +/* + * Since this front end does not support printing (yet), we need + * this stub to satisfy the reference in midend_print_puzzle(). + */ +void document_add_puzzle(document *doc, const game *game, game_params *par, + game_state *st, game_state *st2) +{ +} + +/* + * setAppleMenu isn't listed in the NSApplication header, but an + * NSApp responds to it, so we're adding it here to silence + * warnings. (This was removed from the headers in 10.4, so we + * only need to include it for 10.4+.) + */ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 +@interface NSApplication(NSAppleMenu) +- (void)setAppleMenu:(NSMenu *)menu; +@end +#endif + +/* ---------------------------------------------------------------------- + * Tiny extension to NSMenuItem which carries a payload of a `void + * *', allowing several menu items to invoke the same message but + * pass different data through it. + */ +@interface DataMenuItem : NSMenuItem +{ + void *payload; +} +- (void)setPayload:(void *)d; +- (void *)getPayload; +@end +@implementation DataMenuItem +- (void)setPayload:(void *)d +{ + payload = d; +} +- (void *)getPayload +{ + return payload; +} +@end + +/* ---------------------------------------------------------------------- + * Utility routines for constructing OS X menus. + */ + +NSMenu *newmenu(const char *title) +{ + return [[[NSMenu allocWithZone:[NSMenu menuZone]] + initWithTitle:[NSString stringWithUTF8String:title]] + autorelease]; +} + +NSMenu *newsubmenu(NSMenu *parent, const char *title) +{ + NSMenuItem *item; + NSMenu *child; + + item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] + initWithTitle:[NSString stringWithUTF8String:title] + action:NULL + keyEquivalent:@""] + autorelease]; + child = newmenu(title); + [item setEnabled:YES]; + [item setSubmenu:child]; + [parent addItem:item]; + return child; +} + +id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title, + const char *key, id target, SEL action) +{ + unsigned mask = NSCommandKeyMask; + + if (key[strcspn(key, "-")]) { + while (*key && *key != '-') { + int c = tolower((unsigned char)*key); + if (c == 's') { + mask |= NSShiftKeyMask; + } else if (c == 'o' || c == 'a') { + mask |= NSAlternateKeyMask; + } + key++; + } + if (*key) + key++; + } + + item = [[item initWithTitle:[NSString stringWithUTF8String:title] + action:NULL + keyEquivalent:[NSString stringWithUTF8String:key]] + autorelease]; + + if (*key) + [item setKeyEquivalentModifierMask: mask]; + + [item setEnabled:YES]; + [item setTarget:target]; + [item setAction:action]; + + [parent addItem:item]; + + return item; +} + +NSMenuItem *newitem(NSMenu *parent, char *title, char *key, + id target, SEL action) +{ + return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]], + parent, title, key, target, action); +} + +/* ---------------------------------------------------------------------- + * About box. + */ + +@class AboutBox; + +@interface AboutBox : NSWindow +{ +} +- (id)init; +@end + +@implementation AboutBox +- (id)init +{ + NSRect totalrect; + NSView *views[16]; + int nviews = 0; + NSImageView *iv; + NSTextField *tf; + NSFont *font1 = [NSFont systemFontOfSize:0]; + NSFont *font2 = [NSFont boldSystemFontOfSize:[font1 pointSize] * 1.1]; + const int border = 24; + int i; + double y; + + /* + * Construct the controls that go in the About box. + */ + + iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0,0,64,64)]; + [iv setImage:[NSImage imageNamed:@"NSApplicationIcon"]]; + views[nviews++] = iv; + + tf = [[NSTextField alloc] + initWithFrame:NSMakeRect(0,0,400,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setFont:font2]; + [tf setStringValue:@"Simon Tatham's Portable Puzzle Collection"]; + [tf sizeToFit]; + views[nviews++] = tf; + + tf = [[NSTextField alloc] + initWithFrame:NSMakeRect(0,0,400,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setFont:font1]; + [tf setStringValue:[NSString stringWithUTF8String:ver]]; + [tf sizeToFit]; + views[nviews++] = tf; + + /* + * Lay the controls out. + */ + totalrect = NSMakeRect(0,0,0,0); + for (i = 0; i < nviews; i++) { + NSRect r = [views[i] frame]; + if (totalrect.size.width < r.size.width) + totalrect.size.width = r.size.width; + totalrect.size.height += border + r.size.height; + } + totalrect.size.width += 2 * border; + totalrect.size.height += border; + y = totalrect.size.height; + for (i = 0; i < nviews; i++) { + NSRect r = [views[i] frame]; + r.origin.x = (totalrect.size.width - r.size.width) / 2; + y -= border + r.size.height; + r.origin.y = y; + [views[i] setFrame:r]; + } + + self = [super initWithContentRect:totalrect + styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | + NSClosableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + + for (i = 0; i < nviews; i++) + [[self contentView] addSubview:views[i]]; + + [self center]; /* :-) */ + + return self; +} +@end + +/* ---------------------------------------------------------------------- + * The front end presented to midend.c. + * + * This is mostly a subclass of NSWindow. The actual `frontend' + * structure passed to the midend contains a variety of pointers, + * including that window object but also including the image we + * draw on, an ImageView to display it in the window, and so on. + */ + +@class GameWindow; +@class MyImageView; + +struct frontend { + GameWindow *window; + NSImage *image; + MyImageView *view; + NSColor **colours; + int ncolours; + int clipped; + int w, h; +}; + +@interface MyImageView : NSImageView +{ + GameWindow *ourwin; +} +- (void)setWindow:(GameWindow *)win; +- (void)mouseEvent:(NSEvent *)ev button:(int)b; +- (void)mouseDown:(NSEvent *)ev; +- (void)mouseDragged:(NSEvent *)ev; +- (void)mouseUp:(NSEvent *)ev; +- (void)rightMouseDown:(NSEvent *)ev; +- (void)rightMouseDragged:(NSEvent *)ev; +- (void)rightMouseUp:(NSEvent *)ev; +- (void)otherMouseDown:(NSEvent *)ev; +- (void)otherMouseDragged:(NSEvent *)ev; +- (void)otherMouseUp:(NSEvent *)ev; +@end + +@interface GameWindow : NSWindow +{ + const game *ourgame; + midend *me; + struct frontend fe; + struct timeval last_time; + NSTimer *timer; + NSWindow *sheet; + config_item *cfg; + int cfg_which; + NSView **cfg_controls; + int cfg_ncontrols; + NSTextField *status; +} +- (id)initWithGame:(const game *)g; +- (void)dealloc; +- (void)processButton:(int)b x:(int)x y:(int)y; +- (void)processKey:(int)b; +- (void)keyDown:(NSEvent *)ev; +- (void)activateTimer; +- (void)deactivateTimer; +- (void)setStatusLine:(char *)text; +- (void)resizeForNewGameParams; +- (void)updateTypeMenuTick; +@end + +@implementation MyImageView + +- (void)setWindow:(GameWindow *)win +{ + ourwin = win; +} + +- (void)mouseEvent:(NSEvent *)ev button:(int)b +{ + NSPoint point = [self convertPoint:[ev locationInWindow] fromView:nil]; + [ourwin processButton:b x:point.x y:point.y]; +} + +- (void)mouseDown:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_BUTTON : + (mod & NSShiftKeyMask) ? MIDDLE_BUTTON : + LEFT_BUTTON)]; +} +- (void)mouseDragged:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_DRAG : + (mod & NSShiftKeyMask) ? MIDDLE_DRAG : + LEFT_DRAG)]; +} +- (void)mouseUp:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_RELEASE : + (mod & NSShiftKeyMask) ? MIDDLE_RELEASE : + LEFT_RELEASE)]; +} +- (void)rightMouseDown:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_BUTTON : + RIGHT_BUTTON)]; +} +- (void)rightMouseDragged:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_DRAG : + RIGHT_DRAG)]; +} +- (void)rightMouseUp:(NSEvent *)ev +{ + unsigned mod = [ev modifierFlags]; + [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_RELEASE : + RIGHT_RELEASE)]; +} +- (void)otherMouseDown:(NSEvent *)ev +{ + [self mouseEvent:ev button:MIDDLE_BUTTON]; +} +- (void)otherMouseDragged:(NSEvent *)ev +{ + [self mouseEvent:ev button:MIDDLE_DRAG]; +} +- (void)otherMouseUp:(NSEvent *)ev +{ + [self mouseEvent:ev button:MIDDLE_RELEASE]; +} +@end + +@implementation GameWindow +- (void)setupContentView +{ + NSRect frame; + int w, h; + + if (status) { + frame = [status frame]; + frame.origin.y = frame.size.height; + } else + frame.origin.y = 0; + frame.origin.x = 0; + + w = h = INT_MAX; + midend_size(me, &w, &h, FALSE); + frame.size.width = w; + frame.size.height = h; + fe.w = w; + fe.h = h; + + fe.image = [[NSImage alloc] initWithSize:frame.size]; + fe.view = [[MyImageView alloc] initWithFrame:frame]; + [fe.view setImage:fe.image]; + [fe.view setWindow:self]; + + midend_redraw(me); + + [[self contentView] addSubview:fe.view]; +} +- (id)initWithGame:(const game *)g +{ + NSRect rect = { {0,0}, {0,0} }, rect2; + int w, h; + + ourgame = g; + + fe.window = self; + + me = midend_new(&fe, ourgame, &osx_drawing, &fe); + /* + * If we ever need to open a fresh window using a provided game + * ID, I think the right thing is to move most of this method + * into a new initWithGame:gameID: method, and have + * initWithGame: simply call that one and pass it NULL. + */ + midend_new_game(me); + w = h = INT_MAX; + midend_size(me, &w, &h, FALSE); + rect.size.width = w; + rect.size.height = h; + fe.w = w; + fe.h = h; + + /* + * Create the status bar, which will just be an NSTextField. + */ + if (midend_wants_statusbar(me)) { + status = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,100,50)]; + [status setEditable:NO]; + [status setSelectable:NO]; + [status setBordered:YES]; + [status setBezeled:YES]; + [status setBezelStyle:NSTextFieldSquareBezel]; + [status setDrawsBackground:YES]; + [[status cell] setTitle:@DEFAULT_STATUSBAR_TEXT]; + [status sizeToFit]; + rect2 = [status frame]; + rect.size.height += rect2.size.height; + rect2.size.width = rect.size.width; + rect2.origin.x = rect2.origin.y = 0; + [status setFrame:rect2]; + } else + status = nil; + + self = [super initWithContentRect:rect + styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | + NSClosableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [self setTitle:[NSString stringWithUTF8String:ourgame->name]]; + + { + float *colours; + int i, ncolours; + + colours = midend_colours(me, &ncolours); + fe.ncolours = ncolours; + fe.colours = snewn(ncolours, NSColor *); + + for (i = 0; i < ncolours; i++) { + fe.colours[i] = [[NSColor colorWithDeviceRed:colours[i*3] + green:colours[i*3+1] blue:colours[i*3+2] + alpha:1.0] retain]; + } + } + + [self setupContentView]; + if (status) + [[self contentView] addSubview:status]; + [self setIgnoresMouseEvents:NO]; + + [self center]; /* :-) */ + + return self; +} + +- (void)dealloc +{ + int i; + for (i = 0; i < fe.ncolours; i++) { + [fe.colours[i] release]; + } + sfree(fe.colours); + midend_free(me); + [super dealloc]; +} + +- (void)processButton:(int)b x:(int)x y:(int)y +{ + if (!midend_process_key(me, x, fe.h - 1 - y, b)) + [self close]; +} + +- (void)processKey:(int)b +{ + if (!midend_process_key(me, -1, -1, b)) + [self close]; +} + +- (void)keyDown:(NSEvent *)ev +{ + NSString *s = [ev characters]; + int i, n = [s length]; + + for (i = 0; i < n; i++) { + int c = [s characterAtIndex:i]; + + /* + * ASCII gets passed straight to midend_process_key. + * Anything above that has to be translated to our own + * function key codes. + */ + if (c >= 0x80) { + int mods = FALSE; + switch (c) { + case NSUpArrowFunctionKey: + c = CURSOR_UP; + mods = TRUE; + break; + case NSDownArrowFunctionKey: + c = CURSOR_DOWN; + mods = TRUE; + break; + case NSLeftArrowFunctionKey: + c = CURSOR_LEFT; + mods = TRUE; + break; + case NSRightArrowFunctionKey: + c = CURSOR_RIGHT; + mods = TRUE; + break; + default: + continue; + } + + if (mods) { + if ([ev modifierFlags] & NSShiftKeyMask) + c |= MOD_SHFT; + if ([ev modifierFlags] & NSControlKeyMask) + c |= MOD_CTRL; + } + } + + if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) + c |= MOD_NUM_KEYPAD; + + [self processKey:c]; + } +} + +- (void)activateTimer +{ + if (timer != nil) + return; + + timer = [NSTimer scheduledTimerWithTimeInterval:0.02 + target:self selector:@selector(timerTick:) + userInfo:nil repeats:YES]; + gettimeofday(&last_time, NULL); +} + +- (void)deactivateTimer +{ + if (timer == nil) + return; + + [timer invalidate]; + timer = nil; +} + +- (void)timerTick:(id)sender +{ + struct timeval now; + float elapsed; + gettimeofday(&now, NULL); + elapsed = ((now.tv_usec - last_time.tv_usec) * 0.000001F + + (now.tv_sec - last_time.tv_sec)); + midend_timer(me, elapsed); + last_time = now; +} + +- (void)showError:(char *)message +{ + NSAlert *alert; + + alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:@"Bah"]; + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + [alert beginSheetModalForWindow:self modalDelegate:nil + didEndSelector:NULL contextInfo:nil]; +} + +- (void)newGame:(id)sender +{ + [self processKey:'n']; +} +- (void)restartGame:(id)sender +{ + midend_restart_game(me); +} +- (void)saveGame:(id)sender +{ + NSSavePanel *sp = [NSSavePanel savePanel]; + + if ([sp runModal] == NSFileHandlingPanelOKButton) { + const char *name = [[sp filename] UTF8String]; + + FILE *fp = fopen(name, "w"); + + if (!fp) { + [self showError:"Unable to open save file"]; + return; + } + + midend_serialise(me, savefile_write, fp); + + fclose(fp); + } +} +- (void)loadSavedGame:(id)sender +{ + NSOpenPanel *op = [NSOpenPanel openPanel]; + + [op setAllowsMultipleSelection:NO]; + + if ([op runModalForTypes:nil] == NSOKButton) { + /* + * This used to be + * + * [[[op filenames] objectAtIndex:0] cString] + * + * but the plain cString method became deprecated and Xcode 7 + * started complaining about it. Since OS X 10.9 we can + * apparently use the more modern API + * + * [[[op URLs] objectAtIndex:0] fileSystemRepresentation] + * + * but the alternative below still compiles with Xcode 7 and + * is a bit more backwards compatible, so I'll try it for the + * moment. + */ + const char *name = [[[op filenames] objectAtIndex:0] + cStringUsingEncoding: + [NSString defaultCStringEncoding]]; + char *err; + + FILE *fp = fopen(name, "r"); + + if (!fp) { + [self showError:"Unable to open saved game file"]; + return; + } + + err = midend_deserialise(me, savefile_read, fp); + + fclose(fp); + + if (err) { + [self showError:err]; + return; + } + + [self resizeForNewGameParams]; + [self updateTypeMenuTick]; + } +} +- (void)undoMove:(id)sender +{ + [self processKey:'u']; +} +- (void)redoMove:(id)sender +{ + [self processKey:'r'&0x1F]; +} + +- (void)copy:(id)sender +{ + char *text; + + if ((text = midend_text_format(me)) != NULL) { + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + NSArray *a = [NSArray arrayWithObject:NSStringPboardType]; + [pb declareTypes:a owner:nil]; + [pb setString:[NSString stringWithUTF8String:text] + forType:NSStringPboardType]; + } else + NSBeep(); +} + +- (void)solveGame:(id)sender +{ + char *msg; + + msg = midend_solve(me); + + if (msg) + [self showError:msg]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)item +{ + if ([item action] == @selector(copy:)) + return (ourgame->can_format_as_text_ever && + midend_can_format_as_text_now(me) ? YES : NO); + else if ([item action] == @selector(solveGame:)) + return (ourgame->can_solve ? YES : NO); + else + return [super validateMenuItem:item]; +} + +- (void)clearTypeMenu +{ + while ([typemenu numberOfItems] > 1) + [typemenu removeItemAtIndex:0]; + [[typemenu itemAtIndex:0] setState:NSOffState]; +} + +- (void)updateTypeMenuTick +{ + int i, total, n; + + total = [typemenu numberOfItems]; + n = midend_which_preset(me); + if (n < 0) + n = total - 1; /* that's always where "Custom" lives */ + for (i = 0; i < total; i++) + [[typemenu itemAtIndex:i] setState:(i == n ? NSOnState : NSOffState)]; +} + +- (void)becomeKeyWindow +{ + int n; + + [self clearTypeMenu]; + + [super becomeKeyWindow]; + + n = midend_num_presets(me); + + if (n > 0) { + [typemenu insertItem:[NSMenuItem separatorItem] atIndex:0]; + while (n--) { + char *name; + game_params *params; + DataMenuItem *item; + + midend_fetch_preset(me, n, &name, ¶ms); + + item = [[[DataMenuItem alloc] + initWithTitle:[NSString stringWithUTF8String:name] + action:NULL keyEquivalent:@""] + autorelease]; + + [item setEnabled:YES]; + [item setTarget:self]; + [item setAction:@selector(presetGame:)]; + [item setPayload:params]; + + [typemenu insertItem:item atIndex:0]; + } + } + + [self updateTypeMenuTick]; +} + +- (void)resignKeyWindow +{ + [self clearTypeMenu]; + [super resignKeyWindow]; +} + +- (void)close +{ + [self clearTypeMenu]; + [super close]; +} + +- (void)resizeForNewGameParams +{ + NSSize size = {0,0}; + int w, h; + + w = h = INT_MAX; + midend_size(me, &w, &h, FALSE); + size.width = w; + size.height = h; + fe.w = w; + fe.h = h; + + if (status) { + NSRect frame = [status frame]; + size.height += frame.size.height; + frame.size.width = size.width; + [status setFrame:frame]; + } + +#ifndef GNUSTEP + NSDisableScreenUpdates(); +#endif + [self setContentSize:size]; + [self setupContentView]; +#ifndef GNUSTEP + NSEnableScreenUpdates(); +#endif +} + +- (void)presetGame:(id)sender +{ + game_params *params = [sender getPayload]; + + midend_set_params(me, params); + midend_new_game(me); + + [self resizeForNewGameParams]; + [self updateTypeMenuTick]; +} + +- (void)startConfigureSheet:(int)which +{ + NSButton *ok, *cancel; + int actw, acth, leftw, rightw, totalw, h, thish, y; + int k; + NSRect rect, tmprect; + const int SPACING = 16; + char *title; + config_item *i; + int cfg_controlsize; + NSTextField *tf; + NSButton *b; + NSPopUpButton *pb; + + assert(sheet == NULL); + + /* + * Every control we create here is going to have this size + * until we tell it to calculate a better one. + */ + tmprect = NSMakeRect(0, 0, 100, 50); + + /* + * Set up OK and Cancel buttons. (Actually, MacOS doesn't seem + * to be fond of generic OK and Cancel wording, so I'm going to + * rename them to something nicer.) + */ + actw = acth = 0; + + cancel = [[NSButton alloc] initWithFrame:tmprect]; + [cancel setBezelStyle:NSRoundedBezelStyle]; + [cancel setTitle:@"Abandon"]; + [cancel setTarget:self]; + [cancel setKeyEquivalent:@"\033"]; + [cancel setAction:@selector(sheetCancelButton:)]; + [cancel sizeToFit]; + rect = [cancel frame]; + if (actw < rect.size.width) actw = rect.size.width; + if (acth < rect.size.height) acth = rect.size.height; + + ok = [[NSButton alloc] initWithFrame:tmprect]; + [ok setBezelStyle:NSRoundedBezelStyle]; + [ok setTitle:@"Accept"]; + [ok setTarget:self]; + [ok setKeyEquivalent:@"\r"]; + [ok setAction:@selector(sheetOKButton:)]; + [ok sizeToFit]; + rect = [ok frame]; + if (actw < rect.size.width) actw = rect.size.width; + if (acth < rect.size.height) acth = rect.size.height; + + totalw = SPACING + 2 * actw; + h = 2 * SPACING + acth; + + /* + * Now fetch the midend config data and go through it creating + * controls. + */ + cfg = midend_get_config(me, which, &title); + sfree(title); /* FIXME: should we use this somehow? */ + cfg_which = which; + + cfg_ncontrols = cfg_controlsize = 0; + cfg_controls = NULL; + leftw = rightw = 0; + for (i = cfg; i->type != C_END; i++) { + if (cfg_controlsize < cfg_ncontrols + 5) { + cfg_controlsize = cfg_ncontrols + 32; + cfg_controls = sresize(cfg_controls, cfg_controlsize, NSView *); + } + + thish = 0; + + switch (i->type) { + case C_STRING: + /* + * Two NSTextFields, one being a label and the other + * being an edit box. + */ + + tf = [[NSTextField alloc] initWithFrame:tmprect]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; + [tf sizeToFit]; + rect = [tf frame]; + if (thish < rect.size.height + 1) thish = rect.size.height + 1; + if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; + cfg_controls[cfg_ncontrols++] = tf; + + tf = [[NSTextField alloc] initWithFrame:tmprect]; + [tf setEditable:YES]; + [tf setSelectable:YES]; + [tf setBordered:YES]; + [[tf cell] setTitle:[NSString stringWithUTF8String:i->sval]]; + [tf sizeToFit]; + rect = [tf frame]; + /* + * We impose a minimum and maximum width on editable + * NSTextFields. If we allow them to size themselves to + * the contents of the text within them, then they will + * look very silly if that text is only one or two + * characters, and equally silly if it's an absolutely + * enormous Rectangles or Pattern game ID! + */ + if (rect.size.width < 75) rect.size.width = 75; + if (rect.size.width > 400) rect.size.width = 400; + + if (thish < rect.size.height + 1) thish = rect.size.height + 1; + if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; + cfg_controls[cfg_ncontrols++] = tf; + break; + + case C_BOOLEAN: + /* + * A checkbox is an NSButton with a type of + * NSSwitchButton. + */ + b = [[NSButton alloc] initWithFrame:tmprect]; + [b setBezelStyle:NSRoundedBezelStyle]; + [b setButtonType:NSSwitchButton]; + [b setTitle:[NSString stringWithUTF8String:i->name]]; + [b sizeToFit]; + [b setState:(i->ival ? NSOnState : NSOffState)]; + rect = [b frame]; + if (totalw < rect.size.width + 1) totalw = rect.size.width + 1; + if (thish < rect.size.height + 1) thish = rect.size.height + 1; + cfg_controls[cfg_ncontrols++] = b; + break; + + case C_CHOICES: + /* + * A pop-up menu control is an NSPopUpButton, which + * takes an embedded NSMenu. We also need an + * NSTextField to act as a label. + */ + + tf = [[NSTextField alloc] initWithFrame:tmprect]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; + [tf sizeToFit]; + rect = [tf frame]; + if (thish < rect.size.height + 1) thish = rect.size.height + 1; + if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; + cfg_controls[cfg_ncontrols++] = tf; + + pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO]; + [pb setBezelStyle:NSRoundedBezelStyle]; + { + char c, *p; + + p = i->sval; + c = *p++; + while (*p) { + char *q, *copy; + + q = p; + while (*p && *p != c) p++; + + copy = snewn((p-q) + 1, char); + memcpy(copy, q, p-q); + copy[p-q] = '\0'; + [pb addItemWithTitle:[NSString stringWithUTF8String:copy]]; + sfree(copy); + + if (*p) p++; + } + } + [pb selectItemAtIndex:i->ival]; + [pb sizeToFit]; + + rect = [pb frame]; + if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; + if (thish < rect.size.height + 1) thish = rect.size.height + 1; + cfg_controls[cfg_ncontrols++] = pb; + break; + } + + h += SPACING + thish; + } + + if (totalw < leftw + SPACING + rightw) + totalw = leftw + SPACING + rightw; + if (totalw > leftw + SPACING + rightw) { + int excess = totalw - (leftw + SPACING + rightw); + int leftexcess = leftw * excess / (leftw + rightw); + int rightexcess = excess - leftexcess; + leftw += leftexcess; + rightw += rightexcess; + } + + /* + * Now go through the list again, setting the final position + * for each control. + */ + k = 0; + y = h; + for (i = cfg; i->type != C_END; i++) { + y -= SPACING; + thish = 0; + switch (i->type) { + case C_STRING: + case C_CHOICES: + /* + * These two are treated identically, since both expect + * a control on the left and another on the right. + */ + rect = [cfg_controls[k] frame]; + if (thish < rect.size.height + 1) + thish = rect.size.height + 1; + rect = [cfg_controls[k+1] frame]; + if (thish < rect.size.height + 1) + thish = rect.size.height + 1; + rect = [cfg_controls[k] frame]; + rect.origin.y = y - thish/2 - rect.size.height/2; + rect.origin.x = SPACING; + rect.size.width = leftw; + [cfg_controls[k] setFrame:rect]; + rect = [cfg_controls[k+1] frame]; + rect.origin.y = y - thish/2 - rect.size.height/2; + rect.origin.x = 2 * SPACING + leftw; + rect.size.width = rightw; + [cfg_controls[k+1] setFrame:rect]; + k += 2; + break; + + case C_BOOLEAN: + rect = [cfg_controls[k] frame]; + if (thish < rect.size.height + 1) + thish = rect.size.height + 1; + rect.origin.y = y - thish/2 - rect.size.height/2; + rect.origin.x = SPACING; + rect.size.width = totalw; + [cfg_controls[k] setFrame:rect]; + k++; + break; + } + y -= thish; + } + + assert(k == cfg_ncontrols); + + [cancel setFrame:NSMakeRect(SPACING+totalw/4-actw/2, SPACING, actw, acth)]; + [ok setFrame:NSMakeRect(SPACING+3*totalw/4-actw/2, SPACING, actw, acth)]; + + sheet = [[NSWindow alloc] + initWithContentRect:NSMakeRect(0,0,totalw + 2*SPACING,h) + styleMask:NSTitledWindowMask | NSClosableWindowMask + backing:NSBackingStoreBuffered + defer:YES]; + + [[sheet contentView] addSubview:cancel]; + [[sheet contentView] addSubview:ok]; + + for (k = 0; k < cfg_ncontrols; k++) + [[sheet contentView] addSubview:cfg_controls[k]]; + + [app beginSheet:sheet modalForWindow:self + modalDelegate:nil didEndSelector:NULL contextInfo:nil]; +} + +- (void)specificGame:(id)sender +{ + [self startConfigureSheet:CFG_DESC]; +} + +- (void)specificRandomGame:(id)sender +{ + [self startConfigureSheet:CFG_SEED]; +} + +- (void)customGameType:(id)sender +{ + [self startConfigureSheet:CFG_SETTINGS]; +} + +- (void)sheetEndWithStatus:(BOOL)update +{ + assert(sheet != NULL); + [app endSheet:sheet]; + [sheet orderOut:self]; + sheet = NULL; + if (update) { + int k; + config_item *i; + char *error; + + k = 0; + for (i = cfg; i->type != C_END; i++) { + switch (i->type) { + case C_STRING: + sfree(i->sval); + i->sval = dupstr([[[(id)cfg_controls[k+1] cell] + title] UTF8String]); + k += 2; + break; + case C_BOOLEAN: + i->ival = [(id)cfg_controls[k] state] == NSOnState; + k++; + break; + case C_CHOICES: + i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem]; + k += 2; + break; + } + } + + error = midend_set_config(me, cfg_which, cfg); + if (error) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:@"Bah"]; + [alert setInformativeText:[NSString stringWithUTF8String:error]]; + [alert beginSheetModalForWindow:self modalDelegate:nil + didEndSelector:NULL contextInfo:nil]; + } else { + midend_new_game(me); + [self resizeForNewGameParams]; + [self updateTypeMenuTick]; + } + } + sfree(cfg_controls); + cfg_controls = NULL; +} +- (void)sheetOKButton:(id)sender +{ + [self sheetEndWithStatus:YES]; +} +- (void)sheetCancelButton:(id)sender +{ + [self sheetEndWithStatus:NO]; +} + +- (void)setStatusLine:(char *)text +{ + [[status cell] setTitle:[NSString stringWithUTF8String:text]]; +} + +@end + +/* + * Drawing routines called by the midend. + */ +static void osx_draw_polygon(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + NSBezierPath *path = [NSBezierPath bezierPath]; + int i; + + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + + for (i = 0; i < npoints; i++) { + NSPoint p = { coords[i*2] + 0.5, fe->h - coords[i*2+1] - 0.5 }; + if (i == 0) + [path moveToPoint:p]; + else + [path lineToPoint:p]; + } + + [path closePath]; + + if (fillcolour >= 0) { + assert(fillcolour >= 0 && fillcolour < fe->ncolours); + [fe->colours[fillcolour] set]; + [path fill]; + } + + assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); + [fe->colours[outlinecolour] set]; + [path stroke]; +} +static void osx_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + NSBezierPath *path = [NSBezierPath bezierPath]; + + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + + [path appendBezierPathWithArcWithCenter:NSMakePoint(cx+0.5, fe->h-cy-0.5) + radius:radius startAngle:0.0 endAngle:360.0]; + + [path closePath]; + + if (fillcolour >= 0) { + assert(fillcolour >= 0 && fillcolour < fe->ncolours); + [fe->colours[fillcolour] set]; + [path fill]; + } + + assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); + [fe->colours[outlinecolour] set]; + [path stroke]; +} +static void osx_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) +{ + frontend *fe = (frontend *)handle; + NSBezierPath *path = [NSBezierPath bezierPath]; + NSPoint p1 = { x1 + 0.5, fe->h - y1 - 0.5 }; + NSPoint p2 = { x2 + 0.5, fe->h - y2 - 0.5 }; + + [[NSGraphicsContext currentContext] setShouldAntialias:NO]; + + assert(colour >= 0 && colour < fe->ncolours); + [fe->colours[colour] set]; + + [path moveToPoint:p1]; + [path lineToPoint:p2]; + [path stroke]; + NSRectFill(NSMakeRect(x1, fe->h-y1-1, 1, 1)); + NSRectFill(NSMakeRect(x2, fe->h-y2-1, 1, 1)); +} + +static void osx_draw_thick_line( + void *handle, float thickness, + float x1, float y1, + float x2, float y2, + int colour) +{ + frontend *fe = (frontend *)handle; + NSBezierPath *path = [NSBezierPath bezierPath]; + + assert(colour >= 0 && colour < fe->ncolours); + [fe->colours[colour] set]; + [[NSGraphicsContext currentContext] setShouldAntialias: YES]; + [path setLineWidth: thickness]; + [path setLineCapStyle: NSButtLineCapStyle]; + [path moveToPoint: NSMakePoint(x1, fe->h-y1)]; + [path lineToPoint: NSMakePoint(x2, fe->h-y2)]; + [path stroke]; +} + +static void osx_draw_rect(void *handle, int x, int y, int w, int h, int colour) +{ + frontend *fe = (frontend *)handle; + NSRect r = { {x, fe->h - y - h}, {w,h} }; + + [[NSGraphicsContext currentContext] setShouldAntialias:NO]; + + assert(colour >= 0 && colour < fe->ncolours); + [fe->colours[colour] set]; + + NSRectFill(r); +} +static void osx_draw_text(void *handle, int x, int y, int fonttype, + int fontsize, int align, int colour, char *text) +{ + frontend *fe = (frontend *)handle; + NSString *string = [NSString stringWithUTF8String:text]; + NSDictionary *attr; + NSFont *font; + NSSize size; + NSPoint point; + + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + + assert(colour >= 0 && colour < fe->ncolours); + + if (fonttype == FONT_FIXED) + font = [NSFont userFixedPitchFontOfSize:fontsize]; + else + font = [NSFont userFontOfSize:fontsize]; + + attr = [NSDictionary dictionaryWithObjectsAndKeys: + fe->colours[colour], NSForegroundColorAttributeName, + font, NSFontAttributeName, nil]; + + point.x = x; + point.y = fe->h - y; + + size = [string sizeWithAttributes:attr]; + if (align & ALIGN_HRIGHT) + point.x -= size.width; + else if (align & ALIGN_HCENTRE) + point.x -= size.width / 2; + if (align & ALIGN_VCENTRE) + point.y -= size.height / 2; + + [string drawAtPoint:point withAttributes:attr]; +} +static char *osx_text_fallback(void *handle, const char *const *strings, + int nstrings) +{ + /* + * We assume OS X can cope with any UTF-8 likely to be emitted + * by a puzzle. + */ + return dupstr(strings[0]); +} +struct blitter { + int w, h; + int x, y; + NSImage *img; +}; +static blitter *osx_blitter_new(void *handle, int w, int h) +{ + blitter *bl = snew(blitter); + bl->x = bl->y = -1; + bl->w = w; + bl->h = h; + bl->img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)]; + return bl; +} +static void osx_blitter_free(void *handle, blitter *bl) +{ + [bl->img release]; + sfree(bl); +} +static void osx_blitter_save(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + int sx, sy, sX, sY, dx, dy, dX, dY; + [fe->image unlockFocus]; + [bl->img lockFocus]; + + /* + * Find the intersection of the source and destination rectangles, + * so as to avoid trying to copy from outside the source image, + * which GNUstep dislikes. + * + * Lower-case x,y coordinates are bottom left box corners; + * upper-case X,Y are the top right. + */ + sx = x; sy = fe->h - y - bl->h; + sX = sx + bl->w; sY = sy + bl->h; + dx = dy = 0; + dX = bl->w; dY = bl->h; + if (sx < 0) { + dx += -sx; + sx = 0; + } + if (sy < 0) { + dy += -sy; + sy = 0; + } + if (sX > fe->w) { + dX -= (sX - fe->w); + sX = fe->w; + } + if (sY > fe->h) { + dY -= (sY - fe->h); + sY = fe->h; + } + + [fe->image drawInRect:NSMakeRect(dx, dy, dX-dx, dY-dy) + fromRect:NSMakeRect(sx, sy, sX-sx, sY-sy) + operation:NSCompositeCopy fraction:1.0]; + [bl->img unlockFocus]; + [fe->image lockFocus]; + bl->x = x; + bl->y = y; +} +static void osx_blitter_load(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { + x = bl->x; + y = bl->y; + } + [bl->img drawInRect:NSMakeRect(x, fe->h - y - bl->h, bl->w, bl->h) + fromRect:NSMakeRect(0, 0, bl->w, bl->h) + operation:NSCompositeCopy fraction:1.0]; +} +static void osx_draw_update(void *handle, int x, int y, int w, int h) +{ + frontend *fe = (frontend *)handle; + [fe->view setNeedsDisplayInRect:NSMakeRect(x, fe->h - y - h, w, h)]; +} +static void osx_clip(void *handle, int x, int y, int w, int h) +{ + frontend *fe = (frontend *)handle; + NSRect r = { {x, fe->h - y - h}, {w, h} }; + + if (!fe->clipped) + [[NSGraphicsContext currentContext] saveGraphicsState]; + [NSBezierPath clipRect:r]; + fe->clipped = TRUE; +} +static void osx_unclip(void *handle) +{ + frontend *fe = (frontend *)handle; + if (fe->clipped) + [[NSGraphicsContext currentContext] restoreGraphicsState]; + fe->clipped = FALSE; +} +static void osx_start_draw(void *handle) +{ + frontend *fe = (frontend *)handle; + [fe->image lockFocus]; + fe->clipped = FALSE; +} +static void osx_end_draw(void *handle) +{ + frontend *fe = (frontend *)handle; + [fe->image unlockFocus]; +} +static void osx_status_bar(void *handle, char *text) +{ + frontend *fe = (frontend *)handle; + [fe->window setStatusLine:text]; +} + +const struct drawing_api osx_drawing = { + osx_draw_text, + osx_draw_rect, + osx_draw_line, + osx_draw_polygon, + osx_draw_circle, + osx_draw_update, + osx_clip, + osx_unclip, + osx_start_draw, + osx_end_draw, + osx_status_bar, + osx_blitter_new, + osx_blitter_free, + osx_blitter_save, + osx_blitter_load, + NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ + NULL, NULL, /* line_width, line_dotted */ + osx_text_fallback, + osx_draw_thick_line, +}; + +void deactivate_timer(frontend *fe) +{ + [fe->window deactivateTimer]; +} +void activate_timer(frontend *fe) +{ + [fe->window activateTimer]; +} + +/* ---------------------------------------------------------------------- + * AppController: the object which receives the messages from all + * menu selections that aren't standard OS X functions. + */ +@interface AppController : NSObject +{ +} +- (void)newGameWindow:(id)sender; +- (void)about:(id)sender; +@end + +@implementation AppController + +- (void)newGameWindow:(id)sender +{ + const game *g = [sender getPayload]; + id win; + + win = [[GameWindow alloc] initWithGame:g]; + [win makeKeyAndOrderFront:self]; +} + +- (void)about:(id)sender +{ + id win; + + win = [[AboutBox alloc] init]; + [win makeKeyAndOrderFront:self]; +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender +{ + NSMenu *menu = newmenu("Dock Menu"); + { + int i; + + for (i = 0; i < gamecount; i++) { + id item = + initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], + menu, gamelist[i]->name, "", self, + @selector(newGameWindow:)); + [item setPayload:(void *)gamelist[i]]; + } + } + return menu; +} + +@end + +/* ---------------------------------------------------------------------- + * Main program. Constructs the menus and runs the application. + */ +int main(int argc, char **argv) +{ + NSAutoreleasePool *pool; + NSMenu *menu; + AppController *controller; + NSImage *icon; + + pool = [[NSAutoreleasePool alloc] init]; + + icon = [NSImage imageNamed:@"NSApplicationIcon"]; + app = [NSApplication sharedApplication]; + [app setApplicationIconImage:icon]; + + controller = [[[AppController alloc] init] autorelease]; + [app setDelegate:controller]; + + [app setMainMenu: newmenu("Main Menu")]; + + menu = newsubmenu([app mainMenu], "Apple Menu"); + newitem(menu, "About Puzzles", "", NULL, @selector(about:)); + [menu addItem:[NSMenuItem separatorItem]]; + [app setServicesMenu:newsubmenu(menu, "Services")]; + [menu addItem:[NSMenuItem separatorItem]]; + newitem(menu, "Hide Puzzles", "h", app, @selector(hide:)); + newitem(menu, "Hide Others", "o-h", app, @selector(hideOtherApplications:)); + newitem(menu, "Show All", "", app, @selector(unhideAllApplications:)); + [menu addItem:[NSMenuItem separatorItem]]; + newitem(menu, "Quit", "q", app, @selector(terminate:)); + [app setAppleMenu: menu]; + + menu = newsubmenu([app mainMenu], "File"); + newitem(menu, "Open", "o", NULL, @selector(loadSavedGame:)); + newitem(menu, "Save As", "s", NULL, @selector(saveGame:)); + newitem(menu, "New Game", "n", NULL, @selector(newGame:)); + newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:)); + newitem(menu, "Specific Game", "", NULL, @selector(specificGame:)); + newitem(menu, "Specific Random Seed", "", NULL, + @selector(specificRandomGame:)); + [menu addItem:[NSMenuItem separatorItem]]; + { + NSMenu *submenu = newsubmenu(menu, "New Window"); + int i; + + for (i = 0; i < gamecount; i++) { + id item = + initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], + submenu, gamelist[i]->name, "", controller, + @selector(newGameWindow:)); + [item setPayload:(void *)gamelist[i]]; + } + } + [menu addItem:[NSMenuItem separatorItem]]; + newitem(menu, "Close", "w", NULL, @selector(performClose:)); + + menu = newsubmenu([app mainMenu], "Edit"); + newitem(menu, "Undo", "z", NULL, @selector(undoMove:)); + newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:)); + [menu addItem:[NSMenuItem separatorItem]]; + newitem(menu, "Cut", "x", NULL, @selector(cut:)); + newitem(menu, "Copy", "c", NULL, @selector(copy:)); + newitem(menu, "Paste", "v", NULL, @selector(paste:)); + [menu addItem:[NSMenuItem separatorItem]]; + newitem(menu, "Solve", "S-s", NULL, @selector(solveGame:)); + + menu = newsubmenu([app mainMenu], "Type"); + typemenu = menu; + newitem(menu, "Custom", "", NULL, @selector(customGameType:)); + + menu = newsubmenu([app mainMenu], "Window"); + [app setWindowsMenu: menu]; + newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); + + menu = newsubmenu([app mainMenu], "Help"); + newitem(menu, "Puzzles Help", "?", app, @selector(showHelp:)); + + [app run]; + [pool release]; + + return 0; +} diff --git a/apps/plugins/puzzles/padtoolbar.bmp b/apps/plugins/puzzles/padtoolbar.bmp new file mode 100644 index 0000000000000000000000000000000000000000..46c3e0dcaeef5a7ce078a18fb15bde10bd13d004 GIT binary patch literal 1198 zcmb_bu@S;B3>0_tUV{;+=~;(5WeV=H2{U9QmvrJKy>gDB;J{97S+ewg$@b;?IEuF$ z#uNG-y`Uc?7S3FUscVx6ewn5a7%`P~T_J5j{TEf6q2n_XhIvR6SpcT`{8Nm?sYnof zEMJH!CD^&3mLiy^hPq@P&ZoLw!l`I&aHypbK8Q>WlMgsR3v-L3ftryW4m}aq@UbIw zcwc3f9ZoWL7uEyY?{P>o>Qj=mha{iCVSUp0hC`aMS>WLQ8c7NbBLTj`(c%DG@tHG? zJCez#Mq#>LYKyZ!gU&N?a3sckyA=+=6tdizUVFtD02tQUCX1=Ri*fH_$a9}XUh6Mf AivR!s literal 0 HcmV?d00001 diff --git a/apps/plugins/puzzles/palisade.R b/apps/plugins/puzzles/palisade.R new file mode 100644 index 0000000000..de4bd83126 --- /dev/null +++ b/apps/plugins/puzzles/palisade.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +PALISADE_EXTRA = divvy dsf + +palisade : [X] GTK COMMON palisade PALISADE_EXTRA palisade-icon|no-icon + +palisade : [G] WINDOWS COMMON palisade PALISADE_EXTRA palisade.res|noicon.res + +ALL += palisade[COMBINED] PALISADE_EXTRA + +!begin am gtk +GAMES += palisade +!end + +!begin >list.c + A(palisade) \ +!end + +!begin >gamedesc.txt +palisade:palisade.exe:Palisade:Grid-division puzzle:Divide the grid into equal-sized areas in accordance with the clues. +!end diff --git a/apps/plugins/puzzles/palisade.c b/apps/plugins/puzzles/palisade.c new file mode 100644 index 0000000000..c5f1b62022 --- /dev/null +++ b/apps/plugins/puzzles/palisade.c @@ -0,0 +1,1383 @@ +/* -*- indent-tabs-mode: nil; tab-width: 1000 -*- */ + +/* + * palisade.c: Nikoli's `Five Cells' puzzle. + * + * See http://nikoli.co.jp/en/puzzles/five_cells.html + */ + +/* TODO: + * + * - better solver: implement the sketched-out deductions + * + * - improve the victory flash? + * - the LINE_NOs look ugly against COL_FLASH. + * - white-blink the edges (instead), a la loopy? + */ + +#include "rbassert.h" +#include +#include +#include +#include +#include + +#include "puzzles.h" + +#define setmem(ptr, byte, len) memset((ptr), (byte), (len) * sizeof (ptr)[0]) +#define scopy(dst, src, len) memcpy((dst), (src), (len) * sizeof (dst)[0]) +#define dupmem(p, n) memcpy(smalloc(n * sizeof (*p)), p, n * sizeof (*p)) +#define snewa(ptr, len) (ptr) = smalloc((len) * sizeof (*ptr)) +#define clone(ptr) (dupmem((ptr), 1)) + +static char *string(int n, const char *fmt, ...) +{ + va_list va; + char *ret; + int m; + va_start(va, fmt); + m = vsprintf(snewa(ret, n + 1), fmt, va); + va_end(va); + if (m > n) fatal("memory corruption"); + return ret; +} + +struct game_params { + int w, h, k; +}; + +typedef char clue; +typedef unsigned char borderflag; + +typedef struct shared_state { + game_params params; + clue *clues; + int refcount; +} shared_state; + +struct game_state { + shared_state *shared; + borderflag *borders; /* length w*h */ + + unsigned int completed: 1; + unsigned int cheated: 1; +}; + +#define DEFAULT_PRESET 0 +static struct game_params presets[] = { + {5, 5, 5}, {8, 6, 6}, {10, 8, 8}, {15, 12, 10} + /* I definitely want 5x5n5 since that gives "Five Cells" its name. + * But how about the others? By which criteria do I choose? */ +}; + +static game_params *default_params(void) +{ + return clone(&presets[DEFAULT_PRESET]); +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + if (i < 0 || i >= lenof(presets)) return FALSE; + + *params = clone(&presets[i]); + *name = string(60, "%d x %d, regions of size %d", + presets[i].w, presets[i].h, presets[i].k); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + return clone(params); +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = params->k = atoi(string); + while (*string && isdigit((unsigned char)*string)) ++string; + if (*string == 'x') { + params->h = atoi(++string); + while (*string && isdigit((unsigned char)*string)) ++string; + } + if (*string == 'n') params->k = atoi(++string); +} + +static char *encode_params(const game_params *params, int full) +{ + return string(40, "%dx%dn%d", params->w, params->h, params->k); +} + +#define CONFIG(i, nm, ty, iv, sv) \ + (ret[i].name = nm, ret[i].type = ty, ret[i].ival = iv, ret[i].sval = sv) + +static config_item *game_configure(const game_params *params) +{ + config_item *ret = snewn(4, config_item); + + CONFIG(0, "Width", C_STRING, 0, string(20, "%d", params->w)); + CONFIG(1, "Height", C_STRING, 0, string(20, "%d", params->h)); + CONFIG(2, "Region size", C_STRING, 0, string(20, "%d", params->k)); + CONFIG(3, NULL, C_END, 0, NULL); + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *params = snew(game_params); + + params->w = atoi(cfg[0].sval); + params->h = atoi(cfg[1].sval); + params->k = atoi(cfg[2].sval); + + return params; +} + +/* +---+ << The one possible domino (up to symmetry). +---+---+ + * | 3 | | 3 | 3 | + * | | If two dominos are adjacent as depicted here >> +---+---+ + * | 3 | then it's ambiguous whether the edge between | 3 | 3 | + * +---+ the dominos is horizontal or vertical. +---+---+ + */ + +static char *validate_params(const game_params *params, int full) +{ + int w = params->w, h = params->h, k = params->k, wh = w * h; + + if (k < 1) return "Region size must be at least one"; + if (w < 1) return "Width must be at least one"; + if (h < 1) return "Height must be at least one"; + if (wh % k) return "Region size must divide grid area"; + + if (!full) return NULL; /* succeed partial validation */ + + /* MAYBE FIXME: we (just?) don't have the UI for winning these. */ + if (k == wh) return "Region size must be less than the grid area"; + assert (k < wh); /* or wh % k != 0 */ + + if (k == 2 && w != 1 && h != 1) + return "Region size can't be two unless width or height is one"; + + return NULL; /* succeed full validation */ +} + +/* --- Solver ------------------------------------------------------- */ + +/* the solver may write at will to these arrays, but shouldn't free them */ +/* it's up to the client to dup/free as needed */ +typedef struct solver_ctx { + const game_params *params; /* also in shared_state */ + clue *clues; /* also in shared_state */ + borderflag *borders; /* also in game_state */ + int *dsf; /* particular to the solver */ +} solver_ctx; + +/* Deductions: + * + * - If two adjacent clues do not have a border between them, this + * gives a lower limit on the size of their region (which is also an + * upper limit if both clues are 3). Rule out any non-border which + * would make its region either too large or too small. + * + * - If a clue, k, is adjacent to k borders or (4 - k) non-borders, + * the remaining edges incident to the clue are readily decided. + * + * - If a region has only one other region (e.g. square) to grow into + * and it's not of full size yet, grow it into that one region. + * + * - If two regions are adjacent and their combined size would be too + * large, put an edge between them. + * + * - If a border is adjacent to two non-borders, its last vertex-mate + * must also be a border. If a maybe-border is adjacent to three + * nonborders, the maybe-border is a non-border. + * + * - If a clue square is adjacent to several squares belonging to the + * same region, and enabling (disabling) those borders would violate + * the clue, those borders must be disabled (enabled). + * + * - If there's a path crossing only non-borders between two squares, + * the maybe-border between them is a non-border. + * (This is implicitly computed in the dsf representation) + */ + +/* TODO deductions: + * + * If a vertex is adjacent to a LINE_YES and (4-3)*LINE_NO, at least + * one of the last two edges are LINE_YES. If they're adjacent to a + * 1, then the other two edges incident to that 1 are LINE_NO. + * + * For each square: set all as unknown, then for each k-omino and each + * way of placing it on that square, if that way is consistent with + * the board, mark its edges and interior as possible LINE_YES and + * LINE_NO, respectively. When all k-ominos are through, see what + * isn't possible and remove those impossibilities from the board. + * (Sounds pretty nasty for k > 4 or so.) + * + * A black-bordered subregion must have a size divisible by k. So, + * draw a graph with one node per dsf component and edges between + * those dsf components which have adjacent squares. Identify cut + * vertices and edges. If a cut-vertex-delimited component contains a + * number of squares not divisible by k, cut vertex not included, then + * the cut vertex must belong to the component. If it has exactly one + * edge _out_ of the component, the line(s) corresponding to that edge + * are all LINE_YES (i.e. a BORDER()). + * (This sounds complicated, but visually it is rather easy.) + * + * [Look at loopy and see how the at-least/-most k out of m edges + * thing is done. See how it is propagated across multiple squares.] + */ + +#define EMPTY (~0) + +#define BIT(i) (1 << (i)) +#define BORDER(i) BIT(i) +#define BORDER_U BORDER(0) +#define BORDER_R BORDER(1) +#define BORDER_D BORDER(2) +#define BORDER_L BORDER(3) +#define FLIP(i) ((i) ^ 2) +#define BORDER_MASK (BORDER_U|BORDER_R|BORDER_D|BORDER_L) +#define DISABLED(border) ((border) << 4) +#define UNDISABLED(border) ((border) >> 4) + +static const int dx[4] = { 0, +1, 0, -1}; +static const int dy[4] = {-1, 0, +1, 0}; +static const int bitcount[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; +/* bitcount[x & BORDER_MASK] == number of enabled borders */ + +#define COMPUTE_J (-1) + +static void connect(solver_ctx *ctx, int i, int j) +{ + dsf_merge(ctx->dsf, i, j); +} + +static int connected(solver_ctx *ctx, int i, int j, int dir) +{ + if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; + return dsf_canonify(ctx->dsf, i) == dsf_canonify(ctx->dsf, j); +} + +static void disconnect(solver_ctx *ctx, int i, int j, int dir) +{ + if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir]; + ctx->borders[i] |= BORDER(dir); + ctx->borders[j] |= BORDER(FLIP(dir)); +} + +static int disconnected(solver_ctx *ctx, int i, int j, int dir) +{ + assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]); + return ctx->borders[i] & BORDER(dir); +} + +static int maybe(solver_ctx *ctx, int i, int j, int dir) +{ + assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]); + return !disconnected(ctx, i, j, dir) && !connected(ctx, i, j, dir); + /* the ordering is important: disconnected works for invalid + * squares (i.e. out of bounds), connected doesn't. */ +} + +static void solver_connected_clues_versus_region_size(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; + + /* If i is connected to j and i has borders with p of the + * remaining three squares and j with q of the remaining three + * squares, then the region has size at least 1+(3-p) + 1+(3-q). + * If p = q = 3 then the region has size exactly 2. */ + + for (i = 0; i < wh; ++i) { + if (ctx->clues[i] == EMPTY) continue; + for (dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (disconnected(ctx, i, j, dir)) continue; + if (ctx->clues[j] == EMPTY) continue; + if ((8 - ctx->clues[i] - ctx->clues[j] > ctx->params->k) || + (ctx->clues[i] == 3 && ctx->clues[j] == 3 && + ctx->params->k != 2)) + { + disconnect(ctx, i, j, dir); + /* changed = TRUE, but this is a one-shot... */ + } + } + } +} + +static int solver_number_exhausted(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir, off; + int changed = FALSE; + + for (i = 0; i < wh; ++i) { + if (ctx->clues[i] == EMPTY) continue; + + if (bitcount[(ctx->borders[i] & BORDER_MASK)] == ctx->clues[i]) { + for (dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (!maybe(ctx, i, j, dir)) continue; + connect(ctx, i, j); + changed = TRUE; + } + continue; + } + + for (off = dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (!disconnected(ctx, i, j, dir) && connected(ctx, i, j, dir)) + ++off; /* ^^^ bounds checking before ^^^^^ */ + } + + if (ctx->clues[i] == 4 - off) + for (dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (!maybe(ctx, i, j, dir)) continue; + disconnect(ctx, i, j, dir); + changed = TRUE; + } + } + + return changed; +} + +static int solver_not_too_big(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; + int changed = FALSE; + + for (i = 0; i < wh; ++i) { + int size = dsf_size(ctx->dsf, i); + for (dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (!maybe(ctx, i, j, dir)) continue; + if (size + dsf_size(ctx->dsf, j) <= ctx->params->k) continue; + disconnect(ctx, i, j, dir); + changed = TRUE; + } + } + + return changed; +} + +static int solver_not_too_small(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir; + int *outs, k = ctx->params->k, ci, changed = FALSE; + + snewa(outs, wh); + setmem(outs, -1, wh); + + for (i = 0; i < wh; ++i) { + ci = dsf_canonify(ctx->dsf, i); + if (dsf_size(ctx->dsf, ci) == k) continue; + for (dir = 0; dir < 4; ++dir) { + int j = i + dx[dir] + w*dy[dir]; + if (!maybe(ctx, i, j, dir)) continue; + if (outs[ci] == -1) outs[ci] = dsf_canonify(ctx->dsf, j); + else if (outs[ci] != dsf_canonify(ctx->dsf, j)) outs[ci] = -2; + } + } + + for (i = 0; i < wh; ++i) { + int j = outs[i]; + if (i != dsf_canonify(ctx->dsf, i)) continue; + if (j < 0) continue; + connect(ctx, i, j); /* only one place for i to grow */ + changed = TRUE; + } + + sfree(outs); + return changed; +} + +static int solver_no_dangling_edges(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, r, c; + int changed = FALSE; + + /* for each vertex */ + for (r = 1; r < h; ++r) + for (c = 1; c < w; ++c) { + int i = r * w + c, j = i - w - 1, noline = 0, dir; + int squares[4], e = -1, f = -1, de = -1, df = -1; + + /* feels hacky: I align these with BORDER_[U0 R1 D2 L3] */ + squares[1] = squares[2] = j; + squares[0] = squares[3] = i; + + /* for each edge adjacent to the vertex */ + for (dir = 0; dir < 4; ++dir) + if (!connected(ctx, squares[dir], COMPUTE_J, dir)) { + df = dir; + f = squares[df]; + if (e != -1) continue; + e = f; + de = df; + } else ++noline; + + if (4 - noline == 1) { + assert (e != -1); + disconnect(ctx, e, COMPUTE_J, de); + changed = TRUE; + continue; + } + + if (4 - noline != 2) continue; + + assert (e != -1); + assert (f != -1); + + if (ctx->borders[e] & BORDER(de)) { + if (!(ctx->borders[f] & BORDER(df))) { + disconnect(ctx, f, COMPUTE_J, df); + changed = TRUE; + } + } else if (ctx->borders[f] & BORDER(df)) { + disconnect(ctx, e, COMPUTE_J, de); + changed = TRUE; + } + } + + return changed; +} + +static int solver_equivalent_edges(solver_ctx *ctx) +{ + int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dirj; + int changed = FALSE; + + /* if a square is adjacent to two connected squares, the two + * borders (i,j) and (i,k) are either both on or both off. */ + + for (i = 0; i < wh; ++i) { + int n_on = 0, n_off = 0; + if (ctx->clues[i] < 1 || ctx->clues[i] > 3) continue; + + if (ctx->clues[i] == 2 /* don't need it otherwise */) + for (dirj = 0; dirj < 4; ++dirj) { + int j = i + dx[dirj] + w*dy[dirj]; + if (disconnected(ctx, i, j, dirj)) ++n_on; + else if (connected(ctx, i, j, dirj)) ++n_off; + } + + for (dirj = 0; dirj < 4; ++dirj) { + int j = i + dx[dirj] + w*dy[dirj], dirk; + if (!maybe(ctx, i, j, dirj)) continue; + + for (dirk = dirj + 1; dirk < 4; ++dirk) { + int k = i + dx[dirk] + w*dy[dirk]; + if (!maybe(ctx, i, k, dirk)) continue; + if (!connected(ctx, j, k, -1)) continue; + + if (n_on + 2 > ctx->clues[i]) { + connect(ctx, i, j); + connect(ctx, i, k); + changed = TRUE; + } else if (n_off + 2 > 4 - ctx->clues[i]) { + disconnect(ctx, i, j, dirj); + disconnect(ctx, i, k, dirk); + changed = TRUE; + } + } + } + } + + return changed; +} + +#define UNVISITED 6 + +/* build connected components in `dsf', along the lines of `borders'. */ +static void dfs_dsf(int i, int w, borderflag *border, int *dsf, int black) +{ + int dir; + for (dir = 0; dir < 4; ++dir) { + int ii = i + dx[dir] + w*dy[dir], bdir = BORDER(dir); + if (black ? (border[i] & bdir) : !(border[i] & DISABLED(bdir))) + continue; + if (dsf[ii] != UNVISITED) continue; + dsf_merge(dsf, i, ii); + dfs_dsf(ii, w, border, dsf, black); + } +} + +static int is_solved(const game_params *params, clue *clues, + borderflag *border) +{ + int w = params->w, h = params->h, wh = w*h, k = params->k; + int i, x, y; + int *dsf = snew_dsf(wh); + + assert (dsf[0] == UNVISITED); /* check: UNVISITED and dsf.c match up */ + + /* + * A game is solved if: + * + * - the borders drawn on the grid divide it into connected + * components such that every square is in a component of the + * correct size + * - the borders also satisfy the clue set + */ + for (i = 0; i < wh; ++i) { + if (dsf[i] == UNVISITED) dfs_dsf(i, params->w, border, dsf, TRUE); + if (dsf_size(dsf, i) != k) goto error; + if (clues[i] == EMPTY) continue; + if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error; + } + + /* + * ... and thirdly: + * + * - there are no *stray* borders, in that every border is + * actually part of the division between two components. + * Otherwise you could cheat by finding a subdivision which did + * not *exceed* any clue square's counter, and then adding a + * few extra edges. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (x+1 < w && (border[y*w+x] & BORDER_R) && + dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, y*w+(x+1))) + goto error; + if (y+1 < h && (border[y*w+x] & BORDER_D) && + dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, (y+1)*w+x)) + goto error; + } + } + + sfree(dsf); + return TRUE; + +error: + sfree(dsf); + return FALSE; +} + +static int solver(const game_params *params, clue *clues, borderflag *borders) +{ + int w = params->w, h = params->h, wh = w*h, changed; + solver_ctx ctx; + + ctx.params = params; + ctx.clues = clues; + ctx.borders = borders; + ctx.dsf = snew_dsf(wh); + + solver_connected_clues_versus_region_size(&ctx); /* idempotent */ + do { + changed = FALSE; + changed |= solver_number_exhausted(&ctx); + changed |= solver_not_too_big(&ctx); + changed |= solver_not_too_small(&ctx); + changed |= solver_no_dangling_edges(&ctx); + changed |= solver_equivalent_edges(&ctx); + } while (changed); + + sfree(ctx.dsf); + + return is_solved(params, clues, borders); +} + +/* --- Generator ---------------------------------------------------- */ + +static void init_borders(int w, int h, borderflag *borders) +{ + int r, c; + setmem(borders, 0, w*h); + for (c = 0; c < w; ++c) { + borders[c] |= BORDER_U; + borders[w*h-1 - c] |= BORDER_D; + } + for (r = 0; r < h; ++r) { + borders[r*w] |= BORDER_L; + borders[w*h-1 - r*w] |= BORDER_R; + } +} + +#define OUT_OF_BOUNDS(x, y, w, h) \ + ((x) < 0 || (x) >= (w) || (y) < 0 || (y) >= (h)) + +#define xshuffle(ptr, len, rs) shuffle((ptr), (len), sizeof (ptr)[0], (rs)) + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h, wh = w*h, k = params->k; + + clue *numbers = snewn(wh + 1, clue), *p; + borderflag *rim = snewn(wh, borderflag); + borderflag *scratch_borders = snewn(wh, borderflag); + + char *soln = snewa(*aux, wh + 2); + int *shuf = snewn(wh, int); + int *dsf = NULL, i, r, c; + + int attempts = 0; + + for (i = 0; i < wh; ++i) shuf[i] = i; + xshuffle(shuf, wh, rs); + + init_borders(w, h, rim); + + assert (!('@' & BORDER_MASK)); + *soln++ = 'S'; + soln[wh] = '\0'; + + do { + ++attempts; + setmem(soln, '@', wh); + + sfree(dsf); + dsf = divvy_rectangle(w, h, k, rs); + + for (r = 0; r < h; ++r) + for (c = 0; c < w; ++c) { + int i = r * w + c, dir; + numbers[i] = 0; + for (dir = 0; dir < 4; ++dir) { + int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; + if (OUT_OF_BOUNDS(cc, rr, w, h) || + dsf_canonify(dsf, i) != dsf_canonify(dsf, ii)) { + ++numbers[i]; + soln[i] |= BORDER(dir); + } + } + } + + scopy(scratch_borders, rim, wh); + } while (!solver(params, numbers, scratch_borders)); + + for (i = 0; i < wh; ++i) { + int j = shuf[i]; + clue copy = numbers[j]; + + scopy(scratch_borders, rim, wh); + numbers[j] = EMPTY; /* strip away unnecssary clues */ + if (!solver(params, numbers, scratch_borders)) + numbers[j] = copy; + } + + numbers[wh] = '\0'; + + sfree(scratch_borders); + sfree(rim); + sfree(shuf); + sfree(dsf); + + p = numbers; + r = 0; + for (i = 0; i < wh; ++i) { + if (numbers[i] != EMPTY) { + while (r) { + while (r > 26) { + *p++ = 'z'; + r -= 26; + } + *p++ = 'a'-1 + r; + r = 0; + } + *p++ = '0' + numbers[i]; + } else ++r; + } + *p++ = '\0'; + + return sresize(numbers, p - numbers, clue); +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + + int w = params->w, h = params->h, wh = w*h, squares = 0; + + for (/* nop */; *desc; ++desc) { + if (islower((unsigned char)*desc)) { + squares += *desc - 'a' + 1; + } else if (isdigit((unsigned char)*desc)) { + if (*desc > '4') { + static char buf[] = "Invalid (too large) number: '5'"; + assert (isdigit((unsigned char)buf[lenof(buf) - 3])); + buf[lenof(buf) - 3] = *desc; /* ... or 6, 7, 8, 9 :-) */ + return buf; + } + ++squares; + } else if (isprint((unsigned char)*desc)) { + static char buf[] = "Invalid character in data: '?'"; + buf[lenof(buf) - 3] = *desc; + return buf; + } else return "Invalid (unprintable) character in data"; + } + + if (squares > wh) return "Data describes too many squares"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h, wh = w*h, i; + game_state *state = snew(game_state); + + state->shared = snew(shared_state); + state->shared->refcount = 1; + state->shared->params = *params; /* struct copy */ + snewa(state->shared->clues, wh); + + setmem(state->shared->clues, EMPTY, wh); + for (i = 0; *desc; ++desc) { + if (isdigit((unsigned char)*desc)) state->shared->clues[i++] = *desc - '0'; + else if (isalpha((unsigned char)*desc)) i += *desc - 'a' + 1; + } + + snewa(state->borders, wh); + init_borders(w, h, state->borders); + + state->completed = (params->k == wh); + state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int wh = state->shared->params.w * state->shared->params.h; + game_state *ret = snew(game_state); + + ret->borders = dupmem(state->borders, wh); + + ret->shared = state->shared; + ++ret->shared->refcount; + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->shared->refcount == 0) { + sfree(state->shared->clues); + sfree(state->shared); + } + sfree(state->borders); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; + borderflag *move; + + if (aux) return dupstr(aux); + + snewa(move, wh + 2); + + move[0] = 'S'; + init_borders(w, h, move + 1); + move[wh + 1] = '\0'; + + if (solver(&state->shared->params, state->shared->clues, move + 1)) { + int i; + for (i = 0; i < wh; i++) + move[i+1] |= '@'; /* turn into sensible ASCII */ + return (char *) move; + } + + *error = "Sorry, I can't solve this puzzle"; + sfree(move); + return NULL; + + { + /* compile-time-assert (borderflag is-a-kind-of char). + * + * depends on zero-size arrays being disallowed. GCC says + * ISO C forbids this, pointing to [-Werror=edantic]. Also, + * it depends on type-checking of (obviously) dead code. */ + borderflag b[sizeof (borderflag) == sizeof (char)]; + char c = b[0]; b[0] = c; + /* we could at least in principle put this anywhere, but it + * seems silly to not put it where the assumption is used. */ + } +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->shared->params.w, h = state->shared->params.h, r, c; + int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh; + char *board; + + setmem(snewa(board, len + 1), ' ', len); + for (r = 0; r < h; ++r) { + for (c = 0; c < w; ++c) { + int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; + int i = r * w + c, clue = state->shared->clues[i]; + + if (clue != EMPTY) board[center] = '0' + clue; + + board[cell] = '+'; + + if (state->borders[i] & BORDER_U) + setmem(board + cell + 1, '-', cw - 1); + else if (state->borders[i] & DISABLED(BORDER_U)) + board[cell + cw / 2] = 'x'; + + if (state->borders[i] & BORDER_L) + board[cell + gw] = '|'; + else if (state->borders[i] & DISABLED(BORDER_L)) + board[cell + gw] = 'x'; + } + + for (c = 0; c < ch; ++c) { + board[(r*ch + c)*gw + gw - 2] = c ? '|' : '+'; + board[(r*ch + c)*gw + gw - 1] = '\n'; + } + } + + scopy(board + len - gw, board, gw); + board[len] = '\0'; + + return board; +} + +struct game_ui { + int x, y; + unsigned int show: 1; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->x = ui->y = 0; + ui->show = FALSE; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + assert (encoding == NULL); +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +typedef unsigned short dsflags; + +struct game_drawstate { + int tilesize; + dsflags *grid; +}; + +#define TILESIZE (ds->tilesize) +#define MARGIN (ds->tilesize / 2) +#define WIDTH (1 + (TILESIZE >= 16) + (TILESIZE >= 32) + (TILESIZE >= 64)) +#define CENTER ((ds->tilesize / 2) + WIDTH/2) + +#define FROMCOORD(x) (((x) - MARGIN) / TILESIZE) + +enum {MAYBE_LEFT, MAYBE_RIGHT, ON_LEFT, ON_RIGHT, OFF_LEFT, OFF_RIGHT}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, int x, int y, int button) +{ + int w = state->shared->params.w, h = state->shared->params.h; + int control = button & MOD_CTRL, shift = button & MOD_SHFT; + + button &= ~MOD_MASK; + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + int gx = FROMCOORD(x), gy = FROMCOORD(y), possible = BORDER_MASK; + int px = (x - MARGIN) % TILESIZE, py = (y - MARGIN) % TILESIZE; + int hx, hy, dir, i; + + if (OUT_OF_BOUNDS(gx, gy, w, h)) return NULL; + + ui->x = gx; + ui->y = gy; + + /* find edge closest to click point */ + possible &=~ (2*px < TILESIZE ? BORDER_R : BORDER_L); + possible &=~ (2*py < TILESIZE ? BORDER_D : BORDER_U); + px = min(px, TILESIZE - px); + py = min(py, TILESIZE - py); + possible &=~ (px < py ? (BORDER_U|BORDER_D) : (BORDER_L|BORDER_R)); + + for (dir = 0; dir < 4 && BORDER(dir) != possible; ++dir); + if (dir == 4) return NULL; /* there's not exactly one such edge */ + + hx = gx + dx[dir]; + hy = gy + dy[dir]; + + if (OUT_OF_BOUNDS(hx, hy, w, h)) return NULL; + + ui->show = FALSE; + + i = gy * w + gx; + switch ((button == RIGHT_BUTTON) | + ((state->borders[i] & BORDER(dir)) >> dir << 1) | + ((state->borders[i] & DISABLED(BORDER(dir))) >> dir >> 2)) { + + case MAYBE_LEFT: + case ON_LEFT: + case ON_RIGHT: + return string(80, "F%d,%d,%dF%d,%d,%d", + gx, gy, BORDER(dir), + hx, hy, BORDER(FLIP(dir))); + + case MAYBE_RIGHT: + case OFF_LEFT: + case OFF_RIGHT: + return string(80, "F%d,%d,%dF%d,%d,%d", + gx, gy, DISABLED(BORDER(dir)), + hx, hy, DISABLED(BORDER(FLIP(dir)))); + } + } + + if (IS_CURSOR_MOVE(button)) { + ui->show = TRUE; + if (control || shift) { + borderflag flag = 0, newflag; + int dir, i = ui->y * w + ui->x; + x = ui->x; + y = ui->y; + move_cursor(button, &x, &y, w, h, FALSE); + if (OUT_OF_BOUNDS(x, y, w, h)) return NULL; + + for (dir = 0; dir < 4; ++dir) + if (dx[dir] == x - ui->x && dy[dir] == y - ui->y) break; + if (dir == 4) return NULL; /* how the ... ?! */ + + if (control) flag |= BORDER(dir); + if (shift) flag |= DISABLED(BORDER(dir)); + + newflag = state->borders[i] ^ flag; + if (newflag & BORDER(dir) && newflag & DISABLED(BORDER(dir))) + return NULL; + + newflag = 0; + if (control) newflag |= BORDER(FLIP(dir)); + if (shift) newflag |= DISABLED(BORDER(FLIP(dir))); + return string(80, "F%d,%d,%dF%d,%d,%d", + ui->x, ui->y, flag, x, y, newflag); + } else { + move_cursor(button, &ui->x, &ui->y, w, h, FALSE); + return ""; + } + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->shared->params.w, h = state->shared->params.h, wh = w * h; + game_state *ret = dup_game(state); + int nchars, x, y, flag; + + if (*move == 'S') { + int i; + ++move; + for (i = 0; i < wh && move[i]; ++i) + ret->borders[i] = + (move[i] & BORDER_MASK) | DISABLED(~move[i] & BORDER_MASK); + if (i < wh || move[i]) return NULL; /* leaks `ret', then we die */ + ret->cheated = ret->completed = TRUE; + return ret; + } + + while (sscanf(move, "F%d,%d,%d%n", &x, &y, &flag, &nchars) == 3 && + !OUT_OF_BOUNDS(x, y, w, h)) { + move += nchars; + ret->borders[y*w + x] ^= flag; + } + + if (*move) return NULL; /* leaks `ret', then we die */ + + if (!ret->completed) + ret->completed = is_solved(&ret->shared->params, ret->shared->clues, + ret->borders); + + return ret; +} + +/* --- Drawing routines --------------------------------------------- */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = (params->w + 1) * tilesize; + *y = (params->h + 1) * tilesize; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +enum { + COL_BACKGROUND, + COL_FLASH, + COL_GRID, + COL_CLUE = COL_GRID, + COL_LINE_YES = COL_GRID, + COL_LINE_MAYBE, + COL_LINE_NO, + COL_ERROR, + + NCOLOURS +}; + +#define COLOUR(i, r, g, b) \ + ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b))) +#define DARKER 0.9F + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_FLASH); + + COLOUR(COL_GRID, 0.0F, 0.0F, 0.0F); /* black */ + COLOUR(COL_ERROR, 1.0F, 0.0F, 0.0F); /* red */ + + COLOUR(COL_LINE_MAYBE, /* yellow */ + ret[COL_BACKGROUND*3 + 0] * DARKER, + ret[COL_BACKGROUND*3 + 1] * DARKER, + 0.0F); + + COLOUR(COL_LINE_NO, + ret[COL_BACKGROUND*3 + 0] * DARKER, + ret[COL_BACKGROUND*3 + 1] * DARKER, + ret[COL_BACKGROUND*3 + 2] * DARKER); + + *ncolours = NCOLOURS; + return ret; +} +#undef COLOUR + +#define BORDER_ERROR(x) ((x) << 8) +#define F_ERROR_U BORDER_ERROR(BORDER_U) /* BIT( 8) */ +#define F_ERROR_R BORDER_ERROR(BORDER_R) /* BIT( 9) */ +#define F_ERROR_D BORDER_ERROR(BORDER_D) /* BIT(10) */ +#define F_ERROR_L BORDER_ERROR(BORDER_L) /* BIT(11) */ +#define F_ERROR_CLUE BIT(12) +#define F_FLASH BIT(13) +#define F_CURSOR BIT(14) + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; + ds->grid = NULL; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +#define COLOUR(border) \ + (flags & BORDER_ERROR((border)) ? COL_ERROR : \ + flags & (border) ? COL_LINE_YES : \ + flags & DISABLED((border)) ? COL_LINE_NO : \ + COL_LINE_MAYBE) + +static void draw_tile(drawing *dr, game_drawstate *ds, int r, int c, + dsflags flags, int clue) +{ + int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; + + clip(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); /* { */ + + draw_rect(dr, x + WIDTH, y + WIDTH, TILESIZE - WIDTH, TILESIZE - WIDTH, + (flags & F_FLASH ? COL_FLASH : COL_BACKGROUND)); + + if (flags & F_CURSOR) + draw_rect_corners(dr, x + CENTER, y + CENTER, TILESIZE / 3, COL_GRID); + + if (clue != EMPTY) { + char buf[2]; + buf[0] = '0' + clue; + buf[1] = '\0'; + draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE, + TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE, + (flags & F_ERROR_CLUE ? COL_ERROR : COL_CLUE), buf); + } + + +#define ts TILESIZE +#define w WIDTH + draw_rect(dr, x + w, y, ts - w, w, COLOUR(BORDER_U)); + draw_rect(dr, x + ts, y + w, w, ts - w, COLOUR(BORDER_R)); + draw_rect(dr, x + w, y + ts, ts - w, w, COLOUR(BORDER_D)); + draw_rect(dr, x, y + w, w, ts - w, COLOUR(BORDER_L)); +#undef ts +#undef w + + unclip(dr); /* } */ + draw_update(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); +} + +#define FLASH_TIME 0.7F + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->shared->params.w, h = state->shared->params.h, wh = w*h; + int r, c, i, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; + int *black_border_dsf = snew_dsf(wh), *yellow_border_dsf = snew_dsf(wh); + int k = state->shared->params.k; + + if (!ds->grid) { + char buf[40]; + int bgw = (w+1) * ds->tilesize, bgh = (h+1) * ds->tilesize; + draw_rect(dr, 0, 0, bgw, bgh, COL_BACKGROUND); + + for (r = 0; r <= h; ++r) + for (c = 0; c <= w; ++c) + draw_rect(dr, MARGIN + TILESIZE * c, MARGIN + TILESIZE * r, + WIDTH, WIDTH, COL_GRID); + draw_update(dr, 0, 0, bgw, bgh); + + snewa(ds->grid, wh); + setmem(ds->grid, ~0, wh); + + sprintf(buf, "Region size: %d", state->shared->params.k); + status_bar(dr, buf); + } + + for (i = 0; i < wh; ++i) { + if (black_border_dsf[i] == UNVISITED) + dfs_dsf(i, w, state->borders, black_border_dsf, TRUE); + if (yellow_border_dsf[i] == UNVISITED) + dfs_dsf(i, w, state->borders, yellow_border_dsf, FALSE); + } + + for (r = 0; r < h; ++r) + for (c = 0; c < w; ++c) { + int i = r * w + c, clue = state->shared->clues[i], flags, dir; + int on = bitcount[state->borders[i] & BORDER_MASK]; + int off = bitcount[(state->borders[i] >> 4) & BORDER_MASK]; + + flags = state->borders[i]; + + if (flash) flags |= F_FLASH; + + if (clue != EMPTY && (on > clue || clue > 4 - off)) + flags |= F_ERROR_CLUE; + + if (ui->show && ui->x == c && ui->y == r) + flags |= F_CURSOR; + + /* border errors */ + for (dir = 0; dir < 4; ++dir) { + int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc; + + if (OUT_OF_BOUNDS(cc, rr, w, h)) continue; + + /* we draw each border twice, except the outermost + * big border, so we have to check for errors on + * both sides of each border.*/ + if (/* region too large */ + ((dsf_size(yellow_border_dsf, i) > k || + dsf_size(yellow_border_dsf, ii) > k) && + (dsf_canonify(yellow_border_dsf, i) != + dsf_canonify(yellow_border_dsf, ii))) + + || + /* region too small */ + ((dsf_size(black_border_dsf, i) < k || + dsf_size(black_border_dsf, ii) < k) && + dsf_canonify(black_border_dsf, i) != + dsf_canonify(black_border_dsf, ii)) + + || + /* dangling borders within a single region */ + ((state->borders[i] & BORDER(dir)) && + /* we know it's a single region because there's a + * path crossing no border from i to ii... */ + (dsf_canonify(yellow_border_dsf, i) == + dsf_canonify(yellow_border_dsf, ii) || + /* or because any such border would be an error */ + (dsf_size(black_border_dsf, i) <= k && + dsf_canonify(black_border_dsf, i) == + dsf_canonify(black_border_dsf, ii))))) + + flags |= BORDER_ERROR(BORDER(dir)); + } + + if (flags == ds->grid[i]) continue; + ds->grid[i] = flags; + draw_tile(dr, ds, r, c, ds->grid[i], clue); + } + + sfree(black_border_dsf); + sfree(yellow_border_dsf); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, + int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, + int dir, game_ui *ui) +{ + if (newstate->completed && !newstate->cheated && !oldstate->completed) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + assert (!"this shouldn't get called"); + return 0; /* placate optimiser */ +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + game_compute_size(params, 700, &pw, &ph); /* 7mm, like loopy */ + + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void print_line(drawing *dr, int x1, int y1, int x2, int y2, + int colour, int full) +{ + if (!full) { + int i, subdivisions = 8; + for (i = 1; i < subdivisions; ++i) { + int x = (x1 * (subdivisions - i) + x2 * i) / subdivisions; + int y = (y1 * (subdivisions - i) + y2 * i) / subdivisions; + draw_circle(dr, x, y, 3, colour, colour); + } + } else draw_line(dr, x1, y1, x2, y2, colour); +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->shared->params.w, h = state->shared->params.h; + int ink = print_mono_colour(dr, 0); + game_drawstate for_tilesize_macros, *ds = &for_tilesize_macros; + int r, c; + + ds->tilesize = tilesize; + + for (r = 0; r < h; ++r) + for (c = 0; c < w; ++c) { + int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; + int i = r * w + c, clue = state->shared->clues[i]; + + if (clue != EMPTY) { + char buf[2]; + buf[0] = '0' + clue; + buf[1] = '\0'; + draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE, + TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE, + ink, buf); + } + +#define ts TILESIZE +#define FULL(DIR) (state->borders[i] & (BORDER_ ## DIR)) + print_line(dr, x, y, x + ts, y, ink, FULL(U)); + print_line(dr, x + ts, y, x + ts, y + ts, ink, FULL(R)); + print_line(dr, x, y + ts, x + ts, y + ts, ink, FULL(D)); + print_line(dr, x, y, x, y + ts, ink, FULL(L)); +#undef ts +#undef FULL + } + + for (r = 1; r < h; ++r) + for (c = 1; c < w; ++c) { + int j = r * w + c, i = j - 1 - w; + int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r; + if (state->borders[i] & (BORDER_D|BORDER_R)) continue; + if (state->borders[j] & (BORDER_U|BORDER_L)) continue; + draw_circle(dr, x, y, 3, ink, ink); + } +} + +#ifdef COMBINED +#define thegame palisade +#endif + +const struct game thegame = { + "Palisade", "games.palisade", "palisade", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + 48, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/pattern.R b/apps/plugins/puzzles/pattern.R new file mode 100644 index 0000000000..d6695715f9 --- /dev/null +++ b/apps/plugins/puzzles/pattern.R @@ -0,0 +1,25 @@ +# -*- makefile -*- + +pattern : [X] GTK COMMON pattern pattern-icon|no-icon + +pattern : [G] WINDOWS COMMON pattern pattern.res|noicon.res + +patternsolver : [U] pattern[STANDALONE_SOLVER] STANDALONE +patternsolver : [C] pattern[STANDALONE_SOLVER] STANDALONE + +patternpicture : [U] pattern[STANDALONE_PICTURE_GENERATOR] STANDALONE +patternpicture : [C] pattern[STANDALONE_PICTURE_GENERATOR] STANDALONE + +ALL += pattern[COMBINED] + +!begin am gtk +GAMES += pattern +!end + +!begin >list.c + A(pattern) \ +!end + +!begin >gamedesc.txt +pattern:pattern.exe:Pattern:Pattern puzzle:Fill in the pattern in the grid, given only the lengths of runs of black squares. +!end diff --git a/apps/plugins/puzzles/pattern.c b/apps/plugins/puzzles/pattern.c new file mode 100644 index 0000000000..c741a2e3a4 --- /dev/null +++ b/apps/plugins/puzzles/pattern.c @@ -0,0 +1,2255 @@ +/* + * pattern.c: the pattern-reconstruction game known as `nonograms'. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + COL_EMPTY, + COL_FULL, + COL_TEXT, + COL_UNKNOWN, + COL_GRID, + COL_CURSOR, + COL_ERROR, + NCOLOURS +}; + +#define PREFERRED_TILE_SIZE 24 +#define TILE_SIZE (ds->tilesize) +#define BORDER (3 * TILE_SIZE / 4) +#define TLBORDER(d) ( (d) / 5 + 2 ) +#define GUTTER (TILE_SIZE / 2) + +#define FROMCOORD(d, x) \ + ( ((x) - (BORDER + GUTTER + TILE_SIZE * TLBORDER(d))) / TILE_SIZE ) + +#define SIZE(d) (2*BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (d))) +#define GETTILESIZE(d, w) ((double)w / (2.0 + (double)TLBORDER(d) + (double)(d))) + +#define TOCOORD(d, x) (BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (x))) + +struct game_params { + int w, h; +}; + +#define GRID_UNKNOWN 2 +#define GRID_FULL 1 +#define GRID_EMPTY 0 + +typedef struct game_state_common { + /* Parts of the game state that don't change during play. */ + int w, h; + int rowsize; + int *rowdata, *rowlen; + unsigned char *immutable; + int refcount; +} game_state_common; + +struct game_state { + game_state_common *common; + unsigned char *grid; + int completed, cheated; +}; + +#define FLASH_TIME 0.13F + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 15; + + return ret; +} + +static const struct game_params pattern_presets[] = { + {10, 10}, + {15, 15}, + {20, 20}, +#ifndef SLOW_SYSTEM + {25, 25}, + {30, 30}, +#endif +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(pattern_presets)) + return FALSE; + + ret = snew(game_params); + *ret = pattern_presets[i]; + + sprintf(str, "%dx%d", ret->w, ret->h); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + char const *p = string; + + ret->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + ret->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + ret->h = ret->w; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[400]; + int len; + + len = sprintf(ret, "%dx%d", params->w, params->h); + assert(len < lenof(ret)); + ret[len] = '\0'; + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w <= 0 || params->h <= 0) + return "Width and height must both be greater than zero"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Puzzle generation code. + * + * For this particular puzzle, it seemed important to me to ensure + * a unique solution. I do this the brute-force way, by having a + * solver algorithm alongside the generator, and repeatedly + * generating a random grid until I find one whose solution is + * unique. It turns out that this isn't too onerous on a modern PC + * provided you keep grid size below around 30. Any offers of + * better algorithms, however, will be very gratefully received. + * + * Another annoyance of this approach is that it limits the + * available puzzles to those solvable by the algorithm I've used. + * My algorithm only ever considers a single row or column at any + * one time, which means it's incapable of solving the following + * difficult example (found by Bella Image around 1995/6, when she + * and I were both doing maths degrees): + * + * 2 1 2 1 + * + * +--+--+--+--+ + * 1 1 | | | | | + * +--+--+--+--+ + * 2 | | | | | + * +--+--+--+--+ + * 1 | | | | | + * +--+--+--+--+ + * 1 | | | | | + * +--+--+--+--+ + * + * Obviously this cannot be solved by a one-row-or-column-at-a-time + * algorithm (it would require at least one row or column reading + * `2 1', `1 2', `3' or `4' to get started). However, it can be + * proved to have a unique solution: if the top left square were + * empty, then the only option for the top row would be to fill the + * two squares in the 1 columns, which would imply the squares + * below those were empty, leaving no place for the 2 in the second + * row. Contradiction. Hence the top left square is full, and the + * unique solution follows easily from that starting point. + * + * (The game ID for this puzzle is 4x4:2/1/2/1/1.1/2/1/1 , in case + * it's useful to anyone.) + */ + +#ifndef STANDALONE_PICTURE_GENERATOR +static int float_compare(const void *av, const void *bv) +{ + const float *a = (const float *)av; + const float *b = (const float *)bv; + if (*a < *b) + return -1; + else if (*a > *b) + return +1; + else + return 0; +} + +static void generate(random_state *rs, int w, int h, unsigned char *retgrid) +{ + float *fgrid; + float *fgrid2; + int step, i, j; + float threshold; + + fgrid = snewn(w*h, float); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + fgrid[i*w+j] = random_upto(rs, 100000000UL) / 100000000.F; + } + } + + /* + * The above gives a completely random splattering of black and + * white cells. We want to gently bias this in favour of _some_ + * reasonably thick areas of white and black, while retaining + * some randomness and fine detail. + * + * So we evolve the starting grid using a cellular automaton. + * Currently, I'm doing something very simple indeed, which is + * to set each square to the average of the surrounding nine + * cells (or the average of fewer, if we're on a corner). + */ + for (step = 0; step < 1; step++) { + fgrid2 = snewn(w*h, float); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + float sx, xbar; + int n, p, q; + + /* + * Compute the average of the surrounding cells. + */ + n = 0; + sx = 0.F; + for (p = -1; p <= +1; p++) { + for (q = -1; q <= +1; q++) { + if (i+p < 0 || i+p >= h || j+q < 0 || j+q >= w) + continue; + /* + * An additional special case not mentioned + * above: if a grid dimension is 2xn then + * we do not average across that dimension + * at all. Otherwise a 2x2 grid would + * contain four identical squares. + */ + if ((h==2 && p!=0) || (w==2 && q!=0)) + continue; + n++; + sx += fgrid[(i+p)*w+(j+q)]; + } + } + xbar = sx / n; + + fgrid2[i*w+j] = xbar; + } + } + + sfree(fgrid); + fgrid = fgrid2; + } + + fgrid2 = snewn(w*h, float); + memcpy(fgrid2, fgrid, w*h*sizeof(float)); + qsort(fgrid2, w*h, sizeof(float), float_compare); + threshold = fgrid2[w*h/2]; + sfree(fgrid2); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + retgrid[i*w+j] = (fgrid[i*w+j] >= threshold ? GRID_FULL : + GRID_EMPTY); + } + } + + sfree(fgrid); +} +#endif + +static int compute_rowdata(int *ret, unsigned char *start, int len, int step) +{ + int i, n; + + n = 0; + + for (i = 0; i < len; i++) { + if (start[i*step] == GRID_FULL) { + int runlen = 1; + while (i+runlen < len && start[(i+runlen)*step] == GRID_FULL) + runlen++; + ret[n++] = runlen; + i += runlen; + } + + if (i < len && start[i*step] == GRID_UNKNOWN) + return -1; + } + + return n; +} + +#define UNKNOWN 0 +#define BLOCK 1 +#define DOT 2 +#define STILL_UNKNOWN 3 + +#ifdef STANDALONE_SOLVER +int verbose = FALSE; +#endif + +static int do_recurse(unsigned char *known, unsigned char *deduced, + unsigned char *row, + unsigned char *minpos_done, unsigned char *maxpos_done, + unsigned char *minpos_ok, unsigned char *maxpos_ok, + int *data, int len, + int freespace, int ndone, int lowest) +{ + int i, j, k; + + + /* This algorithm basically tries all possible ways the given rows of + * black blocks can be laid out in the row/column being examined. + * Special care is taken to avoid checking the tail of a row/column + * if the same conditions have already been checked during this recursion + * The algorithm also takes care to cut its losses as soon as an + * invalid (partial) solution is detected. + */ + if (data[ndone]) { + if (lowest >= minpos_done[ndone] && lowest <= maxpos_done[ndone]) { + if (lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]) { + for (i=0; i= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]; + } else { + if (lowest < minpos_done[ndone]) minpos_done[ndone] = lowest; + if (lowest > maxpos_done[ndone]) maxpos_done[ndone] = lowest; + } + for (i=0; i<=freespace; i++) { + j = lowest; + for (k=0; k maxpos_ok[ndone]) maxpos_ok[ndone] = lowest + i; + if (lowest + i > maxpos_done[ndone]) maxpos_done[ndone] = lowest + i; + } + next_iter: + j++; + } + return lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]; + } else { + for (i=lowest; i= 0 && known[i] == DOT; i--) + freespace--; + + if (rowlen == 0) { + memset(deduced, DOT, len); + } else { + do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok, + maxpos_ok, data, len, freespace, 0, 0); + } + + done_any = FALSE; + for (i=0; i "); + for (i = 0; i < len; i++) + putchar(start[i*step] == BLOCK ? '#' : + start[i*step] == DOT ? '.' : '?'); + putchar('\n'); + } +#endif + return done_any; +} + +static int solve_puzzle(const game_state *state, unsigned char *grid, + int w, int h, + unsigned char *matrix, unsigned char *workspace, + unsigned int *changed_h, unsigned int *changed_w, + int *rowdata +#ifdef STANDALONE_SOLVER + , int cluewid +#else + , int dummy +#endif + ) +{ + int i, j, ok, max; + int max_h, max_w; + + assert((state!=NULL && state->common->rowdata!=NULL) ^ (grid!=NULL)); + + max = max(w, h); + + memset(matrix, 0, w*h); + if (state) { + for (i=0; icommon->immutable[i]) + matrix[i] = state->grid[i]; + } + } + + /* For each column, compute how many squares can be deduced + * from just the row-data and initial clues. + * Later, changed_* will hold how many squares were changed + * in every row/column in the previous iteration + * Changed_* is used to choose the next rows / cols to re-examine + */ + for (i=0; icommon->rowdata) { + memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int)); + rowlen = state->common->rowlen[w+i]; + } else { + rowlen = compute_rowdata(rowdata, grid+i*w, w, 1); + } + rowdata[rowlen] = 0; + if (rowlen == 0) { + changed_h[i] = w; + } else { + for (j=0, freespace=w+1; rowdata[j]; j++) + freespace -= rowdata[j] + 1; + for (j=0, changed_h[i]=0; rowdata[j]; j++) + if (rowdata[j] > freespace) + changed_h[i] += rowdata[j] - freespace; + } + for (j = 0; j < w; j++) + if (matrix[i*w+j]) + changed_h[i]++; + } + for (i=0,max_h=0; i max_h) + max_h = changed_h[i]; + for (i=0; icommon->rowdata) { + memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int)); + rowlen = state->common->rowlen[i]; + } else { + rowlen = compute_rowdata(rowdata, grid+i, h, w); + } + rowdata[rowlen] = 0; + if (rowlen == 0) { + changed_w[i] = h; + } else { + for (j=0, freespace=h+1; rowdata[j]; j++) + freespace -= rowdata[j] + 1; + for (j=0, changed_w[i]=0; rowdata[j]; j++) + if (rowdata[j] > freespace) + changed_w[i] += rowdata[j] - freespace; + } + for (j = 0; j < h; j++) + if (matrix[j*w+i]) + changed_w[i]++; + } + for (i=0,max_w=0; i max_w) + max_w = changed_w[i]; + + /* Solve the puzzle. + * Process rows/columns individually. Deductions involving more than one + * row and/or column at a time are not supported. + * Take care to only process rows/columns which have been changed since they + * were previously processed. + * Also, prioritize rows/columns which have had the most changes since their + * previous processing, as they promise the greatest benefit. + * Extremely rectangular grids (e.g. 10x20, 15x40, etc.) are not treated specially. + */ + do { + for (; max_h && max_h >= max_w; max_h--) { + for (i=0; i= max_h) { + if (state && state->common->rowdata) { + memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int)); + rowdata[state->common->rowlen[w+i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i*w, w, 1)] = 0; + } + do_row(workspace, workspace+max, workspace+2*max, + workspace+3*max, workspace+4*max, + workspace+5*max, workspace+6*max, + matrix+i*w, w, 1, rowdata, changed_w +#ifdef STANDALONE_SOLVER + , "row", i+1, cluewid +#endif + ); + changed_h[i] = 0; + } + } + for (i=0,max_w=0; i max_w) + max_w = changed_w[i]; + } + for (; max_w && max_w >= max_h; max_w--) { + for (i=0; i= max_w) { + if (state && state->common->rowdata) { + memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int)); + rowdata[state->common->rowlen[i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i, h, w)] = 0; + } + do_row(workspace, workspace+max, workspace+2*max, + workspace+3*max, workspace+4*max, + workspace+5*max, workspace+6*max, + matrix+i, h, w, rowdata, changed_h +#ifdef STANDALONE_SOLVER + , "col", i+1, cluewid +#endif + ); + changed_w[i] = 0; + } + } + for (i=0,max_h=0; i max_h) + max_h = changed_h[i]; + } + } while (max_h>0 || max_w>0); + + ok = TRUE; + for (i=0; i 2) { + for (i = 0; i < h; i++) { + int colours = 0; + for (j = 0; j < w; j++) + colours |= (grid[i*w+j] == GRID_FULL ? 2 : 1); + if (colours != 3) + ok = FALSE; + } + } + if (h > 2) { + for (j = 0; j < w; j++) { + int colours = 0; + for (i = 0; i < h; i++) + colours |= (grid[i*w+j] == GRID_FULL ? 2 : 1); + if (colours != 3) + ok = FALSE; + } + } + if (!ok) + continue; + + ok = solve_puzzle(NULL, grid, w, h, matrix, workspace, + changed_h, changed_w, rowdata, 0); + } while (!ok); + + sfree(matrix); + sfree(workspace); + sfree(changed_h); + sfree(changed_w); + sfree(rowdata); + return grid; +} +#endif + +#ifdef STANDALONE_PICTURE_GENERATOR +unsigned char *picture; +#endif + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + unsigned char *grid; + int i, j, max, rowlen, *rowdata; + char intbuf[80], *desc; + int desclen, descpos; +#ifdef STANDALONE_PICTURE_GENERATOR + game_state *state; + int *index; +#endif + + max = max(params->w, params->h); + +#ifdef STANDALONE_PICTURE_GENERATOR + /* + * Fixed input picture. + */ + grid = snewn(params->w * params->h, unsigned char); + memcpy(grid, picture, params->w * params->h); + + /* + * Now winnow the immutable square set as far as possible. + */ + state = snew(game_state); + state->grid = grid; + state->common = snew(game_state_common); + state->common->rowdata = NULL; + state->common->immutable = snewn(params->w * params->h, unsigned char); + memset(state->common->immutable, 1, params->w * params->h); + + index = snewn(params->w * params->h, int); + for (i = 0; i < params->w * params->h; i++) + index[i] = i; + shuffle(index, params->w * params->h, sizeof(*index), rs); + + { + unsigned char *matrix = snewn(params->w*params->h, unsigned char); + unsigned char *workspace = snewn(max*7, unsigned char); + unsigned int *changed_h = snewn(max+1, unsigned int); + unsigned int *changed_w = snewn(max+1, unsigned int); + int *rowdata = snewn(max+1, int); + for (i = 0; i < params->w * params->h; i++) { + state->common->immutable[index[i]] = 0; + if (!solve_puzzle(state, grid, params->w, params->h, + matrix, workspace, changed_h, changed_w, + rowdata, 0)) + state->common->immutable[index[i]] = 1; + } + sfree(workspace); + sfree(changed_h); + sfree(changed_w); + sfree(rowdata); + sfree(matrix); + } +#else + grid = generate_soluble(rs, params->w, params->h); +#endif + rowdata = snewn(max, int); + + /* + * Save the solved game in aux. + */ + if (aux) { + char *ai = snewn(params->w * params->h + 2, char); + + /* + * String format is exactly the same as a solve move, so we + * can just dupstr this in solve_game(). + */ + + ai[0] = 'S'; + + for (i = 0; i < params->w * params->h; i++) + ai[i+1] = grid[i] ? '1' : '0'; + + ai[params->w * params->h + 1] = '\0'; + + *aux = ai; + } + + /* + * Seed is a slash-separated list of row contents; each row + * contents section is a dot-separated list of integers. Row + * contents are listed in the order (columns left to right, + * then rows top to bottom). + * + * Simplest way to handle memory allocation is to make two + * passes, first computing the seed size and then writing it + * out. + */ + desclen = 0; + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w); + else + rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w, + params->w, 1); + if (rowlen > 0) { + for (j = 0; j < rowlen; j++) { + desclen += 1 + sprintf(intbuf, "%d", rowdata[j]); + } + } else { + desclen++; + } + } + desc = snewn(desclen, char); + descpos = 0; + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w); + else + rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w, + params->w, 1); + if (rowlen > 0) { + for (j = 0; j < rowlen; j++) { + int len = sprintf(desc+descpos, "%d", rowdata[j]); + if (j+1 < rowlen) + desc[descpos + len] = '.'; + else + desc[descpos + len] = '/'; + descpos += len+1; + } + } else { + desc[descpos++] = '/'; + } + } + assert(descpos == desclen); + assert(desc[desclen-1] == '/'); + desc[desclen-1] = '\0'; +#ifdef STANDALONE_PICTURE_GENERATOR + for (i = 0; i < params->w * params->h; i++) + if (state->common->immutable[i]) + break; + if (i < params->w * params->h) { + /* + * At least one immutable square, so we need a suffix. + */ + int run; + + desc = sresize(desc, desclen + params->w * params->h + 3, char); + desc[descpos-1] = ','; + + run = 0; + for (i = 0; i < params->w * params->h; i++) { + if (!state->common->immutable[i]) { + run++; + if (run == 25) { + desc[descpos++] = 'z'; + run = 0; + } + } else { + desc[descpos++] = run + (grid[i] == GRID_FULL ? 'A' : 'a'); + run = 0; + } + } + if (run > 0) + desc[descpos++] = run + 'a'; + desc[descpos] = '\0'; + } + sfree(state->common->immutable); + sfree(state->common); + sfree(state); +#endif + sfree(rowdata); + sfree(grid); + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int i, n, rowspace; + const char *p; + + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowspace = params->h + 1; + else + rowspace = params->w + 1; + + if (*desc && isdigit((unsigned char)*desc)) { + do { + p = desc; + while (*desc && isdigit((unsigned char)*desc)) desc++; + n = atoi(p); + rowspace -= n+1; + + if (rowspace < 0) { + if (i < params->w) + return "at least one column contains more numbers than will fit"; + else + return "at least one row contains more numbers than will fit"; + } + } while (*desc++ == '.'); + } else { + desc++; /* expect a slash immediately */ + } + + if (desc[-1] == '/') { + if (i+1 == params->w + params->h) + return "too many row/column specifications"; + } else if (desc[-1] == '\0' || desc[-1] == ',') { + if (i+1 < params->w + params->h) + return "too few row/column specifications"; + } else + return "unrecognised character in game specification"; + } + + if (desc[-1] == ',') { + /* + * Optional extra piece of game description which fills in + * some grid squares as extra clues. + */ + i = 0; + while (i < params->w * params->h) { + int c = (unsigned char)*desc++; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z')) { + int len = tolower(c) - 'a'; + i += len; + if (len < 25 && i < params->w*params->h) + i++; + if (i > params->w * params->h) { + return "too much data in clue-squares section"; + } + } else if (!c) { + return "too little data in clue-squares section"; + } else { + return "unrecognised character in clue-squares section"; + } + } + if (*desc) { + return "too much data in clue-squares section"; + } + } + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int i; + const char *p; + game_state *state = snew(game_state); + + state->common = snew(game_state_common); + state->common->refcount = 1; + + state->common->w = params->w; + state->common->h = params->h; + + state->grid = snewn(state->common->w * state->common->h, unsigned char); + memset(state->grid, GRID_UNKNOWN, state->common->w * state->common->h); + + state->common->immutable = snewn(state->common->w * state->common->h, + unsigned char); + memset(state->common->immutable, 0, state->common->w * state->common->h); + + state->common->rowsize = max(state->common->w, state->common->h); + state->common->rowdata = snewn(state->common->rowsize * (state->common->w + state->common->h), int); + state->common->rowlen = snewn(state->common->w + state->common->h, int); + + state->completed = state->cheated = FALSE; + + for (i = 0; i < params->w + params->h; i++) { + state->common->rowlen[i] = 0; + if (*desc && isdigit((unsigned char)*desc)) { + do { + p = desc; + while (*desc && isdigit((unsigned char)*desc)) desc++; + state->common->rowdata[state->common->rowsize * i + state->common->rowlen[i]++] = + atoi(p); + } while (*desc++ == '.'); + } else { + desc++; /* expect a slash immediately */ + } + } + + if (desc[-1] == ',') { + /* + * Optional extra piece of game description which fills in + * some grid squares as extra clues. + */ + i = 0; + while (i < params->w * params->h) { + int c = (unsigned char)*desc++; + int full = isupper(c), len = tolower(c) - 'a'; + i += len; + if (len < 25 && i < params->w*params->h) { + state->grid[i] = full ? GRID_FULL : GRID_EMPTY; + state->common->immutable[i] = TRUE; + i++; + } + } + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->common = state->common; + ret->common->refcount++; + + ret->grid = snewn(ret->common->w * ret->common->h, unsigned char); + memcpy(ret->grid, state->grid, ret->common->w * ret->common->h); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->common->refcount == 0) { + sfree(state->common->rowdata); + sfree(state->common->rowlen); + sfree(state->common->immutable); + sfree(state->common); + } + sfree(state->grid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *ai, char **error) +{ + unsigned char *matrix; + int w = state->common->w, h = state->common->h; + int i; + char *ret; + int max, ok; + unsigned char *workspace; + unsigned int *changed_h, *changed_w; + int *rowdata; + + /* + * If we already have the solved state in ai, copy it out. + */ + if (ai) + return dupstr(ai); + + max = max(w, h); + matrix = snewn(w*h, unsigned char); + workspace = snewn(max*7, unsigned char); + changed_h = snewn(max+1, unsigned int); + changed_w = snewn(max+1, unsigned int); + rowdata = snewn(max+1, int); + + ok = solve_puzzle(state, NULL, w, h, matrix, workspace, + changed_h, changed_w, rowdata, 0); + + sfree(workspace); + sfree(changed_h); + sfree(changed_w); + sfree(rowdata); + + if (!ok) { + sfree(matrix); + *error = "Solving algorithm cannot complete this puzzle"; + return NULL; + } + + ret = snewn(w*h+2, char); + ret[0] = 'S'; + for (i = 0; i < w*h; i++) { + assert(matrix[i] == BLOCK || matrix[i] == DOT); + ret[i+1] = (matrix[i] == BLOCK ? '1' : '0'); + } + ret[w*h+1] = '\0'; + + sfree(matrix); + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->common->w, h = state->common->h, i, j; + int left_gap = 0, top_gap = 0, ch = 2, cw = 1, limit = 1; + + int len, topleft, lw, lh, gw, gh; /* {line,grid}_{width,height} */ + char *board, *buf; + + for (i = 0; i < w; ++i) { + top_gap = max(top_gap, state->common->rowlen[i]); + for (j = 0; j < state->common->rowlen[i]; ++j) + while (state->common->rowdata[i*state->common->rowsize + j] >= limit) { + ++cw; + limit *= 10; + } + } + for (i = 0; i < h; ++i) { + int rowlen = 0, predecessors = FALSE; + for (j = 0; j < state->common->rowlen[i+w]; ++j) { + int copy = state->common->rowdata[(i+w)*state->common->rowsize + j]; + rowlen += predecessors; + predecessors = TRUE; + do ++rowlen; while (copy /= 10); + } + left_gap = max(left_gap, rowlen); + } + + cw = max(cw, 3); + + gw = w*cw + 2; + gh = h*ch + 1; + lw = gw + left_gap; + lh = gh + top_gap; + len = lw * lh; + topleft = lw * top_gap + left_gap; + + board = snewn(len + 1, char); + sprintf(board, "%*s\n", len - 2, ""); + + for (i = 0; i < lh; ++i) { + board[lw - 1 + i*lw] = '\n'; + if (i < top_gap) continue; + board[lw - 2 + i*lw] = ((i - top_gap) % ch ? '|' : '+'); + } + + for (i = 0; i < w; ++i) { + for (j = 0; j < state->common->rowlen[i]; ++j) { + int cell = topleft + i*cw + 1 + lw*(j - state->common->rowlen[i]); + int nch = sprintf(board + cell, "%*d", cw - 1, + state->common->rowdata[i*state->common->rowsize + j]); + board[cell + nch] = ' '; /* de-NUL-ify */ + } + } + + buf = snewn(left_gap, char); + for (i = 0; i < h; ++i) { + char *p = buf, *start = board + top_gap*lw + left_gap + (i*ch+1)*lw; + for (j = 0; j < state->common->rowlen[i+w]; ++j) { + if (p > buf) *p++ = ' '; + p += sprintf(p, "%d", state->common->rowdata[(i+w)*state->common->rowsize + j]); + } + memcpy(start - (p - buf), buf, p - buf); + } + + for (i = 0; i < w; ++i) { + for (j = 0; j < h; ++j) { + int cell = topleft + i*cw + j*ch*lw; + int center = cell + cw/2 + (ch/2)*lw; + int dx, dy; + board[cell] = 0 ? center : '+'; + for (dx = 1; dx < cw; ++dx) board[cell + dx] = '-'; + for (dy = 1; dy < ch; ++dy) board[cell + dy*lw] = '|'; + if (state->grid[i*w+j] == GRID_UNKNOWN) continue; + for (dx = 1; dx < cw; ++dx) + for (dy = 1; dy < ch; ++dy) + board[cell + dx + dy*lw] = + state->grid[i*w+j] == GRID_FULL ? '#' : '.'; + } + } + + memcpy(board + topleft + h*ch*lw, board + topleft, gw - 1); + + sfree(buf); + + return board; +} + +struct game_ui { + int dragging; + int drag_start_x; + int drag_start_y; + int drag_end_x; + int drag_end_y; + int drag, release, state; + int cur_x, cur_y, cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ret; + + ret = snew(game_ui); + ret->dragging = FALSE; + ret->cur_x = ret->cur_y = ret->cur_visible = 0; + + return ret; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int w, h; + int tilesize; + unsigned char *visible, *numcolours; + int cur_x, cur_y; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int control = button & MOD_CTRL, shift = button & MOD_SHFT; + button &= ~MOD_MASK; + + x = FROMCOORD(state->common->w, x); + y = FROMCOORD(state->common->h, y); + + if (x >= 0 && x < state->common->w && y >= 0 && y < state->common->h && + (button == LEFT_BUTTON || button == RIGHT_BUTTON || + button == MIDDLE_BUTTON)) { +#ifdef STYLUS_BASED + int currstate = state->grid[y * state->common->w + x]; +#endif + + ui->dragging = TRUE; + + if (button == LEFT_BUTTON) { + ui->drag = LEFT_DRAG; + ui->release = LEFT_RELEASE; +#ifdef STYLUS_BASED + ui->state = (currstate + 2) % 3; /* FULL -> EMPTY -> UNKNOWN */ +#else + ui->state = GRID_FULL; +#endif + } else if (button == RIGHT_BUTTON) { + ui->drag = RIGHT_DRAG; + ui->release = RIGHT_RELEASE; +#ifdef STYLUS_BASED + ui->state = (currstate + 1) % 3; /* EMPTY -> FULL -> UNKNOWN */ +#else + ui->state = GRID_EMPTY; +#endif + } else /* if (button == MIDDLE_BUTTON) */ { + ui->drag = MIDDLE_DRAG; + ui->release = MIDDLE_RELEASE; + ui->state = GRID_UNKNOWN; + } + + ui->drag_start_x = ui->drag_end_x = x; + ui->drag_start_y = ui->drag_end_y = y; + ui->cur_visible = 0; + + return ""; /* UI activity occurred */ + } + + if (ui->dragging && button == ui->drag) { + /* + * There doesn't seem much point in allowing a rectangle + * drag; people will generally only want to drag a single + * horizontal or vertical line, so we make that easy by + * snapping to it. + * + * Exception: if we're _middle_-button dragging to tag + * things as UNKNOWN, we may well want to trash an entire + * area and start over! + */ + if (ui->state != GRID_UNKNOWN) { + if (abs(x - ui->drag_start_x) > abs(y - ui->drag_start_y)) + y = ui->drag_start_y; + else + x = ui->drag_start_x; + } + + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x >= state->common->w) x = state->common->w - 1; + if (y >= state->common->h) y = state->common->h - 1; + + ui->drag_end_x = x; + ui->drag_end_y = y; + + return ""; /* UI activity occurred */ + } + + if (ui->dragging && button == ui->release) { + int x1, x2, y1, y2, xx, yy; + int move_needed = FALSE; + + x1 = min(ui->drag_start_x, ui->drag_end_x); + x2 = max(ui->drag_start_x, ui->drag_end_x); + y1 = min(ui->drag_start_y, ui->drag_end_y); + y2 = max(ui->drag_start_y, ui->drag_end_y); + + for (yy = y1; yy <= y2; yy++) + for (xx = x1; xx <= x2; xx++) + if (!state->common->immutable[yy * state->common->w + xx] && + state->grid[yy * state->common->w + xx] != ui->state) + move_needed = TRUE; + + ui->dragging = FALSE; + + if (move_needed) { + char buf[80]; + sprintf(buf, "%c%d,%d,%d,%d", + (char)(ui->state == GRID_FULL ? 'F' : + ui->state == GRID_EMPTY ? 'E' : 'U'), + x1, y1, x2-x1+1, y2-y1+1); + return dupstr(buf); + } else + return ""; /* UI activity occurred */ + } + + if (IS_CURSOR_MOVE(button)) { + int x = ui->cur_x, y = ui->cur_y, newstate; + char buf[80]; + move_cursor(button, &ui->cur_x, &ui->cur_y, state->common->w, state->common->h, 0); + ui->cur_visible = 1; + if (!control && !shift) return ""; + + newstate = control ? shift ? GRID_UNKNOWN : GRID_FULL : GRID_EMPTY; + if (state->grid[y * state->common->w + x] == newstate && + state->grid[ui->cur_y * state->common->w + ui->cur_x] == newstate) + return ""; + + sprintf(buf, "%c%d,%d,%d,%d", control ? shift ? 'U' : 'F' : 'E', + min(x, ui->cur_x), min(y, ui->cur_y), + abs(x - ui->cur_x) + 1, abs(y - ui->cur_y) + 1); + return dupstr(buf); + } + + if (IS_CURSOR_SELECT(button)) { + int currstate = state->grid[ui->cur_y * state->common->w + ui->cur_x]; + int newstate; + char buf[80]; + + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + + if (button == CURSOR_SELECT2) + newstate = currstate == GRID_UNKNOWN ? GRID_EMPTY : + currstate == GRID_EMPTY ? GRID_FULL : GRID_UNKNOWN; + else + newstate = currstate == GRID_UNKNOWN ? GRID_FULL : + currstate == GRID_FULL ? GRID_EMPTY : GRID_UNKNOWN; + + sprintf(buf, "%c%d,%d,%d,%d", + (char)(newstate == GRID_FULL ? 'F' : + newstate == GRID_EMPTY ? 'E' : 'U'), + ui->cur_x, ui->cur_y, 1, 1); + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + game_state *ret; + int x1, x2, y1, y2, xx, yy; + int val; + + if (move[0] == 'S' && + strlen(move) == from->common->w * from->common->h + 1) { + int i; + + ret = dup_game(from); + + for (i = 0; i < ret->common->w * ret->common->h; i++) + ret->grid[i] = (move[i+1] == '1' ? GRID_FULL : GRID_EMPTY); + + ret->completed = ret->cheated = TRUE; + + return ret; + } else if ((move[0] == 'F' || move[0] == 'E' || move[0] == 'U') && + sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 && + x1 >= 0 && x2 >= 0 && x1+x2 <= from->common->w && + y1 >= 0 && y2 >= 0 && y1+y2 <= from->common->h) { + + x2 += x1; + y2 += y1; + val = (move[0] == 'F' ? GRID_FULL : + move[0] == 'E' ? GRID_EMPTY : GRID_UNKNOWN); + + ret = dup_game(from); + for (yy = y1; yy < y2; yy++) + for (xx = x1; xx < x2; xx++) + if (!ret->common->immutable[yy * ret->common->w + xx]) + ret->grid[yy * ret->common->w + xx] = val; + + /* + * An actual change, so check to see if we've completed the + * game. + */ + if (!ret->completed) { + int *rowdata = snewn(ret->common->rowsize, int); + int i, len; + + ret->completed = TRUE; + + for (i=0; icommon->w; i++) { + len = compute_rowdata(rowdata, ret->grid+i, + ret->common->h, ret->common->w); + if (len != ret->common->rowlen[i] || + memcmp(ret->common->rowdata+i*ret->common->rowsize, + rowdata, len * sizeof(int))) { + ret->completed = FALSE; + break; + } + } + for (i=0; icommon->h; i++) { + len = compute_rowdata(rowdata, ret->grid+i*ret->common->w, + ret->common->w, 1); + if (len != ret->common->rowlen[i+ret->common->w] || + memcmp(ret->common->rowdata + + (i+ret->common->w)*ret->common->rowsize, + rowdata, len * sizeof(int))) { + ret->completed = FALSE; + break; + } + } + + sfree(rowdata); + } + + return ret; + } else + return NULL; +} + +/* ---------------------------------------------------------------------- + * Error-checking during gameplay. + */ + +/* + * The difficulty in error-checking Pattern is to make the error check + * _weak_ enough. The most obvious way would be to check each row and + * column by calling (a modified form of) do_row() to recursively + * analyse the row contents against the clue set and see if the + * GRID_UNKNOWNs could be filled in in any way that would end up + * correct. However, this turns out to be such a strong error check as + * to constitute a spoiler in many situations: you make a typo while + * trying to fill in one row, and not only does the row light up to + * indicate an error, but several columns crossed by the move also + * light up and draw your attention to deductions you hadn't even + * noticed you could make. + * + * So instead I restrict error-checking to 'complete runs' within a + * row, by which I mean contiguous sequences of GRID_FULL bounded at + * both ends by either GRID_EMPTY or the ends of the row. We identify + * all the complete runs in a row, and verify that _those_ are + * consistent with the row's clue list. Sequences of complete runs + * separated by solid GRID_EMPTY are required to match contiguous + * sequences in the clue list, whereas if there's at least one + * GRID_UNKNOWN between any two complete runs then those two need not + * be contiguous in the clue list. + * + * To simplify the edge cases, I pretend that the clue list for the + * row is extended with a 0 at each end, and I also pretend that the + * grid data for the row is extended with a GRID_EMPTY and a + * zero-length run at each end. This permits the contiguity checker to + * handle the fiddly end effects (e.g. if the first contiguous + * sequence of complete runs in the grid matches _something_ in the + * clue list but not at the beginning, this is allowable iff there's a + * GRID_UNKNOWN before the first one) with minimal faff, since the end + * effects just drop out as special cases of the normal inter-run + * handling (in this code the above case is not 'at the end of the + * clue list' at all, but between the implicit initial zero run and + * the first nonzero one). + * + * We must also be a little careful about how we search for a + * contiguous sequence of runs. In the clue list (1 1 2 1 2 3), + * suppose we see a GRID_UNKNOWN and then a length-1 run. We search + * for 1 in the clue list and find it at the very beginning. But now + * suppose we find a length-2 run with no GRID_UNKNOWN before it. We + * can't naively look at the next clue from the 1 we found, because + * that'll be the second 1 and won't match. Instead, we must backtrack + * by observing that the 2 we've just found must be contiguous with + * the 1 we've already seen, so we search for the sequence (1 2) and + * find it starting at the second 1. Now if we see a 3, we must + * rethink again and search for (1 2 3). + */ + +struct errcheck_state { + /* + * rowdata and rowlen point at the clue data for this row in the + * game state. + */ + int *rowdata; + int rowlen; + /* + * rowpos indicates the lowest position where it would be valid to + * see our next run length. It might be equal to rowlen, + * indicating that the next run would have to be the terminating 0. + */ + int rowpos; + /* + * ncontig indicates how many runs we've seen in a contiguous + * block. This is taken into account when searching for the next + * run we find, unless ncontig is zeroed out first by encountering + * a GRID_UNKNOWN. + */ + int ncontig; +}; + +static int errcheck_found_run(struct errcheck_state *es, int r) +{ +/* Macro to handle the pretence that rowdata has a 0 at each end */ +#define ROWDATA(k) ((k)<0 || (k)>=es->rowlen ? 0 : es->rowdata[(k)]) + + /* + * See if we can find this new run length at a position where it + * also matches the last 'ncontig' runs we've seen. + */ + int i, newpos; + for (newpos = es->rowpos; newpos <= es->rowlen; newpos++) { + + if (ROWDATA(newpos) != r) + goto notfound; + + for (i = 1; i <= es->ncontig; i++) + if (ROWDATA(newpos - i) != ROWDATA(es->rowpos - i)) + goto notfound; + + es->rowpos = newpos+1; + es->ncontig++; + return TRUE; + + notfound:; + } + + return FALSE; + +#undef ROWDATA +} + +static int check_errors(const game_state *state, int i) +{ + int start, step, end, j; + int val, runlen; + struct errcheck_state aes, *es = &aes; + + es->rowlen = state->common->rowlen[i]; + es->rowdata = state->common->rowdata + state->common->rowsize * i; + /* Pretend that we've already encountered the initial zero run */ + es->ncontig = 1; + es->rowpos = 0; + + if (i < state->common->w) { + start = i; + step = state->common->w; + end = start + step * state->common->h; + } else { + start = (i - state->common->w) * state->common->w; + step = 1; + end = start + step * state->common->w; + } + + runlen = -1; + for (j = start - step; j <= end; j += step) { + if (j < start || j == end) + val = GRID_EMPTY; + else + val = state->grid[j]; + + if (val == GRID_UNKNOWN) { + runlen = -1; + es->ncontig = 0; + } else if (val == GRID_FULL) { + if (runlen >= 0) + runlen++; + } else if (val == GRID_EMPTY) { + if (runlen > 0) { + if (!errcheck_found_run(es, runlen)) + return TRUE; /* error! */ + } + runlen = 0; + } + } + + /* Signal end-of-row by sending errcheck_found_run the terminating + * zero run, which will be marked as contiguous with the previous + * run if and only if there hasn't been a GRID_UNKNOWN before. */ + if (!errcheck_found_run(es, 0)) + return TRUE; /* error at the last minute! */ + + return FALSE; /* no error */ +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = SIZE(params->w); + *y = SIZE(params->h); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + for (i = 0; i < 3; i++) { + ret[COL_GRID * 3 + i] = 0.3F; + ret[COL_UNKNOWN * 3 + i] = 0.5F; + ret[COL_TEXT * 3 + i] = 0.0F; + ret[COL_FULL * 3 + i] = 0.0F; + ret[COL_EMPTY * 3 + i] = 1.0F; + } + ret[COL_CURSOR * 3 + 0] = 1.0F; + ret[COL_CURSOR * 3 + 1] = 0.25F; + ret[COL_CURSOR * 3 + 2] = 0.25F; + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->started = FALSE; + ds->w = state->common->w; + ds->h = state->common->h; + ds->visible = snewn(ds->w * ds->h, unsigned char); + ds->tilesize = 0; /* not decided yet */ + memset(ds->visible, 255, ds->w * ds->h); + ds->numcolours = snewn(ds->w + ds->h, unsigned char); + memset(ds->numcolours, 255, ds->w + ds->h); + ds->cur_x = ds->cur_y = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +static void grid_square(drawing *dr, game_drawstate *ds, + int y, int x, int state, int cur) +{ + int xl, xr, yt, yb, dx, dy, dw, dh; + + draw_rect(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y), + TILE_SIZE, TILE_SIZE, COL_GRID); + + xl = (x % 5 == 0 ? 1 : 0); + yt = (y % 5 == 0 ? 1 : 0); + xr = (x % 5 == 4 || x == ds->w-1 ? 1 : 0); + yb = (y % 5 == 4 || y == ds->h-1 ? 1 : 0); + + dx = TOCOORD(ds->w, x) + 1 + xl; + dy = TOCOORD(ds->h, y) + 1 + yt; + dw = TILE_SIZE - xl - xr - 1; + dh = TILE_SIZE - yt - yb - 1; + + draw_rect(dr, dx, dy, dw, dh, + (state == GRID_FULL ? COL_FULL : + state == GRID_EMPTY ? COL_EMPTY : COL_UNKNOWN)); + if (cur) { + draw_rect_outline(dr, dx, dy, dw, dh, COL_CURSOR); + draw_rect_outline(dr, dx+1, dy+1, dw-2, dh-2, COL_CURSOR); + } + + draw_update(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y), + TILE_SIZE, TILE_SIZE); +} + +/* + * Draw the numbers for a single row or column. + */ +static void draw_numbers(drawing *dr, game_drawstate *ds, + const game_state *state, int i, int erase, int colour) +{ + int rowlen = state->common->rowlen[i]; + int *rowdata = state->common->rowdata + state->common->rowsize * i; + int nfit; + int j; + + if (erase) { + if (i < state->common->w) { + draw_rect(dr, TOCOORD(state->common->w, i), 0, + TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE, + COL_BACKGROUND); + } else { + draw_rect(dr, 0, TOCOORD(state->common->h, i - state->common->w), + BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE, + COL_BACKGROUND); + } + } + + /* + * Normally I space the numbers out by the same distance as the + * tile size. However, if there are more numbers than available + * spaces, I have to squash them up a bit. + */ + if (i < state->common->w) + nfit = TLBORDER(state->common->h); + else + nfit = TLBORDER(state->common->w); + nfit = max(rowlen, nfit) - 1; + assert(nfit > 0); + + for (j = 0; j < rowlen; j++) { + int x, y; + char str[80]; + + if (i < state->common->w) { + x = TOCOORD(state->common->w, i); + y = BORDER + TILE_SIZE * (TLBORDER(state->common->h)-1); + y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->h)-1) / nfit; + } else { + y = TOCOORD(state->common->h, i - state->common->w); + x = BORDER + TILE_SIZE * (TLBORDER(state->common->w)-1); + x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->w)-1) / nfit; + } + + sprintf(str, "%d", rowdata[j]); + draw_text(dr, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE, + TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, colour, str); + } + + if (i < state->common->w) { + draw_update(dr, TOCOORD(state->common->w, i), 0, + TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE); + } else { + draw_update(dr, 0, TOCOORD(state->common->h, i - state->common->w), + BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE); + } +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int i, j; + int x1, x2, y1, y2; + int cx, cy, cmoved; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed + * and can vary with front ends. To be on the safe side, + * all games should start by drawing a big background- + * colour rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND); + + /* + * Draw the grid outline. + */ + draw_rect(dr, TOCOORD(ds->w, 0) - 1, TOCOORD(ds->h, 0) - 1, + ds->w * TILE_SIZE + 3, ds->h * TILE_SIZE + 3, + COL_GRID); + + ds->started = TRUE; + + draw_update(dr, 0, 0, SIZE(ds->w), SIZE(ds->h)); + } + + if (ui->dragging) { + x1 = min(ui->drag_start_x, ui->drag_end_x); + x2 = max(ui->drag_start_x, ui->drag_end_x); + y1 = min(ui->drag_start_y, ui->drag_end_y); + y2 = max(ui->drag_start_y, ui->drag_end_y); + } else { + x1 = x2 = y1 = y2 = -1; /* placate gcc warnings */ + } + + if (ui->cur_visible) { + cx = ui->cur_x; cy = ui->cur_y; + } else { + cx = cy = -1; + } + cmoved = (cx != ds->cur_x || cy != ds->cur_y); + + /* + * Now draw any grid squares which have changed since last + * redraw. + */ + for (i = 0; i < ds->h; i++) { + for (j = 0; j < ds->w; j++) { + int val, cc = 0; + + /* + * Work out what state this square should be drawn in, + * taking any current drag operation into account. + */ + if (ui->dragging && x1 <= j && j <= x2 && y1 <= i && i <= y2 && + !state->common->immutable[i * state->common->w + j]) + val = ui->state; + else + val = state->grid[i * state->common->w + j]; + + if (cmoved) { + /* the cursor has moved; if we were the old or + * the new cursor position we need to redraw. */ + if (j == cx && i == cy) cc = 1; + if (j == ds->cur_x && i == ds->cur_y) cc = 1; + } + + /* + * Briefly invert everything twice during a completion + * flash. + */ + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3) && + val != GRID_UNKNOWN) + val = (GRID_FULL ^ GRID_EMPTY) ^ val; + + if (ds->visible[i * ds->w + j] != val || cc) { + grid_square(dr, ds, i, j, val, + (j == cx && i == cy)); + ds->visible[i * ds->w + j] = val; + } + } + } + ds->cur_x = cx; ds->cur_y = cy; + + /* + * Redraw any numbers which have changed their colour due to error + * indication. + */ + for (i = 0; i < state->common->w + state->common->h; i++) { + int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT; + if (ds->numcolours[i] != colour) { + draw_numbers(dr, ds, state, i, TRUE, colour); + ds->numcolours[i] = colour; + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 5mm squares by default. + */ + game_compute_size(params, 500, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->common->w, h = state->common->h; + int ink = print_mono_colour(dr, 0); + int x, y, i; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILE_SIZE / 16); + draw_rect_outline(dr, TOCOORD(w, 0), TOCOORD(h, 0), + w*TILE_SIZE, h*TILE_SIZE, ink); + + /* + * Grid. + */ + for (x = 1; x < w; x++) { + print_line_width(dr, TILE_SIZE / (x % 5 ? 128 : 24)); + draw_line(dr, TOCOORD(w, x), TOCOORD(h, 0), + TOCOORD(w, x), TOCOORD(h, h), ink); + } + for (y = 1; y < h; y++) { + print_line_width(dr, TILE_SIZE / (y % 5 ? 128 : 24)); + draw_line(dr, TOCOORD(w, 0), TOCOORD(h, y), + TOCOORD(w, w), TOCOORD(h, y), ink); + } + + /* + * Clues. + */ + for (i = 0; i < state->common->w + state->common->h; i++) + draw_numbers(dr, ds, state, i, FALSE, ink); + + /* + * Solution. + */ + print_line_width(dr, TILE_SIZE / 128); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + if (state->grid[y*w+x] == GRID_FULL) + draw_rect(dr, TOCOORD(w, x), TOCOORD(h, y), + TILE_SIZE, TILE_SIZE, ink); + else if (state->grid[y*w+x] == GRID_EMPTY) + draw_circle(dr, TOCOORD(w, x) + TILE_SIZE/2, + TOCOORD(h, y) + TILE_SIZE/2, + TILE_SIZE/12, ink, ink); + } +} + +#ifdef COMBINED +#define thegame pattern +#endif + +const struct game thegame = { + "Pattern", "games.pattern", "pattern", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + + while (--argc > 0) { + char *p = *++argv; + if (*p == '-') { + if (!strcmp(p, "-v")) { + verbose = TRUE; + } else { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + { + int w = p->w, h = p->h, i, j, max, cluewid = 0; + unsigned char *matrix, *workspace; + unsigned int *changed_h, *changed_w; + int *rowdata; + + matrix = snewn(w*h, unsigned char); + max = max(w, h); + workspace = snewn(max*7, unsigned char); + changed_h = snewn(max+1, unsigned int); + changed_w = snewn(max+1, unsigned int); + rowdata = snewn(max+1, int); + + if (verbose) { + int thiswid; + /* + * Work out the maximum text width of the clue numbers + * in a row or column, so we can print the solver's + * working in a nicely lined up way. + */ + for (i = 0; i < (w+h); i++) { + char buf[80]; + for (thiswid = -1, j = 0; j < s->common->rowlen[i]; j++) + thiswid += sprintf + (buf, " %d", + s->common->rowdata[s->common->rowsize*i+j]); + if (cluewid < thiswid) + cluewid = thiswid; + } + } + + solve_puzzle(s, NULL, w, h, matrix, workspace, + changed_h, changed_w, rowdata, cluewid); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + int c = (matrix[i*w+j] == UNKNOWN ? '?' : + matrix[i*w+j] == BLOCK ? '#' : + matrix[i*w+j] == DOT ? '.' : + '!'); + putchar(c); + } + printf("\n"); + } + } + + return 0; +} + +#endif + +#ifdef STANDALONE_PICTURE_GENERATOR + +/* + * Main program for the standalone picture generator. To use it, + * simply provide it with an XBM-format bitmap file (note XBM, not + * XPM) on standard input, and it will output a game ID in return. + * For example: + * + * $ ./patternpicture < calligraphic-A.xbm + * 15x15:2/4/2/2/2/3/3/3.1/3.1/3.1/11/14/12/6/1/2/2/3/4/5/1.3/2.3/1.3/2.3/1.4/9/1.1.3/2.2.3/5.4/3.2 + * + * That looks easy, of course - all the program has done is to count + * up the clue numbers! But in fact, it's done more than that: it's + * also checked that the result is uniquely soluble from just the + * numbers. If it hadn't been, then it would have also left some + * filled squares in the playing area as extra clues. + * + * $ ./patternpicture < cube.xbm + * 15x15:10/2.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.10/1.1.1/1.1.1/1.1.1/2.1/10/10/1.2/1.1.1/1.1.1/1.1.1/10.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.2/10,TNINzzzzGNzw + * + * This enables a reasonably convenient design workflow for coming up + * with pictorial Pattern puzzles which _are_ uniquely soluble without + * those inelegant pre-filled squares. Fire up a bitmap editor (X11 + * bitmap(1) is good enough), save a trial .xbm, and then test it by + * running a command along the lines of + * + * $ ./pattern $(./patternpicture < test.xbm) + * + * If the resulting window pops up with some pre-filled squares, then + * that tells you which parts of the image are giving rise to + * ambiguities, so try making tweaks in those areas, try the test + * command again, and see if it helps. Once you have a design for + * which the Pattern starting grid comes out empty, there's your game + * ID. + */ + +#include + +int main(int argc, char **argv) +{ + game_params *par; + char *params, *desc; + random_state *rs; + time_t seed = time(NULL); + char buf[4096]; + int i; + int x, y; + + par = default_params(); + if (argc > 1) + decode_params(par, argv[1]); /* get difficulty */ + par->w = par->h = -1; + + /* + * Now read an XBM file from standard input. This is simple and + * hacky and will do very little error detection, so don't feed + * it bogus data. + */ + picture = NULL; + x = y = 0; + while (fgets(buf, sizeof(buf), stdin)) { + buf[strcspn(buf, "\r\n")] = '\0'; + if (!strncmp(buf, "#define", 7)) { + /* + * Lines starting `#define' give the width and height. + */ + char *num = buf + strlen(buf); + char *symend; + + while (num > buf && isdigit((unsigned char)num[-1])) + num--; + symend = num; + while (symend > buf && isspace((unsigned char)symend[-1])) + symend--; + + if (symend-5 >= buf && !strncmp(symend-5, "width", 5)) + par->w = atoi(num); + else if (symend-6 >= buf && !strncmp(symend-6, "height", 6)) + par->h = atoi(num); + } else { + /* + * Otherwise, break the string up into words and take + * any word of the form `0x' plus hex digits to be a + * byte. + */ + char *p, *wordstart; + + if (!picture) { + if (par->w < 0 || par->h < 0) { + printf("failed to read width and height\n"); + return 1; + } + picture = snewn(par->w * par->h, unsigned char); + for (i = 0; i < par->w * par->h; i++) + picture[i] = GRID_UNKNOWN; + } + + p = buf; + while (*p) { + while (*p && (*p == ',' || isspace((unsigned char)*p))) + p++; + wordstart = p; + while (*p && !(*p == ',' || *p == '}' || + isspace((unsigned char)*p))) + p++; + if (*p) + *p++ = '\0'; + + if (wordstart[0] == '0' && + (wordstart[1] == 'x' || wordstart[1] == 'X') && + !wordstart[2 + strspn(wordstart+2, + "0123456789abcdefABCDEF")]) { + unsigned long byte = strtoul(wordstart+2, NULL, 16); + for (i = 0; i < 8; i++) { + int bit = (byte >> i) & 1; + if (y < par->h && x < par->w) + picture[y * par->w + x] = + bit ? GRID_FULL : GRID_EMPTY; + x++; + } + + if (x >= par->w) { + x = 0; + y++; + } + } + } + } + } + + for (i = 0; i < par->w * par->h; i++) + if (picture[i] == GRID_UNKNOWN) { + fprintf(stderr, "failed to read enough bitmap data\n"); + return 1; + } + + rs = random_new((void*)&seed, sizeof(time_t)); + + desc = new_game_desc(par, rs, NULL, FALSE); + params = encode_params(par, FALSE); + printf("%s:%s\n", params, desc); + + sfree(desc); + sfree(params); + free_params(par); + random_free(rs); + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/pearl.R b/apps/plugins/puzzles/pearl.R new file mode 100644 index 0000000000..79bc7325c7 --- /dev/null +++ b/apps/plugins/puzzles/pearl.R @@ -0,0 +1,23 @@ +# -*- makefile -*- + +PEARL_EXTRA = dsf tree234 grid penrose loopgen tdq + +pearl : [X] GTK COMMON pearl PEARL_EXTRA pearl-icon|no-icon +pearl : [G] WINDOWS COMMON pearl PEARL_EXTRA pearl.res? + +pearlbench : [U] pearl[STANDALONE_SOLVER] PEARL_EXTRA STANDALONE m.lib +pearlbench : [C] pearl[STANDALONE_SOLVER] PEARL_EXTRA STANDALONE + +ALL += pearl[COMBINED] PEARL_EXTRA + +!begin am gtk +GAMES += pearl +!end + +!begin >list.c + A(pearl) \ +!end + +!begin >gamedesc.txt +pearl:pearl.exe:Pearl:Loop-drawing puzzle:Draw a single closed loop, given clues about corner and straight squares. +!end diff --git a/apps/plugins/puzzles/pearl.c b/apps/plugins/puzzles/pearl.c new file mode 100644 index 0000000000..59effeda40 --- /dev/null +++ b/apps/plugins/puzzles/pearl.c @@ -0,0 +1,2772 @@ +/* + * pearl.c: Nikoli's `Masyu' puzzle. + */ + +/* + * TODO: + * + * - The current keyboard cursor mechanism works well on ordinary PC + * keyboards, but for platforms with only arrow keys and a select + * button or two, we may at some point need a simpler one which can + * handle 'x' markings without needing shift keys. For instance, a + * cursor with twice the grid resolution, so that it can range + * across face centres, edge centres and vertices; 'clicks' on face + * centres begin a drag as currently, clicks on edges toggle + * markings, and clicks on vertices are ignored (but it would be + * too confusing not to let the cursor rest on them). But I'm + * pretty sure that would be less pleasant to play on a full + * keyboard, so probably a #ifdef would be the thing. + * + * - Generation is still pretty slow, due to difficulty coming up in + * the first place with a loop that makes a soluble puzzle even + * with all possible clues filled in. + * + A possible alternative strategy to further tuning of the + * existing loop generator would be to throw the entire + * mechanism out and instead write a different generator from + * scratch which evolves the solution along with the puzzle: + * place a few clues, nail down a bit of the loop, place another + * clue, nail down some more, etc. However, I don't have a + * detailed plan for any such mechanism, so it may be a pipe + * dream. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "grid.h" +#include "loopgen.h" + +#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0) + +#define NOCLUE 0 +#define CORNER 1 +#define STRAIGHT 2 + +#define R 1 +#define U 2 +#define L 4 +#define D 8 + +#define DX(d) ( ((d)==R) - ((d)==L) ) +#define DY(d) ( ((d)==D) - ((d)==U) ) + +#define F(d) (((d << 2) | (d >> 2)) & 0xF) +#define C(d) (((d << 3) | (d >> 1)) & 0xF) +#define A(d) (((d << 1) | (d >> 3)) & 0xF) + +#define LR (L | R) +#define RL (R | L) +#define UD (U | D) +#define DU (D | U) +#define LU (L | U) +#define UL (U | L) +#define LD (L | D) +#define DL (D | L) +#define RU (R | U) +#define UR (U | R) +#define RD (R | D) +#define DR (D | R) +#define BLANK 0 +#define UNKNOWN 15 + +#define bLR (1 << LR) +#define bRL (1 << RL) +#define bUD (1 << UD) +#define bDU (1 << DU) +#define bLU (1 << LU) +#define bUL (1 << UL) +#define bLD (1 << LD) +#define bDL (1 << DL) +#define bRU (1 << RU) +#define bUR (1 << UR) +#define bRD (1 << RD) +#define bDR (1 << DR) +#define bBLANK (1 << BLANK) + +enum { + COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT, + COL_CURSOR_BACKGROUND = COL_LOWLIGHT, + COL_BLACK, COL_WHITE, + COL_ERROR, COL_GRID, COL_FLASH, + COL_DRAGON, COL_DRAGOFF, + NCOLOURS +}; + +/* Macro ickery copied from slant.c */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(TRICKY,Tricky,t) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const pearl_diffnames[] = { DIFFLIST(TITLE) "(count)" }; +static char const pearl_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +struct game_params { + int w, h; + int difficulty; + int nosolve; /* XXX remove me! */ +}; + +struct shared_state { + int w, h, sz; + char *clues; /* size w*h */ + int refcnt; +}; + +#define INGRID(state, gx, gy) ((gx) >= 0 && (gx) < (state)->shared->w && \ + (gy) >= 0 && (gy) < (state)->shared->h) +struct game_state { + struct shared_state *shared; + char *lines; /* size w*h: lines placed */ + char *errors; /* size w*h: errors detected */ + char *marks; /* size w*h: 'no line here' marks placed. */ + int completed, used_solve; +}; + +#define DEFAULT_PRESET 3 + +static const struct game_params pearl_presets[] = { + {6, 6, DIFF_EASY}, + {6, 6, DIFF_TRICKY}, + {8, 8, DIFF_EASY}, + {8, 8, DIFF_TRICKY}, + {10, 10, DIFF_EASY}, + {10, 10, DIFF_TRICKY}, + {12, 8, DIFF_EASY}, + {12, 8, DIFF_TRICKY}, +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + *ret = pearl_presets[DEFAULT_PRESET]; + ret->nosolve = FALSE; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[64]; + + if (i < 0 || i >= lenof(pearl_presets)) return FALSE; + + ret = default_params(); + *ret = pearl_presets[i]; /* struct copy */ + *params = ret; + + sprintf(buf, "%dx%d %s", + pearl_presets[i].w, pearl_presets[i].h, + pearl_diffnames[pearl_presets[i].difficulty]); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + while (*string && isdigit((unsigned char) *string)) ++string; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + + ret->difficulty = DIFF_EASY; + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == pearl_diffchars[i]) + ret->difficulty = i; + if (*string) string++; + } + + ret->nosolve = FALSE; + if (*string == 'n') { + ret->nosolve = TRUE; + string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[256]; + sprintf(buf, "%dx%d", params->w, params->h); + if (full) + sprintf(buf + strlen(buf), "d%c%s", + pearl_diffchars[params->difficulty], + params->nosolve ? "n" : ""); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[64]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->difficulty; + + ret[3].name = "Allow unsoluble"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = params->nosolve; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->difficulty = cfg[2].ival; + ret->nosolve = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 5) return "Width must be at least five"; + if (params->h < 5) return "Height must be at least five"; + if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT) + return "Unknown difficulty level"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +int pearl_solve(int w, int h, char *clues, char *result, + int difficulty, int partial) +{ + int W = 2*w+1, H = 2*h+1; + short *workspace; + int *dsf, *dsfsize; + int x, y, b, d; + int ret = -1; + + /* + * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature + * of the square (x,y), as a logical OR of bitfields. + * + * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates + * whether the horizontal edge between (x,y) and (x+1,y) is + * connected (1), disconnected (2) or unknown (3). + * + * workspace[(2*y+1)*W+(2*x)], indicates the same about the + * vertical edge between (x,y) and (x,y+1). + * + * Initially, every square is considered capable of being in + * any of the seven possible states (two straights, four + * corners and empty), except those corresponding to clue + * squares which are more restricted. + * + * Initially, all edges are unknown, except the ones around the + * grid border which are known to be disconnected. + */ + workspace = snewn(W*H, short); + for (x = 0; x < W*H; x++) + workspace[x] = 0; + /* Square states */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + switch (clues[y*w+x]) { + case CORNER: + workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD; + break; + case STRAIGHT: + workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD; + break; + default: + workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK; + break; + } + /* Horizontal edges */ + for (y = 0; y <= h; y++) + for (x = 0; x < w; x++) + workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3); + /* Vertical edges */ + for (y = 0; y < h; y++) + for (x = 0; x <= w; x++) + workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3); + + /* + * We maintain a dsf of connected squares, together with a + * count of the size of each equivalence class. + */ + dsf = snewn(w*h, int); + dsfsize = snewn(w*h, int); + + /* + * Now repeatedly try to find something we can do. + */ + while (1) { + int done_something = FALSE; + +#ifdef SOLVER_DIAGNOSTICS + for (y = 0; y < H; y++) { + for (x = 0; x < W; x++) + printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]); + printf("\n"); + } +#endif + + /* + * Go through the square state words, and discard any + * square state which is inconsistent with known facts + * about the edges around the square. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + for (b = 0; b < 0xD; b++) + if (workspace[(2*y+1)*W+(2*x+1)] & (1<= 0) { + /* + * This square state would form + * a loop on equivalence class + * e. Measure the size of that + * loop, and see if it's a + * shortcut. + */ + int loopsize = dsfsize[e]; + if (e != ae) + loopsize++;/* add the square itself */ + if (loopsize < nonblanks) { + /* + * It is! Mark this square + * state invalid. + */ + workspace[y*W+x] &= ~(1<= 0); + return ret; +} + +/* ---------------------------------------------------------------------- + * Loop generator. + */ + +/* + * We use the loop generator code from loopy, hard-coding to a square + * grid of the appropriate size. Knowing the grid layout and the tile + * size we can shrink that to our small grid and then make our line + * layout from the face colour info. + * + * We provide a bias function to the loop generator which tries to + * bias in favour of loops with more scope for Pearl black clues. This + * seems to improve the success rate of the puzzle generator, in that + * such loops have a better chance of being soluble with all valid + * clues put in. + */ + +struct pearl_loopgen_bias_ctx { + /* + * Our bias function counts the number of 'black clue' corners + * (i.e. corners adjacent to two straights) in both the + * BLACK/nonBLACK and WHITE/nonWHITE boundaries. In order to do + * this, we must: + * + * - track the edges that are part of each of those loops + * - track the types of vertex in each loop (corner, straight, + * none) + * - track the current black-clue status of each vertex in each + * loop. + * + * Each of these chunks of data is updated incrementally from the + * previous one, to avoid slowdown due to the bias function + * rescanning the whole grid every time it's called. + * + * So we need a lot of separate arrays, plus a tdq for each one, + * and we must repeat it all twice for the BLACK and WHITE + * boundaries. + */ + struct pearl_loopgen_bias_ctx_boundary { + int colour; /* FACE_WHITE or FACE_BLACK */ + + char *edges; /* is each edge part of the loop? */ + tdq *edges_todo; + + char *vertextypes; /* bits 0-3 == outgoing edge bitmap; + * bit 4 set iff corner clue. + * Hence, 0 means non-vertex; + * nonzero but bit 4 zero = straight. */ + int *neighbour[2]; /* indices of neighbour vertices in loop */ + tdq *vertextypes_todo; + + char *blackclues; /* is each vertex a black clue site? */ + tdq *blackclues_todo; + } boundaries[2]; /* boundaries[0]=WHITE, [1]=BLACK */ + + char *faces; /* remember last-seen colour of each face */ + tdq *faces_todo; + + int score; + + grid *g; +}; +int pearl_loopgen_bias(void *vctx, char *board, int face) +{ + struct pearl_loopgen_bias_ctx *ctx = (struct pearl_loopgen_bias_ctx *)vctx; + grid *g = ctx->g; + int oldface, newface; + int i, j, k; + + tdq_add(ctx->faces_todo, face); + while ((j = tdq_remove(ctx->faces_todo)) >= 0) { + oldface = ctx->faces[j]; + ctx->faces[j] = newface = board[j]; + for (i = 0; i < 2; i++) { + struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i]; + int c = b->colour; + + /* + * If the face has changed either from or to colour c, we need + * to reprocess the edges for this boundary. + */ + if (oldface == c || newface == c) { + grid_face *f = &g->faces[face]; + for (k = 0; k < f->order; k++) + tdq_add(b->edges_todo, f->edges[k] - g->edges); + } + } + } + + for (i = 0; i < 2; i++) { + struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i]; + int c = b->colour; + + /* + * Go through the to-do list of edges. For each edge, decide + * anew whether it's part of this boundary or not. Any edge + * that changes state has to have both its endpoints put on + * the vertextypes_todo list. + */ + while ((j = tdq_remove(b->edges_todo)) >= 0) { + grid_edge *e = &g->edges[j]; + int fc1 = e->face1 ? board[e->face1 - g->faces] : FACE_BLACK; + int fc2 = e->face2 ? board[e->face2 - g->faces] : FACE_BLACK; + int oldedge = b->edges[j]; + int newedge = (fc1==c) ^ (fc2==c); + if (oldedge != newedge) { + b->edges[j] = newedge; + tdq_add(b->vertextypes_todo, e->dot1 - g->dots); + tdq_add(b->vertextypes_todo, e->dot2 - g->dots); + } + } + + /* + * Go through the to-do list of vertices whose types need + * refreshing. For each one, decide whether it's a corner, a + * straight, or a vertex not in the loop, and in the former + * two cases also work out the indices of its neighbour + * vertices along the loop. Any vertex that changes state must + * be put back on the to-do list for deciding if it's a black + * clue site, and so must its two new neighbours _and_ its two + * old neighbours. + */ + while ((j = tdq_remove(b->vertextypes_todo)) >= 0) { + grid_dot *d = &g->dots[j]; + int neighbours[2], type = 0, n = 0; + + for (k = 0; k < d->order; k++) { + grid_edge *e = d->edges[k]; + grid_dot *d2 = (e->dot1 == d ? e->dot2 : e->dot1); + /* dir == 0,1,2,3 for an edge going L,U,R,D */ + int dir = (d->y == d2->y) + 2*(d->x+d->y > d2->x+d2->y); + int ei = e - g->edges; + if (b->edges[ei]) { + type |= 1 << dir; + neighbours[n] = d2 - g->dots; + n++; + } + } + + /* + * Decide if it's a corner, and set the corner flag if so. + */ + if (type != 0 && type != 0x5 && type != 0xA) + type |= 0x10; + + if (type != b->vertextypes[j]) { + /* + * Recompute old neighbours, if any. + */ + if (b->vertextypes[j]) { + tdq_add(b->blackclues_todo, b->neighbour[0][j]); + tdq_add(b->blackclues_todo, b->neighbour[1][j]); + } + /* + * Recompute this vertex. + */ + tdq_add(b->blackclues_todo, j); + b->vertextypes[j] = type; + /* + * Recompute new neighbours, if any. + */ + if (b->vertextypes[j]) { + b->neighbour[0][j] = neighbours[0]; + b->neighbour[1][j] = neighbours[1]; + tdq_add(b->blackclues_todo, b->neighbour[0][j]); + tdq_add(b->blackclues_todo, b->neighbour[1][j]); + } + } + } + + /* + * Go through the list of vertices which we must check to see + * if they're black clue sites. Each one is a black clue site + * iff it is a corner and its loop neighbours are non-corners. + * Adjust the running total of black clues we've counted. + */ + while ((j = tdq_remove(b->blackclues_todo)) >= 0) { + ctx->score -= b->blackclues[j]; + b->blackclues[j] = ((b->vertextypes[j] & 0x10) && + !((b->vertextypes[b->neighbour[0][j]] | + b->vertextypes[b->neighbour[1][j]]) + & 0x10)); + ctx->score += b->blackclues[j]; + } + } + + return ctx->score; +} + +void pearl_loopgen(int w, int h, char *lines, random_state *rs) +{ + grid *g = grid_new(GRID_SQUARE, w-1, h-1, NULL); + char *board = snewn(g->num_faces, char); + int i, s = g->tilesize; + struct pearl_loopgen_bias_ctx biasctx; + + memset(lines, 0, w*h); + + /* + * Initialise the context for the bias function. Initially we fill + * all the to-do lists, so that the first call will scan + * everything; thereafter the lists stay empty so we make + * incremental changes. + */ + biasctx.g = g; + biasctx.faces = snewn(g->num_faces, char); + biasctx.faces_todo = tdq_new(g->num_faces); + tdq_fill(biasctx.faces_todo); + biasctx.score = 0; + memset(biasctx.faces, FACE_GREY, g->num_faces); + for (i = 0; i < 2; i++) { + biasctx.boundaries[i].edges = snewn(g->num_edges, char); + memset(biasctx.boundaries[i].edges, 0, g->num_edges); + biasctx.boundaries[i].edges_todo = tdq_new(g->num_edges); + tdq_fill(biasctx.boundaries[i].edges_todo); + biasctx.boundaries[i].vertextypes = snewn(g->num_dots, char); + memset(biasctx.boundaries[i].vertextypes, 0, g->num_dots); + biasctx.boundaries[i].neighbour[0] = snewn(g->num_dots, int); + biasctx.boundaries[i].neighbour[1] = snewn(g->num_dots, int); + biasctx.boundaries[i].vertextypes_todo = tdq_new(g->num_dots); + tdq_fill(biasctx.boundaries[i].vertextypes_todo); + biasctx.boundaries[i].blackclues = snewn(g->num_dots, char); + memset(biasctx.boundaries[i].blackclues, 0, g->num_dots); + biasctx.boundaries[i].blackclues_todo = tdq_new(g->num_dots); + tdq_fill(biasctx.boundaries[i].blackclues_todo); + } + biasctx.boundaries[0].colour = FACE_WHITE; + biasctx.boundaries[1].colour = FACE_BLACK; + generate_loop(g, board, rs, pearl_loopgen_bias, &biasctx); + sfree(biasctx.faces); + tdq_free(biasctx.faces_todo); + for (i = 0; i < 2; i++) { + sfree(biasctx.boundaries[i].edges); + tdq_free(biasctx.boundaries[i].edges_todo); + sfree(biasctx.boundaries[i].vertextypes); + sfree(biasctx.boundaries[i].neighbour[0]); + sfree(biasctx.boundaries[i].neighbour[1]); + tdq_free(biasctx.boundaries[i].vertextypes_todo); + sfree(biasctx.boundaries[i].blackclues); + tdq_free(biasctx.boundaries[i].blackclues_todo); + } + + for (i = 0; i < g->num_edges; i++) { + grid_edge *e = g->edges + i; + enum face_colour c1 = FACE_COLOUR(e->face1); + enum face_colour c2 = FACE_COLOUR(e->face2); + assert(c1 != FACE_GREY); + assert(c2 != FACE_GREY); + if (c1 != c2) { + /* This grid edge is on the loop: lay line along it */ + int x1 = e->dot1->x/s, y1 = e->dot1->y/s; + int x2 = e->dot2->x/s, y2 = e->dot2->y/s; + + /* (x1,y1) and (x2,y2) are now in our grid coords (0-w,0-h). */ + if (x1 == x2) { + if (y1 > y2) SWAP(y1,y2); + + assert(y1+1 == y2); + lines[y1*w+x1] |= D; + lines[y2*w+x1] |= U; + } else if (y1 == y2) { + if (x1 > x2) SWAP(x1,x2); + + assert(x1+1 == x2); + lines[y1*w+x1] |= R; + lines[y1*w+x2] |= L; + } else + assert(!"grid with diagonal coords?!"); + } + } + + grid_free(g); + sfree(board); + +#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS + printf("as returned:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int type = lines[y*w+x]; + char s[5], *p = s; + if (type & L) *p++ = 'L'; + if (type & R) *p++ = 'R'; + if (type & U) *p++ = 'U'; + if (type & D) *p++ = 'D'; + *p = '\0'; + printf("%3s", s); + } + printf("\n"); + } + printf("\n"); +#endif +} + +static int new_clues(const game_params *params, random_state *rs, + char *clues, char *grid) +{ + int w = params->w, h = params->h, diff = params->difficulty; + int ngen = 0, x, y, d, ret, i; + + + /* + * Difficulty exception: 5x5 Tricky is not generable (the + * generator will spin forever trying) and so we fudge it to Easy. + */ + if (w == 5 && h == 5 && diff > DIFF_EASY) + diff = DIFF_EASY; + + while (1) { + ngen++; + pearl_loopgen(w, h, grid, rs); + +#ifdef GENERATION_DIAGNOSTICS + printf("grid array:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int type = grid[y*w+x]; + char s[5], *p = s; + if (type & L) *p++ = 'L'; + if (type & R) *p++ = 'R'; + if (type & U) *p++ = 'U'; + if (type & D) *p++ = 'D'; + *p = '\0'; + printf("%2s ", s); + } + printf("\n"); + } + printf("\n"); +#endif + + /* + * Set up the maximal clue array. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int type = grid[y*w+x]; + + clues[y*w+x] = NOCLUE; + + if ((bLR|bUD) & (1 << type)) { + /* + * This is a straight; see if it's a viable + * candidate for a straight clue. It qualifies if + * at least one of the squares it connects to is a + * corner. + */ + for (d = 1; d <= 8; d += d) if (type & d) { + int xx = x + DX(d), yy = y + DY(d); + assert(xx >= 0 && xx < w && yy >= 0 && yy < h); + if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx])) + break; + } + if (d <= 8) /* we found one */ + clues[y*w+x] = STRAIGHT; + } else if ((bLU|bLD|bRU|bRD) & (1 << type)) { + /* + * This is a corner; see if it's a viable candidate + * for a corner clue. It qualifies if all the + * squares it connects to are straights. + */ + for (d = 1; d <= 8; d += d) if (type & d) { + int xx = x + DX(d), yy = y + DY(d); + assert(xx >= 0 && xx < w && yy >= 0 && yy < h); + if (!((bLR|bUD) & (1 << grid[yy*w+xx]))) + break; + } + if (d > 8) /* we didn't find a counterexample */ + clues[y*w+x] = CORNER; + } + } + +#ifdef GENERATION_DIAGNOSTICS + printf("clue array:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + printf("%c", " *O"[(unsigned char)clues[y*w+x]]); + } + printf("\n"); + } + printf("\n"); +#endif + + if (!params->nosolve) { + int *cluespace, *straights, *corners; + int nstraights, ncorners, nstraightpos, ncornerpos; + + /* + * See if we can solve the puzzle just like this. + */ + ret = pearl_solve(w, h, clues, grid, diff, FALSE); + assert(ret > 0); /* shouldn't be inconsistent! */ + if (ret != 1) + continue; /* go round and try again */ + + /* + * Check this puzzle isn't too easy. + */ + if (diff > DIFF_EASY) { + ret = pearl_solve(w, h, clues, grid, diff-1, FALSE); + assert(ret > 0); + if (ret == 1) + continue; /* too easy: try again */ + } + + /* + * Now shuffle the grid points and gradually remove the + * clues to find a minimal set which still leaves the + * puzzle soluble. + * + * We preferentially attempt to remove whichever type of + * clue is currently most numerous, to combat a general + * tendency of plain random generation to bias in favour + * of many white clues and few black. + * + * 'nstraights' and 'ncorners' count the number of clues + * of each type currently remaining in the grid; + * 'nstraightpos' and 'ncornerpos' count the clues of each + * type we have left to try to remove. (Clues which we + * have tried and failed to remove are counted by the + * former but not the latter.) + */ + cluespace = snewn(w*h, int); + straights = cluespace; + nstraightpos = 0; + for (i = 0; i < w*h; i++) + if (clues[i] == STRAIGHT) + straights[nstraightpos++] = i; + corners = straights + nstraightpos; + ncornerpos = 0; + for (i = 0; i < w*h; i++) + if (clues[i] == STRAIGHT) + corners[ncornerpos++] = i; + nstraights = nstraightpos; + ncorners = ncornerpos; + + shuffle(straights, nstraightpos, sizeof(*straights), rs); + shuffle(corners, ncornerpos, sizeof(*corners), rs); + while (nstraightpos > 0 || ncornerpos > 0) { + int cluepos; + int clue; + + /* + * Decide which clue to try to remove next. If both + * types are available, we choose whichever kind is + * currently overrepresented; otherwise we take + * whatever we can get. + */ + if (nstraightpos > 0 && ncornerpos > 0) { + if (nstraights >= ncorners) + cluepos = straights[--nstraightpos]; + else + cluepos = straights[--ncornerpos]; + } else { + if (nstraightpos > 0) + cluepos = straights[--nstraightpos]; + else + cluepos = straights[--ncornerpos]; + } + + y = cluepos / w; + x = cluepos % w; + + clue = clues[y*w+x]; + clues[y*w+x] = 0; /* try removing this clue */ + + ret = pearl_solve(w, h, clues, grid, diff, FALSE); + assert(ret > 0); + if (ret != 1) + clues[y*w+x] = clue; /* oops, put it back again */ + } + sfree(cluespace); + } + +#ifdef FINISHED_PUZZLE + printf("clue array:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + printf("%c", " *O"[(unsigned char)clues[y*w+x]]); + } + printf("\n"); + } + printf("\n"); +#endif + + break; /* got it */ + } + + debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h)); + + return ngen; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + char *grid, *clues; + char *desc; + int w = params->w, h = params->h, i, j; + + grid = snewn(w*h, char); + clues = snewn(w*h, char); + + new_clues(params, rs, clues, grid); + + desc = snewn(w * h + 1, char); + for (i = j = 0; i < w*h; i++) { + if (clues[i] == NOCLUE && j > 0 && + desc[j-1] >= 'a' && desc[j-1] < 'z') + desc[j-1]++; + else if (clues[i] == NOCLUE) + desc[j++] = 'a'; + else if (clues[i] == CORNER) + desc[j++] = 'B'; + else if (clues[i] == STRAIGHT) + desc[j++] = 'W'; + } + desc[j] = '\0'; + + *aux = snewn(w*h+1, char); + for (i = 0; i < w*h; i++) + (*aux)[i] = (grid[i] < 10) ? (grid[i] + '0') : (grid[i] + 'A' - 10); + (*aux)[w*h] = '\0'; + + sfree(grid); + sfree(clues); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int i, sizesofar; + const int totalsize = params->w * params->h; + + sizesofar = 0; + for (i = 0; desc[i]; i++) { + if (desc[i] >= 'a' && desc[i] <= 'z') + sizesofar += desc[i] - 'a' + 1; + else if (desc[i] == 'B' || desc[i] == 'W') + sizesofar++; + else + return "unrecognised character in string"; + } + + if (sizesofar > totalsize) + return "string too long"; + else if (sizesofar < totalsize) + return "string too short"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int i, j, sz = params->w*params->h; + + state->completed = state->used_solve = FALSE; + state->shared = snew(struct shared_state); + + state->shared->w = params->w; + state->shared->h = params->h; + state->shared->sz = sz; + state->shared->refcnt = 1; + state->shared->clues = snewn(sz, char); + for (i = j = 0; desc[i]; i++) { + assert(j < sz); + if (desc[i] >= 'a' && desc[i] <= 'z') { + int n = desc[i] - 'a' + 1; + assert(j + n <= sz); + while (n-- > 0) + state->shared->clues[j++] = NOCLUE; + } else if (desc[i] == 'B') { + state->shared->clues[j++] = CORNER; + } else if (desc[i] == 'W') { + state->shared->clues[j++] = STRAIGHT; + } + } + + state->lines = snewn(sz, char); + state->errors = snewn(sz, char); + state->marks = snewn(sz, char); + for (i = 0; i < sz; i++) + state->lines[i] = state->errors[i] = state->marks[i] = BLANK; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + int sz = state->shared->sz, i; + + ret->shared = state->shared; + ret->completed = state->completed; + ret->used_solve = state->used_solve; + ++ret->shared->refcnt; + + ret->lines = snewn(sz, char); + ret->errors = snewn(sz, char); + ret->marks = snewn(sz, char); + for (i = 0; i < sz; i++) { + ret->lines[i] = state->lines[i]; + ret->errors[i] = state->errors[i]; + ret->marks[i] = state->marks[i]; + } + + return ret; +} + +static void free_game(game_state *state) +{ + assert(state); + if (--state->shared->refcnt == 0) { + sfree(state->shared->clues); + sfree(state->shared); + } + sfree(state->lines); + sfree(state->errors); + sfree(state->marks); + sfree(state); +} + +static char nbits[16] = { 0, 1, 1, 2, + 1, 2, 2, 3, + 1, 2, 2, 3, + 2, 3, 3, 4 }; +#define NBITS(l) ( ((l) < 0 || (l) > 15) ? 4 : nbits[l] ) + +#define ERROR_CLUE 16 + +static void dsf_update_completion(game_state *state, int ax, int ay, char dir, + int *dsf) +{ + int w = state->shared->w /*, h = state->shared->h */; + int ac = ay*w+ax, bx, by, bc; + + if (!(state->lines[ac] & dir)) return; /* no link */ + bx = ax + DX(dir); by = ay + DY(dir); + + assert(INGRID(state, bx, by)); /* should not have a link off grid */ + + bc = by*w+bx; + assert(state->lines[bc] & F(dir)); /* should have reciprocal link */ + if (!(state->lines[bc] & F(dir))) return; + + dsf_merge(dsf, ac, bc); +} + +static void check_completion(game_state *state, int mark) +{ + int w = state->shared->w, h = state->shared->h, x, y, i, d; + int had_error = FALSE; + int *dsf, *component_state; + int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize; + enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY }; + + if (mark) { + for (i = 0; i < w*h; i++) { + state->errors[i] = 0; + } + } + +#define ERROR(x,y,e) do { had_error = TRUE; if (mark) state->errors[(y)*w+(x)] |= (e); } while(0) + + /* + * Analyse the solution into loops, paths and stranger things. + * Basic strategy here is all the same as in Loopy - see the big + * comment in loopy.c's check_completion() - and for exactly the + * same reasons, since Loopy and Pearl have basically the same + * form of expected solution. + */ + dsf = snew_dsf(w*h); + + /* Build the dsf. */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + dsf_update_completion(state, x, y, R, dsf); + dsf_update_completion(state, x, y, D, dsf); + } + } + + /* Initialise a state variable for each connected component. */ + component_state = snewn(w*h, int); + for (i = 0; i < w*h; i++) { + if (dsf_canonify(dsf, i) == i) + component_state[i] = COMP_LOOP; + else + component_state[i] = COMP_NONE; + } + + /* + * Classify components, and mark errors where a square has more + * than two line segments. + */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + int type = state->lines[y*w+x]; + int degree = NBITS(type); + int comp = dsf_canonify(dsf, y*w+x); + if (degree > 2) { + ERROR(x,y,type); + component_state[comp] = COMP_SILLY; + } else if (degree == 0) { + component_state[comp] = COMP_EMPTY; + } else if (degree == 1) { + if (component_state[comp] != COMP_SILLY) + component_state[comp] = COMP_PATH; + } + } + } + + /* Count the components, and find the largest sensible one. */ + nsilly = nloop = npath = 0; + total_pathsize = 0; + largest_comp = largest_size = -1; + for (i = 0; i < w*h; i++) { + if (component_state[i] == COMP_SILLY) { + nsilly++; + } else if (component_state[i] == COMP_PATH) { + total_pathsize += dsf_size(dsf, i); + npath = 1; + } else if (component_state[i] == COMP_LOOP) { + int this_size; + + nloop++; + + if ((this_size = dsf_size(dsf, i)) > largest_size) { + largest_comp = i; + largest_size = this_size; + } + } + } + if (largest_size < total_pathsize) { + largest_comp = -1; /* means the paths */ + largest_size = total_pathsize; + } + + if (nloop > 0 && nloop + npath > 1) { + /* + * If there are at least two sensible components including at + * least one loop, highlight every sensible component that is + * not the largest one. + */ + for (i = 0; i < w*h; i++) { + int comp = dsf_canonify(dsf, i); + if (component_state[comp] == COMP_PATH) + comp = -1; /* part of the 'all paths' quasi-component */ + if ((component_state[comp] == COMP_PATH && + -1 != largest_comp) || + (component_state[comp] == COMP_LOOP && + comp != largest_comp)) + ERROR(i%w, i/w, state->lines[i]); + } + } + + /* Now we've finished with the dsf and component states. The only + * thing we'll need to remember later on is whether all edges were + * part of a single loop, for which our counter variables + * nsilly,nloop,npath are enough. */ + sfree(component_state); + sfree(dsf); + + /* + * Check that no clues are contradicted. This code is similar to + * the code that sets up the maximal clue array for any given + * loop. + */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + int type = state->lines[y*w+x]; + if (state->shared->clues[y*w+x] == CORNER) { + /* Supposed to be a corner: will find a contradiction if + * it actually contains a straight line, or if it touches any + * corners. */ + if ((bLR|bUD) & (1 << type)) { + ERROR(x,y,ERROR_CLUE); /* actually straight */ + } + for (d = 1; d <= 8; d += d) if (type & d) { + int xx = x + DX(d), yy = y + DY(d); + if (!INGRID(state, xx, yy)) { + ERROR(x,y,d); /* leads off grid */ + } else { + if ((bLU|bLD|bRU|bRD) & (1 << state->lines[yy*w+xx])) { + ERROR(x,y,ERROR_CLUE); /* touches corner */ + } + } + } + } else if (state->shared->clues[y*w+x] == STRAIGHT) { + /* Supposed to be straight: will find a contradiction if + * it actually contains a corner, or if it only touches + * straight lines. */ + if ((bLU|bLD|bRU|bRD) & (1 << type)) { + ERROR(x,y,ERROR_CLUE); /* actually a corner */ + } + i = 0; + for (d = 1; d <= 8; d += d) if (type & d) { + int xx = x + DX(d), yy = y + DY(d); + if (!INGRID(state, xx, yy)) { + ERROR(x,y,d); /* leads off grid */ + } else { + if ((bLR|bUD) & (1 << state->lines[yy*w+xx])) + i++; /* a straight */ + } + } + if (i >= 2 && NBITS(type) >= 2) { + ERROR(x,y,ERROR_CLUE); /* everything touched is straight */ + } + } + } + } + + if (nloop == 1 && nsilly == 0 && npath == 0) { + /* + * If there's exactly one loop (so that the puzzle is at least + * potentially complete), we need to ensure it hasn't left any + * clue out completely. + */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + if (state->lines[y*w+x] == BLANK) { + if (state->shared->clues[y*w+x] != NOCLUE) { + /* the loop doesn't include this clue square! */ + ERROR(x, y, ERROR_CLUE); + } + } + } + } + + /* + * But if not, then we're done! + */ + if (!had_error) + state->completed = TRUE; + } +} + +/* completion check: + * + * - no clues must be contradicted (highlight clue itself in error if so) + * - if there is a closed loop it must include every line segment laid + * - if there's a smaller closed loop then highlight whole loop as error + * - no square must have more than 2 lines radiating from centre point + * (highlight all lines in that square as error if so) + */ + +static char *solve_for_diff(game_state *state, char *old_lines, char *new_lines) +{ + int w = state->shared->w, h = state->shared->h, i; + char *move = snewn(w*h*40, char), *p = move; + + *p++ = 'S'; + for (i = 0; i < w*h; i++) { + if (old_lines[i] != new_lines[i]) { + p += sprintf(p, ";R%d,%d,%d", new_lines[i], i%w, i/w); + } + } + *p++ = '\0'; + move = sresize(move, p - move, char); + + return move; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved = dup_game(state); + int i, ret, sz = state->shared->sz; + char *move; + + if (aux) { + for (i = 0; i < sz; i++) { + if (aux[i] >= '0' && aux[i] <= '9') + solved->lines[i] = aux[i] - '0'; + else if (aux[i] >= 'A' && aux[i] <= 'F') + solved->lines[i] = aux[i] - 'A' + 10; + else { + *error = "invalid char in aux"; + move = NULL; + goto done; + } + } + ret = 1; + } else { + /* Try to solve with present (half-solved) state first: if there's no + * solution from there go back to original state. */ + ret = pearl_solve(currstate->shared->w, currstate->shared->h, + currstate->shared->clues, solved->lines, + DIFFCOUNT, FALSE); + if (ret < 1) + ret = pearl_solve(state->shared->w, state->shared->h, + state->shared->clues, solved->lines, + DIFFCOUNT, FALSE); + + } + + if (ret < 1) { + *error = "Unable to find solution"; + move = NULL; + } else { + move = solve_for_diff(solved, currstate->lines, solved->lines); + } + +done: + free_game(solved); + return move; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->shared->w, h = state->shared->h, cw = 4, ch = 2; + int gw = cw*(w-1) + 2, gh = ch*(h-1) + 1, len = gw * gh, r, c, j; + char *board = snewn(len + 1, char); + + assert(board); + memset(board, ' ', len); + + for (r = 0; r < h; ++r) { + for (c = 0; c < w; ++c) { + int i = r*w + c, cell = r*ch*gw + c*cw; + board[cell] = "+BW"[(unsigned char)state->shared->clues[i]]; + if (c < w - 1 && (state->lines[i] & R || state->lines[i+1] & L)) + memset(board + cell + 1, '-', cw - 1); + if (r < h - 1 && (state->lines[i] & D || state->lines[i+w] & U)) + for (j = 1; j < ch; ++j) board[cell + j*gw] = '|'; + if (c < w - 1 && (state->marks[i] & R || state->marks[i+1] & L)) + board[cell + cw/2] = 'x'; + if (r < h - 1 && (state->marks[i] & D || state->marks[i+w] & U)) + board[cell + (ch/2 * gw)] = 'x'; + } + + for (j = 0; j < (r == h - 1 ? 1 : ch); ++j) + board[r*ch*gw + (gw - 1) + j*gw] = '\n'; + } + + board[len] = '\0'; + return board; +} + +struct game_ui { + int *dragcoords; /* list of (y*w+x) coords in drag so far */ + int ndragcoords; /* number of entries in dragcoords. + * 0 = click but no drag yet. -1 = no drag at all */ + int clickx, clicky; /* pixel position of initial click */ + + int curx, cury; /* grid position of keyboard cursor */ + int cursor_active; /* TRUE iff cursor is shown */ +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + int sz = state->shared->sz; + + ui->ndragcoords = -1; + ui->dragcoords = snewn(sz, int); + ui->cursor_active = FALSE; + ui->curx = ui->cury = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui->dragcoords); + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +#define PREFERRED_TILE_SIZE 31 +#define HALFSZ (ds->halfsz) +#define TILE_SIZE (ds->halfsz*2 + 1) + +#define BORDER ((get_gui_style() == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2)) + +#define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) +#define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) ) + +#define DS_ESHIFT 4 /* R/U/L/D shift, for error flags */ +#define DS_DSHIFT 8 /* R/U/L/D shift, for drag-in-progress flags */ +#define DS_MSHIFT 12 /* shift for no-line mark */ + +#define DS_ERROR_CLUE (1 << 20) +#define DS_FLASH (1 << 21) +#define DS_CURSOR (1 << 22) + +enum { GUI_MASYU, GUI_LOOPY }; + +static int get_gui_style(void) +{ + static int gui_style = -1; + + if (gui_style == -1) { + char *env = getenv("PEARL_GUI_LOOPY"); + if (env && (env[0] == 'y' || env[0] == 'Y')) + gui_style = GUI_LOOPY; + else + gui_style = GUI_MASYU; + } + return gui_style; +} + +struct game_drawstate { + int halfsz; + int started; + + int w, h, sz; + unsigned int *lflags; /* size w*h */ + + char *draglines; /* size w*h; lines flipped by current drag */ +}; + +static void update_ui_drag(const game_state *state, game_ui *ui, + int gx, int gy) +{ + int /* sz = state->shared->sz, */ w = state->shared->w; + int i, ox, oy, pos; + int lastpos; + + if (!INGRID(state, gx, gy)) + return; /* square is outside grid */ + + if (ui->ndragcoords < 0) + return; /* drag not in progress anyway */ + + pos = gy * w + gx; + + lastpos = ui->dragcoords[ui->ndragcoords > 0 ? ui->ndragcoords-1 : 0]; + if (pos == lastpos) + return; /* same square as last visited one */ + + /* Drag confirmed, if it wasn't already. */ + if (ui->ndragcoords == 0) + ui->ndragcoords = 1; + + /* + * Dragging the mouse into a square that's already been visited by + * the drag path so far has the effect of truncating the path back + * to that square, so a player can back out part of an uncommitted + * drag without having to let go of the mouse. + */ + for (i = 0; i < ui->ndragcoords; i++) + if (pos == ui->dragcoords[i]) { + ui->ndragcoords = i+1; + return; + } + + /* + * Otherwise, dragging the mouse into a square that's a rook-move + * away from the last one on the path extends the path. + */ + oy = ui->dragcoords[ui->ndragcoords-1] / w; + ox = ui->dragcoords[ui->ndragcoords-1] % w; + if (ox == gx || oy == gy) { + int dx = (gx < ox ? -1 : gx > ox ? +1 : 0); + int dy = (gy < oy ? -1 : gy > oy ? +1 : 0); + int dir = (dy>0 ? D : dy<0 ? U : dx>0 ? R : L); + while (ox != gx || oy != gy) { + /* + * If the drag attempts to cross a 'no line here' mark, + * stop there. We physically don't allow the user to drag + * over those marks. + */ + if (state->marks[oy*w+ox] & dir) + break; + ox += dx; + oy += dy; + ui->dragcoords[ui->ndragcoords++] = oy * w + ox; + } + } + + /* + * Failing that, we do nothing at all: if the user has dragged + * diagonally across the board, they'll just have to return the + * mouse to the last known position and do whatever they meant to + * do again, more slowly and clearly. + */ +} + +/* + * Routine shared between interpret_move and game_redraw to work out + * the intended effect of a drag path on the grid. + * + * Call it in a loop, like this: + * + * int clearing = TRUE; + * for (i = 0; i < ui->ndragcoords - 1; i++) { + * int sx, sy, dx, dy, dir, oldstate, newstate; + * interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, + * &dir, &oldstate, &newstate); + * + * [do whatever is needed to handle the fact that the drag + * wants the edge from sx,sy to dx,dy (heading in direction + * 'dir' at the sx,sy end) to be changed from state oldstate + * to state newstate, each of which equals either 0 or dir] + * } + */ +static void interpret_ui_drag(const game_state *state, const game_ui *ui, + int *clearing, int i, int *sx, int *sy, + int *dx, int *dy, int *dir, + int *oldstate, int *newstate) +{ + int w = state->shared->w; + int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1]; + *sy = sp/w; + *sx = sp%w; + *dy = dp/w; + *dx = dp%w; + *dir = (*dy>*sy ? D : *dy<*sy ? U : *dx>*sx ? R : L); + *oldstate = state->lines[sp] & *dir; + if (*oldstate) { + /* + * The edge we've dragged over was previously + * present. Set it to absent, unless we've already + * stopped doing that. + */ + *newstate = *clearing ? 0 : *dir; + } else { + /* + * The edge we've dragged over was previously + * absent. Set it to present, and cancel the + * 'clearing' flag so that all subsequent edges in + * the drag are set rather than cleared. + */ + *newstate = *dir; + *clearing = FALSE; + } +} + +static char *mark_in_direction(const game_state *state, int x, int y, int dir, + int primary, char *buf) +{ + int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */; + int x2 = x + DX(dir); + int y2 = y + DY(dir); + int dir2 = F(dir); + + char ch = primary ? 'F' : 'M', *other; + + if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return ""; + + /* disallow laying a mark over a line, or vice versa. */ + other = primary ? state->marks : state->lines; + if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return ""; + + sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2); + return dupstr(buf); +} + +#define KEY_DIRECTION(btn) (\ + (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\ + (btn) == CURSOR_LEFT ? L : R) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->shared->w, h = state->shared->h /*, sz = state->shared->sz */; + int gx = FROMCOORD(x), gy = FROMCOORD(y), i; + int release = FALSE; + char tmpbuf[80]; + + int shift = button & MOD_SHFT, control = button & MOD_CTRL; + button &= ~MOD_MASK; + + if (IS_MOUSE_DOWN(button)) { + ui->cursor_active = FALSE; + + if (!INGRID(state, gx, gy)) { + ui->ndragcoords = -1; + return NULL; + } + + ui->clickx = x; ui->clicky = y; + ui->dragcoords[0] = gy * w + gx; + ui->ndragcoords = 0; /* will be 1 once drag is confirmed */ + + return ""; + } + + if (button == LEFT_DRAG && ui->ndragcoords >= 0) { + update_ui_drag(state, ui, gx, gy); + return ""; + } + + if (IS_MOUSE_RELEASE(button)) release = TRUE; + + if (IS_CURSOR_MOVE(button)) { + if (!ui->cursor_active) { + ui->cursor_active = TRUE; + } else if (control | shift) { + char *move; + if (ui->ndragcoords > 0) return NULL; + ui->ndragcoords = -1; + move = mark_in_direction(state, ui->curx, ui->cury, + KEY_DIRECTION(button), control, tmpbuf); + if (control && !shift && *move) + move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE); + return move; + } else { + move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE); + if (ui->ndragcoords >= 0) + update_ui_drag(state, ui, ui->curx, ui->cury); + } + return ""; + } + + if (IS_CURSOR_SELECT(button)) { + if (!ui->cursor_active) { + ui->cursor_active = TRUE; + return ""; + } else if (button == CURSOR_SELECT) { + if (ui->ndragcoords == -1) { + ui->ndragcoords = 0; + ui->dragcoords[0] = ui->cury * w + ui->curx; + ui->clickx = CENTERED_COORD(ui->curx); + ui->clicky = CENTERED_COORD(ui->cury); + return ""; + } else release = TRUE; + } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) { + ui->ndragcoords = -1; + return ""; + } + } + + if (button == 27 || button == '\b') { + ui->ndragcoords = -1; + return ""; + } + + if (release) { + if (ui->ndragcoords > 0) { + /* End of a drag: process the cached line data. */ + int buflen = 0, bufsize = 256, tmplen; + char *buf = NULL; + const char *sep = ""; + int clearing = TRUE; + + for (i = 0; i < ui->ndragcoords - 1; i++) { + int sx, sy, dx, dy, dir, oldstate, newstate; + interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, + &dir, &oldstate, &newstate); + + if (oldstate != newstate) { + if (!buf) buf = snewn(bufsize, char); + tmplen = sprintf(tmpbuf, "%sF%d,%d,%d;F%d,%d,%d", sep, + dir, sx, sy, F(dir), dx, dy); + if (buflen + tmplen >= bufsize) { + bufsize = (buflen + tmplen) * 5 / 4 + 256; + buf = sresize(buf, bufsize, char); + } + strcpy(buf + buflen, tmpbuf); + buflen += tmplen; + sep = ";"; + } + } + + ui->ndragcoords = -1; + + return buf ? buf : ""; + } else if (ui->ndragcoords == 0) { + /* Click (or tiny drag). Work out which edge we were + * closest to. */ + int cx, cy; + + ui->ndragcoords = -1; + + /* + * We process clicks based on the mouse-down location, + * because that's more natural for a user to carefully + * control than the mouse-up. + */ + x = ui->clickx; + y = ui->clicky; + + gx = FROMCOORD(x); + gy = FROMCOORD(y); + cx = CENTERED_COORD(gx); + cy = CENTERED_COORD(gy); + + if (!INGRID(state, gx, gy)) return ""; + + if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { + /* TODO closer to centre of grid: process as a cell click not an edge click. */ + + return ""; + } else { + int direction; + if (abs(x-cx) < abs(y-cy)) { + /* Closest to top/bottom edge. */ + direction = (y < cy) ? U : D; + } else { + /* Closest to left/right edge. */ + direction = (x < cx) ? L : R; + } + return mark_in_direction(state, gx, gy, direction, + (button == LEFT_RELEASE), tmpbuf); + } + } + } + + if (button == 'H' || button == 'h') + return dupstr("H"); + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->shared->w, h = state->shared->h; + char c; + int x, y, l, n; + game_state *ret = dup_game(state); + + debug(("move: %s\n", move)); + + while (*move) { + c = *move; + if (c == 'S') { + ret->used_solve = TRUE; + move++; + } else if (c == 'L' || c == 'N' || c == 'R' || c == 'F' || c == 'M') { + /* 'line' or 'noline' or 'replace' or 'flip' or 'mark' */ + move++; + if (sscanf(move, "%d,%d,%d%n", &l, &x, &y, &n) != 3) + goto badmove; + if (!INGRID(state, x, y)) goto badmove; + if (l < 0 || l > 15) goto badmove; + + if (c == 'L') + ret->lines[y*w + x] |= (char)l; + else if (c == 'N') + ret->lines[y*w + x] &= ~((char)l); + else if (c == 'R') { + ret->lines[y*w + x] = (char)l; + ret->marks[y*w + x] &= ~((char)l); /* erase marks too */ + } else if (c == 'F') + ret->lines[y*w + x] ^= (char)l; + else if (c == 'M') + ret->marks[y*w + x] ^= (char)l; + + /* + * If we ended up trying to lay a line _over_ a mark, + * that's a failed move: interpret_move() should have + * ensured we never received a move string like that in + * the first place. + */ + if ((ret->lines[y*w + x] & (char)l) && + (ret->marks[y*w + x] & (char)l)) + goto badmove; + + move += n; + } else if (strcmp(move, "H") == 0) { + pearl_solve(ret->shared->w, ret->shared->h, + ret->shared->clues, ret->lines, DIFFCOUNT, TRUE); + for (n = 0; n < w*h; n++) + ret->marks[n] &= ~ret->lines[n]; /* erase marks too */ + move++; + } else { + goto badmove; + } + if (*move == ';') + move++; + else if (*move) + goto badmove; + } + + check_completion(ret, TRUE); + + return ret; + +badmove: + free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define FLASH_TIME 0.5F + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int halfsz; } ads, *ds = &ads; + ads.halfsz = (tilesize-1)/2; + + *x = (params->w) * TILE_SIZE + 2 * BORDER; + *y = (params->h) * TILE_SIZE + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->halfsz = (tilesize-1)/2; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) { + ret[COL_BLACK * 3 + i] = 0.0F; + ret[COL_WHITE * 3 + i] = 1.0F; + ret[COL_GRID * 3 + i] = 0.4F; + } + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_DRAGON * 3 + 0] = 0.0F; + ret[COL_DRAGON * 3 + 1] = 0.0F; + ret[COL_DRAGON * 3 + 2] = 1.0F; + + ret[COL_DRAGOFF * 3 + 0] = 0.8F; + ret[COL_DRAGOFF * 3 + 1] = 0.8F; + ret[COL_DRAGOFF * 3 + 2] = 1.0F; + + ret[COL_FLASH * 3 + 0] = 1.0F; + ret[COL_FLASH * 3 + 1] = 1.0F; + ret[COL_FLASH * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->halfsz = 0; + ds->started = FALSE; + + ds->w = state->shared->w; + ds->h = state->shared->h; + ds->sz = state->shared->sz; + ds->lflags = snewn(ds->sz, unsigned int); + for (i = 0; i < ds->sz; i++) + ds->lflags[i] = 0; + + ds->draglines = snewn(ds->sz, char); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->draglines); + sfree(ds->lflags); + sfree(ds); +} + +static void draw_lines_specific(drawing *dr, game_drawstate *ds, + int x, int y, unsigned int lflags, + unsigned int shift, int c) +{ + int ox = COORD(x), oy = COORD(y); + int t2 = HALFSZ, t16 = HALFSZ/4; + int cx = ox + t2, cy = oy + t2; + int d; + + /* Draw each of the four directions, where laid (or error, or drag, etc.) */ + for (d = 1; d < 16; d *= 2) { + int xoff = t2 * DX(d), yoff = t2 * DY(d); + int xnudge = abs(t16 * DX(C(d))), ynudge = abs(t16 * DY(C(d))); + + if ((lflags >> shift) & d) { + int lx = cx + ((xoff < 0) ? xoff : 0) - xnudge; + int ly = cy + ((yoff < 0) ? yoff : 0) - ynudge; + + if (c == COL_DRAGOFF && !(lflags & d)) + continue; + if (c == COL_DRAGON && (lflags & d)) + continue; + + draw_rect(dr, lx, ly, + abs(xoff)+2*xnudge+1, + abs(yoff)+2*ynudge+1, c); + /* end cap */ + draw_rect(dr, cx - t16, cy - t16, 2*t16+1, 2*t16+1, c); + } + } +} + +static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui, + int x, int y, unsigned int lflags, char clue) +{ + int ox = COORD(x), oy = COORD(y); + int t2 = HALFSZ, t16 = HALFSZ/4; + int cx = ox + t2, cy = oy + t2; + int d; + + assert(dr); + + /* Clip to the grid square. */ + clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); + + /* Clear the square. */ + draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, + (lflags & DS_CURSOR) ? + COL_CURSOR_BACKGROUND : COL_BACKGROUND); + + + if (get_gui_style() == GUI_LOOPY) { + /* Draw small dot, underneath any lines. */ + draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID); + } else { + /* Draw outline of grid square */ + draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID); + draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID); + } + + /* Draw grid: either thin gridlines, or no-line marks. + * We draw these first because the thick laid lines should be on top. */ + for (d = 1; d < 16; d *= 2) { + int xoff = t2 * DX(d), yoff = t2 * DY(d); + + if ((x == 0 && d == L) || + (y == 0 && d == U) || + (x == ds->w-1 && d == R) || + (y == ds->h-1 && d == D)) + continue; /* no gridlines out to the border. */ + + if ((lflags >> DS_MSHIFT) & d) { + /* either a no-line mark ... */ + int mx = cx + xoff, my = cy + yoff, msz = t16; + + draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK); + draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK); + } else { + if (get_gui_style() == GUI_LOOPY) { + /* draw grid lines connecting centre of cells */ + draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID); + } + } + } + + /* Draw each of the four directions, where laid (or error, or drag, etc.) + * Order is important here, specifically for the eventual colours of the + * exposed end caps. */ + draw_lines_specific(dr, ds, x, y, lflags, 0, + (lflags & DS_FLASH ? COL_FLASH : COL_BLACK)); + draw_lines_specific(dr, ds, x, y, lflags, DS_ESHIFT, COL_ERROR); + draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGOFF); + draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGON); + + /* Draw a clue, if present */ + if (clue != NOCLUE) { + int c = (lflags & DS_FLASH) ? COL_FLASH : + (clue == STRAIGHT) ? COL_WHITE : COL_BLACK; + + if (lflags & DS_ERROR_CLUE) /* draw a bigger 'error' clue circle. */ + draw_circle(dr, cx, cy, TILE_SIZE*3/8, COL_ERROR, COL_ERROR); + + draw_circle(dr, cx, cy, TILE_SIZE/4, c, COL_BLACK); + } + + unclip(dr); + draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->shared->w, h = state->shared->h, sz = state->shared->sz; + int x, y, force = 0, flashing = 0; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + draw_rect(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER, + COL_BACKGROUND); + + if (get_gui_style() == GUI_MASYU) { + /* + * Smaller black rectangle which is the main grid. + */ + draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH, + w*TILE_SIZE + 2*BORDER_WIDTH + 1, + h*TILE_SIZE + 2*BORDER_WIDTH + 1, + COL_GRID); + } + + draw_update(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER); + + ds->started = TRUE; + force = 1; + } + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + flashing = DS_FLASH; + + memset(ds->draglines, 0, sz); + if (ui->ndragcoords > 0) { + int i, clearing = TRUE; + for (i = 0; i < ui->ndragcoords - 1; i++) { + int sx, sy, dx, dy, dir, oldstate, newstate; + interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy, + &dir, &oldstate, &newstate); + ds->draglines[sy*w+sx] ^= (oldstate ^ newstate); + ds->draglines[dy*w+dx] ^= (F(oldstate) ^ F(newstate)); + } + } + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + unsigned int f = (unsigned int)state->lines[y*w+x]; + unsigned int eline = (unsigned int)(state->errors[y*w+x] & (R|U|L|D)); + + f |= eline << DS_ESHIFT; + f |= ((unsigned int)ds->draglines[y*w+x]) << DS_DSHIFT; + f |= ((unsigned int)state->marks[y*w+x]) << DS_MSHIFT; + + if (state->errors[y*w+x] & ERROR_CLUE) + f |= DS_ERROR_CLUE; + + f |= flashing; + + if (ui->cursor_active && x == ui->curx && y == ui->cury) + f |= DS_CURSOR; + + if (f != ds->lflags[y*w+x] || force) { + ds->lflags[y*w+x] = f; + draw_square(dr, ds, ui, x, y, f, state->shared->clues[y*w+x]); + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return FLASH_TIME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->shared->w, h = state->shared->h, x, y; + int black = print_mono_colour(dr, 0); + int white = print_mono_colour(dr, 1); + + /* No GUI_LOOPY here: only use the familiar masyu style. */ + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate *ds = game_new_drawstate(dr, state); + game_set_size(dr, ds, NULL, tilesize); + + /* Draw grid outlines (black). */ + for (x = 0; x <= w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black); + for (y = 0; y <= h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black); + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ; + int clue = state->shared->clues[y*w+x]; + + draw_lines_specific(dr, ds, x, y, state->lines[y*w+x], 0, black); + + if (clue != NOCLUE) { + int c = (clue == CORNER) ? black : white; + draw_circle(dr, cx, cy, TILE_SIZE/4, c, black); + } + } + } + + game_free_drawstate(dr, ds); +} + +#ifdef COMBINED +#define thegame pearl +#endif + +const struct game thegame = { + "Pearl", "games.pearl", "pearl", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include +#include + +const char *quis = NULL; + +static void usage(FILE *out) { + fprintf(out, "usage: %s \n", quis); +} + +static void pnum(int n, int ntot, const char *desc) +{ + printf("%2.1f%% (%d) %s", (double)n*100.0 / (double)ntot, n, desc); +} + +static void start_soak(game_params *p, random_state *rs, int nsecs) +{ + time_t tt_start, tt_now, tt_last; + int n = 0, nsolved = 0, nimpossible = 0, ret; + char *grid, *clues; + + tt_start = tt_last = time(NULL); + + /* Currently this generates puzzles of any difficulty (trying to solve it + * on the maximum difficulty level and not checking it's not too easy). */ + printf("Soak-testing a %dx%d grid (any difficulty)", p->w, p->h); + if (nsecs > 0) printf(" for %d seconds", nsecs); + printf(".\n"); + + p->nosolve = TRUE; + + grid = snewn(p->w*p->h, char); + clues = snewn(p->w*p->h, char); + + while (1) { + n += new_clues(p, rs, clues, grid); /* should be 1, with nosolve */ + + ret = pearl_solve(p->w, p->h, clues, grid, DIFF_TRICKY, FALSE); + if (ret <= 0) nimpossible++; + if (ret == 1) nsolved++; + + tt_now = time(NULL); + if (tt_now > tt_last) { + tt_last = tt_now; + + printf("%d total, %3.1f/s, ", + n, (double)n / ((double)tt_now - tt_start)); + pnum(nsolved, n, "solved"); printf(", "); + printf("%3.1f/s", (double)nsolved / ((double)tt_now - tt_start)); + if (nimpossible > 0) + pnum(nimpossible, n, "impossible"); + printf("\n"); + } + if (nsecs > 0 && (tt_now - tt_start) > nsecs) { + printf("\n"); + break; + } + } + + sfree(grid); + sfree(clues); +} + +int main(int argc, const char *argv[]) +{ + game_params *p = NULL; + random_state *rs = NULL; + time_t seed = time(NULL); + char *id = NULL, *err; + + setvbuf(stdout, NULL, _IONBF, 0); + + quis = argv[0]; + + while (--argc > 0) { + char *p = (char*)(*++argv); + if (!strcmp(p, "-e") || !strcmp(p, "--seed")) { + seed = atoi(*++argv); + argc--; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + usage(stderr); + exit(1); + } else { + id = p; + } + } + + rs = random_new((void*)&seed, sizeof(time_t)); + p = default_params(); + + if (id) { + if (strchr(id, ':')) { + fprintf(stderr, "soak takes params only.\n"); + goto done; + } + + decode_params(p, id); + err = validate_params(p, 1); + if (err) { + fprintf(stderr, "%s: %s", argv[0], err); + goto done; + } + + start_soak(p, rs, 0); /* run forever */ + } else { + int i; + + for (i = 5; i <= 12; i++) { + p->w = p->h = i; + start_soak(p, rs, 5); + } + } + +done: + free_params(p); + random_free(rs); + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/pegs.R b/apps/plugins/puzzles/pegs.R new file mode 100644 index 0000000000..1e79e99ca0 --- /dev/null +++ b/apps/plugins/puzzles/pegs.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +PEGS_EXTRA = tree234 + +pegs : [X] GTK COMMON pegs PEGS_EXTRA pegs-icon|no-icon + +pegs : [G] WINDOWS COMMON pegs PEGS_EXTRA pegs.res|noicon.res + +ALL += pegs[COMBINED] PEGS_EXTRA + +!begin am gtk +GAMES += pegs +!end + +!begin >list.c + A(pegs) \ +!end + +!begin >gamedesc.txt +pegs:pegs.exe:Pegs:Peg solitaire puzzle:Jump pegs over each other to remove all but one. +!end diff --git a/apps/plugins/puzzles/pegs.c b/apps/plugins/puzzles/pegs.c new file mode 100644 index 0000000000..f02e25cafa --- /dev/null +++ b/apps/plugins/puzzles/pegs.c @@ -0,0 +1,1340 @@ +/* + * pegs.c: the classic Peg Solitaire game. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +#define GRID_HOLE 0 +#define GRID_PEG 1 +#define GRID_OBST 2 + +#define GRID_CURSOR 10 +#define GRID_JUMPING 20 + +enum { + COL_BACKGROUND, + COL_HIGHLIGHT, + COL_LOWLIGHT, + COL_PEG, + COL_CURSOR, + NCOLOURS +}; + +/* + * Grid shapes. I do some macro ickery here to ensure that my enum + * and the various forms of my name list always match up. + */ +#define TYPELIST(A) \ + A(CROSS,Cross,cross) \ + A(OCTAGON,Octagon,octagon) \ + A(RANDOM,Random,random) +#define ENUM(upper,title,lower) TYPE_ ## upper, +#define TITLE(upper,title,lower) #title, +#define LOWER(upper,title,lower) #lower, +#define CONFIG(upper,title,lower) ":" #title + +enum { TYPELIST(ENUM) TYPECOUNT }; +static char const *const pegs_titletypes[] = { TYPELIST(TITLE) }; +static char const *const pegs_lowertypes[] = { TYPELIST(LOWER) }; +#define TYPECONFIG TYPELIST(CONFIG) + +#define FLASH_FRAME 0.13F + +struct game_params { + int w, h; + int type; +}; + +struct game_state { + int w, h; + int completed; + unsigned char *grid; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 7; + ret->type = TYPE_CROSS; + + return ret; +} + +static const struct game_params pegs_presets[] = { + {7, 7, TYPE_CROSS}, + {7, 7, TYPE_OCTAGON}, + {5, 5, TYPE_RANDOM}, + {7, 7, TYPE_RANDOM}, + {9, 9, TYPE_RANDOM}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(pegs_presets)) + return FALSE; + + ret = snew(game_params); + *ret = pegs_presets[i]; + + strcpy(str, pegs_titletypes[ret->type]); + if (ret->type == TYPE_RANDOM) + sprintf(str + strlen(str), " %dx%d", ret->w, ret->h); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + int i; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + params->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->h = params->w; + } + + for (i = 0; i < lenof(pegs_lowertypes); i++) + if (!strcmp(p, pegs_lowertypes[i])) + params->type = i; +} + +static char *encode_params(const game_params *params, int full) +{ + char str[80]; + + sprintf(str, "%dx%d", params->w, params->h); + if (full) { + assert(params->type >= 0 && params->type < lenof(pegs_lowertypes)); + strcat(str, pegs_lowertypes[params->type]); + } + return dupstr(str); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret = snewn(4, config_item); + char buf[80]; + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Board type"; + ret[2].type = C_CHOICES; + ret[2].sval = TYPECONFIG; + ret[2].ival = params->type; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->type = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (full && (params->w <= 3 || params->h <= 3)) + return "Width and height must both be greater than three"; + + /* + * It might be possible to implement generalisations of Cross + * and Octagon, but only if I can find a proof that they're all + * soluble. For the moment, therefore, I'm going to disallow + * them at any size other than the standard one. + */ + if (full && (params->type == TYPE_CROSS || params->type == TYPE_OCTAGON)) { + if (params->w != 7 || params->h != 7) + return "This board type is only supported at 7x7"; + } + return NULL; +} + +/* ---------------------------------------------------------------------- + * Beginning of code to generate random Peg Solitaire boards. + * + * This procedure is done with no aesthetic judgment, no effort at + * symmetry, no difficulty grading and generally no finesse + * whatsoever. We simply begin with an empty board containing a + * single peg, and repeatedly make random reverse moves until it's + * plausibly full. This typically yields a scrappy haphazard mess + * with several holes, an uneven shape, and no redeeming features + * except guaranteed solubility. + * + * My only concessions to sophistication are (a) to repeat the + * generation process until I at least get a grid that touches + * every edge of the specified board size, and (b) to try when + * selecting moves to reuse existing space rather than expanding + * into new space (so that non-rectangular board shape becomes a + * factor during play). + */ + +struct move { + /* + * x,y are the start point of the move during generation (hence + * its endpoint during normal play). + * + * dx,dy are the direction of the move during generation. + * Absolute value 1. Hence, for example, x=3,y=5,dx=1,dy=0 + * means that the move during generation starts at (3,5) and + * ends at (5,5), and vice versa during normal play. + */ + int x, y, dx, dy; + /* + * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must + * turn into GRID_HOLEs to play this move. + */ + int cost; +}; + +static int movecmp(void *av, void *bv) +{ + struct move *a = (struct move *)av; + struct move *b = (struct move *)bv; + + if (a->y < b->y) + return -1; + else if (a->y > b->y) + return +1; + + if (a->x < b->x) + return -1; + else if (a->x > b->x) + return +1; + + if (a->dy < b->dy) + return -1; + else if (a->dy > b->dy) + return +1; + + if (a->dx < b->dx) + return -1; + else if (a->dx > b->dx) + return +1; + + return 0; +} + +static int movecmpcost(void *av, void *bv) +{ + struct move *a = (struct move *)av; + struct move *b = (struct move *)bv; + + if (a->cost < b->cost) + return -1; + else if (a->cost > b->cost) + return +1; + + return movecmp(av, bv); +} + +struct movetrees { + tree234 *bymove, *bycost; +}; + +static void update_moves(unsigned char *grid, int w, int h, int x, int y, + struct movetrees *trees) +{ + struct move move; + int dir, pos; + + /* + * There are twelve moves that can include (x,y): three in each + * of four directions. Check each one to see if it's possible. + */ + for (dir = 0; dir < 4; dir++) { + int dx, dy; + + if (dir & 1) + dx = 0, dy = dir - 2; + else + dy = 0, dx = dir - 1; + + assert(abs(dx) + abs(dy) == 1); + + for (pos = 0; pos < 3; pos++) { + int v1, v2, v3; + + move.dx = dx; + move.dy = dy; + move.x = x - pos*dx; + move.y = y - pos*dy; + + if (move.x < 0 || move.x >= w || move.y < 0 || move.y >= h) + continue; /* completely invalid move */ + if (move.x+2*move.dx < 0 || move.x+2*move.dx >= w || + move.y+2*move.dy < 0 || move.y+2*move.dy >= h) + continue; /* completely invalid move */ + + v1 = grid[move.y * w + move.x]; + v2 = grid[(move.y+move.dy) * w + (move.x+move.dx)]; + v3 = grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)]; + if (v1 == GRID_PEG && v2 != GRID_PEG && v3 != GRID_PEG) { + struct move *m; + + move.cost = (v2 == GRID_OBST) + (v3 == GRID_OBST); + + /* + * This move is possible. See if it's already in + * the tree. + */ + m = find234(trees->bymove, &move, NULL); + if (m && m->cost != move.cost) { + /* + * It's in the tree but listed with the wrong + * cost. Remove the old version. + */ +#ifdef GENERATION_DIAGNOSTICS + printf("correcting %d%+d,%d%+d at cost %d\n", + m->x, m->dx, m->y, m->dy, m->cost); +#endif + del234(trees->bymove, m); + del234(trees->bycost, m); + sfree(m); + m = NULL; + } + if (!m) { + struct move *m, *m2; + m = snew(struct move); + *m = move; + m2 = add234(trees->bymove, m); + m2 = add234(trees->bycost, m); + assert(m2 == m); +#ifdef GENERATION_DIAGNOSTICS + printf("adding %d%+d,%d%+d at cost %d\n", + move.x, move.dx, move.y, move.dy, move.cost); +#endif + } else { +#ifdef GENERATION_DIAGNOSTICS + printf("not adding %d%+d,%d%+d at cost %d\n", + move.x, move.dx, move.y, move.dy, move.cost); +#endif + } + } else { + /* + * This move is impossible. If it is already in the + * tree, delete it. + * + * (We make use here of the fact that del234 + * doesn't have to be passed a pointer to the + * _actual_ element it's deleting: it merely needs + * one that compares equal to it, and it will + * return the one it deletes.) + */ + struct move *m = del234(trees->bymove, &move); +#ifdef GENERATION_DIAGNOSTICS + printf("%sdeleting %d%+d,%d%+d\n", m ? "" : "not ", + move.x, move.dx, move.y, move.dy); +#endif + if (m) { + del234(trees->bycost, m); + sfree(m); + } + } + } + } +} + +static void pegs_genmoves(unsigned char *grid, int w, int h, random_state *rs) +{ + struct movetrees atrees, *trees = &atrees; + struct move *m; + int x, y, i, nmoves; + + trees->bymove = newtree234(movecmp); + trees->bycost = newtree234(movecmpcost); + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (grid[y*w+x] == GRID_PEG) + update_moves(grid, w, h, x, y, trees); + + nmoves = 0; + + while (1) { + int limit, maxcost, index; + struct move mtmp, move, *m; + + /* + * See how many moves we can make at zero cost. Make one, + * if possible. Failing that, make a one-cost move, and + * then a two-cost one. + * + * After filling at least half the input grid, we no longer + * accept cost-2 moves: if that's our only option, we give + * up and finish. + */ + mtmp.y = h+1; + maxcost = (nmoves < w*h/2 ? 2 : 1); + m = NULL; /* placate optimiser */ + for (mtmp.cost = 0; mtmp.cost <= maxcost; mtmp.cost++) { + limit = -1; + m = findrelpos234(trees->bycost, &mtmp, NULL, REL234_LT, &limit); +#ifdef GENERATION_DIAGNOSTICS + printf("%d moves available with cost %d\n", limit+1, mtmp.cost); +#endif + if (m) + break; + } + if (!m) + break; + + index = random_upto(rs, limit+1); + move = *(struct move *)index234(trees->bycost, index); + +#ifdef GENERATION_DIAGNOSTICS + printf("selecting move %d%+d,%d%+d at cost %d\n", + move.x, move.dx, move.y, move.dy, move.cost); +#endif + + grid[move.y * w + move.x] = GRID_HOLE; + grid[(move.y+move.dy) * w + (move.x+move.dx)] = GRID_PEG; + grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)] = GRID_PEG; + + for (i = 0; i <= 2; i++) { + int tx = move.x + i*move.dx; + int ty = move.y + i*move.dy; + update_moves(grid, w, h, tx, ty, trees); + } + + nmoves++; + } + + while ((m = delpos234(trees->bymove, 0)) != NULL) { + del234(trees->bycost, m); + sfree(m); + } + freetree234(trees->bymove); + freetree234(trees->bycost); +} + +static void pegs_generate(unsigned char *grid, int w, int h, random_state *rs) +{ + while (1) { + int x, y, extremes; + + memset(grid, GRID_OBST, w*h); + grid[(h/2) * w + (w/2)] = GRID_PEG; +#ifdef GENERATION_DIAGNOSTICS + printf("beginning move selection\n"); +#endif + pegs_genmoves(grid, w, h, rs); +#ifdef GENERATION_DIAGNOSTICS + printf("finished move selection\n"); +#endif + + extremes = 0; + for (y = 0; y < h; y++) { + if (grid[y*w+0] != GRID_OBST) + extremes |= 1; + if (grid[y*w+w-1] != GRID_OBST) + extremes |= 2; + } + for (x = 0; x < w; x++) { + if (grid[0*w+x] != GRID_OBST) + extremes |= 4; + if (grid[(h-1)*w+x] != GRID_OBST) + extremes |= 8; + } + + if (extremes == 15) + break; +#ifdef GENERATION_DIAGNOSTICS + printf("insufficient extent; trying again\n"); +#endif + } +#ifdef GENERATION_DIAGNOSTICS + fflush(stdout); +#endif +} + +/* ---------------------------------------------------------------------- + * End of board generation code. Now for the client code which uses + * it as part of the puzzle. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h; + unsigned char *grid; + char *ret; + int i; + + grid = snewn(w*h, unsigned char); + if (params->type == TYPE_RANDOM) { + pegs_generate(grid, w, h, rs); + } else { + int x, y, cx, cy, v; + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + v = GRID_OBST; /* placate optimiser */ + switch (params->type) { + case TYPE_CROSS: + cx = abs(x - w/2); + cy = abs(y - h/2); + if (cx == 0 && cy == 0) + v = GRID_HOLE; + else if (cx > 1 && cy > 1) + v = GRID_OBST; + else + v = GRID_PEG; + break; + case TYPE_OCTAGON: + cx = abs(x - w/2); + cy = abs(y - h/2); + if (cx + cy > 1 + max(w,h)/2) + v = GRID_OBST; + else + v = GRID_PEG; + break; + } + grid[y*w+x] = v; + } + + if (params->type == TYPE_OCTAGON) { + /* + * The octagonal (European) solitaire layout is + * actually _insoluble_ with the starting hole at the + * centre. Here's a proof: + * + * Colour the squares of the board diagonally in + * stripes of three different colours, which I'll call + * A, B and C. So the board looks like this: + * + * A B C + * A B C A B + * A B C A B C A + * B C A B C A B + * C A B C A B C + * B C A B C + * A B C + * + * Suppose we keep running track of the number of pegs + * occuping each colour of square. This colouring has + * the property that any valid move whatsoever changes + * all three of those counts by one (two of them go + * down and one goes up), which means that the _parity_ + * of every count flips on every move. + * + * If the centre square starts off unoccupied, then + * there are twelve pegs on each colour and all three + * counts start off even; therefore, after 35 moves all + * three counts would have to be odd, which isn't + * possible if there's only one peg left. [] + * + * This proof works just as well if the starting hole + * is _any_ of the thirteen positions labelled B. Also, + * we can stripe the board in the opposite direction + * and rule out any square labelled B in that colouring + * as well. This leaves: + * + * Y n Y + * n n Y n n + * Y n n Y n n Y + * n Y Y n Y Y n + * Y n n Y n n Y + * n n Y n n + * Y n Y + * + * where the ns are squares we've proved insoluble, and + * the Ys are the ones remaining. + * + * That doesn't prove all those starting positions to + * be soluble, of course; they're merely the ones we + * _haven't_ proved to be impossible. Nevertheless, it + * turns out that they are all soluble, so when the + * user requests an Octagon board the simplest thing is + * to pick one of these at random. + * + * Rather than picking equiprobably from those twelve + * positions, we'll pick equiprobably from the three + * equivalence classes + */ + switch (random_upto(rs, 3)) { + case 0: + /* Remove a random corner piece. */ + { + int dx, dy; + + dx = random_upto(rs, 2) * 2 - 1; /* +1 or -1 */ + dy = random_upto(rs, 2) * 2 - 1; /* +1 or -1 */ + if (random_upto(rs, 2)) + dy *= 3; + else + dx *= 3; + grid[(3+dy)*w+(3+dx)] = GRID_HOLE; + } + break; + case 1: + /* Remove a random piece two from the centre. */ + { + int dx, dy; + dx = 2 * (random_upto(rs, 2) * 2 - 1); + if (random_upto(rs, 2)) + dy = 0; + else + dy = dx, dx = 0; + grid[(3+dy)*w+(3+dx)] = GRID_HOLE; + } + break; + default /* case 2 */: + /* Remove a random piece one from the centre. */ + { + int dx, dy; + dx = random_upto(rs, 2) * 2 - 1; + if (random_upto(rs, 2)) + dy = 0; + else + dy = dx, dx = 0; + grid[(3+dy)*w+(3+dx)] = GRID_HOLE; + } + break; + } + } + } + + /* + * Encode a game description which is simply a long list of P + * for peg, H for hole or O for obstacle. + */ + ret = snewn(w*h+1, char); + for (i = 0; i < w*h; i++) + ret[i] = (grid[i] == GRID_PEG ? 'P' : + grid[i] == GRID_HOLE ? 'H' : 'O'); + ret[w*h] = '\0'; + + sfree(grid); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int len = params->w * params->h; + + if (len != strlen(desc)) + return "Game description is wrong length"; + if (len != strspn(desc, "PHO")) + return "Invalid character in game description"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h; + game_state *state = snew(game_state); + int i; + + state->w = w; + state->h = h; + state->completed = 0; + state->grid = snewn(w*h, unsigned char); + for (i = 0; i < w*h; i++) + state->grid[i] = (desc[i] == 'P' ? GRID_PEG : + desc[i] == 'H' ? GRID_HOLE : GRID_OBST); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->w, h = state->h; + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + ret->completed = state->completed; + ret->grid = snewn(w*h, unsigned char); + memcpy(ret->grid, state->grid, w*h); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return NULL; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->w, h = state->h; + int x, y; + char *ret; + + ret = snewn((w+1)*h + 1, char); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + ret[y*(w+1)+x] = (state->grid[y*w+x] == GRID_HOLE ? '-' : + state->grid[y*w+x] == GRID_PEG ? '*' : ' '); + ret[y*(w+1)+w] = '\n'; + } + ret[h*(w+1)] = '\0'; + + return ret; +} + +struct game_ui { + int dragging; /* boolean: is a drag in progress? */ + int sx, sy; /* grid coords of drag start cell */ + int dx, dy; /* pixel coords of current drag posn */ + int cur_x, cur_y, cur_visible, cur_jumping; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + int x, y, v; + + ui->sx = ui->sy = ui->dx = ui->dy = 0; + ui->dragging = FALSE; + ui->cur_visible = ui->cur_jumping = 0; + + /* make sure we start the cursor somewhere on the grid. */ + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + v = state->grid[y*state->w+x]; + if (v == GRID_PEG || v == GRID_HOLE) { + ui->cur_x = x; ui->cur_y = y; + goto found; + } + } + } + assert(!"new_ui found nowhere for cursor"); +found: + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + /* + * Cancel a drag, in case the source square has become + * unoccupied. + */ + ui->dragging = FALSE; +} + +#define PREFERRED_TILE_SIZE 33 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE / 2) + +#define HIGHLIGHT_WIDTH (TILESIZE / 16) + +#define COORD(x) ( BORDER + (x) * TILESIZE ) +#define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 ) + +struct game_drawstate { + int tilesize; + blitter *drag_background; + int dragging, dragx, dragy; + int w, h; + unsigned char *grid; + int started; + int bgcolour; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->w, h = state->h; + char buf[80]; + + if (button == LEFT_BUTTON) { + int tx, ty; + + /* + * Left button down: we attempt to start a drag. + */ + + /* + * There certainly shouldn't be a current drag in progress, + * unless the midend failed to send us button events in + * order; it has a responsibility to always get that right, + * so we can legitimately punish it by failing an + * assertion. + */ + assert(!ui->dragging); + + tx = FROMCOORD(x); + ty = FROMCOORD(y); + if (tx >= 0 && tx < w && ty >= 0 && ty < h && + state->grid[ty*w+tx] == GRID_PEG) { + ui->dragging = TRUE; + ui->sx = tx; + ui->sy = ty; + ui->dx = x; + ui->dy = y; + ui->cur_visible = ui->cur_jumping = 0; + return ""; /* ui modified */ + } + } else if (button == LEFT_DRAG && ui->dragging) { + /* + * Mouse moved; just move the peg being dragged. + */ + ui->dx = x; + ui->dy = y; + return ""; /* ui modified */ + } else if (button == LEFT_RELEASE && ui->dragging) { + int tx, ty, dx, dy; + + /* + * Button released. Identify the target square of the drag, + * see if it represents a valid move, and if so make it. + */ + ui->dragging = FALSE; /* cancel the drag no matter what */ + tx = FROMCOORD(x); + ty = FROMCOORD(y); + if (tx < 0 || tx >= w || ty < 0 || ty >= h) + return ""; /* target out of range */ + dx = tx - ui->sx; + dy = ty - ui->sy; + if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) + return ""; /* move length was wrong */ + dx /= 2; + dy /= 2; + + if (state->grid[ty*w+tx] != GRID_HOLE || + state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG || + state->grid[ui->sy*w+ui->sx] != GRID_PEG) + return ""; /* grid contents were invalid */ + + /* + * We have a valid move. Encode it simply as source and + * destination coordinate pairs. + */ + sprintf(buf, "%d,%d-%d,%d", ui->sx, ui->sy, tx, ty); + return dupstr(buf); + } else if (IS_CURSOR_MOVE(button)) { + if (!ui->cur_jumping) { + /* Not jumping; move cursor as usual, making sure we don't + * leave the gameboard (which may be an irregular shape) */ + int cx = ui->cur_x, cy = ui->cur_y; + move_cursor(button, &cx, &cy, w, h, 0); + ui->cur_visible = 1; + if (state->grid[cy*w+cx] == GRID_HOLE || + state->grid[cy*w+cx] == GRID_PEG) { + ui->cur_x = cx; + ui->cur_y = cy; + } + return ""; + } else { + int dx, dy, mx, my, jx, jy; + + /* We're jumping; if the requested direction has a hole, and + * there's a peg in the way, */ + assert(state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG); + dx = (button == CURSOR_RIGHT) ? 1 : (button == CURSOR_LEFT) ? -1 : 0; + dy = (button == CURSOR_DOWN) ? 1 : (button == CURSOR_UP) ? -1 : 0; + + mx = ui->cur_x+dx; my = ui->cur_y+dy; + jx = mx+dx; jy = my+dy; + + ui->cur_jumping = 0; /* reset, whatever. */ + if (jx >= 0 && jy >= 0 && jx < w && jy < h && + state->grid[my*w+mx] == GRID_PEG && + state->grid[jy*w+jx] == GRID_HOLE) { + /* Move cursor to the jumped-to location (this felt more + * natural while playtesting) */ + sprintf(buf, "%d,%d-%d,%d", ui->cur_x, ui->cur_y, jx, jy); + ui->cur_x = jx; ui->cur_y = jy; + return dupstr(buf); + } + return ""; + } + } else if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + if (ui->cur_jumping) { + ui->cur_jumping = 0; + return ""; + } + if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) { + /* cursor is on peg: next arrow-move wil jump. */ + ui->cur_jumping = 1; + return ""; + } + return NULL; + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->w, h = state->h; + int sx, sy, tx, ty; + game_state *ret; + + if (sscanf(move, "%d,%d-%d,%d", &sx, &sy, &tx, &ty) == 4) { + int mx, my, dx, dy; + + if (sx < 0 || sx >= w || sy < 0 || sy >= h) + return NULL; /* source out of range */ + if (tx < 0 || tx >= w || ty < 0 || ty >= h) + return NULL; /* target out of range */ + + dx = tx - sx; + dy = ty - sy; + if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0) + return NULL; /* move length was wrong */ + mx = sx + dx/2; + my = sy + dy/2; + + if (state->grid[sy*w+sx] != GRID_PEG || + state->grid[my*w+mx] != GRID_PEG || + state->grid[ty*w+tx] != GRID_HOLE) + return NULL; /* grid contents were invalid */ + + ret = dup_game(state); + ret->grid[sy*w+sx] = GRID_HOLE; + ret->grid[my*w+mx] = GRID_HOLE; + ret->grid[ty*w+tx] = GRID_PEG; + + /* + * Opinion varies on whether getting to a single peg counts as + * completing the game, or whether that peg has to be at a + * specific location (central in the classic cross game, for + * instance). For now we take the former, rather lax position. + */ + if (!ret->completed) { + int count = 0, i; + for (i = 0; i < w*h; i++) + if (ret->grid[i] == GRID_PEG) + count++; + if (count == 1) + ret->completed = 1; + } + + return ret; + } + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILESIZE * params->w + 2 * BORDER; + *y = TILESIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + + assert(TILESIZE > 0); + + assert(!ds->drag_background); /* set_size is never called twice */ + ds->drag_background = blitter_new(dr, TILESIZE, TILESIZE); +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + ret[COL_PEG * 3 + 0] = 0.0F; + ret[COL_PEG * 3 + 1] = 0.0F; + ret[COL_PEG * 3 + 2] = 1.0F; + + ret[COL_CURSOR * 3 + 0] = 0.5F; + ret[COL_CURSOR * 3 + 1] = 0.5F; + ret[COL_CURSOR * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->w, h = state->h; + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; /* not decided yet */ + + /* We can't allocate the blitter rectangle for the drag background + * until we know what size to make it. */ + ds->drag_background = NULL; + ds->dragging = FALSE; + + ds->w = w; + ds->h = h; + ds->grid = snewn(w*h, unsigned char); + memset(ds->grid, 255, w*h); + + ds->started = FALSE; + ds->bgcolour = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + if (ds->drag_background) + blitter_free(dr, ds->drag_background); + sfree(ds->grid); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, + int x, int y, int v, int bgcolour) +{ + int cursor = 0, jumping = 0, bg; + + if (bgcolour >= 0) { + draw_rect(dr, x, y, TILESIZE, TILESIZE, bgcolour); + } + if (v >= GRID_JUMPING) { + jumping = 1; v -= GRID_JUMPING; + } + if (v >= GRID_CURSOR) { + cursor = 1; v -= GRID_CURSOR; + } + + if (v == GRID_HOLE) { + bg = cursor ? COL_HIGHLIGHT : COL_LOWLIGHT; + assert(!jumping); /* can't jump from a hole! */ + draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4, + bg, bg); + } else if (v == GRID_PEG) { + bg = (cursor || jumping) ? COL_CURSOR : COL_PEG; + draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3, + bg, bg); + bg = (!cursor || jumping) ? COL_PEG : COL_CURSOR; + draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4, + bg, bg); + } + + draw_update(dr, x, y, TILESIZE, TILESIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->w, h = state->h; + int x, y; + int bgcolour; + + if (flashtime > 0) { + int frame = (int)(flashtime / FLASH_FRAME); + bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); + } else + bgcolour = COL_BACKGROUND; + + /* + * Erase the sprite currently being dragged, if any. + */ + if (ds->dragging) { + assert(ds->drag_background); + blitter_load(dr, ds->drag_background, ds->dragx, ds->dragy); + draw_update(dr, ds->dragx, ds->dragy, TILESIZE, TILESIZE); + ds->dragging = FALSE; + } + + if (!ds->started) { + draw_rect(dr, 0, 0, + TILESIZE * state->w + 2 * BORDER, + TILESIZE * state->h + 2 * BORDER, COL_BACKGROUND); + + /* + * Draw relief marks around all the squares that aren't + * GRID_OBST. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x] != GRID_OBST) { + /* + * First pass: draw the full relief square. + */ + int coords[6]; + coords[0] = COORD(x+1) + HIGHLIGHT_WIDTH - 1; + coords[1] = COORD(y) - HIGHLIGHT_WIDTH; + coords[2] = COORD(x) - HIGHLIGHT_WIDTH; + coords[3] = COORD(y+1) + HIGHLIGHT_WIDTH - 1; + coords[4] = COORD(x) - HIGHLIGHT_WIDTH; + coords[5] = COORD(y) - HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + coords[4] = COORD(x+1) + HIGHLIGHT_WIDTH - 1; + coords[5] = COORD(y+1) + HIGHLIGHT_WIDTH - 1; + draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); + } + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x] != GRID_OBST) { + /* + * Second pass: draw everything but the two + * diagonal corners. + */ + draw_rect(dr, COORD(x) - HIGHLIGHT_WIDTH, + COORD(y) - HIGHLIGHT_WIDTH, + TILESIZE + HIGHLIGHT_WIDTH, + TILESIZE + HIGHLIGHT_WIDTH, COL_HIGHLIGHT); + draw_rect(dr, COORD(x), + COORD(y), + TILESIZE + HIGHLIGHT_WIDTH, + TILESIZE + HIGHLIGHT_WIDTH, COL_LOWLIGHT); + } + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x] != GRID_OBST) { + /* + * Third pass: draw a trapezium on each edge. + */ + int coords[8]; + int dx, dy, s, sn, c; + + for (dx = 0; dx < 2; dx++) { + dy = 1 - dx; + for (s = 0; s < 2; s++) { + sn = 2*s - 1; + c = s ? COL_LOWLIGHT : COL_HIGHLIGHT; + + coords[0] = COORD(x) + (s*dx)*(TILESIZE-1); + coords[1] = COORD(y) + (s*dy)*(TILESIZE-1); + coords[2] = COORD(x) + (s*dx+dy)*(TILESIZE-1); + coords[3] = COORD(y) + (s*dy+dx)*(TILESIZE-1); + coords[4] = coords[2] - HIGHLIGHT_WIDTH * (dy-sn*dx); + coords[5] = coords[3] - HIGHLIGHT_WIDTH * (dx-sn*dy); + coords[6] = coords[0] + HIGHLIGHT_WIDTH * (dy+sn*dx); + coords[7] = coords[1] + HIGHLIGHT_WIDTH * (dx+sn*dy); + draw_polygon(dr, coords, 4, c, c); + } + } + } + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x] != GRID_OBST) { + /* + * Second pass: draw everything but the two + * diagonal corners. + */ + draw_rect(dr, COORD(x), + COORD(y), + TILESIZE, + TILESIZE, COL_BACKGROUND); + } + + ds->started = TRUE; + + draw_update(dr, 0, 0, + TILESIZE * state->w + 2 * BORDER, + TILESIZE * state->h + 2 * BORDER); + } + + /* + * Loop over the grid redrawing anything that looks as if it + * needs it. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int v; + + v = state->grid[y*w+x]; + /* + * Blank the source of a drag so it looks as if the + * user picked the peg up physically. + */ + if (ui->dragging && ui->sx == x && ui->sy == y && v == GRID_PEG) + v = GRID_HOLE; + + if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y) + v += ui->cur_jumping ? GRID_JUMPING : GRID_CURSOR; + + if (v != GRID_OBST && + (bgcolour != ds->bgcolour || /* always redraw when flashing */ + v != ds->grid[y*w+x])) { + draw_tile(dr, ds, COORD(x), COORD(y), v, bgcolour); + ds->grid[y*w+x] = v; + } + } + + /* + * Draw the dragging sprite if any. + */ + if (ui->dragging) { + ds->dragging = TRUE; + ds->dragx = ui->dx - TILESIZE/2; + ds->dragy = ui->dy - TILESIZE/2; + blitter_save(dr, ds->drag_background, ds->dragx, ds->dragy); + draw_tile(dr, ds, ds->dragx, ds->dragy, GRID_PEG, -1); + } + + ds->bgcolour = bgcolour; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed) + return 2 * FLASH_FRAME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + /* + * Dead-end situations are assumed to be rescuable by Undo, so we + * don't bother to identify them and return -1. + */ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame pegs +#endif + +const struct game thegame = { + "Pegs", "games.pegs", "pegs", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + FALSE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/penrose.c b/apps/plugins/puzzles/penrose.c new file mode 100644 index 0000000000..839b50a853 --- /dev/null +++ b/apps/plugins/puzzles/penrose.c @@ -0,0 +1,629 @@ +/* penrose.c + * + * Penrose tile generator. + * + * Uses half-tile technique outlined on: + * + * http://tartarus.org/simon/20110412-penrose/penrose.xhtml + */ + +#include "rbassert.h" +#include +#include +#include + +#include "puzzles.h" /* for malloc routines, and PI */ +#include "penrose.h" + +/* ------------------------------------------------------- + * 36-degree basis vector arithmetic routines. + */ + +/* Imagine drawing a + * ten-point 'clock face' like this: + * + * -E + * -D | A + * \ | / + * -C. \ | / ,B + * `-._\|/_,-' + * ,-' /|\ `-. + * -B' / | \ `C + * / | \ + * -A | D + * E + * + * In case the ASCII art isn't clear, those are supposed to be ten + * vectors of length 1, all sticking out from the origin at equal + * angular spacing (hence 36 degrees). Our basis vectors are A,B,C,D (I + * choose them to be symmetric about the x-axis so that the final + * translation into 2d coordinates will also be symmetric, which I + * think will avoid minor rounding uglinesses), so our vector + * representation sets + * + * A = (1,0,0,0) + * B = (0,1,0,0) + * C = (0,0,1,0) + * D = (0,0,0,1) + * + * The fifth vector E looks at first glance as if it needs to be + * another basis vector, but in fact it doesn't, because it can be + * represented in terms of the other four. Imagine starting from the + * origin and following the path -A, +B, -C, +D: you'll find you've + * traced four sides of a pentagram, and ended up one E-vector away + * from the origin. So we have + * + * E = (-1,1,-1,1) + * + * This tells us that we can rotate any vector in this system by 36 + * degrees: if we start with a*A + b*B + c*C + d*D, we want to end up + * with a*B + b*C + c*D + d*E, and we substitute our identity for E to + * turn that into a*B + b*C + c*D + d*(-A+B-C+D). In other words, + * + * rotate_one_notch_clockwise(a,b,c,d) = (-d, d+a, -d+b, d+c) + * + * and you can verify for yourself that applying that operation + * repeatedly starting with (1,0,0,0) cycles round ten vectors and + * comes back to where it started. + * + * The other operation that may be required is to construct vectors + * with lengths that are multiples of phi. That can be done by + * observing that the vector C-B is parallel to E and has length 1/phi, + * and the vector D-A is parallel to E and has length phi. So this + * tells us that given any vector, we can construct one which points in + * the same direction and is 1/phi or phi times its length, like this: + * + * divide_by_phi(vector) = rotate(vector, 2) - rotate(vector, 3) + * multiply_by_phi(vector) = rotate(vector, 1) - rotate(vector, 4) + * + * where rotate(vector, n) means applying the above + * rotate_one_notch_clockwise primitive n times. Expanding out the + * applications of rotate gives the following direct representation in + * terms of the vector coordinates: + * + * divide_by_phi(a,b,c,d) = (b-d, c+d-b, a+b-c, c-a) + * multiply_by_phi(a,b,c,d) = (a+b-d, c+d, a+b, c+d-a) + * + * and you can verify for yourself that those two operations are + * inverses of each other (as you'd hope!). + * + * Having done all of this, testing for equality between two vectors is + * a trivial matter of comparing the four integer coordinates. (Which + * it _wouldn't_ have been if we'd kept E as a fifth basis vector, + * because then (-1,1,-1,1,0) and (0,0,0,0,1) would have had to be + * considered identical. So leaving E out is vital.) + */ + +struct vector { int a, b, c, d; }; + +static vector v_origin(void) +{ + vector v; + v.a = v.b = v.c = v.d = 0; + return v; +} + +/* We start with a unit vector of B: this means we can easily + * draw an isoceles triangle centred on the X axis. */ +#ifdef TEST_VECTORS + +static vector v_unit(void) +{ + vector v; + + v.b = 1; + v.a = v.c = v.d = 0; + return v; +} + +#endif + +#define COS54 0.5877852 +#define SIN54 0.8090169 +#define COS18 0.9510565 +#define SIN18 0.3090169 + +/* These two are a bit rough-and-ready for now. Note that B/C are + * 18 degrees from the x-axis, and A/D are 54 degrees. */ +double v_x(vector *vs, int i) +{ + return (vs[i].a + vs[i].d) * COS54 + + (vs[i].b + vs[i].c) * COS18; +} + +double v_y(vector *vs, int i) +{ + return (vs[i].a - vs[i].d) * SIN54 + + (vs[i].b - vs[i].c) * SIN18; + +} + +static vector v_trans(vector v, vector trans) +{ + v.a += trans.a; + v.b += trans.b; + v.c += trans.c; + v.d += trans.d; + return v; +} + +static vector v_rotate_36(vector v) +{ + vector vv; + vv.a = -v.d; + vv.b = v.d + v.a; + vv.c = -v.d + v.b; + vv.d = v.d + v.c; + return vv; +} + +static vector v_rotate(vector v, int ang) +{ + int i; + + assert((ang % 36) == 0); + while (ang < 0) ang += 360; + ang = 360-ang; + for (i = 0; i < (ang/36); i++) + v = v_rotate_36(v); + return v; +} + +#ifdef TEST_VECTORS + +static vector v_scale(vector v, int sc) +{ + v.a *= sc; + v.b *= sc; + v.c *= sc; + v.d *= sc; + return v; +} + +#endif + +static vector v_growphi(vector v) +{ + vector vv; + vv.a = v.a + v.b - v.d; + vv.b = v.c + v.d; + vv.c = v.a + v.b; + vv.d = v.c + v.d - v.a; + return vv; +} + +static vector v_shrinkphi(vector v) +{ + vector vv; + vv.a = v.b - v.d; + vv.b = v.c + v.d - v.b; + vv.c = v.a + v.b - v.c; + vv.d = v.c - v.a; + return vv; +} + +#ifdef TEST_VECTORS + +static const char *v_debug(vector v) +{ + static char buf[255]; + sprintf(buf, + "(%d,%d,%d,%d)[%2.2f,%2.2f]", + v.a, v.b, v.c, v.d, v_x(&v,0), v_y(&v,0)); + return buf; +} + +#endif + +/* ------------------------------------------------------- + * Tiling routines. + */ + +static vector xform_coord(vector vo, int shrink, vector vtrans, int ang) +{ + if (shrink < 0) + vo = v_shrinkphi(vo); + else if (shrink > 0) + vo = v_growphi(vo); + + vo = v_rotate(vo, ang); + vo = v_trans(vo, vtrans); + + return vo; +} + + +#define XFORM(n,o,s,a) vs[(n)] = xform_coord(v_edge, (s), vs[(o)], (a)) + +static int penrose_p2_small(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge); + +static int penrose_p2_large(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge) +{ + vector vv_orig, vv_edge; + +#ifdef DEBUG_PENROSE + { + vector vs[3]; + vs[0] = v_orig; + XFORM(1, 0, 0, 0); + XFORM(2, 0, 0, -36*flip); + + state->new_tile(state, vs, 3, depth); + } +#endif + + if (flip > 0) { + vector vs[4]; + + vs[0] = v_orig; + XFORM(1, 0, 0, -36); + XFORM(2, 0, 0, 0); + XFORM(3, 0, 0, 36); + + state->new_tile(state, vs, 4, depth); + } + if (depth >= state->max_depth) return 0; + + vv_orig = v_trans(v_orig, v_rotate(v_edge, -36*flip)); + vv_edge = v_rotate(v_edge, 108*flip); + + penrose_p2_small(state, depth+1, flip, + v_orig, v_shrinkphi(v_edge)); + + penrose_p2_large(state, depth+1, flip, + vv_orig, v_shrinkphi(vv_edge)); + penrose_p2_large(state, depth+1, -flip, + vv_orig, v_shrinkphi(vv_edge)); + + return 0; +} + +static int penrose_p2_small(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge) +{ + vector vv_orig; + +#ifdef DEBUG_PENROSE + { + vector vs[3]; + vs[0] = v_orig; + XFORM(1, 0, 0, 0); + XFORM(2, 0, -1, -36*flip); + + state->new_tile(state, vs, 3, depth); + } +#endif + + if (flip > 0) { + vector vs[4]; + + vs[0] = v_orig; + XFORM(1, 0, 0, -72); + XFORM(2, 0, -1, -36); + XFORM(3, 0, 0, 0); + + state->new_tile(state, vs, 4, depth); + } + + if (depth >= state->max_depth) return 0; + + vv_orig = v_trans(v_orig, v_edge); + + penrose_p2_large(state, depth+1, -flip, + v_orig, v_shrinkphi(v_rotate(v_edge, -36*flip))); + + penrose_p2_small(state, depth+1, flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip))); + + return 0; +} + +static int penrose_p3_small(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge); + +static int penrose_p3_large(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge) +{ + vector vv_orig; + +#ifdef DEBUG_PENROSE + { + vector vs[3]; + vs[0] = v_orig; + XFORM(1, 0, 1, 0); + XFORM(2, 0, 0, -36*flip); + + state->new_tile(state, vs, 3, depth); + } +#endif + + if (flip > 0) { + vector vs[4]; + + vs[0] = v_orig; + XFORM(1, 0, 0, -36); + XFORM(2, 0, 1, 0); + XFORM(3, 0, 0, 36); + + state->new_tile(state, vs, 4, depth); + } + if (depth >= state->max_depth) return 0; + + vv_orig = v_trans(v_orig, v_edge); + + penrose_p3_large(state, depth+1, -flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, 180))); + + penrose_p3_small(state, depth+1, flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip))); + + vv_orig = v_trans(v_orig, v_growphi(v_edge)); + + penrose_p3_large(state, depth+1, flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip))); + + + return 0; +} + +static int penrose_p3_small(penrose_state *state, int depth, int flip, + vector v_orig, vector v_edge) +{ + vector vv_orig; + +#ifdef DEBUG_PENROSE + { + vector vs[3]; + vs[0] = v_orig; + XFORM(1, 0, 0, 0); + XFORM(2, 0, 0, -36*flip); + + state->new_tile(state, vs, 3, depth); + } +#endif + + if (flip > 0) { + vector vs[4]; + + vs[0] = v_orig; + XFORM(1, 0, 0, -36); + XFORM(3, 0, 0, 0); + XFORM(2, 3, 0, -36); + + state->new_tile(state, vs, 4, depth); + } + if (depth >= state->max_depth) return 0; + + /* NB these two are identical to the first two of p3_large. */ + vv_orig = v_trans(v_orig, v_edge); + + penrose_p3_large(state, depth+1, -flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, 180))); + + penrose_p3_small(state, depth+1, flip, + vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip))); + + return 0; +} + +/* ------------------------------------------------------- + * Utility routines. + */ + +double penrose_side_length(double start_size, int depth) +{ + return start_size / pow(PHI, depth); +} + +void penrose_count_tiles(int depth, int *nlarge, int *nsmall) +{ + /* Steal sgt's fibonacci thingummy. */ +} + +/* + * It turns out that an acute isosceles triangle with sides in ratio 1:phi:phi + * has an incentre which is conveniently 2*phi^-2 of the way from the apex to + * the base. Why's that convenient? Because: if we situate the incentre of the + * triangle at the origin, then we can place the apex at phi^-2 * (B+C), and + * the other two vertices at apex-B and apex-C respectively. So that's an acute + * triangle with its long sides of unit length, covering a circle about the + * origin of radius 1-(2*phi^-2), which is conveniently enough phi^-3. + * + * (later mail: this is an overestimate by about 5%) + */ + +int penrose(penrose_state *state, int which, int angle) +{ + vector vo = v_origin(); + vector vb = v_origin(); + + vo.b = vo.c = -state->start_size; + vo = v_shrinkphi(v_shrinkphi(vo)); + + vb.b = state->start_size; + + vo = v_rotate(vo, angle); + vb = v_rotate(vb, angle); + + if (which == PENROSE_P2) + return penrose_p2_large(state, 0, 1, vo, vb); + else + return penrose_p3_small(state, 0, 1, vo, vb); +} + +/* + * We're asked for a MxN grid, which just means a tiling fitting into roughly + * an MxN space in some kind of reasonable unit - say, the side length of the + * two-arrow edges of the tiles. By some reasoning in a previous email, that + * means we want to pick some subarea of a circle of radius 3.11*sqrt(M^2+N^2). + * To cover that circle, we need to subdivide a triangle large enough that it + * contains a circle of that radius. + * + * Hence: start with those three vectors marking triangle vertices, scale them + * all up by phi repeatedly until the radius of the inscribed circle gets + * bigger than the target, and then recurse into that triangle with the same + * recursion depth as the number of times you scaled up. That will give you + * tiles of unit side length, covering a circle big enough that if you randomly + * choose an orientation and coordinates within the circle, you'll be able to + * get any valid piece of Penrose tiling of size MxN. + */ +#define INCIRCLE_RADIUS 0.22426 /* phi^-3 less 5%: see above */ + +void penrose_calculate_size(int which, int tilesize, int w, int h, + double *required_radius, int *start_size, int *depth) +{ + double rradius, size; + int n = 0; + + /* + * Fudge factor to scale P2 and P3 tilings differently. This + * doesn't seem to have much relevance to questions like the + * average number of tiles per unit area; it's just aesthetic. + */ + if (which == PENROSE_P2) + tilesize = tilesize * 3 / 2; + else + tilesize = tilesize * 5 / 4; + + rradius = tilesize * 3.11 * sqrt((double)(w*w + h*h)); + size = tilesize; + + while ((size * INCIRCLE_RADIUS) < rradius) { + n++; + size = size * PHI; + } + + *start_size = (int)size; + *depth = n; + *required_radius = rradius; +} + +/* ------------------------------------------------------- + * Test code. + */ + +#ifdef TEST_PENROSE + +#include +#include + +int show_recursion = 0; +int ntiles, nfinal; + +int test_cb(penrose_state *state, vector *vs, int n, int depth) +{ + int i, xoff = 0, yoff = 0; + double l = penrose_side_length(state->start_size, depth); + double rball = l / 10.0; + const char *col; + + ntiles++; + if (state->max_depth == depth) { + col = n == 4 ? "black" : "green"; + nfinal++; + } else { + if (!show_recursion) + return 0; + col = n == 4 ? "red" : "blue"; + } + if (n != 4) yoff = state->start_size; + + printf("\n", col, col); + printf("", + v_x(vs, 0) + xoff, v_y(vs, 0) + yoff, rball, rball, col); + + return 0; +} + +void usage_exit(void) +{ + fprintf(stderr, "Usage: penrose-test [--recursion] P2|P3 SIZE DEPTH\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + penrose_state ps; + int which = 0; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-h") || !strcmp(p, "--help")) { + usage_exit(); + } else if (!strcmp(p, "--recursion")) { + show_recursion = 1; + } else if (*p == '-') { + fprintf(stderr, "Unrecognised option '%s'\n", p); + exit(1); + } else { + break; + } + } + + if (argc < 3) usage_exit(); + + if (strcmp(argv[0], "P2") == 0) which = PENROSE_P2; + else if (strcmp(argv[0], "P3") == 0) which = PENROSE_P3; + else usage_exit(); + + ps.start_size = atoi(argv[1]); + ps.max_depth = atoi(argv[2]); + ps.new_tile = test_cb; + + ntiles = nfinal = 0; + + printf("\ +\n\ +\n\ +\n\ +\n\n"); + + printf("\n"); + penrose(&ps, which); + printf("\n"); + + printf("\n", + ntiles, nfinal); + + printf(""); + + return 0; +} + +#endif + +#ifdef TEST_VECTORS + +static void dbgv(const char *msg, vector v) +{ + printf("%s: %s\n", msg, v_debug(v)); +} + +int main(int argc, const char *argv[]) +{ + vector v = v_unit(); + + dbgv("unit vector", v); + v = v_rotate(v, 36); + dbgv("rotated 36", v); + v = v_scale(v, 2); + dbgv("scaled x2", v); + v = v_shrinkphi(v); + dbgv("shrunk phi", v); + v = v_rotate(v, -36); + dbgv("rotated -36", v); + + return 0; +} + +#endif +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/penrose.h b/apps/plugins/puzzles/penrose.h new file mode 100644 index 0000000000..ba5ae16f2c --- /dev/null +++ b/apps/plugins/puzzles/penrose.h @@ -0,0 +1,59 @@ +/* penrose.h + * + * Penrose tiling functions. + * + * Provides an interface with which to generate Penrose tilings + * by recursive subdivision of an initial tile of choice (one of the + * four sets of two pairs kite/dart, or thin/thick rhombus). + * + * You supply a callback function and a context pointer, which is + * called with each tile in turn: you choose how many times to recurse. + */ + +#ifndef _PENROSE_H +#define _PENROSE_H + +#ifndef PHI +#define PHI 1.6180339887 +#endif + +typedef struct vector vector; + +double v_x(vector *vs, int i); +double v_y(vector *vs, int i); + +typedef struct penrose_state penrose_state; + +/* Return non-zero to clip the tree here (i.e. not recurse + * below this tile). + * + * Parameters are state, vector array, npoints, depth. + * ctx is inside state. + */ +typedef int (*tile_callback)(penrose_state *, vector *, int, int); + +struct penrose_state { + int start_size; /* initial side length */ + int max_depth; /* Recursion depth */ + + tile_callback new_tile; + void *ctx; /* for callback */ +}; + +enum { PENROSE_P2, PENROSE_P3 }; + +extern int penrose(penrose_state *state, int which, int angle); + +/* Returns the side-length of a penrose tile at recursion level + * gen, given a starting side length. */ +extern double penrose_side_length(double start_size, int depth); + +/* Returns the count of each type of tile at a given recursion depth. */ +extern void penrose_count_tiles(int gen, int *nlarge, int *nsmall); + +/* Calculate start size and recursion depth required to produce a + * width-by-height sized patch of penrose tiles with the given tilesize */ +extern void penrose_calculate_size(int which, int tilesize, int w, int h, + double *required_radius, int *start_size, int *depth); + +#endif diff --git a/apps/plugins/puzzles/printing.c b/apps/plugins/puzzles/printing.c new file mode 100644 index 0000000000..e921a4d545 --- /dev/null +++ b/apps/plugins/puzzles/printing.c @@ -0,0 +1,247 @@ +/* + * printing.c: Cross-platform printing manager. Handles document + * setup and layout. + */ + +#include "puzzles.h" + +struct puzzle { + const game *game; + game_params *par; + game_state *st; + game_state *st2; +}; + +struct document { + int pw, ph; + int npuzzles; + struct puzzle *puzzles; + int puzzlesize; + int got_solns; + float *colwid, *rowht; + float userscale; +}; + +/* + * Create a new print document. pw and ph are the layout + * parameters: they state how many puzzles will be printed across + * the page, and down the page. + */ +document *document_new(int pw, int ph, float userscale) +{ + document *doc = snew(document); + + doc->pw = pw; + doc->ph = ph; + doc->puzzles = NULL; + doc->puzzlesize = doc->npuzzles = 0; + doc->got_solns = FALSE; + + doc->colwid = snewn(pw, float); + doc->rowht = snewn(ph, float); + + doc->userscale = userscale; + + return doc; +} + +/* + * Free a document structure, whether it's been printed or not. + */ +void document_free(document *doc) +{ + int i; + + for (i = 0; i < doc->npuzzles; i++) { + doc->puzzles[i].game->free_params(doc->puzzles[i].par); + doc->puzzles[i].game->free_game(doc->puzzles[i].st); + if (doc->puzzles[i].st2) + doc->puzzles[i].game->free_game(doc->puzzles[i].st2); + } + + sfree(doc->colwid); + sfree(doc->rowht); + + sfree(doc->puzzles); + sfree(doc); +} + +/* + * Called from midend.c to add a puzzle to be printed. Provides a + * game_params (for initial layout computation), a game_state, and + * optionally a second game_state to be printed in parallel on + * another sheet (typically the solution to the first game_state). + */ +void document_add_puzzle(document *doc, const game *game, game_params *par, + game_state *st, game_state *st2) +{ + if (doc->npuzzles >= doc->puzzlesize) { + doc->puzzlesize += 32; + doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle); + } + doc->puzzles[doc->npuzzles].game = game; + doc->puzzles[doc->npuzzles].par = par; + doc->puzzles[doc->npuzzles].st = st; + doc->puzzles[doc->npuzzles].st2 = st2; + doc->npuzzles++; + if (st2) + doc->got_solns = TRUE; +} + +static void get_puzzle_size(document *doc, struct puzzle *pz, + float *w, float *h, float *scale) +{ + float ww, hh, ourscale; + + /* Get the preferred size of the game, in mm. */ + pz->game->print_size(pz->par, &ww, &hh); + + /* Adjust for user-supplied scale factor. */ + ourscale = doc->userscale; + + /* + * FIXME: scale it down here if it's too big for the page size. + * Rather than do complicated things involving scaling all + * columns down in proportion, the simplest approach seems to + * me to be to scale down until the game fits within one evenly + * divided cell of the page (i.e. width/pw by height/ph). + * + * In order to do this step we need the page size available. + */ + + *scale = ourscale; + *w = ww * ourscale; + *h = hh * ourscale; +} + +/* + * Having accumulated a load of puzzles, actually do the printing. + */ +void document_print(document *doc, drawing *dr) +{ + int ppp; /* puzzles per page */ + int pages, passes; + int page, pass; + int pageno; + + ppp = doc->pw * doc->ph; + pages = (doc->npuzzles + ppp - 1) / ppp; + passes = (doc->got_solns ? 2 : 1); + + print_begin_doc(dr, pages * passes); + + pageno = 1; + for (pass = 0; pass < passes; pass++) { + for (page = 0; page < pages; page++) { + int i, n, offset; + float colsum, rowsum; + + print_begin_page(dr, pageno); + + offset = page * ppp; + n = min(ppp, doc->npuzzles - offset); + + for (i = 0; i < doc->pw; i++) + doc->colwid[i] = 0; + for (i = 0; i < doc->ph; i++) + doc->rowht[i] = 0; + + /* + * Lay the page out by computing all the puzzle sizes. + */ + for (i = 0; i < n; i++) { + struct puzzle *pz = doc->puzzles + offset + i; + int x = i % doc->pw, y = i / doc->pw; + float w, h, scale; + + get_puzzle_size(doc, pz, &w, &h, &scale); + + /* Update the maximum width/height of this column. */ + doc->colwid[x] = max(doc->colwid[x], w); + doc->rowht[y] = max(doc->rowht[y], h); + } + + /* + * Add up the maximum column/row widths to get the + * total amount of space used up by puzzles on the + * page. We will use this to compute gutter widths. + */ + colsum = 0.0; + for (i = 0; i < doc->pw; i++) + colsum += doc->colwid[i]; + rowsum = 0.0; + for (i = 0; i < doc->ph; i++) + rowsum += doc->rowht[i]; + + /* + * Now do the printing. + */ + for (i = 0; i < n; i++) { + struct puzzle *pz = doc->puzzles + offset + i; + int x = i % doc->pw, y = i / doc->pw, j; + float w, h, scale, xm, xc, ym, yc; + int pixw, pixh, tilesize; + + if (pass == 1 && !pz->st2) + continue; /* nothing to do */ + + /* + * The total amount of gutter space is the page + * width minus colsum. This is divided into pw+1 + * gutters, so the amount of horizontal gutter + * space appearing to the left of this puzzle + * column is + * + * (width-colsum) * (x+1)/(pw+1) + * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1)) + */ + xm = (float)(x+1) / (doc->pw + 1); + xc = -xm * colsum; + /* And similarly for y. */ + ym = (float)(y+1) / (doc->ph + 1); + yc = -ym * rowsum; + + /* + * However, the amount of space to the left of this + * puzzle isn't just gutter space: we must also + * count the widths of all the previous columns. + */ + for (j = 0; j < x; j++) + xc += doc->colwid[j]; + /* And similarly for rows. */ + for (j = 0; j < y; j++) + yc += doc->rowht[j]; + + /* + * Now we adjust for this _specific_ puzzle, which + * means centring it within the cell we've just + * computed. + */ + get_puzzle_size(doc, pz, &w, &h, &scale); + xc += (doc->colwid[x] - w) / 2; + yc += (doc->rowht[y] - h) / 2; + + /* + * And now we know where and how big we want to + * print the puzzle, just go ahead and do so. For + * the moment I'll pick a standard pixel tile size + * of 512. + * + * (FIXME: would it be better to pick this value + * with reference to the printer resolution? Or + * permit each game to choose its own?) + */ + tilesize = 512; + pz->game->compute_size(pz->par, tilesize, &pixw, &pixh); + print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale); + pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize); + print_end_puzzle(dr); + } + + print_end_page(dr, pageno); + pageno++; + } + } + + print_end_doc(dr); +} diff --git a/apps/plugins/puzzles/ps.c b/apps/plugins/puzzles/ps.c new file mode 100644 index 0000000000..67d76b4daa --- /dev/null +++ b/apps/plugins/puzzles/ps.c @@ -0,0 +1,433 @@ +/* + * ps.c: PostScript printing functions. + */ + +#include +#include +#include +#include "rbassert.h" + +#include "puzzles.h" + +#define ROOT2 1.414213562 + +struct psdata { + FILE *fp; + int colour; + int ytop; + int clipped; + float hatchthick, hatchspace; + int gamewidth, gameheight; + drawing *drawing; +}; + +static void ps_printf(psdata *ps, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(ps->fp, fmt, ap); + va_end(ap); +} + +static void ps_fill(psdata *ps, int colour) +{ + int hatch; + float r, g, b; + + print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b); + + if (hatch < 0) { + if (ps->colour) + ps_printf(ps, "%g %g %g setrgbcolor fill\n", r, g, b); + else + ps_printf(ps, "%g setgray fill\n", r); + } else { + /* Clip to the region. */ + ps_printf(ps, "gsave clip\n"); + /* Hatch the entire game printing area. */ + ps_printf(ps, "newpath\n"); + if (hatch == HATCH_VERT || hatch == HATCH_PLUS) + ps_printf(ps, "0 %g %d {\n" + " 0 moveto 0 %d rlineto\n" + "} for\n", ps->hatchspace, ps->gamewidth, + ps->gameheight); + if (hatch == HATCH_HORIZ || hatch == HATCH_PLUS) + ps_printf(ps, "0 %g %d {\n" + " 0 exch moveto %d 0 rlineto\n" + "} for\n", ps->hatchspace, ps->gameheight, + ps->gamewidth); + if (hatch == HATCH_SLASH || hatch == HATCH_X) + ps_printf(ps, "%d %g %d {\n" + " 0 moveto %d dup rlineto\n" + "} for\n", -ps->gameheight, ps->hatchspace * ROOT2, + ps->gamewidth, max(ps->gamewidth, ps->gameheight)); + if (hatch == HATCH_BACKSLASH || hatch == HATCH_X) + ps_printf(ps, "0 %g %d {\n" + " 0 moveto %d neg dup neg rlineto\n" + "} for\n", ps->hatchspace * ROOT2, + ps->gamewidth+ps->gameheight, + max(ps->gamewidth, ps->gameheight)); + ps_printf(ps, "0 setgray %g setlinewidth stroke grestore\n", + ps->hatchthick); + } +} + +static void ps_setcolour_internal(psdata *ps, int colour, char *suffix) +{ + int hatch; + float r, g, b; + + print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b); + + /* + * Stroking in hatched colours is not permitted. + */ + assert(hatch < 0); + + if (ps->colour) + ps_printf(ps, "%g %g %g setrgbcolor%s\n", r, g, b, suffix); + else + ps_printf(ps, "%g setgray%s\n", r, suffix); +} + +static void ps_setcolour(psdata *ps, int colour) +{ + ps_setcolour_internal(ps, colour, ""); +} + +static void ps_stroke(psdata *ps, int colour) +{ + ps_setcolour_internal(ps, colour, " stroke"); +} + +static void ps_draw_text(void *handle, int x, int y, int fonttype, + int fontsize, int align, int colour, char *text) +{ + psdata *ps = (psdata *)handle; + + y = ps->ytop - y; + ps_setcolour(ps, colour); + ps_printf(ps, "/%s findfont %d scalefont setfont\n", + fonttype == FONT_FIXED ? "Courier-L1" : "Helvetica-L1", + fontsize); + if (align & ALIGN_VCENTRE) { + ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath" + " pathbbox\n" + "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n", + y, x); + } else { + ps_printf(ps, "%d %d moveto\n", x, y); + } + ps_printf(ps, "("); + while (*text) { + if (*text == '\\' || *text == '(' || *text == ')') + ps_printf(ps, "\\"); + ps_printf(ps, "%c", *text); + text++; + } + ps_printf(ps, ") "); + if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT)) + ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n", + (align & ALIGN_HCENTRE) ? "2 div " : ""); + else + ps_printf(ps, "show\n"); +} + +static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour) +{ + psdata *ps = (psdata *)handle; + + y = ps->ytop - y; + /* + * Offset by half a pixel for the exactness requirement. + */ + ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto" + " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w); + ps_fill(ps, colour); +} + +static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2, + int colour) +{ + psdata *ps = (psdata *)handle; + + y1 = ps->ytop - y1; + y2 = ps->ytop - y2; + ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2); + ps_stroke(ps, colour); +} + +static void ps_draw_polygon(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + psdata *ps = (psdata *)handle; + + int i; + + ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]); + + for (i = 1; i < npoints; i++) + ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]); + + ps_printf(ps, "closepath\n"); + + if (fillcolour >= 0) { + ps_printf(ps, "gsave\n"); + ps_fill(ps, fillcolour); + ps_printf(ps, "grestore\n"); + } + ps_stroke(ps, outlinecolour); +} + +static void ps_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + psdata *ps = (psdata *)handle; + + cy = ps->ytop - cy; + + ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius); + + if (fillcolour >= 0) { + ps_printf(ps, "gsave\n"); + ps_fill(ps, fillcolour); + ps_printf(ps, "grestore\n"); + } + ps_stroke(ps, outlinecolour); +} + +static void ps_unclip(void *handle) +{ + psdata *ps = (psdata *)handle; + + assert(ps->clipped); + ps_printf(ps, "grestore\n"); + ps->clipped = FALSE; +} + +static void ps_clip(void *handle, int x, int y, int w, int h) +{ + psdata *ps = (psdata *)handle; + + if (ps->clipped) + ps_unclip(ps); + + y = ps->ytop - y; + /* + * Offset by half a pixel for the exactness requirement. + */ + ps_printf(ps, "gsave\n"); + ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto" + " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w); + ps_printf(ps, "clip\n"); + ps->clipped = TRUE; +} + +static void ps_line_width(void *handle, float width) +{ + psdata *ps = (psdata *)handle; + + ps_printf(ps, "%g setlinewidth\n", width); +} + +static void ps_line_dotted(void *handle, int dotted) +{ + psdata *ps = (psdata *)handle; + + if (dotted) { + ps_printf(ps, "[ currentlinewidth 3 mul ] 0 setdash\n"); + } else { + ps_printf(ps, "[ ] 0 setdash\n"); + } +} + +static char *ps_text_fallback(void *handle, const char *const *strings, + int nstrings) +{ + /* + * We can handle anything in ISO 8859-1, and we'll manually + * translate it out of UTF-8 for the purpose. + */ + int i, maxlen; + char *ret; + + maxlen = 0; + for (i = 0; i < nstrings; i++) { + int len = strlen(strings[i]); + if (maxlen < len) maxlen = len; + } + + ret = snewn(maxlen + 1, char); + + for (i = 0; i < nstrings; i++) { + const char *p = strings[i]; + char *q = ret; + + while (*p) { + int c = (unsigned char)*p++; + if (c < 0x80) { + *q++ = c; /* ASCII */ + } else if ((c == 0xC2 || c == 0xC3) && (*p & 0xC0) == 0x80) { + *q++ = (c << 6) | (*p++ & 0x3F); /* top half of 8859-1 */ + } else { + break; + } + } + + if (!*p) { + *q = '\0'; + return ret; + } + } + + assert(!"Should never reach here"); + return NULL; +} + +static void ps_begin_doc(void *handle, int pages) +{ + psdata *ps = (psdata *)handle; + + fputs("%!PS-Adobe-3.0\n", ps->fp); + fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp); + fputs("%%DocumentData: Clean7Bit\n", ps->fp); + fputs("%%LanguageLevel: 1\n", ps->fp); + fprintf(ps->fp, "%%%%Pages: %d\n", pages); + fputs("%%DocumentNeededResources:\n", ps->fp); + fputs("%%+ font Helvetica\n", ps->fp); + fputs("%%+ font Courier\n", ps->fp); + fputs("%%EndComments\n", ps->fp); + fputs("%%BeginSetup\n", ps->fp); + fputs("%%IncludeResource: font Helvetica\n", ps->fp); + fputs("%%IncludeResource: font Courier\n", ps->fp); + fputs("%%EndSetup\n", ps->fp); + fputs("%%BeginProlog\n", ps->fp); + /* + * Re-encode Helvetica and Courier into ISO-8859-1, which gives + * us times and divide signs - and also (according to the + * Language Reference Manual) a bonus in that the ASCII '-' code + * point now points to a minus sign instead of a hyphen. + */ + fputs("/Helvetica findfont " /* get the font dictionary */ + "dup maxlength dict dup begin " /* create and open a new dict */ + "exch " /* move the original font to top of stack */ + "{1 index /FID ne {def} {pop pop} ifelse} forall " + /* copy everything except FID */ + "/Encoding ISOLatin1Encoding def " + /* set the thing we actually wanted to change */ + "/FontName /Helvetica-L1 def " /* set a new font name */ + "FontName end exch definefont" /* and define the font */ + "\n", ps->fp); + fputs("/Courier findfont " /* get the font dictionary */ + "dup maxlength dict dup begin " /* create and open a new dict */ + "exch " /* move the original font to top of stack */ + "{1 index /FID ne {def} {pop pop} ifelse} forall " + /* copy everything except FID */ + "/Encoding ISOLatin1Encoding def " + /* set the thing we actually wanted to change */ + "/FontName /Courier-L1 def " /* set a new font name */ + "FontName end exch definefont" /* and define the font */ + "\n", ps->fp); + fputs("%%EndProlog\n", ps->fp); +} + +static void ps_begin_page(void *handle, int number) +{ + psdata *ps = (psdata *)handle; + + fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n", + number, number, 72.0 / 25.4); +} + +static void ps_begin_puzzle(void *handle, float xm, float xc, + float ym, float yc, int pw, int ph, float wmm) +{ + psdata *ps = (psdata *)handle; + + fprintf(ps->fp, "gsave\n" + "clippath flattenpath pathbbox pop pop translate\n" + "clippath flattenpath pathbbox 4 2 roll pop pop\n" + "exch %g mul %g add exch dup %g mul %g add sub translate\n" + "%g dup scale\n" + "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph); + ps->ytop = ph; + ps->clipped = FALSE; + ps->gamewidth = pw; + ps->gameheight = ph; + ps->hatchthick = 0.2 * pw / wmm; + ps->hatchspace = 1.0 * pw / wmm; +} + +static void ps_end_puzzle(void *handle) +{ + psdata *ps = (psdata *)handle; + + fputs("grestore\n", ps->fp); +} + +static void ps_end_page(void *handle, int number) +{ + psdata *ps = (psdata *)handle; + + fputs("restore grestore showpage\n", ps->fp); +} + +static void ps_end_doc(void *handle) +{ + psdata *ps = (psdata *)handle; + + fputs("%%EOF\n", ps->fp); +} + +static const struct drawing_api ps_drawing = { + ps_draw_text, + ps_draw_rect, + ps_draw_line, + ps_draw_polygon, + ps_draw_circle, + NULL /* draw_update */, + ps_clip, + ps_unclip, + NULL /* start_draw */, + NULL /* end_draw */, + NULL /* status_bar */, + NULL /* blitter_new */, + NULL /* blitter_free */, + NULL /* blitter_save */, + NULL /* blitter_load */, + ps_begin_doc, + ps_begin_page, + ps_begin_puzzle, + ps_end_puzzle, + ps_end_page, + ps_end_doc, + ps_line_width, + ps_line_dotted, + ps_text_fallback, +}; + +psdata *ps_init(FILE *outfile, int colour) +{ + psdata *ps = snew(psdata); + + ps->fp = outfile; + ps->colour = colour; + ps->ytop = 0; + ps->clipped = FALSE; + ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0; + ps->drawing = drawing_new(&ps_drawing, NULL, ps); + + return ps; +} + +void ps_free(psdata *ps) +{ + drawing_free(ps->drawing); + sfree(ps); +} + +drawing *ps_drawing_api(psdata *ps) +{ + return ps->drawing; +} diff --git a/apps/plugins/puzzles/puzzles.but b/apps/plugins/puzzles/puzzles.but new file mode 100644 index 0000000000..2508fe3337 --- /dev/null +++ b/apps/plugins/puzzles/puzzles.but @@ -0,0 +1,3401 @@ +\title Simon Tatham's Portable Puzzle Collection + +\cfg{winhelp-filename}{puzzles.hlp} +\cfg{winhelp-contents-titlepage}{Contents} + +\cfg{text-filename}{puzzles.txt} + +\cfg{html-contents-filename}{index.html} +\cfg{html-template-filename}{%k.html} +\cfg{html-index-filename}{docindex.html} +\cfg{html-leaf-level}{1} +\cfg{html-contents-depth-0}{1} +\cfg{html-contents-depth-1}{2} +\cfg{html-leaf-contains-contents}{true} + +\cfg{info-filename}{puzzles.info} + +\cfg{ps-filename}{puzzles.ps} +\cfg{pdf-filename}{puzzles.pdf} + +\define{by} \u00D7{x} + +\define{dash} \u2013{-} + +\define{times} \u00D7{*} + +\define{divide} \u00F7{/} + +\define{minus} \u2212{-} + +This is a collection of small one-player puzzle games. + +\copyright This manual is copyright 2004-2014 Simon Tatham. All rights +reserved. You may distribute this documentation under the MIT licence. +See \k{licence} for the licence text in full. + +\cfg{html-local-head}{} + +\C{intro} Introduction + +I wrote this collection because I thought there should be more small +desktop toys available: little games you can pop up in a window and +play for two or three minutes while you take a break from whatever +else you were doing. And I was also annoyed that every time I found +a good game on (say) \i{Unix}, it wasn't available the next time I +was sitting at a \i{Windows} machine, or vice versa; so I arranged +that everything in my personal puzzle collection will happily run on +both, and have more recently done a port to \i{Mac OS X} as well. When I +find (or perhaps invent) further puzzle games that I like, they'll +be added to this collection and will immediately be available on +both platforms. And if anyone feels like writing any other front +ends \dash PocketPC, Mac OS pre-10, or whatever it might be \dash +then all the games in this framework will immediately become +available on another platform as well. + +The actual games in this collection were mostly not my invention; they +are re-implementations of existing game concepts within my portable +puzzle framework. I do not claim credit, in general, for inventing the +rules of any of these puzzles. (I don't even claim authorship of all +the code; some of the puzzles have been submitted by other authors.) + +This collection is distributed under the \i{MIT licence} (see +\k{licence}). This means that you can do pretty much anything you like +with the game binaries or the code, except pretending you wrote them +yourself, or suing me if anything goes wrong. + +The most recent versions, and \i{source code}, can be found at +\I{website}\W{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}. + +Please report \I{feedback}\i{bugs} to +\W{mailto:anakin@pobox.com}\cw{anakin@pobox.com}. +You might find it helpful to read this article before reporting a bug: + +\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html} + +\ii{Patches} are welcome. Especially if they provide a new front end +(to make all these games run on another platform), or a new game. + + +\C{common} \ii{Common features} + +This chapter describes features that are common to all the games. + +\H{common-actions} \I{controls}Common actions + +These actions are all available from the \I{Game menu}\q{Game} menu +and via \I{keys}keyboard shortcuts, in addition to any game-specific +actions. + +(On \i{Mac OS X}, to conform with local user interface standards, these +actions are situated on the \I{File menu}\q{File} and \I{Edit +menu}\q{Edit} menus instead.) + +\dt \ii\e{New game} (\q{N}, Ctrl+\q{N}) + +\dd Starts a new game, with a random initial state. + +\dt \ii\e{Restart game} + +\dd Resets the current game to its initial state. (This can be undone.) + +\dt \ii\e{Load} + +\dd Loads a saved game from a file on disk. + +\dt \ii\e{Save} + +\dd Saves the current state of your game to a file on disk. + +\lcont{ + +The Load and Save operations preserve your entire game +history (so you can save, reload, and still Undo and Redo things you +had done before saving). + +} + +\dt \I{printing, on Windows}\e{Print} + +\dd Where supported (currently only on Windows), brings up a dialog +allowing you to print an arbitrary number of puzzles randomly +generated from the current parameters, optionally including the +current puzzle. (Only for puzzles which make sense to print, of +course \dash it's hard to think of a sensible printable representation +of Fifteen!) + +\dt \ii\e{Undo} (\q{U}, Ctrl+\q{Z}, Ctrl+\q{_}) + +\dd Undoes a single move. (You can undo moves back to the start of the +session.) + +\dt \ii\e{Redo} (\q{R}, Ctrl+\q{R}) + +\dd Redoes a previously undone move. + +\dt \ii\e{Copy} + +\dd Copies the current state of your game to the clipboard in text +format, so that you can paste it into (say) an e-mail client or a +web message board if you're discussing the game with someone else. +(Not all games support this feature.) + +\dt \ii\e{Solve} + +\dd Transforms the puzzle instantly into its solved state. For some +games (Cube) this feature is not supported at all because it is of +no particular use. For other games (such as Pattern), the solved +state can be used to give you information, if you can't see how a +solution can exist at all or you want to know where you made a +mistake. For still other games (such as Sixteen), automatic solution +tells you nothing about how to \e{get} to the solution, but it does +provide a useful way to get there quickly so that you can experiment +with set-piece moves and transformations. + +\lcont{ + +Some games (such as Solo) are capable of solving a game ID you have +typed in from elsewhere. Other games (such as Rectangles) cannot +solve a game ID they didn't invent themself, but when they did +invent the game ID they know what the solution is already. Still +other games (Pattern) can solve \e{some} external game IDs, but only +if they aren't too difficult. + +The \q{Solve} command adds the solved state to the end of the undo +chain for the puzzle. In other words, if you want to go back to +solving it yourself after seeing the answer, you can just press Undo. + +} + +\dt \I{exit}\ii\e{Quit} (\q{Q}, Ctrl+\q{Q}) + +\dd Closes the application entirely. + +\H{common-id} Specifying games with the \ii{game ID} + +There are two ways to save a game specification out of a puzzle and +recreate it later, or recreate it in somebody else's copy of the +same puzzle. + +The \q{\i{Specific}} and \q{\i{Random Seed}} options from the +\I{Game menu}\q{Game} menu (or the \q{File} menu, on \i{Mac OS X}) each +show a piece of text (a \q{game ID}) which is sufficient to +reconstruct precisely the same game at a later date. + +You can enter either of these pieces of text back into the program +(via the same \q{Specific} or \q{Random Seed} menu options) at a +later point, and it will recreate the same game. You can also use +either one as a \i{command line} argument (on Windows or Unix); see +\k{common-cmdline} for more detail. + +The difference between the two forms is that a descriptive game ID +is a literal \e{description} of the \i{initial state} of the game, +whereas a random seed is just a piece of arbitrary text which was +provided as input to the random number generator used to create the +puzzle. This means that: + +\b Descriptive game IDs tend to be longer in many puzzles (although +some, such as Cube (\k{cube}), only need very short descriptions). +So a random seed is often a \e{quicker} way to note down the puzzle +you're currently playing, or to tell it to somebody else so they can +play the same one as you. + +\b Any text at all is a valid random seed. The automatically +generated ones are fifteen-digit numbers, but anything will do; you +can type in your full name, or a word you just made up, and a valid +puzzle will be generated from it. This provides a way for two or +more people to race to complete the same puzzle: you think of a +random seed, then everybody types it in at the same time, and nobody +has an advantage due to having seen the generated puzzle before +anybody else. + +\b It is often possible to convert puzzles from other sources (such +as \q{nonograms} or \q{sudoku} from newspapers) into descriptive +game IDs suitable for use with these programs. + +\b Random seeds are not guaranteed to produce the same result if you +use them with a different \i\e{version} of the puzzle program. This +is because the generation algorithm might have been improved or +modified in later versions of the code, and will therefore produce a +different result when given the same sequence of random numbers. Use +a descriptive game ID if you aren't sure that it will be used on the +same version of the program as yours. + +\lcont{(Use the \q{About} menu option to find out the version number +of the program. Programs with the same version number running on +different platforms should still be random-seed compatible.)} + +\I{ID format}A descriptive game ID starts with a piece of text which +encodes the \i\e{parameters} of the current game (such as grid +size). Then there is a colon, and after that is the description of +the game's initial state. A random seed starts with a similar string +of parameters, but then it contains a hash sign followed by +arbitrary data. + +If you enter a descriptive game ID, the program will not be able to +show you the random seed which generated it, since it wasn't +generated \e{from} a random seed. If you \e{enter} a random seed, +however, the program will be able to show you the descriptive game +ID derived from that random seed. + +Note that the game parameter strings are not always identical +between the two forms. For some games, there will be parameter data +provided with the random seed which is not included in the +descriptive game ID. This is because that parameter information is +only relevant when \e{generating} puzzle grids, and is not important +when playing them. Thus, for example, the difficulty level in Solo +(\k{solo}) is not mentioned in the descriptive game ID. + +These additional parameters are also not set permanently if you type +in a game ID. For example, suppose you have Solo set to \q{Advanced} +difficulty level, and then a friend wants your help with a +\q{Trivial} puzzle; so the friend reads out a random seed specifying +\q{Trivial} difficulty, and you type it in. The program will +generate you the same \q{Trivial} grid which your friend was having +trouble with, but once you have finished playing it, when you ask +for a new game it will automatically go back to the \q{Advanced} +difficulty which it was previously set on. + +\H{common-type} The \q{Type} menu + +The \I{Type menu}\q{Type} menu, if present, may contain a list of +\i{preset} game settings. Selecting one of these will start a new +random game with the parameters specified. + +The \q{Type} menu may also contain a \q{\i{Custom}} option which +allows you to fine-tune game \i{parameters}. The parameters +available are specific to each game and are described in the +following sections. + +\H{common-cmdline} Specifying game parameters on the \i{command line} + +(This section does not apply to the \i{Mac OS X} version.) + +The games in this collection deliberately do not ever save +information on to the computer they run on: they have no high score +tables and no saved preferences. (This is because I expect at least +some people to play them at work, and those people will probably +appreciate leaving as little evidence as possible!) + +However, if you do want to arrange for one of these games to +\I{default parameters, specifying}default to a particular set of +parameters, you can specify them on the command line. + +The easiest way to do this is to set up the parameters you want +using the \q{Type} menu (see \k{common-type}), and then to select +\q{Random Seed} from the \q{Game} or \q{File} menu (see +\k{common-id}). The text in the \q{Game ID} box will be composed of +two parts, separated by a hash. The first of these parts represents +the game parameters (the size of the playing area, for example, and +anything else you set using the \q{Type} menu). + +If you run the game with just that parameter text on the command +line, it will start up with the settings you specified. + +For example: if you run Cube (see \k{cube}), select \q{Octahedron} +from the \q{Type} menu, and then go to the game ID selection, you +will see a string of the form \cq{o2x2#338686542711620}. Take only +the part before the hash (\cq{o2x2}), and start Cube with that text +on the command line: \cq{PREFIX-cube o2x2}. + +If you copy the \e{entire} game ID on to the command line, the game +will start up in the specific game that was described. This is +occasionally a more convenient way to start a particular game ID +than by pasting it into the game ID selection box. + +(You could also retrieve the encoded game parameters using the +\q{Specific} menu option instead of \q{Random Seed}, but if you do +then some options, such as the difficulty level in Solo, will be +missing. See \k{common-id} for more details on this.) + +\H{common-unix-cmdline} \i{Unix} \i{command-line} options + +(This section only applies to the Unix port.) + +In addition to being able to specify game parameters on the command +line (see \k{common-cmdline}), there are various other options: + +\dt \cw{--game} + +\dt \cw{--load} + +\dd These options respectively determine whether the command-line +argument is treated as specifying game parameters or a \i{save} file +to \i{load}. Only one should be specified. If neither of these options +is specified, a guess is made based on the format of the argument. + +\dt \cw{--generate }\e{n} + +\dd If this option is specified, instead of a puzzle being displayed, +a number of descriptive game IDs will be \I{generating game IDs}invented +and printed on standard output. This is useful for gaining access to +the game generation algorithms without necessarily using the frontend. + +\lcont{ + +If game parameters are specified on the command-line, they will be +used to generate the game IDs; otherwise a default set of parameters +will be used. + +The most common use of this option is in conjunction with \c{--print}, +in which case its behaviour is slightly different; see below. + +} + +\dt \I{printing, on Unix}\cw{--print }\e{w}\cw{x}\e{h} + +\dd If this option is specified, instead of a puzzle being displayed, +a printed representation of one or more unsolved puzzles is sent to +standard output, in \i{PostScript} format. + +\lcont{ + +On each page of puzzles, there will be \e{w} across and \e{h} down. If +there are more puzzles than \e{w}\by\e{h}, more than one page will be +printed. + +If \c{--generate} has also been specified, the invented game IDs will +be used to generate the printed output. Otherwise, a list of game IDs +is expected on standard input (which can be descriptive or random +seeds; see \k{common-id}), in the same format produced by +\c{--generate}. + +For example: + +\c PREFIX-net --generate 12 --print 2x3 7x7w | lpr + +will generate two pages of printed Net puzzles (each of which will +have a 7\by\.7 wrapping grid), and pipe the output to the \c{lpr} +command, which on many systems will send them to an actual printer. + +There are various other options which affect printing; see below. + +} + +\dt \cw{--save }\e{file-prefix} [ \cw{--save-suffix }\e{file-suffix} ] + +\dd If this option is specified, instead of a puzzle being +displayed, saved-game files for one or more unsolved puzzles are +written to files constructed from the supplied prefix and/or suffix. + +\lcont{ + +If \c{--generate} has also been specified, the invented game IDs will +be used to generate the printed output. Otherwise, a list of game IDs +is expected on standard input (which can be descriptive or random +seeds; see \k{common-id}), in the same format produced by +\c{--generate}. + +For example: + +\c PREFIX-net --generate 12 --save game --save-suffix .sav + +will generate twelve Net saved-game files with the names +\cw{game0.sav} to \cw{game11.sav}. + +} + +\dt \cw{--version} + +\dd Prints version information about the game, and then quits. + +The following options are only meaningful if \c{--print} is also +specified: + +\dt \cw{--with-solutions} + +\dd The set of pages filled with unsolved puzzles will be followed by +the solutions to those puzzles. + +\dt \cw{--scale }\e{n} + +\dd Adjusts how big each puzzle is when printed. Larger numbers make +puzzles bigger; the default is 1.0. + +\dt \cw{--colour} + +\dd Puzzles will be printed in colour, rather than in black and white +(if supported by the puzzle). + + +\C{net} \i{Net} + +\cfg{winhelp-topic}{games.net} + +(\e{Note:} the \i{Windows} version of this game is called +\i\cw{NETGAME.EXE} to avoid clashing with Windows's own \cw{NET.EXE}.) + +I originally saw this in the form of a Flash game called \i{FreeNet} +\k{FreeNet}, written by Pavils Jurjans; there are several other +implementations under the name \i{NetWalk}. The computer prepares a +network by connecting up the centres of squares in a grid, and then +shuffles the network by rotating every tile randomly. Your job is to +rotate it all back into place. The successful solution will be an +entirely connected network, with no closed loops. \#{The latter +clause means that there are no closed paths within the network. +Could this be clearer? "No closed paths"?} As a visual aid, +all tiles which are connected to the one in the middle are +highlighted. + +\B{FreeNet} \W{http://www.jurjans.lv/stuff/net/FreeNet.htm}\cw{http://www.jurjans.lv/stuff/net/FreeNet.htm} + +\H{net-controls} \i{Net controls} + +\IM{Net controls} controls, for Net +\IM{Net controls} keys, for Net +\IM{Net controls} shortcuts (keyboard), for Net + +This game can be played with either the keyboard or the mouse. The +controls are: + +\dt \e{Select tile}: mouse pointer, arrow keys + +\dt \e{Rotate tile anticlockwise}: left mouse button, \q{A} key + +\dt \e{Rotate tile clockwise}: right mouse button, \q{D} key + +\dt \e{Rotate tile by 180 degrees}: \q{F} key + +\dt \e{Lock (or unlock) tile}: middle mouse button, shift-click, \q{S} key + +\dd You can lock a tile once you're sure of its orientation. You can +also unlock it again, but while it's locked you can't accidentally +turn it. + +The following controls are not necessary to complete the game, but may +be useful: + +\dt \e{Shift grid}: Shift + arrow keys + +\dd On grids that wrap, you can move the origin of the grid, so that +tiles that were on opposite sides of the grid can be seen together. + +\dt \e{Move centre}: Ctrl + arrow keys + +\dd You can change which tile is used as the source of highlighting. +(It doesn't ultimately matter which tile this is, as every tile will +be connected to every other tile in a correct solution, but it may be +helpful in the intermediate stages of solving the puzzle.) + +\dt \e{Jumble tiles}: \q{J} key + +\dd This key turns all tiles that are not locked to random +orientations. + +(All the actions described in \k{common-actions} are also available.) + +\H{net-params} \I{parameters, for Net}Net parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in tiles. + +\dt \e{Walls wrap around} + +\dd If checked, flow can pass from the left edge to the right edge, +and from top to bottom, and vice versa. + +\dt \e{Barrier probability} + +\dd A number between 0.0 and 1.0 controlling whether an immovable +barrier is placed between two tiles to prevent flow between them (a +higher number gives more barriers). Since barriers are immovable, they +act as constraints on the solution (i.e., hints). + +\lcont{ + +The grid generation in Net has been carefully arranged so that the +barriers are independent of the rest of the grid. This means that if +you note down the random seed used to generate the current puzzle +(see \k{common-id}), change the \e{Barrier probability} parameter, +and then re-enter the same random seed, you should see exactly the +same starting grid, with the only change being the number of +barriers. So if you're stuck on a particular grid and need a hint, +you could start up another instance of Net, set up the same +parameters but a higher barrier probability, and enter the game seed +from the original Net window. + +} + +\dt \e{Ensure unique solution} + +\dd Normally, Net will make sure that the puzzles it presents have +only one solution. Puzzles with ambiguous sections can be more +difficult and more subtle, so if you like you can turn off this +feature and risk having ambiguous puzzles. (Also, finding \e{all} +the possible solutions can be an additional challenge for an +advanced player.) + + +\C{cube} \i{Cube} + +\cfg{winhelp-topic}{games.cube} + +This is another one I originally saw as a web game. This one was a +Java game \k{cube-java-game}, by Paul Scott. You have a grid of 16 +squares, six of which are blue; on one square rests a cube. Your move +is to use the arrow keys to roll the cube through 90 degrees so that +it moves to an adjacent square. If you roll the cube on to a blue +square, the blue square is picked up on one face of the cube; if you +roll a blue face of the cube on to a non-blue square, the blueness is +put down again. (In general, whenever you roll the cube, the two faces +that come into contact swap colours.) Your job is to get all six blue +squares on to the six faces of the cube at the same time. Count your +moves and try to do it in as few as possible. + +Unlike the original Java game, my version has an additional feature: +once you've mastered the game with a cube rolling on a square grid, +you can change to a triangular grid and roll any of a tetrahedron, an +octahedron or an icosahedron. + +\B{cube-java-game} \W{http://www3.sympatico.ca/paulscott/cube/cube.htm}\cw{http://www3.sympatico.ca/paulscott/cube/cube.htm} + +\H{cube-controls} \i{Cube controls} + +\IM{Cube controls} controls, for Cube +\IM{Cube controls} keys, for Cube +\IM{Cube controls} shortcuts (keyboard), for Cube + +This game can be played with either the keyboard or the mouse. + +Left-clicking anywhere on the window will move the cube (or other +solid) towards the mouse pointer. + +The arrow keys can also used to roll the cube on its square grid in +the four cardinal directions. +On the triangular grids, the mapping of arrow keys to directions is +more approximate. Vertical movement is disallowed where it doesn't +make sense. The four keys surrounding the arrow keys on the numeric +keypad (\q{7}, \q{9}, \q{1}, \q{3}) can be used for diagonal movement. + +(All the actions described in \k{common-actions} are also available.) + +\H{cube-params} \I{parameters, for Cube}Cube parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Type of solid} + +\dd Selects the solid to roll (and hence the shape of the grid): +tetrahedron, cube, octahedron, or icosahedron. + +\dt \e{Width / top}, \e{Height / bottom} + +\dd On a square grid, horizontal and vertical dimensions. On a +triangular grid, the number of triangles on the top and bottom rows +respectively. + + +\C{fifteen} \i{Fifteen} + +\cfg{winhelp-topic}{games.fifteen} + +The old ones are the best: this is the good old \q{\i{15-puzzle}} +with sliding tiles. You have a 4\by\.4 square grid; 15 squares +contain numbered tiles, and the sixteenth is empty. Your move is to +choose a tile next to the empty space, and slide it into the space. +The aim is to end up with the tiles in numerical order, with the +space in the bottom right (so that the top row reads 1,2,3,4 and the +bottom row reads 13,14,15,\e{space}). + +\H{fifteen-controls} \i{Fifteen controls} + +\IM{Fifteen controls} controls, for Fifteen +\IM{Fifteen controls} keys, for Fifteen +\IM{Fifteen controls} shortcuts (keyboard), for Fifteen + +This game can be controlled with the mouse or the keyboard. + +A left-click with the mouse in the row or column containing the empty +space will move as many tiles as necessary to move the space to the +mouse pointer. + +The arrow keys will move a tile adjacent to the space in the direction +indicated (moving the space in the \e{opposite} direction). + +Pressing \q{h} will make a suggested move. Pressing \q{h} enough +times will solve the game, but it may scramble your progress while +doing so. + +(All the actions described in \k{common-actions} are also available.) + +\H{fifteen-params} \I{parameters, for Fifteen}Fifteen parameters + +The only options available from the \q{Custom...} option on the \q{Type} +menu are \e{Width} and \e{Height}, which are self-explanatory. (Once +you've changed these, it's not a \q{15-puzzle} any more, of course!) + + +\C{sixteen} \i{Sixteen} + +\cfg{winhelp-topic}{games.sixteen} + +Another sliding tile puzzle, visually similar to Fifteen (see +\k{fifteen}) but with a different type of move. This time, there is no +hole: all 16 squares on the grid contain numbered squares. Your move +is to shift an entire row left or right, or shift an entire column up +or down; every time you do that, the tile you shift off the grid +re-appears at the other end of the same row, in the space you just +vacated. To win, arrange the tiles into numerical order (1,2,3,4 on +the top row, 13,14,15,16 on the bottom). When you've done that, try +playing on different sizes of grid. + +I \e{might} have invented this game myself, though only by accident if +so (and I'm sure other people have independently invented it). I +thought I was imitating a screensaver I'd seen, but I have a feeling +that the screensaver might actually have been a Fifteen-type puzzle +rather than this slightly different kind. So this might be the one +thing in my puzzle collection which represents creativity on my part +rather than just engineering. + +\H{sixteen-controls} \I{controls, for Sixteen}Sixteen controls + +Left-clicking on an arrow will move the appropriate row or column in +the direction indicated. Right-clicking will move it in the opposite +direction. + +Alternatively, use the cursor keys to move the position indicator +around the edge of the grid, and use the return key to move the +row/column in the direction indicated. + +You can also move the tiles directly. Move the cursor onto a tile, +hold Control and press an arrow key to move the tile under the +cursor and move the cursor along with the tile. Or, hold Shift to +move only the tile. Pressing Enter simulates holding down Control +(press Enter again to release), while pressing Space simulates +holding down shift. + +(All the actions described in \k{common-actions} are also available.) + +\H{sixteen-params} \I{parameters, for Sixteen}Sixteen parameters + +The parameters available from the \q{Custom...} option on the +\q{Type} menu are: + +\b \e{Width} and \e{Height}, which are self-explanatory. + +\b You can ask for a limited shuffling operation to be performed on +the grid. By default, Sixteen will shuffle the grid in such a way +that any arrangement is about as probable as any other. You can +override this by requesting a precise number of shuffling moves to +be performed. Typically your aim is then to determine the precise +set of shuffling moves and invert them exactly, so that you answer +(say) a four-move shuffle with a four-move solution. Note that the +more moves you ask for, the more likely it is that solutions shorter +than the target length will turn out to be possible. + + +\C{twiddle} \i{Twiddle} + +\cfg{winhelp-topic}{games.twiddle} + +Twiddle is a tile-rearrangement puzzle, visually similar to Sixteen +(see \k{sixteen}): you are given a grid of square tiles, each +containing a number, and your aim is to arrange the numbers into +ascending order. + +In basic Twiddle, your move is to rotate a square group of four +tiles about their common centre. (Orientation is not significant in +the basic puzzle, although you can select it.) On more advanced +settings, you can rotate a larger square group of tiles. + +I first saw this type of puzzle in the GameCube game \q{Metroid +Prime 2}. In the Main Gyro Chamber in that game, there is a puzzle +you solve to unlock a door, which is a special case of Twiddle. I +developed this game as a generalisation of that puzzle. + +\H{twiddle-controls} \I{controls, for Twiddle}Twiddle controls + +To play Twiddle, click the mouse in the centre of the square group +you wish to rotate. In the basic mode, you rotate a 2\by\.2 square, +which means you have to click at a corner point where four tiles +meet. + +In more advanced modes you might be rotating 3\by\.3 or even more at +a time; if the size of the square is odd then you simply click in +the centre tile of the square you want to rotate. + +Clicking with the left mouse button rotates the group anticlockwise. +Clicking with the right button rotates it clockwise. + +You can also move an outline square around the grid with the cursor +keys; the square is the size above (2\by\.2 by default, or larger). +Pressing the return key or space bar will rotate the current square +anticlockwise or clockwise respectively. + +(All the actions described in \k{common-actions} are also available.) + +\H{twiddle-parameters} \I{parameters, for Twiddle}Twiddle parameters + +Twiddle provides several configuration options via the \q{Custom} +option on the \q{Type} menu: + +\b You can configure the width and height of the puzzle grid. + +\b You can configure the size of square block that rotates at a time. + +\b You can ask for every square in the grid to be distinguishable +(the default), or you can ask for a simplified puzzle in which there +are groups of identical numbers. In the simplified puzzle your aim +is just to arrange all the 1s into the first row, all the 2s into +the second row, and so on. + +\b You can configure whether the orientation of tiles matters. If +you ask for an orientable puzzle, each tile will have a triangle +drawn in it. All the triangles must be pointing upwards to complete +the puzzle. + +\b You can ask for a limited shuffling operation to be performed on +the grid. By default, Twiddle will shuffle the grid so much that any +arrangement is about as probable as any other. You can override this +by requesting a precise number of shuffling moves to be performed. +Typically your aim is then to determine the precise set of shuffling +moves and invert them exactly, so that you answer (say) a four-move +shuffle with a four-move solution. Note that the more moves you ask +for, the more likely it is that solutions shorter than the target +length will turn out to be possible. + + +\C{rect} \i{Rectangles} + +\cfg{winhelp-topic}{games.rectangles} + +You have a grid of squares, with numbers written in some (but not all) +of the squares. Your task is to subdivide the grid into rectangles of +various sizes, such that (a) every rectangle contains exactly one +numbered square, and (b) the area of each rectangle is equal to the +number written in its numbered square. + +Credit for this game goes to the Japanese puzzle magazine \i{Nikoli} +\k{nikoli-rect}; I've also seen a Palm implementation at \i{Puzzle +Palace} \k{puzzle-palace-rect}. Unlike Puzzle Palace's +implementation, my version automatically generates random grids of +any size you like. The quality of puzzle design is therefore not +quite as good as hand-crafted puzzles would be, but on the plus side +you get an inexhaustible supply of puzzles tailored to your own +specification. + +\B{nikoli-rect} \W{http://www.nikoli.co.jp/en/puzzles/shikaku.html}\cw{http://www.nikoli.co.jp/en/puzzles/shikaku.html} +(beware of Flash) + +\B{puzzle-palace-rect} \W{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en}\cw{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en} + +\H{rectangles-controls} \I{controls, for Rectangles}Rectangles controls + +This game is played with the mouse or cursor keys. + +Left-click any edge to toggle it on or off, or left-click and drag to draw +an entire rectangle (or line) on the grid in one go (removing any existing +edges within that rectangle). Right-clicking and dragging will allow you +to erase the contents of a rectangle without affecting its edges. + +Alternatively, use the cursor keys to move the position indicator +around the board. Pressing the return key then allows you to use the +cursor keys to drag a rectangle out from that position, and pressing +the return key again completes the rectangle. Using the space bar +instead of the return key allows you to erase the contents of a +rectangle without affecting its edges, as above. Pressing escape +cancels a drag. + +When a rectangle of the correct size is completed, it will be shaded. + +(All the actions described in \k{common-actions} are also available.) + +\H{rectangles-params} \I{parameters, for Rectangles}Rectangles parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid, in squares. + +\dt \e{Expansion factor} + +\dd This is a mechanism for changing the type of grids generated by +the program. Some people prefer a grid containing a few large +rectangles to one containing many small ones. So you can ask +Rectangles to essentially generate a \e{smaller} grid than the size +you specified, and then to expand it by adding rows and columns. + +\lcont{ + +The default expansion factor of zero means that Rectangles will +simply generate a grid of the size you ask for, and do nothing +further. If you set an expansion factor of (say) 0.5, it means that +each dimension of the grid will be expanded to half again as big +after generation. In other words, the initial grid will be 2/3 the +size in each dimension, and will be expanded to its full size +without adding any more rectangles. + +Setting an expansion factor of around 0.5 tends to make the game +more difficult, and also (in my experience) rewards a less deductive +and more intuitive playing style. If you set it \e{too} high, +though, the game simply cannot generate more than a few rectangles +to cover the entire grid, and the game becomes trivial. + +} + +\dt \e{Ensure unique solution} + +\dd Normally, Rectangles will make sure that the puzzles it presents +have only one solution. Puzzles with ambiguous sections can be more +difficult and more subtle, so if you like you can turn off this +feature and risk having ambiguous puzzles. Also, finding \e{all} the +possible solutions can be an additional challenge for an advanced +player. Turning off this option can also speed up puzzle generation. + + +\C{netslide} \i{Netslide} + +\cfg{winhelp-topic}{games.netslide} + +This game combines the grid generation of Net (see \k{net}) with the +movement of Sixteen (see \k{sixteen}): you have a Net grid, but +instead of rotating tiles back into place you have to slide them +into place by moving a whole row at a time. + +As in Sixteen, \I{controls, for Netslide}control is with the mouse or +cursor keys. See \k{sixteen-controls}. + +\I{parameters, for Netslide}The available game parameters have similar +meanings to those in Net (see \k{net-params}) and Sixteen (see +\k{sixteen-params}). + +Netslide was contributed to this collection by Richard Boulton. + + +\C{pattern} \i{Pattern} + +\cfg{winhelp-topic}{games.pattern} + +You have a grid of squares, which must all be filled in either black +or white. Beside each row of the grid are listed the lengths of the +runs of black squares on that row; above each column are listed the +lengths of the runs of black squares in that column. Your aim is to +fill in the entire grid black or white. + +I first saw this puzzle form around 1995, under the name +\q{\i{nonograms}}. I've seen it in various places since then, under +different names. + +Normally, puzzles of this type turn out to be a meaningful picture +of something once you've solved them. However, since this version +generates the puzzles automatically, they will just look like random +groupings of squares. (One user has suggested that this is actually +a \e{good} thing, since it prevents you from guessing the colour of +squares based on the picture, and forces you to use logic instead.) +The advantage, though, is that you never run out of them. + +\H{pattern-controls} \I{controls, for Pattern}Pattern controls + +This game is played with the mouse. + +Left-click in a square to colour it black. Right-click to colour it +white. If you make a mistake, you can middle-click, or hold down +Shift while clicking with any button, to colour the square in the +default grey (meaning \q{undecided}) again. + +You can click and drag with the left or right mouse button to colour +a vertical or horizontal line of squares black or white at a time +(respectively). If you click and drag with the middle button, or +with Shift held down, you can colour a whole rectangle of squares +grey. + +You can also move around the grid with the cursor keys. Pressing the +return key will cycle the current cell through empty, then black, then +white, then empty, and the space bar does the same cycle in reverse. + +Moving the cursor while holding Control will colour the moved-over +squares black. Holding Shift will colour the moved-over squares +white, and holding both will colour them grey. + +(All the actions described in \k{common-actions} are also available.) + +\H{pattern-parameters} \I{parameters, for Pattern}Pattern parameters + +The only options available from the \q{Custom...} option on the \q{Type} +menu are \e{Width} and \e{Height}, which are self-explanatory. + + +\C{solo} \i{Solo} + +\cfg{winhelp-topic}{games.solo} + +You have a square grid, which is divided into as many equally sized +sub-blocks as the grid has rows. Each square must be filled in with +a digit from 1 to the size of the grid, in such a way that + +\b every row contains only one occurrence of each digit + +\b every column contains only one occurrence of each digit + +\b every block contains only one occurrence of each digit. + +\b (optionally, by default off) each of the square's two main +diagonals contains only one occurrence of each digit. + +You are given some of the numbers as clues; your aim is to place the +rest of the numbers correctly. + +Under the default settings, the sub-blocks are square or +rectangular. The default puzzle size is 3\by\.3 (a 9\by\.9 actual +grid, divided into nine 3\by\.3 blocks). You can also select sizes +with rectangular blocks instead of square ones, such as 2\by\.3 (a +6\by\.6 grid divided into six 3\by\.2 blocks). Alternatively, you +can select \q{jigsaw} mode, in which the sub-blocks are arbitrary +shapes which differ between individual puzzles. + +Another available mode is \q{killer}. In this mode, clues are not +given in the form of filled-in squares; instead, the grid is divided +into \q{cages} by coloured lines, and for each cage the game tells +you what the sum of all the digits in that cage should be. Also, no +digit may appear more than once within a cage, even if the cage +crosses the boundaries of existing regions. + +If you select a puzzle size which requires more than 9 digits, the +additional digits will be letters of the alphabet. For example, if +you select 3\by\.4 then the digits which go in your grid will be 1 +to 9, plus \cq{a}, \cq{b} and \cq{c}. This cannot be selected for +killer puzzles. + +I first saw this puzzle in \i{Nikoli} \k{nikoli-solo}, although it's +also been popularised by various newspapers under the name +\q{Sudoku} or \q{Su Doku}. Howard Garns is considered the inventor +of the modern form of the puzzle, and it was first published in +\e{Dell Pencil Puzzles and Word Games}. A more elaborate treatment +of the history of the puzzle can be found on Wikipedia +\k{wikipedia-solo}. + +\B{nikoli-solo} \W{http://www.nikoli.co.jp/en/puzzles/sudoku.html}\cw{http://www.nikoli.co.jp/en/puzzles/sudoku.html} +(beware of Flash) + +\B{wikipedia-solo} \W{http://en.wikipedia.org/wiki/Sudoku}\cw{http://en.wikipedia.org/wiki/Sudoku} + +\H{solo-controls} \I{controls, for Solo}Solo controls + +To play Solo, simply click the mouse in any empty square and then +type a digit or letter on the keyboard to fill that square. If you +make a mistake, click the mouse in the incorrect square and press +Space to clear it again (or use the Undo feature). + +If you \e{right}-click in a square and then type a number, that +number will be entered in the square as a \q{pencil mark}. You can +have pencil marks for multiple numbers in the same square. Squares +containing filled-in numbers cannot also contain pencil marks. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a +particular square needs to be re-examined once you know more about a +particular number, or you can use them as lists of the possible +numbers in a given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same number again. + +All pencil marks in a square are erased when you left-click and type +a number, or when you left-click and press space. Right-clicking and +pressing space will also erase pencil marks. + +Alternatively, use the cursor keys to move the mark around the grid. +Pressing the return key toggles the mark (from a normal mark to a +pencil mark), and typing a number in is entered in the square in the +appropriate way; typing in a 0 or using the space bar will clear a +filled square. + +(All the actions described in \k{common-actions} are also available.) + +\H{solo-parameters} \I{parameters, for Solo}Solo parameters + +Solo allows you to configure two separate dimensions of the puzzle +grid on the \q{Type} menu: the number of columns, and the number of +rows, into which the main grid is divided. (The size of a block is +the inverse of this: for example, if you select 2 columns and 3 rows, +each actual block will have 3 columns and 2 rows.) + +If you tick the \q{X} checkbox, Solo will apply the optional extra +constraint that the two main diagonals of the grid also contain one +of every digit. (This is sometimes known as \q{Sudoku-X} in +newspapers.) In this mode, the squares on the two main diagonals +will be shaded slightly so that you know it's enabled. + +If you tick the \q{Jigsaw} checkbox, Solo will generate randomly +shaped sub-blocks. In this mode, the actual grid size will be taken +to be the product of the numbers entered in the \q{Columns} and +\q{Rows} boxes. There is no reason why you have to enter a number +greater than 1 in both boxes; Jigsaw mode has no constraint on the +grid size, and it can even be a prime number if you feel like it. + +If you tick the \q{Killer} checkbox, Solo will generate a set of +of cages, which are randomly shaped and drawn in an outline of a +different colour. Each of these regions contains a smaller clue +which shows the digit sum of all the squares in this region. + +You can also configure the type of symmetry shown in the generated +puzzles. More symmetry makes the puzzles look prettier but may also +make them easier, since the symmetry constraints can force more +clues than necessary to be present. Completely asymmetric puzzles +have the freedom to contain as few clues as possible. + +Finally, you can configure the difficulty of the generated puzzles. +Difficulty levels are judged by the complexity of the techniques of +deduction required to solve the puzzle: each level requires a mode +of reasoning which was not necessary in the previous one. In +particular, on difficulty levels \q{Trivial} and \q{Basic} there +will be a square you can fill in with a single number at all times, +whereas at \q{Intermediate} level and beyond you will have to make +partial deductions about the \e{set} of squares a number could be in +(or the set of numbers that could be in a square). +\#{Advanced, Extreme?} +At \q{Unreasonable} level, even this is not enough, and you will +eventually have to make a guess, and then backtrack if it turns out +to be wrong. + +Generating difficult puzzles is itself difficult: if you select one +of the higher difficulty levels, Solo may have to make many attempts +at generating a puzzle before it finds one hard enough for you. Be +prepared to wait, especially if you have also configured a large +puzzle size. + + +\C{mines} \i{Mines} + +\cfg{winhelp-topic}{games.mines} + +You have a grid of covered squares, some of which contain mines, but +you don't know which. Your job is to uncover every square which does +\e{not} contain a mine. If you uncover a square containing a mine, +you lose. If you uncover a square which does not contain a mine, you +are told how many mines are contained within the eight surrounding +squares. + +This game needs no introduction; popularised by Windows, it is +perhaps the single best known desktop puzzle game in existence. + +This version of it has an unusual property. By default, it will +generate its mine positions in such a way as to ensure that you +never need to \e{guess} where a mine is: you will always be able to +deduce it somehow. So you will never, as can happen in other +versions, get to the last four squares and discover that there are +two mines left but you have no way of knowing for sure where they +are. + +\H{mines-controls} \I{controls, for Mines}Mines controls + +This game is played with the mouse. + +If you left-click in a covered square, it will be uncovered. + +If you right-click in a covered square, it will place a flag which +indicates that the square is believed to be a mine. Left-clicking in +a marked square will not uncover it, for safety. You can right-click +again to remove a mark placed in error. + +If you left-click in an \e{uncovered} square, it will \q{clear +around} the square. This means: if the square has exactly as many +flags surrounding it as it should have mines, then all the covered +squares next to it which are \e{not} flagged will be uncovered. So +once you think you know the location of all the mines around a +square, you can use this function as a shortcut to avoid having to +click on each of the remaining squares one by one. + +If you uncover a square which has \e{no} mines in the surrounding +eight squares, then it is obviously safe to uncover those squares in +turn, and so on if any of them also has no surrounding mines. This +will be done for you automatically; so sometimes when you uncover a +square, a whole new area will open up to be explored. + +You can also use the cursor keys to move around the minefield. +Pressing the return key in a covered square uncovers it, and in an +uncovered square will clear around it (so it acts as the left button), +pressing the space bar in a covered square will place a flag +(similarly, it acts as the right button). + +All the actions described in \k{common-actions} are also available. + +Even Undo is available, although you might consider it cheating to +use it. If you step on a mine, the program will only reveal the mine +in question (unlike most other implementations, which reveal all of +them). You can then Undo your fatal move and continue playing if you +like. The program will track the number of times you died (and Undo +will not reduce that counter), so when you get to the end of the +game you know whether or not you did it without making any errors. + +(If you really want to know the full layout of the grid, which other +implementations will show you after you die, you can always use the +Solve menu option.) + +\H{mines-parameters} \I{parameters, for Mines}Mines parameters + +The options available from the \q{Custom...} option on the \q{Type} +menu are: + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Mines} + +\dd Number of mines in the grid. You can enter this as an absolute +mine count, or alternatively you can put a \cw{%} sign on the end in +which case the game will arrange for that proportion of the squares +in the grid to be mines. + +\lcont{ + +Beware of setting the mine count too high. At very high densities, +the program may spend forever searching for a solvable grid. + +} + +\dt \e{Ensure solubility} + +\dd When this option is enabled (as it is by default), Mines will +ensure that the entire grid can be fully deduced starting from the +initial open space. If you prefer the riskier grids generated by +other implementations, you can switch off this option. + + +\C{samegame} \i{Same Game} + +\cfg{winhelp-topic}{games.samegame} + +You have a grid of coloured squares, which you have to clear by +highlighting contiguous regions of more than one coloured square; +the larger the region you highlight, the more points you get (and +the faster you clear the arena). + +If you clear the grid you win. If you end up with nothing but +single squares (i.e., there are no more clickable regions left) you +lose. + +Removing a region causes the rest of the grid to shuffle up: +blocks that are suspended will fall down (first), and then empty +columns are filled from the right. + +Same Game was contributed to this collection by James Harvey. + +\H{samegame-controls} \i{Same Game controls} + +\IM{Same Game controls} controls, for Same Game +\IM{Same Game controls} keys, for Same Game +\IM{Same Game controls} shortcuts (keyboard), for Same Game + +This game can be played with either the keyboard or the mouse. + +If you left-click an unselected region, it becomes selected (possibly +clearing the current selection). + +If you left-click the selected region, it will be removed (and the +rest of the grid shuffled immediately). + +If you right-click the selected region, it will be unselected. + +The cursor keys move a cursor around the grid. Pressing the Space or +Enter keys while the cursor is in an unselected region selects it; +pressing Space or Enter again removes it as above. + +(All the actions described in \k{common-actions} are also available.) + +\H{samegame-parameters} \I{parameters, for Same Game}Same Game parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{No. of colours} + +\dd Number of different colours used to fill the grid; the more colours, +the fewer large regions of colour and thus the more difficult it is to +successfully clear the grid. + +\dt \e{Scoring system} + +\dd Controls the precise mechanism used for scoring. With the default +system, \q{(n-2)^2}, only regions of three squares or more will score +any points at all. With the alternative \q{(n-1)^2} system, regions of +two squares score a point each, and larger regions score relatively +more points. + +\dt \e{Ensure solubility} + +\dd If this option is ticked (the default state), generated grids +will be guaranteed to have at least one solution. + +\lcont{ + +If you turn it off, the game generator will not try to guarantee +soluble grids; it will, however, still ensure that there are at +least 2 squares of each colour on the grid at the start (since a +grid with exactly one square of a given colour is \e{definitely} +insoluble). Grids generated with this option disabled may contain +more large areas of contiguous colour, leading to opportunities for +higher scores; they can also take less time to generate. + +} + + +\C{flip} \i{Flip} + +\cfg{winhelp-topic}{games.flip} + +You have a grid of squares, some light and some dark. Your aim is to +light all the squares up at the same time. You can choose any square +and flip its state from light to dark or dark to light, but when you +do so, other squares around it change state as well. + +Each square contains a small diagram showing which other squares +change when you flip it. + +\H{flip-controls} \i{Flip controls} + +\IM{Flip controls} controls, for Flip +\IM{Flip controls} keys, for Flip +\IM{Flip controls} shortcuts (keyboard), for Flip + +This game can be played with either the keyboard or the mouse. + +Left-click in a square to flip it and its associated squares, or +use the cursor keys to choose a square and the space bar or Enter +key to flip. + +If you use the \q{Solve} function on this game, it will mark some of +the squares in red. If you click once in every square with a red +mark, the game should be solved. (If you click in a square +\e{without} a red mark, a red mark will appear in it to indicate +that you will need to reverse that operation to reach the solution.) + +(All the actions described in \k{common-actions} are also available.) + +\H{flip-parameters} \I{parameters, for flip}Flip parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Shape type} + +\dd This control determines the shape of the region which is flipped +by clicking in any given square. The default setting, \q{Crosses}, +causes every square to flip itself and its four immediate neighbours +(or three or two if it's at an edge or corner). The other setting, +\q{Random}, causes a random shape to be chosen for every square, so +the game is different every time. + + +\C{guess} \i{Guess} + +\cfg{winhelp-topic}{games.guess} + +You have a set of coloured pegs, and have to reproduce a +predetermined sequence of them (chosen by the computer) within a +certain number of guesses. + +Each guess gets marked with the number of correctly-coloured pegs +in the correct places (in black), and also the number of +correctly-coloured pegs in the wrong places (in white). + +This game is also known (and marketed, by Hasbro, mainly) as +a board game \q{\i{Mastermind}}, with 6 colours, 4 pegs per row, +and 10 guesses. However, this version allows custom settings of number +of colours (up to 10), number of pegs per row, and number of guesses. + +Guess was contributed to this collection by James Harvey. + +\H{guess-controls} \i{Guess controls} + +\IM{Guess controls} controls, for Guess +\IM{Guess controls} keys, for Guess +\IM{Guess controls} shortcuts (keyboard), for Guess + +This game can be played with either the keyboard or the mouse. + +With the mouse, drag a coloured peg from the tray on the left-hand +side to its required position in the current guess; pegs may also be +dragged from current and past guesses to copy them elsewhere. To +remove a peg, drag it off its current position to somewhere invalid. + +Right-clicking in the current guess adds a \q{hold} marker; pegs +that have hold markers will be automatically added to the next guess +after marking. + +Alternatively, with the keyboard, the up and down cursor keys can be +used to select a peg colour, the left and right keys to select a +peg position, and the space bar or Enter key to place a peg of the +selected colour in the chosen position. \q{D} or Backspace removes a +peg, and Space adds a hold marker. + +Pressing \q{h} or \q{?} will fill the current guess with a suggested +guess. Using this is not recommended for 10 or more pegs as it is +slow. + +When the guess is complete, the smaller feedback pegs will be highlighted; +clicking on these (or moving the peg cursor to them with the arrow keys +and pressing the space bar or Enter key) will mark the current guess, +copy any held pegs to the next guess, and move the \q{current guess} +marker. + +If you correctly position all the pegs the solution will be displayed +below; if you run out of guesses (or select \q{Solve...}) the solution +will also be revealed. + +(All the actions described in \k{common-actions} are also available.) + +\H{guess-parameters} \I{parameters, for Guess}Guess parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. The default game matches the parameters for the +board game \q{Mastermind}. + +\dt \e{Colours} + +\dd Number of colours the solution is chosen from; from 2 to 10 +(more is harder). + +\dt \e{Pegs per guess} + +\dd Number of pegs per guess (more is harder). + +\dt \e{Guesses} + +\dd Number of guesses you have to find the solution in (fewer is harder). + +\dt \e{Allow blanks} + +\dd Allows blank pegs to be given as part of a guess (makes it easier, because +you know that those will never be counted as part of the solution). This +is turned off by default. + +\lcont{ + +Note that this doesn't allow blank pegs in the solution; if you really wanted +that, use one extra colour. + +} + +\dt \e{Allow duplicates} + +\dd Allows the solution (and the guesses) to contain colours more than once; +this increases the search space (making things harder), and is turned on by +default. + + +\C{pegs} \i{Pegs} + +\cfg{winhelp-topic}{games.pegs} + +A number of pegs are placed in holes on a board. You can remove a +peg by jumping an adjacent peg over it (horizontally or vertically) +to a vacant hole on the other side. Your aim is to remove all but one +of the pegs initially present. + +This game, best known as \I{Solitaire, Peg}\q{Peg Solitaire}, is +possibly one of the oldest puzzle games still commonly known. + +\H{pegs-controls} \i{Pegs controls} + +\IM{Pegs controls} controls, for Pegs + +To move a peg, drag it with the mouse from its current position to +its final position. If the final position is exactly two holes away +from the initial position, is currently unoccupied by a peg, and +there is a peg in the intervening square, the move will be permitted +and the intervening peg will be removed. + +Vacant spaces which you can move a peg into are marked with holes. A +space with no peg and no hole is not available for moving at all: it +is an obstacle which you must work around. + +You can also use the cursor keys to move a position indicator around +the board. Pressing the return key while over a peg, followed by a +cursor key, will jump the peg in that direction (if that is a legal +move). + +(All the actions described in \k{common-actions} are also available.) + +\H{pegs-parameters} \I{parameters, for Pegs}Pegs parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in holes. + +\dt \e{Board type} + +\dd Controls whether you are given a board of a standard shape or a +randomly generated shape. The two standard shapes currently +supported are \q{Cross} and \q{Octagon} (also commonly known as the +English and European traditional board layouts respectively). +Selecting \q{Random} will give you a different board shape every +time (but always one that is known to have a solution). + + +\C{dominosa} \i{Dominosa} + +\cfg{winhelp-topic}{games.dominosa} + +A normal set of dominoes \dash that is, one instance of every +(unordered) pair of numbers from 0 to 6 \dash has been arranged +irregularly into a rectangle; then the number in each square has +been written down and the dominoes themselves removed. Your task is +to reconstruct the pattern by arranging the set of dominoes to match +the provided array of numbers. + +This puzzle is widely credited to O. S. Adler, and takes part of its +name from those initials. + +\H{dominosa-controls} \i{Dominosa controls} + +\IM{Dominosa controls} controls, for Dominosa + +Left-clicking between any two adjacent numbers places a domino +covering them, or removes one if it is already present. Trying to +place a domino which overlaps existing dominoes will remove the ones +it overlaps. + +Right-clicking between two adjacent numbers draws a line between +them, which you can use to remind yourself that you know those two +numbers are \e{not} covered by a single domino. Right-clicking again +removes the line. + +You can also use the cursor keys to move a cursor around the grid. +When the cursor is half way between two adjacent numbers, pressing +the return key will place a domino covering those numbers, or +pressing the space bar will lay a line between the two squares. +Repeating either action removes the domino or line. + +Pressing a number key will highlight all occurrences of that +number. Pressing that number again will clear the highlighting. Up to two +different numbers can be highlighted at any given time. + +(All the actions described in \k{common-actions} are also available.) + +\H{dominosa-parameters} \I{parameters, for Dominosa}Dominosa parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Maximum number on dominoes} + +\dd Controls the size of the puzzle, by controlling the size of the +set of dominoes used to make it. Dominoes with numbers going up to N +will give rise to an (N+2) \by (N+1) rectangle; so, in particular, +the default value of 6 gives an 8\by\.7 grid. + +\dt \e{Ensure unique solution} + +\dd Normally, Dominosa will make sure that the puzzles it presents +have only one solution. Puzzles with ambiguous sections can be more +difficult and sometimes more subtle, so if you like you can turn off +this feature. Also, finding \e{all} the possible solutions can be an +additional challenge for an advanced player. Turning off this option +can also speed up puzzle generation. + + +\C{untangle} \i{Untangle} + +\cfg{winhelp-topic}{games.untangle} + +You are given a number of points, some of which have lines drawn +between them. You can move the points about arbitrarily; your aim is +to position the points so that no line crosses another. + +I originally saw this in the form of a Flash game called \i{Planarity} +\k{Planarity}, written by John Tantalo. + +\B{Planarity} \W{http://planarity.net}\cw{http://planarity.net} + +\H{untangle-controls} \i{Untangle controls} + +\IM{Untangle controls} controls, for Untangle + +To move a point, click on it with the left mouse button and drag it +into a new position. + +(All the actions described in \k{common-actions} are also available.) + +\H{untangle-parameters} \I{parameters, for Untangle}Untangle parameters + +There is only one parameter available from the \q{Custom...} option +on the \q{Type} menu: + +\dt \e{Number of points} + +\dd Controls the size of the puzzle, by specifying the number of +points in the generated graph. + + +\C{blackbox} \i{Black Box} + +\cfg{winhelp-topic}{games.blackbox} + +A number of balls are hidden in a rectangular arena. You have to +deduce the positions of the balls by firing lasers positioned at +the edges of the arena and observing how their beams are deflected. + +Beams will travel straight from their origin until they hit the +opposite side of the arena (at which point they emerge), unless +affected by balls in one of the following ways: + +\b A beam that hits a ball head-on is absorbed and will never + re-emerge. This includes beams that meet a ball on the first rank + of the arena. + +\b A beam with a ball in its front-left square and no ball ahead of it + gets deflected 90 degrees to the right. + +\b A beam with a ball in its front-right square and no ball ahead of + it gets similarly deflected to the left. + +\b A beam that would re-emerge from its entry location is considered to be + \q{reflected}. + +\b A beam which would get deflected before entering the arena by a + ball to the front-left or front-right of its entry point is also + considered to be \q{reflected}. + +Beams that are reflected appear as a \q{R}; beams that hit balls +head-on appear as \q{H}. Otherwise, a number appears at the firing +point and the location where the beam emerges (this number is unique +to that shot). + +You can place guesses as to the location of the balls, based on the +entry and exit patterns of the beams; once you have placed enough +balls a button appears enabling you to have your guesses checked. + +Here is a diagram showing how the positions of balls can create each +of the beam behaviours shown above: + +\c 1RHR---- +\c |..O.O...| +\c 2........3 +\c |........| +\c |........| +\c 3........| +\c |......O.| +\c H........| +\c |.....O..| +\c 12-RR--- + +As shown, it is possible for a beam to receive multiple reflections +before re-emerging (see turn 3). Similarly, a beam may be reflected +(possibly more than once) before receiving a hit (the \q{H} on the +left side of the example). + +Note that any layout with more than 4 balls may have a non-unique +solution. The following diagram illustrates this; if you know the +board contains 5 balls, it is impossible to determine where the fifth +ball is (possible positions marked with an \cw{x}): + +\c -------- +\c |........| +\c |........| +\c |..O..O..| +\c |...xx...| +\c |...xx...| +\c |..O..O..| +\c |........| +\c |........| +\c -------- + +For this reason, when you have your guesses checked, the game will +check that your solution \e{produces the same results} as the +computer's, rather than that your solution is identical to the +computer's. So in the above example, you could put the fifth ball at +\e{any} of the locations marked with an \cw{x}, and you would still +win. + +Black Box was contributed to this collection by James Harvey. + +\H{blackbox-controls} \i{Black Box controls} + +\IM{Black Box controls} controls, for Black Box +\IM{Black Box controls} keys, for Black Box +\IM{Black Box controls} shortcuts (keyboard), for Black Box + +To fire a laser beam, left-click in a square around the edge of the +arena. The results will be displayed immediately. Clicking or holding +the left button on one of these squares will highlight the current go +(or a previous go) to confirm the exit point for that laser, if +applicable. + +To guess the location of a ball, left-click within the arena and a +black circle will appear marking the guess; click again to remove the +guessed ball. + +Locations in the arena may be locked against modification by +right-clicking; whole rows and columns may be similarly locked by +right-clicking in the laser square above/below that column, or to the +left/right of that row. + +The cursor keys may also be used to move around the grid. Pressing the +Enter key will fire a laser or add a new ball-location guess, and +pressing Space will lock a cell, row, or column. + +When an appropriate number of balls have been guessed, a button will +appear at the top-left corner of the grid; clicking that (with mouse +or cursor) will check your guesses. + +If you click the \q{check} button and your guesses are not correct, +the game will show you the minimum information necessary to +demonstrate this to you, so you can try again. If your ball +positions are not consistent with the beam paths you already know +about, one beam path will be circled to indicate that it proves you +wrong. If your positions match all the existing beam paths but are +still wrong, one new beam path will be revealed (written in red) +which is not consistent with your current guesses. + +If you decide to give up completely, you can select Solve to reveal +the actual ball positions. At this point, correctly-placed balls +will be displayed as filled black circles, incorrectly-placed balls +as filled black circles with red crosses, and missing balls as filled +red circles. In addition, a red circle marks any laser you had already +fired which is not consistent with your ball layout (just as when you +press the \q{check} button), and red text marks any laser you +\e{could} have fired in order to distinguish your ball layout from the +correct one. + +(All the actions described in \k{common-actions} are also available.) + +\H{blackbox-parameters} \I{parameters, for Black Box}Black Box parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. There are 2 \by \e{Width} \by \e{Height} lasers +per grid, two per row and two per column. + +\dt \e{No. of balls} + +\dd Number of balls to place in the grid. This can be a single number, +or a range (separated with a hyphen, like \q{2-6}), and determines the +number of balls to place on the grid. The \q{reveal} button is only +enabled if you have guessed an appropriate number of balls; a guess +using a different number to the original solution is still acceptable, +if all the beam inputs and outputs match. + + +\C{slant} \i{Slant} + +\cfg{winhelp-topic}{games.slant} + +You have a grid of squares. Your aim is to draw a diagonal line +through each square, and choose which way each line slants so that +the following conditions are met: + +\b The diagonal lines never form a loop. + +\b Any point with a circled number has precisely that many lines +meeting at it. (Thus, a 4 is the centre of a cross shape, whereas a +zero is the centre of a diamond shape \dash or rather, a partial +diamond shape, because a zero can never appear in the middle of the +grid because that would immediately cause a loop.) + +Credit for this puzzle goes to \i{Nikoli} \k{nikoli-slant}. + +\B{nikoli-slant} +\W{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname}\cw{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname} +(in Japanese) + +\H{slant-controls} \i{Slant controls} + +\IM{Slant controls} controls, for Slant + +Left-clicking in a blank square will place a \cw{\\} in it (a line +leaning to the left, i.e. running from the top left of the square to +the bottom right). Right-clicking in a blank square will place a +\cw{/} in it (leaning to the right, running from top right to bottom +left). + +Continuing to click either button will cycle between the three +possible square contents. Thus, if you left-click repeatedly in a +blank square it will change from blank to \cw{\\} to \cw{/} back to +blank, and if you right-click repeatedly the square will change from +blank to \cw{/} to \cw{\\} back to blank. (Therefore, you can play +the game entirely with one button if you need to.) + +You can also use the cursor keys to move around the grid. Pressing the +return or space keys will place a \cw{\\} or a \cw{/}, respectively, +and will then cycle them as above. You can also press \cw{/} or +\cw{\\} to place a \cw{/} or \cw{\\}, respectively, independent of +what is already in the cursor square. Backspace removes any line from +the cursor square. + +(All the actions described in \k{common-actions} are also available.) + +\H{slant-parameters} \I{parameters, for Slant}Slant parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. At Hard level, +you are required to do deductions based on knowledge of +\e{relationships} between squares rather than always being able to +deduce the exact contents of one square at a time. (For example, you +might know that two squares slant in the same direction, even if you +don't yet know what that direction is, and this might enable you to +deduce something about still other squares.) Even at Hard level, +guesswork and backtracking should never be necessary. + + +\C{lightup} \i{Light Up} + +\cfg{winhelp-topic}{games.lightup} + +You have a grid of squares. Some are filled in black; some of the +black squares are numbered. Your aim is to \q{light up} all the +empty squares by placing light bulbs in some of them. + +Each light bulb illuminates the square it is on, plus all squares in +line with it horizontally or vertically unless a black square is +blocking the way. + +To win the game, you must satisfy the following conditions: + +\b All non-black squares are lit. + +\b No light is lit by another light. + +\b All numbered black squares have exactly that number of lights adjacent to + them (in the four squares above, below, and to the side). + +Non-numbered black squares may have any number of lights adjacent to them. + +Credit for this puzzle goes to \i{Nikoli} \k{nikoli-lightup}. + +Light Up was contributed to this collection by James Harvey. + +\B{nikoli-lightup} +\W{http://www.nikoli.co.jp/en/puzzles/akari.html}\cw{http://www.nikoli.co.jp/en/puzzles/akari.html} +(beware of Flash) + +\H{lightup-controls} \i{Light Up controls} + +\IM{Light Up controls} controls, for Light Up + +Left-clicking in a non-black square will toggle the presence of a light +in that square. Right-clicking in a non-black square toggles a mark there to aid +solving; it can be used to highlight squares that cannot be lit, for example. + +You may not place a light in a marked square, nor place a mark in a lit square. + +The game will highlight obvious errors in red. Lights lit by other +lights are highlighted in this way, as are numbered squares which +do not (or cannot) have the right number of lights next to them. + +Thus, the grid is solved when all non-black squares have yellow +highlights and there are no red lights. + +(All the actions described in \k{common-actions} are also available.) + +\H{lightup-parameters} \I{parameters, for Light Up}Light Up parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{%age of black squares} + +\dd Rough percentage of black squares in the grid. + +\lcont{ + +This is a hint rather than an instruction. If the grid generator is +unable to generate a puzzle to this precise specification, it will +increase the proportion of black squares until it can. + +} + +\dt \e{Symmetry} + +\dd Allows you to specify the required symmetry of the black squares +in the grid. (This does not affect the difficulty of the puzzles +noticeably.) + +\dt \e{Difficulty} + +\dd \q{Easy} means that the puzzles should be soluble without +backtracking or guessing, \q{Hard} means that some guesses will +probably be necessary. + + +\C{map} \i{Map} + +\cfg{winhelp-topic}{games.map} + +You are given a map consisting of a number of regions. Your task is +to colour each region with one of four colours, in such a way that +no two regions sharing a boundary have the same colour. You are +provided with some regions already coloured, sufficient to make the +remainder of the solution unique. + +Only regions which share a length of border are required to be +different colours. Two regions which meet at only one \e{point} +(i.e. are diagonally separated) may be the same colour. + +I believe this puzzle is original; I've never seen an implementation +of it anywhere else. The concept of a \i{four-colouring} puzzle was +suggested by Owen Dunn; credit must also go to Nikoli and to Verity +Allan for inspiring the train of thought that led to me realising +Owen's suggestion was a viable puzzle. Thanks also to Gareth Taylor +for many detailed suggestions. + +\H{map-controls} \i{Map controls} + +\IM{Map controls} controls, for Map + +To colour a region, click the left mouse button on an existing +region of the desired colour and drag that colour into the new +region. + +(The program will always ensure the starting puzzle has at least one +region of each colour, so that this is always possible!) + +If you need to clear a region, you can drag from an empty region, or +from the puzzle boundary if there are no empty regions left. + +Dragging a colour using the \e{right} mouse button will stipple the +region in that colour, which you can use as a note to yourself that +you think the region \e{might} be that colour. A region can contain +stipples in multiple colours at once. (This is often useful at the +harder difficulty levels.) + +You can also use the cursor keys to move around the map: the colour of +the cursor indicates the position of the colour you would drag (which +is not obvious if you're on a region's boundary, since it depends on the +direction from which you approached the boundary). Pressing the return +key starts a drag of that colour, as above, which you control with the +cursor keys; pressing the return key again finishes the drag. The +space bar can be used similarly to create a stippled region. +Double-pressing the return key (without moving the cursor) will clear +the region, as a drag from an empty region does: this is useful with +the cursor mode if you have filled the entire map in but need to +correct the layout. + +If you press L during play, the game will toggle display of a number +in each region of the map. This is useful if you want to discuss a +particular puzzle instance with a friend \dash having an unambiguous +name for each region is much easier than trying to refer to them all +by names such as \q{the one down and right of the brown one on the +top border}. + +(All the actions described in \k{common-actions} are also available.) + +\H{map-parameters} \I{parameters, for Map}Map parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Regions} + +\dd Number of regions in the generated map. + +\dt \e{Difficulty} + +\dd In \q{Easy} mode, there should always be at least one region +whose colour can be determined trivially. In \q{Normal} and \q{Hard} +modes, you will have to use increasingly complex logic to deduce the +colour of some regions. However, it will always be possible without +having to guess or backtrack. + +\lcont{ + +In \q{Unreasonable} mode, the program will feel free to generate +puzzles which are as hard as it can possibly make them: the only +constraint is that they should still have a unique solution. Solving +Unreasonable puzzles may require guessing and backtracking. + +} + + +\C{loopy} \i{Loopy} + +\cfg{winhelp-topic}{games.loopy} + +You are given a grid of dots, marked with yellow lines to indicate +which dots you are allowed to connect directly together. Your aim is +to use some subset of those yellow lines to draw a single unbroken +loop from dot to dot within the grid. + +Some of the spaces between the lines contain numbers. These numbers +indicate how many of the lines around that space form part of the +loop. The loop you draw must correctly satisfy all of these clues to +be considered a correct solution. + +In the default mode, the dots are arranged in a grid of squares; +however, you can also play on triangular or hexagonal grids, or even +more exotic ones. + +Credit for the basic puzzle idea goes to \i{Nikoli} +\k{nikoli-loopy}. + +Loopy was originally contributed to this collection by Mike Pinna, +and subsequently enhanced to handle various types of non-square grid +by Lambros Lambrou. + +\B{nikoli-loopy} +\W{http://www.nikoli.co.jp/en/puzzles/slitherlink.html}\cw{http://www.nikoli.co.jp/en/puzzles/slitherlink.html} +(beware of Flash) + +\H{loopy-controls} \i{Loopy controls} + +\IM{Loopy controls} controls, for Loopy + +Click the left mouse button on a yellow line to turn it black, +indicating that you think it is part of the loop. Click again to +turn the line yellow again (meaning you aren't sure yet). + +If you are sure that a particular line segment is \e{not} part of +the loop, you can click the right mouse button to remove it +completely. Again, clicking a second time will turn the line back to +yellow. + +(All the actions described in \k{common-actions} are also available.) + +\H{loopy-parameters} \I{parameters, for Loopy}Loopy parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid, measured in number of regions across and down. For +square grids, it's clear how this is counted; for other types of +grid you may have to think a bit to see how the dimensions are +measured. + +\dt \e{Grid type} + +\dd Allows you to choose between a selection of types of tiling. +Some have all the faces the same but may have multiple different +types of vertex (e.g. the \e{Cairo} or \e{Kites} mode); others have +all the vertices the same but may have different types of face (e.g. +the \e{Great Hexagonal}). The square, triangular and honeycomb grids +are fully regular, and have all their vertices \e{and} faces the +same; this makes them the least confusing to play. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. +\#{FIXME: what distinguishes Easy, Medium, and Hard? In particular, +when are backtracking/guesswork required, if ever?} + + +\C{inertia} \i{Inertia} + +\cfg{winhelp-topic}{games.inertia} + +You are a small green ball sitting in a grid full of obstacles. Your +aim is to collect all the gems without running into any mines. + +You can move the ball in any orthogonal \e{or diagonal} direction. +Once the ball starts moving, it will continue until something stops +it. A wall directly in its path will stop it (but if it is moving +diagonally, it will move through a diagonal gap between two other +walls without stopping). Also, some of the squares are \q{stops}; +when the ball moves on to a stop, it will stop moving no matter what +direction it was going in. Gems do \e{not} stop the ball; it picks +them up and keeps on going. + +Running into a mine is fatal. Even if you picked up the last gem in +the same move which then hit a mine, the game will count you as dead +rather than victorious. + +This game was originally implemented for Windows by Ben Olmstead +\k{bem}, who was kind enough to release his source code on request +so that it could be re-implemented for this collection. + +\B{bem} \W{http://xn13.com/}\cw{http://xn13.com/} + +\H{inertia-controls} \i{Inertia controls} + +\IM{Inertia controls} controls, for Inertia +\IM{Inertia controls} keys, for Inertia +\IM{Inertia controls} shortcuts (keyboard), for Inertia + +You can move the ball in any of the eight directions using the +numeric keypad. Alternatively, if you click the left mouse button on +the grid, the ball will begin a move in the general direction of +where you clicked. + +If you use the \q{Solve} function on this game, the program will +compute a path through the grid which collects all the remaining +gems and returns to the current position. A hint arrow will appear +on the ball indicating the direction in which you should move to +begin on this path. If you then move in that direction, the arrow +will update to indicate the next direction on the path. You can also +press Space to automatically move in the direction of the hint +arrow. If you move in a different direction from the one shown by +the arrow, arrows will be shown only if the puzzle is still solvable. + +All the actions described in \k{common-actions} are also available. +In particular, if you do run into a mine and die, you can use the +Undo function and resume playing from before the fatal move. The +game will keep track of the number of times you have done this. + +\H{inertia-parameters} \I{parameters, for Inertia}Inertia parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + + +\C{tents} \i{Tents} + +\cfg{winhelp-topic}{games.tents} + +You have a grid of squares, some of which contain trees. Your aim is +to place tents in some of the remaining squares, in such a way that +the following conditions are met: + +\b There are exactly as many tents as trees. + +\b The tents and trees can be matched up in such a way that each +tent is directly adjacent (horizontally or vertically, but not +diagonally) to its own tree. However, a tent may be adjacent to +other trees as well as its own. + +\b No two tents are adjacent horizontally, vertically \e{or +diagonally}. + +\b The number of tents in each row, and in each column, matches the +numbers given round the sides of the grid. + +This puzzle can be found in several places on the Internet, and was +brought to my attention by e-mail. I don't know who I should credit +for inventing it. + +\H{tents-controls} \i{Tents controls} + +\IM{Tents controls} controls, for Tents + +Left-clicking in a blank square will place a tent in it. +Right-clicking in a blank square will colour it green, indicating +that you are sure it \e{isn't} a tent. Clicking either button in an +occupied square will clear it. + +If you \e{drag} with the right button along a row or column, every +blank square in the region you cover will be turned green, and no +other squares will be affected. (This is useful for clearing the +remainder of a row once you have placed all its tents.) + +You can also use the cursor keys to move around the grid. Pressing the +return key over an empty square will place a tent, and pressing the +space bar over an empty square will colour it green; either key will +clear an occupied square. Holding Shift and pressing the cursor keys +will colour empty squares green. Holding Control and pressing the +cursor keys will colour green both empty squares and squares with tents. + +(All the actions described in \k{common-actions} are also available.) + +\H{tents-parameters} \I{parameters, for Tents}Tents parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. More difficult +puzzles require more complex deductions, but at present none of the +available difficulty levels requires guesswork or backtracking. + + +\C{bridges} \i{Bridges} + +\cfg{winhelp-topic}{games.bridges} + +You have a set of islands distributed across the playing area. Each +island contains a number. Your aim is to connect the islands +together with bridges, in such a way that: + +\b Bridges run horizontally or vertically. + +\b The number of bridges terminating at any island is equal to the +number written in that island. + +\b Two bridges may run in parallel between the same two islands, but +no more than two may do so. + +\b No bridge crosses another bridge. + +\b All the islands are connected together. + +There are some configurable alternative modes, which involve +changing the parallel-bridge limit to something other than 2, and +introducing the additional constraint that no sequence of bridges +may form a loop from one island back to the same island. The rules +stated above are the default ones. + +Credit for this puzzle goes to \i{Nikoli} \k{nikoli-bridges}. + +Bridges was contributed to this collection by James Harvey. + +\B{nikoli-bridges} +\W{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html}\cw{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html} +(beware of Flash) + +\H{bridges-controls} \i{Bridges controls} + +\IM{Bridges controls} controls, for Bridges + +To place a bridge between two islands, click the mouse down on one +island and drag it towards the other. You do not need to drag all +the way to the other island; you only need to move the mouse far +enough for the intended bridge direction to be unambiguous. (So you +can keep the mouse near the starting island and conveniently throw +bridges out from it in many directions.) + +Doing this again when a bridge is already present will add another +parallel bridge. If there are already as many bridges between the +two islands as permitted by the current game rules (i.e. two by +default), the same dragging action will remove all of them. + +If you want to remind yourself that two islands definitely \e{do +not} have a bridge between them, you can right-drag between them in +the same way to draw a \q{non-bridge} marker. + +If you think you have finished with an island (i.e. you have placed +all its bridges and are confident that they are in the right +places), you can mark the island as finished by left-clicking on it. +This will highlight it and all the bridges connected to it, and you +will be prevented from accidentally modifying any of those bridges +in future. Left-clicking again on a highlighted island will unmark +it and restore your ability to modify it. + +You can also use the cursor keys to move around the grid: if possible +the cursor will always move orthogonally, otherwise it will move +towards the nearest island to the indicated direction. Holding Control +and pressing a cursor key will lay a bridge in that direction (if +available); Shift and a cursor key will lay a \q{non-bridge} marker. +Pressing the return key followed by a cursor key will also lay a +bridge in that direction. + +You can mark an island as finished by pressing the space bar or by +pressing the return key twice. + +By pressing a number key, you can jump to the nearest island with that +number. Letters \q{a}, ..., \q{f} count as 10, ..., 15 and \q{0} as +16. + +Violations of the puzzle rules will be marked in red: + +\b An island with too many bridges will be highlighted in red. + +\b An island with too few bridges will be highlighted in red if it +is definitely an error (as opposed to merely not being finished +yet): if adding enough bridges would involve having to cross another +bridge or remove a non-bridge marker, or if the island has been +highlighted as complete. + +\b A group of islands and bridges may be highlighted in red if it is +a closed subset of the puzzle with no way to connect it to the rest +of the islands. For example, if you directly connect two 1s together +with a bridge and they are not the only two islands on the grid, +they will light up red to indicate that such a group cannot be +contained in any valid solution. + +\b If you have selected the (non-default) option to disallow loops +in the solution, a group of bridges which forms a loop will be +highlighted. + +(All the actions described in \k{common-actions} are also available.) + +\H{bridges-parameters} \I{parameters, for Bridges}Bridges parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Difficulty level of puzzle. + +\dt \e{Allow loops} + +\dd This is set by default. If cleared, puzzles will be generated in +such a way that they are always soluble without creating a loop, and +solutions which do involve a loop will be disallowed. + +\dt \e{Max. bridges per direction} + +\dd Maximum number of bridges in any particular direction. The +default is 2, but you can change it to 1, 3 or 4. In general, fewer +is easier. + +\dt \e{%age of island squares} + +\dd Gives a rough percentage of islands the generator will try and +lay before finishing the puzzle. Certain layouts will not manage to +lay enough islands; this is an upper bound. + +\dt \e{Expansion factor (%age)} + +\dd The grid generator works by picking an existing island at random +(after first creating an initial island somewhere). It then decides +on a direction (at random), and then works out how far it could +extend before creating another island. This parameter determines how +likely it is to extend as far as it can, rather than choosing +somewhere closer. + +\lcont{ + +High expansion factors usually mean easier puzzles with fewer +possible islands; low expansion factors can create lots of +tightly-packed islands. + +} + + +\C{unequal} \i{Unequal} + +\cfg{winhelp-topic}{games.unequal} + +You have a square grid; each square may contain a digit from 1 to +the size of the grid, and some squares have clue signs between +them. Your aim is to fully populate the grid with numbers such that: + +\b Each row contains only one occurrence of each digit + +\b Each column contains only one occurrence of each digit + +\b All the clue signs are satisfied. + +There are two modes for this game, \q{Unequal} and \q{Adjacent}. + +In \q{Unequal} mode, the clue signs are greater-than symbols indicating one +square's value is greater than its neighbour's. In this mode not all clues +may be visible, particularly at higher difficulty levels. + +In \q{Adjacent} mode, the clue signs are bars indicating +one square's value is numerically adjacent (i.e. one higher or one lower) +than its neighbour. In this mode all clues are always visible: absence of +a bar thus means that a square's value is definitely not numerically adjacent +to that neighbour's. + +In \q{Trivial} difficulty level (available via the \q{Custom} game type +selector), there are no greater-than signs in \q{Unequal} mode; the puzzle is +to solve the \i{Latin square} only. + +At the time of writing, the \q{Unequal} mode of this puzzle is appearing in the +Guardian weekly under the name \q{\i{Futoshiki}}. + +Unequal was contributed to this collection by James Harvey. + +\H{unequal-controls} \i{Unequal controls} + +\IM{Unequal controls} controls, for Unequal + +Unequal shares much of its control system with Solo. + +To play Unequal, simply click the mouse in any empty square and then +type a digit or letter on the keyboard to fill that square. If you +make a mistake, click the mouse in the incorrect square and press +Space to clear it again (or use the Undo feature). + +If you \e{right}-click in a square and then type a number, that +number will be entered in the square as a \q{pencil mark}. You can +have pencil marks for multiple numbers in the same square. Squares +containing filled-in numbers cannot also contain pencil marks. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a +particular square needs to be re-examined once you know more about a +particular number, or you can use them as lists of the possible +numbers in a given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same number again. + +All pencil marks in a square are erased when you left-click and type +a number, or when you left-click and press space. Right-clicking and +pressing space will also erase pencil marks. + +As for Solo, the cursor keys can be used in conjunction with the digit +keys to set numbers or pencil marks. You can also use the \q{M} key to +auto-fill every numeric hint, ready for removal as required, or the \q{H} +key to do the same but also to remove all obvious hints. + +Alternatively, use the cursor keys to move the mark around the grid. +Pressing the return key toggles the mark (from a normal mark to a +pencil mark), and typing a number in is entered in the square in the +appropriate way; typing in a 0 or using the space bar will clear a +filled square. + +Left-clicking a clue will mark it as done (grey it out), or unmark it +if it is already marked. Holding Control or Shift and pressing an +arrow key likewise marks any clue adjacent to the cursor in the given +direction. + +(All the actions described in \k{common-actions} are also available.) + +\H{unequal-parameters} \I{parameters, for Unequal}Unequal parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Mode} + +\dd Mode of the puzzle (\q{Unequal} or \q{Adjacent}) + +\dt \e{Size (s*s)} + +\dd Size of grid. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. At Trivial +level, there are no greater-than signs; the puzzle is to solve the +Latin square only. At Recursive level (only available via the +\q{Custom} game type selector) backtracking will be required, but +the solution should still be unique. The levels in between require +increasingly complex reasoning to avoid having to backtrack. + + + +\C{galaxies} \i{Galaxies} + +\cfg{winhelp-topic}{games.galaxies} + +You have a rectangular grid containing a number of dots. Your aim is +to draw edges along the grid lines which divide the rectangle into +regions in such a way that every region is 180\u00b0{-degree} +rotationally symmetric, and contains exactly one dot which is +located at its centre of symmetry. + +This puzzle was invented by \i{Nikoli} \k{nikoli-galaxies}, under +the name \q{Tentai Show}; its name is commonly translated into +English as \q{Spiral Galaxies}. + +Galaxies was contributed to this collection by James Harvey. + +\B{nikoli-galaxies} \W{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html}\cw{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html} + +\H{galaxies-controls} \i{Galaxies controls} + +\IM{Galaxies controls} controls, for Galaxies + +Left-click on any grid line to draw an edge if there isn't one +already, or to remove one if there is. When you create a valid +region (one which is closed, contains exactly one dot, is +180\u00b0{-degree} symmetric about that dot, and contains no +extraneous edges inside it) it will be highlighted automatically; so +your aim is to have the whole grid highlighted in that way. + +During solving, you might know that a particular grid square belongs +to a specific dot, but not be sure of where the edges go and which +other squares are connected to the dot. In order to mark this so you +don't forget, you can right-click on the dot and drag, which will +create an arrow marker pointing at the dot. Drop that in a square of +your choice and it will remind you which dot it's associated with. +You can also right-click on existing arrows to pick them up and move +them, or destroy them by dropping them off the edge of the grid. +(Also, if you're not sure which dot an arrow is pointing at, you can +pick it up and move it around to make it clearer. It will swivel +constantly as you drag it, to stay pointed at its parent dot.) + +You can also use the cursor keys to move around the grid squares and +lines. Pressing the return key when over a grid line will draw or +clear its edge, as above. Pressing the return key when over a dot will +pick up an arrow, to be dropped the next time the return key is +pressed; this can also be used to move existing arrows around, removing +them by dropping them on a dot or another arrow. + +(All the actions described in \k{common-actions} are also available.) + +\H{galaxies-parameters} \I{parameters, for Galaxies}Galaxies parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. More difficult +puzzles require more complex deductions, and the \q{Unreasonable} +difficulty level may require backtracking. + + + +\C{filling} \i{Filling} + +\cfg{winhelp-topic}{games.filling} + +You have a grid of squares, some of which contain digits, and the +rest of which are empty. Your job is to fill in digits in the empty +squares, in such a way that each connected region of squares all +containing the same digit has an area equal to that digit. + +(\q{Connected region}, for the purposes of this game, does not count +diagonally separated squares as adjacent.) + +For example, it follows that no square can contain a zero, and that +two adjacent squares can not both contain a one. No region has an +area greater than 9 (because then its area would not be a single +digit). + +Credit for this puzzle goes to \i{Nikoli} \k{nikoli-fillomino}. + +Filling was contributed to this collection by Jonas K\u00F6{oe}lker. + +\B{nikoli-fillomino} +\W{http://www.nikoli.co.jp/en/puzzles/fillomino.html}\cw{http://www.nikoli.co.jp/en/puzzles/fillomino.html} + +\H{filling-controls} \I{controls, for Filling}Filling controls + +To play Filling, simply click the mouse in any empty square and then +type a digit on the keyboard to fill that square. By dragging the +mouse, you can select multiple squares to fill with a single keypress. +If you make a mistake, click the mouse in the incorrect square and +press 0, Space, Backspace or Enter to clear it again (or use the Undo +feature). + +You can also move around the grid with the cursor keys; typing a digit will +fill the square containing the cursor with that number; typing 0 will clear +it. You can also select multiple squares for numbering or clearing with the +return and arrow keys, before typing a digit to fill or clear the highlighted +squares (as above). The space bar adds and removes single squares to and from +the selection. Backspace and escape remove all squares from the selection. + +(All the actions described in \k{common-actions} are also available.) + +\H{filling-parameters} \I{parameters, for Filling}Filling parameters + +Filling allows you to configure the number of rows and columns of the +grid, through the \q{Type} menu. + + +\C{keen} \i{Keen} + +\cfg{winhelp-topic}{games.keen} + +You have a square grid; each square may contain a digit from 1 to +the size of the grid. The grid is divided into blocks of varying +shape and size, with arithmetic clues written in them. Your aim is +to fully populate the grid with digits such that: + +\b Each row contains only one occurrence of each digit + +\b Each column contains only one occurrence of each digit + +\b The digits in each block can be combined to form the number +stated in the clue, using the arithmetic operation given in the +clue. That is: + +\lcont{ + +\b An addition clue means that the sum of the digits in the block +must be the given number. For example, \q{15+} means the contents of +the block adds up to fifteen. + +\b A multiplication clue (e.g. \q{60\times}), similarly, means that +the product of the digits in the block must be the given number. + +\b A subtraction clue will always be written in a block of size two, +and it means that one of the digits in the block is greater than the +other by the given amount. For example, \q{2\minus} means that one +of the digits in the block is 2 more than the other, or equivalently +that one digit minus the other one is 2. The two digits could be +either way round, though. + +\b A division clue (e.g. \q{3\divide}), similarly, is always in a +block of size two and means that one digit divided by the other is +equal to the given amount. + +Note that a block may contain the same digit more than once +(provided the identical ones are not in the same row and column). +This rule is precisely the opposite of the rule in Solo's \q{Killer} +mode (see \k{solo}). + +} + +This puzzle appears in the Times under the name \q{\i{KenKen}}. + + +\H{keen-controls} \i{Keen controls} + +\IM{Keen controls} controls, for Keen + +Keen shares much of its control system with Solo (and Unequal). + +To play Keen, simply click the mouse in any empty square and then +type a digit on the keyboard to fill that square. If you make a +mistake, click the mouse in the incorrect square and press Space to +clear it again (or use the Undo feature). + +If you \e{right}-click in a square and then type a number, that +number will be entered in the square as a \q{pencil mark}. You can +have pencil marks for multiple numbers in the same square. Squares +containing filled-in numbers cannot also contain pencil marks. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a +particular square needs to be re-examined once you know more about a +particular number, or you can use them as lists of the possible +numbers in a given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same number again. + +All pencil marks in a square are erased when you left-click and type +a number, or when you left-click and press space. Right-clicking and +pressing space will also erase pencil marks. + +As for Solo, the cursor keys can be used in conjunction with the +digit keys to set numbers or pencil marks. Use the cursor keys to +move a highlight around the grid, and type a digit to enter it in +the highlighted square. Pressing return toggles the highlight into a +mode in which you can enter or remove pencil marks. + +Pressing M will fill in a full set of pencil marks in every square +that does not have a main digit in it. + +(All the actions described in \k{common-actions} are also available.) + +\H{keen-parameters} \I{parameters, for Keen}Keen parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Grid size} + +\dd Specifies the size of the grid. Lower limit is 3; upper limit is +9 (because the user interface would become more difficult with +\q{digits} bigger than 9!). + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. At Unreasonable +level, some backtracking will be required, but the solution should +still be unique. The remaining levels require increasingly complex +reasoning to avoid having to backtrack. + +\dt \e{Multiplication only} + +\dd If this is enabled, all boxes will be multiplication boxes. +With this rule, the puzzle is known as \q{Inshi No Heya}. + +\C{towers} \i{Towers} + +\cfg{winhelp-topic}{games.towers} + +You have a square grid. On each square of the grid you can build a +tower, with its height ranging from 1 to the size of the grid. +Around the edge of the grid are some numeric clues. + +Your task is to build a tower on every square, in such a way that: + +\b Each row contains every possible height of tower once + +\b Each column contains every possible height of tower once + +\b Each numeric clue describes the number of towers that can be seen +if you look into the square from that direction, assuming that +shorter towers are hidden behind taller ones. For example, in a +5\by\.5 grid, a clue marked \q{5} indicates that the five tower +heights must appear in increasing order (otherwise you would not be +able to see all five towers), whereas a clue marked \q{1} indicates +that the tallest tower (the one marked 5) must come first. + +In harder or larger puzzles, some towers will be specified for you +as well as the clues round the edge, and some edge clues may be +missing. + +This puzzle appears on the web under various names, particularly +\q{\i{Skyscrapers}}, but I don't know who first invented it. + + +\H{towers-controls} \i{Towers controls} + +\IM{Towers controls} controls, for Towers + +Towers shares much of its control system with Solo, Unequal and Keen. + +To play Towers, simply click the mouse in any empty square and then +type a digit on the keyboard to fill that square with a tower of the +given height. If you make a mistake, click the mouse in the +incorrect square and press Space to clear it again (or use the Undo +feature). + +If you \e{right}-click in a square and then type a number, that +number will be entered in the square as a \q{pencil mark}. You can +have pencil marks for multiple numbers in the same square. A square +containing a tower cannot also contain pencil marks. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a +particular square needs to be re-examined once you know more about a +particular number, or you can use them as lists of the possible +numbers in a given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same number again. + +All pencil marks in a square are erased when you left-click and type +a number, or when you left-click and press space. Right-clicking and +pressing space will also erase pencil marks. + +As for Solo, the cursor keys can be used in conjunction with the +digit keys to set numbers or pencil marks. Use the cursor keys to +move a highlight around the grid, and type a digit to enter it in +the highlighted square. Pressing return toggles the highlight into a +mode in which you can enter or remove pencil marks. + +Pressing M will fill in a full set of pencil marks in every square +that does not have a main digit in it. + +Left-clicking a clue will mark it as done (grey it out), or unmark it +if it is already marked. Holding Control or Shift and pressing an +arrow key likewise marks any clue in the given direction. + +(All the actions described in \k{common-actions} are also available.) + +\H{towers-parameters} \I{parameters, for Towers}Towers parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Grid size} + +\dd Specifies the size of the grid. Lower limit is 3; upper limit is +9 (because the user interface would become more difficult with +\q{digits} bigger than 9!). + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. At Unreasonable +level, some backtracking will be required, but the solution should +still be unique. The remaining levels require increasingly complex +reasoning to avoid having to backtrack. + + +\C{singles} \i{Singles} + +\cfg{winhelp-topic}{games.singles} + +You have a grid of white squares, all of which contain numbers. Your task +is to colour some of the squares black (removing the number) so as to satisfy +all of the following conditions: + +\b No number occurs more than once in any row or column. + +\b No black square is horizontally or vertically adjacent to any other black +square. + +\b The remaining white squares must all form one contiguous region +(connected by edges, not just touching at corners). + +Credit for this puzzle goes to \i{Nikoli} \k{nikoli-hitori} who call it +\i{Hitori}. + +Singles was contributed to this collection by James Harvey. + +\B{nikoli-hitori} +\W{http://www.nikoli.com/en/puzzles/hitori.html}\cw{http://www.nikoli.com/en/puzzles/hitori.html} +(beware of Flash) + +\H{singles-controls} \i{Singles controls} + +\IM{Singles controls} controls, for Singles + +Left-clicking on an empty square will colour it black; left-clicking again +will restore the number. Right-clicking will add a circle (useful for +indicating that a cell is definitely not black). + +You can also use the cursor keys to move around the grid. Pressing the +return or space keys will turn a square black or add a circle respectively, +and pressing the key again will restore the number or remove the circle. + +(All the actions described in \k{common-actions} are also available.) + +\H{singles-parameters} \I{parameters, for Singles}Singles parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. + + +\C{magnets} \i{Magnets} + +\cfg{winhelp-topic}{games.magnets} + +A rectangular grid has been filled with a mixture of magnets (that is, +dominoes with one positive end and one negative end) and blank dominoes +(that is, dominoes with two neutral poles). +These dominoes are initially only seen in silhouette. Around the grid +are placed a number of clues indicating the number of positive and +negative poles contained in certain columns and rows. + +Your aim is to correctly place the magnets and blank dominoes such that +all the clues are satisfied, with the additional constraint that no two +similar magnetic poles may be orthogonally adjacent (since they repel). +Neutral poles do not repel, and can be adjacent to any other pole. + +Credit for this puzzle goes to \i{Janko} \k{janko-magnets}. + +Magnets was contributed to this collection by James Harvey. + +\B{janko-magnets} +\W{http://www.janko.at/Raetsel/Magnete/index.htm}\cw{http://www.janko.at/Raetsel/Magnete/index.htm} + +\H{magnets-controls} \i{Magnets controls} + +\IM{Magnets controls} controls, for Magnets + +Left-clicking on an empty square places a magnet at that position with +the positive pole on the square and the negative pole on the other half +of the magnet; left-clicking again reverses the polarity, and a third +click removes the magnet. + +Right-clicking on an empty square places a blank domino there. +Right-clicking again places two question marks on the domino, signifying +\q{this cannot be blank} (which can be useful to note deductions while +solving), and right-clicking again empties the domino. + +Left-clicking a clue will mark it as done (grey it out), or unmark it if +it is already marked. + +You can also use the cursor keys to move a cursor around the grid. +Pressing the return key will lay a domino with a positive pole at that +position; pressing again reverses the polarity and then removes the +domino, as with left-clicking. Using the space bar allows placement +of blank dominoes and cannot-be-blank hints, as for right-clicking. + +(All the actions described in \k{common-actions} are also available.) + +\H{magnets-parameters} \I{parameters, for Magnets}Magnets parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. There will be half \e{Width} \by \e{Height} +dominoes in the grid: if this number is odd then one square will be blank. + +\lcont{ + +(Grids with at least one odd dimension tend to be easier to solve.) + +} + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. At Tricky level, +you are required to make more deductions about empty dominoes and +row/column counts. + +\dt \e{Strip clues} + +\dd If true, some of the clues around the grid are removed at generation +time, making the puzzle more difficult. + + +\C{signpost} \i{Signpost} + +\cfg{winhelp-topic}{games.signpost} + +You have a grid of squares; each square (except the last one) +contains an arrow, and some squares also contain numbers. Your job +is to connect the squares to form a continuous list of numbers +starting at 1 and linked in the direction of the arrows \dash so the +arrow inside the square with the number 1 will point to the square +containing the number 2, which will point to the square containing +the number 3, etc. Each square can be any distance away from the +previous one, as long as it is somewhere in the direction of the +arrow. + +By convention the first and last numbers are shown; one or more +interim numbers may also appear at the beginning. + +Credit for this puzzle goes to \i{Janko} \k{janko-arrowpath}, who call it +\q{Pfeilpfad} (\q{arrow path}). + +Signpost was contributed to this collection by James Harvey. + +\B{janko-arrowpath} +\W{http://janko.at/Raetsel/Pfeilpfad/index.htm}\cw{http://janko.at/Raetsel/Pfeilpfad/index.htm} + +\H{signpost-controls} \I{controls, for Signpost}Signpost controls + +To play Signpost, you connect squares together by dragging from one +square to another, indicating that they are adjacent in the +sequence. Drag with the left button from a square to its successor, +or with the right button from a square to its predecessor. + +If you connect together two squares in this way and one of them has +a number in it, the appropriate number will appear in the other +square. If you connect two non-numbered squares, they will be +assigned temporary algebraic labels: on the first occasion, they +will be labelled \cq{a} and \cq{a+1}, and then \cq{b} and \cq{b+1}, +and so on. Connecting more squares on to the ends of such a chain +will cause them all to be labelled with the same letter. + +When you left-click or right-click in a square, the legal squares to +connect it to will be shown. + +The arrow in each square starts off black, and goes grey once you +connect the square to its successor. Also, each square which needs a +predecessor has a small dot in the bottom left corner, which +vanishes once you link a square to it. So your aim is always to +connect a square with a black arrow to a square with a dot. + +To remove any links for a particular square (both incoming and +outgoing), left-drag it off the grid. To remove a whole chain, +right-drag any square in the chain off the grid. + +You can also use the cursor keys to move around the grid squares and +lines. Pressing the return key when over a square starts a link +operation, and pressing the return key again over a square will +finish the link, if allowable. Pressing the space bar over a square +will show the other squares pointing to it, and allow you to form a +backward link, and pressing the space bar again cancels this. + +(All the actions described in \k{common-actions} are also available.) + +\H{signpost-parameters} \I{parameters, for Signpost}Signpost parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Force start/end to corners} + +\dd If true, the start and end squares are always placed in opposite corners +(the start at the top left, and the end at the bottom right). If false the start +and end squares are placed randomly (although always both shown). + +\C{range} \i{Range} + +\cfg{winhelp-topic}{games.range} + +You have a grid of squares; some squares contain numbers. Your job is +to colour some of the squares black, such that several criteria are +satisfied: + +\b no square with a number is coloured black. + +\b no two black squares are adjacent (horizontally or vertically). + +\b for any two white squares, there is a path between them using only +white squares. + +\b for each square with a number, that number denotes the total number +of white squares reachable from that square going in a straight line +in any horizontal or vertical direction until hitting a wall or a +black square; the square with the number is included in the total +(once). + +For instance, a square containing the number one must have four black +squares as its neighbours by the last criterion; but then it's +impossible for it to be connected to any outside white square, which +violates the second to last criterion. So no square will contain the +number one. + +Credit for this puzzle goes to \i{Nikoli}, who have variously called +it \q{Kurodoko}, \q{Kuromasu} or \q{Where is Black Cells}. +\k{nikoli-range}. + +Range was contributed to this collection by Jonas K\u00F6{oe}lker. + +\B{nikoli-range} +\W{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html}\cw{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html} + +\H{range-controls} \I{controls, for Range}Range controls + +Click with the left button to paint a square black, or with the right +button to mark a square with a dot to indicate that you are sure it +should \e{not} be painted black. Repeated clicking with either button +will cycle the square through the three possible states (filled, +dotted or empty) in opposite directions. + +You can also use the cursor keys to move around the grid squares. +Pressing Return does the same as clicking with the left button, while +pressing Space does the same as a right button click. Moving with the +cursor keys while holding Shift will place dots in all squares that +are moved through. + + +(All the actions described in \k{common-actions} are also available.) + +\H{range-parameters} \I{parameters, for Range}Range parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\C{pearl} \i{Pearl} + +\cfg{winhelp-topic}{games.pearl} + +You have a grid of squares. Your job is to draw lines between the +centres of horizontally or vertically adjacent squares, so that the +lines form a single closed loop. In the resulting grid, some of the +squares that the loop passes through will contain corners, and some +will be straight horizontal or vertical lines. (And some squares can +be completely empty \dash the loop doesn't have to pass through every +square.) + +Some of the squares contain black and white circles, which are clues +that the loop must satisfy. + +A black circle in a square indicates that that square is a corner, but +neither of the squares adjacent to it in the loop is also a corner. + +A white circle indicates that the square is a straight edge, but \e{at +least one} of the squares adjacent to it in the loop is a corner. + +(In both cases, the clue only constrains the two squares adjacent +\e{in the loop}, that is, the squares that the loop passes into after +leaving the clue square. The squares that are only adjacent \e{in the +grid} are not constrained.) + +Credit for this puzzle goes to \i{Nikoli}, who call it \q{Masyu}. +\k{nikoli-pearl} + +Thanks to James Harvey for assistance with the implementation. + +\B{nikoli-pearl} +\W{http://www.nikoli.co.jp/en/puzzles/masyu.html}\cw{http://www.nikoli.co.jp/en/puzzles/masyu.html} +(beware of Flash) + +\H{pearl-controls} \I{controls, for Pearl}Pearl controls + +Click with the left button on a grid edge to draw a segment of the +loop through that edge, or to remove a segment once it is drawn. + +Drag with the left button through a series of squares to draw more +than one segment of the loop in one go. Alternatively, drag over an +existing part of the loop to undraw it, or to undraw part of it and +then go in a different direction. + +Click with the right button on a grid edge to mark it with a cross, +indicating that you are sure the loop does not go through that edge. +(For instance, if you have decided which of the squares adjacent to a +white clue has to be a corner, but don't yet know which way the corner +turns, you might mark the one way it \e{can't} go with a cross.) + +Alternatively, use the cursor keys to move the cursor. Use the Enter +key to begin and end keyboard \q{drag} operations. Use the Space, +Escape or Backspace keys to cancel the drag. Or, hold Control while +dragging with the cursor keys to toggle segments as you move between +squares. + +Pressing Control-Shift-arrowkey or Shift-arrowkey simulates a left or +right click, respectively, on the edge in the direction of the key. + +(All the actions described in \k{common-actions} are also available.) + +\H{pearl-parameters} \I{parameters, for Pearl}Pearl parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\C{undead} \i{Undead} + +\cfg{winhelp-topic}{games.undead} + +You are given a grid of squares, some of which contain diagonal +mirrors. Every square which is not a mirror must be filled with one of +three types of undead monster: a ghost, a vampire, or a zombie. + +Vampires can be seen directly, but are invisible when reflected in +mirrors. Ghosts are the opposite way round: they can be seen in +mirrors, but are invisible when looked at directly. Zombies are +visible by any means. + +You are also told the total number of each type of monster in the +grid. Also around the edge of the grid are written numbers, which +indicate how many monsters can be seen if you look into the grid along +a row or column starting from that position. (The diagonal mirrors are +reflective on both sides. If your reflected line of sight crosses the +same monster more than once, the number will count it each time it is +visible, not just once.) + +This puzzle type was invented by David Millar, under the name +\q{Haunted Mirror Maze}. See \k{janko-undead} for more details. + +Undead was contributed to this collection by Steffen Bauer. + +\B{janko-undead} +\W{http://www.janko.at/Raetsel/Spukschloss/index.htm}\cw{http://www.janko.at/Raetsel/Spukschloss/index.htm} + +\H{undead-controls} \I{controls, for Undead}Undead controls + +Undead has a similar control system to Solo, Unequal and Keen. + +To play Undead, click the mouse in any empty square and then type a +letter on the keyboard indicating the type of monster: \q{G} for a +ghost, \q{V} for a vampire, or \q{Z} for a zombie. If you make a +mistake, click the mouse in the incorrect square and press Space to +clear it again (or use the Undo feature). + +If you \e{right}-click in a square and then type a letter, the +corresponding monster will be shown in reduced size in that square, as +a \q{pencil mark}. You can have pencil marks for multiple monsters in +the same square. A square containing a full-size monster cannot also +contain pencil marks. + +The game pays no attention to pencil marks, so exactly what you use +them for is up to you: you can use them as reminders that a particular +square needs to be re-examined once you know more about a particular +monster, or you can use them as lists of the possible monster in a +given square, or anything else you feel like. + +To erase a single pencil mark, right-click in the square and type +the same letter again. + +All pencil marks in a square are erased when you left-click and type a +monster letter, or when you left-click and press Space. Right-clicking +and pressing space will also erase pencil marks. + +As for Solo, the cursor keys can be used in conjunction with the letter +keys to place monsters or pencil marks. Use the cursor keys to move a +highlight around the grid, and type a monster letter to enter it in +the highlighted square. Pressing return toggles the highlight into a +mode in which you can enter or remove pencil marks. + +If you prefer plain letters of the alphabet to cute monster pictures, +you can press \q{A} to toggle between showing the monsters as monsters or +showing them as letters. + +Left-clicking a clue will mark it as done (grey it out), or unmark it +if it is already marked. + +(All the actions described in \k{common-actions} are also available.) + +\H{undead-parameters} \I{parameters, for Undead}Undead parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. + +\C{unruly} \i{Unruly} + +\cfg{winhelp-topic}{games.unruly} + +You are given a grid of squares, which you must colour either black or +white. Some squares are provided as clues; the rest are left for you +to fill in. Each row and column must contain the same number of black +and white squares, and no row or column may contain three consecutive +squares of the same colour. + +This puzzle type was invented by Adolfo Zanellati, under the name +\q{Tohu wa Vohu}. See \k{janko-unruly} for more details. + +Unruly was contributed to this collection by Lennard Sprong. + +\B{janko-unruly} +\W{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm}\cw{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm} + +\H{unruly-controls} \I{controls, for Unruly}Unruly controls + +To play Unruly, click the mouse in a square to change its colour. +Left-clicking an empty square will turn it black, and right-clicking +will turn it white. Keep clicking the same button to cycle through the +three possible states for the square. If you middle-click in a square +it will be reset to empty. + +You can also use the cursor keys to move around the grid. Pressing the +return or space keys will turn an empty square black or white +respectively (and then cycle the colours in the same way as the mouse +buttons), and pressing Backspace will reset a square to empty. + +(All the actions described in \k{common-actions} are also available.) + +\H{unruly-parameters} \I{parameters, for Unruly}Unruly parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. (Note that the rules of the game require +both the width and height to be even numbers.) + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle. + +\dt \e{Unique rows and columns} + +\dd If enabled, no two rows are permitted to have exactly the same +pattern, and likewise columns. (A row and a column can match, though.) + +\C{flood} \i{Flood} + +\cfg{winhelp-topic}{games.flood} + +You are given a grid of squares, coloured at random in multiple +colours. In each move, you can flood-fill the top left square in a +colour of your choice (i.e. every square reachable from the starting +square by an orthogonally connected path of squares all the same +colour will be filled in the new colour). As you do this, more and +more of the grid becomes connected to the starting square. + +Your aim is to make the whole grid the same colour, in as few moves as +possible. The game will set a limit on the number of moves, based on +running its own internal solver. You win if you can make the whole +grid the same colour in that many moves or fewer. + +I saw this game (with a fixed grid size, fixed number of colours, and +fixed move limit) at http://floodit.appspot.com (no longer accessible). + +\H{flood-controls} \I{controls, for Flood}Flood controls + +To play Flood, click the mouse in a square. The top left corner and +everything connected to it will be flood-filled with the colour of the +square you clicked. Clicking a square the same colour as the top left +corner has no effect, and therefore does not count as a move. + +You can also use the cursor keys to move a cursor (outline black +square) around the grid. Pressing the return key will fill the top +left corner in the colour of the square under the cursor. + +(All the actions described in \k{common-actions} are also available.) + +\H{flood-parameters} \I{parameters, for Flood}Flood parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of the grid, in squares. + +\dt \e{Colours} + +\dd Number of colours used to fill the grid. Must be at least 3 (with +two colours there would only be one legal move at any stage, hence no +choice to make at all), and at most 10. + +\dt \e{Extra moves permitted} + +\dd Controls the difficulty of the puzzle, by increasing the move +limit. In each new grid, Flood will run an internal solver to generate +its own solution, and then the value in this field will be added to +the length of Flood's solution to generate the game's move limit. So a +value of 0 requires you to be just as efficient as Flood's automated +solver, and a larger value makes it easier. + +\lcont{ + +(Note that Flood's internal solver will not necessarily find the +shortest possible solution, though I believe it's pretty close. For a +real challenge, set this value to 0 and then try to solve a grid in +\e{strictly fewer} moves than the limit you're given!) + +} + +\C{tracks} \i{Tracks} + +\cfg{winhelp-topic}{games.tracks} + +You are given a grid of squares, some of which are filled with train +tracks. You need to complete the track from A to B so that the rows and +columns contain the same number of track segments as are indicated in the +clues to the top and right of the grid. + +There are only straight and 90 degree curved rails, and the track may not +cross itself. + +Tracks was contributed to this collection by James Harvey. + +\H{tracks-controls} \I{controls, for Tracks}Tracks controls + +Left-clicking on an edge between two squares adds a track segment between +the two squares. Right-clicking on an edge adds a cross on the edge, +indicating no track is possible there. + +Left-clicking in a square adds a colour indicator showing that you know the +square must contain a track, even if you don't know which edges it crosses +yet. Right-clicking in a square adds a cross indicating it contains no +track segment. + +Left- or right-dragging between squares allows you to lay a straight line +of is-track or is-not-track indicators, useful for filling in rows or +columns to match the clue. + +(All the actions described in \k{common-actions} are also available.) + +\H{tracks-parameters} \I{parameters, for Tracks}Tracks parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of the grid, in squares. + +\dt \e{Difficulty} + +\dd Controls the difficulty of the generated puzzle: at Tricky level, +you are required to make more deductions regarding disregarding moves +that would lead to impossible crossings later. + +\dt \e{Disallow consecutive 1 clues} + +\dd Controls whether the Tracks game generation permits two adjacent +rows or columns to have a 1 clue, or permits the row or column of the +track's endpoint to have a 1 clue. By default this is not permitted, +to avoid long straight boring segments of track and make the games +more twiddly and interesting. If you want to restore the possibility, +turn this option off. + + +\C{palisade} \i{Palisade} + +\cfg{winhelp-topic}{games.palisade} + +You're given a grid of squares, some of which contain numbers. Your +goal is to subdivide the grid into contiguous regions, all of the same +(given) size, such that each square containing a number is adjacent to +exactly that many edges (including those between the inside and the +outside of the grid). + +Credit for this puzzle goes to \i{Nikoli}, who call it \q{Five Cells}. +\k{nikoli-palisade}. + +Palisade was contributed to this collection by Jonas K\u00F6{oe}lker. + +\B{nikoli-palisade} +\W{http://nikoli.co.jp/en/puzzles/five_cells.html}\cw{http://nikoli.co.jp/en/puzzles/five_cells.html} + +\H{palisade-controls} \I{controls, for Palisade}Palisade controls + +Left-click to place an edge. Right-click to indicate \q{no edge}. +Alternatively, the arrow keys will move a keyboard cursor. Holding +Control while pressing an arrow key will place an edge. Press +Shift-arrowkey to switch off an edge. Repeat an action to perform +its inverse. + +(All the actions described in \k{common-actions} are also available.) + +\H{Palisade-parameters} \I{parameters, for Palisade}Palisade parameters + +These parameters are available from the \q{Custom...} option on the +\q{Type} menu. + +\dt \e{Width}, \e{Height} + +\dd Size of grid in squares. + +\dt \e{Region size} + +\dd The size of the regions into which the grid must be subdivided. + +\A{licence} \I{MIT licence}\ii{Licence} + +This software is \i{copyright} 2004-2014 Simon Tatham. + +Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas +K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, +Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the \q{Software}), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED \q{AS IS}, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +\IM{command-line}{command line} command line + +\IM{default parameters, specifying} default parameters, specifying +\IM{default parameters, specifying} preferences, specifying default + +\IM{Unix} Unix +\IM{Unix} Linux + +\IM{generating game IDs} generating game IDs +\IM{generating game IDs} game ID, generating + +\IM{specific} \q{Specific}, menu option +\IM{custom} \q{Custom}, menu option + +\IM{game ID} game ID +\IM{game ID} ID, game +\IM{ID format} ID format +\IM{ID format} format, ID +\IM{ID format} game ID, format + +\IM{keys} keys +\IM{keys} shortcuts (keyboard) + +\IM{initial state} initial state +\IM{initial state} state, initial + +\IM{MIT licence} MIT licence +\IM{MIT licence} licence, MIT diff --git a/apps/plugins/puzzles/puzzles.h b/apps/plugins/puzzles/puzzles.h new file mode 100644 index 0000000000..c70b33ccbf --- /dev/null +++ b/apps/plugins/puzzles/puzzles.h @@ -0,0 +1,636 @@ +/* + * puzzles.h: header file for my puzzle collection + */ + +#ifndef PUZZLES_PUZZLES_H +#define PUZZLES_PUZZLES_H + +#include "plugin.h" +#include +#include "rbcompat.h" +#include "lib/pluginlib_exit.h" + +#ifdef ROCKBOX +#if LCD_WIDTH < LCD_HEIGHT +#define PORTRAIT_SCREEN +#endif +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define PI 3.141592653589793238462643383279502884197169399 + +#define lenof(array) ( sizeof(array) / sizeof(*(array)) ) + +#undef STR +#define STR_INT(x) #x +#define STR(x) STR_INT(x) + +/* NB not perfect because they evaluate arguments multiple times. */ +#ifndef max +#define max(x,y) ( (x)>(y) ? (x) : (y) ) +#endif /* max */ +#ifndef min +#define min(x,y) ( (x)<(y) ? (x) : (y) ) +#endif /* min */ + +enum { + LEFT_BUTTON = 0x0200, + MIDDLE_BUTTON, + RIGHT_BUTTON, + LEFT_DRAG, + MIDDLE_DRAG, + RIGHT_DRAG, + LEFT_RELEASE, + MIDDLE_RELEASE, + RIGHT_RELEASE, + CURSOR_UP, + CURSOR_DOWN, + CURSOR_LEFT, + CURSOR_RIGHT, + CURSOR_SELECT, + CURSOR_SELECT2, + + /* made smaller because of 'limited range of datatype' errors. */ + MOD_CTRL = 0x1000, + MOD_SHFT = 0x2000, + MOD_NUM_KEYPAD = 0x4000, + MOD_MASK = 0x7000 /* mask for all modifiers */ +}; + +#define IS_MOUSE_DOWN(m) ( (unsigned)((m) - LEFT_BUTTON) <= \ + (unsigned)(RIGHT_BUTTON - LEFT_BUTTON)) +#define IS_MOUSE_DRAG(m) ( (unsigned)((m) - LEFT_DRAG) <= \ + (unsigned)(RIGHT_DRAG - LEFT_DRAG)) +#define IS_MOUSE_RELEASE(m) ( (unsigned)((m) - LEFT_RELEASE) <= \ + (unsigned)(RIGHT_RELEASE - LEFT_RELEASE)) +#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \ + (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT ) +#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2) + +/* + * Flags in the back end's `flags' word. + */ +/* Bit flags indicating mouse button priorities */ +#define BUTTON_BEATS(x,y) ( 1 << (((x)-LEFT_BUTTON)*3+(y)-LEFT_BUTTON) ) +/* Flag indicating that Solve operations should be animated */ +#define SOLVE_ANIMATES ( 1 << 9 ) +/* Pocket PC: Game requires right mouse button emulation */ +#define REQUIRE_RBUTTON ( 1 << 10 ) +/* Pocket PC: Game requires numeric input */ +#define REQUIRE_NUMPAD ( 1 << 11 ) +/* end of `flags' word definitions */ + +#ifdef _WIN32_WCE + /* Pocket PC devices have small, portrait screen that requires more vivid colours */ + #define SMALL_SCREEN + #define PORTRAIT_SCREEN + #define VIVID_COLOURS + #define STYLUS_BASED +#endif + +#define IGNOREARG(x) ( (x) = (x) ) + +typedef struct frontend frontend; +typedef struct config_item config_item; +typedef struct midend midend; +typedef struct random_state random_state; +typedef struct game_params game_params; +typedef struct game_state game_state; +typedef struct game_ui game_ui; +typedef struct game_drawstate game_drawstate; +typedef struct game game; +typedef struct blitter blitter; +typedef struct document document; +typedef struct drawing_api drawing_api; +typedef struct drawing drawing; +typedef struct psdata psdata; + +#define ALIGN_VNORMAL 0x000 +#define ALIGN_VCENTRE 0x100 + +#define ALIGN_HLEFT 0x000 +#define ALIGN_HCENTRE 0x001 +#define ALIGN_HRIGHT 0x002 + +#define FONT_FIXED 0 +#define FONT_VARIABLE 1 + +/* For printing colours */ +#define HATCH_SLASH 1 +#define HATCH_BACKSLASH 2 +#define HATCH_HORIZ 3 +#define HATCH_VERT 4 +#define HATCH_PLUS 5 +#define HATCH_X 6 + +/* + * Structure used to pass configuration data between frontend and + * game + */ +enum { C_STRING, C_CHOICES, C_BOOLEAN, C_END }; +struct config_item { + /* + * `name' is never dynamically allocated. + */ + char *name; + /* + * `type' contains one of the above values. + */ + int type; + /* + * For C_STRING, `sval' is always dynamically allocated and + * non-NULL. For C_BOOLEAN and C_END, `sval' is always NULL. + * For C_CHOICES, `sval' is non-NULL, _not_ dynamically + * allocated, and contains a set of option strings separated by + * a delimiter. The delimeter is also the first character in + * the string, so for example ":Foo:Bar:Baz" gives three + * options `Foo', `Bar' and `Baz'. + */ + char *sval; + /* + * For C_BOOLEAN, this is TRUE or FALSE. For C_CHOICES, it + * indicates the chosen index from the `sval' list. In the + * above example, 0==Foo, 1==Bar and 2==Baz. + */ + int ival; +}; + +/* + * Platform routines + */ + +/* We can't use #ifdef DEBUG, because Cygwin defines it by default. */ +#ifdef DEBUGGING +#define debug(x) (debug_printf x) +void debug_printf(char *fmt, ...); +#else +#define debug(x) +#endif + +void fatal(char *fmt, ...); +void frontend_default_colour(frontend *fe, float *output); +void deactivate_timer(frontend *fe); +void activate_timer(frontend *fe); +void get_random_seed(void **randseed, int *randseedsize); + +/* + * drawing.c + */ +drawing *drawing_new(const drawing_api *api, midend *me, void *handle); +void drawing_free(drawing *dr); +void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text); +void draw_rect(drawing *dr, int x, int y, int w, int h, int colour); +void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour); +void draw_polygon(drawing *dr, int *coords, int npoints, + int fillcolour, int outlinecolour); +void draw_circle(drawing *dr, int cx, int cy, int radius, + int fillcolour, int outlinecolour); +void draw_thick_line(drawing *dr, float thickness, + float x1, float y1, float x2, float y2, int colour); +void clip(drawing *dr, int x, int y, int w, int h); +void unclip(drawing *dr); +void start_draw(drawing *dr); +void draw_update(drawing *dr, int x, int y, int w, int h); +void end_draw(drawing *dr); +char *text_fallback(drawing *dr, const char *const *strings, int nstrings); +void status_bar(drawing *dr, char *text); +blitter *blitter_new(drawing *dr, int w, int h); +void blitter_free(drawing *dr, blitter *bl); +/* save puts the portion of the current display with top-left corner + * (x,y) to the blitter. load puts it back again to the specified + * coords, or else wherever it was saved from + * (if x = y = BLITTER_FROMSAVED). */ +void blitter_save(drawing *dr, blitter *bl, int x, int y); +#define BLITTER_FROMSAVED (-1) +void blitter_load(drawing *dr, blitter *bl, int x, int y); +void print_begin_doc(drawing *dr, int pages); +void print_begin_page(drawing *dr, int number); +void print_begin_puzzle(drawing *dr, float xm, float xc, + float ym, float yc, int pw, int ph, float wmm, + float scale); +void print_end_puzzle(drawing *dr); +void print_end_page(drawing *dr, int number); +void print_end_doc(drawing *dr); +void print_get_colour(drawing *dr, int colour, int printing_in_colour, + int *hatch, float *r, float *g, float *b); +int print_mono_colour(drawing *dr, int grey); /* 0==black, 1==white */ +int print_grey_colour(drawing *dr, float grey); +int print_hatched_colour(drawing *dr, int hatch); +int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int mono); +int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey); +int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, + int hatch); +void print_line_width(drawing *dr, int width); +void print_line_dotted(drawing *dr, int dotted); + +/* + * midend.c + */ +midend *midend_new(frontend *fe, const game *ourgame, + const drawing_api *drapi, void *drhandle); +void midend_free(midend *me); +const game *midend_which_game(midend *me); +void midend_set_params(midend *me, game_params *params); +game_params *midend_get_params(midend *me); +void midend_size(midend *me, int *x, int *y, int user_size); +void midend_reset_tilesize(midend *me); +void midend_new_game(midend *me); +void midend_restart_game(midend *me); +void midend_stop_anim(midend *me); +int midend_process_key(midend *me, int x, int y, int button); +void midend_force_redraw(midend *me); +void midend_redraw(midend *me); +float *midend_colours(midend *me, int *ncolours); +void midend_freeze_timer(midend *me, float tprop); +void midend_timer(midend *me, float tplus); +int midend_num_presets(midend *me); +void midend_fetch_preset(midend *me, int n, + char **name, game_params **params); +int midend_which_preset(midend *me); +int midend_wants_statusbar(midend *me); +enum { CFG_SETTINGS, CFG_SEED, CFG_DESC, CFG_FRONTEND_SPECIFIC }; +config_item *midend_get_config(midend *me, int which, char **wintitle); +char *midend_set_config(midend *me, int which, config_item *cfg); +char *midend_game_id(midend *me, char *id); +char *midend_get_game_id(midend *me); +char *midend_get_random_seed(midend *me); +int midend_can_format_as_text_now(midend *me); +char *midend_text_format(midend *me); +char *midend_solve(midend *me); +int midend_status(midend *me); +int midend_can_undo(midend *me); +int midend_can_redo(midend *me); +void midend_supersede_game_desc(midend *me, char *desc, char *privdesc); +char *midend_rewrite_statusbar(midend *me, char *text); +void midend_serialise(midend *me, + void (*write)(void *ctx, void *buf, int len), + void *wctx); +char *midend_deserialise(midend *me, + int (*read)(void *ctx, void *buf, int len), + void *rctx); +char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), + void *rctx); +void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx); +/* Printing functions supplied by the mid-end */ +char *midend_print_puzzle(midend *me, document *doc, int with_soln); +int midend_tilesize(midend *me); + +/* + * malloc.c + */ +void *smalloc(size_t size); +void *srealloc(void *p, size_t size); +void sfree(void *p); +char *dupstr(const char *s); +#define snew(type) \ + ( (type *) smalloc (sizeof (type)) ) +#define snewn(number, type) \ + ( (type *) smalloc ((number) * sizeof (type)) ) +#define sresize(array, number, type) \ + ( (type *) srealloc ((array), (number) * sizeof (type)) ) + +/* + * misc.c + */ +void free_cfg(config_item *cfg); +void obfuscate_bitmap(unsigned char *bmp, int bits, int decode); + +/* allocates output each time. len is always in bytes of binary data. + * May assert (or just go wrong) if lengths are unchecked. */ +char *bin2hex(const unsigned char *in, int inlen); +unsigned char *hex2bin(const char *in, int outlen); + +/* Sets (and possibly dims) background from frontend default colour, + * and auto-generates highlight and lowlight colours too. */ +void game_mkhighlight(frontend *fe, float *ret, + int background, int highlight, int lowlight); +/* As above, but starts from a provided background colour rather + * than the frontend default. */ +void game_mkhighlight_specific(frontend *fe, float *ret, + int background, int highlight, int lowlight); + +/* Randomly shuffles an array of items. */ +void shuffle(void *array, int nelts, int eltsize, random_state *rs); + +/* Draw a rectangle outline, using the drawing API's draw_line. */ +void draw_rect_outline(drawing *dr, int x, int y, int w, int h, + int colour); + +/* Draw a set of rectangle corners (e.g. for a cursor display). */ +void draw_rect_corners(drawing *dr, int cx, int cy, int r, int col); + +void move_cursor(int button, int *x, int *y, int maxw, int maxh, int wrap); + +/* Used in netslide.c and sixteen.c for cursor movement around edge. */ +int c2pos(int w, int h, int cx, int cy); +int c2diff(int w, int h, int cx, int cy, int button); +void pos2c(int w, int h, int pos, int *cx, int *cy); + +/* Draws text with an 'outline' formed by offsetting the text + * by one pixel; useful for highlighting. Outline is omitted if -1. */ +void draw_text_outline(drawing *dr, int x, int y, int fonttype, + int fontsize, int align, + int text_colour, int outline_colour, char *text); +/* + * dsf.c + */ +int *snew_dsf(int size); + +void print_dsf(int *dsf, int size); + +/* Return the canonical element of the equivalence class containing element + * val. If 'inverse' is non-NULL, this function will put into it a flag + * indicating whether the canonical element is inverse to val. */ +int edsf_canonify(int *dsf, int val, int *inverse); +int dsf_canonify(int *dsf, int val); +int dsf_size(int *dsf, int val); + +/* Allow the caller to specify that two elements should be in the same + * equivalence class. If 'inverse' is TRUE, the elements are actually opposite + * to one another in some sense. This function will fail an assertion if the + * caller gives it self-contradictory data, ie if two elements are claimed to + * be both opposite and non-opposite. */ +void edsf_merge(int *dsf, int v1, int v2, int inverse); +void dsf_merge(int *dsf, int v1, int v2); +void dsf_init(int *dsf, int len); + +/* + * tdq.c + */ + +/* + * Data structure implementing a 'to-do queue', a simple + * de-duplicating to-do list mechanism. + * + * Specification: a tdq is a queue which can hold integers from 0 to + * n-1, where n was some constant specified at tdq creation time. No + * integer may appear in the queue's current contents more than once; + * an attempt to add an already-present integer again will do nothing, + * so that that integer is removed from the queue at the position + * where it was _first_ inserted. The add and remove operations take + * constant time. + * + * The idea is that you might use this in applications like solvers: + * keep a tdq listing the indices of grid squares that you currently + * need to process in some way. Whenever you modify a square in a way + * that will require you to re-scan its neighbours, add them to the + * list with tdq_add; meanwhile you're constantly taking elements off + * the list when you need another square to process. In solvers where + * deductions are mostly localised, this should prevent repeated + * O(N^2) loops over the whole grid looking for something to do. (But + * if only _most_ of the deductions are localised, then you should + * respond to an empty to-do list by re-adding everything using + * tdq_fill, so _then_ you rescan the whole grid looking for newly + * enabled non-local deductions. Only if you've done that and emptied + * the list again finding nothing new to do are you actually done.) + */ +typedef struct tdq tdq; +tdq *tdq_new(int n); +void tdq_free(tdq *tdq); +void tdq_add(tdq *tdq, int k); +int tdq_remove(tdq *tdq); /* returns -1 if nothing available */ +void tdq_fill(tdq *tdq); /* add everything to the tdq at once */ + +/* + * laydomino.c + */ +int *domino_layout(int w, int h, random_state *rs); +void domino_layout_prealloc(int w, int h, random_state *rs, + int *grid, int *grid2, int *list); +/* + * version.c + */ +extern char ver[]; + +/* + * random.c + */ +random_state *random_new(const char *seed, int len); +random_state *random_copy(random_state *tocopy); +unsigned long random_bits(random_state *state, int bits); +unsigned long random_upto(random_state *state, unsigned long limit); +void random_free(random_state *state); +char *random_state_encode(random_state *state); +random_state *random_state_decode(const char *input); +/* random.c also exports SHA, which occasionally comes in useful. */ +#if __STDC_VERSION__ >= 199901L +#include +typedef uint32_t uint32; +#elif UINT_MAX >= 4294967295L +typedef unsigned int uint32; +#else +typedef unsigned long uint32; +#endif +typedef struct { + uint32 h[5]; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +} SHA_State; +void SHA_Init(SHA_State *s); +void SHA_Bytes(SHA_State *s, const void *p, int len); +void SHA_Final(SHA_State *s, unsigned char *output); +void SHA_Simple(const void *p, int len, unsigned char *output); + +/* + * printing.c + */ +document *document_new(int pw, int ph, float userscale); +void document_free(document *doc); +void document_add_puzzle(document *doc, const game *game, game_params *par, + game_state *st, game_state *st2); +void document_print(document *doc, drawing *dr); + +/* + * ps.c + */ +//psdata *ps_init(FILE *outfile, int colour); +//void ps_free(psdata *ps); +//drawing *ps_drawing_api(psdata *ps); + +/* + * combi.c: provides a structure and functions for iterating over + * combinations (i.e. choosing r things out of n). + */ +typedef struct _combi_ctx { + int r, n, nleft, total; + int *a; +} combi_ctx; + +combi_ctx *new_combi(int r, int n); +void reset_combi(combi_ctx *combi); +combi_ctx *next_combi(combi_ctx *combi); /* returns NULL for end */ +void free_combi(combi_ctx *combi); + +/* + * divvy.c + */ +/* divides w*h rectangle into pieces of size k. Returns w*h dsf. */ +int *divvy_rectangle(int w, int h, int k, random_state *rs); + +/* + * findloop.c + */ +struct findloopstate; +struct findloopstate *findloop_new_state(int nvertices); +void findloop_free_state(struct findloopstate *); +/* + * Callback provided by the client code to enumerate the graph + * vertices joined directly to a given vertex. + * + * Semantics: if vertex >= 0, return one of its neighbours; if vertex + * < 0, return a previously unmentioned neighbour of whatever vertex + * was last passed as input. Write to 'ctx' as necessary to store + * state. In either case, return < 0 if no such vertex can be found. + */ +typedef int (*neighbour_fn_t)(int vertex, void *ctx); +/* + * Actual function to find loops. 'ctx' will be passed unchanged to + * the 'neighbour' function to query graph edges. Returns FALSE if no + * loop was found, or TRUE if one was. + */ +int findloop_run(struct findloopstate *state, int nvertices, + neighbour_fn_t neighbour, void *ctx); +/* + * Query whether an edge is part of a loop, in the output of + * find_loops. + * + * Due to the internal storage format, if you pass u,v which are not + * connected at all, the output will be TRUE. (The algorithm actually + * stores an exhaustive list of *non*-loop edges, because there are + * fewer of those, so really it's querying whether the edge is on that + * list.) + */ +int findloop_is_loop_edge(struct findloopstate *state, int u, int v); + +/* + * Data structure containing the function calls and data specific + * to a particular game. This is enclosed in a data structure so + * that a particular platform can choose, if it wishes, to compile + * all the games into a single combined executable rather than + * having lots of little ones. + */ +struct game { + const char *name; + const char *winhelp_topic, *htmlhelp_topic; + game_params *(*default_params)(void); + int (*fetch_preset)(int i, char **name, game_params **params); + void (*decode_params)(game_params *, char const *string); + char *(*encode_params)(const game_params *, int full); + void (*free_params)(game_params *params); + game_params *(*dup_params)(const game_params *params); + int can_configure; + config_item *(*configure)(const game_params *params); + game_params *(*custom_params)(const config_item *cfg); + char *(*validate_params)(const game_params *params, int full); + char *(*new_desc)(const game_params *params, random_state *rs, + char **aux, int interactive); + char *(*validate_desc)(const game_params *params, const char *desc); + game_state *(*new_game)(midend *me, const game_params *params, + const char *desc); + game_state *(*dup_game)(const game_state *state); + void (*free_game)(game_state *state); + int can_solve; + char *(*solve)(const game_state *orig, const game_state *curr, + const char *aux, char **error); + int can_format_as_text_ever; + int (*can_format_as_text_now)(const game_params *params); + char *(*text_format)(const game_state *state); + game_ui *(*new_ui)(const game_state *state); + void (*free_ui)(game_ui *ui); + char *(*encode_ui)(const game_ui *ui); + void (*decode_ui)(game_ui *ui, const char *encoding); + void (*changed_state)(game_ui *ui, const game_state *oldstate, + const game_state *newstate); + char *(*interpret_move)(const game_state *state, game_ui *ui, + const game_drawstate *ds, int x, int y, int button); + game_state *(*execute_move)(const game_state *state, const char *move); + int preferred_tilesize; + void (*compute_size)(const game_params *params, int tilesize, + int *x, int *y); + void (*set_size)(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize); + float *(*colours)(frontend *fe, int *ncolours); + game_drawstate *(*new_drawstate)(drawing *dr, const game_state *state); + void (*free_drawstate)(drawing *dr, game_drawstate *ds); + void (*redraw)(drawing *dr, game_drawstate *ds, const game_state *oldstate, + const game_state *newstate, int dir, const game_ui *ui, + float anim_time, float flash_time); + float (*anim_length)(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui); + float (*flash_length)(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui); + int (*status)(const game_state *state); + int can_print, can_print_in_colour; + void (*print_size)(const game_params *params, float *x, float *y); + void (*print)(drawing *dr, const game_state *state, int tilesize); + int wants_statusbar; + int is_timed; + int (*timing_state)(const game_state *state, game_ui *ui); + int flags; +}; + +/* + * Data structure containing the drawing API implemented by the + * front end and also by cross-platform printing modules such as + * PostScript. + */ +struct drawing_api { + void (*draw_text)(void *handle, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text); + void (*draw_rect)(void *handle, int x, int y, int w, int h, int colour); + void (*draw_line)(void *handle, int x1, int y1, int x2, int y2, + int colour); + void (*draw_polygon)(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour); + void (*draw_circle)(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour); + void (*draw_update)(void *handle, int x, int y, int w, int h); + void (*clip)(void *handle, int x, int y, int w, int h); + void (*unclip)(void *handle); + void (*start_draw)(void *handle); + void (*end_draw)(void *handle); + void (*status_bar)(void *handle, char *text); + blitter *(*blitter_new)(void *handle, int w, int h); + void (*blitter_free)(void *handle, blitter *bl); + void (*blitter_save)(void *handle, blitter *bl, int x, int y); + void (*blitter_load)(void *handle, blitter *bl, int x, int y); + void (*begin_doc)(void *handle, int pages); + void (*begin_page)(void *handle, int number); + void (*begin_puzzle)(void *handle, float xm, float xc, + float ym, float yc, int pw, int ph, float wmm); + void (*end_puzzle)(void *handle); + void (*end_page)(void *handle, int number); + void (*end_doc)(void *handle); + void (*line_width)(void *handle, float width); + void (*line_dotted)(void *handle, int dotted); + char *(*text_fallback)(void *handle, const char *const *strings, + int nstrings); + void (*draw_thick_line)(void *handle, float thickness, + float x1, float y1, float x2, float y2, + int colour); +}; + +/* + * For one-game-at-a-time platforms, there's a single structure + * like the above, under a fixed name. For all-at-once platforms, + * there's a list of all available puzzles in array form. + */ +#ifdef COMBINED +extern const game *gamelist[]; +extern const int gamecount; +#else +extern const game thegame; +#endif + +/* A little bit of help to lazy developers */ +#define DEFAULT_STATUSBAR_TEXT "Use status_bar() to fill this in." + +#endif /* PUZZLES_PUZZLES_H */ diff --git a/apps/plugins/puzzles/puzzles.make b/apps/plugins/puzzles/puzzles.make new file mode 100644 index 0000000000..054e53eeb1 --- /dev/null +++ b/apps/plugins/puzzles/puzzles.make @@ -0,0 +1,113 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +PUZZLES_SRCDIR = $(APPSDIR)/plugins/puzzles +PUZZLES_OBJDIR = $(BUILDDIR)/apps/plugins/puzzles + +# define this as "yes" to do a monolithic build -- remember to also +# uncomment the sgt-puzzles loader in apps/plugins/SOURCES +PUZZLES_COMBINED = + +ifdef PUZZLES_COMBINED +PUZZLES_SRC := $(call preprocess, $(PUZZLES_SRCDIR)/SOURCES, -DCOMBINED) +else +PUZZLES_SRC := $(call preprocess, $(PUZZLES_SRCDIR)/SOURCES) +endif + +ifndef PUZZLES_COMBINED +PUZZLES_SHARED_OBJ := $(call c2obj, $(PUZZLES_SRC)) +endif + +PUZZLES_GAMES_SRC := $(call preprocess, $(PUZZLES_SRCDIR)/SOURCES.games) +PUZZLES_SRC += $(PUZZLES_GAMES_SRC) +PUZZLES_OBJ := $(call c2obj, $(PUZZLES_SRC)) +OTHER_SRC += $(PUZZLES_SRC) + +ifdef PUZZLES_COMBINED +ifndef APP_TYPE +ROCKS += $(PUZZLES_OBJDIR)/puzzles.ovl +PUZZLES_OUTLDS = $(PUZZLES_OBJDIR)/puzzles.link +PUZZLES_OVLFLAGS = -T$(PUZZLES_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map +else +ROCKS += $(PUZZLES_OBJDIR)/puzzles.rock +endif +else +PUZZLES_ROCKS := $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock))) +ROCKS += $(PUZZLES_ROCKS) +endif + +PUZZLESFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -Os \ + -Wno-unused-parameter -Wno-sign-compare -Wno-strict-aliasing -w \ + -DFOR_REAL +ifdef PUZZLES_COMBINED +PUZZLESFLAGS += -DCOMBINED +endif + +ifdef PUZZLES_COMBINED +$(PUZZLES_OBJDIR)/puzzles.rock: $(PUZZLES_OBJ) $(TLSFLIB) + +$(PUZZLES_OBJDIR)/puzzles.refmap: $(PUZZLES_OBJ) $(TLSFLIB) + +$(PUZZLES_OUTLDS): $(PLUGIN_LDS) $(PUZZLES_OBJDIR)/puzzles.refmap + $(call PRINTS,PP $(@F))$(call preprocess2file,$<,$@,-DOVERLAY_OFFSET=$(shell \ + $(TOOLSDIR)/ovl_offset.pl $(PUZZLES_OBJDIR)/puzzles.refmap)) + +$(PUZZLES_OBJDIR)/puzzles.ovl: $(PUZZLES_OBJ) $(PUZZLES_OUTLDS) $(TLSFLIB) + $(SILENT)$(CC) $(PLUGINFLAGS) -o $(basename $@).elf \ + $(filter %.o, $^) \ + $(filter %.a, $+) \ + -lgcc $(PUZZLES_OVLFLAGS) + $(call PRINTS,LD $(@F))$(call objcopy,$(basename $@).elf,$@) +else +# todo: figure out how to do this with wildcards +$(PUZZLES_OBJDIR)/sgt-blackbox.rock: $(PUZZLES_OBJDIR)/blackbox.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-bridges.rock: $(PUZZLES_OBJDIR)/bridges.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-cube.rock: $(PUZZLES_OBJDIR)/cube.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-dominosa.rock: $(PUZZLES_OBJDIR)/dominosa.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-fifteen.rock: $(PUZZLES_OBJDIR)/fifteen.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-filling.rock: $(PUZZLES_OBJDIR)/filling.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-flip.rock: $(PUZZLES_OBJDIR)/flip.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-flood.rock: $(PUZZLES_OBJDIR)/flood.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-galaxies.rock: $(PUZZLES_OBJDIR)/galaxies.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-guess.rock: $(PUZZLES_OBJDIR)/guess.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-inertia.rock: $(PUZZLES_OBJDIR)/inertia.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-keen.rock: $(PUZZLES_OBJDIR)/keen.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-lightup.rock: $(PUZZLES_OBJDIR)/lightup.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-loopy.rock: $(PUZZLES_OBJDIR)/loopy.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-magnets.rock: $(PUZZLES_OBJDIR)/magnets.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-map.rock: $(PUZZLES_OBJDIR)/map.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-mines.rock: $(PUZZLES_OBJDIR)/mines.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-net.rock: $(PUZZLES_OBJDIR)/net.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-netslide.rock: $(PUZZLES_OBJDIR)/netslide.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-palisade.rock: $(PUZZLES_OBJDIR)/palisade.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-pattern.rock: $(PUZZLES_OBJDIR)/pattern.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-pearl.rock: $(PUZZLES_OBJDIR)/pearl.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-pegs.rock: $(PUZZLES_OBJDIR)/pegs.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-range.rock: $(PUZZLES_OBJDIR)/range.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-rect.rock: $(PUZZLES_OBJDIR)/rect.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-samegame.rock: $(PUZZLES_OBJDIR)/samegame.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-signpost.rock: $(PUZZLES_OBJDIR)/signpost.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-singles.rock: $(PUZZLES_OBJDIR)/singles.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-sixteen.rock: $(PUZZLES_OBJDIR)/sixteen.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-slant.rock: $(PUZZLES_OBJDIR)/slant.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-solo.rock: $(PUZZLES_OBJDIR)/solo.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-tents.rock: $(PUZZLES_OBJDIR)/tents.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-towers.rock: $(PUZZLES_OBJDIR)/towers.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-tracks.rock: $(PUZZLES_OBJDIR)/tracks.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-twiddle.rock: $(PUZZLES_OBJDIR)/twiddle.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-undead.rock: $(PUZZLES_OBJDIR)/undead.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-unequal.rock: $(PUZZLES_OBJDIR)/unequal.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-unruly.rock: $(PUZZLES_OBJDIR)/unruly.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +$(PUZZLES_OBJDIR)/sgt-untangle.rock: $(PUZZLES_OBJDIR)/untangle.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB) +endif + +# special pattern rule for compiling puzzles with extra flags +$(PUZZLES_OBJDIR)/%.o: $(PUZZLES_SRCDIR)/%.c $(PUZZLES_SRCDIR)/puzzles.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(PUZZLESFLAGS) -c $< -o $@ diff --git a/apps/plugins/puzzles/puzzles.rc2 b/apps/plugins/puzzles/puzzles.rc2 new file mode 100644 index 0000000000..4442a9b138 --- /dev/null +++ b/apps/plugins/puzzles/puzzles.rc2 @@ -0,0 +1,65 @@ +/* Standard stuff that goes into the Windows resources for all puzzles. */ + +#ifdef _WIN32_WCE + +#include +#include + +#define SHMENUBAR RCDATA +#define I_IMAGENONE (-2) +#define IDC_STATIC (-1) + +#include "resource.h" + +IDR_MENUBAR1 MENU DISCARDABLE +BEGIN + POPUP "Game" + BEGIN + MENUITEM "Dummy", 0 + END + POPUP "Type" + BEGIN + MENUITEM "Dummy", 0 + END +END + +IDR_MENUBAR1 SHMENUBAR DISCARDABLE +BEGIN + IDR_MENUBAR1, 2, + I_IMAGENONE, ID_GAME, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_GAME, 0, 0, + I_IMAGENONE, ID_TYPE, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_TYPE, 0, 1, +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_CAP_GAME "Game" + IDS_CAP_TYPE "Type" +END + +IDD_ABOUT DIALOG DISCARDABLE 0, 0, 0, 0 +STYLE WS_POPUP +FONT 8, "Tahoma" +BEGIN + LTEXT "", IDC_ABOUT_CAPTION, 4, 4, 150, 8 + LTEXT "", IDC_ABOUT_LINE, 4, 16, 150, 1, WS_BORDER + LTEXT "", IDC_ABOUT_GAME, 4, 22, 150, 8 + LTEXT "from Simon Tatham's Portable Puzzle Collection", + IDC_STATIC, 4, 36, 150, 8, SS_LEFTNOWORDWRAP + LTEXT "Pocket PC port by Darek Olszewski", + IDC_STATIC, 4, 46, 150, 8 + LTEXT "", IDC_ABOUT_VERSION, 4, 60, 150, 8 +END + +IDD_CONFIG DIALOG DISCARDABLE 0, 0, 0, 0 +STYLE WS_POPUP +FONT 8, "Tahoma" +BEGIN + LTEXT "", IDC_CONFIG_CAPTION, 4, 4, 150, 8 + LTEXT "", IDC_CONFIG_LINE, 4, 16, 150, 1, WS_BORDER +END + +IDR_PADTOOLBAR BITMAP DISCARDABLE "padtoolbar.bmp" + +#endif /* _WIN32_WCE */ diff --git a/apps/plugins/puzzles/random.c b/apps/plugins/puzzles/random.c new file mode 100644 index 0000000000..c129a8d2ba --- /dev/null +++ b/apps/plugins/puzzles/random.c @@ -0,0 +1,350 @@ +/* + * random.c: Internal random number generator, guaranteed to work + * the same way on all platforms. Used when generating an initial + * game state from a random game seed; required to ensure that game + * seeds can be exchanged between versions of a puzzle compiled for + * different platforms. + * + * The generator is based on SHA-1. This is almost certainly + * overkill, but I had the SHA-1 code kicking around and it was + * easier to reuse it than to do anything else! + */ + +#include +#include + +#include "puzzles.h" + +/* ---------------------------------------------------------------------- + * Core SHA algorithm: processes 16-word blocks into a message digest. + */ + +#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) + +static void SHA_Core_Init(uint32 h[5]) +{ + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; +} + +static void SHATransform(uint32 * digest, uint32 * block) +{ + uint32 w[80]; + uint32 a, b, c, d, e; + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 80; t++) { + uint32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = rol(tmp, 1); + } + + a = digest[0]; + b = digest[1]; + c = digest[2]; + d = digest[3]; + e = digest[4]; + + for (t = 0; t < 20; t++) { + uint32 tmp = + rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 20; t < 40; t++) { + uint32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 40; t < 60; t++) { + uint32 tmp = rol(a, + 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] + + 0x8f1bbcdc; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 60; t < 80; t++) { + uint32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; +} + +/* ---------------------------------------------------------------------- + * Outer SHA algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core SHA algorithm. + */ + +void SHA_Init(SHA_State * s) +{ + SHA_Core_Init(s->h); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void SHA_Bytes(SHA_State * s, const void *p, int len) +{ + unsigned char *q = (unsigned char *) p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused && s->blkused + len < 64) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= 64) { + memcpy(s->block + s->blkused, q, 64 - s->blkused); + q += 64 - s->blkused; + len -= 64 - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + (((uint32) s->block[i * 4 + 0]) << 24) | + (((uint32) s->block[i * 4 + 1]) << 16) | + (((uint32) s->block[i * 4 + 2]) << 8) | + (((uint32) s->block[i * 4 + 3]) << 0); + } + SHATransform(s->h, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void SHA_Final(SHA_State * s, unsigned char *output) +{ + int i; + int pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + SHA_Bytes(s, &c, pad); + + c[0] = (unsigned char)((lenhi >> 24) & 0xFF); + c[1] = (unsigned char)((lenhi >> 16) & 0xFF); + c[2] = (unsigned char)((lenhi >> 8) & 0xFF); + c[3] = (unsigned char)((lenhi >> 0) & 0xFF); + c[4] = (unsigned char)((lenlo >> 24) & 0xFF); + c[5] = (unsigned char)((lenlo >> 16) & 0xFF); + c[6] = (unsigned char)((lenlo >> 8) & 0xFF); + c[7] = (unsigned char)((lenlo >> 0) & 0xFF); + + SHA_Bytes(s, &c, 8); + + for (i = 0; i < 5; i++) { + output[i * 4] = (unsigned char)((s->h[i] >> 24) & 0xFF); + output[i * 4 + 1] = (unsigned char)((s->h[i] >> 16) & 0xFF); + output[i * 4 + 2] = (unsigned char)((s->h[i] >> 8) & 0xFF); + output[i * 4 + 3] = (unsigned char)((s->h[i]) & 0xFF); + } +} + +void SHA_Simple(const void *p, int len, unsigned char *output) +{ + SHA_State s; + + SHA_Init(&s); + SHA_Bytes(&s, p, len); + SHA_Final(&s, output); +} + +/* ---------------------------------------------------------------------- + * The random number generator. + */ + +struct random_state { + unsigned char seedbuf[40]; + unsigned char databuf[20]; + int pos; +}; + +random_state *random_new(const char *seed, int len) +{ + random_state *state; + + state = snew(random_state); + + SHA_Simple(seed, len, state->seedbuf); + SHA_Simple(state->seedbuf, 20, state->seedbuf + 20); + SHA_Simple(state->seedbuf, 40, state->databuf); + state->pos = 0; + + return state; +} + +random_state *random_copy(random_state *tocopy) +{ + random_state *result; + result = snew(random_state); + memcpy(result->seedbuf, tocopy->seedbuf, sizeof(result->seedbuf)); + memcpy(result->databuf, tocopy->databuf, sizeof(result->databuf)); + result->pos = tocopy->pos; + return result; +} + +unsigned long random_bits(random_state *state, int bits) +{ + unsigned long ret = 0; + int n; + + for (n = 0; n < bits; n += 8) { + if (state->pos >= 20) { + int i; + + for (i = 0; i < 20; i++) { + if (state->seedbuf[i] != 0xFF) { + state->seedbuf[i]++; + break; + } else + state->seedbuf[i] = 0; + } + SHA_Simple(state->seedbuf, 40, state->databuf); + state->pos = 0; + } + ret = (ret << 8) | state->databuf[state->pos++]; + } + + /* + * `(1 << bits) - 1' is not good enough, since if bits==32 on a + * 32-bit machine, behaviour is undefined and Intel has a nasty + * habit of shifting left by zero instead. We'll shift by + * bits-1 and then separately shift by one. + */ + ret &= (1 << (bits-1)) * 2 - 1; + return ret; +} + +unsigned long random_upto(random_state *state, unsigned long limit) +{ + int bits = 0; + unsigned long max, divisor, data; + + while ((limit >> bits) != 0) + bits++; + + bits += 3; + //assert(bits < 32); + + max = 1L << bits; + divisor = max / limit; + max = limit * divisor; + + do { + data = random_bits(state, bits); + } while (data >= max); + + return data / divisor; +} + +void random_free(random_state *state) +{ + sfree(state); +} + +char *random_state_encode(random_state *state) +{ + char retbuf[256]; + int len = 0, i; + + for (i = 0; i < lenof(state->seedbuf); i++) + len += sprintf(retbuf+len, "%02x", state->seedbuf[i]); + for (i = 0; i < lenof(state->databuf); i++) + len += sprintf(retbuf+len, "%02x", state->databuf[i]); + len += sprintf(retbuf+len, "%02x", state->pos); + + return dupstr(retbuf); +} + +random_state *random_state_decode(const char *input) +{ + random_state *state; + int pos, byte, digits; + + state = snew(random_state); + + memset(state->seedbuf, 0, sizeof(state->seedbuf)); + memset(state->databuf, 0, sizeof(state->databuf)); + state->pos = 0; + + byte = digits = 0; + pos = 0; + while (*input) { + int v = *input++; + + if (v >= '0' && v <= '9') + v = v - '0'; + else if (v >= 'A' && v <= 'F') + v = v - 'A' + 10; + else if (v >= 'a' && v <= 'f') + v = v - 'a' + 10; + else + v = 0; + + byte = (byte << 4) | v; + digits++; + + if (digits == 2) { + /* + * We have a byte. Put it somewhere. + */ + if (pos < lenof(state->seedbuf)) + state->seedbuf[pos++] = byte; + else if (pos < lenof(state->seedbuf) + lenof(state->databuf)) + state->databuf[pos++ - lenof(state->seedbuf)] = byte; + else if (pos == lenof(state->seedbuf) + lenof(state->databuf) && + byte <= lenof(state->databuf)) + state->pos = byte; + byte = digits = 0; + } + } + + return state; +} diff --git a/apps/plugins/puzzles/range.R b/apps/plugins/puzzles/range.R new file mode 100644 index 0000000000..f1256efd1e --- /dev/null +++ b/apps/plugins/puzzles/range.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +RANGE_EXTRA = dsf + +range : [X] GTK COMMON range RANGE_EXTRA range-icon|no-icon + +range : [G] WINDOWS COMMON range RANGE_EXTRA range.res|noicon.res + +ALL += range[COMBINED] RANGE_EXTRA + +!begin am gtk +GAMES += range +!end + +!begin >list.c + A(range) \ +!end + +!begin >gamedesc.txt +range:range.exe:Range:Visible-distance puzzle:Place black squares to limit the visible distance from each numbered cell. +!end diff --git a/apps/plugins/puzzles/range.c b/apps/plugins/puzzles/range.c new file mode 100644 index 0000000000..e1067cbcc5 --- /dev/null +++ b/apps/plugins/puzzles/range.c @@ -0,0 +1,1833 @@ +/* + * range.c: implementation of the Nikoli game 'Kurodoko' / 'Kuromasu'. + */ + +/* + * Puzzle rules: the player is given a WxH grid of white squares, some + * of which contain numbers. The goal is to paint some of the squares + * black, such that: + * + * - no cell (err, cell = square) with a number is painted black + * - no black cells have an adjacent (horz/vert) black cell + * - the white cells are all connected (through other white cells) + * - if a cell contains a number n, let h and v be the lengths of the + * maximal horizontal and vertical white sequences containing that + * cell. Then n must equal h + v - 1. + */ + +/* example instance with its encoding and textual representation, both + * solved and unsolved (made by thegame.solve and thegame.text_format) + * + * +--+--+--+--+--+--+--+ + * | | | | | 7| | | + * +--+--+--+--+--+--+--+ + * | 3| | | | | | 8| + * +--+--+--+--+--+--+--+ + * | | | | | | 5| | + * +--+--+--+--+--+--+--+ + * | | | 7| | 7| | | + * +--+--+--+--+--+--+--+ + * | |13| | | | | | + * +--+--+--+--+--+--+--+ + * | 4| | | | | | 8| + * +--+--+--+--+--+--+--+ + * | | | 4| | | | | + * +--+--+--+--+--+--+--+ + * + * 7x7:d7b3e8e5c7a7c13e4d8b4d + * + * +--+--+--+--+--+--+--+ + * |..|..|..|..| 7|..|..| + * +--+--+--+--+--+--+--+ + * | 3|..|##|..|##|..| 8| + * +--+--+--+--+--+--+--+ + * |##|..|..|##|..| 5|..| + * +--+--+--+--+--+--+--+ + * |..|..| 7|..| 7|##|..| + * +--+--+--+--+--+--+--+ + * |..|13|..|..|..|..|..| + * +--+--+--+--+--+--+--+ + * | 4|..|##|..|##|..| 8| + * +--+--+--+--+--+--+--+ + * |##|..| 4|..|..|##|..| + * +--+--+--+--+--+--+--+ + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#include + +#define setmember(obj, field) ( (obj) . field = field ) + +static char *nfmtstr(int n, char *fmt, ...) { + va_list va; + char *ret = snewn(n+1, char); + va_start(va, fmt); + vsprintf(ret, fmt, va); + va_end(va); + return ret; +} + +#define SWAP(type, lvar1, lvar2) do { \ + type tmp = (lvar1); \ + (lvar1) = (lvar2); \ + (lvar2) = tmp; \ +} while (0) + +/* ---------------------------------------------------------------------- + * Game parameters, presets, states + */ + +typedef signed char puzzle_size; + +struct game_params { + puzzle_size w; + puzzle_size h; +}; + +struct game_state { + struct game_params params; + unsigned int has_cheated: 1; + unsigned int was_solved: 1; + puzzle_size *grid; +}; + +#define DEFAULT_PRESET 0 +static struct game_params range_presets[] = {{9, 6}, {12, 8}, {13, 9}, {16, 11}}; +/* rationale: I want all four combinations of {odd/even, odd/even}, as + * they play out differently with respect to two-way symmetry. I also + * want them to be generated relatively fast yet still be large enough + * to be entertaining for a decent amount of time, and I want them to + * make good use of monitor real estate (the typical screen resolution + * is why I do 13x9 and not 9x13). + */ + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + *ret = range_presets[DEFAULT_PRESET]; /* structure copy */ + return ret; +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + + if (i < 0 || i >= lenof(range_presets)) return FALSE; + + ret = default_params(); + *ret = range_presets[i]; /* struct copy */ + *params = ret; + + *name = nfmtstr(40, "%d x %d", range_presets[i].w, range_presets[i].h); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static void decode_params(game_params *params, char const *string) +{ + /* FIXME check for puzzle_size overflow and decoding issues */ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char) *string)) ++string; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char str[80]; + sprintf(str, "%dx%d", params->w, params->h); + return dupstr(str); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + ret[0].sval = nfmtstr(10, "%d", params->w); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + ret[1].sval = nfmtstr(10, "%d", params->h); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *configuration) +{ + game_params *ret = snew(game_params); + ret->w = atoi(configuration[0].sval); + ret->h = atoi(configuration[1].sval); + return ret; +} + +#define memdup(dst, src, n, type) do { \ + dst = snewn(n, type); \ + memcpy(dst, src, n * sizeof (type)); \ +} while (0) + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + int const n = state->params.w * state->params.h; + + *ret = *state; /* structure copy */ + + /* copy the poin_tee_, set a new value of the poin_ter_ */ + memdup(ret->grid, state->grid, n, puzzle_size); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state); +} + + +/* ---------------------------------------------------------------------- + * The solver subsystem. + * + * The solver is used for two purposes: + * - To solve puzzles when the user selects `Solve'. + * - To test solubility of a grid as clues are being removed from it + * during the puzzle generation. + * + * It supports the following ways of reasoning: + * + * - A cell adjacent to a black cell must be white. + * + * - If painting a square black would bisect the white regions, that + * square is white (by finding biconnected components' cut points) + * + * - A cell with number n, covering at most k white squares in three + * directions must white-cover n-k squares in the last direction. + * + * - A cell with number n known to cover k squares, if extending the + * cover by one square in a given direction causes the cell to + * cover _more_ than n squares, that extension cell must be black. + * + * (either if the square already covers n, or if it extends into a + * chunk of size > n - k) + * + * - Recursion. Pick any cell and see if this leads to either a + * contradiction or a solution (and then act appropriately). + * + * + * TODO: + * + * (propagation upper limit) + * - If one has two numbers on the same line, the smaller limits the + * larger. Example: in |b|_|_|8|4|_|_|b|, only two _'s can be both + * white and connected to the "8" cell; so that cell will propagate + * at least four cells orthogonally to the displayed line (which is + * better than the current "at least 2"). + * + * (propagation upper limit) + * - cells can't propagate into other cells if doing so exceeds that + * number. Example: in |b|4|.|.|2|b|, at most one _ can be white; + * otherwise, the |2| would have too many reaching white cells. + * + * (propagation lower and upper limit) + * - `Full Combo': in each four directions d_1 ... d_4, find a set of + * possible propagation distances S_1 ... S_4. For each i=1..4, + * for each x in S_i: if not exists (y, z, w) in the other sets + * such that (x+y+z+w+1 == clue value): then remove x from S_i. + * Repeat until this stabilizes. If any cell would contradict + */ + +#define idx(i, j, w) ((i)*(w) + (j)) +#define out_of_bounds(r, c, w, h) \ + ((r) < 0 || (r) >= h || (c) < 0 || (c) >= w) + +typedef struct square { + puzzle_size r, c; +} square; + +enum {BLACK = -2, WHITE, EMPTY}; +/* white is for pencil marks, empty is undecided */ + +static int const dr[4] = {+1, 0, -1, 0}; +static int const dc[4] = { 0, +1, 0, -1}; +static int const cursors[4] = /* must match dr and dc */ +{CURSOR_DOWN, CURSOR_RIGHT, CURSOR_UP, CURSOR_LEFT}; + +typedef struct move { + square square; + unsigned int colour: 1; +} move; +enum {M_BLACK = 0, M_WHITE = 1}; + +typedef move *(reasoning)(game_state *state, + int nclues, + const square *clues, + move *buf); + +static reasoning solver_reasoning_not_too_big; +static reasoning solver_reasoning_adjacency; +static reasoning solver_reasoning_connectedness; +static reasoning solver_reasoning_recursion; + +enum { + DIFF_NOT_TOO_BIG, + DIFF_ADJACENCY, + DIFF_CONNECTEDNESS, + DIFF_RECURSION +}; + +static move *solve_internal(const game_state *state, move *base, int diff); + +static char *solve_game(const game_state *orig, const game_state *curpos, + const char *aux, char **error) +{ + int const n = orig->params.w * orig->params.h; + move *const base = snewn(n, move); + move *moves = solve_internal(orig, base, DIFF_RECURSION); + + char *ret = NULL; + + if (moves != NULL) { + int const k = moves - base; + char *str = ret = snewn(15*k + 2, char); + char colour[2] = "BW"; + move *it; + *str++ = 'S'; + *str = '\0'; + for (it = base; it < moves; ++it) + str += sprintf(str, "%c,%d,%d", colour[it->colour], + it->square.r, it->square.c); + } else *error = "This puzzle instance contains a contradiction"; + + sfree(base); + return ret; +} + +static square *find_clues(const game_state *state, int *ret_nclues); +static move *do_solve(game_state *state, + int nclues, + const square *clues, + move *move_buffer, + int difficulty); + +/* new_game_desc entry point in the solver subsystem */ +static move *solve_internal(const game_state *state, move *base, int diff) +{ + int nclues; + square *const clues = find_clues(state, &nclues); + game_state *dup = dup_game(state); + move *const moves = do_solve(dup, nclues, clues, base, diff); + free_game(dup); + sfree(clues); + return moves; +} + +static reasoning *const reasonings[] = { + solver_reasoning_not_too_big, + solver_reasoning_adjacency, + solver_reasoning_connectedness, + solver_reasoning_recursion +}; + +static move *do_solve(game_state *state, + int nclues, + const square *clues, + move *move_buffer, + int difficulty) +{ + struct move *buf = move_buffer, *oldbuf; + int i; + + do { + oldbuf = buf; + for (i = 0; i < lenof(reasonings) && i <= difficulty; ++i) { + /* only recurse if all else fails */ + if (i == DIFF_RECURSION && buf > oldbuf) continue; + buf = (*reasonings[i])(state, nclues, clues, buf); + if (buf == NULL) return NULL; + } + } while (buf > oldbuf); + + return buf; +} + +#define MASK(n) (1 << ((n) + 2)) + +static int runlength(puzzle_size r, puzzle_size c, + puzzle_size dr, puzzle_size dc, + const game_state *state, int colourmask) +{ + int const w = state->params.w, h = state->params.h; + int sz = 0; + while (TRUE) { + int cell = idx(r, c, w); + if (out_of_bounds(r, c, w, h)) break; + if (state->grid[cell] > 0) { + if (!(colourmask & ~(MASK(BLACK) | MASK(WHITE) | MASK(EMPTY)))) + break; + } else if (!(MASK(state->grid[cell]) & colourmask)) break; + ++sz; + r += dr; + c += dc; + } + return sz; +} + +static void solver_makemove(puzzle_size r, puzzle_size c, int colour, + game_state *state, move **buffer_ptr) +{ + int const cell = idx(r, c, state->params.w); + if (out_of_bounds(r, c, state->params.w, state->params.h)) return; + if (state->grid[cell] != EMPTY) return; + setmember((*buffer_ptr)->square, r); + setmember((*buffer_ptr)->square, c); + setmember(**buffer_ptr, colour); + ++*buffer_ptr; + state->grid[cell] = (colour == M_BLACK ? BLACK : WHITE); +} + +static move *solver_reasoning_adjacency(game_state *state, + int nclues, + const square *clues, + move *buf) +{ + int r, c, i; + for (r = 0; r < state->params.h; ++r) + for (c = 0; c < state->params.w; ++c) { + int const cell = idx(r, c, state->params.w); + if (state->grid[cell] != BLACK) continue; + for (i = 0; i < 4; ++i) + solver_makemove(r + dr[i], c + dc[i], M_WHITE, state, &buf); + } + return buf; +} + +enum {NOT_VISITED = -1}; + +static int dfs_biconnect_visit(puzzle_size r, puzzle_size c, + game_state *state, + square *dfs_parent, int *dfs_depth, + move **buf); + +static move *solver_reasoning_connectedness(game_state *state, + int nclues, + const square *clues, + move *buf) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + + square *const dfs_parent = snewn(n, square); + int *const dfs_depth = snewn(n, int); + + int i; + for (i = 0; i < n; ++i) { + dfs_parent[i].r = NOT_VISITED; + dfs_depth[i] = -n; + } + + for (i = 0; i < n && state->grid[i] == BLACK; ++i); + + dfs_parent[i].r = i / w; + dfs_parent[i].c = i % w; /* `dfs root`.parent == `dfs root` */ + dfs_depth[i] = 0; + + dfs_biconnect_visit(i / w, i % w, state, dfs_parent, dfs_depth, &buf); + + sfree(dfs_parent); + sfree(dfs_depth); + + return buf; +} + +/* returns the `lowpoint` of (r, c) */ +static int dfs_biconnect_visit(puzzle_size r, puzzle_size c, + game_state *state, + square *dfs_parent, int *dfs_depth, + move **buf) +{ + const puzzle_size w = state->params.w, h = state->params.h; + int const i = idx(r, c, w), mydepth = dfs_depth[i]; + int lowpoint = mydepth, j, nchildren = 0; + + for (j = 0; j < 4; ++j) { + const puzzle_size rr = r + dr[j], cc = c + dc[j]; + int const cell = idx(rr, cc, w); + + if (out_of_bounds(rr, cc, w, h)) continue; + if (state->grid[cell] == BLACK) continue; + + if (dfs_parent[cell].r == NOT_VISITED) { + int child_lowpoint; + dfs_parent[cell].r = r; + dfs_parent[cell].c = c; + dfs_depth[cell] = mydepth + 1; + child_lowpoint = dfs_biconnect_visit(rr, cc, state, dfs_parent, + dfs_depth, buf); + + if (child_lowpoint >= mydepth && mydepth > 0) + solver_makemove(r, c, M_WHITE, state, buf); + + lowpoint = min(lowpoint, child_lowpoint); + ++nchildren; + } else if (rr != dfs_parent[i].r || cc != dfs_parent[i].c) { + lowpoint = min(lowpoint, dfs_depth[cell]); + } + } + + if (mydepth == 0 && nchildren >= 2) + solver_makemove(r, c, M_WHITE, state, buf); + + return lowpoint; +} + +static move *solver_reasoning_not_too_big(game_state *state, + int nclues, + const square *clues, + move *buf) +{ + int const w = state->params.w, runmasks[4] = { + ~(MASK(BLACK) | MASK(EMPTY)), + MASK(EMPTY), + ~(MASK(BLACK) | MASK(EMPTY)), + ~(MASK(BLACK)) + }; + enum {RUN_WHITE, RUN_EMPTY, RUN_BEYOND, RUN_SPACE}; + + int i, runlengths[4][4]; + + for (i = 0; i < nclues; ++i) { + int j, k, whites, space; + + const puzzle_size row = clues[i].r, col = clues[i].c; + int const clue = state->grid[idx(row, col, w)]; + + for (j = 0; j < 4; ++j) { + puzzle_size r = row + dr[j], c = col + dc[j]; + runlengths[RUN_SPACE][j] = 0; + for (k = 0; k <= RUN_SPACE; ++k) { + int l = runlength(r, c, dr[j], dc[j], state, runmasks[k]); + if (k < RUN_SPACE) { + runlengths[k][j] = l; + r += dr[j] * l; + c += dc[j] * l; + } + runlengths[RUN_SPACE][j] += l; + } + } + + whites = 1; + for (j = 0; j < 4; ++j) whites += runlengths[RUN_WHITE][j]; + + for (j = 0; j < 4; ++j) { + int const delta = 1 + runlengths[RUN_WHITE][j]; + const puzzle_size r = row + delta * dr[j]; + const puzzle_size c = col + delta * dc[j]; + + if (whites == clue) { + solver_makemove(r, c, M_BLACK, state, &buf); + continue; + } + + if (runlengths[RUN_EMPTY][j] == 1 && + whites + + runlengths[RUN_EMPTY][j] + + runlengths[RUN_BEYOND][j] + > clue) { + solver_makemove(r, c, M_BLACK, state, &buf); + continue; + } + + if (whites + + runlengths[RUN_EMPTY][j] + + runlengths[RUN_BEYOND][j] + > clue) { + runlengths[RUN_SPACE][j] = + runlengths[RUN_WHITE][j] + + runlengths[RUN_EMPTY][j] - 1; + + if (runlengths[RUN_EMPTY][j] == 1) + solver_makemove(r, c, M_BLACK, state, &buf); + } + } + + space = 1; + for (j = 0; j < 4; ++j) space += runlengths[RUN_SPACE][j]; + for (j = 0; j < 4; ++j) { + puzzle_size r = row + dr[j], c = col + dc[j]; + + int k = space - runlengths[RUN_SPACE][j]; + if (k >= clue) continue; + + for (; k < clue; ++k, r += dr[j], c += dc[j]) + solver_makemove(r, c, M_WHITE, state, &buf); + } + } + return buf; +} + +static move *solver_reasoning_recursion(game_state *state, + int nclues, + const square *clues, + move *buf) +{ + int const w = state->params.w, n = w * state->params.h; + int cell, colour; + + for (cell = 0; cell < n; ++cell) { + int const r = cell / w, c = cell % w; + int i; + game_state *newstate; + move *recursive_result; + + if (state->grid[cell] != EMPTY) continue; + + /* FIXME: add enum alias for smallest and largest (or N) */ + for (colour = M_BLACK; colour <= M_WHITE; ++colour) { + newstate = dup_game(state); + newstate->grid[cell] = colour; + recursive_result = do_solve(newstate, nclues, clues, buf, + DIFF_RECURSION); + free_game(newstate); + if (recursive_result == NULL) { + solver_makemove(r, c, M_BLACK + M_WHITE - colour, state, &buf); + return buf; + } + for (i = 0; i < n && newstate->grid[i] != EMPTY; ++i); + if (i == n) return buf; + } + } + return buf; +} + +static square *find_clues(const game_state *state, int *ret_nclues) +{ + int r, c, i, nclues = 0; + square *ret = snewn(state->params.w * state->params.h, struct square); + + for (i = r = 0; r < state->params.h; ++r) + for (c = 0; c < state->params.w; ++c, ++i) + if (state->grid[i] > 0) { + ret[nclues].r = r; + ret[nclues].c = c; + ++nclues; + } + + *ret_nclues = nclues; + return sresize(ret, nclues + (nclues == 0), square); +} + +/* ---------------------------------------------------------------------- + * Puzzle generation + * + * Generating kurodoko instances is rather straightforward: + * + * - Start with a white grid and add black squares at randomly chosen + * locations, unless colouring that square black would violate + * either the adjacency or connectedness constraints. + * + * - For each white square, compute the number it would contain if it + * were given as a clue. + * + * - From a starting point of "give _every_ white square as a clue", + * for each white square (in a random order), see if the board is + * solvable when that square is not given as a clue. If not, don't + * give it as a clue, otherwise do. + * + * This never fails, but it's only _almost_ what I do. The real final + * step is this: + * + * - From a starting point of "give _every_ white square as a clue", + * first remove all clues that are two-way rotationally symmetric + * to a black square. If this leaves the puzzle unsolvable, throw + * it out and try again. Otherwise, remove all _pairs_ of clues + * (that are rotationally symmetric) which can be removed without + * rendering the puzzle unsolvable. + * + * This can fail even if one only removes the black and symmetric + * clues; indeed it happens often (avg. once or twice per puzzle) when + * generating 1xN instances. (If you add black cells they must be in + * the end, and if you only add one, it's ambiguous where). + */ + +/* forward declarations of internal calls */ +static void newdesc_choose_black_squares(game_state *state, + const int *shuffle_1toN); +static void newdesc_compute_clues(game_state *state); +static int newdesc_strip_clues(game_state *state, int *shuffle_1toN); +static char *newdesc_encode_game_description(int n, puzzle_size *grid); + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int const w = params->w, h = params->h, n = w * h; + + puzzle_size *const grid = snewn(n, puzzle_size); + int *const shuffle_1toN = snewn(n, int); + + int i, clues_removed; + + char *encoding; + + game_state state; + state.params = *params; + state.grid = grid; + + interactive = 0; /* I don't need it, I shouldn't use it*/ + + for (i = 0; i < n; ++i) shuffle_1toN[i] = i; + + while (TRUE) { + shuffle(shuffle_1toN, n, sizeof (int), rs); + newdesc_choose_black_squares(&state, shuffle_1toN); + + newdesc_compute_clues(&state); + + shuffle(shuffle_1toN, n, sizeof (int), rs); + clues_removed = newdesc_strip_clues(&state, shuffle_1toN); + + if (clues_removed < 0) continue; else break; + } + + encoding = newdesc_encode_game_description(n, grid); + + sfree(grid); + sfree(shuffle_1toN); + + return encoding; +} + +static int dfs_count_white(game_state *state, int cell); + +static void newdesc_choose_black_squares(game_state *state, + const int *shuffle_1toN) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + + int k, any_white_cell, n_black_cells; + + for (k = 0; k < n; ++k) state->grid[k] = WHITE; + + any_white_cell = shuffle_1toN[n - 1]; + n_black_cells = 0; + + /* I like the puzzles that result from n / 3, but maybe this + * could be made a (generation, i.e. non-full) parameter? */ + for (k = 0; k < n / 3; ++k) { + int const i = shuffle_1toN[k], c = i % w, r = i / w; + + int j; + for (j = 0; j < 4; ++j) { + int const rr = r + dr[j], cc = c + dc[j], cell = idx(rr, cc, w); + /* if you're out of bounds, we skip you */ + if (out_of_bounds(rr, cc, w, h)) continue; + if (state->grid[cell] == BLACK) break; /* I can't be black */ + } if (j < 4) continue; /* I have black neighbour: I'm white */ + + state->grid[i] = BLACK; + ++n_black_cells; + + j = dfs_count_white(state, any_white_cell); + if (j + n_black_cells < n) { + state->grid[i] = WHITE; + --n_black_cells; + } + } +} + +static void newdesc_compute_clues(game_state *state) +{ + int const w = state->params.w, h = state->params.h; + int r, c; + + for (r = 0; r < h; ++r) { + int run_size = 0, c, cc; + for (c = 0; c <= w; ++c) { + if (c == w || state->grid[idx(r, c, w)] == BLACK) { + for (cc = c - run_size; cc < c; ++cc) + state->grid[idx(r, cc, w)] += run_size; + run_size = 0; + } else ++run_size; + } + } + + for (c = 0; c < w; ++c) { + int run_size = 0, r, rr; + for (r = 0; r <= h; ++r) { + if (r == h || state->grid[idx(r, c, w)] == BLACK) { + for (rr = r - run_size; rr < r; ++rr) + state->grid[idx(rr, c, w)] += run_size; + run_size = 0; + } else ++run_size; + } + } +} + +#define rotate(x) (n - 1 - (x)) + +static int newdesc_strip_clues(game_state *state, int *shuffle_1toN) +{ + int const w = state->params.w, n = w * state->params.h; + + move *const move_buffer = snewn(n, move); + move *buf; + game_state *dupstate; + + /* + * do a partition/pivot of shuffle_1toN into three groups: + * (1) squares rotationally-symmetric to (3) + * (2) squares not in (1) or (3) + * (3) black squares + * + * They go from [0, left), [left, right) and [right, n) in + * shuffle_1toN (and from there into state->grid[ ]) + * + * Then, remove clues from the grid one by one in shuffle_1toN + * order, until the solver becomes unhappy. If we didn't remove + * all of (1), return (-1). Else, we're happy. + */ + + /* do the partition */ + int clues_removed, k = 0, left = 0, right = n; + + for (;; ++k) { + while (k < right && state->grid[shuffle_1toN[k]] == BLACK) { + --right; + SWAP(int, shuffle_1toN[right], shuffle_1toN[k]); + assert(state->grid[shuffle_1toN[right]] == BLACK); + } + if (k >= right) break; + assert (k >= left); + if (state->grid[rotate(shuffle_1toN[k])] == BLACK) { + SWAP(int, shuffle_1toN[k], shuffle_1toN[left]); + ++left; + } + assert (state->grid[rotate(shuffle_1toN[k])] != BLACK + || k == left - 1); + } + + for (k = 0; k < left; ++k) { + assert (state->grid[rotate(shuffle_1toN[k])] == BLACK); + state->grid[shuffle_1toN[k]] = EMPTY; + } + for (k = left; k < right; ++k) { + assert (state->grid[rotate(shuffle_1toN[k])] != BLACK); + assert (state->grid[shuffle_1toN[k]] != BLACK); + } + for (k = right; k < n; ++k) { + assert (state->grid[shuffle_1toN[k]] == BLACK); + state->grid[shuffle_1toN[k]] = EMPTY; + } + + clues_removed = (left - 0) + (n - right); + + dupstate = dup_game(state); + buf = solve_internal(dupstate, move_buffer, DIFF_RECURSION - 1); + free_game(dupstate); + if (buf - move_buffer < clues_removed) { + /* branch prediction: I don't think I'll go here */ + clues_removed = -1; + goto ret; + } + + for (k = left; k < right; ++k) { + const int i = shuffle_1toN[k], j = rotate(i); + int const clue = state->grid[i], clue_rot = state->grid[j]; + if (clue == BLACK) continue; + state->grid[i] = state->grid[j] = EMPTY; + dupstate = dup_game(state); + buf = solve_internal(dupstate, move_buffer, DIFF_RECURSION - 1); + free_game(dupstate); + clues_removed += 2 - (i == j); + /* if i is the center square, then i == (j = rotate(i)) + * when i and j are one, removing i and j removes only one */ + if (buf - move_buffer == clues_removed) continue; + /* if the solver is sound, refilling all removed clues means + * we have filled all squares, i.e. solved the puzzle. */ + state->grid[i] = clue; + state->grid[j] = clue_rot; + clues_removed -= 2 - (i == j); + } + +ret: + sfree(move_buffer); + return clues_removed; +} + +static int dfs_count_rec(puzzle_size *grid, int r, int c, int w, int h) +{ + int const cell = idx(r, c, w); + if (out_of_bounds(r, c, w, h)) return 0; + if (grid[cell] != WHITE) return 0; + grid[cell] = EMPTY; + return 1 + + dfs_count_rec(grid, r + 0, c + 1, w, h) + + dfs_count_rec(grid, r + 0, c - 1, w, h) + + dfs_count_rec(grid, r + 1, c + 0, w, h) + + dfs_count_rec(grid, r - 1, c + 0, w, h); +} + +static int dfs_count_white(game_state *state, int cell) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + int const r = cell / w, c = cell % w; + int i, k = dfs_count_rec(state->grid, r, c, w, h); + for (i = 0; i < n; ++i) + if (state->grid[i] == EMPTY) + state->grid[i] = WHITE; + return k; +} + +static char *validate_params(const game_params *params, int full) +{ + int const w = params->w, h = params->h; + if (w < 1) return "Error: width is less than 1"; + if (h < 1) return "Error: height is less than 1"; + if (w * h < 1) return "Error: size is less than 1"; + if (w + h - 1 > SCHAR_MAX) return "Error: w + h is too big"; + /* I might be unable to store clues in my puzzle_size *grid; */ + if (full) { + if (w == 2 && h == 2) return "Error: can't create 2x2 puzzles"; + if (w == 1 && h == 2) return "Error: can't create 1x2 puzzles"; + if (w == 2 && h == 1) return "Error: can't create 2x1 puzzles"; + if (w == 1 && h == 1) return "Error: can't create 1x1 puzzles"; + } + return NULL; +} + +/* Definition: a puzzle instance is _good_ if: + * - it has a unique solution + * - the solver can find this solution without using recursion + * - the solution contains at least one black square + * - the clues are 2-way rotationally symmetric + * + * (the idea being: the generator can not output any _bad_ puzzles) + * + * Theorem: validate_params, when full != 0, discards exactly the set + * of parameters for which there are _no_ good puzzle instances. + * + * Proof: it's an immediate consequence of the five lemmas below. + * + * Observation: not only do puzzles on non-tiny grids exist, the + * generator is pretty fast about coming up with them. On my pre-2004 + * desktop box, it generates 100 puzzles on the highest preset (16x11) + * in 8.383 seconds, or <= 0.1 second per puzzle. + * + * ---------------------------------------------------------------------- + * + * Lemma: On a 1x1 grid, there are no good puzzles. + * + * Proof: the one square can't be a clue because at least one square + * is black. But both a white square and a black square satisfy the + * solution criteria, so the puzzle is ambiguous (and hence bad). + * + * Lemma: On a 1x2 grid, there are no good puzzles. + * + * Proof: let's name the squares l and r. Note that there can be at + * most one black square, or adjacency is violated. By assumption at + * least one square is black, so let's call that one l. By clue + * symmetry, neither l nor r can be given as a clue, so the puzzle + * instance is blank and thus ambiguous. + * + * Corollary: On a 2x1 grid, there are no good puzzles. + * Proof: rotate the above proof 90 degrees ;-) + * + * ---------------------------------------------------------------------- + * + * Lemma: On a 2x2 grid, there are no soluble puzzles with 2-way + * rotational symmetric clues and at least one black square. + * + * Proof: Let's name the squares a, b, c, and d, with a and b on the + * top row, a and c in the left column. Let's consider the case where + * a is black. Then no other square can be black: b and c would both + * violate the adjacency constraint; d would disconnect b from c. + * + * So exactly one square is black (and by 4-way rotation symmetry of + * the 2x2 square, it doesn't matter which one, so let's stick to a). + * By 2-way rotational symmetry of the clues and the rule about not + * painting numbers black, neither a nor d can be clues. A blank + * puzzle would be ambiguous, so one of {b, c} is a clue; by symmetry, + * so is the other one. + * + * It is readily seen that their clue value is 2. But "a is black" + * and "d is black" are both valid solutions in this case, so the + * puzzle is ambiguous (and hence bad). + * + * ---------------------------------------------------------------------- + * + * Lemma: On a wxh grid with w, h >= 1 and (w > 2 or h > 2), there is + * at least one good puzzle. + * + * Proof: assume that w > h (otherwise rotate the proof again). Paint + * the top left and bottom right corners black, and fill a clue into + * all the other squares. Present this board to the solver code (or + * player, hypothetically), except with the two black squares as blank + * squares. + * + * For an Nx1 puzzle, observe that every clue is N - 2, and there are + * N - 2 of them in one connected sequence, so the remaining two + * squares can be deduced to be black, which solves the puzzle. + * + * For any other puzzle, let j be a cell in the same row as a black + * cell, but not in the same column (such a cell doesn't exist in 2x3 + * puzzles, but we assume w > h and such cells exist in 3x2 puzzles). + * + * Note that the number of cells in axis parallel `rays' going out + * from j exceeds j's clue value by one. Only one such cell is a + * non-clue, so it must be black. Similarly for the other corner (let + * j' be a cell in the same row as the _other_ black cell, but not in + * the same column as _any_ black cell; repeat this argument at j'). + * + * This fills the grid and satisfies all clues and the adjacency + * constraint and doesn't paint on top of any clues. All that is left + * to see is connectedness. + * + * Observe that the white cells in each column form a single connected + * `run', and each column contains a white cell adjacent to a white + * cell in the column to the right, if that column exists. + * + * Thus, any cell in the left-most column can reach any other cell: + * first go to the target column (by repeatedly going to the cell in + * your current column that lets you go right, then going right), then + * go up or down to the desired cell. + * + * As reachability is symmetric (in undirected graphs) and transitive, + * any cell can reach any left-column cell, and from there any other + * cell. + */ + +/* ---------------------------------------------------------------------- + * Game encoding and decoding + */ + +#define NDIGITS_BASE '!' + +static char *newdesc_encode_game_description(int area, puzzle_size *grid) +{ + char *desc = NULL; + int desclen = 0, descsize = 0; + int run, i; + + run = 0; + for (i = 0; i <= area; i++) { + int n = (i < area ? grid[i] : -1); + + if (!n) + run++; + else { + if (descsize < desclen + 40) { + descsize = desclen * 3 / 2 + 40; + desc = sresize(desc, descsize, char); + } + if (run) { + while (run > 0) { + int c = 'a' - 1 + run; + if (run > 26) + c = 'z'; + desc[desclen++] = c; + run -= c - ('a' - 1); + } + } else { + /* + * If there's a number in the very top left or + * bottom right, there's no point putting an + * unnecessary _ before or after it. + */ + if (desclen > 0 && n > 0) + desc[desclen++] = '_'; + } + if (n > 0) + desclen += sprintf(desc+desclen, "%d", n); + run = 0; + } + } + desc[desclen] = '\0'; + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int const n = params->w * params->h; + int squares = 0; + int range = params->w + params->h - 1; /* maximum cell value */ + + while (*desc && *desc != ',') { + int c = *desc++; + if (c >= 'a' && c <= 'z') { + squares += c - 'a' + 1; + } else if (c == '_') { + /* do nothing */; + } else if (c > '0' && c <= '9') { + int val = atoi(desc-1); + if (val < 1 || val > range) + return "Out-of-range number in game description"; + squares++; + while (*desc >= '0' && *desc <= '9') + desc++; + } else + return "Invalid character in game description"; + } + + if (squares < n) + return "Not enough data to fill grid"; + + if (squares > n) + return "Too much data to fit in grid"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int i; + const char *p; + + int const n = params->w * params->h; + game_state *state = snew(game_state); + + me = NULL; /* I don't need it, I shouldn't use it */ + + state->params = *params; /* structure copy */ + state->grid = snewn(n, puzzle_size); + + p = desc; + i = 0; + while (i < n && *p) { + int c = *p++; + if (c >= 'a' && c <= 'z') { + int squares = c - 'a' + 1; + while (squares--) + state->grid[i++] = 0; + } else if (c == '_') { + /* do nothing */; + } else if (c > '0' && c <= '9') { + int val = atoi(p-1); + assert(val >= 1 && val <= params->w+params->h-1); + state->grid[i++] = val; + while (*p >= '0' && *p <= '9') + p++; + } + } + assert(i == n); + state->has_cheated = FALSE; + state->was_solved = FALSE; + + return state; +} + +/* ---------------------------------------------------------------------- + * User interface: ascii + */ + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int cellsize, r, c, i, w_string, h_string, n_string; + char *ret, *buf, *gridline; + + int const w = state->params.w, h = state->params.h; + + cellsize = 0; /* or may be used uninitialized */ + + for (c = 0; c < w; ++c) { + for (r = 0; r < h; ++r) { + puzzle_size k = state->grid[idx(r, c, w)]; + int d; + for (d = 0; k; k /= 10, ++d); + cellsize = max(cellsize, d); + } + } + + ++cellsize; + + w_string = w * cellsize + 2; /* "|%d|%d|...|\n" */ + h_string = 2 * h + 1; /* "+--+--+...+\n%s\n+--+--+...+\n" */ + n_string = w_string * h_string; + + gridline = snewn(w_string + 1, char); /* +1: NUL terminator */ + memset(gridline, '-', w_string); + for (c = 0; c <= w; ++c) gridline[c * cellsize] = '+'; + gridline[w_string - 1] = '\n'; + gridline[w_string - 0] = '\0'; + + buf = ret = snewn(n_string + 1, char); /* +1: NUL terminator */ + for (i = r = 0; r < h; ++r) { + memcpy(buf, gridline, w_string); + buf += w_string; + for (c = 0; c < w; ++c, ++i) { + char ch; + switch (state->grid[i]) { + case BLACK: ch = '#'; break; + case WHITE: ch = '.'; break; + case EMPTY: ch = ' '; break; + default: + buf += sprintf(buf, "|%*d", cellsize - 1, state->grid[i]); + continue; + } + *buf++ = '|'; + memset(buf, ch, cellsize - 1); + buf += cellsize - 1; + } + buf += sprintf(buf, "|\n"); + } + memcpy(buf, gridline, w_string); + buf += w_string; + assert (buf - ret == n_string); + *buf = '\0'; + + sfree(gridline); + + return ret; +} + +/* ---------------------------------------------------------------------- + * User interfaces: interactive + */ + +struct game_ui { + puzzle_size r, c; /* cursor position */ + unsigned int cursor_show: 1; +}; + +static game_ui *new_ui(const game_state *state) +{ + struct game_ui *ui = snew(game_ui); + ui->r = ui->c = 0; + ui->cursor_show = FALSE; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +typedef struct drawcell { + puzzle_size value; + unsigned int error: 1; + unsigned int cursor: 1; + unsigned int flash: 1; +} drawcell; + +struct game_drawstate { + int tilesize; + drawcell *grid; + unsigned int started: 1; +}; + +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE / 2) +#define COORD(x) ((x) * TILESIZE + BORDER) +#define FROMCOORD(x) (((x) - BORDER) / TILESIZE) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + enum {none, forwards, backwards, hint}; + int const w = state->params.w, h = state->params.h; + int r = ui->r, c = ui->c, action = none, cell; + int shift = button & MOD_SHFT; + button &= ~shift; + + if (IS_CURSOR_SELECT(button) && !ui->cursor_show) return NULL; + + if (IS_MOUSE_DOWN(button)) { + r = FROMCOORD(y + TILESIZE) - 1; /* or (x, y) < TILESIZE) */ + c = FROMCOORD(x + TILESIZE) - 1; /* are considered inside */ + if (out_of_bounds(r, c, w, h)) return NULL; + ui->r = r; + ui->c = c; + ui->cursor_show = FALSE; + } + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + /* + * Utterly awful hack, exactly analogous to the one in Slant, + * to configure the left and right mouse buttons the opposite + * way round. + * + * The original puzzle submitter thought it would be more + * useful to have the left button turn an empty square into a + * dotted one, on the grounds that that was what you did most + * often; I (SGT) felt instinctively that the left button + * ought to place black squares and the right button place + * dots, on the grounds that that was consistent with many + * other puzzles in which the left button fills in the data + * used by the solution checker while the right button places + * pencil marks for the user's convenience. + * + * My first beta-player wasn't sure either, so I thought I'd + * pre-emptively put in a 'configuration' mechanism just in + * case. + */ + { + static int swap_buttons = -1; + if (swap_buttons < 0) { + char *env = getenv("RANGE_SWAP_BUTTONS"); + swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y')); + } + if (swap_buttons) { + if (button == LEFT_BUTTON) + button = RIGHT_BUTTON; + else + button = LEFT_BUTTON; + } + } + } + + switch (button) { + case CURSOR_SELECT : case LEFT_BUTTON: action = backwards; break; + case CURSOR_SELECT2: case RIGHT_BUTTON: action = forwards; break; + case 'h': case 'H' : action = hint; break; + case CURSOR_UP: case CURSOR_DOWN: + case CURSOR_LEFT: case CURSOR_RIGHT: + if (ui->cursor_show) { + int i; + for (i = 0; i < 4 && cursors[i] != button; ++i); + assert (i < 4); + if (shift) { + int pre_r = r, pre_c = c, do_pre, do_post; + cell = state->grid[idx(r, c, state->params.w)]; + do_pre = (cell == EMPTY); + + if (out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) { + if (do_pre) + return nfmtstr(40, "W,%d,%d", pre_r, pre_c); + else + return NULL; + } + + ui->r += dr[i]; + ui->c += dc[i]; + + cell = state->grid[idx(ui->r, ui->c, state->params.w)]; + do_post = (cell == EMPTY); + + /* (do_pre ? "..." : "") concat (do_post ? "..." : "") */ + if (do_pre && do_post) + return nfmtstr(80, "W,%d,%dW,%d,%d", + pre_r, pre_c, ui->r, ui->c); + else if (do_pre) + return nfmtstr(40, "W,%d,%d", pre_r, pre_c); + else if (do_post) + return nfmtstr(40, "W,%d,%d", ui->r, ui->c); + else + return ""; + + } else if (!out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) { + ui->r += dr[i]; + ui->c += dc[i]; + } + } else ui->cursor_show = TRUE; + return ""; + } + + if (action == hint) { + move *end, *buf = snewn(state->params.w * state->params.h, + struct move); + char *ret = NULL; + end = solve_internal(state, buf, DIFF_RECURSION); + if (end != NULL && end > buf) { + ret = nfmtstr(40, "%c,%d,%d", + buf->colour == M_BLACK ? 'B' : 'W', + buf->square.r, buf->square.c); + /* We used to set a flag here in the game_ui indicating + * that the player had used the hint function. I (SGT) + * retired it, on grounds of consistency with other games + * (most of these games will still flash to indicate + * completion if you solved and undid it, so why not if + * you got a hint?) and because the flash is as much about + * checking you got it all right than about congratulating + * you on a job well done. */ + } + sfree(buf); + return ret; + } + + cell = state->grid[idx(r, c, state->params.w)]; + if (cell > 0) return NULL; + + if (action == forwards) switch (cell) { + case EMPTY: return nfmtstr(40, "W,%d,%d", r, c); + case WHITE: return nfmtstr(40, "B,%d,%d", r, c); + case BLACK: return nfmtstr(40, "E,%d,%d", r, c); + } + + else if (action == backwards) switch (cell) { + case BLACK: return nfmtstr(40, "W,%d,%d", r, c); + case WHITE: return nfmtstr(40, "E,%d,%d", r, c); + case EMPTY: return nfmtstr(40, "B,%d,%d", r, c); + } + + return NULL; +} + +static int find_errors(const game_state *state, int *report) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + int *dsf; + + int r, c, i; + + int nblack = 0, any_white_cell = -1; + game_state *dup = dup_game(state); + + for (i = r = 0; r < h; ++r) + for (c = 0; c < w; ++c, ++i) { + switch (state->grid[i]) { + + case BLACK: + { + int j; + ++nblack; + for (j = 0; j < 4; ++j) { + int const rr = r + dr[j], cc = c + dc[j]; + if (out_of_bounds(rr, cc, w, h)) continue; + if (state->grid[idx(rr, cc, w)] != BLACK) continue; + if (!report) goto found_error; + report[i] = TRUE; + break; + } + } + break; + default: + { + int j, runs; + for (runs = 1, j = 0; j < 4; ++j) { + int const rr = r + dr[j], cc = c + dc[j]; + runs += runlength(rr, cc, dr[j], dc[j], state, + ~MASK(BLACK)); + } + if (!report) { + if (runs != state->grid[i]) goto found_error; + } else if (runs < state->grid[i]) report[i] = TRUE; + else { + for (runs = 1, j = 0; j < 4; ++j) { + int const rr = r + dr[j], cc = c + dc[j]; + runs += runlength(rr, cc, dr[j], dc[j], state, + ~(MASK(BLACK) | MASK(EMPTY))); + } + if (runs > state->grid[i]) report[i] = TRUE; + } + } + + /* note: fallthrough _into_ these cases */ + case EMPTY: + case WHITE: any_white_cell = i; + } + } + + /* + * Check that all the white cells form a single connected component. + */ + dsf = snew_dsf(n); + for (r = 0; r < h-1; ++r) + for (c = 0; c < w; ++c) + if (state->grid[r*w+c] != BLACK && + state->grid[(r+1)*w+c] != BLACK) + dsf_merge(dsf, r*w+c, (r+1)*w+c); + for (r = 0; r < h; ++r) + for (c = 0; c < w-1; ++c) + if (state->grid[r*w+c] != BLACK && + state->grid[r*w+(c+1)] != BLACK) + dsf_merge(dsf, r*w+c, r*w+(c+1)); + if (nblack + dsf_size(dsf, any_white_cell) < n) { + int biggest, canonical; + + if (!report) { + sfree(dsf); + goto found_error; + } + + /* + * Report this error by choosing one component to be the + * canonical one (we pick the largest, arbitrarily + * tie-breaking towards lower array indices) and highlighting + * as an error any square in a different component. + */ + canonical = -1; + biggest = 0; + for (i = 0; i < n; ++i) + if (state->grid[i] != BLACK) { + int size = dsf_size(dsf, i); + if (size > biggest) { + biggest = size; + canonical = dsf_canonify(dsf, i); + } + } + + for (i = 0; i < n; ++i) + if (state->grid[i] != BLACK && dsf_canonify(dsf, i) != canonical) + report[i] = TRUE; + } + sfree(dsf); + + free_game(dup); + return FALSE; /* if report != NULL, this is ignored */ + +found_error: + free_game(dup); + return TRUE; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + signed int r, c, value, nchars, ntok; + signed char what_to_do; + game_state *ret; + + assert (move); + + ret = dup_game(state); + + if (*move == 'S') { + ++move; + ret->has_cheated = ret->was_solved = TRUE; + } + + for (; *move; move += nchars) { + ntok = sscanf(move, "%c,%d,%d%n", &what_to_do, &r, &c, &nchars); + if (ntok < 3) goto failure; + switch (what_to_do) { + case 'W': value = WHITE; break; + case 'E': value = EMPTY; break; + case 'B': value = BLACK; break; + default: goto failure; + } + if (out_of_bounds(r, c, ret->params.w, ret->params.h)) goto failure; + ret->grid[idx(r, c, ret->params.w)] = value; + } + + if (ret->was_solved == FALSE) + ret->was_solved = !find_errors(ret, NULL); + + return ret; + +failure: + free_game(ret); + return NULL; +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +#define FLASH_TIME 0.7F + +static float game_flash_length(const game_state *from, + const game_state *to, int dir, game_ui *ui) +{ + if (!from->was_solved && to->was_solved && !to->has_cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->was_solved ? +1 : 0; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define PREFERRED_TILE_SIZE 32 + +enum { + COL_BACKGROUND = 0, + COL_GRID, + COL_BLACK = COL_GRID, + COL_TEXT = COL_GRID, + COL_USER = COL_GRID, + COL_ERROR, + COL_LOWLIGHT, + COL_HIGHLIGHT = COL_ERROR, /* mkhighlight needs it, I don't */ + COL_CURSOR = COL_LOWLIGHT, + NCOLOURS +}; + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = (1 + params->w) * tilesize; + *y = (1 + params->h) * tilesize; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +#define COLOUR(ret, i, r, g, b) \ + ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b))) + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + COLOUR(ret, COL_GRID, 0.0F, 0.0F, 0.0F); + COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F); + + *ncolours = NCOLOURS; + return ret; +} + +static drawcell makecell(puzzle_size value, int error, int cursor, int flash) +{ + drawcell ret; + setmember(ret, value); + setmember(ret, error); + setmember(ret, cursor); + setmember(ret, flash); + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->started = FALSE; + + ds->grid = snewn(n, drawcell); + for (i = 0; i < n; ++i) + ds->grid[i] = makecell(w + h, FALSE, FALSE, FALSE); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +#define cmpmember(a, b, field) ((a) . field == (b) . field) + +static int cell_eq(drawcell a, drawcell b) +{ + return + cmpmember(a, b, value) && + cmpmember(a, b, error) && + cmpmember(a, b, cursor) && + cmpmember(a, b, flash); +} + +static void draw_cell(drawing *dr, game_drawstate *ds, int r, int c, + drawcell cell); + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int const w = state->params.w, h = state->params.h, n = w * h; + int const wpx = (w+1) * ds->tilesize, hpx = (h+1) * ds->tilesize; + int const flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2; + + int r, c, i; + + int *errors = snewn(n, int); + memset(errors, FALSE, n * sizeof (int)); + find_errors(state, errors); + + assert (oldstate == NULL); /* only happens if animating moves */ + + if (!ds->started) { + ds->started = TRUE; + draw_rect(dr, 0, 0, wpx, hpx, COL_BACKGROUND); + draw_update(dr, 0, 0, wpx, hpx); + } + + for (i = r = 0; r < h; ++r) { + for (c = 0; c < w; ++c, ++i) { + drawcell cell = makecell(state->grid[i], errors[i], FALSE, flash); + if (r == ui->r && c == ui->c && ui->cursor_show) + cell.cursor = TRUE; + if (!cell_eq(cell, ds->grid[i])) { + draw_cell(dr, ds, r, c, cell); + ds->grid[i] = cell; + } + } + } + + sfree(errors); +} + +static void draw_cell(drawing *draw, game_drawstate *ds, int r, int c, + drawcell cell) +{ + int const ts = ds->tilesize; + int const y = BORDER + ts * r, x = BORDER + ts * c; + int const tx = x + (ts / 2), ty = y + (ts / 2); + int const dotsz = (ds->tilesize + 9) / 10; + + int const colour = (cell.value == BLACK ? + cell.error ? COL_ERROR : COL_BLACK : + cell.flash || cell.cursor ? + COL_LOWLIGHT : COL_BACKGROUND); + + draw_rect_outline(draw, x, y, ts + 1, ts + 1, COL_GRID); + draw_rect (draw, x + 1, y + 1, ts - 1, ts - 1, colour); + if (cell.error) + draw_rect_outline(draw, x + 1, y + 1, ts - 1, ts - 1, COL_ERROR); + + switch (cell.value) { + case WHITE: draw_rect(draw, tx - dotsz / 2, ty - dotsz / 2, dotsz, dotsz, + cell.error ? COL_ERROR : COL_USER); + case BLACK: case EMPTY: break; + default: + { + int const colour = (cell.error ? COL_ERROR : COL_GRID); + char *msg = nfmtstr(10, "%d", cell.value); + draw_text(draw, tx, ty, FONT_VARIABLE, ts * 3 / 5, + ALIGN_VCENTRE | ALIGN_HCENTRE, colour, msg); + sfree(msg); + } + } + + draw_update(draw, x, y, ts + 1, ts + 1); +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + puts("warning: game_timing_state was called (this shouldn't happen)"); + return FALSE; /* the (non-existing) timer should not be running */ +} + +/* ---------------------------------------------------------------------- + * User interface: print + */ + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int print_width, print_height; + game_compute_size(params, 800, &print_width, &print_height); + *x = print_width / 100.0F; + *y = print_height / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int const w = state->params.w, h = state->params.h; + game_drawstate ds_obj, *ds = &ds_obj; + int r, c, i, colour; + + ds->tilesize = tilesize; + + colour = print_mono_colour(dr, 1); assert(colour == COL_BACKGROUND); + colour = print_mono_colour(dr, 0); assert(colour == COL_GRID); + colour = print_mono_colour(dr, 1); assert(colour == COL_ERROR); + colour = print_mono_colour(dr, 0); assert(colour == COL_LOWLIGHT); + colour = print_mono_colour(dr, 0); assert(colour == NCOLOURS); + + for (i = r = 0; r < h; ++r) + for (c = 0; c < w; ++c, ++i) + draw_cell(dr, ds, r, c, + makecell(state->grid[i], FALSE, FALSE, FALSE)); + + print_line_width(dr, 3 * tilesize / 40); + draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, h*TILESIZE, COL_GRID); +} + +/* And that's about it ;-) **************************************************/ + +#ifdef COMBINED +#define thegame range +#endif + +struct game const thegame = { + "Range", "games.range", "range", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/rbassert.h b/apps/plugins/puzzles/rbassert.h new file mode 100644 index 0000000000..f3fef84351 --- /dev/null +++ b/apps/plugins/puzzles/rbassert.h @@ -0,0 +1,13 @@ +/* + copied from firmware/assert.h +*/ + +#undef assert + +#ifdef NDEBUG /* required by ANSI standard */ +#define assert(p) ((void)0) +#else + +#define assert(e) ((e) ? (void)0 : fatal("assertion failed %s:%d", __FILE__, __LINE__)) + +#endif /* NDEBUG */ diff --git a/apps/plugins/puzzles/rbcompat.h b/apps/plugins/puzzles/rbcompat.h new file mode 100644 index 0000000000..148aaef073 --- /dev/null +++ b/apps/plugins/puzzles/rbcompat.h @@ -0,0 +1,62 @@ +#include "plugin.h" +#include "rbassert.h" + +int sprintf_wrapper(char *str, const char *fmt, ...); +char *getenv_wrapper(const char *c); +int puts_wrapper(const char *s); +double sin_wrapper(double rads); +double cos_wrapper(double rads); +int vsprintf_wrapper(char *s, const char *fmt, va_list ap); +float fabs_wrapper(float n); +float floor_wrapper(float n); + +float atan_wrapper(float x); +float atan2_wrapper(float y, float x); +float sqrt_wrapper(float x); +long strtol_wrapper(const char *nptr, char **endptr, int base); +int64_t strtoq_wrapper(const char *nptr, char **endptr, int base); +uint64_t strtouq_wrapper(const char *nptr, char **endptr, int base); +float pow_wrapper(float x, float y); +float ceil_wrapper(float x); + +size_t strspn_wrapper(const char *s1, const char *s2); +size_t strcspn_wrapper(const char *s1, const char *s2); +int sscanf_wrapper(const char *ibuf, const char *fmt, ...); +double atof_wrapper(const char *s); +double acos_wrapper(double x); + +#define acos acos_wrapper +#define atan atan_wrapper +#define atan2 atan2_wrapper +#define atof atof_wrapper +#define atoi rb->atoi +#define atol atoi +#define calloc tlsf_calloc +#define ceil ceil_wrapper +#define cos cos_wrapper +#define fabs fabs_wrapper +#define floor floor_wrapper +#define free tlsf_free +#define getenv getenv_wrapper +#define malloc tlsf_malloc +#define memchr rb->memchr +#define pow pow_wrapper +#define printf LOGF +#define puts puts_wrapper +#define qsort rb->qsort +#define realloc tlsf_realloc +#define sin sin_wrapper +#define sprintf sprintf_wrapper +#define sqrt sqrt_wrapper +#define sscanf sscanf_wrapper +#define strcat rb->strcat +#define strchr rb->strchr +#define strcmp rb->strcmp +#define strcpy rb->strcpy +#define strcspn strcspn_wrapper +#define strlen rb->strlen +#define strspn strspn_wrapper +#define strtol strtol_wrapper +#define strtoq strtoq_wrapper +#define strtouq strtouq_wrapper +#define vsprintf vsprintf_wrapper diff --git a/apps/plugins/puzzles/rbwrappers.c b/apps/plugins/puzzles/rbwrappers.c new file mode 100644 index 0000000000..8eca1909c3 --- /dev/null +++ b/apps/plugins/puzzles/rbwrappers.c @@ -0,0 +1,2378 @@ +#include +#include "fixedpoint.h" + +int sprintf_wrapper(char *str, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int ret = rb->vsnprintf(str, 9999, fmt, ap); + va_end(ap); + return ret; +} + +char *getenv_wrapper(const char *c) +{ + return NULL; +} + +int puts_wrapper(const char *s) +{ + LOGF("%s", s); + return 0; +} + +/* fixed-point wrappers */ +double sin_wrapper(double rads) +{ + int degs = rads * 180/PI; + long fixed = fp14_sin(degs); + return fixed / (16384.0); +} + +double cos_wrapper(double rads) +{ + int degs = rads * 180/PI; + long fixed = fp14_cos(degs); + return fixed / (16384.0); +} + +int vsprintf_wrapper(char *s, const char *fmt, va_list ap) +{ + return rb->vsnprintf(s, 9999, fmt, ap); + +} + +/* Absolute value, simple calculus */ +float fabs_wrapper(float x) +{ + return (x < 0.0f) ? -x : x; +} + +float floor_wrapper(float n) +{ + if(n < 0.0f) + return ((int)n - 1); + else + return (int)n; +} + +/* Natural logarithm. + Taken from glibc-2.8 */ +static const float +ln2_hi = 6.9313812256e-01, /* 0x3f317180 */ +ln2_lo = 9.0580006145e-06, /* 0x3717f7d1 */ +two25 = 3.355443200e+07, /* 0x4c000000 */ +Lg1 = 6.6666668653e-01, /* 3F2AAAAB */ +Lg2 = 4.0000000596e-01, /* 3ECCCCCD */ +Lg3 = 2.8571429849e-01, /* 3E924925 */ +Lg4 = 2.2222198546e-01, /* 3E638E29 */ +Lg5 = 1.8183572590e-01, /* 3E3A3325 */ +Lg6 = 1.5313838422e-01, /* 3E1CD04F */ +Lg7 = 1.4798198640e-01; /* 3E178897 */ + +static const float zero = 0.0; + +/* A union which permits us to convert between a float and a 32 bit + int. */ + +typedef union +{ + float value; + uint32_t word; +} ieee_float_shape_type; + +/* Get a 32 bit int from a float. */ + +#define GET_FLOAT_WORD(i,d) \ +do { \ + ieee_float_shape_type gf_u; \ + gf_u.value = (d); \ + (i) = gf_u.word; \ +} while (0) + +/* Set a float from a 32 bit int. */ + +#define SET_FLOAT_WORD(d,i) \ +do { \ + ieee_float_shape_type sf_u; \ + sf_u.word = (i); \ + (d) = sf_u.value; \ +} while (0) + +#ifdef ROCKBOX_LITTLE_ENDIAN +#define __HI(x) *(1+(int*)&x) +#define __LO(x) *(int*)&x +#define __HIp(x) *(1+(int*)x) +#define __LOp(x) *(int*)x +#else +#define __HI(x) *(int*)&x +#define __LO(x) *(1+(int*)&x) +#define __HIp(x) *(int*)x +#define __LOp(x) *(1+(int*)x) +#endif + +static float rb_log(float x) +{ + float hfsq, f, s, z, R, w, t1, t2, dk; + int32_t k, ix, i, j; + + GET_FLOAT_WORD(ix,x); + + k=0; + if (ix < 0x00800000) { /* x < 2**-126 */ + if ((ix&0x7fffffff)==0) + return -two25/(x-x); /* log(+-0)=-inf */ + if (ix<0) return (x-x)/(x-x); /* log(-#) = NaN */ + k -= 25; x *= two25; /* subnormal number, scale up x */ + GET_FLOAT_WORD(ix,x); + } + if (ix >= 0x7f800000) return x+x; + k += (ix>>23)-127; + ix &= 0x007fffff; + i = (ix+(0x95f64<<3))&0x800000; + SET_FLOAT_WORD(x,ix|(i^0x3f800000)); /* normalize x or x/2 */ + k += (i>>23); + f = x-(float)1.0; + if((0x007fffff&(15+ix))<16) { /* |f| < 2**-20 */ + if(f==zero) { + if(k==0) + return zero; + else + { + dk=(float)k; + return dk*ln2_hi+dk*ln2_lo; + } + } + R = f*f*((float)0.5-(float)0.33333333333333333*f); + if(k==0) + return f-R; + else + { + dk=(float)k; + return dk*ln2_hi-((R-dk*ln2_lo)-f); + } + } + s = f/((float)2.0+f); + dk = (float)k; + z = s*s; + i = ix-(0x6147a<<3); + w = z*z; + j = (0x6b851<<3)-ix; + t1= w*(Lg2+w*(Lg4+w*Lg6)); + t2= z*(Lg1+w*(Lg3+w*(Lg5+w*Lg7))); + i |= j; + R = t2+t1; + if(i>0) { + hfsq=(float)0.5*f*f; + if(k==0) + return f-(hfsq-s*(hfsq+R)); + else + return dk*ln2_hi-((hfsq-(s*(hfsq+R)+dk*ln2_lo))-f); + } else { + if(k==0) + return f-s*(f-R); + else + return dk*ln2_hi-((s*(f-R)-dk*ln2_lo)-f); + } +} + +union ieee754_double + { + double d; + + /* This is the IEEE 754 double-precision format. */ + struct + { +#ifdef ROCKBOX_BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:20; + unsigned int mantissa1:32; +#else /* ROCKBOX_LITTLE_ENDIAN */ + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:20; + unsigned int exponent:11; + unsigned int negative:1; +#endif /* ROCKBOX_LITTLE_ENDIAN */ + } ieee; + + /* This format makes it easier to see if a NaN is a signalling NaN. */ + struct + { +#ifdef ROCKBOX_BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + unsigned int quiet_nan:1; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:19; + unsigned int mantissa1:32; +#else /* ROCKBOX_LITTLE_ENDIAN */ + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:19; + unsigned int quiet_nan:1; + unsigned int exponent:11; + unsigned int negative:1; +#endif /* ROCKBOX_LITTLE_ENDIAN */ + } ieee_nan; + }; + +static const volatile float TWOM100 = 7.88860905e-31; +static const volatile float TWO127 = 1.7014118346e+38; + +/* Exponential function, + taken from glibc-2.8 + As it uses double values and udefines some symbols, + it was moved to the end of the source code */ + +#define W52 (2.22044605e-16) +#define W55 (2.77555756e-17) +#define W58 (3.46944695e-18) +#define W59 (1.73472348e-18) +#define W60 (8.67361738e-19) +const float __exp_deltatable[178] = { + 0*W60, 16558714*W60, -10672149*W59, 1441652*W60, + -15787963*W55, 462888*W60, 7291806*W60, 1698880*W60, + -14375103*W58, -2021016*W60, 728829*W60, -3759654*W60, + 3202123*W60, -10916019*W58, -251570*W60, -1043086*W60, + 8207536*W60, -409964*W60, -5993931*W60, -475500*W60, + 2237522*W60, 324170*W60, -244117*W60, 32077*W60, + 123907*W60, -1019734*W60, -143*W60, 813077*W60, + 743345*W60, 462461*W60, 629794*W60, 2125066*W60, + -2339121*W60, -337951*W60, 9922067*W60, -648704*W60, + 149407*W60, -2687209*W60, -631608*W60, 2128280*W60, + -4882082*W60, 2001360*W60, 175074*W60, 2923216*W60, + -538947*W60, -1212193*W60, -1920926*W60, -1080577*W60, + 3690196*W60, 2643367*W60, 2911937*W60, 671455*W60, + -1128674*W60, 593282*W60, -5219347*W60, -1941490*W60, + 11007953*W60, 239609*W60, -2969658*W60, -1183650*W60, + 942998*W60, 699063*W60, 450569*W60, -329250*W60, + -7257875*W60, -312436*W60, 51626*W60, 555877*W60, + -641761*W60, 1565666*W60, 884327*W60, -10960035*W60, + -2004679*W60, -995793*W60, -2229051*W60, -146179*W60, + -510327*W60, 1453482*W60, -3778852*W60, -2238056*W60, + -4895983*W60, 3398883*W60, -252738*W60, 1230155*W60, + 346918*W60, 1109352*W60, 268941*W60, -2930483*W60, + -1036263*W60, -1159280*W60, 1328176*W60, 2937642*W60, + -9371420*W60, -6902650*W60, -1419134*W60, 1442904*W60, + -1319056*W60, -16369*W60, 696555*W60, -279987*W60, + -7919763*W60, 252741*W60, 459711*W60, -1709645*W60, + 354913*W60, 6025867*W60, -421460*W60, -853103*W60, + -338649*W60, 962151*W60, 955965*W60, 784419*W60, + -3633653*W60, 2277133*W60, -8847927*W52, 1223028*W60, + 5907079*W60, 623167*W60, 5142888*W60, 2599099*W60, + 1214280*W60, 4870359*W60, 593349*W60, -57705*W60, + 7761209*W60, -5564097*W60, 2051261*W60, 6216869*W60, + 4692163*W60, 601691*W60, -5264906*W60, 1077872*W60, + -3205949*W60, 1833082*W60, 2081746*W60, -987363*W60, + -1049535*W60, 2015244*W60, 874230*W60, 2168259*W60, + -1740124*W60, -10068269*W60, -18242*W60, -3013583*W60, + 580601*W60, -2547161*W60, -535689*W60, 2220815*W60, + 1285067*W60, 2806933*W60, -983086*W60, -1729097*W60, + -1162985*W60, -2561904*W60, 801988*W60, 244351*W60, + 1441893*W60, -7517981*W60, 271781*W60, -15021588*W60, + -2341588*W60, -919198*W60, 1642232*W60, 4771771*W60, + -1220099*W60, -3062372*W60, 628624*W60, 1278114*W60, + 13083513*W60, -10521925*W60, 3180310*W60, -1659307*W60, + 3543773*W60, 2501203*W60, 4151*W60, -340748*W60, + -2285625*W60, 2495202*W60 +}; + +const double __exp_atable[355] /* __attribute__((mode(DF))) */ = { + 0.707722561055888932371, /* 0x0.b52d4e46605c27ffd */ + 0.709106182438804188967, /* 0x0.b587fb96f75097ffb */ + 0.710492508843861281234, /* 0x0.b5e2d649899167ffd */ + 0.711881545564593931623, /* 0x0.b63dde74d36bdfffe */ + 0.713273297897442870573, /* 0x0.b699142f945f87ffc */ + 0.714667771153751463236, /* 0x0.b6f477909c4ea0001 */ + 0.716064970655995725059, /* 0x0.b75008aec758f8004 */ + 0.717464901723956938193, /* 0x0.b7abc7a0eea7e0002 */ + 0.718867569715736398602, /* 0x0.b807b47e1586c7ff8 */ + 0.720272979947266023271, /* 0x0.b863cf5d10e380003 */ + 0.721681137825144314297, /* 0x0.b8c01855195c37ffb */ + 0.723092048691992950199, /* 0x0.b91c8f7d213740004 */ + 0.724505717938892290800, /* 0x0.b97934ec5002d0007 */ + 0.725922150953176470431, /* 0x0.b9d608b9c92ea7ffc */ + 0.727341353138962865022, /* 0x0.ba330afcc29e98003 */ + 0.728763329918453162104, /* 0x0.ba903bcc8618b7ffc */ + 0.730188086709957051568, /* 0x0.baed9b40591ba0000 */ + 0.731615628948127705309, /* 0x0.bb4b296f931e30002 */ + 0.733045962086486091436, /* 0x0.bba8e671a05617ff9 */ + 0.734479091556371366251, /* 0x0.bc06d25dd49568001 */ + 0.735915022857225542529, /* 0x0.bc64ed4bce8f6fff9 */ + 0.737353761441304711410, /* 0x0.bcc33752f915d7ff9 */ + 0.738795312814142124419, /* 0x0.bd21b08af98e78005 */ + 0.740239682467211168593, /* 0x0.bd80590b65e9a8000 */ + 0.741686875913991849885, /* 0x0.bddf30ebec4a10000 */ + 0.743136898669507939299, /* 0x0.be3e38443c84e0007 */ + 0.744589756269486091620, /* 0x0.be9d6f2c1d32a0002 */ + 0.746045454254026796384, /* 0x0.befcd5bb59baf8004 */ + 0.747503998175051087583, /* 0x0.bf5c6c09ca84c0003 */ + 0.748965393601880857739, /* 0x0.bfbc322f5b18b7ff8 */ + 0.750429646104262104698, /* 0x0.c01c2843f776fffff */ + 0.751896761271877989160, /* 0x0.c07c4e5fa18b88002 */ + 0.753366744698445112140, /* 0x0.c0dca49a5fb18fffd */ + 0.754839601988627206827, /* 0x0.c13d2b0c444db0005 */ + 0.756315338768691947122, /* 0x0.c19de1cd798578006 */ + 0.757793960659406629066, /* 0x0.c1fec8f623723fffd */ + 0.759275473314173443536, /* 0x0.c25fe09e8a0f47ff8 */ + 0.760759882363831851927, /* 0x0.c2c128dedc88f8000 */ + 0.762247193485956486805, /* 0x0.c322a1cf7d6e7fffa */ + 0.763737412354726363781, /* 0x0.c3844b88cb9347ffc */ + 0.765230544649828092739, /* 0x0.c3e626232bd8f7ffc */ + 0.766726596071518051729, /* 0x0.c44831b719bf18002 */ + 0.768225572321911687194, /* 0x0.c4aa6e5d12d078001 */ + 0.769727479119219348810, /* 0x0.c50cdc2da64a37ffb */ + 0.771232322196981678892, /* 0x0.c56f7b41744490001 */ + 0.772740107296721268087, /* 0x0.c5d24bb1259e70004 */ + 0.774250840160724651565, /* 0x0.c6354d95640dd0007 */ + 0.775764526565368872643, /* 0x0.c6988106fec447fff */ + 0.777281172269557396602, /* 0x0.c6fbe61eb1bd0ffff */ + 0.778800783068235302750, /* 0x0.c75f7cf560942fffc */ + 0.780323364758801041312, /* 0x0.c7c345a3f1983fffe */ + 0.781848923151573727006, /* 0x0.c8274043594cb0002 */ + 0.783377464064598849602, /* 0x0.c88b6cec94b3b7ff9 */ + 0.784908993312207869935, /* 0x0.c8efcbb89cba27ffe */ + 0.786443516765346961618, /* 0x0.c9545cc0a88c70003 */ + 0.787981040257604625744, /* 0x0.c9b9201dc643bfffa */ + 0.789521569657452682047, /* 0x0.ca1e15e92a5410007 */ + 0.791065110849462849192, /* 0x0.ca833e3c1ae510005 */ + 0.792611669712891875319, /* 0x0.cae8992fd84667ffd */ + 0.794161252150049179450, /* 0x0.cb4e26ddbc207fff8 */ + 0.795713864077794763584, /* 0x0.cbb3e75f301b60003 */ + 0.797269511407239561694, /* 0x0.cc19dacd978cd8002 */ + 0.798828200086368567220, /* 0x0.cc8001427e55d7ffb */ + 0.800389937624300440456, /* 0x0.cce65ade24d360006 */ + 0.801954725261124767840, /* 0x0.cd4ce7a5de839fffb */ + 0.803522573691593189330, /* 0x0.cdb3a7c79a678fffd */ + 0.805093487311204114563, /* 0x0.ce1a9b563965ffffc */ + 0.806667472122675088819, /* 0x0.ce81c26b838db8000 */ + 0.808244534127439906441, /* 0x0.cee91d213f8428002 */ + 0.809824679342317166307, /* 0x0.cf50ab9144d92fff9 */ + 0.811407913793616542005, /* 0x0.cfb86dd5758c2ffff */ + 0.812994243520784198882, /* 0x0.d0206407c20e20005 */ + 0.814583674571603966162, /* 0x0.d0888e4223facfff9 */ + 0.816176213022088536960, /* 0x0.d0f0ec9eb3f7c8002 */ + 0.817771864936188586101, /* 0x0.d1597f377d6768002 */ + 0.819370636400374108252, /* 0x0.d1c24626a46eafff8 */ + 0.820972533518165570298, /* 0x0.d22b41865ff1e7ff9 */ + 0.822577562404315121269, /* 0x0.d2947170f32ec7ff9 */ + 0.824185729164559344159, /* 0x0.d2fdd60097795fff8 */ + 0.825797039949601741075, /* 0x0.d3676f4fb796d0001 */ + 0.827411500902565544264, /* 0x0.d3d13d78b5f68fffb */ + 0.829029118181348834154, /* 0x0.d43b40960546d8001 */ + 0.830649897953322891022, /* 0x0.d4a578c222a058000 */ + 0.832273846408250750368, /* 0x0.d50fe617a3ba78005 */ + 0.833900969738858188772, /* 0x0.d57a88b1218e90002 */ + 0.835531274148056613016, /* 0x0.d5e560a94048f8006 */ + 0.837164765846411529371, /* 0x0.d6506e1aac8078003 */ + 0.838801451086016225394, /* 0x0.d6bbb1204074e0001 */ + 0.840441336100884561780, /* 0x0.d72729d4c28518004 */ + 0.842084427144139224814, /* 0x0.d792d8530e12b0001 */ + 0.843730730487052604790, /* 0x0.d7febcb61273e7fff */ + 0.845380252404570153833, /* 0x0.d86ad718c308dfff9 */ + 0.847032999194574087728, /* 0x0.d8d727962c69d7fff */ + 0.848688977161248581090, /* 0x0.d943ae49621ce7ffb */ + 0.850348192619261200615, /* 0x0.d9b06b4d832ef8005 */ + 0.852010651900976245816, /* 0x0.da1d5ebdc22220005 */ + 0.853676361342631029337, /* 0x0.da8a88b555baa0006 */ + 0.855345327311054837175, /* 0x0.daf7e94f965f98004 */ + 0.857017556155879489641, /* 0x0.db6580a7c98f7fff8 */ + 0.858693054267390953857, /* 0x0.dbd34ed9617befff8 */ + 0.860371828028939855647, /* 0x0.dc4153ffc8b65fff9 */ + 0.862053883854957292436, /* 0x0.dcaf90368bfca8004 */ + 0.863739228154875360306, /* 0x0.dd1e0399328d87ffe */ + 0.865427867361348468455, /* 0x0.dd8cae435d303fff9 */ + 0.867119807911702289458, /* 0x0.ddfb9050b1cee8006 */ + 0.868815056264353846599, /* 0x0.de6aa9dced8448001 */ + 0.870513618890481399881, /* 0x0.ded9fb03db7320006 */ + 0.872215502247877139094, /* 0x0.df4983e1380657ff8 */ + 0.873920712852848668986, /* 0x0.dfb94490ffff77ffd */ + 0.875629257204025623884, /* 0x0.e0293d2f1cb01fff9 */ + 0.877341141814212965880, /* 0x0.e0996dd786fff0007 */ + 0.879056373217612985183, /* 0x0.e109d6a64f5d57ffc */ + 0.880774957955916648615, /* 0x0.e17a77b78e72a7ffe */ + 0.882496902590150900078, /* 0x0.e1eb5127722cc7ff8 */ + 0.884222213673356738383, /* 0x0.e25c63121fb0c8006 */ + 0.885950897802399772740, /* 0x0.e2cdad93ec5340003 */ + 0.887682961567391237685, /* 0x0.e33f30c925fb97ffb */ + 0.889418411575228162725, /* 0x0.e3b0ecce2d05ffff9 */ + 0.891157254447957902797, /* 0x0.e422e1bf727718006 */ + 0.892899496816652704641, /* 0x0.e4950fb9713fc7ffe */ + 0.894645145323828439008, /* 0x0.e50776d8b0e60fff8 */ + 0.896394206626591749641, /* 0x0.e57a1739c8fadfffc */ + 0.898146687421414902124, /* 0x0.e5ecf0f97c5798007 */ + 0.899902594367530173098, /* 0x0.e660043464e378005 */ + 0.901661934163603406867, /* 0x0.e6d3510747e150006 */ + 0.903424713533971135418, /* 0x0.e746d78f06cd97ffd */ + 0.905190939194458810123, /* 0x0.e7ba97e879c91fffc */ + 0.906960617885092856864, /* 0x0.e82e92309390b0007 */ + 0.908733756358986566306, /* 0x0.e8a2c6845544afffa */ + 0.910510361377119825629, /* 0x0.e9173500c8abc7ff8 */ + 0.912290439722343249336, /* 0x0.e98bddc30f98b0002 */ + 0.914073998177417412765, /* 0x0.ea00c0e84bc4c7fff */ + 0.915861043547953501680, /* 0x0.ea75de8db8094fffe */ + 0.917651582652244779397, /* 0x0.eaeb36d09d3137ffe */ + 0.919445622318405764159, /* 0x0.eb60c9ce4ed3dffff */ + 0.921243169397334638073, /* 0x0.ebd697a43995b0007 */ + 0.923044230737526172328, /* 0x0.ec4ca06fc7768fffa */ + 0.924848813220121135342, /* 0x0.ecc2e44e865b6fffb */ + 0.926656923710931002014, /* 0x0.ed39635df34e70006 */ + 0.928468569126343790092, /* 0x0.edb01dbbc2f5b7ffa */ + 0.930283756368834757725, /* 0x0.ee2713859aab57ffa */ + 0.932102492359406786818, /* 0x0.ee9e44d9342870004 */ + 0.933924784042873379360, /* 0x0.ef15b1d4635438005 */ + 0.935750638358567643520, /* 0x0.ef8d5a94f60f50007 */ + 0.937580062297704630580, /* 0x0.f0053f38f345cffff */ + 0.939413062815381727516, /* 0x0.f07d5fde3a2d98001 */ + 0.941249646905368053689, /* 0x0.f0f5bca2d481a8004 */ + 0.943089821583810716806, /* 0x0.f16e55a4e497d7ffe */ + 0.944933593864477061592, /* 0x0.f1e72b028a2827ffb */ + 0.946780970781518460559, /* 0x0.f2603cd9fb5430001 */ + 0.948631959382661205081, /* 0x0.f2d98b497d2a87ff9 */ + 0.950486566729423554277, /* 0x0.f353166f63e3dffff */ + 0.952344799896018723290, /* 0x0.f3ccde6a11ae37ffe */ + 0.954206665969085765512, /* 0x0.f446e357f66120000 */ + 0.956072172053890279009, /* 0x0.f4c12557964f0fff9 */ + 0.957941325265908139014, /* 0x0.f53ba48781046fffb */ + 0.959814132734539637840, /* 0x0.f5b66106555d07ffa */ + 0.961690601603558903308, /* 0x0.f6315af2c2027fffc */ + 0.963570739036113010927, /* 0x0.f6ac926b8aeb80004 */ + 0.965454552202857141381, /* 0x0.f728078f7c5008002 */ + 0.967342048278315158608, /* 0x0.f7a3ba7d66a908001 */ + 0.969233234469444204768, /* 0x0.f81fab543e1897ffb */ + 0.971128118008140250896, /* 0x0.f89bda33122c78007 */ + 0.973026706099345495256, /* 0x0.f9184738d4cf97ff8 */ + 0.974929006031422851235, /* 0x0.f994f284d3a5c0008 */ + 0.976835024947348973265, /* 0x0.fa11dc35bc7820002 */ + 0.978744770239899142285, /* 0x0.fa8f046b4fb7f8007 */ + 0.980658249138918636210, /* 0x0.fb0c6b449ab1cfff9 */ + 0.982575468959622777535, /* 0x0.fb8a10e1088fb7ffa */ + 0.984496437054508843888, /* 0x0.fc07f5602d79afffc */ + 0.986421160608523028820, /* 0x0.fc8618e0e55e47ffb */ + 0.988349647107594098099, /* 0x0.fd047b83571b1fffa */ + 0.990281903873210800357, /* 0x0.fd831d66f4c018002 */ + 0.992217938695037382475, /* 0x0.fe01fead3320bfff8 */ + 0.994157757657894713987, /* 0x0.fe811f703491e8006 */ + 0.996101369488558541238, /* 0x0.ff007fd5744490005 */ + 0.998048781093141101932, /* 0x0.ff801ffa9b9280007 */ + 1.000000000000000000000, /* 0x1.00000000000000000 */ + 1.001955033605393285965, /* 0x1.0080200565d29ffff */ + 1.003913889319761887310, /* 0x1.0100802aa0e80fff0 */ + 1.005876574715736104818, /* 0x1.01812090377240007 */ + 1.007843096764807100351, /* 0x1.020201541aad7fff6 */ + 1.009813464316352327214, /* 0x1.0283229c4c9820007 */ + 1.011787683565730677817, /* 0x1.030484836910a000e */ + 1.013765762469146736174, /* 0x1.0386272b9c077fffe */ + 1.015747708536026694351, /* 0x1.04080ab526304fff0 */ + 1.017733529475172815584, /* 0x1.048a2f412375ffff0 */ + 1.019723232714418781378, /* 0x1.050c94ef7ad5e000a */ + 1.021716825883923762690, /* 0x1.058f3be0f1c2d0004 */ + 1.023714316605201180057, /* 0x1.06122436442e2000e */ + 1.025715712440059545995, /* 0x1.06954e0fec63afff2 */ + 1.027721021151397406936, /* 0x1.0718b98f41c92fff6 */ + 1.029730250269221158939, /* 0x1.079c66d49bb2ffff1 */ + 1.031743407506447551857, /* 0x1.082056011a9230009 */ + 1.033760500517691527387, /* 0x1.08a487359ebd50002 */ + 1.035781537016238873464, /* 0x1.0928fa93490d4fff3 */ + 1.037806524719013578963, /* 0x1.09adb03b3e5b3000d */ + 1.039835471338248051878, /* 0x1.0a32a84e9e5760004 */ + 1.041868384612101516848, /* 0x1.0ab7e2eea5340ffff */ + 1.043905272300907460835, /* 0x1.0b3d603ca784f0009 */ + 1.045946142174331239262, /* 0x1.0bc3205a042060000 */ + 1.047991002016745332165, /* 0x1.0c4923682a086fffe */ + 1.050039859627715177527, /* 0x1.0ccf698898f3a000d */ + 1.052092722826109660856, /* 0x1.0d55f2dce5d1dfffb */ + 1.054149599440827866881, /* 0x1.0ddcbf86b09a5fff6 */ + 1.056210497317612961855, /* 0x1.0e63cfa7abc97fffd */ + 1.058275424318780855142, /* 0x1.0eeb23619c146fffb */ + 1.060344388322010722446, /* 0x1.0f72bad65714bffff */ + 1.062417397220589476718, /* 0x1.0ffa9627c38d30004 */ + 1.064494458915699715017, /* 0x1.1082b577d0eef0003 */ + 1.066575581342167566880, /* 0x1.110b18e893a90000a */ + 1.068660772440545025953, /* 0x1.1193c09c267610006 */ + 1.070750040138235936705, /* 0x1.121cacb4959befff6 */ + 1.072843392435016474095, /* 0x1.12a5dd543cf36ffff */ + 1.074940837302467588937, /* 0x1.132f529d59552000b */ + 1.077042382749654914030, /* 0x1.13b90cb250d08fff5 */ + 1.079148036789447484528, /* 0x1.14430bb58da3dfff9 */ + 1.081257807444460983297, /* 0x1.14cd4fc984c4a000e */ + 1.083371702785017154417, /* 0x1.1557d910df9c7000e */ + 1.085489730853784307038, /* 0x1.15e2a7ae292d30002 */ + 1.087611899742884524772, /* 0x1.166dbbc422d8c0004 */ + 1.089738217537583819804, /* 0x1.16f9157586772ffff */ + 1.091868692357631731528, /* 0x1.1784b4e533cacfff0 */ + 1.094003332327482702577, /* 0x1.18109a360fc23fff2 */ + 1.096142145591650907149, /* 0x1.189cc58b155a70008 */ + 1.098285140311341168136, /* 0x1.1929370751ea50002 */ + 1.100432324652149906842, /* 0x1.19b5eecdd79cefff0 */ + 1.102583706811727015711, /* 0x1.1a42ed01dbdba000e */ + 1.104739294993289488947, /* 0x1.1ad031c69a2eafff0 */ + 1.106899097422573863281, /* 0x1.1b5dbd3f66e120003 */ + 1.109063122341542140286, /* 0x1.1beb8f8fa8150000b */ + 1.111231377994659874592, /* 0x1.1c79a8dac6ad0fff4 */ + 1.113403872669181282605, /* 0x1.1d0809445a97ffffc */ + 1.115580614653132185460, /* 0x1.1d96b0effc9db000e */ + 1.117761612217810673898, /* 0x1.1e25a001332190000 */ + 1.119946873713312474002, /* 0x1.1eb4d69bdb2a9fff1 */ + 1.122136407473298902480, /* 0x1.1f4454e3bfae00006 */ + 1.124330221845670330058, /* 0x1.1fd41afcbb48bfff8 */ + 1.126528325196519908506, /* 0x1.2064290abc98c0001 */ + 1.128730725913251964394, /* 0x1.20f47f31c9aa7000f */ + 1.130937432396844410880, /* 0x1.21851d95f776dfff0 */ + 1.133148453059692917203, /* 0x1.2216045b6784efffa */ + 1.135363796355857157764, /* 0x1.22a733a6692ae0004 */ + 1.137583470716100553249, /* 0x1.2338ab9b3221a0004 */ + 1.139807484614418608939, /* 0x1.23ca6c5e27aadfff7 */ + 1.142035846532929888057, /* 0x1.245c7613b7f6c0004 */ + 1.144268564977221958089, /* 0x1.24eec8e06b035000c */ + 1.146505648458203463465, /* 0x1.258164e8cea85fff8 */ + 1.148747105501412235671, /* 0x1.26144a5180d380009 */ + 1.150992944689175123667, /* 0x1.26a7793f5de2efffa */ + 1.153243174560058870217, /* 0x1.273af1d712179000d */ + 1.155497803703682491111, /* 0x1.27ceb43d81d42fff1 */ + 1.157756840726344771440, /* 0x1.2862c097a3d29000c */ + 1.160020294239811677834, /* 0x1.28f7170a74cf4fff1 */ + 1.162288172883275239058, /* 0x1.298bb7bb0faed0004 */ + 1.164560485298402170388, /* 0x1.2a20a2ce920dffff4 */ + 1.166837240167474476460, /* 0x1.2ab5d86a4631ffff6 */ + 1.169118446164539637555, /* 0x1.2b4b58b36d5220009 */ + 1.171404112007080167155, /* 0x1.2be123cf786790002 */ + 1.173694246390975415341, /* 0x1.2c7739e3c0aac000d */ + 1.175988858069749065617, /* 0x1.2d0d9b15deb58fff6 */ + 1.178287955789017793514, /* 0x1.2da4478b627040002 */ + 1.180591548323240091978, /* 0x1.2e3b3f69fb794fffc */ + 1.182899644456603782686, /* 0x1.2ed282d76421d0004 */ + 1.185212252993012693694, /* 0x1.2f6a11f96c685fff3 */ + 1.187529382762033236513, /* 0x1.3001ecf60082ffffa */ + 1.189851042595508889847, /* 0x1.309a13f30f28a0004 */ + 1.192177241354644978669, /* 0x1.31328716a758cfff7 */ + 1.194507987909589896687, /* 0x1.31cb4686e1e85fffb */ + 1.196843291137896336843, /* 0x1.32645269dfd04000a */ + 1.199183159977805113226, /* 0x1.32fdaae604c39000f */ + 1.201527603343041317132, /* 0x1.339750219980dfff3 */ + 1.203876630171082595692, /* 0x1.3431424300e480007 */ + 1.206230249419600664189, /* 0x1.34cb8170b3fee000e */ + 1.208588470077065268869, /* 0x1.35660dd14dbd4fffc */ + 1.210951301134513435915, /* 0x1.3600e78b6bdfc0005 */ + 1.213318751604272271958, /* 0x1.369c0ec5c38ebfff2 */ + 1.215690830512196507537, /* 0x1.373783a718d29000f */ + 1.218067546930756250870, /* 0x1.37d3465662f480007 */ + 1.220448909901335365929, /* 0x1.386f56fa770fe0008 */ + 1.222834928513994334780, /* 0x1.390bb5ba5fc540004 */ + 1.225225611877684750397, /* 0x1.39a862bd3c7a8fff3 */ + 1.227620969111500981433, /* 0x1.3a455e2a37bcafffd */ + 1.230021009336254911271, /* 0x1.3ae2a8287dfbefff6 */ + 1.232425741726685064472, /* 0x1.3b8040df76f39fffa */ + 1.234835175450728295084, /* 0x1.3c1e287682e48fff1 */ + 1.237249319699482263931, /* 0x1.3cbc5f151b86bfff8 */ + 1.239668183679933477545, /* 0x1.3d5ae4e2cc0a8000f */ + 1.242091776620540377629, /* 0x1.3df9ba07373bf0006 */ + 1.244520107762172811399, /* 0x1.3e98deaa0d8cafffe */ + 1.246953186383919165383, /* 0x1.3f3852f32973efff0 */ + 1.249391019292643401078, /* 0x1.3fd816ffc72b90001 */ + 1.251833623164381181797, /* 0x1.40782b17863250005 */ + 1.254280999953110153911, /* 0x1.41188f42caf400000 */ + 1.256733161434815393410, /* 0x1.41b943b42945bfffd */ + 1.259190116985283935980, /* 0x1.425a4893e5f10000a */ + 1.261651875958665236542, /* 0x1.42fb9e0a2df4c0009 */ + 1.264118447754797758244, /* 0x1.439d443f608c4fff9 */ + 1.266589841787181258708, /* 0x1.443f3b5bebf850008 */ + 1.269066067469190262045, /* 0x1.44e183883e561fff7 */ + 1.271547134259576328224, /* 0x1.45841cecf7a7a0001 */ + 1.274033051628237434048, /* 0x1.462707b2c43020009 */ + 1.276523829025464573684, /* 0x1.46ca44023aa410007 */ + 1.279019475999373156531, /* 0x1.476dd2045d46ffff0 */ + 1.281520002043128991825, /* 0x1.4811b1e1f1f19000b */ + 1.284025416692967214122, /* 0x1.48b5e3c3edd74fff4 */ + 1.286535729509738823464, /* 0x1.495a67d3613c8fff7 */ + 1.289050950070396384145, /* 0x1.49ff3e396e19d000b */ + 1.291571087985403654081, /* 0x1.4aa4671f5b401fff1 */ + 1.294096152842774794011, /* 0x1.4b49e2ae56d19000d */ + 1.296626154297237043484, /* 0x1.4befb10fd84a3fff4 */ + 1.299161101984141142272, /* 0x1.4c95d26d41d84fff8 */ + 1.301701005575179204100, /* 0x1.4d3c46f01d9f0fff3 */ + 1.304245874766450485904, /* 0x1.4de30ec21097d0003 */ + 1.306795719266019562007, /* 0x1.4e8a2a0ccce3d0002 */ + 1.309350548792467483458, /* 0x1.4f3198fa10346fff5 */ + 1.311910373099227200545, /* 0x1.4fd95bb3be8cffffd */ + 1.314475201942565174546, /* 0x1.50817263bf0e5fffb */ + 1.317045045107389400535, /* 0x1.5129dd3418575000e */ + 1.319619912422941299109, /* 0x1.51d29c4f01c54ffff */ + 1.322199813675649204855, /* 0x1.527bafde83a310009 */ + 1.324784758729532718739, /* 0x1.5325180cfb8b3fffd */ + 1.327374757430096474625, /* 0x1.53ced504b2bd0fff4 */ + 1.329969819671041886272, /* 0x1.5478e6f02775e0001 */ + 1.332569955346704748651, /* 0x1.55234df9d8a59fff8 */ + 1.335175174370685002822, /* 0x1.55ce0a4c5a6a9fff6 */ + 1.337785486688218616860, /* 0x1.56791c1263abefff7 */ + 1.340400902247843806217, /* 0x1.57248376aef21fffa */ + 1.343021431036279800211, /* 0x1.57d040a420c0bfff3 */ + 1.345647083048053138662, /* 0x1.587c53c5a630f0002 */ + 1.348277868295411074918, /* 0x1.5928bd063fd7bfff9 */ + 1.350913796821875845231, /* 0x1.59d57c9110ad60006 */ + 1.353554878672557082439, /* 0x1.5a8292913d68cfffc */ + 1.356201123929036356254, /* 0x1.5b2fff3212db00007 */ + 1.358852542671913132777, /* 0x1.5bddc29edcc06fff3 */ + 1.361509145047255398051, /* 0x1.5c8bdd032ed16000f */ + 1.364170941142184734180, /* 0x1.5d3a4e8a5bf61fff4 */ + 1.366837941171020309735, /* 0x1.5de9176042f1effff */ + 1.369510155261156381121, /* 0x1.5e9837b062f4e0005 */ + 1.372187593620959988833, /* 0x1.5f47afa69436cfff1 */ + 1.374870266463378287715, /* 0x1.5ff77f6eb3f8cfffd */ + 1.377558184010425845733, /* 0x1.60a7a734a9742fff9 */ + 1.380251356531521533853, /* 0x1.6158272490016000c */ + 1.382949794301995272203, /* 0x1.6208ff6a8978a000f */ + 1.385653507605306700170, /* 0x1.62ba3032c0a280004 */ + 1.388362506772382154503, /* 0x1.636bb9a994784000f */ + 1.391076802081129493127, /* 0x1.641d9bfb29a7bfff6 */ + 1.393796403973427855412, /* 0x1.64cfd7545928b0002 */ + 1.396521322756352656542, /* 0x1.65826be167badfff8 */ + 1.399251568859207761660, /* 0x1.663559cf20826000c */ + 1.401987152677323100733, /* 0x1.66e8a14a29486fffc */ + 1.404728084651919228815, /* 0x1.679c427f5a4b6000b */ + 1.407474375243217723560, /* 0x1.68503d9ba0add000f */ + 1.410226034922914983815, /* 0x1.690492cbf6303fff9 */ + 1.412983074197955213304, /* 0x1.69b9423d7b548fff6 */ +}; + +/* All floating-point numbers can be put in one of these categories. */ +enum + { + FP_NAN, +# define FP_NAN FP_NAN + FP_INFINITE, +# define FP_INFINITE FP_INFINITE + FP_ZERO, +# define FP_ZERO FP_ZERO + FP_SUBNORMAL, +# define FP_SUBNORMAL FP_SUBNORMAL + FP_NORMAL +# define FP_NORMAL FP_NORMAL + }; + + +int +__fpclassifyf (float x) +{ + uint32_t wx; + int retval = FP_NORMAL; + + GET_FLOAT_WORD (wx, x); + wx &= 0x7fffffff; + if (wx == 0) + retval = FP_ZERO; + else if (wx < 0x800000) + retval = FP_SUBNORMAL; + else if (wx >= 0x7f800000) + retval = wx > 0x7f800000 ? FP_NAN : FP_INFINITE; + + return retval; +} + + +int +__isinff (float x) +{ + int32_t ix,t; + GET_FLOAT_WORD(ix,x); + t = ix & 0x7fffffff; + t ^= 0x7f800000; + t |= -t; + return ~(t >> 31) & (ix >> 30); +} + +/* Return nonzero value if arguments are unordered. */ +#define fpclassify(x) \ + (sizeof (x) == sizeof (float) ? __fpclassifyf (x) : __fpclassifyf (x)) + +#ifndef isunordered +#define isunordered(u, v) \ + (__extension__ \ + ({ __typeof__(u) __u = (u); __typeof__(v) __v = (v); \ + fpclassify (__u) == FP_NAN || fpclassify (__v) == FP_NAN; })) +#endif + +/* Return nonzero value if X is less than Y. */ +#ifndef isless +#define isless(x, y) \ + (__extension__ \ + ({ __typeof__(x) __x = (x); __typeof__(y) __y = (y); \ + !isunordered (__x, __y) && __x < __y; })) +#endif + +/* Return nonzero value if X is greater than Y. */ +#ifndef isgreater +#define isgreater(x, y) \ + (__extension__ \ + ({ __typeof__(x) __x = (x); __typeof__(y) __y = (y); \ + !isunordered (__x, __y) && __x > __y; })) +#endif + +float rb_exp(float x) +{ + static const float himark = 88.72283935546875; + static const float lomark = -103.972084045410; + /* Check for usual case. */ + if (isless (x, himark) && isgreater (x, lomark)) + { + static const float THREEp42 = 13194139533312.0; + static const float THREEp22 = 12582912.0; + /* 1/ln(2). */ +#undef M_1_LN2 + static const float M_1_LN2 = 1.44269502163f; + /* ln(2) */ +#undef M_LN2 + static const double M_LN2 = .6931471805599452862; + + int tval; + double x22, t, result, dx; + float n, delta; + union ieee754_double ex2_u; +#ifndef ROCKBOX + fenv_t oldenv; + + feholdexcept (&oldenv); +#endif + +#ifdef FE_TONEAREST + fesetround (FE_TONEAREST); +#endif + + /* Calculate n. */ + n = x * M_1_LN2 + THREEp22; + n -= THREEp22; + dx = x - n*M_LN2; + + /* Calculate t/512. */ + t = dx + THREEp42; + t -= THREEp42; + dx -= t; + + /* Compute tval = t. */ + tval = (int) (t * 512.0); + + if (t >= 0) + delta = - __exp_deltatable[tval]; + else + delta = __exp_deltatable[-tval]; + + /* Compute ex2 = 2^n e^(t/512+delta[t]). */ + ex2_u.d = __exp_atable[tval+177]; + ex2_u.ieee.exponent += (int) n; + + /* Approximate e^(dx+delta) - 1, using a second-degree polynomial, + with maximum error in [-2^-10-2^-28,2^-10+2^-28] + less than 5e-11. */ + x22 = (0.5000000496709180453 * dx + 1.0000001192102037084) * dx + delta; + + /* Return result. */ +#ifndef ROCKBOX + fesetenv (&oldenv); +#endif + + result = x22 * ex2_u.d + ex2_u.d; + return (float) result; + } + /* Exceptional cases: */ + else if (isless (x, himark)) + { + if (__isinff (x)) + /* e^-inf == 0, with no error. */ + return 0; + else + /* Underflow */ + return TWOM100 * TWOM100; + } + else + /* Return x, if x is a NaN or Inf; or overflow, otherwise. */ + return TWO127*x; +} + +/* Arc tangent, + taken from glibc-2.8. */ + +static const float atanhi[] = { + 4.6364760399e-01, /* atan(0.5)hi 0x3eed6338 */ + 7.8539812565e-01, /* atan(1.0)hi 0x3f490fda */ + 9.8279368877e-01, /* atan(1.5)hi 0x3f7b985e */ + 1.5707962513e+00, /* atan(inf)hi 0x3fc90fda */ +}; + +static const float atanlo[] = { + 5.0121582440e-09, /* atan(0.5)lo 0x31ac3769 */ + 3.7748947079e-08, /* atan(1.0)lo 0x33222168 */ + 3.4473217170e-08, /* atan(1.5)lo 0x33140fb4 */ + 7.5497894159e-08, /* atan(inf)lo 0x33a22168 */ +}; + +static const float aT[] = { + 3.3333334327e-01, /* 0x3eaaaaaa */ + -2.0000000298e-01, /* 0xbe4ccccd */ + 1.4285714924e-01, /* 0x3e124925 */ + -1.1111110449e-01, /* 0xbde38e38 */ + 9.0908870101e-02, /* 0x3dba2e6e */ + -7.6918758452e-02, /* 0xbd9d8795 */ + 6.6610731184e-02, /* 0x3d886b35 */ + -5.8335702866e-02, /* 0xbd6ef16b */ + 4.9768779427e-02, /* 0x3d4bda59 */ + -3.6531571299e-02, /* 0xbd15a221 */ + 1.6285819933e-02, /* 0x3c8569d7 */ +}; + +static const float +huge = 1.0e+30, +tiny = 1.0e-30, +one = 1.0f; + +float atan_wrapper(float x) +{ + float w,s1,s2,z; + int32_t ix,hx,id; + + GET_FLOAT_WORD(hx,x); + ix = hx&0x7fffffff; + if(ix>=0x50800000) { /* if |x| >= 2^34 */ + if(ix>0x7f800000) + return x+x; /* NaN */ + if(hx>0) return atanhi[3]+atanlo[3]; + else return -atanhi[3]-atanlo[3]; + } if (ix < 0x3ee00000) { /* |x| < 0.4375 */ + if (ix < 0x31000000) { /* |x| < 2^-29 */ + if(huge+x>one) return x; /* raise inexact */ + } + id = -1; + } else { + x = fabs_wrapper(x); + if (ix < 0x3f980000) { /* |x| < 1.1875 */ + if (ix < 0x3f300000) { /* 7/16 <=|x|<11/16 */ + id = 0; x = ((float)2.0*x-one)/((float)2.0+x); + } else { /* 11/16<=|x|< 19/16 */ + id = 1; x = (x-one)/(x+one); + } + } else { + if (ix < 0x401c0000) { /* |x| < 2.4375 */ + id = 2; x = (x-(float)1.5)/(one+(float)1.5*x); + } else { /* 2.4375 <= |x| < 2^66 */ + id = 3; x = -(float)1.0/x; + } + }} + /* end of argument reduction */ + z = x*x; + w = z*z; + /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ + s1 = z*(aT[0]+w*(aT[2]+w*(aT[4]+w*(aT[6]+w*(aT[8]+w*aT[10]))))); + s2 = w*(aT[1]+w*(aT[3]+w*(aT[5]+w*(aT[7]+w*aT[9])))); + if (id<0) return x - x*(s1+s2); + else { + z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); + return (hx<0)? -z:z; + } +} + +/* Arc tangent from two variables, original. */ + +static const float +pi_o_4 = 7.8539818525e-01, /* 0x3f490fdb */ +pi_o_2 = 1.5707963705e+00, /* 0x3fc90fdb */ +pi = 3.1415927410e+00, /* 0x40490fdb */ +pi_lo = -8.7422776573e-08; /* 0xb3bbbd2e */ + +float atan2_wrapper(float y, float x) +{ + float z; + int32_t k,m,hx,hy,ix,iy; + + GET_FLOAT_WORD(hx,x); + ix = hx&0x7fffffff; + GET_FLOAT_WORD(hy,y); + iy = hy&0x7fffffff; + if((ix>0x7f800000)|| + (iy>0x7f800000)) /* x or y is NaN */ + return x+y; + if(hx==0x3f800000) return atan_wrapper(y); /* x=1.0 */ + m = ((hy>>31)&1)|((hx>>30)&2); /* 2*sign(x)+sign(y) */ + + /* when y = 0 */ + if(iy==0) { + switch(m) { + case 0: + case 1: return y; /* atan(+-0,+anything)=+-0 */ + case 2: return pi+tiny;/* atan(+0,-anything) = pi */ + case 3: return -pi-tiny;/* atan(-0,-anything) =-pi */ + } + } + /* when x = 0 */ + if(ix==0) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* when x is INF */ + if(ix==0x7f800000) { + if(iy==0x7f800000) { + switch(m) { + case 0: return pi_o_4+tiny;/* atan(+INF,+INF) */ + case 1: return -pi_o_4-tiny;/* atan(-INF,+INF) */ + case 2: return (float)3.0*pi_o_4+tiny;/*atan(+INF,-INF)*/ + case 3: return (float)-3.0*pi_o_4-tiny;/*atan(-INF,-INF)*/ + } + } else { + switch(m) { + case 0: return zero ; /* atan(+...,+INF) */ + case 1: return -zero ; /* atan(-...,+INF) */ + case 2: return pi+tiny ; /* atan(+...,-INF) */ + case 3: return -pi-tiny ; /* atan(-...,-INF) */ + } + } + } + /* when y is INF */ + if(iy==0x7f800000) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* compute y/x */ + k = (iy-ix)>>23; + if(k > 60) z=pi_o_2+(float)0.5*pi_lo; /* |y/x| > 2**60 */ + else if(hx<0&&k<-60) z=0.0; /* |y|/x < -2**60 */ + else z=atan_wrapper(fabs_wrapper(y/x)); /* safe to do y/x */ + switch (m) { + case 0: return z ; /* atan(+,+) */ + case 1: { + uint32_t zh; + GET_FLOAT_WORD(zh,z); + SET_FLOAT_WORD(z,zh ^ 0x80000000); + } + return z ; /* atan(-,+) */ + case 2: return pi-(z-pi_lo);/* atan(+,-) */ + default: /* case 3 */ + return (z-pi_lo)-pi;/* atan(-,-) */ + } +} + +/* Square root function, original. */ +float sqrt_wrapper(float x) +{ + float z; + int32_t sign = (int)0x80000000; + int32_t ix,s,q,m,t,i; + uint32_t r; + + GET_FLOAT_WORD(ix,x); + + /* take care of Inf and NaN */ + if((ix&0x7f800000)==0x7f800000) { + return x*x+x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf + sqrt(-inf)=sNaN */ + } + /* take care of zero */ + if(ix<=0) { + if((ix&(~sign))==0) return x;/* sqrt(+-0) = +-0 */ + else if(ix<0) + return (x-x)/(x-x); /* sqrt(-ve) = sNaN */ + } + /* normalize x */ + m = (ix>>23); + if(m==0) { /* subnormal x */ + for(i=0;(ix&0x00800000)==0;i++) ix<<=1; + m -= i-1; + } + m -= 127; /* unbias exponent */ + ix = (ix&0x007fffff)|0x00800000; + if(m&1) /* odd m, double x to make it even */ + ix += ix; + m >>= 1; /* m = [m/2] */ + + /* generate sqrt(x) bit by bit */ + ix += ix; + q = s = 0; /* q = sqrt(x) */ + r = 0x01000000; /* r = moving bit from right to left */ + + while(r!=0) { + t = s+r; + if(t<=ix) { + s = t+r; + ix -= t; + q += r; + } + ix += ix; + r>>=1; + } + + /* use floating add to find out rounding direction */ + if(ix!=0) { + z = one-tiny; /* trigger inexact flag */ + if (z>=one) { + z = one+tiny; + if (z>one) + q += 2; + else + q += (q&1); + } + } + ix = (q>>1)+0x3f000000; + ix += (m <<23); + SET_FLOAT_WORD(z,ix); + return z; +} + +/* @(#)e_acos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_acos(x) + * Method : + * acos(x) = pi/2 - asin(x) + * acos(-x) = pi/2 + asin(x) + * For |x|<=0.5 + * acos(x) = pi/2 - (x + x*x^2*R(x^2)) (see asin.c) + * For x>0.5 + * acos(x) = pi/2 - (pi/2 - 2asin(sqrt((1-x)/2))) + * = 2asin(sqrt((1-x)/2)) + * = 2s + 2s*z*R(z) ...z=(1-x)/2, s=sqrt(z) + * = 2f + (2c + 2s*z*R(z)) + * where f=hi part of s, and c = (z-f*f)/(s+f) is the correction term + * for f so that f+c ~ sqrt(z). + * For x<-0.5 + * acos(x) = pi - 2asin(sqrt((1-|x|)/2)) + * = pi - 0.5*(s+s*z*R(z)), where z=(1-|x|)/2,s=sqrt(z) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + * Function needed: sqrt + */ + +#ifdef __STDC__ +static const double +#else +static double +#endif +pio2_hi = 1.57079632679489655800e+00, /* 0x3FF921FB, 0x54442D18 */ +pio2_lo = 6.12323399573676603587e-17, /* 0x3C91A626, 0x33145C07 */ +pS0 = 1.66666666666666657415e-01, /* 0x3FC55555, 0x55555555 */ +pS1 = -3.25565818622400915405e-01, /* 0xBFD4D612, 0x03EB6F7D */ +pS2 = 2.01212532134862925881e-01, /* 0x3FC9C155, 0x0E884455 */ +pS3 = -4.00555345006794114027e-02, /* 0xBFA48228, 0xB5688F3B */ +pS4 = 7.91534994289814532176e-04, /* 0x3F49EFE0, 0x7501B288 */ +pS5 = 3.47933107596021167570e-05, /* 0x3F023DE1, 0x0DFDF709 */ +qS1 = -2.40339491173441421878e+00, /* 0xC0033A27, 0x1C8A2D4B */ +qS2 = 2.02094576023350569471e+00, /* 0x40002AE5, 0x9C598AC8 */ +qS3 = -6.88283971605453293030e-01, /* 0xBFE6066C, 0x1B8D0159 */ +qS4 = 7.70381505559019352791e-02; /* 0x3FB3B8C5, 0xB12E9282 */ + +double acos_wrapper(double x) +{ + double z,p,q,r,w,s,c,df; + int hx,ix; + hx = __HI(x); + ix = hx&0x7fffffff; + if(ix>=0x3ff00000) { /* |x| >= 1 */ + if(((ix-0x3ff00000)|__LO(x))==0) { /* |x|==1 */ + if(hx>0) return 0.0; /* acos(1) = 0 */ + else return pi+2.0*pio2_lo; /* acos(-1)= pi */ + } + return (x-x)/(x-x); /* acos(|x|>1) is NaN */ + } + if(ix<0x3fe00000) { /* |x| < 0.5 */ + if(ix<=0x3c600000) return pio2_hi+pio2_lo;/*if|x|<2**-57*/ + z = x*x; + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + r = p/q; + return pio2_hi - (x - (pio2_lo-x*r)); + } else if (hx<0) { /* x < -0.5 */ + z = (one+x)*0.5; + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + s = sqrt_wrapper(z); + r = p/q; + w = r*s-pio2_lo; + return pi - 2.0*(s+w); + } else { /* x > 0.5 */ + z = (one-x)*0.5; + s = sqrt_wrapper(z); + df = s; + __LO(df) = 0; + c = (z-df*df)/(s+df); + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + r = p/q; + w = r*s+c; + return 2.0*(df+w); + } + +} + +/* + * Copyright (C) 2004 by egnite Software GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY EGNITE SOFTWARE GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL EGNITE + * SOFTWARE GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * For additional information see http://www.ethernut.de/ + * + *- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * $Log$ + * Revision 1.4 2008/02/15 17:13:01 haraldkipp + * Use configurable constant attribute. + * + * Revision 1.3 2006/10/08 16:48:08 haraldkipp + * Documentation fixed + * + * Revision 1.2 2005/08/02 17:46:47 haraldkipp + * Major API documentation update. + * + * Revision 1.1 2004/09/08 10:23:35 haraldkipp + * Generic C stdlib added + * + */ + +#define CONST const +long strtol_wrapper(CONST char *nptr, char **endptr, int base) +{ + register CONST char *s; + register long acc, cutoff; + register int c; + register int neg, any, cutlim; + + /* + * Skip white space and pick up leading +/- sign if any. + * If base is 0, allow 0x for hex and 0 for octal, else + * assume decimal; if base is already 16, allow 0x. + */ + s = nptr; + do { + c = (unsigned char) *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + /* + * Compute the cutoff value between legal numbers and illegal + * numbers. That is the largest legal value, divided by the + * base. An input number that is greater than this value, if + * followed by a legal input character, is too big. One that + * is equal to this value may be valid or not; the limit + * between valid and invalid numbers is then based on the last + * digit. For instance, if the range for longs is + * [-2147483648..2147483647] and the input base is 10, + * cutoff will be set to 214748364 and cutlim to either + * 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated + * a value > 214748364, or equal but the next digit is > 7 (or 8), + * the number is too big, and we will return a range error. + * + * Set any if any `digits' consumed; make it negative to indicate + * overflow. + */ + cutoff = neg ? LONG_MIN : LONG_MAX; + cutlim = cutoff % base; + cutoff /= base; + if (neg) { + if (cutlim > 0) { + cutlim -= base; + cutoff += 1; + } + cutlim = -cutlim; + } + for (acc = 0, any = 0;; c = (unsigned char) *s++) { + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0) + continue; + if (neg) { + if ((acc < cutoff || acc == cutoff) && c > cutlim) { + any = -1; + acc = LONG_MIN; + } else { + any = 1; + acc *= base; + acc -= c; + } + } else { + if ((acc > cutoff || acc == cutoff) && c > cutlim) { + any = -1; + acc = LONG_MAX; + } else { + any = 1; + acc *= base; + acc += c; + } + } + } + if (endptr != 0) + *endptr = (char *) (any ? s - 1 : nptr); + return (acc); +} + +int64_t strtoq_wrapper(CONST char *nptr, char **endptr, int base) +{ + return strtol(nptr, endptr, base); +} + +uint64_t strtouq_wrapper(CONST char *nptr, char **endptr, int base) +{ + return strtol(nptr, endptr, base); +} + +/* Power function, taken from glibc-2.8 and dietlibc-0.32 */ +float pow_wrapper(float x, float y) +{ + unsigned int e; + float result; + + /* Special cases 0^x */ + if(x == 0.0f) + { + if(y > 0.0f) + return 0.0f; + else if(y == 0.0f) + return 1.0f; + else + return 1.0f / x; + } + + /* Special case x^n where n is integer */ + if(y == (int) (e = (int) y)) + { + if((int) e < 0) + { + e = -e; + x = 1.0f / x; + } + + result = 1.0f; + + while(1) + { + if(e & 1) + result *= x; + + if((e >>= 1) == 0) + break; + + x *= x; + } + + return result; + } + + /* Normal case */ + return rb_exp(rb_log(x) * y); +} + +/* @(#)s_copysign.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * copysign(double x, double y) + * copysign(x,y) returns a value with the magnitude of x and + * with the sign bit of y. + */ + +double copysign_wrapper(double x, double y) +{ + __HI(x) = (__HI(x)&0x7fffffff)|(__HI(y)&0x80000000); + return x; +} + +/* @(#)s_scalbn.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * scalbn (double x, int n) + * scalbn(x,n) returns x* 2**n computed by exponent + * manipulation rather than by actually performing an + * exponentiation or a multiplication. + */ + +#ifdef __STDC__ +static const double +#else +static double +#endif +two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ + twom54 = 5.55111512312578270212e-17; /* 0x3C900000, 0x00000000 */ + +double scalbn_wrapper (double x, int n) +{ + int k,hx,lx; + hx = __HI(x); + lx = __LO(x); + k = (hx&0x7ff00000)>>20; /* extract exponent */ + if (k==0) { /* 0 or subnormal x */ + if ((lx|(hx&0x7fffffff))==0) return x; /* +-0 */ + x *= two54; + hx = __HI(x); + k = ((hx&0x7ff00000)>>20) - 54; + if (n< -50000) return tiny*x; /*underflow*/ + } + if (k==0x7ff) return x+x; /* NaN or Inf */ + k = k+n; + if (k > 0x7fe) return huge*copysign_wrapper(huge,x); /* overflow */ + if (k > 0) /* normal result */ + {__HI(x) = (hx&0x800fffff)|(k<<20); return x;} + if (k <= -54) + if (n > 50000) /* in case integer overflow in n+k */ + return huge*copysign_wrapper(huge,x); /*overflow*/ + else return tiny*copysign_wrapper(tiny,x); /*underflow*/ + k += 54; /* subnormal result */ + __HI(x) = (hx&0x800fffff)|(k<<20); + return x*twom54; +} + +/* horrible hack */ +float ceil_wrapper(float x) +{ + return floor_wrapper(x) + 1.0; +} + +/* Implementation of strtod() and atof(), + taken from SanOS (http://www.jbox.dk/sanos/). */ +static int rb_errno = 0; + +static double rb_strtod(const char *str, char **endptr) +{ + double number; + int exponent; + int negative; + char *p = (char *) str; + double p10; + int n; + int num_digits; + int num_decimals; + + /* Reset Rockbox errno -- W.B. */ +#ifdef ROCKBOX + rb_errno = 0; +#endif + + // Skip leading whitespace + while (isspace(*p)) p++; + + // Handle optional sign + negative = 0; + switch (*p) + { + case '-': negative = 1; // Fall through to increment position + case '+': p++; + } + + number = 0.; + exponent = 0; + num_digits = 0; + num_decimals = 0; + + // Process string of digits + while (isdigit(*p)) + { + number = number * 10. + (*p - '0'); + p++; + num_digits++; + } + + // Process decimal part + if (*p == '.') + { + p++; + + while (isdigit(*p)) + { + number = number * 10. + (*p - '0'); + p++; + num_digits++; + num_decimals++; + } + + exponent -= num_decimals; + } + + if (num_digits == 0) + { +#ifdef ROCKBOX + rb_errno = 1; +#else + errno = ERANGE; +#endif + return 0.0; + } + + // Correct for sign + if (negative) number = -number; + + // Process an exponent string + if (*p == 'e' || *p == 'E') + { + // Handle optional sign + negative = 0; + switch(*++p) + { + case '-': negative = 1; // Fall through to increment pos + case '+': p++; + } + + // Process string of digits + n = 0; + while (isdigit(*p)) + { + n = n * 10 + (*p - '0'); + p++; + } + + if (negative) + exponent -= n; + else + exponent += n; + } + +#ifndef ROCKBOX + if (exponent < DBL_MIN_EXP || exponent > DBL_MAX_EXP) + { + errno = ERANGE; + return HUGE_VAL; + } +#endif + + // Scale the result + p10 = 10.; + n = exponent; + if (n < 0) n = -n; + while (n) + { + if (n & 1) + { + if (exponent < 0) + number /= p10; + else + number *= p10; + } + n >>= 1; + p10 *= p10; + } + +#ifndef ROCKBOX + if (number == HUGE_VAL) errno = ERANGE; +#endif + if (endptr) *endptr = p; + + return number; +} + +double atof_wrapper(const char *str) +{ + return rb_strtod(str, NULL); +} + +/* + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "rbcompat.h" + +#define BUF 32 /* Maximum length of numeric string. */ + +/* + * Flags used during conversion. + */ +#define LONG 0x01 /* l: long or double */ +#define SHORT 0x04 /* h: short */ +#define SUPPRESS 0x08 /* *: suppress assignment */ +#define POINTER 0x10 /* p: void * (as hex) */ +#define NOSKIP 0x20 /* [ or c: do not skip blanks */ +#define LONGLONG 0x400 /* ll: long long (+ deprecated q: quad) */ +#define SHORTSHORT 0x4000 /* hh: char */ +#define UNSIGNED 0x8000 /* %[oupxX] conversions */ + +/* + * The following are used in numeric conversions only: + * SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point; + * SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral. + */ +#define SIGNOK 0x40 /* +/- is (still) legal */ +#define NDIGITS 0x80 /* no digits detected */ + +#define DPTOK 0x100 /* (float) decimal point is still legal */ +#define EXPOK 0x200 /* (float) exponent (e+3, etc) still legal */ + +#define PFXOK 0x100 /* 0x prefix is (still) legal */ +#define NZDIGITS 0x200 /* no zero digits detected */ + +/* + * Conversion types. + */ +#define CT_CHAR 0 /* %c conversion */ +#define CT_CCL 1 /* %[...] conversion */ +#define CT_STRING 2 /* %s conversion */ +#define CT_INT 3 /* %[dioupxX] conversion */ + +typedef unsigned char u_char; +typedef uint64_t u_quad_t; + +static const u_char *__sccl(char *, const u_char *); + +void bcopy(const void *src, void *dst, size_t n) +{ + memmove(dst, src, n); +} + +int +sscanf_wrapper(const char *ibuf, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vsscanf(ibuf, fmt, ap); + va_end(ap); + return(ret); +} + +int +vsscanf(const char *inp, char const *fmt0, va_list ap) +{ + int inr; + const u_char *fmt = (const u_char *)fmt0; + int c; /* character from format, or conversion */ + size_t width; /* field width, or 0 */ + char *p; /* points into all kinds of strings */ + int n; /* handy integer */ + int flags; /* flags as defined above */ + char *p0; /* saves original value of p when necessary */ + int nassigned; /* number of fields assigned */ + int nconversions; /* number of conversions */ + int nread; /* number of characters consumed from fp */ + int base; /* base argument to conversion function */ + char ccltab[256]; /* character class table for %[...] */ + char buf[BUF]; /* buffer for numeric conversions */ + + /* `basefix' is used to avoid `if' tests in the integer scanner */ + static short basefix[17] = + { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + + inr = strlen(inp); + + nassigned = 0; + nconversions = 0; + nread = 0; + base = 0; /* XXX just to keep gcc happy */ + for (;;) { + c = *fmt++; + if (c == 0) + return (nassigned); + if (isspace(c)) { + while (inr > 0 && isspace(*inp)) + nread++, inr--, inp++; + continue; + } + if (c != '%') + goto literal; + width = 0; + flags = 0; + /* + * switch on the format. continue if done; + * break once format type is derived. + */ +again: c = *fmt++; + switch (c) { + case '%': +literal: + if (inr <= 0) + goto input_failure; + if (*inp != c) + goto match_failure; + inr--, inp++; + nread++; + continue; + + case '*': + flags |= SUPPRESS; + goto again; + case 'l': + if (flags & LONG) { + flags &= ~LONG; + flags |= LONGLONG; + } else + flags |= LONG; + goto again; + case 'q': + flags |= LONGLONG; /* not quite */ + goto again; + case 'h': + if (flags & SHORT) { + flags &= ~SHORT; + flags |= SHORTSHORT; + } else + flags |= SHORT; + goto again; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + width = width * 10 + c - '0'; + goto again; + + /* + * Conversions. + */ + case 'd': + c = CT_INT; + base = 10; + break; + + case 'i': + c = CT_INT; + base = 0; + break; + + case 'o': + c = CT_INT; + flags |= UNSIGNED; + base = 8; + break; + + case 'u': + c = CT_INT; + flags |= UNSIGNED; + base = 10; + break; + + case 'X': + case 'x': + flags |= PFXOK; /* enable 0x prefixing */ + c = CT_INT; + flags |= UNSIGNED; + base = 16; + break; + + case 's': + c = CT_STRING; + break; + + case '[': + fmt = __sccl(ccltab, fmt); + flags |= NOSKIP; + c = CT_CCL; + break; + + case 'c': + flags |= NOSKIP; + c = CT_CHAR; + break; + + case 'p': /* pointer format is like hex */ + flags |= POINTER | PFXOK; + c = CT_INT; + flags |= UNSIGNED; + base = 16; + break; + + case 'n': + nconversions++; + if (flags & SUPPRESS) /* ??? */ + continue; + if (flags & SHORTSHORT) + *va_arg(ap, char *) = nread; + else if (flags & SHORT) + *va_arg(ap, short *) = nread; + else if (flags & LONG) + *va_arg(ap, long *) = nread; + else if (flags & LONGLONG) + *va_arg(ap, long long *) = nread; + else + *va_arg(ap, int *) = nread; + continue; + } + + /* + * We have a conversion that requires input. + */ + if (inr <= 0) + goto input_failure; + + /* + * Consume leading white space, except for formats + * that suppress this. + */ + if ((flags & NOSKIP) == 0) { + while (isspace(*inp)) { + nread++; + if (--inr > 0) + inp++; + else + goto input_failure; + } + /* + * Note that there is at least one character in + * the buffer, so conversions that do not set NOSKIP + * can no longer result in an input failure. + */ + } + + /* + * Do the conversion. + */ + switch (c) { + + case CT_CHAR: + /* scan arbitrary characters (sets NOSKIP) */ + if (width == 0) + width = 1; + if (flags & SUPPRESS) { + size_t sum = 0; + for (;;) { + if ((n = inr) < (int)width) { + sum += n; + width -= n; + inp += n; + if (sum == 0) + goto input_failure; + break; + } else { + sum += width; + inr -= width; + inp += width; + break; + } + } + nread += sum; + } else { + bcopy(inp, va_arg(ap, char *), width); + inr -= width; + inp += width; + nread += width; + nassigned++; + } + nconversions++; + break; + + case CT_CCL: + /* scan a (nonempty) character class (sets NOSKIP) */ + if (width == 0) + width = (size_t)~0; /* `infinity' */ + /* take only those things in the class */ + if (flags & SUPPRESS) { + n = 0; + while (ccltab[(unsigned char)*inp]) { + n++, inr--, inp++; + if (--width == 0) + break; + if (inr <= 0) { + if (n == 0) + goto input_failure; + break; + } + } + if (n == 0) + goto match_failure; + } else { + p0 = p = va_arg(ap, char *); + while (ccltab[(unsigned char)*inp]) { + inr--; + *p++ = *inp++; + if (--width == 0) + break; + if (inr <= 0) { + if (p == p0) + goto input_failure; + break; + } + } + n = p - p0; + if (n == 0) + goto match_failure; + *p = 0; + nassigned++; + } + nread += n; + nconversions++; + break; + + case CT_STRING: + /* like CCL, but zero-length string OK, & no NOSKIP */ + if (width == 0) + width = (size_t)~0; + if (flags & SUPPRESS) { + n = 0; + while (!isspace(*inp)) { + n++, inr--, inp++; + if (--width == 0) + break; + if (inr <= 0) + break; + } + nread += n; + } else { + p0 = p = va_arg(ap, char *); + while (!isspace(*inp)) { + inr--; + *p++ = *inp++; + if (--width == 0) + break; + if (inr <= 0) + break; + } + *p = 0; + nread += p - p0; + nassigned++; + } + nconversions++; + continue; + + case CT_INT: + /* scan an integer as if by the conversion function */ +#ifdef hardway + if (width == 0 || width > sizeof(buf) - 1) + width = sizeof(buf) - 1; +#else + /* size_t is unsigned, hence this optimisation */ + if (--width > sizeof(buf) - 2) + width = sizeof(buf) - 2; + width++; +#endif + flags |= SIGNOK | NDIGITS | NZDIGITS; + for (p = buf; width; width--) { + c = *inp; + /* + * Switch on the character; `goto ok' + * if we accept it as a part of number. + */ + switch (c) { + + /* + * The digit 0 is always legal, but is + * special. For %i conversions, if no + * digits (zero or nonzero) have been + * scanned (only signs), we will have + * base==0. In that case, we should set + * it to 8 and enable 0x prefixing. + * Also, if we have not scanned zero digits + * before this, do not turn off prefixing + * (someone else will turn it off if we + * have scanned any nonzero digits). + */ + case '0': + if (base == 0) { + base = 8; + flags |= PFXOK; + } + if (flags & NZDIGITS) + flags &= ~(SIGNOK|NZDIGITS|NDIGITS); + else + flags &= ~(SIGNOK|PFXOK|NDIGITS); + goto ok; + + /* 1 through 7 always legal */ + case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + base = basefix[base]; + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* digits 8 and 9 ok iff decimal or hex */ + case '8': case '9': + base = basefix[base]; + if (base <= 8) + break; /* not legal here */ + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* letters ok iff hex */ + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + /* no need to fix base here */ + if (base <= 10) + break; /* not legal here */ + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* sign ok only as first character */ + case '+': case '-': + if (flags & SIGNOK) { + flags &= ~SIGNOK; + goto ok; + } + break; + + /* x ok iff flag still set & 2nd char */ + case 'x': case 'X': + if (flags & PFXOK && p == buf + 1) { + base = 16; /* if %i */ + flags &= ~PFXOK; + goto ok; + } + break; + } + + /* + * If we got here, c is not a legal character + * for a number. Stop accumulating digits. + */ + break; + ok: + /* + * c is legal: store it and look at the next. + */ + *p++ = c; + if (--inr > 0) + inp++; + else + break; /* end of input */ + } + /* + * If we had only a sign, it is no good; push + * back the sign. If the number ends in `x', + * it was [sign] '0' 'x', so push back the x + * and treat it as [sign] '0'. + */ + if (flags & NDIGITS) { + if (p > buf) { + inp--; + inr++; + } + goto match_failure; + } + c = ((u_char *)p)[-1]; + if (c == 'x' || c == 'X') { + --p; + inp--; + inr++; + } + if ((flags & SUPPRESS) == 0) { + u_quad_t res; + + *p = 0; + if ((flags & UNSIGNED) == 0) + res = strtoq(buf, (char **)NULL, base); + else + res = strtouq(buf, (char **)NULL, base); + if (flags & POINTER) + *va_arg(ap, void **) = + (void *)(uintptr_t)res; + else if (flags & SHORTSHORT) + *va_arg(ap, char *) = res; + else if (flags & SHORT) + *va_arg(ap, short *) = res; + else if (flags & LONG) + *va_arg(ap, long *) = res; + else if (flags & LONGLONG) + *va_arg(ap, long long *) = res; + else + *va_arg(ap, int *) = res; + nassigned++; + } + nread += p - buf; + nconversions++; + break; + + } + } +input_failure: + return (nconversions != 0 ? nassigned : -1); +match_failure: + return (nassigned); +} + +/* + * Fill in the given table from the scanset at the given format + * (just after `['). Return a pointer to the character past the + * closing `]'. The table has a 1 wherever characters should be + * considered part of the scanset. + */ +static const u_char * +__sccl(char *tab, const u_char *fmt) +{ + int c, n, v; + + /* first `clear' the whole table */ + c = *fmt++; /* first char hat => negated scanset */ + if (c == '^') { + v = 1; /* default => accept */ + c = *fmt++; /* get new first char */ + } else + v = 0; /* default => reject */ + + /* XXX: Will not work if sizeof(tab*) > sizeof(char) */ + (void) memset(tab, v, 256); + + if (c == 0) + return (fmt - 1);/* format ended before closing ] */ + + /* + * Now set the entries corresponding to the actual scanset + * to the opposite of the above. + * + * The first character may be ']' (or '-') without being special; + * the last character may be '-'. + */ + v = 1 - v; + for (;;) { + tab[c] = v; /* take character c */ +doswitch: + n = *fmt++; /* and examine the next */ + switch (n) { + + case 0: /* format ended too soon */ + return (fmt - 1); + + case '-': + /* + * A scanset of the form + * [01+-] + * is defined as `the digit 0, the digit 1, + * the character +, the character -', but + * the effect of a scanset such as + * [a-zA-Z0-9] + * is implementation defined. The V7 Unix + * scanf treats `a-z' as `the letters a through + * z', but treats `a-a' as `the letter a, the + * character -, and the letter a'. + * + * For compatibility, the `-' is not considerd + * to define a range if the character following + * it is either a close bracket (required by ANSI) + * or is not numerically greater than the character + * we just stored in the table (c). + */ + n = *fmt; + if (n == ']' || n < c) { + c = '-'; + break; /* resume the for(;;) */ + } + fmt++; + /* fill in the range */ + do { + tab[++c] = v; + } while (c < n); + c = n; + /* + * Alas, the V7 Unix scanf also treats formats + * such as [a-c-e] as `the letters a through e'. + * This too is permitted by the standard.... + */ + goto doswitch; + break; + + case ']': /* end of scanset */ + return (fmt); + + default: /* just another character */ + c = n; + break; + } + } + /* NOTREACHED */ +} + +/* + * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * The contents of this file constitute Original Code as defined in and + * are subject to the Apple Public Source License Version 1.1 (the + * "License"). You may not use this file except in compliance with the + * License. Please obtain a copy of the License at + * http://www.apple.com/publicsource and read it before using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the + * License for the specific language governing rights and limitations + * under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "plugin.h" + +/* + * Span the complement of string s2. + */ +size_t +strcspn_wrapper(const char *s1, const char *s2) +{ + register const char *p, *spanp; + register char c, sc; + + /* + * Stop as soon as we find any character from s2. Note that there + * must be a NUL in s2; it suffices to stop when we find that, too. + */ + for (p = s1;;) { + c = *p++; + spanp = s2; + do { + if ((sc = *spanp++) == c) + return (p - 1 - s1); + } while (sc != 0); + } + /* NOTREACHED */ +} + +/* + * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * The contents of this file constitute Original Code as defined in and + * are subject to the Apple Public Source License Version 1.1 (the + * "License"). You may not use this file except in compliance with the + * License. Please obtain a copy of the License at + * http://www.apple.com/publicsource and read it before using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the + * License for the specific language governing rights and limitations + * under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "rbcompat.h" + +/* + * Span the string s2 (skip characters that are in s2). + */ +size_t +strspn_wrapper(const char *s1, const char *s2) +{ + register const char *p = s1, *spanp; + register char c, sc; + + /* + * Skip any characters in s2, excluding the terminating \0. + */ +cont: + c = *p++; + for (spanp = s2; (sc = *spanp++) != 0;) + if (sc == c) + goto cont; + return (p - 1 - s1); +} diff --git a/apps/plugins/puzzles/rect.R b/apps/plugins/puzzles/rect.R new file mode 100644 index 0000000000..1448c0fa63 --- /dev/null +++ b/apps/plugins/puzzles/rect.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +rect : [X] GTK COMMON rect rect-icon|no-icon + +rect : [G] WINDOWS COMMON rect rect.res|noicon.res + +ALL += rect[COMBINED] + +!begin am gtk +GAMES += rect +!end + +!begin >list.c + A(rect) \ +!end + +!begin >gamedesc.txt +rect:rect.exe:Rectangles:Rectangles puzzle:Divide the grid into rectangles with areas equal to the numbers. +!end diff --git a/apps/plugins/puzzles/rect.c b/apps/plugins/puzzles/rect.c new file mode 100644 index 0000000000..ca3cb984ea --- /dev/null +++ b/apps/plugins/puzzles/rect.c @@ -0,0 +1,3000 @@ +/* + * rect.c: Puzzle from nikoli.co.jp. You have a square grid with + * numbers in some squares; you must divide the square grid up into + * variously sized rectangles, such that every rectangle contains + * exactly one numbered square and the area of each rectangle is + * equal to the number contained in it. + */ + +/* + * TODO: + * + * - Improve singleton removal. + * + It would be nice to limit the size of the generated + * rectangles in accordance with existing constraints such as + * the maximum rectangle size and the one about not + * generating a rectangle the full width or height of the + * grid. + * + This could be achieved by making a less random choice + * about which of the available options to use. + * + Alternatively, we could create our rectangle and then + * split it up. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + COL_CORRECT, + COL_LINE, + COL_TEXT, + COL_GRID, + COL_DRAG, COL_DRAGERASE, + COL_CURSOR, + NCOLOURS +}; + +struct game_params { + int w, h; + float expandfactor; + int unique; +}; + +#define INDEX(state, x, y) (((y) * (state)->w) + (x)) +#define index(state, a, x, y) ((a) [ INDEX(state,x,y) ]) +#define grid(state,x,y) index(state, (state)->grid, x, y) +#define vedge(state,x,y) index(state, (state)->vedge, x, y) +#define hedge(state,x,y) index(state, (state)->hedge, x, y) + +#define CRANGE(state,x,y,dx,dy) ( (x) >= dx && (x) < (state)->w && \ + (y) >= dy && (y) < (state)->h ) +#define RANGE(state,x,y) CRANGE(state,x,y,0,0) +#define HRANGE(state,x,y) CRANGE(state,x,y,0,1) +#define VRANGE(state,x,y) CRANGE(state,x,y,1,0) + +#define PREFERRED_TILE_SIZE 24 +#define TILE_SIZE (ds->tilesize) +#ifdef SMALL_SCREEN +#define BORDER (2) +#else +#define BORDER (TILE_SIZE * 3 / 4) +#endif + +#define CORNER_TOLERANCE 0.15F +#define CENTRE_TOLERANCE 0.15F + +#define FLASH_TIME 0.13F + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE ) + +struct game_state { + int w, h; + int *grid; /* contains the numbers */ + unsigned char *vedge; /* (w+1) x h */ + unsigned char *hedge; /* w x (h+1) */ + int completed, cheated; + unsigned char *correct; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 7; + ret->expandfactor = 0.0F; + ret->unique = TRUE; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + int w, h; + char buf[80]; + + switch (i) { + case 0: w = 7, h = 7; break; + case 1: w = 9, h = 9; break; + case 2: w = 11, h = 11; break; + case 3: w = 13, h = 13; break; + case 4: w = 15, h = 15; break; +#ifndef SMALL_SCREEN + case 5: w = 17, h = 17; break; + case 6: w = 19, h = 19; break; +#endif + default: return FALSE; + } + + sprintf(buf, "%dx%d", w, h); + *name = dupstr(buf); + *params = ret = snew(game_params); + ret->w = w; + ret->h = h; + ret->expandfactor = 0.0F; + ret->unique = TRUE; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'e') { + string++; + ret->expandfactor = (float)atof(string); + while (*string && + (*string == '.' || isdigit((unsigned char)*string))) string++; + } + if (*string == 'a') { + string++; + ret->unique = FALSE; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + if (full && params->expandfactor) + sprintf(data + strlen(data), "e%g", params->expandfactor); + if (full && !params->unique) + strcat(data, "a"); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Expansion factor"; + ret[2].type = C_STRING; + sprintf(buf, "%g", params->expandfactor); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "Ensure unique solution"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = params->unique; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->expandfactor = (float)atof(cfg[2].sval); + ret->unique = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w <= 0 || params->h <= 0) + return "Width and height must both be greater than zero"; + if (params->w*params->h < 2) + return "Grid area must be greater than one"; + if (params->expandfactor < 0.0F) + return "Expansion factor may not be negative"; + return NULL; +} + +struct point { + int x, y; +}; + +struct rect { + int x, y; + int w, h; +}; + +struct rectlist { + struct rect *rects; + int n; +}; + +struct numberdata { + int area; + int npoints; + struct point *points; +}; + +/* ---------------------------------------------------------------------- + * Solver for Rectangles games. + * + * This solver is souped up beyond the needs of actually _solving_ + * a puzzle. It is also designed to cope with uncertainty about + * where the numbers have been placed. This is because I run it on + * my generated grids _before_ placing the numbers, and have it + * tell me where I need to place the numbers to ensure a unique + * solution. + */ + +static void remove_rect_placement(int w, int h, + struct rectlist *rectpositions, + int *overlaps, + int rectnum, int placement) +{ + int x, y, xx, yy; + +#ifdef SOLVER_DIAGNOSTICS + printf("ruling out rect %d placement at %d,%d w=%d h=%d\n", rectnum, + rectpositions[rectnum].rects[placement].x, + rectpositions[rectnum].rects[placement].y, + rectpositions[rectnum].rects[placement].w, + rectpositions[rectnum].rects[placement].h); +#endif + + /* + * Decrement each entry in the overlaps array to reflect the + * removal of this rectangle placement. + */ + for (yy = 0; yy < rectpositions[rectnum].rects[placement].h; yy++) { + y = yy + rectpositions[rectnum].rects[placement].y; + for (xx = 0; xx < rectpositions[rectnum].rects[placement].w; xx++) { + x = xx + rectpositions[rectnum].rects[placement].x; + + assert(overlaps[(rectnum * h + y) * w + x] != 0); + + if (overlaps[(rectnum * h + y) * w + x] > 0) + overlaps[(rectnum * h + y) * w + x]--; + } + } + + /* + * Remove the placement from the list of positions for that + * rectangle, by interchanging it with the one on the end. + */ + if (placement < rectpositions[rectnum].n - 1) { + struct rect t; + + t = rectpositions[rectnum].rects[rectpositions[rectnum].n - 1]; + rectpositions[rectnum].rects[rectpositions[rectnum].n - 1] = + rectpositions[rectnum].rects[placement]; + rectpositions[rectnum].rects[placement] = t; + } + rectpositions[rectnum].n--; +} + +static void remove_number_placement(int w, int h, struct numberdata *number, + int index, int *rectbyplace) +{ + /* + * Remove the entry from the rectbyplace array. + */ + rectbyplace[number->points[index].y * w + number->points[index].x] = -1; + + /* + * Remove the placement from the list of candidates for that + * number, by interchanging it with the one on the end. + */ + if (index < number->npoints - 1) { + struct point t; + + t = number->points[number->npoints - 1]; + number->points[number->npoints - 1] = number->points[index]; + number->points[index] = t; + } + number->npoints--; +} + +/* + * Returns 0 for failure to solve due to inconsistency; 1 for + * success; 2 for failure to complete a solution due to either + * ambiguity or it being too difficult. + */ +static int rect_solver(int w, int h, int nrects, struct numberdata *numbers, + unsigned char *hedge, unsigned char *vedge, + random_state *rs) +{ + struct rectlist *rectpositions; + int *overlaps, *rectbyplace, *workspace; + int i, ret; + + /* + * Start by setting up a list of candidate positions for each + * rectangle. + */ + rectpositions = snewn(nrects, struct rectlist); + for (i = 0; i < nrects; i++) { + int rw, rh, area = numbers[i].area; + int j, minx, miny, maxx, maxy; + struct rect *rlist; + int rlistn, rlistsize; + + /* + * For each rectangle, begin by finding the bounding + * rectangle of its candidate number placements. + */ + maxx = maxy = -1; + minx = w; + miny = h; + for (j = 0; j < numbers[i].npoints; j++) { + if (minx > numbers[i].points[j].x) minx = numbers[i].points[j].x; + if (miny > numbers[i].points[j].y) miny = numbers[i].points[j].y; + if (maxx < numbers[i].points[j].x) maxx = numbers[i].points[j].x; + if (maxy < numbers[i].points[j].y) maxy = numbers[i].points[j].y; + } + + /* + * Now loop over all possible rectangle placements + * overlapping a point within that bounding rectangle; + * ensure each one actually contains a candidate number + * placement, and add it to the list. + */ + rlist = NULL; + rlistn = rlistsize = 0; + + for (rw = 1; rw <= area && rw <= w; rw++) { + int x, y; + + if (area % rw) + continue; + rh = area / rw; + if (rh > h) + continue; + + for (y = miny - rh + 1; y <= maxy; y++) { + if (y < 0 || y+rh > h) + continue; + + for (x = minx - rw + 1; x <= maxx; x++) { + if (x < 0 || x+rw > w) + continue; + + /* + * See if we can find a candidate number + * placement within this rectangle. + */ + for (j = 0; j < numbers[i].npoints; j++) + if (numbers[i].points[j].x >= x && + numbers[i].points[j].x < x+rw && + numbers[i].points[j].y >= y && + numbers[i].points[j].y < y+rh) + break; + + if (j < numbers[i].npoints) { + /* + * Add this to the list of candidate + * placements for this rectangle. + */ + if (rlistn >= rlistsize) { + rlistsize = rlistn + 32; + rlist = sresize(rlist, rlistsize, struct rect); + } + rlist[rlistn].x = x; + rlist[rlistn].y = y; + rlist[rlistn].w = rw; + rlist[rlistn].h = rh; +#ifdef SOLVER_DIAGNOSTICS + printf("rect %d [area %d]: candidate position at" + " %d,%d w=%d h=%d\n", + i, area, x, y, rw, rh); +#endif + rlistn++; + } + } + } + } + + rectpositions[i].rects = rlist; + rectpositions[i].n = rlistn; + } + + /* + * Next, construct a multidimensional array tracking how many + * candidate positions for each rectangle overlap each square. + * + * Indexing of this array is by the formula + * + * overlaps[(rectindex * h + y) * w + x] + * + * A positive or zero value indicates what it sounds as if it + * should; -1 indicates that this square _cannot_ be part of + * this rectangle; and -2 indicates that it _definitely_ is + * (which is distinct from 1, because one might very well know + * that _if_ square S is part of rectangle R then it must be + * because R is placed in a certain position without knowing + * that it definitely _is_). + */ + overlaps = snewn(nrects * w * h, int); + memset(overlaps, 0, nrects * w * h * sizeof(int)); + for (i = 0; i < nrects; i++) { + int j; + + for (j = 0; j < rectpositions[i].n; j++) { + int xx, yy; + + for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) + for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) + overlaps[(i * h + yy+rectpositions[i].rects[j].y) * w + + xx+rectpositions[i].rects[j].x]++; + } + } + + /* + * Also we want an array covering the grid once, to make it + * easy to figure out which squares are candidate number + * placements for which rectangles. (The existence of this + * single array assumes that no square starts off as a + * candidate number placement for more than one rectangle. This + * assumption is justified, because this solver is _either_ + * used to solve real problems - in which case there is a + * single placement for every number - _or_ used to decide on + * number placements for a new puzzle, in which case each + * number's placements are confined to the intended position of + * the rectangle containing that number.) + */ + rectbyplace = snewn(w * h, int); + for (i = 0; i < w*h; i++) + rectbyplace[i] = -1; + + for (i = 0; i < nrects; i++) { + int j; + + for (j = 0; j < numbers[i].npoints; j++) { + int x = numbers[i].points[j].x; + int y = numbers[i].points[j].y; + + assert(rectbyplace[y * w + x] == -1); + rectbyplace[y * w + x] = i; + } + } + + workspace = snewn(nrects, int); + + /* + * Now run the actual deduction loop. + */ + while (1) { + int done_something = FALSE; + +#ifdef SOLVER_DIAGNOSTICS + printf("starting deduction loop\n"); + + for (i = 0; i < nrects; i++) { + printf("rect %d overlaps:\n", i); + { + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + printf("%3d", overlaps[(i * h + y) * w + x]); + } + printf("\n"); + } + } + } + printf("rectbyplace:\n"); + { + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + printf("%3d", rectbyplace[y * w + x]); + } + printf("\n"); + } + } +#endif + + /* + * Housekeeping. Look for rectangles whose number has only + * one candidate position left, and mark that square as + * known if it isn't already. + */ + for (i = 0; i < nrects; i++) { + if (numbers[i].npoints == 1) { + int x = numbers[i].points[0].x; + int y = numbers[i].points[0].y; + if (overlaps[(i * h + y) * w + x] >= -1) { + int j; + + if (overlaps[(i * h + y) * w + x] <= 0) { + ret = 0; /* inconsistency */ + goto cleanup; + } +#ifdef SOLVER_DIAGNOSTICS + printf("marking %d,%d as known for rect %d" + " (sole remaining number position)\n", x, y, i); +#endif + + for (j = 0; j < nrects; j++) + overlaps[(j * h + y) * w + x] = -1; + + overlaps[(i * h + y) * w + x] = -2; + } + } + } + + /* + * Now look at the intersection of all possible placements + * for each rectangle, and mark all squares in that + * intersection as known for that rectangle if they aren't + * already. + */ + for (i = 0; i < nrects; i++) { + int minx, miny, maxx, maxy, xx, yy, j; + + minx = miny = 0; + maxx = w; + maxy = h; + + for (j = 0; j < rectpositions[i].n; j++) { + int x = rectpositions[i].rects[j].x; + int y = rectpositions[i].rects[j].y; + int w = rectpositions[i].rects[j].w; + int h = rectpositions[i].rects[j].h; + + if (minx < x) minx = x; + if (miny < y) miny = y; + if (maxx > x+w) maxx = x+w; + if (maxy > y+h) maxy = y+h; + } + + for (yy = miny; yy < maxy; yy++) + for (xx = minx; xx < maxx; xx++) + if (overlaps[(i * h + yy) * w + xx] >= -1) { + if (overlaps[(i * h + yy) * w + xx] <= 0) { + ret = 0; /* inconsistency */ + goto cleanup; + } +#ifdef SOLVER_DIAGNOSTICS + printf("marking %d,%d as known for rect %d" + " (intersection of all placements)\n", + xx, yy, i); +#endif + + for (j = 0; j < nrects; j++) + overlaps[(j * h + yy) * w + xx] = -1; + + overlaps[(i * h + yy) * w + xx] = -2; + } + } + + /* + * Rectangle-focused deduction. Look at each rectangle in + * turn and try to rule out some of its candidate + * placements. + */ + for (i = 0; i < nrects; i++) { + int j; + + for (j = 0; j < rectpositions[i].n; j++) { + int xx, yy, k; + int del = FALSE; + + for (k = 0; k < nrects; k++) + workspace[k] = 0; + + for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) { + int y = yy + rectpositions[i].rects[j].y; + for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) { + int x = xx + rectpositions[i].rects[j].x; + + if (overlaps[(i * h + y) * w + x] == -1) { + /* + * This placement overlaps a square + * which is _known_ to be part of + * another rectangle. Therefore we must + * rule it out. + */ +#ifdef SOLVER_DIAGNOSTICS + printf("rect %d placement at %d,%d w=%d h=%d " + "contains %d,%d which is known-other\n", i, + rectpositions[i].rects[j].x, + rectpositions[i].rects[j].y, + rectpositions[i].rects[j].w, + rectpositions[i].rects[j].h, + x, y); +#endif + del = TRUE; + } + + if (rectbyplace[y * w + x] != -1) { + /* + * This placement overlaps one of the + * candidate number placements for some + * rectangle. Count it. + */ + workspace[rectbyplace[y * w + x]]++; + } + } + } + + if (!del) { + /* + * If we haven't ruled this placement out + * already, see if it overlaps _all_ of the + * candidate number placements for any + * rectangle. If so, we can rule it out. + */ + for (k = 0; k < nrects; k++) + if (k != i && workspace[k] == numbers[k].npoints) { +#ifdef SOLVER_DIAGNOSTICS + printf("rect %d placement at %d,%d w=%d h=%d " + "contains all number points for rect %d\n", + i, + rectpositions[i].rects[j].x, + rectpositions[i].rects[j].y, + rectpositions[i].rects[j].w, + rectpositions[i].rects[j].h, + k); +#endif + del = TRUE; + break; + } + + /* + * Failing that, see if it overlaps at least + * one of the candidate number placements for + * itself! (This might not be the case if one + * of those number placements has been removed + * recently.). + */ + if (!del && workspace[i] == 0) { +#ifdef SOLVER_DIAGNOSTICS + printf("rect %d placement at %d,%d w=%d h=%d " + "contains none of its own number points\n", + i, + rectpositions[i].rects[j].x, + rectpositions[i].rects[j].y, + rectpositions[i].rects[j].w, + rectpositions[i].rects[j].h); +#endif + del = TRUE; + } + } + + if (del) { + remove_rect_placement(w, h, rectpositions, overlaps, i, j); + + j--; /* don't skip over next placement */ + + done_something = TRUE; + } + } + } + + /* + * Square-focused deduction. Look at each square not marked + * as known, and see if there are any which can only be + * part of a single rectangle. + */ + { + int x, y, n, index; + for (y = 0; y < h; y++) for (x = 0; x < w; x++) { + /* Known squares are marked as <0 everywhere, so we only need + * to check the overlaps entry for rect 0. */ + if (overlaps[y * w + x] < 0) + continue; /* known already */ + + n = 0; + index = -1; + for (i = 0; i < nrects; i++) + if (overlaps[(i * h + y) * w + x] > 0) + n++, index = i; + + if (n == 1) { + int j; + + /* + * Now we can rule out all placements for + * rectangle `index' which _don't_ contain + * square x,y. + */ +#ifdef SOLVER_DIAGNOSTICS + printf("square %d,%d can only be in rectangle %d\n", + x, y, index); +#endif + for (j = 0; j < rectpositions[index].n; j++) { + struct rect *r = &rectpositions[index].rects[j]; + if (x >= r->x && x < r->x + r->w && + y >= r->y && y < r->y + r->h) + continue; /* this one is OK */ + remove_rect_placement(w, h, rectpositions, overlaps, + index, j); + j--; /* don't skip over next placement */ + done_something = TRUE; + } + } + } + } + + /* + * If we've managed to deduce anything by normal means, + * loop round again and see if there's more to be done. + * Only if normal deduction has completely failed us should + * we now move on to narrowing down the possible number + * placements. + */ + if (done_something) + continue; + + /* + * Now we have done everything we can with the current set + * of number placements. So we need to winnow the number + * placements so as to narrow down the possibilities. We do + * this by searching for a candidate placement (of _any_ + * rectangle) which overlaps a candidate placement of the + * number for some other rectangle. + */ + if (rs) { + struct rpn { + int rect; + int placement; + int number; + } *rpns = NULL; + size_t nrpns = 0, rpnsize = 0; + int j; + + for (i = 0; i < nrects; i++) { + for (j = 0; j < rectpositions[i].n; j++) { + int xx, yy; + + for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) { + int y = yy + rectpositions[i].rects[j].y; + for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) { + int x = xx + rectpositions[i].rects[j].x; + + if (rectbyplace[y * w + x] >= 0 && + rectbyplace[y * w + x] != i) { + /* + * Add this to the list of + * winnowing possibilities. + */ + if (nrpns >= rpnsize) { + rpnsize = rpnsize * 3 / 2 + 32; + rpns = sresize(rpns, rpnsize, struct rpn); + } + rpns[nrpns].rect = i; + rpns[nrpns].placement = j; + rpns[nrpns].number = rectbyplace[y * w + x]; + nrpns++; + } + } + } + + } + } + +#ifdef SOLVER_DIAGNOSTICS + printf("%d candidate rect placements we could eliminate\n", nrpns); +#endif + if (nrpns > 0) { + /* + * Now choose one of these unwanted rectangle + * placements, and eliminate it. + */ + int index = random_upto(rs, nrpns); + int k, m; + struct rpn rpn = rpns[index]; + struct rect r; + sfree(rpns); + + i = rpn.rect; + j = rpn.placement; + k = rpn.number; + r = rectpositions[i].rects[j]; + + /* + * We rule out placement j of rectangle i by means + * of removing all of rectangle k's candidate + * number placements which do _not_ overlap it. + * This will ensure that it is eliminated during + * the next pass of rectangle-focused deduction. + */ +#ifdef SOLVER_DIAGNOSTICS + printf("ensuring number for rect %d is within" + " rect %d's placement at %d,%d w=%d h=%d\n", + k, i, r.x, r.y, r.w, r.h); +#endif + + for (m = 0; m < numbers[k].npoints; m++) { + int x = numbers[k].points[m].x; + int y = numbers[k].points[m].y; + + if (x < r.x || x >= r.x + r.w || + y < r.y || y >= r.y + r.h) { +#ifdef SOLVER_DIAGNOSTICS + printf("eliminating number for rect %d at %d,%d\n", + k, x, y); +#endif + remove_number_placement(w, h, &numbers[k], + m, rectbyplace); + m--; /* don't skip the next one */ + done_something = TRUE; + } + } + } + } + + if (!done_something) { +#ifdef SOLVER_DIAGNOSTICS + printf("terminating deduction loop\n"); +#endif + break; + } + } + + cleanup: + ret = 1; + for (i = 0; i < nrects; i++) { +#ifdef SOLVER_DIAGNOSTICS + printf("rect %d has %d possible placements\n", + i, rectpositions[i].n); +#endif + if (rectpositions[i].n <= 0) { + ret = 0; /* inconsistency */ + } else if (rectpositions[i].n > 1) { + ret = 2; /* remaining uncertainty */ + } else if (hedge && vedge) { + /* + * Place the rectangle in its only possible position. + */ + int x, y; + struct rect *r = &rectpositions[i].rects[0]; + + for (y = 0; y < r->h; y++) { + if (r->x > 0) + vedge[(r->y+y) * w + r->x] = 1; + if (r->x+r->w < w) + vedge[(r->y+y) * w + r->x+r->w] = 1; + } + for (x = 0; x < r->w; x++) { + if (r->y > 0) + hedge[r->y * w + r->x+x] = 1; + if (r->y+r->h < h) + hedge[(r->y+r->h) * w + r->x+x] = 1; + } + } + } + + /* + * Free up all allocated storage. + */ + sfree(workspace); + sfree(rectbyplace); + sfree(overlaps); + for (i = 0; i < nrects; i++) + sfree(rectpositions[i].rects); + sfree(rectpositions); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Grid generation code. + */ + +/* + * This function does one of two things. If passed r==NULL, it + * counts the number of possible rectangles which cover the given + * square, and returns it in *n. If passed r!=NULL then it _reads_ + * *n to find an index, counts the possible rectangles until it + * reaches the nth, and writes it into r. + * + * `scratch' is expected to point to an array of 2 * params->w + * ints, used internally as scratch space (and passed in like this + * to avoid re-allocating and re-freeing it every time round a + * tight loop). + */ +static void enum_rects(game_params *params, int *grid, struct rect *r, int *n, + int sx, int sy, int *scratch) +{ + int rw, rh, mw, mh; + int x, y, dx, dy; + int maxarea, realmaxarea; + int index = 0; + int *top, *bottom; + + /* + * Maximum rectangle area is 1/6 of total grid size, unless + * this means we can't place any rectangles at all in which + * case we set it to 2 at minimum. + */ + maxarea = params->w * params->h / 6; + if (maxarea < 2) + maxarea = 2; + + /* + * Scan the grid to find the limits of the region within which + * any rectangle containing this point must fall. This will + * save us trawling the inside of every rectangle later on to + * see if it contains any used squares. + */ + top = scratch; + bottom = scratch + params->w; + for (dy = -1; dy <= +1; dy += 2) { + int *array = (dy == -1 ? top : bottom); + for (dx = -1; dx <= +1; dx += 2) { + for (x = sx; x >= 0 && x < params->w; x += dx) { + array[x] = -2 * params->h * dy; + for (y = sy; y >= 0 && y < params->h; y += dy) { + if (index(params, grid, x, y) == -1 && + (x == sx || dy*y <= dy*array[x-dx])) + array[x] = y; + else + break; + } + } + } + } + + /* + * Now scan again to work out the largest rectangles we can fit + * in the grid, so that we can terminate the following loops + * early once we get down to not having much space left in the + * grid. + */ + realmaxarea = 0; + for (x = 0; x < params->w; x++) { + int x2; + + rh = bottom[x] - top[x] + 1; + if (rh <= 0) + continue; /* no rectangles can start here */ + + dx = (x > sx ? -1 : +1); + for (x2 = x; x2 >= 0 && x2 < params->w; x2 += dx) + if (bottom[x2] < bottom[x] || top[x2] > top[x]) + break; + + rw = abs(x2 - x); + if (realmaxarea < rw * rh) + realmaxarea = rw * rh; + } + + if (realmaxarea > maxarea) + realmaxarea = maxarea; + + /* + * Rectangles which go right the way across the grid are + * boring, although they can't be helped in the case of + * extremely small grids. (Also they might be generated later + * on by the singleton-removal process; we can't help that.) + */ + mw = params->w - 1; + if (mw < 3) mw++; + mh = params->h - 1; + if (mh < 3) mh++; + + for (rw = 1; rw <= mw; rw++) + for (rh = 1; rh <= mh; rh++) { + if (rw * rh > realmaxarea) + continue; + if (rw * rh == 1) + continue; + for (x = max(sx - rw + 1, 0); x <= min(sx, params->w - rw); x++) + for (y = max(sy - rh + 1, 0); y <= min(sy, params->h - rh); + y++) { + /* + * Check this rectangle against the region we + * defined above. + */ + if (top[x] <= y && top[x+rw-1] <= y && + bottom[x] >= y+rh-1 && bottom[x+rw-1] >= y+rh-1) { + if (r && index == *n) { + r->x = x; + r->y = y; + r->w = rw; + r->h = rh; + return; + } + index++; + } + } + } + + assert(!r); + *n = index; +} + +static void place_rect(game_params *params, int *grid, struct rect r) +{ + int idx = INDEX(params, r.x, r.y); + int x, y; + + for (x = r.x; x < r.x+r.w; x++) + for (y = r.y; y < r.y+r.h; y++) { + index(params, grid, x, y) = idx; + } +#ifdef GENERATION_DIAGNOSTICS + printf(" placing rectangle at (%d,%d) size %d x %d\n", + r.x, r.y, r.w, r.h); +#endif +} + +static struct rect find_rect(game_params *params, int *grid, int x, int y) +{ + int idx, w, h; + struct rect r; + + /* + * Find the top left of the rectangle. + */ + idx = index(params, grid, x, y); + + if (idx < 0) { + r.x = x; + r.y = y; + r.w = r.h = 1; + return r; /* 1x1 singleton here */ + } + + y = idx / params->w; + x = idx % params->w; + + /* + * Find the width and height of the rectangle. + */ + for (w = 1; + (x+w < params->w && index(params,grid,x+w,y)==idx); + w++); + for (h = 1; + (y+h < params->h && index(params,grid,x,y+h)==idx); + h++); + + r.x = x; + r.y = y; + r.w = w; + r.h = h; + + return r; +} + +#ifdef GENERATION_DIAGNOSTICS +static void display_grid(game_params *params, int *grid, int *numbers, int all) +{ + unsigned char *egrid = snewn((params->w*2+3) * (params->h*2+3), + unsigned char); + int x, y; + int r = (params->w*2+3); + + memset(egrid, 0, (params->w*2+3) * (params->h*2+3)); + + for (x = 0; x < params->w; x++) + for (y = 0; y < params->h; y++) { + int i = index(params, grid, x, y); + if (x == 0 || index(params, grid, x-1, y) != i) + egrid[(2*y+2) * r + (2*x+1)] = 1; + if (x == params->w-1 || index(params, grid, x+1, y) != i) + egrid[(2*y+2) * r + (2*x+3)] = 1; + if (y == 0 || index(params, grid, x, y-1) != i) + egrid[(2*y+1) * r + (2*x+2)] = 1; + if (y == params->h-1 || index(params, grid, x, y+1) != i) + egrid[(2*y+3) * r + (2*x+2)] = 1; + } + + for (y = 1; y < 2*params->h+2; y++) { + for (x = 1; x < 2*params->w+2; x++) { + if (!((y|x)&1)) { + int k = numbers ? index(params, numbers, x/2-1, y/2-1) : 0; + if (k || (all && numbers)) printf("%2d", k); else printf(" "); + } else if (!((y&x)&1)) { + int v = egrid[y*r+x]; + if ((y&1) && v) v = '-'; + if ((x&1) && v) v = '|'; + if (!v) v = ' '; + putchar(v); + if (!(x&1)) putchar(v); + } else { + int c, d = 0; + if (egrid[y*r+(x+1)]) d |= 1; + if (egrid[(y-1)*r+x]) d |= 2; + if (egrid[y*r+(x-1)]) d |= 4; + if (egrid[(y+1)*r+x]) d |= 8; + c = " ??+?-++?+|+++++"[d]; + putchar(c); + if (!(x&1)) putchar(c); + } + } + putchar('\n'); + } + + sfree(egrid); +} +#endif + +static char *new_game_desc(const game_params *params_in, random_state *rs, + char **aux, int interactive) +{ + game_params params_copy = *params_in; /* structure copy */ + game_params *params = ¶ms_copy; + int *grid, *numbers = NULL; + int x, y, y2, y2last, yx, run, i, nsquares; + char *desc, *p; + int *enum_rects_scratch; + game_params params2real, *params2 = ¶ms2real; + + while (1) { + /* + * Set up the smaller width and height which we will use to + * generate the base grid. + */ + params2->w = (int)((float)params->w / (1.0F + params->expandfactor)); + if (params2->w < 2 && params->w >= 2) params2->w = 2; + params2->h = (int)((float)params->h / (1.0F + params->expandfactor)); + if (params2->h < 2 && params->h >= 2) params2->h = 2; + + grid = snewn(params2->w * params2->h, int); + + enum_rects_scratch = snewn(2 * params2->w, int); + + nsquares = 0; + for (y = 0; y < params2->h; y++) + for (x = 0; x < params2->w; x++) { + index(params2, grid, x, y) = -1; + nsquares++; + } + + /* + * Place rectangles until we can't any more. We do this by + * finding a square we haven't yet covered, and randomly + * choosing a rectangle to cover it. + */ + + while (nsquares > 0) { + int square = random_upto(rs, nsquares); + int n; + struct rect r; + + x = params2->w; + y = params2->h; + for (y = 0; y < params2->h; y++) { + for (x = 0; x < params2->w; x++) { + if (index(params2, grid, x, y) == -1 && square-- == 0) + break; + } + if (x < params2->w) + break; + } + assert(x < params2->w && y < params2->h); + + /* + * Now see how many rectangles fit around this one. + */ + enum_rects(params2, grid, NULL, &n, x, y, enum_rects_scratch); + + if (!n) { + /* + * There are no possible rectangles covering this + * square, meaning it must be a singleton. Mark it + * -2 so we know not to keep trying. + */ + index(params2, grid, x, y) = -2; + nsquares--; + } else { + /* + * Pick one at random. + */ + n = random_upto(rs, n); + enum_rects(params2, grid, &r, &n, x, y, enum_rects_scratch); + + /* + * Place it. + */ + place_rect(params2, grid, r); + nsquares -= r.w * r.h; + } + } + + sfree(enum_rects_scratch); + + /* + * Deal with singleton spaces remaining in the grid, one by + * one. + * + * We do this by making a local change to the layout. There are + * several possibilities: + * + * +-----+-----+ Here, we can remove the singleton by + * | | | extending the 1x2 rectangle below it + * +--+--+-----+ into a 1x3. + * | | | | + * | +--+ | + * | | | | + * | | | | + * | | | | + * +--+--+-----+ + * + * +--+--+--+ Here, that trick doesn't work: there's no + * | | | 1 x n rectangle with the singleton at one + * | | | end. Instead, we extend a 1 x n rectangle + * | | | _out_ from the singleton, shaving a layer + * +--+--+ | off the end of another rectangle. So if we + * | | | | extended up, we'd make our singleton part + * | +--+--+ of a 1x3 and generate a 1x2 where the 2x2 + * | | | used to be; or we could extend right into + * +--+-----+ a 2x1, turning the 1x3 into a 1x2. + * + * +-----+--+ Here, we can't even do _that_, since any + * | | | direction we choose to extend the singleton + * +--+--+ | will produce a new singleton as a result of + * | | | | truncating one of the size-2 rectangles. + * | +--+--+ Fortunately, this case can _only_ occur when + * | | | a singleton is surrounded by four size-2s + * +--+-----+ in this fashion; so instead we can simply + * replace the whole section with a single 3x3. + */ + for (x = 0; x < params2->w; x++) { + for (y = 0; y < params2->h; y++) { + if (index(params2, grid, x, y) < 0) { + int dirs[4], ndirs; + +#ifdef GENERATION_DIAGNOSTICS + display_grid(params2, grid, NULL, FALSE); + printf("singleton at %d,%d\n", x, y); +#endif + + /* + * Check in which directions we can feasibly extend + * the singleton. We can extend in a particular + * direction iff either: + * + * - the rectangle on that side of the singleton + * is not 2x1, and we are at one end of the edge + * of it we are touching + * + * - it is 2x1 but we are on its short side. + * + * FIXME: we could plausibly choose between these + * based on the sizes of the rectangles they would + * create? + */ + ndirs = 0; + if (x < params2->w-1) { + struct rect r = find_rect(params2, grid, x+1, y); + if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1) + dirs[ndirs++] = 1; /* right */ + } + if (y > 0) { + struct rect r = find_rect(params2, grid, x, y-1); + if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1) + dirs[ndirs++] = 2; /* up */ + } + if (x > 0) { + struct rect r = find_rect(params2, grid, x-1, y); + if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1) + dirs[ndirs++] = 4; /* left */ + } + if (y < params2->h-1) { + struct rect r = find_rect(params2, grid, x, y+1); + if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1) + dirs[ndirs++] = 8; /* down */ + } + + if (ndirs > 0) { + int which, dir; + struct rect r1, r2; + memset(&r1, 0, sizeof(struct rect)); + memset(&r2, 0, sizeof(struct rect)); + which = random_upto(rs, ndirs); + dir = dirs[which]; + + switch (dir) { + case 1: /* right */ + assert(x < params2->w+1); +#ifdef GENERATION_DIAGNOSTICS + printf("extending right\n"); +#endif + r1 = find_rect(params2, grid, x+1, y); + r2.x = x; + r2.y = y; + r2.w = 1 + r1.w; + r2.h = 1; + if (r1.y == y) + r1.y++; + r1.h--; + break; + case 2: /* up */ + assert(y > 0); +#ifdef GENERATION_DIAGNOSTICS + printf("extending up\n"); +#endif + r1 = find_rect(params2, grid, x, y-1); + r2.x = x; + r2.y = r1.y; + r2.w = 1; + r2.h = 1 + r1.h; + if (r1.x == x) + r1.x++; + r1.w--; + break; + case 4: /* left */ + assert(x > 0); +#ifdef GENERATION_DIAGNOSTICS + printf("extending left\n"); +#endif + r1 = find_rect(params2, grid, x-1, y); + r2.x = r1.x; + r2.y = y; + r2.w = 1 + r1.w; + r2.h = 1; + if (r1.y == y) + r1.y++; + r1.h--; + break; + case 8: /* down */ + assert(y < params2->h+1); +#ifdef GENERATION_DIAGNOSTICS + printf("extending down\n"); +#endif + r1 = find_rect(params2, grid, x, y+1); + r2.x = x; + r2.y = y; + r2.w = 1; + r2.h = 1 + r1.h; + if (r1.x == x) + r1.x++; + r1.w--; + break; + default: /* should never happen */ + assert(!"invalid direction"); + } + if (r1.h > 0 && r1.w > 0) + place_rect(params2, grid, r1); + place_rect(params2, grid, r2); + } else { +#ifndef NDEBUG + /* + * Sanity-check that there really is a 3x3 + * rectangle surrounding this singleton and it + * contains absolutely everything we could + * possibly need. + */ + { + int xx, yy; + assert(x > 0 && x < params2->w-1); + assert(y > 0 && y < params2->h-1); + + for (xx = x-1; xx <= x+1; xx++) + for (yy = y-1; yy <= y+1; yy++) { + struct rect r = find_rect(params2,grid,xx,yy); + assert(r.x >= x-1); + assert(r.y >= y-1); + assert(r.x+r.w-1 <= x+1); + assert(r.y+r.h-1 <= y+1); + } + } +#endif + +#ifdef GENERATION_DIAGNOSTICS + printf("need the 3x3 trick\n"); +#endif + + /* + * FIXME: If the maximum rectangle area for + * this grid is less than 9, we ought to + * subdivide the 3x3 in some fashion. There are + * five other possibilities: + * + * - a 6 and a 3 + * - a 4, a 3 and a 2 + * - three 3s + * - a 3 and three 2s (two different arrangements). + */ + + { + struct rect r; + r.x = x-1; + r.y = y-1; + r.w = r.h = 3; + place_rect(params2, grid, r); + } + } + } + } + } + + /* + * We have now constructed a grid of the size specified in + * params2. Now we extend it into a grid of the size specified + * in params. We do this in two passes: we extend it vertically + * until it's the right height, then we transpose it, then + * extend it vertically again (getting it effectively the right + * width), then finally transpose again. + */ + for (i = 0; i < 2; i++) { + int *grid2, *expand, *where; + game_params params3real, *params3 = ¶ms3real; + +#ifdef GENERATION_DIAGNOSTICS + printf("before expansion:\n"); + display_grid(params2, grid, NULL, TRUE); +#endif + + /* + * Set up the new grid. + */ + grid2 = snewn(params2->w * params->h, int); + expand = snewn(params2->h-1, int); + where = snewn(params2->w, int); + params3->w = params2->w; + params3->h = params->h; + + /* + * Decide which horizontal edges are going to get expanded, + * and by how much. + */ + for (y = 0; y < params2->h-1; y++) + expand[y] = 0; + for (y = params2->h; y < params->h; y++) { + x = random_upto(rs, params2->h-1); + expand[x]++; + } + +#ifdef GENERATION_DIAGNOSTICS + printf("expand[] = {"); + for (y = 0; y < params2->h-1; y++) + printf(" %d", expand[y]); + printf(" }\n"); +#endif + + /* + * Perform the expansion. The way this works is that we + * alternately: + * + * - copy a row from grid into grid2 + * + * - invent some number of additional rows in grid2 where + * there was previously only a horizontal line between + * rows in grid, and make random decisions about where + * among these to place each rectangle edge that ran + * along this line. + */ + for (y = y2 = y2last = 0; y < params2->h; y++) { + /* + * Copy a single line from row y of grid into row y2 of + * grid2. + */ + for (x = 0; x < params2->w; x++) { + int val = index(params2, grid, x, y); + if (val / params2->w == y && /* rect starts on this line */ + (y2 == 0 || /* we're at the very top, or... */ + index(params3, grid2, x, y2-1) / params3->w < y2last + /* this rect isn't already started */)) + index(params3, grid2, x, y2) = + INDEX(params3, val % params2->w, y2); + else + index(params3, grid2, x, y2) = + index(params3, grid2, x, y2-1); + } + + /* + * If that was the last line, terminate the loop early. + */ + if (++y2 == params3->h) + break; + + y2last = y2; + + /* + * Invent some number of additional lines. First walk + * along this line working out where to put all the + * edges that coincide with it. + */ + yx = -1; + for (x = 0; x < params2->w; x++) { + if (index(params2, grid, x, y) != + index(params2, grid, x, y+1)) { + /* + * This is a horizontal edge, so it needs + * placing. + */ + if (x == 0 || + (index(params2, grid, x-1, y) != + index(params2, grid, x, y) && + index(params2, grid, x-1, y+1) != + index(params2, grid, x, y+1))) { + /* + * Here we have the chance to make a new + * decision. + */ + yx = random_upto(rs, expand[y]+1); + } else { + /* + * Here we just reuse the previous value of + * yx. + */ + } + } else + yx = -1; + where[x] = yx; + } + + for (yx = 0; yx < expand[y]; yx++) { + /* + * Invent a single row. For each square in the row, + * we copy the grid entry from the square above it, + * unless we're starting the new rectangle here. + */ + for (x = 0; x < params2->w; x++) { + if (yx == where[x]) { + int val = index(params2, grid, x, y+1); + val %= params2->w; + val = INDEX(params3, val, y2); + index(params3, grid2, x, y2) = val; + } else + index(params3, grid2, x, y2) = + index(params3, grid2, x, y2-1); + } + + y2++; + } + } + + sfree(expand); + sfree(where); + +#ifdef GENERATION_DIAGNOSTICS + printf("after expansion:\n"); + display_grid(params3, grid2, NULL, TRUE); +#endif + /* + * Transpose. + */ + params2->w = params3->h; + params2->h = params3->w; + sfree(grid); + grid = snewn(params2->w * params2->h, int); + for (x = 0; x < params2->w; x++) + for (y = 0; y < params2->h; y++) { + int idx1 = INDEX(params2, x, y); + int idx2 = INDEX(params3, y, x); + int tmp; + + tmp = grid2[idx2]; + tmp = (tmp % params3->w) * params2->w + (tmp / params3->w); + grid[idx1] = tmp; + } + + sfree(grid2); + + { + int tmp; + tmp = params->w; + params->w = params->h; + params->h = tmp; + } + +#ifdef GENERATION_DIAGNOSTICS + printf("after transposition:\n"); + display_grid(params2, grid, NULL, TRUE); +#endif + } + + /* + * Run the solver to narrow down the possible number + * placements. + */ + { + struct numberdata *nd; + int nnumbers, i, ret; + + /* Count the rectangles. */ + nnumbers = 0; + for (y = 0; y < params->h; y++) { + for (x = 0; x < params->w; x++) { + int idx = INDEX(params, x, y); + if (index(params, grid, x, y) == idx) + nnumbers++; + } + } + + nd = snewn(nnumbers, struct numberdata); + + /* Now set up each number's candidate position list. */ + i = 0; + for (y = 0; y < params->h; y++) { + for (x = 0; x < params->w; x++) { + int idx = INDEX(params, x, y); + if (index(params, grid, x, y) == idx) { + struct rect r = find_rect(params, grid, x, y); + int j, k, m; + + nd[i].area = r.w * r.h; + nd[i].npoints = nd[i].area; + nd[i].points = snewn(nd[i].npoints, struct point); + m = 0; + for (j = 0; j < r.h; j++) + for (k = 0; k < r.w; k++) { + nd[i].points[m].x = k + r.x; + nd[i].points[m].y = j + r.y; + m++; + } + assert(m == nd[i].npoints); + + i++; + } + } + } + + if (params->unique) + ret = rect_solver(params->w, params->h, nnumbers, nd, + NULL, NULL, rs); + else + ret = 1; /* allow any number placement at all */ + + if (ret == 1) { + /* + * Now place the numbers according to the solver's + * recommendations. + */ + numbers = snewn(params->w * params->h, int); + + for (y = 0; y < params->h; y++) + for (x = 0; x < params->w; x++) { + index(params, numbers, x, y) = 0; + } + + for (i = 0; i < nnumbers; i++) { + int idx = random_upto(rs, nd[i].npoints); + int x = nd[i].points[idx].x; + int y = nd[i].points[idx].y; + index(params,numbers,x,y) = nd[i].area; + } + } + + /* + * Clean up. + */ + for (i = 0; i < nnumbers; i++) + sfree(nd[i].points); + sfree(nd); + + /* + * If we've succeeded, then terminate the loop. + */ + if (ret == 1) + break; + } + + /* + * Give up and go round again. + */ + sfree(grid); + } + + /* + * Store the solution in aux. + */ + { + char *ai; + int len; + + len = 2 + (params->w-1)*params->h + (params->h-1)*params->w; + ai = snewn(len, char); + + ai[0] = 'S'; + + p = ai+1; + + for (y = 0; y < params->h; y++) + for (x = 1; x < params->w; x++) + *p++ = (index(params, grid, x, y) != + index(params, grid, x-1, y) ? '1' : '0'); + + for (y = 1; y < params->h; y++) + for (x = 0; x < params->w; x++) + *p++ = (index(params, grid, x, y) != + index(params, grid, x, y-1) ? '1' : '0'); + + assert(p - ai == len-1); + *p = '\0'; + + *aux = ai; + } + +#ifdef GENERATION_DIAGNOSTICS + display_grid(params, grid, numbers, FALSE); +#endif + + desc = snewn(11 * params->w * params->h, char); + p = desc; + run = 0; + for (i = 0; i <= params->w * params->h; i++) { + int n = (i < params->w * params->h ? numbers[i] : -1); + + if (!n) + run++; + else { + if (run) { + while (run > 0) { + int c = 'a' - 1 + run; + if (run > 26) + c = 'z'; + *p++ = c; + run -= c - ('a' - 1); + } + } else { + /* + * If there's a number in the very top left or + * bottom right, there's no point putting an + * unnecessary _ before or after it. + */ + if (p > desc && n > 0) + *p++ = '_'; + } + if (n > 0) + p += sprintf(p, "%d", n); + run = 0; + } + } + *p = '\0'; + + sfree(grid); + sfree(numbers); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int area = params->w * params->h; + int squares = 0; + + while (*desc) { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + squares += n - 'a' + 1; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + squares++; + while (*desc >= '0' && *desc <= '9') + desc++; + } else + return "Invalid character in game description"; + } + + if (squares < area) + return "Not enough data to fill grid"; + + if (squares > area) + return "Too much data to fit in grid"; + + return NULL; +} + +static unsigned char *get_correct(game_state *state) +{ + unsigned char *ret; + int x, y; + + ret = snewn(state->w * state->h, unsigned char); + memset(ret, 0xFF, state->w * state->h); + + for (x = 0; x < state->w; x++) + for (y = 0; y < state->h; y++) + if (index(state,ret,x,y) == 0xFF) { + int rw, rh; + int xx, yy; + int num, area, valid; + + /* + * Find a rectangle starting at this point. + */ + rw = 1; + while (x+rw < state->w && !vedge(state,x+rw,y)) + rw++; + rh = 1; + while (y+rh < state->h && !hedge(state,x,y+rh)) + rh++; + + /* + * We know what the dimensions of the rectangle + * should be if it's there at all. Find out if we + * really have a valid rectangle. + */ + valid = TRUE; + /* Check the horizontal edges. */ + for (xx = x; xx < x+rw; xx++) { + for (yy = y; yy <= y+rh; yy++) { + int e = !HRANGE(state,xx,yy) || hedge(state,xx,yy); + int ec = (yy == y || yy == y+rh); + if (e != ec) + valid = FALSE; + } + } + /* Check the vertical edges. */ + for (yy = y; yy < y+rh; yy++) { + for (xx = x; xx <= x+rw; xx++) { + int e = !VRANGE(state,xx,yy) || vedge(state,xx,yy); + int ec = (xx == x || xx == x+rw); + if (e != ec) + valid = FALSE; + } + } + + /* + * If this is not a valid rectangle with no other + * edges inside it, we just mark this square as not + * complete and proceed to the next square. + */ + if (!valid) { + index(state, ret, x, y) = 0; + continue; + } + + /* + * We have a rectangle. Now see what its area is, + * and how many numbers are in it. + */ + num = 0; + area = 0; + for (xx = x; xx < x+rw; xx++) { + for (yy = y; yy < y+rh; yy++) { + area++; + if (grid(state,xx,yy)) { + if (num > 0) + valid = FALSE; /* two numbers */ + num = grid(state,xx,yy); + } + } + } + if (num != area) + valid = FALSE; + + /* + * Now fill in the whole rectangle based on the + * value of `valid'. + */ + for (xx = x; xx < x+rw; xx++) { + for (yy = y; yy < y+rh; yy++) { + index(state, ret, xx, yy) = valid; + } + } + } + + return ret; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int x, y, i, area; + + state->w = params->w; + state->h = params->h; + + area = state->w * state->h; + + state->grid = snewn(area, int); + state->vedge = snewn(area, unsigned char); + state->hedge = snewn(area, unsigned char); + state->completed = state->cheated = FALSE; + + i = 0; + while (*desc) { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + int run = n - 'a' + 1; + assert(i + run <= area); + while (run-- > 0) + state->grid[i++] = 0; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + assert(i < area); + state->grid[i++] = atoi(desc-1); + while (*desc >= '0' && *desc <= '9') + desc++; + } else { + assert(!"We can't get here"); + } + } + assert(i == area); + + for (y = 0; y < state->h; y++) + for (x = 0; x < state->w; x++) + vedge(state,x,y) = hedge(state,x,y) = 0; + + state->correct = get_correct(state); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + + ret->vedge = snewn(state->w * state->h, unsigned char); + ret->hedge = snewn(state->w * state->h, unsigned char); + ret->grid = snewn(state->w * state->h, int); + ret->correct = snewn(ret->w * ret->h, unsigned char); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int)); + memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char)); + memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char)); + + memcpy(ret->correct, state->correct, state->w*state->h*sizeof(unsigned char)); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->vedge); + sfree(state->hedge); + sfree(state->correct); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *ai, char **error) +{ + unsigned char *vedge, *hedge; + int x, y, len; + char *ret, *p; + int i, j, n; + struct numberdata *nd; + + if (ai) + return dupstr(ai); + + /* + * Attempt the in-built solver. + */ + + /* Set up each number's (very short) candidate position list. */ + for (i = n = 0; i < state->h * state->w; i++) + if (state->grid[i]) + n++; + + nd = snewn(n, struct numberdata); + + for (i = j = 0; i < state->h * state->w; i++) + if (state->grid[i]) { + nd[j].area = state->grid[i]; + nd[j].npoints = 1; + nd[j].points = snewn(1, struct point); + nd[j].points[0].x = i % state->w; + nd[j].points[0].y = i / state->w; + j++; + } + + assert(j == n); + + vedge = snewn(state->w * state->h, unsigned char); + hedge = snewn(state->w * state->h, unsigned char); + memset(vedge, 0, state->w * state->h); + memset(hedge, 0, state->w * state->h); + + rect_solver(state->w, state->h, n, nd, hedge, vedge, NULL); + + /* + * Clean up. + */ + for (i = 0; i < n; i++) + sfree(nd[i].points); + sfree(nd); + + len = 2 + (state->w-1)*state->h + (state->h-1)*state->w; + ret = snewn(len, char); + + p = ret; + *p++ = 'S'; + for (y = 0; y < state->h; y++) + for (x = 1; x < state->w; x++) + *p++ = vedge[y*state->w+x] ? '1' : '0'; + for (y = 1; y < state->h; y++) + for (x = 0; x < state->w; x++) + *p++ = hedge[y*state->w+x] ? '1' : '0'; + *p++ = '\0'; + assert(p - ret == len); + + sfree(vedge); + sfree(hedge); + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret, *p, buf[80]; + int i, x, y, col, maxlen; + + /* + * First determine the number of spaces required to display a + * number. We'll use at least two, because one looks a bit + * silly. + */ + col = 2; + for (i = 0; i < state->w * state->h; i++) { + x = sprintf(buf, "%d", state->grid[i]); + if (col < x) col = x; + } + + /* + * Now we know the exact total size of the grid we're going to + * produce: it's got 2*h+1 rows, each containing w lots of col, + * w+1 boundary characters and a trailing newline. + */ + maxlen = (2*state->h+1) * (state->w * (col+1) + 2); + + ret = snewn(maxlen+1, char); + p = ret; + + for (y = 0; y <= 2*state->h; y++) { + for (x = 0; x <= 2*state->w; x++) { + if (x & y & 1) { + /* + * Display a number. + */ + int v = grid(state, x/2, y/2); + if (v) + sprintf(buf, "%*d", col, v); + else + sprintf(buf, "%*s", col, ""); + memcpy(p, buf, col); + p += col; + } else if (x & 1) { + /* + * Display a horizontal edge or nothing. + */ + int h = (y==0 || y==2*state->h ? 1 : + HRANGE(state, x/2, y/2) && hedge(state, x/2, y/2)); + int i; + if (h) + h = '-'; + else + h = ' '; + for (i = 0; i < col; i++) + *p++ = h; + } else if (y & 1) { + /* + * Display a vertical edge or nothing. + */ + int v = (x==0 || x==2*state->w ? 1 : + VRANGE(state, x/2, y/2) && vedge(state, x/2, y/2)); + if (v) + *p++ = '|'; + else + *p++ = ' '; + } else { + /* + * Display a corner, or a vertical edge, or a + * horizontal edge, or nothing. + */ + int hl = (y==0 || y==2*state->h ? 1 : + HRANGE(state, (x-1)/2, y/2) && hedge(state, (x-1)/2, y/2)); + int hr = (y==0 || y==2*state->h ? 1 : + HRANGE(state, (x+1)/2, y/2) && hedge(state, (x+1)/2, y/2)); + int vu = (x==0 || x==2*state->w ? 1 : + VRANGE(state, x/2, (y-1)/2) && vedge(state, x/2, (y-1)/2)); + int vd = (x==0 || x==2*state->w ? 1 : + VRANGE(state, x/2, (y+1)/2) && vedge(state, x/2, (y+1)/2)); + if (!hl && !hr && !vu && !vd) + *p++ = ' '; + else if (hl && hr && !vu && !vd) + *p++ = '-'; + else if (!hl && !hr && vu && vd) + *p++ = '|'; + else + *p++ = '+'; + } + } + *p++ = '\n'; + } + + assert(p - ret == maxlen); + *p = '\0'; + return ret; +} + +struct game_ui { + /* + * These coordinates are 2 times the obvious grid coordinates. + * Hence, the top left of the grid is (0,0), the grid point to + * the right of that is (2,0), the one _below that_ is (2,2) + * and so on. This is so that we can specify a drag start point + * on an edge (one odd coordinate) or in the middle of a square + * (two odd coordinates) rather than always at a corner. + * + * -1,-1 means no drag is in progress. + */ + int drag_start_x; + int drag_start_y; + int drag_end_x; + int drag_end_y; + /* + * This flag is set as soon as a dragging action moves the + * mouse pointer away from its starting point, so that even if + * the pointer _returns_ to its starting point the action is + * treated as a small drag rather than a click. + */ + int dragged; + /* This flag is set if we're doing an erase operation (i.e. + * removing edges in the centre of the rectangle without altering + * the outlines). + */ + int erasing; + /* + * These are the co-ordinates of the top-left and bottom-right squares + * in the drag box, respectively, or -1 otherwise. + */ + int x1; + int y1; + int x2; + int y2; + /* + * These are the coordinates of a cursor, whether it's visible, and + * whether it was used to start a drag. + */ + int cur_x, cur_y, cur_visible, cur_dragging; +}; + +static void reset_ui(game_ui *ui) +{ + ui->drag_start_x = -1; + ui->drag_start_y = -1; + ui->drag_end_x = -1; + ui->drag_end_y = -1; + ui->x1 = -1; + ui->y1 = -1; + ui->x2 = -1; + ui->y2 = -1; + ui->dragged = FALSE; +} + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + reset_ui(ui); + ui->erasing = FALSE; + ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_dragging = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void coord_round(float x, float y, int *xr, int *yr) +{ + float xs, ys, xv, yv, dx, dy, dist; + + /* + * Find the nearest square-centre. + */ + xs = (float)floor(x) + 0.5F; + ys = (float)floor(y) + 0.5F; + + /* + * And find the nearest grid vertex. + */ + xv = (float)floor(x + 0.5F); + yv = (float)floor(y + 0.5F); + + /* + * We allocate clicks in parts of the grid square to either + * corners, edges or square centres, as follows: + * + * +--+--------+--+ + * | | | | + * +--+ +--+ + * | `. ,' | + * | +--+ | + * | | | | + * | +--+ | + * | ,' `. | + * +--+ +--+ + * | | | | + * +--+--------+--+ + * + * (Not to scale!) + * + * In other words: we measure the square distance (i.e. + * max(dx,dy)) from the click to the nearest corner, and if + * it's within CORNER_TOLERANCE then we return a corner click. + * We measure the square distance from the click to the nearest + * centre, and if that's within CENTRE_TOLERANCE we return a + * centre click. Failing that, we find which of the two edge + * centres is nearer to the click and return that edge. + */ + + /* + * Check for corner click. + */ + dx = (float)fabs(x - xv); + dy = (float)fabs(y - yv); + dist = (dx > dy ? dx : dy); + if (dist < CORNER_TOLERANCE) { + *xr = 2 * (int)xv; + *yr = 2 * (int)yv; + } else { + /* + * Check for centre click. + */ + dx = (float)fabs(x - xs); + dy = (float)fabs(y - ys); + dist = (dx > dy ? dx : dy); + if (dist < CENTRE_TOLERANCE) { + *xr = 1 + 2 * (int)xs; + *yr = 1 + 2 * (int)ys; + } else { + /* + * Failing both of those, see which edge we're closer to. + * Conveniently, this is simply done by testing the relative + * magnitude of dx and dy (which are currently distances from + * the square centre). + */ + if (dx > dy) { + /* Vertical edge: x-coord of corner, + * y-coord of square centre. */ + *xr = 2 * (int)xv; + *yr = 1 + 2 * (int)floor(ys); + } else { + /* Horizontal edge: x-coord of square centre, + * y-coord of corner. */ + *xr = 1 + 2 * (int)floor(xs); + *yr = 2 * (int)yv; + } + } + } +} + +/* + * Returns TRUE if it has made any change to the grid. + */ +static int grid_draw_rect(const game_state *state, + unsigned char *hedge, unsigned char *vedge, + int c, int really, int outline, + int x1, int y1, int x2, int y2) +{ + int x, y; + int changed = FALSE; + + /* + * Draw horizontal edges of rectangles. + */ + for (x = x1; x < x2; x++) + for (y = y1; y <= y2; y++) + if (HRANGE(state,x,y)) { + int val = index(state,hedge,x,y); + if (y == y1 || y == y2) { + if (!outline) continue; + val = c; + } else if (c == 1) + val = 0; + changed = changed || (index(state,hedge,x,y) != val); + if (really) + index(state,hedge,x,y) = val; + } + + /* + * Draw vertical edges of rectangles. + */ + for (y = y1; y < y2; y++) + for (x = x1; x <= x2; x++) + if (VRANGE(state,x,y)) { + int val = index(state,vedge,x,y); + if (x == x1 || x == x2) { + if (!outline) continue; + val = c; + } else if (c == 1) + val = 0; + changed = changed || (index(state,vedge,x,y) != val); + if (really) + index(state,vedge,x,y) = val; + } + + return changed; +} + +static int ui_draw_rect(const game_state *state, const game_ui *ui, + unsigned char *hedge, unsigned char *vedge, int c, + int really, int outline) +{ + return grid_draw_rect(state, hedge, vedge, c, really, outline, + ui->x1, ui->y1, ui->x2, ui->y2); +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int w, h, tilesize; + unsigned long *visible; +}; + +static char *interpret_move(const game_state *from, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int xc, yc; + int startdrag = FALSE, enddrag = FALSE, active = FALSE, erasing = FALSE; + char buf[80], *ret; + + button &= ~MOD_MASK; + + coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc); + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + if (ui->drag_start_x >= 0 && ui->cur_dragging) + reset_ui(ui); /* cancel keyboard dragging */ + startdrag = TRUE; + ui->cur_visible = ui->cur_dragging = FALSE; + active = TRUE; + erasing = (button == RIGHT_BUTTON); + } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) { + /* We assert we should have had a LEFT_BUTTON first. */ + if (ui->cur_visible) { + ui->cur_visible = FALSE; + active = TRUE; + } + assert(!ui->cur_dragging); + enddrag = TRUE; + erasing = (button == RIGHT_RELEASE); + } else if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0); + ui->cur_visible = TRUE; + active = TRUE; + if (!ui->cur_dragging) return ""; + coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc); + } else if (IS_CURSOR_SELECT(button)) { + if (ui->drag_start_x >= 0 && !ui->cur_dragging) { + /* + * If a mouse drag is in progress, ignore attempts to + * start a keyboard one. + */ + return NULL; + } + if (!ui->cur_visible) { + assert(!ui->cur_dragging); + ui->cur_visible = TRUE; + return ""; + } + coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc); + erasing = (button == CURSOR_SELECT2); + if (ui->cur_dragging) { + ui->cur_dragging = FALSE; + enddrag = TRUE; + active = TRUE; + } else { + ui->cur_dragging = TRUE; + startdrag = TRUE; + active = TRUE; + } + } else if (button == '\b' || button == 27) { + if (!ui->cur_dragging) { + ui->cur_visible = FALSE; + } else { + assert(ui->cur_visible); + reset_ui(ui); /* cancel keyboard dragging */ + ui->cur_dragging = FALSE; + } + return ""; + } else if (button != LEFT_DRAG && button != RIGHT_DRAG) { + return NULL; + } + + if (startdrag && + xc >= 0 && xc <= 2*from->w && + yc >= 0 && yc <= 2*from->h) { + + ui->drag_start_x = xc; + ui->drag_start_y = yc; + ui->drag_end_x = -1; + ui->drag_end_y = -1; + ui->dragged = FALSE; + ui->erasing = erasing; + active = TRUE; + } + + if (ui->drag_start_x >= 0 && + (xc != ui->drag_end_x || yc != ui->drag_end_y)) { + int t; + + if (ui->drag_end_x != -1 && ui->drag_end_y != -1) + ui->dragged = TRUE; + ui->drag_end_x = xc; + ui->drag_end_y = yc; + active = TRUE; + + if (xc >= 0 && xc <= 2*from->w && + yc >= 0 && yc <= 2*from->h) { + ui->x1 = ui->drag_start_x; + ui->x2 = ui->drag_end_x; + if (ui->x2 < ui->x1) { t = ui->x1; ui->x1 = ui->x2; ui->x2 = t; } + + ui->y1 = ui->drag_start_y; + ui->y2 = ui->drag_end_y; + if (ui->y2 < ui->y1) { t = ui->y1; ui->y1 = ui->y2; ui->y2 = t; } + + ui->x1 = ui->x1 / 2; /* rounds down */ + ui->x2 = (ui->x2+1) / 2; /* rounds up */ + ui->y1 = ui->y1 / 2; /* rounds down */ + ui->y2 = (ui->y2+1) / 2; /* rounds up */ + } else { + ui->x1 = -1; + ui->y1 = -1; + ui->x2 = -1; + ui->y2 = -1; + } + } + + ret = NULL; + + if (enddrag && (ui->drag_start_x >= 0)) { + if (xc >= 0 && xc <= 2*from->w && + yc >= 0 && yc <= 2*from->h && + erasing == ui->erasing) { + + if (ui->dragged) { + if (ui_draw_rect(from, ui, from->hedge, + from->vedge, 1, FALSE, !ui->erasing)) { + sprintf(buf, "%c%d,%d,%d,%d", + (int)(ui->erasing ? 'E' : 'R'), + ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1); + ret = dupstr(buf); + } + } else { + if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) { + sprintf(buf, "H%d,%d", xc/2, yc/2); + ret = dupstr(buf); + } + if ((yc & 1) && !(xc & 1) && VRANGE(from,xc/2,yc/2)) { + sprintf(buf, "V%d,%d", xc/2, yc/2); + ret = dupstr(buf); + } + } + } + + reset_ui(ui); + active = TRUE; + } + + if (ret) + return ret; /* a move has been made */ + else if (active) + return ""; /* UI activity has occurred */ + else + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + game_state *ret; + int x1, y1, x2, y2, mode; + + if (move[0] == 'S') { + const char *p = move+1; + int x, y; + + ret = dup_game(from); + ret->cheated = TRUE; + + for (y = 0; y < ret->h; y++) + for (x = 1; x < ret->w; x++) { + vedge(ret, x, y) = (*p == '1'); + if (*p) p++; + } + for (y = 1; y < ret->h; y++) + for (x = 0; x < ret->w; x++) { + hedge(ret, x, y) = (*p == '1'); + if (*p) p++; + } + + sfree(ret->correct); + ret->correct = get_correct(ret); + + return ret; + + } else if ((move[0] == 'R' || move[0] == 'E') && + sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 && + x1 >= 0 && x2 >= 0 && x1+x2 <= from->w && + y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) { + x2 += x1; + y2 += y1; + mode = move[0]; + } else if ((move[0] == 'H' || move[0] == 'V') && + sscanf(move+1, "%d,%d", &x1, &y1) == 2 && + (move[0] == 'H' ? HRANGE(from, x1, y1) : + VRANGE(from, x1, y1))) { + mode = move[0]; + } else + return NULL; /* can't parse move string */ + + ret = dup_game(from); + + if (mode == 'R' || mode == 'E') { + grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE, + mode == 'R', x1, y1, x2, y2); + } else if (mode == 'H') { + hedge(ret,x1,y1) = !hedge(ret,x1,y1); + } else if (mode == 'V') { + vedge(ret,x1,y1) = !vedge(ret,x1,y1); + } + + sfree(ret->correct); + ret->correct = get_correct(ret); + + /* + * We've made a real change to the grid. Check to see + * if the game has been completed. + */ + if (!ret->completed) { + int x, y, ok; + + ok = TRUE; + for (x = 0; x < ret->w; x++) + for (y = 0; y < ret->h; y++) + if (!index(ret, ret->correct, x, y)) + ok = FALSE; + + if (ok) + ret->completed = TRUE; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define CORRECT (1L<<16) +#define CURSOR (1L<<17) + +#define COLOUR(k) ( (k)==1 ? COL_LINE : (k)==2 ? COL_DRAG : COL_DRAGERASE ) +#define MAX4(x,y,z,w) ( max(max(x,y),max(z,w)) ) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = params->w * TILE_SIZE + 2*BORDER + 1; + *y = params->h * TILE_SIZE + 2*BORDER + 1; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_GRID * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_GRID * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_DRAG * 3 + 0] = 1.0F; + ret[COL_DRAG * 3 + 1] = 0.0F; + ret[COL_DRAG * 3 + 2] = 0.0F; + + ret[COL_DRAGERASE * 3 + 0] = 0.2F; + ret[COL_DRAGERASE * 3 + 1] = 0.2F; + ret[COL_DRAGERASE * 3 + 2] = 1.0F; + + ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_LINE * 3 + 0] = 0.0F; + ret[COL_LINE * 3 + 1] = 0.0F; + ret[COL_LINE * 3 + 2] = 0.0F; + + ret[COL_TEXT * 3 + 0] = 0.0F; + ret[COL_TEXT * 3 + 1] = 0.0F; + ret[COL_TEXT * 3 + 2] = 0.0F; + + ret[COL_CURSOR * 3 + 0] = 1.0F; + ret[COL_CURSOR * 3 + 1] = 0.5F; + ret[COL_CURSOR * 3 + 2] = 0.5F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->started = FALSE; + ds->w = state->w; + ds->h = state->h; + ds->visible = snewn(ds->w * ds->h, unsigned long); + ds->tilesize = 0; /* not decided yet */ + for (i = 0; i < ds->w * ds->h; i++) + ds->visible[i] = 0xFFFF; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, + int x, int y, unsigned char *hedge, unsigned char *vedge, + unsigned char *corners, unsigned long bgflags) +{ + int cx = COORD(x), cy = COORD(y); + char str[80]; + + draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID); + draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1, + (bgflags & CURSOR) ? COL_CURSOR : + (bgflags & CORRECT) ? COL_CORRECT : COL_BACKGROUND); + + if (grid(state,x,y)) { + sprintf(str, "%d", grid(state,x,y)); + draw_text(dr, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE, + TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str); + } + + /* + * Draw edges. + */ + if (!HRANGE(state,x,y) || index(state,hedge,x,y)) + draw_rect(dr, cx, cy, TILE_SIZE+1, 2, + HRANGE(state,x,y) ? COLOUR(index(state,hedge,x,y)) : + COL_LINE); + if (!HRANGE(state,x,y+1) || index(state,hedge,x,y+1)) + draw_rect(dr, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2, + HRANGE(state,x,y+1) ? COLOUR(index(state,hedge,x,y+1)) : + COL_LINE); + if (!VRANGE(state,x,y) || index(state,vedge,x,y)) + draw_rect(dr, cx, cy, 2, TILE_SIZE+1, + VRANGE(state,x,y) ? COLOUR(index(state,vedge,x,y)) : + COL_LINE); + if (!VRANGE(state,x+1,y) || index(state,vedge,x+1,y)) + draw_rect(dr, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1, + VRANGE(state,x+1,y) ? COLOUR(index(state,vedge,x+1,y)) : + COL_LINE); + + /* + * Draw corners. + */ + if (index(state,corners,x,y)) + draw_rect(dr, cx, cy, 2, 2, + COLOUR(index(state,corners,x,y))); + if (x+1 < state->w && index(state,corners,x+1,y)) + draw_rect(dr, cx+TILE_SIZE-1, cy, 2, 2, + COLOUR(index(state,corners,x+1,y))); + if (y+1 < state->h && index(state,corners,x,y+1)) + draw_rect(dr, cx, cy+TILE_SIZE-1, 2, 2, + COLOUR(index(state,corners,x,y+1))); + if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1)) + draw_rect(dr, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2, + COLOUR(index(state,corners,x+1,y+1))); + + draw_update(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y; + unsigned char *hedge, *vedge, *corners; + + if (ui->dragged) { + hedge = snewn(state->w*state->h, unsigned char); + vedge = snewn(state->w*state->h, unsigned char); + memcpy(hedge, state->hedge, state->w*state->h); + memcpy(vedge, state->vedge, state->w*state->h); + ui_draw_rect(state, ui, hedge, vedge, ui->erasing ? 3 : 2, TRUE, TRUE); + } else { + hedge = state->hedge; + vedge = state->vedge; + } + + corners = snewn(state->w * state->h, unsigned char); + memset(corners, 0, state->w * state->h); + for (x = 0; x < state->w; x++) + for (y = 0; y < state->h; y++) { + if (x > 0) { + int e = index(state, vedge, x, y); + if (index(state,corners,x,y) < e) + index(state,corners,x,y) = e; + if (y+1 < state->h && + index(state,corners,x,y+1) < e) + index(state,corners,x,y+1) = e; + } + if (y > 0) { + int e = index(state, hedge, x, y); + if (index(state,corners,x,y) < e) + index(state,corners,x,y) = e; + if (x+1 < state->w && + index(state,corners,x+1,y) < e) + index(state,corners,x+1,y) = e; + } + } + + if (!ds->started) { + draw_rect(dr, 0, 0, + state->w * TILE_SIZE + 2*BORDER + 1, + state->h * TILE_SIZE + 2*BORDER + 1, COL_BACKGROUND); + draw_rect(dr, COORD(0)-1, COORD(0)-1, + ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE); + ds->started = TRUE; + draw_update(dr, 0, 0, + state->w * TILE_SIZE + 2*BORDER + 1, + state->h * TILE_SIZE + 2*BORDER + 1); + } + + for (x = 0; x < state->w; x++) + for (y = 0; y < state->h; y++) { + unsigned long c = 0; + + if (HRANGE(state,x,y)) + c |= index(state,hedge,x,y); + if (HRANGE(state,x,y+1)) + c |= index(state,hedge,x,y+1) << 2; + if (VRANGE(state,x,y)) + c |= index(state,vedge,x,y) << 4; + if (VRANGE(state,x+1,y)) + c |= index(state,vedge,x+1,y) << 6; + c |= index(state,corners,x,y) << 8; + if (x+1 < state->w) + c |= index(state,corners,x+1,y) << 10; + if (y+1 < state->h) + c |= index(state,corners,x,y+1) << 12; + if (x+1 < state->w && y+1 < state->h) + /* cast to prevent 2<<14 sign-extending on promotion to long */ + c |= (unsigned long)index(state,corners,x+1,y+1) << 14; + if (index(state, state->correct, x, y) && !flashtime) + c |= CORRECT; + if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y) + c |= CURSOR; + + if (index(ds,ds->visible,x,y) != c) { + draw_tile(dr, ds, state, x, y, hedge, vedge, corners, + (c & (CORRECT|CURSOR)) ); + index(ds,ds->visible,x,y) = c; + } + } + + { + char buf[256]; + + if (ui->dragged && + ui->x1 >= 0 && ui->y1 >= 0 && + ui->x2 >= 0 && ui->y2 >= 0) { + sprintf(buf, "%dx%d ", + ui->x2-ui->x1, + ui->y2-ui->y1); + } else { + buf[0] = '\0'; + } + + if (state->cheated) + strcat(buf, "Auto-solved."); + else if (state->completed) + strcat(buf, "COMPLETED!"); + + status_bar(dr, buf); + } + + if (hedge != state->hedge) { + sfree(hedge); + sfree(vedge); + } + + sfree(corners); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 5mm squares by default. + */ + game_compute_size(params, 500, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->w, h = state->h; + int ink = print_mono_colour(dr, 0); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILE_SIZE / 10); + draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, ink); + + /* + * Grid. We have to make the grid lines particularly thin, + * because users will be drawing lines _along_ them and we want + * those lines to be visible. + */ + print_line_width(dr, TILE_SIZE / 256); + for (x = 1; x < w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink); + for (y = 1; y < h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink); + + /* + * Solution. + */ + print_line_width(dr, TILE_SIZE / 10); + for (y = 0; y <= h; y++) + for (x = 0; x <= w; x++) { + if (HRANGE(state,x,y) && hedge(state,x,y)) + draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y), ink); + if (VRANGE(state,x,y) && vedge(state,x,y)) + draw_line(dr, COORD(x), COORD(y), COORD(x), COORD(y+1), ink); + } + + /* + * Clues. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (grid(state,x,y)) { + char str[80]; + sprintf(str, "%d", grid(state,x,y)); + draw_text(dr, COORD(x)+TILE_SIZE/2, COORD(y)+TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/2, + ALIGN_HCENTRE | ALIGN_VCENTRE, ink, str); + } +} + +#ifdef COMBINED +#define thegame rect +#endif + +const struct game thegame = { + "Rectangles", "games.rectangles", "rect", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/resource.h b/apps/plugins/puzzles/resource.h new file mode 100644 index 0000000000..f0bfa16d6d --- /dev/null +++ b/apps/plugins/puzzles/resource.h @@ -0,0 +1,20 @@ + +#define IDR_MENUBAR1 101 + +#define ID_GAME 40005 +#define ID_TYPE 40006 + +#define IDS_CAP_GAME 40105 +#define IDS_CAP_TYPE 40106 + +#define IDD_ABOUT 2000 +#define IDC_ABOUT_CAPTION 2001 +#define IDC_ABOUT_LINE 2002 +#define IDC_ABOUT_GAME 2003 +#define IDC_ABOUT_VERSION 2004 + +#define IDD_CONFIG 2100 +#define IDC_CONFIG_CAPTION 2101 +#define IDC_CONFIG_LINE 2102 + +#define IDR_PADTOOLBAR 4000 diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c new file mode 100644 index 0000000000..3a1c00e615 --- /dev/null +++ b/apps/plugins/puzzles/rockbox.c @@ -0,0 +1,1682 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 Franklin Wei + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* rockbox frontend for puzzles */ + +#include "plugin.h" + +#include "puzzles.h" +#include "keymaps.h" + +#ifndef COMBINED +#include "lib/playback_control.h" +#endif +#include "lib/xlcd.h" + +/* how many ticks between timer callbacks */ +#define TIMER_INTERVAL (HZ / 50) +#define BG_R .9f /* very light gray */ +#define BG_G .9f +#define BG_B .9f + +#ifdef COMBINED +#define SAVE_FILE PLUGIN_GAMES_DATA_DIR "/puzzles.sav" +#else +static char save_file_path[MAX_PATH]; +#define SAVE_FILE ((const char*)save_file_path) +#endif + +#define BG_COLOR LCD_RGBPACK((int)(255*BG_R), (int)(255*BG_G), (int)(255*BG_B)) + +#define MURICA + +#ifdef MURICA +#define midend_serialize midend_serialise +#define midend_deserialize midend_deserialise +#define frontend_default_color frontend_default_colour +#define midend_colors midend_colours +#endif + +static midend *me = NULL; +static unsigned *colors = NULL; +static int ncolors = 0; +static long last_keystate = 0; + +static void fix_size(void); + +static void rb_start_draw(void *handle) +{ + (void) handle; +} + +static struct viewport clip_rect; +static bool clipped = false; + +static struct settings_t { + int slowmo_factor; + bool bulk, timerflash; +} settings; + +/* clipping is implemented through viewports and offsetting + * coordinates */ +static void rb_clip(void *handle, int x, int y, int w, int h) +{ + LOGF("rb_clip(%d %d %d %d)", x, y, w, h); + clip_rect.x = x; + clip_rect.y = y; + clip_rect.width = w; + clip_rect.height = h; + clip_rect.font = FONT_UI; + clip_rect.drawmode = DRMODE_SOLID; +#if LCD_DEPTH > 1 + clip_rect.fg_pattern = LCD_DEFAULT_FG; + clip_rect.bg_pattern = LCD_DEFAULT_BG; +#endif + rb->lcd_set_viewport(&clip_rect); + clipped = true; +} + +static void rb_unclip(void *handle) +{ + LOGF("rb_unclip"); + rb->lcd_set_viewport(NULL); + clipped = false; +} + +static void offset_coords(int *x, int *y) +{ + if(clipped) + { + *x -= clip_rect.x; + *y -= clip_rect.y; + } +} + +static void rb_color(int n) +{ + if(n < 0) + { + fatal("bad color %d", n); + return; + } + rb->lcd_set_foreground(colors[n]); +} + +static void rb_draw_text(void *handle, int x, int y, int fonttype, + int fontsize, int align, int color, char *text) +{ + (void) fontsize; + LOGF("rb_draw_text(%d %d %s)", x, y, text); + + offset_coords(&x, &y); + + /* TODO: variable font size */ + switch(fonttype) + { + case FONT_FIXED: + rb->lcd_setfont(FONT_SYSFIXED); + break; + case FONT_VARIABLE: + rb->lcd_setfont(FONT_UI); + break; + default: + fatal("bad font"); + break; + } + + int w, h; + rb->lcd_getstringsize(text, &w, &h); + + static int cap_h = -1; + if(cap_h < 0) + rb->lcd_getstringsize("X", NULL, &cap_h); + + if(align & ALIGN_VNORMAL) + y -= h; + else if(align & ALIGN_VCENTRE) + y -= cap_h / 2; + + if(align & ALIGN_HCENTRE) + x -= w / 2; + else if(align & ALIGN_HRIGHT) + x -= w; + + rb_color(color); + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_putsxy(x, y, text); + rb->lcd_set_drawmode(DRMODE_SOLID); +} + +static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color) +{ + LOGF("rb_draw_rect(%d, %d, %d, %d, %d)", x, y, w, h, color); + rb_color(color); + offset_coords(&x, &y); + rb->lcd_fillrect(x, y, w, h); +} + +static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, + int color) +{ + LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color); + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + rb_color(color); + rb->lcd_drawline(x1, y1, x2, y2); +} + +/* + * draw filled polygon + * originally by Sebastian Leonhardt (ulmutul) + * 'count' : number of coordinate pairs + * 'pxy': array of coordinates. pxy[0]=x0,pxy[1]=y0,... + * note: provide space for one extra coordinate, because the starting point + * will automatically be inserted as end point. + */ + +/* + * helper function: + * find points of intersection between polygon and scanline + */ + +#define MAX_INTERSECTION 32 + +static void fill_poly_line(int scanline, int count, int *pxy) +{ + int i; + int j; + int num_of_intersects; + int direct, old_direct; + //intersections of every line with scanline (y-coord) + int intersection[MAX_INTERSECTION]; + /* add starting point as ending point */ + pxy[count*2] = pxy[0]; + pxy[count*2+1] = pxy[1]; + + old_direct=0; + num_of_intersects=0; + for (i=0; i y2) + continue; + } + else { + if (scanline < y2 || scanline > y1) + continue; + } + // calculate x-coord of intersection + if (y1==y2) { + direct=0; + } + else { + direct = y1>y2 ? 1 : -1; + // omit double intersections, if both lines lead in the same direction + intersection[num_of_intersects] = + x1+((scanline-y1)*(x2-x1))/(y2-y1); + if ( (direct!=old_direct) + || (intersection[num_of_intersects] != intersection[num_of_intersects-1]) + ) + ++num_of_intersects; + } + old_direct = direct; + } + + // sort points of intersection + for (i=0; ilcd_hline(intersection[i], intersection[i+1], scanline); + } +} + +/* two extra elements at end of pxy needed */ +static void v_fillarea(int count, int *pxy) +{ + int i; + int y1, y2; + + // find min and max y coords + y1=y2=pxy[1]; + for (i=3; i y2) y2 = pxy[i]; + } + + for (i=y1; i<=y2; ++i) { + fill_poly_line(i, count, pxy); + } +} + +static void rb_draw_poly(void *handle, int *coords, int npoints, + int fillcolor, int outlinecolor) +{ + LOGF("rb_draw_poly"); + + if(fillcolor >= 0) + { + rb_color(fillcolor); +#if 1 + /* serious hack: draw a bunch of triangles between adjacent points */ + /* this generally works, even with some concave polygons */ + for(int i = 2; i < npoints; ++i) + { + int x1, y1, x2, y2, x3, y3; + x1 = coords[0]; + y1 = coords[1]; + x2 = coords[(i - 1) * 2]; + y2 = coords[(i - 1) * 2 + 1]; + x3 = coords[i * 2]; + y3 = coords[i * 2 + 1]; + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + offset_coords(&x3, &y3); + xlcd_filltriangle(x1, y1, + x2, y2, + x3, y3); + +#if 0 + /* debug code */ + rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); + rb->lcd_drawpixel(x1, y1); + rb->lcd_drawpixel(x2, y2); + rb->lcd_drawpixel(x3, y3); + rb->lcd_update(); + rb->sleep(HZ); + rb_color(fillcolor); + rb->lcd_drawpixel(x1, y1); + rb->lcd_drawpixel(x2, y2); + rb->lcd_drawpixel(x3, y3); + rb->lcd_update(); +#endif + } +#else + int *pxy = smalloc(sizeof(int) * 2 * npoints + 2); + /* copy points, offsetted */ + for(int i = 0; i < npoints; ++i) + { + pxy[2 * i + 0] = coords[2 * i + 0]; + pxy[2 * i + 1] = coords[2 * i + 1]; + offset_coords(&pxy[2*i+0], &pxy[2*i+1]); + } + v_fillarea(npoints, pxy); + sfree(pxy); +#endif + } + + /* draw outlines last so they're not covered by the fill */ + assert(outlinecolor >= 0); + rb_color(outlinecolor); + + for(int i = 1; i < npoints; ++i) + { + int x1, y1, x2, y2; + x1 = coords[2 * (i - 1)]; + y1 = coords[2 * (i - 1) + 1]; + x2 = coords[2 * i]; + y2 = coords[2 * i + 1]; + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + rb->lcd_drawline(x1, y1, + x2, y2); + //rb->lcd_update(); + //rb->sleep(HZ/2); + } + + int x1, y1, x2, y2; + x1 = coords[0]; + y1 = coords[1]; + x2 = coords[2 * (npoints - 1)]; + y2 = coords[2 * (npoints - 1) + 1]; + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + + rb->lcd_drawline(x1, y1, + x2, y2); +} + +static void rb_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolor, int outlinecolor) +{ + LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius); + offset_coords(&cx, &cy); + + if(fillcolor >= 0) + { + rb_color(fillcolor); + xlcd_fillcircle(cx, cy, radius - 1); + } + + assert(outlinecolor >= 0); + rb_color(outlinecolor); + xlcd_drawcircle(cx, cy, radius - 1); +} + +struct blitter { + bool have_data; + int x, y; + struct bitmap bmp; +}; + +static blitter *rb_blitter_new(void *handle, int w, int h) +{ + LOGF("rb_blitter_new"); + blitter *b = snew(blitter); + b->bmp.width = w; + b->bmp.height = h; + b->bmp.data = smalloc(w * h * sizeof(fb_data)); + b->have_data = false; + return b; +} + +static void rb_blitter_free(void *handle, blitter *bl) +{ + LOGF("rb_blitter_free"); + sfree(bl->bmp.data); + sfree(bl); + return; +} + +static void trim_rect(int *x, int *y, int *w, int *h) +{ + int x0, x1, y0, y1; + + /* + * Reduce the size of the copied rectangle to stop it going + * outside the bounds of the canvas. + */ + + /* Transform from x,y,w,h form into coordinates of all edges */ + x0 = *x; + y0 = *y; + x1 = *x + *w; + y1 = *y + *h; + + /* Clip each coordinate at both extremes of the canvas */ + x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH ? LCD_WIDTH : x0); + x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH ? LCD_WIDTH : x1); + y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT ? LCD_HEIGHT : y0); + y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT ? LCD_HEIGHT : y1); + + /* Transform back into x,y,w,h to return */ + *x = x0; + *y = y0; + *w = x1 - x0; + *h = y1 - y0; +} + +/* copy a section of the framebuffer */ +static void rb_blitter_save(void *handle, blitter *bl, int x, int y) +{ + /* no viewport offset */ +#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#error no vertical stride +#else + if(bl->bmp.data) + { + int w = bl->bmp.width, h = bl->bmp.height; + trim_rect(&x, &y, &w, &h); + LOGF("rb_blitter_save(%d, %d, %d, %d)", x, y, w, h); + for(int i = 0; i < h; ++i) + { + /* copy line-by-line */ + rb->memcpy(bl->bmp.data + sizeof(fb_data) * i * w, + rb->lcd_framebuffer + (y + i) * LCD_WIDTH + x, + w * sizeof(fb_data)); + } + bl->x = x; + bl->y = y; + bl->have_data = true; + } +#endif +} + +static void rb_blitter_load(void *handle, blitter *bl, int x, int y) +{ + LOGF("rb_blitter_load"); + if(!bl->have_data) + return; + int w = bl->bmp.width, h = bl->bmp.height; + + if(x == BLITTER_FROMSAVED) x = bl->x; + if(y == BLITTER_FROMSAVED) y = bl->y; + + offset_coords(&x, &y); + trim_rect(&x, &y, &w, &h); + rb->lcd_bitmap((fb_data*)bl->bmp.data, x, y, w, h); +} + +static void rb_draw_update(void *handle, int x, int y, int w, int h) +{ + LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h); + if(!settings.bulk) + rb->lcd_update_rect(x, y, w, h); + else + rb->lcd_update(); +} + +static void rb_end_draw(void *handle) +{ + LOGF("rb_end_draw"); +} + +static char *titlebar = NULL; + +static void rb_status_bar(void *handle, char *text) +{ + if(titlebar) + sfree(titlebar); + titlebar = dupstr(text); + LOGF("game title is %s\n", text); +} + +static void draw_title(void) +{ + const char *str = NULL; + if(titlebar) + str = titlebar; + else + str = midend_which_game(me)->name; + + /* quick hack */ + bool orig_clipped = clipped; + if(orig_clipped) + rb_unclip(NULL); + + int h; + rb->lcd_setfont(FONT_UI); + rb->lcd_getstringsize(str, NULL, &h); + + rb->lcd_set_foreground(BG_COLOR); + rb->lcd_fillrect(0, LCD_HEIGHT - h, LCD_WIDTH, h); + + rb->lcd_set_foreground(LCD_BLACK); + rb->lcd_putsxy(0, LCD_HEIGHT - h, str); + rb->lcd_update_rect(0, LCD_HEIGHT - h, LCD_WIDTH, h); + + if(orig_clipped) + rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); +} + +static char *rb_text_fallback(void *handle, const char *const *strings, + int nstrings) +{ + return dupstr(strings[0]); +} + +const drawing_api rb_drawing = { + rb_draw_text, + rb_draw_rect, + rb_draw_line, + rb_draw_poly, + rb_draw_circle, + rb_draw_update, + rb_clip, + rb_unclip, + rb_start_draw, + rb_end_draw, + rb_status_bar, + rb_blitter_new, + rb_blitter_free, + rb_blitter_save, + rb_blitter_load, + NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ + NULL, NULL, /* line_width, line_dotted */ + rb_text_fallback, + NULL, +}; + +void frontend_default_color(frontend *fe, float *out) +{ + *out++ = BG_R; + *out++ = BG_G; + *out++ = BG_B; +} + +void fatal(char *fmt, ...) +{ + va_list ap; + + rb->splash(HZ, "FATAL ERROR"); + + va_start(ap, fmt); + char buf[80]; + rb->vsnprintf(buf, 80, fmt, ap); + rb->splash(HZ * 2, buf); + va_end(ap); + + exit(1); +} + +void get_random_seed(void **randseed, int *randseedsize) +{ + *randseed = snew(long); + long seed = *rb->current_tick; + rb->memcpy(*randseed, &seed, sizeof(seed)); + //*(long*)*randseed = 42; // debug + //rb->splash(HZ, "DEBUG SEED ON"); + *randseedsize = sizeof(long); +} + +const char *config_choices_formatter(int sel, void *data, char *buf, size_t len) +{ + /* we can't rely on being called in any particular order */ + char *list = dupstr(data); + char delimbuf[2] = { *list, 0 }; + char *save = NULL; + char *str = rb->strtok_r(list, delimbuf, &save); + for(int i = 0; i < sel; ++i) + str = rb->strtok_r(NULL, delimbuf, &save); + rb->snprintf(buf, len, "%s", str); + sfree(list); + return buf; +} + +static int list_choose(const char *list_str, const char *title) +{ + char delim = *list_str; + + const char *ptr = list_str + 1; + int n = 0; + while(ptr) + { + n++; + ptr = strchr(ptr + 1, delim); + } + + struct gui_synclist list; + + rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL); + rb->gui_synclist_set_icon_callback(&list, NULL); + rb->gui_synclist_set_nb_items(&list, n); + rb->gui_synclist_limit_scroll(&list, false); + + rb->gui_synclist_select_item(&list, 0); + + rb->gui_synclist_set_title(&list, (char*)title, NOICON); + while (1) + { + rb->gui_synclist_draw(&list); + int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); + if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) + continue; + switch(button) + { + case ACTION_STD_OK: + return rb->gui_synclist_get_sel_pos(&list); + case ACTION_STD_PREV: + case ACTION_STD_CANCEL: + return -1; + default: + break; + } + } +} + +static void do_configure_item(config_item *cfg) +{ + switch(cfg->type) + { + case C_STRING: + { +#define MAX_STRLEN 128 + char *newstr = smalloc(MAX_STRLEN); + rb->strlcpy(newstr, cfg->sval, MAX_STRLEN); + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); + if(rb->kbd_input(newstr, MAX_STRLEN) < 0) + { + sfree(newstr); + break; + } + sfree(cfg->sval); + cfg->sval = newstr; + break; + } + case C_BOOLEAN: + { + bool res = cfg->ival != 0; + rb->set_bool(cfg->name, &res); + cfg->ival = res; + break; + } + case C_CHOICES: + { + int sel = list_choose(cfg->sval, cfg->name); + if(sel >= 0) + cfg->ival = sel; + break; + } + default: + fatal("bad type"); + break; + } +} + +const char *config_formatter(int sel, void *data, char *buf, size_t len) +{ + config_item *cfg = data; + cfg += sel; + rb->snprintf(buf, len, "%s", cfg->name); + return buf; +} + +static void config_menu(void) +{ + char *title; + config_item *config = midend_get_config(me, CFG_SETTINGS, &title); + + if(!config) + { + rb->splash(HZ, "Nothing to configure."); + goto done; + } + + /* count */ + int n = 0; + config_item *ptr = config; + while(ptr->type != C_END) + { + n++; + ptr++; + } + + /* display a list */ + struct gui_synclist list; + + rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL); + rb->gui_synclist_set_icon_callback(&list, NULL); + rb->gui_synclist_set_nb_items(&list, n); + rb->gui_synclist_limit_scroll(&list, false); + + rb->gui_synclist_select_item(&list, 0); + + bool done = false; + rb->gui_synclist_set_title(&list, title, NOICON); + while (!done) + { + rb->gui_synclist_draw(&list); + int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); + if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) + continue; + switch(button) + { + case ACTION_STD_OK: + { + config_item old; + int pos = rb->gui_synclist_get_sel_pos(&list); + memcpy(&old, config + pos, sizeof(old)); + do_configure_item(config + pos); + char *err = midend_set_config(me, CFG_SETTINGS, config); + if(err) + { + rb->splash(HZ, err); + memcpy(config + pos, &old, sizeof(old)); + } + break; + } + case ACTION_STD_PREV: + case ACTION_STD_CANCEL: + done = true; + break; + default: + break; + } + } + +done: + sfree(title); + free_cfg(config); +} + +const char *preset_formatter(int sel, void *data, char *buf, size_t len) +{ + char *name; + game_params *junk; + midend_fetch_preset(me, sel, &name, &junk); + rb->strlcpy(buf, name, len); + return buf; +} + +static void presets_menu(void) +{ + if(!midend_num_presets(me)) + { + rb->splash(HZ, "No presets!"); + return; + } + + /* display a list */ + struct gui_synclist list; + + rb->gui_synclist_init(&list, &preset_formatter, NULL, false, 1, NULL); + rb->gui_synclist_set_icon_callback(&list, NULL); + rb->gui_synclist_set_nb_items(&list, midend_num_presets(me)); + rb->gui_synclist_limit_scroll(&list, false); + + int current = midend_which_preset(me); + rb->gui_synclist_select_item(&list, current >= 0 ? current : 0); + + bool done = false; + rb->gui_synclist_set_title(&list, "Game Type", NOICON); + while (!done) + { + rb->gui_synclist_draw(&list); + int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); + if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) + continue; + switch(button) + { + case ACTION_STD_OK: + { + int sel = rb->gui_synclist_get_sel_pos(&list); + char *junk; + game_params *params; + midend_fetch_preset(me, sel, &junk, ¶ms); + midend_set_params(me, params); + done = true; + break; + } + case ACTION_STD_PREV: + case ACTION_STD_CANCEL: + done = true; + break; + default: + break; + } + } +} + +static const struct { + const char *game, *help; +} quick_help_text[] = { + { "Black Box", "Find the hidden balls in the box by bouncing laser beams off them." }, + { "Bridges", "Connect all the islands with a network of bridges." }, + { "Cube", "Pick up all the blue squares by rolling the cube over them." }, + { "Dominosa", "Tile the rectangle with a full set of dominoes." }, + { "Fifteen", "Slide the tiles around to arrange them into order." }, + { "Filling", "Mark every square with the area of its containing region." }, + { "Flip", "Flip groups of squares to light them all up at once." }, + { "Flood", "Turn the grid the same colour in as few flood fills as possible." }, + { "Galaxies", "Divide the grid into rotationally symmetric regions each centred on a dot." }, + { "Guess", "Guess the hidden combination of colours." }, + { "Inertia", "Collect all the gems without running into any of the mines." }, + { "Keen", "Complete the latin square in accordance with the arithmetic clues." }, + { "Light Up", "Place bulbs to light up all the squares." }, + { "Loopy", "Draw a single closed loop, given clues about number of adjacent edges." }, + { "Magnets", "Place magnets to satisfy the clues and avoid like poles touching." }, + { "Map", "Colour the map so that adjacent regions are never the same colour." }, + { "Mines", "Find all the mines without treading on any of them." }, + { "Net", "Rotate each tile to reassemble the network." }, + { "Netslide", "Slide a row at a time to reassemble the network." }, + { "Palisade", "Divide the grid into equal-sized areas in accordance with the clues." }, + { "Pattern", "Fill in the pattern in the grid, given only the lengths of runs of black squares." }, + { "Pearl", "Draw a single closed loop, given clues about corner and straight squares." }, + { "Pegs", "Jump pegs over each other to remove all but one." }, + { "Range", "Place black squares to limit the visible distance from each numbered cell." }, + { "Rectangles", "Divide the grid into rectangles with areas equal to the numbers." }, + { "Same Game", "Clear the grid by removing touching groups of the same colour squares." }, + { "Signpost", "Connect the squares into a path following the arrows." }, + { "Singles", "Black out the right set of duplicate numbers." }, + { "Sixteen", "Slide a row at a time to arrange the tiles into order." }, + { "Slant", "Draw a maze of slanting lines that matches the clues." }, + { "Solo", "Fill in the grid so that each row, column and square block contains one of every digit." }, + { "Tents", "Place a tent next to each tree." }, + { "Towers", "Complete the latin square of towers in accordance with the clues." }, + { "Tracks", "Fill in the railway track according to the clues." }, + { "Twiddle", "Rotate the tiles around themselves to arrange them into order." }, + { "Undead", "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors." }, + { "Unequal", "Complete the latin square in accordance with the > signs." }, + { "Unruly", "Fill in the black and white grid to avoid runs of three." }, + { "Untangle", "Reposition the points so that the lines do not cross." }, +}; + +static void quick_help(void) +{ + for(int i = 0; i < ARRAYLEN(quick_help_text); ++i) + { + if(!strcmp(midend_which_game(me)->name, quick_help_text[i].game)) + { + rb->splash(0, quick_help_text[i].help); + rb->button_get(true); + return; + } + } +} + +static void full_help(void) +{ + /* TODO */ +} + +static void init_default_settings(void) +{ + settings.slowmo_factor = 1; + settings.bulk = false; + settings.timerflash = false; +} + +static void debug_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Debug Menu", NULL, + "Slowmo factor", + "Randomize colors", + "Toggle bulk update", + "Toggle flash pixel on timer", + "Back"); + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + rb->set_int("Slowmo factor", "", UNIT_INT, &settings.slowmo_factor, NULL, 1, 1, 10, NULL); + break; + case 1: + { + unsigned *ptr = colors; + for(int i = 0; i < ncolors; ++i) + { + /* not seeded, who cares? */ + *ptr++ = LCD_RGBPACK(rb->rand()%255, rb->rand()%255, rb->rand()%255); + } + break; + } + case 2: + settings.bulk = !settings.bulk; + break; + case 3: + settings.timerflash = !settings.timerflash; + break; + case 4: + default: + quit = true; + break; + } + } +} + +static int pausemenu_cb(int action, const struct menu_item_ex *this_item) +{ + int i = (intptr_t) this_item; + if(action == ACTION_REQUEST_MENUITEM) + { + switch(i) + { + case 3: + if(!midend_can_undo(me)) + return ACTION_EXIT_MENUITEM; + break; + case 4: + if(!midend_can_redo(me)) + return ACTION_EXIT_MENUITEM; + break; + case 5: + if(!midend_which_game(me)->can_solve) + return ACTION_EXIT_MENUITEM; + break; + case 7: +#ifdef FOR_REAL + return ACTION_EXIT_MENUITEM; +#else + break; +#endif + case 8: + if(!midend_num_presets(me)) + return ACTION_EXIT_MENUITEM; + break; + case 9: +#ifdef FOR_REAL + return ACTION_EXIT_MENUITEM; +#else + break; +#endif + case 10: + if(!midend_which_game(me)->can_configure) + return ACTION_EXIT_MENUITEM; + break; + default: + break; + } + } + return action; +} + +static int pause_menu(void) +{ +#define static auto +#define const + MENUITEM_STRINGLIST(menu, NULL, pausemenu_cb, + "Resume Game", + "New Game", + "Restart Game", + "Undo", + "Redo", + "Solve", + "Quick Help", + "Extensive Help", + "Game Type", + "Debug Menu", + "Configure Game", +#ifdef COMBINED + "Select Another Game", +#endif + "Quit without Saving", + "Quit"); +#undef static +#undef const + /* HACK ALERT */ + char title[32] = { 0 }; + rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name); + menu__.desc = title; + + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + quit = true; + break; + case 1: + midend_new_game(me); + fix_size(); + quit = true; + break; + case 2: + midend_restart_game(me); + fix_size(); + quit = true; + break; + case 3: + if(!midend_can_undo(me)) + rb->splash(HZ, "Cannot undo."); + else + midend_process_key(me, 0, 0, 'u'); + quit = true; + break; + case 4: + if(!midend_can_redo(me)) + rb->splash(HZ, "Cannot redo."); + else + midend_process_key(me, 0, 0, 'r'); + quit = true; + break; + case 5: + { + char *msg = midend_solve(me); + if(msg) + rb->splash(HZ, msg); + quit = true; + break; + } + case 6: + quick_help(); + break; + case 7: + full_help(); + break; + case 8: + presets_menu(); + midend_new_game(me); + fix_size(); + quit = true; + break; + case 9: + debug_menu(); + break; + case 10: + config_menu(); + midend_new_game(me); + fix_size(); + quit = true; + break; +#ifdef COMBINED + case 11: + return -1; + case 12: + return -2; + case 13: + return -3; +#else + case 11: + return -2; + case 12: + return -3; +#endif + default: + break; + } + } + rb->lcd_set_background(BG_COLOR); + rb->lcd_clear_display(); + rb->lcd_update(); + midend_force_redraw(me); + return 0; +} + +static bool want_redraw = true; +static bool accept_input = true; + +/* ignore the excess of LOGFs below... */ +#ifdef LOGF_ENABLE +#undef LOGF_ENABLE +#endif +static int process_input(int tmo) +{ + LOGF("process_input start"); + LOGF("------------------"); + int state = 0; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); /* about to block for button input */ +#endif + + int button = rb->button_get_w_tmo(tmo); + + /* weird stuff */ + exit_on_usb(button); + + button = rb->button_status(); + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + + if(button == BTN_PAUSE) + { + want_redraw = false; + /* quick hack to preserve the clipping state */ + bool orig_clipped = clipped; + if(orig_clipped) + rb_unclip(NULL); + + int rc = pause_menu(); + + if(orig_clipped) + rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); + + last_keystate = 0; + accept_input = true; + + return rc; + } + + /* special case for inertia: moves occur after RELEASES */ + if(!strcmp("Inertia", midend_which_game(me)->name)) + { + LOGF("received button 0x%08x", button); + + unsigned released = ~button & last_keystate; + last_keystate = button; + + if(!button) + { + if(!accept_input) + { + LOGF("ignoring, all keys released but not accepting input before, can accept input later"); + accept_input = true; + return 0; + } + } + + if(!released || !accept_input) + { + LOGF("released keys detected: 0x%08x", released); + LOGF("ignoring, either no keys released or not accepting input"); + return 0; + } + + button |= released; + if(last_keystate) + { + LOGF("ignoring input from now until all released"); + accept_input = false; + } + LOGF("accepting event 0x%08x", button); + } + /* not inertia: events fire on presses */ + else + { + /* start accepting input again after a release */ + if(!button) + { + accept_input = true; + return 0; + } + /* ignore repeats */ + if(!accept_input) + return 0; + accept_input = false; + } + + switch(button) + { + case BTN_UP: + state = CURSOR_UP; + break; + case BTN_DOWN: + state = CURSOR_DOWN; + break; + case BTN_LEFT: + state = CURSOR_LEFT; + break; + case BTN_RIGHT: + state = CURSOR_RIGHT; + break; + + /* handle diagonals (mainly for Inertia) */ + case BTN_DOWN | BTN_LEFT: +#ifdef BTN_DOWN_LEFT + case BTN_DOWN_LEFT: +#endif + state = '1' | MOD_NUM_KEYPAD; + break; + case BTN_DOWN | BTN_RIGHT: +#ifdef BTN_DOWN_RIGHT + case BTN_DOWN_RIGHT: +#endif + state = '3' | MOD_NUM_KEYPAD; + break; + case BTN_UP | BTN_LEFT: +#ifdef BTN_UP_LEFT + case BTN_UP_LEFT: +#endif + state = '7' | MOD_NUM_KEYPAD; + break; + case BTN_UP | BTN_RIGHT: +#ifdef BTN_UP_RIGHT + case BTN_UP_RIGHT: +#endif + state = '9' | MOD_NUM_KEYPAD; + break; + + case BTN_FIRE: + state = CURSOR_SELECT; + break; + } + LOGF("process_input done"); + LOGF("------------------"); + return state; +} + +static long last_tstamp; + +static void timer_cb(void) +{ +#if LCD_DEPTH != 24 + if(settings.timerflash) + { + static bool what = false; + what = !what; + if(what) + rb->lcd_framebuffer[0] = LCD_BLACK; + else + rb->lcd_framebuffer[0] = LCD_WHITE; + rb->lcd_update(); + } +#endif + + LOGF("timer callback"); + midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / settings.slowmo_factor); + last_tstamp = *rb->current_tick; +} + +static volatile bool timer_on = false; + +void activate_timer(frontend *fe) +{ + last_tstamp = *rb->current_tick; + timer_on = true; +} + +void deactivate_timer(frontend *fe) +{ + timer_on = false; +} + +#ifdef COMBINED +/* can't use audio buffer */ +static char giant_buffer[1024*1024*4]; +#else +/* points to audiobuf */ +static char *giant_buffer = NULL; +#endif +static size_t giant_buffer_len = 0; /* set on start */ + +#ifdef COMBINED +const char *formatter(char *buf, size_t n, int i, const char *unit) +{ + rb->snprintf(buf, n, "%s", gamelist[i]->name); + return buf; +} +#endif + +static void fix_size(void) +{ + int w = LCD_WIDTH, h = LCD_HEIGHT, h_x; + rb->lcd_setfont(FONT_UI); + rb->lcd_getstringsize("X", NULL, &h_x); + h -= h_x; + midend_size(me, &w, &h, TRUE); +} + +static void reset_tlsf(void) +{ + /* reset tlsf by nuking the signature */ + /* will make any already-allocated memory point to garbage */ + memset(giant_buffer, 0, 4); + init_memory_pool(giant_buffer_len, giant_buffer); +} + +static int read_wrapper(void *ptr, void *buf, int len) +{ + int fd = (int) ptr; + return rb->read(fd, buf, len); +} + +static void write_wrapper(void *ptr, void *buf, int len) +{ + int fd = (int) ptr; + rb->write(fd, buf, len); +} + +static void clear_and_draw(void) +{ + rb->lcd_clear_display(); + rb->lcd_update(); + + midend_force_redraw(me); + draw_title(); +} + +static char *init_for_game(const game *gm, int load_fd, bool draw) +{ + /* if we are loading a game tlsf has already been initialized */ + if(load_fd < 0) + reset_tlsf(); + + me = midend_new(NULL, gm, &rb_drawing, NULL); + + if(load_fd < 0) + midend_new_game(me); + else + { + char *ret = midend_deserialize(me, read_wrapper, (void*) load_fd); + if(ret) + return ret; + } + + fix_size(); + + float *floatcolors = midend_colors(me, &ncolors); + + /* convert them to packed RGB */ + colors = smalloc(ncolors * sizeof(unsigned)); + unsigned *ptr = colors; + float *floatptr = floatcolors; + for(int i = 0; i < ncolors; ++i) + { + int r = 255 * *(floatptr++); + int g = 255 * *(floatptr++); + int b = 255 * *(floatptr++); + LOGF("color %d is %d %d %d", i, r, g, b); + *ptr++ = LCD_RGBPACK(r, g, b); + } + sfree(floatcolors); + + rb->lcd_set_viewport(NULL); + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_BLACK); + rb->lcd_set_background(BG_COLOR); + + if(draw) + { + clear_and_draw(); + } + return NULL; +} + +static void exit_handler(void) +{ +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif +} + +/* expects a totally free me* pointer */ +static bool load_game(void) +{ + reset_tlsf(); + + int fd = rb->open(SAVE_FILE, O_RDONLY); + if(fd < 0) + return false; + + rb->splash(0, "Loading..."); + + LOGF("opening %s", SAVE_FILE); + + char *game; + char *ret = identify_game(&game, read_wrapper, (void*)fd); + + if(!*game && ret) + { + sfree(game); + rb->splash(HZ, ret); + rb->close(fd); + return false; + } + else + { + /* seek to beginning */ + rb->lseek(fd, 0, SEEK_SET); + +#ifdef COMBINED + /* search for the game and initialize the midend */ + for(int i = 0; i < gamecount; ++i) + { + if(!strcmp(game, gamelist[i]->name)) + { + sfree(ret); + ret = init_for_game(gamelist[i], fd, true); + if(ret) + { + rb->splash(HZ, ret); + sfree(ret); + rb->close(fd); + return false; + } + rb->close(fd); + /* success, we delete the save */ + rb->remove(SAVE_FILE); + return true; + } + } + rb->splashf(HZ, "Incompatible game %s reported as compatible!?!? REEEPORT MEEEE!!!!", game); + rb->close(fd); + return false; +#else + if(!strcmp(game, thegame.name)) + { + sfree(ret); + ret = init_for_game(&thegame, fd, false); + if(ret) + { + rb->splash(HZ, ret); + sfree(ret); + rb->close(fd); + return false; + } + rb->close(fd); + /* success, we delete the save */ + rb->remove(SAVE_FILE); + return true; + } + rb->splashf(HZ, "Cannot load save game for %s!", game); + return false; +#endif + } +} + +static void save_game(void) +{ + rb->splash(0, "Saving..."); + int fd = rb->open(SAVE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666); + midend_serialize(me, write_wrapper, (void*) fd); + rb->close(fd); + rb->lcd_update(); +} + +static bool load_success; + +static int mainmenu_cb(int action, const struct menu_item_ex *this_item) +{ + int i = (intptr_t) this_item; + if(action == ACTION_REQUEST_MENUITEM) + { + switch(i) + { + case 0: + case 7: + if(!load_success) + return ACTION_EXIT_MENUITEM; + break; + case 3: +#ifdef FOR_REAL + return ACTION_EXIT_MENUITEM; +#else + break; +#endif + case 5: + if(!midend_num_presets(me)) + return ACTION_EXIT_MENUITEM; + break; + case 6: + if(!midend_which_game(me)->can_configure) + return ACTION_EXIT_MENUITEM; + break; + default: + break; + } + } + return action; +} + +enum plugin_status plugin_start(const void *param) +{ + (void) param; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + /* boost for init */ + rb->cpu_boost(true); +#endif + +#ifdef COMBINED + giant_buffer_len = sizeof(giant_buffer); +#else + giant_buffer = rb->plugin_get_buffer(&giant_buffer_len); +#endif + +#ifndef COMBINED + rb->snprintf(save_file_path, sizeof(save_file_path), "%s/sgt-%s.sav", PLUGIN_GAMES_DATA_DIR, thegame.htmlhelp_topic); +#endif + + rb_atexit(exit_handler); + + if(fabs(sqrt(3)/2 - sin(PI/3)) > .01) + rb->splash(HZ, "WARNING: floating-point functions are being weird... report me!"); + + init_default_settings(); + + load_success = load_game(); + +#ifndef COMBINED + if(!load_success) + { + /* our main menu expects a ready-to-use midend */ + init_for_game(&thegame, -1, false); + } +#endif + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + /* about to go to menu or button block */ + rb->cpu_boost(false); +#endif + +#ifndef COMBINED +#define static auto +#define const + MENUITEM_STRINGLIST(menu, NULL, mainmenu_cb, + "Resume Game", + "New Game", + "Quick Help", + "Extensive Help", + "Playback Control", + "Game Type", + "Configure Game", + "Quit without Saving", + "Quit"); +#undef static +#undef const + + /* HACK ALERT */ + char title[32] = { 0 }; + rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name); + menu__.desc = title; + + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + clear_and_draw(); + goto game_loop; + case 1: + if(!load_success) + { + clear_and_draw(); + goto game_loop; + } + quit = true; + break; + case 2: + quick_help(); + break; + case 3: + full_help(); + break; + case 4: + playback_control(NULL); + break; + case 5: + presets_menu(); + break; + case 6: + config_menu(); + break; + case 8: + if(load_success) + save_game(); + /* fall through */ + case 7: + /* we don't care about freeing anything because tlsf will + * be wiped out the next time around */ + return PLUGIN_OK; + default: + break; + } + } +#else + if(load_success) + goto game_loop; +#endif + +#ifdef COMBINED + int gm = 0; +#endif + while(1) + { +#ifdef COMBINED + if(rb->set_int("Choose Game", "", UNIT_INT, &gm, NULL, 1, 0, gamecount - 1, formatter)) + return PLUGIN_OK; + + init_for_game(gamelist[gm], -1, true); +#else + init_for_game(&thegame, -1, true); +#endif + + last_keystate = 0; + accept_input = true; + + game_loop: + while(1) + { + want_redraw = true; + + draw_title(); + + int button = process_input(timer_on ? TIMER_INTERVAL : -1); + + if(button < 0) + { + rb_unclip(NULL); + deactivate_timer(NULL); + + if(titlebar) + { + sfree(titlebar); + titlebar = NULL; + } + + if(button == -1) + { + /* new game */ + midend_free(me); + break; + } + else if(button == -2) + { + /* quit without saving */ + midend_free(me); + sfree(colors); + exit(PLUGIN_OK); + } + else if(button == -3) + { + /* save and quit */ + save_game(); + midend_free(me); + sfree(colors); + exit(PLUGIN_OK); + } + } + + if(button) + midend_process_key(me, 0, 0, button); + + if(want_redraw) + midend_redraw(me); + + if(timer_on) + timer_cb(); + + rb->yield(); + } + sfree(colors); + } +} diff --git a/apps/plugins/puzzles/samegame.R b/apps/plugins/puzzles/samegame.R new file mode 100644 index 0000000000..cc0d350041 --- /dev/null +++ b/apps/plugins/puzzles/samegame.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +samegame : [X] GTK COMMON samegame samegame-icon|no-icon + +samegame : [G] WINDOWS COMMON samegame samegame.res|noicon.res + +ALL += samegame[COMBINED] + +!begin am gtk +GAMES += samegame +!end + +!begin >list.c + A(samegame) \ +!end + +!begin >gamedesc.txt +samegame:samegame.exe:Same Game:Block-clearing puzzle:Clear the grid by removing touching groups of the same colour squares. +!end diff --git a/apps/plugins/puzzles/samegame.c b/apps/plugins/puzzles/samegame.c new file mode 100644 index 0000000000..ed5efd224e --- /dev/null +++ b/apps/plugins/puzzles/samegame.c @@ -0,0 +1,1679 @@ +/* + * 'same game' -- try to remove all the coloured squares by + * selecting regions of contiguous colours. + */ + +/* + * TODO on grid generation: + * + * - Generation speed could still be improved. + * * 15x10c3 is the only really difficult one of the existing + * presets. The others are all either small enough, or have + * the great flexibility given by four colours, that they + * don't take long at all. + * * I still suspect many problems arise from separate + * subareas. I wonder if we can also somehow prioritise left- + * or rightmost insertions so as to avoid area splitting at + * all where feasible? It's not easy, though, because the + * current shuffle-then-try-all-options approach to move + * choice doesn't leave room for `soft' probabilistic + * prioritisation: we either try all class A moves before any + * class B ones, or we don't. + * + * - The current generation algorithm inserts exactly two squares + * at a time, with a single exception at the beginning of + * generation for grids of odd overall size. An obvious + * extension would be to permit larger inverse moves during + * generation. + * * this might reduce the number of failed generations by + * making the insertion algorithm more flexible + * * on the other hand, it would be significantly more complex + * * if I do this I'll need to take out the odd-subarea + * avoidance + * * a nice feature of the current algorithm is that the + * computer's `intended' solution always receives the minimum + * possible score, so that pretty much the player's entire + * score represents how much better they did than the + * computer. + * + * - Is it possible we can _temporarily_ tolerate neighbouring + * squares of the same colour, until we've finished setting up + * our inverse move? + * * or perhaps even not choose the colour of our inserted + * region until we have finished placing it, and _then_ look + * at what colours border on it? + * * I don't think this is currently meaningful unless we're + * placing more than a domino at a time. + * + * - possibly write out a full solution so that Solve can somehow + * show it step by step? + * * aux_info would have to encode the click points + * * solve_game() would have to encode not only those click + * points but also give a move string which reconstructed the + * initial state + * * the game_state would include a pointer to a solution move + * list, plus an index into that list + * * game_changed_state would auto-select the next move if + * handed a new state which had a solution move list active + * * execute_move, if passed such a state as input, would check + * to see whether the move being made was the same as the one + * stated by the solution, and if so would advance the move + * index. Failing that it would return a game_state without a + * solution move list active at all. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#define TILE_INNER (ds->tileinner) +#define TILE_GAP (ds->tilegap) +#define TILE_SIZE (TILE_INNER + TILE_GAP) +#define PREFERRED_TILE_SIZE 32 +#define BORDER (TILE_SIZE / 2) +#define HIGHLIGHT_WIDTH 2 + +#define FLASH_FRAME 0.13F + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define X(state, i) ( (i) % (state)->params.w ) +#define Y(state, i) ( (i) / (state)->params.w ) +#define C(state, x, y) ( (y) * (state)->w + (x) ) + +enum { + COL_BACKGROUND, + COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, + COL_IMPOSSIBLE, COL_SEL, COL_HIGHLIGHT, COL_LOWLIGHT, + NCOLOURS +}; + +/* scoresub is 1 or 2 (for (n-1)^2 or (n-2)^2) */ +struct game_params { + int w, h, ncols, scoresub; + int soluble; /* choose generation algorithm */ +}; + +/* These flags must be unique across all uses; in the game_state, + * the game_ui, and the drawstate (as they all get combined in the + * drawstate). */ +#define TILE_COLMASK 0x00ff +#define TILE_SELECTED 0x0100 /* used in ui and drawstate */ +#define TILE_JOINRIGHT 0x0200 /* used in drawstate */ +#define TILE_JOINDOWN 0x0400 /* used in drawstate */ +#define TILE_JOINDIAG 0x0800 /* used in drawstate */ +#define TILE_HASSEL 0x1000 /* used in drawstate */ +#define TILE_IMPOSSIBLE 0x2000 /* used in drawstate */ + +#define TILE(gs,x,y) ((gs)->tiles[(gs)->params.w*(y)+(x)]) +#define COL(gs,x,y) (TILE(gs,x,y) & TILE_COLMASK) +#define ISSEL(gs,x,y) (TILE(gs,x,y) & TILE_SELECTED) + +#define SWAPTILE(gs,x1,y1,x2,y2) do { \ + int t = TILE(gs,x1,y1); \ + TILE(gs,x1,y1) = TILE(gs,x2,y2); \ + TILE(gs,x2,y2) = t; \ +} while (0) + +static int npoints(const game_params *params, int nsel) +{ + int sdiff = nsel - params->scoresub; + return (sdiff > 0) ? sdiff * sdiff : 0; +} + +struct game_state { + struct game_params params; + int n; + int *tiles; /* colour only */ + int score; + int complete, impossible; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + ret->w = 5; + ret->h = 5; + ret->ncols = 3; + ret->scoresub = 2; + ret->soluble = TRUE; + return ret; +} + +static const struct game_params samegame_presets[] = { + { 5, 5, 3, 2, TRUE }, + { 10, 5, 3, 2, TRUE }, +#ifdef SLOW_SYSTEM + { 10, 10, 3, 2, TRUE }, +#else + { 15, 10, 3, 2, TRUE }, +#endif + { 15, 10, 4, 2, TRUE }, + { 20, 15, 4, 2, TRUE } +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(samegame_presets)) + return FALSE; + + ret = snew(game_params); + *ret = samegame_presets[i]; + + sprintf(str, "%dx%d, %d colours", ret->w, ret->h, ret->ncols); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + params->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->h = params->w; + } + if (*p == 'c') { + p++; + params->ncols = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->ncols = 3; + } + if (*p == 's') { + p++; + params->scoresub = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->scoresub = 2; + } + if (*p == 'r') { + p++; + params->soluble = FALSE; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[80]; + + sprintf(ret, "%dx%dc%ds%d%s", + params->w, params->h, params->ncols, params->scoresub, + full && !params->soluble ? "r" : ""); + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(6, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "No. of colours"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->ncols); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "Scoring system"; + ret[3].type = C_CHOICES; + ret[3].sval = ":(n-1)^2:(n-2)^2"; + ret[3].ival = params->scoresub-1; + + ret[4].name = "Ensure solubility"; + ret[4].type = C_BOOLEAN; + ret[4].sval = NULL; + ret[4].ival = params->soluble; + + ret[5].name = NULL; + ret[5].type = C_END; + ret[5].sval = NULL; + ret[5].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->ncols = atoi(cfg[2].sval); + ret->scoresub = cfg[3].ival + 1; + ret->soluble = cfg[4].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 1 || params->h < 1) + return "Width and height must both be positive"; + + if (params->ncols > 9) + return "Maximum of 9 colours"; + + if (params->soluble) { + if (params->ncols < 3) + return "Number of colours must be at least three"; + if (params->w * params->h <= 1) + return "Grid area must be greater than 1"; + } else { + if (params->ncols < 2) + return "Number of colours must be at least three"; + /* ...and we must make sure we can generate at least 2 squares + * of each colour so it's theoretically soluble. */ + if ((params->w * params->h) < (params->ncols * 2)) + return "Too many colours makes given grid size impossible"; + } + + if ((params->scoresub < 1) || (params->scoresub > 2)) + return "Scoring system not recognised"; + + return NULL; +} + +/* + * Guaranteed-soluble grid generator. + */ +static void gen_grid(int w, int h, int nc, int *grid, random_state *rs) +{ + int wh = w*h, tc = nc+1; + int i, j, k, c, x, y, pos, n; + int *list, *grid2; + int ok, failures = 0; + + /* + * We'll use `list' to track the possible places to put our + * next insertion. There are up to h places to insert in each + * column: in a column of height n there are n+1 places because + * we can insert at the very bottom or the very top, but a + * column of height h can't have anything at all inserted in it + * so we have up to h in each column. Likewise, with n columns + * present there are n+1 places to fit a new one in between but + * we can't insert a column if there are already w; so there + * are a maximum of w new columns too. Total is wh + w. + */ + list = snewn(wh + w, int); + grid2 = snewn(wh, int); + + do { + /* + * Start with two or three squares - depending on parity of w*h + * - of a random colour. + */ + for (i = 0; i < wh; i++) + grid[i] = 0; + j = 2 + (wh % 2); + c = 1 + random_upto(rs, nc); + if (j <= w) { + for (i = 0; i < j; i++) + grid[(h-1)*w+i] = c; + } else { + assert(j <= h); + for (i = 0; i < j; i++) + grid[(h-1-i)*w] = c; + } + + /* + * Now repeatedly insert a two-square blob in the grid, of + * whatever colour will go at the position we chose. + */ + while (1) { + n = 0; + + /* + * Build up a list of insertion points. Each point is + * encoded as y*w+x; insertion points between columns are + * encoded as h*w+x. + */ + + if (grid[wh - 1] == 0) { + /* + * The final column is empty, so we can insert new + * columns. + */ + for (i = 0; i < w; i++) { + list[n++] = wh + i; + if (grid[(h-1)*w + i] == 0) + break; + } + } + + /* + * Now look for places to insert within columns. + */ + for (i = 0; i < w; i++) { + if (grid[(h-1)*w+i] == 0) + break; /* no more columns */ + + if (grid[i] != 0) + continue; /* this column is full */ + + for (j = h; j-- > 0 ;) { + list[n++] = j*w+i; + if (grid[j*w+i] == 0) + break; /* this column is exhausted */ + } + } + + if (n == 0) + break; /* we're done */ + +#ifdef GENERATION_DIAGNOSTICS + printf("initial grid:\n"); + { + int x,y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (grid[y*w+x] == 0) + printf("-"); + else + printf("%d", grid[y*w+x]); + } + printf("\n"); + } + } +#endif + + /* + * Now go through the list one element at a time in + * random order, and actually attempt to insert + * something there. + */ + while (n-- > 0) { + int dirs[4], ndirs, dir; + + i = random_upto(rs, n+1); + pos = list[i]; + list[i] = list[n]; + + x = pos % w; + y = pos / w; + + memcpy(grid2, grid, wh * sizeof(int)); + + if (y == h) { + /* + * Insert a column at position x. + */ + for (i = w-1; i > x; i--) + for (j = 0; j < h; j++) + grid2[j*w+i] = grid2[j*w+(i-1)]; + /* + * Clear the new column. + */ + for (j = 0; j < h; j++) + grid2[j*w+x] = 0; + /* + * Decrement y so that our first square is actually + * inserted _in_ the grid rather than just below it. + */ + y--; + } + + /* + * Insert a square within column x at position y. + */ + for (i = 0; i+1 <= y; i++) + grid2[i*w+x] = grid2[(i+1)*w+x]; + +#ifdef GENERATION_DIAGNOSTICS + printf("trying at n=%d (%d,%d)\n", n, x, y); + grid2[y*w+x] = tc; + { + int x,y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (grid2[y*w+x] == 0) + printf("-"); + else if (grid2[y*w+x] <= nc) + printf("%d", grid2[y*w+x]); + else + printf("*"); + } + printf("\n"); + } + } +#endif + + /* + * Pick our square colour so that it doesn't match any + * of its neighbours. + */ + { + int wrongcol[4], nwrong = 0; + + /* + * List the neighbouring colours. + */ + if (x > 0) + wrongcol[nwrong++] = grid2[y*w+(x-1)]; + if (x+1 < w) + wrongcol[nwrong++] = grid2[y*w+(x+1)]; + if (y > 0) + wrongcol[nwrong++] = grid2[(y-1)*w+x]; + if (y+1 < h) + wrongcol[nwrong++] = grid2[(y+1)*w+x]; + + /* + * Eliminate duplicates. We can afford a shoddy + * algorithm here because the problem size is + * bounded. + */ + for (i = j = 0 ;; i++) { + int pos = -1, min = 0; + if (j > 0) + min = wrongcol[j-1]; + for (k = i; k < nwrong; k++) + if (wrongcol[k] > min && + (pos == -1 || wrongcol[k] < wrongcol[pos])) + pos = k; + if (pos >= 0) { + int v = wrongcol[pos]; + wrongcol[pos] = wrongcol[j]; + wrongcol[j++] = v; + } else + break; + } + nwrong = j; + + /* + * If no colour will go here, stop trying. + */ + if (nwrong == nc) + continue; + + /* + * Otherwise, pick a colour from the remaining + * ones. + */ + c = 1 + random_upto(rs, nc - nwrong); + for (i = 0; i < nwrong; i++) { + if (c >= wrongcol[i]) + c++; + else + break; + } + } + + /* + * Place the new square. + * + * Although I've _chosen_ the new region's colour + * (so that we can check adjacency), I'm going to + * actually place it as an invalid colour (tc) + * until I'm sure it's viable. This is so that I + * can conveniently check that I really have made a + * _valid_ inverse move later on. + */ +#ifdef GENERATION_DIAGNOSTICS + printf("picked colour %d\n", c); +#endif + grid2[y*w+x] = tc; + + /* + * Now attempt to extend it in one of three ways: left, + * right or up. + */ + ndirs = 0; + if (x > 0 && + grid2[y*w+(x-1)] != c && + grid2[x-1] == 0 && + (y+1 >= h || grid2[(y+1)*w+(x-1)] != c) && + (y+1 >= h || grid2[(y+1)*w+(x-1)] != 0) && + (x <= 1 || grid2[y*w+(x-2)] != c)) + dirs[ndirs++] = -1; /* left */ + if (x+1 < w && + grid2[y*w+(x+1)] != c && + grid2[x+1] == 0 && + (y+1 >= h || grid2[(y+1)*w+(x+1)] != c) && + (y+1 >= h || grid2[(y+1)*w+(x+1)] != 0) && + (x+2 >= w || grid2[y*w+(x+2)] != c)) + dirs[ndirs++] = +1; /* right */ + if (y > 0 && + grid2[x] == 0 && + (x <= 0 || grid2[(y-1)*w+(x-1)] != c) && + (x+1 >= w || grid2[(y-1)*w+(x+1)] != c)) { + /* + * We add this possibility _twice_, so that the + * probability of placing a vertical domino is + * about the same as that of a horizontal. This + * should yield less bias in the generated + * grids. + */ + dirs[ndirs++] = 0; /* up */ + dirs[ndirs++] = 0; /* up */ + } + + if (ndirs == 0) + continue; + + dir = dirs[random_upto(rs, ndirs)]; + +#ifdef GENERATION_DIAGNOSTICS + printf("picked dir %d\n", dir); +#endif + + /* + * Insert a square within column (x+dir) at position y. + */ + for (i = 0; i+1 <= y; i++) + grid2[i*w+x+dir] = grid2[(i+1)*w+x+dir]; + grid2[y*w+x+dir] = tc; + + /* + * See if we've divided the remaining grid squares + * into sub-areas. If so, we need every sub-area to + * have an even area or we won't be able to + * complete generation. + * + * If the height is odd and not all columns are + * present, we can increase the area of a subarea + * by adding a new column in it, so in that + * situation we don't mind having as many odd + * subareas as there are spare columns. + * + * If the height is even, we can't fix it at all. + */ + { + int nerrs = 0, nfix = 0; + k = 0; /* current subarea size */ + for (i = 0; i < w; i++) { + if (grid2[(h-1)*w+i] == 0) { + if (h % 2) + nfix++; + continue; + } + for (j = 0; j < h && grid2[j*w+i] == 0; j++); + assert(j < h); + if (j == 0) { + /* + * End of previous subarea. + */ + if (k % 2) + nerrs++; + k = 0; + } else { + k += j; + } + } + if (k % 2) + nerrs++; + if (nerrs > nfix) + continue; /* try a different placement */ + } + + /* + * We've made a move. Verify that it is a valid + * move and that if made it would indeed yield the + * previous grid state. The criteria are: + * + * (a) removing all the squares of colour tc (and + * shuffling the columns up etc) from grid2 + * would yield grid + * (b) no square of colour tc is adjacent to one + * of colour c + * (c) all the squares of colour tc form a single + * connected component + * + * We verify the latter property at the same time + * as checking that removing all the tc squares + * would yield the previous grid. Then we colour + * the tc squares in colour c by breadth-first + * search, which conveniently permits us to test + * that they're all connected. + */ + { + int x1, x2, y1, y2; + int ok = TRUE; + int fillstart = -1, ntc = 0; + +#ifdef GENERATION_DIAGNOSTICS + { + int x,y; + printf("testing move (new, old):\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (grid2[y*w+x] == 0) + printf("-"); + else if (grid2[y*w+x] <= nc) + printf("%d", grid2[y*w+x]); + else + printf("*"); + } + printf(" "); + for (x = 0; x < w; x++) { + if (grid[y*w+x] == 0) + printf("-"); + else + printf("%d", grid[y*w+x]); + } + printf("\n"); + } + } +#endif + + for (x1 = x2 = 0; x2 < w; x2++) { + int usedcol = FALSE; + + for (y1 = y2 = h-1; y2 >= 0; y2--) { + if (grid2[y2*w+x2] == tc) { + ntc++; + if (fillstart == -1) + fillstart = y2*w+x2; + if ((y2+1 < h && grid2[(y2+1)*w+x2] == c) || + (y2-1 >= 0 && grid2[(y2-1)*w+x2] == c) || + (x2+1 < w && grid2[y2*w+x2+1] == c) || + (x2-1 >= 0 && grid2[y2*w+x2-1] == c)) { +#ifdef GENERATION_DIAGNOSTICS + printf("adjacency failure at %d,%d\n", + x2, y2); +#endif + ok = FALSE; + } + continue; + } + if (grid2[y2*w+x2] == 0) + break; + usedcol = TRUE; + if (grid2[y2*w+x2] != grid[y1*w+x1]) { +#ifdef GENERATION_DIAGNOSTICS + printf("matching failure at %d,%d vs %d,%d\n", + x2, y2, x1, y1); +#endif + ok = FALSE; + } + y1--; + } + + /* + * If we've reached the top of the column + * in grid2, verify that we've also reached + * the top of the column in `grid'. + */ + if (usedcol) { + while (y1 >= 0) { + if (grid[y1*w+x1] != 0) { +#ifdef GENERATION_DIAGNOSTICS + printf("junk at column top (%d,%d)\n", + x1, y1); +#endif + ok = FALSE; + } + y1--; + } + } + + if (!ok) + break; + + if (usedcol) + x1++; + } + + if (!ok) { + assert(!"This should never happen"); + + /* + * If this game is compiled NDEBUG so that + * the assertion doesn't bring it to a + * crashing halt, the only thing we can do + * is to give up, loop round again, and + * hope to randomly avoid making whatever + * type of move just caused this failure. + */ + continue; + } + + /* + * Now use bfs to fill in the tc section as + * colour c. We use `list' to store the set of + * squares we have to process. + */ + i = j = 0; + assert(fillstart >= 0); + list[i++] = fillstart; +#ifdef OUTPUT_SOLUTION + printf("M"); +#endif + while (j < i) { + k = list[j]; + x = k % w; + y = k / w; +#ifdef OUTPUT_SOLUTION + printf("%s%d", j ? "," : "", k); +#endif + j++; + + assert(grid2[k] == tc); + grid2[k] = c; + + if (x > 0 && grid2[k-1] == tc) + list[i++] = k-1; + if (x+1 < w && grid2[k+1] == tc) + list[i++] = k+1; + if (y > 0 && grid2[k-w] == tc) + list[i++] = k-w; + if (y+1 < h && grid2[k+w] == tc) + list[i++] = k+w; + } +#ifdef OUTPUT_SOLUTION + printf("\n"); +#endif + + /* + * Check that we've filled the same number of + * tc squares as we originally found. + */ + assert(j == ntc); + } + + memcpy(grid, grid2, wh * sizeof(int)); + + break; /* done it! */ + } + +#ifdef GENERATION_DIAGNOSTICS + { + int x,y; + printf("n=%d\n", n); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (grid[y*w+x] == 0) + printf("-"); + else + printf("%d", grid[y*w+x]); + } + printf("\n"); + } + } +#endif + + if (n < 0) + break; + } + + ok = TRUE; + for (i = 0; i < wh; i++) + if (grid[i] == 0) { + ok = FALSE; + failures++; +#if defined GENERATION_DIAGNOSTICS || defined SHOW_INCOMPLETE + { + int x,y; + printf("incomplete grid:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (grid[y*w+x] == 0) + printf("-"); + else + printf("%d", grid[y*w+x]); + } + printf("\n"); + } + } +#endif + break; + } + + } while (!ok); + +#if defined GENERATION_DIAGNOSTICS || defined COUNT_FAILURES + printf("%d failures\n", failures); +#endif +#ifdef GENERATION_DIAGNOSTICS + { + int x,y; + printf("final grid:\n"); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + printf("%d", grid[y*w+x]); + } + printf("\n"); + } + } +#endif + + sfree(grid2); + sfree(list); +} + +/* + * Not-guaranteed-soluble grid generator; kept as a legacy, and in + * case someone finds the slightly odd quality of the guaranteed- + * soluble grids to be aesthetically displeasing or finds its CPU + * utilisation to be excessive. + */ +static void gen_grid_random(int w, int h, int nc, int *grid, random_state *rs) +{ + int i, j, c; + int n = w * h; + + for (i = 0; i < n; i++) + grid[i] = 0; + + /* + * Our sole concession to not gratuitously generating insoluble + * grids is to ensure we have at least two of every colour. + */ + for (c = 1; c <= nc; c++) { + for (j = 0; j < 2; j++) { + do { + i = (int)random_upto(rs, n); + } while (grid[i] != 0); + grid[i] = c; + } + } + + /* + * Fill in the rest of the grid at random. + */ + for (i = 0; i < n; i++) { + if (grid[i] == 0) + grid[i] = (int)random_upto(rs, nc)+1; + } +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + char *ret; + int n, i, retlen, *tiles; + + n = params->w * params->h; + tiles = snewn(n, int); + + if (params->soluble) + gen_grid(params->w, params->h, params->ncols, tiles, rs); + else + gen_grid_random(params->w, params->h, params->ncols, tiles, rs); + + ret = NULL; + retlen = 0; + for (i = 0; i < n; i++) { + char buf[80]; + int k; + + k = sprintf(buf, "%d,", tiles[i]); + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + ret[retlen-1] = '\0'; /* delete last comma */ + + sfree(tiles); + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int area = params->w * params->h, i; + const char *p = desc; + + for (i = 0; i < area; i++) { + const char *q = p; + int n; + + if (!isdigit((unsigned char)*p)) + return "Not enough numbers in string"; + while (isdigit((unsigned char)*p)) p++; + + if (i < area-1 && *p != ',') + return "Expected comma after number"; + else if (i == area-1 && *p) + return "Excess junk at end of string"; + + n = atoi(q); + if (n < 0 || n > params->ncols) + return "Colour out of range"; + + if (*p) p++; /* eat comma */ + } + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + const char *p = desc; + int i; + + state->params = *params; /* struct copy */ + state->n = state->params.w * state->params.h; + state->tiles = snewn(state->n, int); + + for (i = 0; i < state->n; i++) { + assert(*p); + state->tiles[i] = atoi(p); + while (*p && *p != ',') + p++; + if (*p) p++; /* eat comma */ + } + state->complete = state->impossible = 0; + state->score = 0; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + *ret = *state; /* structure copy, except... */ + + ret->tiles = snewn(state->n, int); + memcpy(ret->tiles, state->tiles, state->n * sizeof(int)); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->tiles); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return NULL; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret, *p; + int x, y, maxlen; + + maxlen = state->params.h * (state->params.w + 1); + ret = snewn(maxlen+1, char); + p = ret; + + for (y = 0; y < state->params.h; y++) { + for (x = 0; x < state->params.w; x++) { + int t = TILE(state,x,y); + if (t <= 0) *p++ = ' '; + else if (t < 10) *p++ = '0'+t; + else *p++ = 'a'+(t-10); + } + *p++ = '\n'; + } + assert(p - ret == maxlen); + *p = '\0'; + return ret; +} + +struct game_ui { + struct game_params params; + int *tiles; /* selected-ness only */ + int nselected; + int xsel, ysel, displaysel; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->params = state->params; /* structure copy */ + ui->tiles = snewn(state->n, int); + memset(ui->tiles, 0, state->n*sizeof(int)); + ui->nselected = 0; + + ui->xsel = ui->ysel = ui->displaysel = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui->tiles); + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void sel_clear(game_ui *ui, const game_state *state) +{ + int i; + + for (i = 0; i < state->n; i++) + ui->tiles[i] &= ~TILE_SELECTED; + ui->nselected = 0; +} + + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + sel_clear(ui, newstate); + + /* + * If the game state has just changed into an unplayable one + * (either completed or impossible), we vanish the keyboard- + * control cursor. + */ + if (newstate->complete || newstate->impossible) + ui->displaysel = 0; +} + +static char *sel_movedesc(game_ui *ui, const game_state *state) +{ + int i; + char *ret, *sep, buf[80]; + int retlen, retsize; + + retsize = 256; + ret = snewn(retsize, char); + retlen = 0; + ret[retlen++] = 'M'; + sep = ""; + + for (i = 0; i < state->n; i++) { + if (ui->tiles[i] & TILE_SELECTED) { + sprintf(buf, "%s%d", sep, i); + sep = ","; + if (retlen + (int)strlen(buf) >= retsize) { + retsize = retlen + strlen(buf) + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += strlen(buf); + + ui->tiles[i] &= ~TILE_SELECTED; + } + } + ui->nselected = 0; + + assert(retlen < retsize); + ret[retlen++] = '\0'; + return sresize(ret, retlen, char); +} + +static void sel_expand(game_ui *ui, const game_state *state, int tx, int ty) +{ + int ns = 1, nadded, x, y, c; + + TILE(ui,tx,ty) |= TILE_SELECTED; + do { + nadded = 0; + + for (x = 0; x < state->params.w; x++) { + for (y = 0; y < state->params.h; y++) { + if (x == tx && y == ty) continue; + if (ISSEL(ui,x,y)) continue; + + c = COL(state,x,y); + if ((x > 0) && + ISSEL(ui,x-1,y) && COL(state,x-1,y) == c) { + TILE(ui,x,y) |= TILE_SELECTED; + nadded++; + continue; + } + + if ((x+1 < state->params.w) && + ISSEL(ui,x+1,y) && COL(state,x+1,y) == c) { + TILE(ui,x,y) |= TILE_SELECTED; + nadded++; + continue; + } + + if ((y > 0) && + ISSEL(ui,x,y-1) && COL(state,x,y-1) == c) { + TILE(ui,x,y) |= TILE_SELECTED; + nadded++; + continue; + } + + if ((y+1 < state->params.h) && + ISSEL(ui,x,y+1) && COL(state,x,y+1) == c) { + TILE(ui,x,y) |= TILE_SELECTED; + nadded++; + continue; + } + } + } + ns += nadded; + } while (nadded > 0); + + if (ns > 1) { + ui->nselected = ns; + } else { + sel_clear(ui, state); + } +} + +static int sg_emptycol(game_state *ret, int x) +{ + int y; + for (y = 0; y < ret->params.h; y++) { + if (COL(ret,x,y)) return 0; + } + return 1; +} + + +static void sg_snuggle(game_state *ret) +{ + int x,y, ndone; + + /* make all unsupported tiles fall down. */ + do { + ndone = 0; + for (x = 0; x < ret->params.w; x++) { + for (y = ret->params.h-1; y > 0; y--) { + if (COL(ret,x,y) != 0) continue; + if (COL(ret,x,y-1) != 0) { + SWAPTILE(ret,x,y,x,y-1); + ndone++; + } + } + } + } while (ndone); + + /* shuffle all columns as far left as they can go. */ + do { + ndone = 0; + for (x = 0; x < ret->params.w-1; x++) { + if (sg_emptycol(ret,x) && !sg_emptycol(ret,x+1)) { + ndone++; + for (y = 0; y < ret->params.h; y++) { + SWAPTILE(ret,x,y,x+1,y); + } + } + } + } while (ndone); +} + +static void sg_check(game_state *ret) +{ + int x,y, complete = 1, impossible = 1; + + for (x = 0; x < ret->params.w; x++) { + for (y = 0; y < ret->params.h; y++) { + if (COL(ret,x,y) == 0) + continue; + complete = 0; + if (x+1 < ret->params.w) { + if (COL(ret,x,y) == COL(ret,x+1,y)) + impossible = 0; + } + if (y+1 < ret->params.h) { + if (COL(ret,x,y) == COL(ret,x,y+1)) + impossible = 0; + } + } + } + ret->complete = complete; + ret->impossible = impossible; +} + +struct game_drawstate { + int started, bgcolour; + int tileinner, tilegap; + int *tiles; /* contains colour and SELECTED. */ +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int tx, ty; + char *ret = ""; + + ui->displaysel = 0; + + if (button == RIGHT_BUTTON || button == LEFT_BUTTON) { + tx = FROMCOORD(x); ty= FROMCOORD(y); + } else if (IS_CURSOR_MOVE(button)) { + int dx = 0, dy = 0; + ui->displaysel = 1; + dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0); + dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0); + ui->xsel = (ui->xsel + state->params.w + dx) % state->params.w; + ui->ysel = (ui->ysel + state->params.h + dy) % state->params.h; + return ret; + } else if (IS_CURSOR_SELECT(button)) { + ui->displaysel = 1; + tx = ui->xsel; + ty = ui->ysel; + } else + return NULL; + + if (tx < 0 || tx >= state->params.w || ty < 0 || ty >= state->params.h) + return NULL; + if (COL(state, tx, ty) == 0) return NULL; + + if (ISSEL(ui,tx,ty)) { + if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) + sel_clear(ui, state); + else + ret = sel_movedesc(ui, state); + } else { + sel_clear(ui, state); /* might be no-op */ + sel_expand(ui, state, tx, ty); + } + + return ret; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int i, n; + game_state *ret; + + if (move[0] == 'M') { + ret = dup_game(from); + + n = 0; + move++; + + while (*move) { + i = atoi(move); + if (i < 0 || i >= ret->n) { + free_game(ret); + return NULL; + } + n++; + ret->tiles[i] = 0; + + while (*move && isdigit((unsigned char)*move)) move++; + if (*move == ',') move++; + } + + ret->score += npoints(&ret->params, n); + + sg_snuggle(ret); /* shifts blanks down and to the left */ + sg_check(ret); /* checks for completeness or impossibility */ + + return ret; + } else + return NULL; /* couldn't parse move string */ +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilegap = 2; + ds->tileinner = tilesize - ds->tilegap; +} + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up tile size variables for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(NULL, ds, params, tilesize); + + *x = TILE_SIZE * params->w + 2 * BORDER - TILE_GAP; + *y = TILE_SIZE * params->h + 2 * BORDER - TILE_GAP; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_1 * 3 + 0] = 0.0F; + ret[COL_1 * 3 + 1] = 0.0F; + ret[COL_1 * 3 + 2] = 1.0F; + + ret[COL_2 * 3 + 0] = 0.0F; + ret[COL_2 * 3 + 1] = 0.5F; + ret[COL_2 * 3 + 2] = 0.0F; + + ret[COL_3 * 3 + 0] = 1.0F; + ret[COL_3 * 3 + 1] = 0.0F; + ret[COL_3 * 3 + 2] = 0.0F; + + ret[COL_4 * 3 + 0] = 1.0F; + ret[COL_4 * 3 + 1] = 1.0F; + ret[COL_4 * 3 + 2] = 0.0F; + + ret[COL_5 * 3 + 0] = 1.0F; + ret[COL_5 * 3 + 1] = 0.0F; + ret[COL_5 * 3 + 2] = 1.0F; + + ret[COL_6 * 3 + 0] = 0.0F; + ret[COL_6 * 3 + 1] = 1.0F; + ret[COL_6 * 3 + 2] = 1.0F; + + ret[COL_7 * 3 + 0] = 0.5F; + ret[COL_7 * 3 + 1] = 0.5F; + ret[COL_7 * 3 + 2] = 1.0F; + + ret[COL_8 * 3 + 0] = 0.5F; + ret[COL_8 * 3 + 1] = 1.0F; + ret[COL_8 * 3 + 2] = 0.5F; + + ret[COL_9 * 3 + 0] = 1.0F; + ret[COL_9 * 3 + 1] = 0.5F; + ret[COL_9 * 3 + 2] = 0.5F; + + ret[COL_IMPOSSIBLE * 3 + 0] = 0.0F; + ret[COL_IMPOSSIBLE * 3 + 1] = 0.0F; + ret[COL_IMPOSSIBLE * 3 + 2] = 0.0F; + + ret[COL_SEL * 3 + 0] = 1.0F; + ret[COL_SEL * 3 + 1] = 1.0F; + ret[COL_SEL * 3 + 2] = 1.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 1] = 1.0F; + ret[COL_HIGHLIGHT * 3 + 2] = 1.0F; + + ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F; + ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F; + ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->started = 0; + ds->tileinner = ds->tilegap = 0; /* not decided yet */ + ds->tiles = snewn(state->n, int); + ds->bgcolour = -1; + for (i = 0; i < state->n; i++) + ds->tiles[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->tiles); + sfree(ds); +} + +/* Drawing routing for the tile at (x,y) is responsible for drawing + * itself and the gaps to its right and below. If we're the same colour + * as the tile to our right, then we fill in the gap; ditto below, and if + * both then we fill the teeny tiny square in the corner as well. + */ + +static void tile_redraw(drawing *dr, game_drawstate *ds, + int x, int y, int dright, int dbelow, + int tile, int bgcolour) +{ + int outer = bgcolour, inner = outer, col = tile & TILE_COLMASK; + + if (col) { + if (tile & TILE_IMPOSSIBLE) { + outer = col; + inner = COL_IMPOSSIBLE; + } else if (tile & TILE_SELECTED) { + outer = COL_SEL; + inner = col; + } else { + outer = inner = col; + } + } + draw_rect(dr, COORD(x), COORD(y), TILE_INNER, TILE_INNER, outer); + draw_rect(dr, COORD(x)+TILE_INNER/4, COORD(y)+TILE_INNER/4, + TILE_INNER/2, TILE_INNER/2, inner); + + if (dright) + draw_rect(dr, COORD(x)+TILE_INNER, COORD(y), TILE_GAP, TILE_INNER, + (tile & TILE_JOINRIGHT) ? outer : bgcolour); + if (dbelow) + draw_rect(dr, COORD(x), COORD(y)+TILE_INNER, TILE_INNER, TILE_GAP, + (tile & TILE_JOINDOWN) ? outer : bgcolour); + if (dright && dbelow) + draw_rect(dr, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, TILE_GAP, TILE_GAP, + (tile & TILE_JOINDIAG) ? outer : bgcolour); + + if (tile & TILE_HASSEL) { + int sx = COORD(x)+2, sy = COORD(y)+2, ssz = TILE_INNER-5; + int scol = (outer == COL_SEL) ? COL_LOWLIGHT : COL_HIGHLIGHT; + draw_line(dr, sx, sy, sx+ssz, sy, scol); + draw_line(dr, sx+ssz, sy, sx+ssz, sy+ssz, scol); + draw_line(dr, sx+ssz, sy+ssz, sx, sy+ssz, scol); + draw_line(dr, sx, sy+ssz, sx, sy, scol); + } + + draw_update(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int bgcolour, x, y; + + /* This was entirely cloned from fifteen.c; it should probably be + * moved into some generic 'draw-recessed-rectangle' utility fn. */ + if (!ds->started) { + int coords[10]; + + draw_rect(dr, 0, 0, + TILE_SIZE * state->params.w + 2 * BORDER, + TILE_SIZE * state->params.h + 2 * BORDER, COL_BACKGROUND); + draw_update(dr, 0, 0, + TILE_SIZE * state->params.w + 2 * BORDER, + TILE_SIZE * state->params.h + 2 * BORDER); + + /* + * Recessed area containing the whole puzzle. + */ + coords[0] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; + coords[1] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; + coords[2] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; + coords[3] = COORD(0) - HIGHLIGHT_WIDTH; + coords[4] = coords[2] - TILE_SIZE; + coords[5] = coords[3] + TILE_SIZE; + coords[8] = COORD(0) - HIGHLIGHT_WIDTH; + coords[9] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP; + coords[6] = coords[8] + TILE_SIZE; + coords[7] = coords[9] - TILE_SIZE; + draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); + + coords[1] = COORD(0) - HIGHLIGHT_WIDTH; + coords[0] = COORD(0) - HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); + + ds->started = 1; + } + + if (flashtime > 0.0) { + int frame = (int)(flashtime / FLASH_FRAME); + bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); + } else + bgcolour = COL_BACKGROUND; + + for (x = 0; x < state->params.w; x++) { + for (y = 0; y < state->params.h; y++) { + int i = (state->params.w * y) + x; + int col = COL(state,x,y), tile = col; + int dright = (x+1 < state->params.w); + int dbelow = (y+1 < state->params.h); + + tile |= ISSEL(ui,x,y); + if (state->impossible) + tile |= TILE_IMPOSSIBLE; + if (dright && COL(state,x+1,y) == col) + tile |= TILE_JOINRIGHT; + if (dbelow && COL(state,x,y+1) == col) + tile |= TILE_JOINDOWN; + if ((tile & TILE_JOINRIGHT) && (tile & TILE_JOINDOWN) && + COL(state,x+1,y+1) == col) + tile |= TILE_JOINDIAG; + + if (ui->displaysel && ui->xsel == x && ui->ysel == y) + tile |= TILE_HASSEL; + + /* For now we're never expecting oldstate at all (because we have + * no animation); when we do we might well want to be looking + * at the tile colours from oldstate, not state. */ + if ((oldstate && COL(oldstate,x,y) != col) || + (ds->bgcolour != bgcolour) || + (tile != ds->tiles[i])) { + tile_redraw(dr, ds, x, y, dright, dbelow, tile, bgcolour); + ds->tiles[i] = tile; + } + } + } + ds->bgcolour = bgcolour; + + { + char status[255], score[80]; + + sprintf(score, "Score: %d", state->score); + + if (state->complete) + sprintf(status, "COMPLETE! %s", score); + else if (state->impossible) + sprintf(status, "Cannot move! %s", score); + else if (ui->nselected) + sprintf(status, "%s Selected: %d (%d)", + score, ui->nselected, npoints(&state->params, ui->nselected)); + else + sprintf(status, "%s", score); + status_bar(dr, status); + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if ((!oldstate->complete && newstate->complete) || + (!oldstate->impossible && newstate->impossible)) + return 2 * FLASH_FRAME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + /* + * Dead-end situations are assumed to be rescuable by Undo, so we + * don't bother to identify them and return -1. + */ + return state->complete ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame samegame +#endif + +const struct game thegame = { + "Same Game", "games.samegame", "samegame", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + FALSE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/signpost.R b/apps/plugins/puzzles/signpost.R new file mode 100644 index 0000000000..09ea367d57 --- /dev/null +++ b/apps/plugins/puzzles/signpost.R @@ -0,0 +1,23 @@ +# -*- makefile -*- + +SIGNPOST_EXTRA = dsf + +signpost : [X] GTK COMMON signpost SIGNPOST_EXTRA signpost-icon|no-icon +signpost : [G] WINDOWS COMMON signpost SIGNPOST_EXTRA signpost.res|noicon.res + +signpostsolver : [U] signpost[STANDALONE_SOLVER] SIGNPOST_EXTRA STANDALONE m.lib +signpostsolver : [C] signpost[STANDALONE_SOLVER] SIGNPOST_EXTRA STANDALONE + +ALL += signpost[COMBINED] SIGNPOST_EXTRA + +!begin am gtk +GAMES += signpost +!end + +!begin >list.c + A(signpost) \ +!end + +!begin >gamedesc.txt +signpost:signpost.exe:Signpost:Square-connecting puzzle:Connect the squares into a path following the arrows. +!end diff --git a/apps/plugins/puzzles/signpost.c b/apps/plugins/puzzles/signpost.c new file mode 100644 index 0000000000..a2e431e746 --- /dev/null +++ b/apps/plugins/puzzles/signpost.c @@ -0,0 +1,2480 @@ +/* + * signpost.c: implementation of the janko game 'arrow path' + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) +#define BLITTER_SIZE TILE_SIZE +#define BORDER (TILE_SIZE / 2) + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h) + +#define FLASH_SPIN 0.7F + +#define NBACKGROUNDS 16 + +enum { + COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT, + COL_GRID, COL_CURSOR, COL_ERROR, COL_DRAG_ORIGIN, + COL_ARROW, COL_ARROW_BG_DIM, + COL_NUMBER, COL_NUMBER_SET, COL_NUMBER_SET_MID, + COL_B0, /* background colours */ + COL_M0 = COL_B0 + 1*NBACKGROUNDS, /* mid arrow colours */ + COL_D0 = COL_B0 + 2*NBACKGROUNDS, /* dim arrow colours */ + COL_X0 = COL_B0 + 3*NBACKGROUNDS, /* dim arrow colours */ + NCOLOURS = COL_B0 + 4*NBACKGROUNDS +}; + +struct game_params { + int w, h; + int force_corner_start; +}; + +enum { DIR_N = 0, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_MAX }; +static const char *dirstrings[8] = { "N ", "NE", "E ", "SE", "S ", "SW", "W ", "NW" }; + +static const int dxs[DIR_MAX] = { 0, 1, 1, 1, 0, -1, -1, -1 }; +static const int dys[DIR_MAX] = { -1, -1, 0, 1, 1, 1, 0, -1 }; + +#define DIR_OPPOSITE(d) ((d+4)%8) + +struct game_state { + int w, h, n; + int completed, used_solve, impossible; + int *dirs; /* direction enums, size n */ + int *nums; /* numbers, size n */ + unsigned int *flags; /* flags, size n */ + int *next, *prev; /* links to other cell indexes, size n (-1 absent) */ + int *dsf; /* connects regions with a dsf. */ + int *numsi; /* for each number, which index is it in? (-1 absent) */ +}; + +#define FLAG_IMMUTABLE 1 +#define FLAG_ERROR 2 + +/* --- Generally useful functions --- */ + +#define ISREALNUM(state, num) ((num) > 0 && (num) <= (state)->n) + +static int whichdir(int fromx, int fromy, int tox, int toy) +{ + int i, dx, dy; + + dx = tox - fromx; + dy = toy - fromy; + + if (dx && dy && abs(dx) != abs(dy)) return -1; + + if (dx) dx = dx / abs(dx); /* limit to (-1, 0, 1) */ + if (dy) dy = dy / abs(dy); /* ditto */ + + for (i = 0; i < DIR_MAX; i++) { + if (dx == dxs[i] && dy == dys[i]) return i; + } + return -1; +} + +static int whichdiri(game_state *state, int fromi, int toi) +{ + int w = state->w; + return whichdir(fromi%w, fromi/w, toi%w, toi/w); +} + +static int ispointing(const game_state *state, int fromx, int fromy, + int tox, int toy) +{ + int w = state->w, dir = state->dirs[fromy*w+fromx]; + + /* (by convention) squares do not point to themselves. */ + if (fromx == tox && fromy == toy) return 0; + + /* the final number points to nothing. */ + if (state->nums[fromy*w + fromx] == state->n) return 0; + + while (1) { + if (!INGRID(state, fromx, fromy)) return 0; + if (fromx == tox && fromy == toy) return 1; + fromx += dxs[dir]; fromy += dys[dir]; + } + return 0; /* not reached */ +} + +static int ispointingi(game_state *state, int fromi, int toi) +{ + int w = state->w; + return ispointing(state, fromi%w, fromi/w, toi%w, toi/w); +} + +/* Taking the number 'num', work out the gap between it and the next + * available number up or down (depending on d). Return 1 if the region + * at (x,y) will fit in that gap, or 0 otherwise. */ +static int move_couldfit(const game_state *state, int num, int d, int x, int y) +{ + int n, gap, i = y*state->w+x, sz; + + assert(d != 0); + /* The 'gap' is the number of missing numbers in the grid between + * our number and the next one in the sequence (up or down), or + * the end of the sequence (if we happen not to have 1/n present) */ + for (n = num + d, gap = 0; + ISREALNUM(state, n) && state->numsi[n] == -1; + n += d, gap++) ; /* empty loop */ + + if (gap == 0) { + /* no gap, so the only allowable move is that that directly + * links the two numbers. */ + n = state->nums[i]; + return (n == num+d) ? 0 : 1; + } + if (state->prev[i] == -1 && state->next[i] == -1) + return 1; /* single unconnected square, always OK */ + + sz = dsf_size(state->dsf, i); + return (sz > gap) ? 0 : 1; +} + +static int isvalidmove(const game_state *state, int clever, + int fromx, int fromy, int tox, int toy) +{ + int w = state->w, from = fromy*w+fromx, to = toy*w+tox; + int nfrom, nto; + + if (!INGRID(state, fromx, fromy) || !INGRID(state, tox, toy)) + return 0; + + /* can only move where we point */ + if (!ispointing(state, fromx, fromy, tox, toy)) + return 0; + + nfrom = state->nums[from]; nto = state->nums[to]; + + /* can't move _from_ the preset final number, or _to_ the preset 1. */ + if (((nfrom == state->n) && (state->flags[from] & FLAG_IMMUTABLE)) || + ((nto == 1) && (state->flags[to] & FLAG_IMMUTABLE))) + return 0; + + /* can't create a new connection between cells in the same region + * as that would create a loop. */ + if (dsf_canonify(state->dsf, from) == dsf_canonify(state->dsf, to)) + return 0; + + /* if both cells are actual numbers, can't drag if we're not + * one digit apart. */ + if (ISREALNUM(state, nfrom) && ISREALNUM(state, nto)) { + if (nfrom != nto-1) + return 0; + } else if (clever && ISREALNUM(state, nfrom)) { + if (!move_couldfit(state, nfrom, +1, tox, toy)) + return 0; + } else if (clever && ISREALNUM(state, nto)) { + if (!move_couldfit(state, nto, -1, fromx, fromy)) + return 0; + } + + return 1; +} + +static void makelink(game_state *state, int from, int to) +{ + if (state->next[from] != -1) + state->prev[state->next[from]] = -1; + state->next[from] = to; + + if (state->prev[to] != -1) + state->next[state->prev[to]] = -1; + state->prev[to] = from; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + if (params->w * params->h >= 100) return 0; + return 1; +} + +static char *game_text_format(const game_state *state) +{ + int len = state->h * 2 * (4*state->w + 1) + state->h + 2; + int x, y, i, num, n, set; + char *ret, *p; + + p = ret = snewn(len, char); + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->h; x++) { + i = y*state->w+x; + *p++ = dirstrings[state->dirs[i]][0]; + *p++ = dirstrings[state->dirs[i]][1]; + *p++ = (state->flags[i] & FLAG_IMMUTABLE) ? 'I' : ' '; + *p++ = ' '; + } + *p++ = '\n'; + for (x = 0; x < state->h; x++) { + i = y*state->w+x; + num = state->nums[i]; + if (num == 0) { + *p++ = ' '; + *p++ = ' '; + *p++ = ' '; + } else { + n = num % (state->n+1); + set = num / (state->n+1); + + assert(n <= 99); /* two digits only! */ + + if (set != 0) + *p++ = set+'a'-1; + + *p++ = (n >= 10) ? ('0' + (n/10)) : ' '; + *p++ = '0' + (n%10); + + if (set == 0) + *p++ = ' '; + } + *p++ = ' '; + } + *p++ = '\n'; + *p++ = '\n'; + } + *p++ = '\0'; + + return ret; +} + +static void debug_state(const char *desc, game_state *state) +{ +#ifdef DEBUGGING + char *dbg; + if (state->n >= 100) { + debug(("[ no game_text_format for this size ]")); + return; + } + dbg = game_text_format(state); + debug(("%s\n%s", desc, dbg)); + sfree(dbg); +#endif +} + + +static void strip_nums(game_state *state) { + int i; + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & FLAG_IMMUTABLE)) + state->nums[i] = 0; + } + memset(state->next, -1, state->n*sizeof(int)); + memset(state->prev, -1, state->n*sizeof(int)); + memset(state->numsi, -1, (state->n+1)*sizeof(int)); + dsf_init(state->dsf, state->n); +} + +static int check_nums(game_state *orig, game_state *copy, int only_immutable) +{ + int i, ret = 1; + assert(copy->n == orig->n); + for (i = 0; i < copy->n; i++) { + if (only_immutable && !copy->flags[i] & FLAG_IMMUTABLE) continue; + assert(copy->nums[i] >= 0); + assert(copy->nums[i] <= copy->n); + if (copy->nums[i] != orig->nums[i]) { + debug(("check_nums: (%d,%d) copy=%d, orig=%d.", + i%orig->w, i/orig->w, copy->nums[i], orig->nums[i])); + ret = 0; + } + } + return ret; +} + +/* --- Game parameter/presets functions --- */ + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + ret->w = ret->h = 4; + ret->force_corner_start = 1; + + return ret; +} + +static const struct game_params signpost_presets[] = { + { 4, 4, 1 }, + { 4, 4, 0 }, + { 5, 5, 1 }, + { 5, 5, 0 }, + { 6, 6, 1 }, + { 7, 7, 1 } +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(signpost_presets)) + return FALSE; + + ret = default_params(); + *ret = signpost_presets[i]; + *params = ret; + + sprintf(buf, "%dx%d%s", ret->w, ret->h, + ret->force_corner_start ? "" : ", free ends"); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + ret->force_corner_start = 0; + if (*string == 'c') { + string++; + ret->force_corner_start = 1; + } + +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + if (full) + sprintf(data, "%dx%d%s", params->w, params->h, + params->force_corner_start ? "c" : ""); + else + sprintf(data, "%dx%d", params->w, params->h); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Start and end in corners"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->force_corner_start; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->force_corner_start = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 1) return "Width must be at least one"; + if (params->h < 1) return "Height must be at least one"; + if (full && params->w == 1 && params->h == 1) + /* The UI doesn't let us move these from unsolved to solved, + * so we disallow generating (but not playing) them. */ + return "Width and height cannot both be one"; + return NULL; +} + +/* --- Game description string generation and unpicking --- */ + +static void blank_game_into(game_state *state) +{ + memset(state->dirs, 0, state->n*sizeof(int)); + memset(state->nums, 0, state->n*sizeof(int)); + memset(state->flags, 0, state->n*sizeof(unsigned int)); + memset(state->next, -1, state->n*sizeof(int)); + memset(state->prev, -1, state->n*sizeof(int)); + memset(state->numsi, -1, (state->n+1)*sizeof(int)); +} + +static game_state *blank_game(int w, int h) +{ + game_state *state = snew(game_state); + + memset(state, 0, sizeof(game_state)); + state->w = w; + state->h = h; + state->n = w*h; + + state->dirs = snewn(state->n, int); + state->nums = snewn(state->n, int); + state->flags = snewn(state->n, unsigned int); + state->next = snewn(state->n, int); + state->prev = snewn(state->n, int); + state->dsf = snew_dsf(state->n); + state->numsi = snewn(state->n+1, int); + + blank_game_into(state); + + return state; +} + +static void dup_game_to(game_state *to, const game_state *from) +{ + to->completed = from->completed; + to->used_solve = from->used_solve; + to->impossible = from->impossible; + + memcpy(to->dirs, from->dirs, to->n*sizeof(int)); + memcpy(to->flags, from->flags, to->n*sizeof(unsigned int)); + memcpy(to->nums, from->nums, to->n*sizeof(int)); + + memcpy(to->next, from->next, to->n*sizeof(int)); + memcpy(to->prev, from->prev, to->n*sizeof(int)); + + memcpy(to->dsf, from->dsf, to->n*sizeof(int)); + memcpy(to->numsi, from->numsi, (to->n+1)*sizeof(int)); +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = blank_game(state->w, state->h); + dup_game_to(ret, state); + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->dirs); + sfree(state->nums); + sfree(state->flags); + sfree(state->next); + sfree(state->prev); + sfree(state->dsf); + sfree(state->numsi); + sfree(state); +} + +static void unpick_desc(const game_params *params, const char *desc, + game_state **sout, char **mout) +{ + game_state *state = blank_game(params->w, params->h); + char *msg = NULL, c; + int num = 0, i = 0; + + while (*desc) { + if (i >= state->n) { + msg = "Game description longer than expected"; + goto done; + } + + c = *desc; + if (isdigit((unsigned char)c)) { + num = (num*10) + (int)(c-'0'); + if (num > state->n) { + msg = "Number too large"; + goto done; + } + } else if ((c-'a') >= 0 && (c-'a') < DIR_MAX) { + state->nums[i] = num; + state->flags[i] = num ? FLAG_IMMUTABLE : 0; + num = 0; + + state->dirs[i] = c - 'a'; + i++; + } else if (!*desc) { + msg = "Game description shorter than expected"; + goto done; + } else { + msg = "Game description contains unexpected characters"; + goto done; + } + desc++; + } + if (i < state->n) { + msg = "Game description shorter than expected"; + goto done; + } + +done: + if (msg) { /* sth went wrong. */ + if (mout) *mout = msg; + free_game(state); + } else { + if (mout) *mout = NULL; + if (sout) *sout = state; + else free_game(state); + } +} + +static char *generate_desc(game_state *state, int issolve) +{ + char *ret, buf[80]; + int retlen, i, k; + + ret = NULL; retlen = 0; + if (issolve) { + ret = sresize(ret, 2, char); + ret[0] = 'S'; ret[1] = '\0'; + retlen += 1; + } + for (i = 0; i < state->n; i++) { + if (state->nums[i]) + k = sprintf(buf, "%d%c", state->nums[i], (int)(state->dirs[i]+'a')); + else + k = sprintf(buf, "%c", (int)(state->dirs[i]+'a')); + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + return ret; +} + +/* --- Game generation --- */ + +/* Fills in preallocated arrays ai (indices) and ad (directions) + * showing all non-numbered cells adjacent to index i, returns length */ +/* This function has been somewhat optimised... */ +static int cell_adj(game_state *state, int i, int *ai, int *ad) +{ + int n = 0, a, x, y, sx, sy, dx, dy, newi; + int w = state->w, h = state->h; + + sx = i % w; sy = i / w; + + for (a = 0; a < DIR_MAX; a++) { + x = sx; y = sy; + dx = dxs[a]; dy = dys[a]; + while (1) { + x += dx; y += dy; + if (x < 0 || y < 0 || x >= w || y >= h) break; + + newi = y*w + x; + if (state->nums[newi] == 0) { + ai[n] = newi; + ad[n] = a; + n++; + } + } + } + return n; +} + +static int new_game_fill(game_state *state, random_state *rs, + int headi, int taili) +{ + int nfilled, an, ret = 0, j; + int *aidx, *adir; + + aidx = snewn(state->n, int); + adir = snewn(state->n, int); + + debug(("new_game_fill: headi=%d, taili=%d.", headi, taili)); + + memset(state->nums, 0, state->n*sizeof(int)); + + state->nums[headi] = 1; + state->nums[taili] = state->n; + + state->dirs[taili] = 0; + nfilled = 2; + assert(state->n > 1); + + while (nfilled < state->n) { + /* Try and expand _from_ headi; keep going if there's only one + * place to go to. */ + an = cell_adj(state, headi, aidx, adir); + do { + if (an == 0) goto done; + j = random_upto(rs, an); + state->dirs[headi] = adir[j]; + state->nums[aidx[j]] = state->nums[headi] + 1; + nfilled++; + headi = aidx[j]; + an = cell_adj(state, headi, aidx, adir); + } while (an == 1); + + if (nfilled == state->n) break; + + /* Try and expand _to_ taili; keep going if there's only one + * place to go to. */ + an = cell_adj(state, taili, aidx, adir); + do { + if (an == 0) goto done; + j = random_upto(rs, an); + state->dirs[aidx[j]] = DIR_OPPOSITE(adir[j]); + state->nums[aidx[j]] = state->nums[taili] - 1; + nfilled++; + taili = aidx[j]; + an = cell_adj(state, taili, aidx, adir); + } while (an == 1); + } + /* If we get here we have headi and taili set but unconnected + * by direction: we need to set headi's direction so as to point + * at taili. */ + state->dirs[headi] = whichdiri(state, headi, taili); + + /* it could happen that our last two weren't in line; if that's the + * case, we have to start again. */ + if (state->dirs[headi] != -1) ret = 1; + +done: + sfree(aidx); + sfree(adir); + return ret; +} + +/* Better generator: with the 'generate, sprinkle numbers, solve, + * repeat' algorithm we're _never_ generating anything greater than + * 6x6, and spending all of our time in new_game_fill (and very little + * in solve_state). + * + * So, new generator steps: + * generate the grid, at random (same as now). Numbers 1 and N get + immutable flag immediately. + * squirrel that away for the solved state. + * + * (solve:) Try and solve it. + * If we solved it, we're done: + * generate the description from current immutable numbers, + * free stuff that needs freeing, + * return description + solved state. + * If we didn't solve it: + * count #tiles in state we've made deductions about. + * while (1): + * randomise a scratch array. + * for each index in scratch (in turn): + * if the cell isn't empty, continue (through scratch array) + * set number + immutable in state. + * try and solve state. + * if we've solved it, we're done. + * otherwise, count #tiles. If it's more than we had before: + * good, break from this loop and re-randomise. + * otherwise (number didn't help): + * remove number and try next in scratch array. + * if we've got to the end of the scratch array, no luck: + free everything we need to, and go back to regenerate the grid. + */ + +static int solve_state(game_state *state); + +static void debug_desc(const char *what, game_state *state) +{ +#if DEBUGGING + { + char *desc = generate_desc(state, 0); + debug(("%s game state: %dx%d:%s", what, state->w, state->h, desc)); + sfree(desc); + } +#endif +} + +/* Expects a fully-numbered game_state on input, and makes sure + * FLAG_IMMUTABLE is only set on those numbers we need to solve + * (as for a real new-game); returns 1 if it managed + * this (such that it could solve it), or 0 if not. */ +static int new_game_strip(game_state *state, random_state *rs) +{ + int *scratch, i, j, ret = 1; + game_state *copy = dup_game(state); + + debug(("new_game_strip.")); + + strip_nums(copy); + debug_desc("Stripped", copy); + + if (solve_state(copy) > 0) { + debug(("new_game_strip: soluble immediately after strip.")); + free_game(copy); + return 1; + } + + scratch = snewn(state->n, int); + for (i = 0; i < state->n; i++) scratch[i] = i; + shuffle(scratch, state->n, sizeof(int), rs); + + /* This is scungy. It might just be quick enough. + * It goes through, adding set numbers in empty squares + * until either we run out of empty squares (in the one + * we're half-solving) or else we solve it properly. + * NB that we run the entire solver each time, which + * strips the grid beforehand; we will save time if we + * avoid that. */ + for (i = 0; i < state->n; i++) { + j = scratch[i]; + if (copy->nums[j] > 0 && copy->nums[j] <= state->n) + continue; /* already solved to a real number here. */ + assert(state->nums[j] <= state->n); + debug(("new_game_strip: testing add IMMUTABLE number %d at square (%d,%d).", + state->nums[j], j%state->w, j/state->w)); + copy->nums[j] = state->nums[j]; + copy->flags[j] |= FLAG_IMMUTABLE; + state->flags[j] |= FLAG_IMMUTABLE; + debug_state("Copy of state: ", copy); + strip_nums(copy); + if (solve_state(copy) > 0) goto solved; + assert(check_nums(state, copy, 1)); + } + ret = 0; + goto done; + +solved: + debug(("new_game_strip: now solved.")); + /* Since we added basically at random, try now to remove numbers + * and see if we can still solve it; if we can (still), really + * remove the number. Make sure we don't remove the anchor numbers + * 1 and N. */ + for (i = 0; i < state->n; i++) { + j = scratch[i]; + if ((state->flags[j] & FLAG_IMMUTABLE) && + (state->nums[j] != 1 && state->nums[j] != state->n)) { + debug(("new_game_strip: testing remove IMMUTABLE number %d at square (%d,%d).", + state->nums[j], j%state->w, j/state->w)); + state->flags[j] &= ~FLAG_IMMUTABLE; + dup_game_to(copy, state); + strip_nums(copy); + if (solve_state(copy) > 0) { + assert(check_nums(state, copy, 0)); + debug(("new_game_strip: OK, removing number")); + } else { + assert(state->nums[j] <= state->n); + debug(("new_game_strip: cannot solve, putting IMMUTABLE back.")); + copy->nums[j] = state->nums[j]; + state->flags[j] |= FLAG_IMMUTABLE; + } + } + } + +done: + debug(("new_game_strip: %ssuccessful.", ret ? "" : "not ")); + sfree(scratch); + free_game(copy); + return ret; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + game_state *state = blank_game(params->w, params->h); + char *ret; + int headi, taili; + + /* this shouldn't happen (validate_params), but let's play it safe */ + if (params->w == 1 && params->h == 1) return dupstr("1a"); + +generate: + blank_game_into(state); + + /* keep trying until we fill successfully. */ + do { + if (params->force_corner_start) { + headi = 0; + taili = state->n-1; + } else { + do { + headi = random_upto(rs, state->n); + taili = random_upto(rs, state->n); + } while (headi == taili); + } + } while (!new_game_fill(state, rs, headi, taili)); + + debug_state("Filled game:", state); + + assert(state->nums[headi] <= state->n); + assert(state->nums[taili] <= state->n); + + state->flags[headi] |= FLAG_IMMUTABLE; + state->flags[taili] |= FLAG_IMMUTABLE; + + /* This will have filled in directions and _all_ numbers. + * Store the game definition for this, as the solved-state. */ + if (!new_game_strip(state, rs)) { + goto generate; + } + strip_nums(state); + { + game_state *tosolve = dup_game(state); + assert(solve_state(tosolve) > 0); + free_game(tosolve); + } + ret = generate_desc(state, 0); + free_game(state); + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + char *ret = NULL; + + unpick_desc(params, desc, NULL, &ret); + return ret; +} + +/* --- Linked-list and numbers array --- */ + +/* Assuming numbers are always up-to-date, there are only four possibilities + * for regions changing after a single valid move: + * + * 1) two differently-coloured regions being combined (the resulting colouring + * should be based on the larger of the two regions) + * 2) a numbered region having a single number added to the start (the + * region's colour will remain, and the numbers will shift by 1) + * 3) a numbered region having a single number added to the end (the + * region's colour and numbering remains as-is) + * 4) two unnumbered squares being joined (will pick the smallest unused set + * of colours to use for the new region). + * + * There should never be any complications with regions containing 3 colours + * being combined, since two of those colours should have been merged on a + * previous move. + * + * Most of the complications are in ensuring we don't accidentally set two + * regions with the same colour (e.g. if a region was split). If this happens + * we always try and give the largest original portion the original colour. + */ + +#define COLOUR(a) ((a) / (state->n+1)) +#define START(c) ((c) * (state->n+1)) + +struct head_meta { + int i; /* position */ + int sz; /* size of region */ + int start; /* region start number preferred, or 0 if !preference */ + int preference; /* 0 if we have no preference (and should just pick one) */ + const char *why; +}; + +static void head_number(game_state *state, int i, struct head_meta *head) +{ + int off = 0, ss, j = i, c, n, sz; + + /* Insist we really were passed the head of a chain. */ + assert(state->prev[i] == -1 && state->next[i] != -1); + + head->i = i; + head->sz = dsf_size(state->dsf, i); + head->why = NULL; + + /* Search through this chain looking for real numbers, checking that + * they match up (if there are more than one). */ + head->preference = 0; + while (j != -1) { + if (state->flags[j] & FLAG_IMMUTABLE) { + ss = state->nums[j] - off; + if (!head->preference) { + head->start = ss; + head->preference = 1; + head->why = "contains cell with immutable number"; + } else if (head->start != ss) { + debug(("head_number: chain with non-sequential numbers!")); + state->impossible = 1; + } + } + off++; + j = state->next[j]; + assert(j != i); /* we have created a loop, obviously wrong */ + } + if (head->preference) goto done; + + if (state->nums[i] == 0 && state->nums[state->next[i]] > state->n) { + /* (probably) empty cell onto the head of a coloured region: + * make sure we start at a 0 offset. */ + head->start = START(COLOUR(state->nums[state->next[i]])); + head->preference = 1; + head->why = "adding blank cell to head of numbered region"; + } else if (state->nums[i] <= state->n) { + /* if we're 0 we're probably just blank -- but even if we're a + * (real) numbered region, we don't have an immutable number + * in it (any more) otherwise it'd have been caught above, so + * reassign the colour. */ + head->start = 0; + head->preference = 0; + head->why = "lowest available colour group"; + } else { + c = COLOUR(state->nums[i]); + n = 1; + sz = dsf_size(state->dsf, i); + j = i; + while (state->next[j] != -1) { + j = state->next[j]; + if (state->nums[j] == 0 && state->next[j] == -1) { + head->start = START(c); + head->preference = 1; + head->why = "adding blank cell to end of numbered region"; + goto done; + } + if (COLOUR(state->nums[j]) == c) + n++; + else { + int start_alternate = START(COLOUR(state->nums[j])); + if (n < (sz - n)) { + head->start = start_alternate; + head->preference = 1; + head->why = "joining two coloured regions, swapping to larger colour"; + } else { + head->start = START(c); + head->preference = 1; + head->why = "joining two coloured regions, taking largest"; + } + goto done; + } + } + /* If we got here then we may have split a region into + * two; make sure we don't assign a colour we've already used. */ + if (c == 0) { + /* not convinced this shouldn't be an assertion failure here. */ + head->start = 0; + head->preference = 0; + } else { + head->start = START(c); + head->preference = 1; + } + head->why = "got to end of coloured region"; + } + +done: + assert(head->why != NULL); + if (head->preference) + debug(("Chain at (%d,%d) numbered for preference at %d (colour %d): %s.", + head->i%state->w, head->i/state->w, + head->start, COLOUR(head->start), head->why)); + else + debug(("Chain at (%d,%d) using next available colour: %s.", + head->i%state->w, head->i/state->w, + head->why)); +} + +#if 0 +static void debug_numbers(game_state *state) +{ + int i, w=state->w; + + for (i = 0; i < state->n; i++) { + debug(("(%d,%d) --> (%d,%d) --> (%d,%d)", + state->prev[i]==-1 ? -1 : state->prev[i]%w, + state->prev[i]==-1 ? -1 : state->prev[i]/w, + i%w, i/w, + state->next[i]==-1 ? -1 : state->next[i]%w, + state->next[i]==-1 ? -1 : state->next[i]/w)); + } + w = w+1; +} +#endif + +static void connect_numbers(game_state *state) +{ + int i, di, dni; + + dsf_init(state->dsf, state->n); + for (i = 0; i < state->n; i++) { + if (state->next[i] != -1) { + assert(state->prev[state->next[i]] == i); + di = dsf_canonify(state->dsf, i); + dni = dsf_canonify(state->dsf, state->next[i]); + if (di == dni) { + debug(("connect_numbers: chain forms a loop.")); + state->impossible = 1; + } + dsf_merge(state->dsf, di, dni); + } + } +} + +static int compare_heads(const void *a, const void *b) +{ + struct head_meta *ha = (struct head_meta *)a; + struct head_meta *hb = (struct head_meta *)b; + + /* Heads with preferred colours first... */ + if (ha->preference && !hb->preference) return -1; + if (hb->preference && !ha->preference) return 1; + + /* ...then heads with low colours first... */ + if (ha->start < hb->start) return -1; + if (ha->start > hb->start) return 1; + + /* ... then large regions first... */ + if (ha->sz > hb->sz) return -1; + if (ha->sz < hb->sz) return 1; + + /* ... then position. */ + if (ha->i > hb->i) return -1; + if (ha->i < hb->i) return 1; + + return 0; +} + +static int lowest_start(game_state *state, struct head_meta *heads, int nheads) +{ + int n, c; + + /* NB start at 1: colour 0 is real numbers */ + for (c = 1; c < state->n; c++) { + for (n = 0; n < nheads; n++) { + if (COLOUR(heads[n].start) == c) + goto used; + } + return c; +used: + ; + } + assert(!"No available colours!"); + return 0; +} + +static void update_numbers(game_state *state) +{ + int i, j, n, nnum, nheads; + struct head_meta *heads = snewn(state->n, struct head_meta); + + for (n = 0; n < state->n; n++) + state->numsi[n] = -1; + + for (i = 0; i < state->n; i++) { + if (state->flags[i] & FLAG_IMMUTABLE) { + assert(state->nums[i] > 0); + assert(state->nums[i] <= state->n); + state->numsi[state->nums[i]] = i; + } + else if (state->prev[i] == -1 && state->next[i] == -1) + state->nums[i] = 0; + } + connect_numbers(state); + + /* Construct an array of the heads of all current regions, together + * with their preferred colours. */ + nheads = 0; + for (i = 0; i < state->n; i++) { + /* Look for a cell that is the start of a chain + * (has a next but no prev). */ + if (state->prev[i] != -1 || state->next[i] == -1) continue; + + head_number(state, i, &heads[nheads++]); + } + + /* Sort that array: + * - heads with preferred colours first, then + * - heads with low colours first, then + * - large regions first + */ + qsort(heads, nheads, sizeof(struct head_meta), compare_heads); + + /* Remove duplicate-coloured regions. */ + for (n = nheads-1; n >= 0; n--) { /* order is important! */ + if ((n != 0) && (heads[n].start == heads[n-1].start)) { + /* We have a duplicate-coloured region: since we're + * sorted in size order and this is not the first + * of its colour it's not the largest: recolour it. */ + heads[n].start = START(lowest_start(state, heads, nheads)); + heads[n].preference = -1; /* '-1' means 'was duplicate' */ + } + else if (!heads[n].preference) { + assert(heads[n].start == 0); + heads[n].start = START(lowest_start(state, heads, nheads)); + } + } + + debug(("Region colouring after duplicate removal:")); + + for (n = 0; n < nheads; n++) { + debug((" Chain at (%d,%d) sz %d numbered at %d (colour %d): %s%s", + heads[n].i % state->w, heads[n].i / state->w, heads[n].sz, + heads[n].start, COLOUR(heads[n].start), heads[n].why, + heads[n].preference == 0 ? " (next available)" : + heads[n].preference < 0 ? " (duplicate, next available)" : "")); + + nnum = heads[n].start; + j = heads[n].i; + while (j != -1) { + if (!(state->flags[j] & FLAG_IMMUTABLE)) { + if (nnum > 0 && nnum <= state->n) + state->numsi[nnum] = j; + state->nums[j] = nnum; + } + nnum++; + j = state->next[j]; + assert(j != heads[n].i); /* loop?! */ + } + } + /*debug_numbers(state);*/ + sfree(heads); +} + +static int check_completion(game_state *state, int mark_errors) +{ + int n, j, k, error = 0, complete; + + /* NB This only marks errors that are possible to perpetrate with + * the current UI in interpret_move. Things like forming loops in + * linked sections and having numbers not add up should be forbidden + * by the code elsewhere, so we don't bother marking those (because + * it would add lots of tricky drawing code for very little gain). */ + if (mark_errors) { + for (j = 0; j < state->n; j++) + state->flags[j] &= ~FLAG_ERROR; + } + + /* Search for repeated numbers. */ + for (j = 0; j < state->n; j++) { + if (state->nums[j] > 0 && state->nums[j] <= state->n) { + for (k = j+1; k < state->n; k++) { + if (state->nums[k] == state->nums[j]) { + if (mark_errors) { + state->flags[j] |= FLAG_ERROR; + state->flags[k] |= FLAG_ERROR; + } + error = 1; + } + } + } + } + + /* Search and mark numbers n not pointing to n+1; if any numbers + * are missing we know we've not completed. */ + complete = 1; + for (n = 1; n < state->n; n++) { + if (state->numsi[n] == -1 || state->numsi[n+1] == -1) + complete = 0; + else if (!ispointingi(state, state->numsi[n], state->numsi[n+1])) { + if (mark_errors) { + state->flags[state->numsi[n]] |= FLAG_ERROR; + state->flags[state->numsi[n+1]] |= FLAG_ERROR; + } + error = 1; + } else { + /* make sure the link is explicitly made here; for instance, this + * is nice if the user drags from 2 out (making 3) and a 4 is also + * visible; this ensures that the link from 3 to 4 is also made. */ + if (mark_errors) + makelink(state, state->numsi[n], state->numsi[n+1]); + } + } + + /* Search and mark numbers less than 0, or 0 with links. */ + for (n = 1; n < state->n; n++) { + if ((state->nums[n] < 0) || + (state->nums[n] == 0 && + (state->next[n] != -1 || state->prev[n] != -1))) { + error = 1; + if (mark_errors) + state->flags[n] |= FLAG_ERROR; + } + } + + if (error) return 0; + return complete; +} +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = NULL; + + unpick_desc(params, desc, &state, NULL); + if (!state) assert(!"new_game failed to unpick"); + + update_numbers(state); + check_completion(state, 1); /* update any auto-links */ + + return state; +} + +/* --- Solver --- */ + +/* If a tile has a single tile it can link _to_, or there's only a single + * location that can link to a given tile, fill that link in. */ +static int solve_single(game_state *state, game_state *copy, int *from) +{ + int i, j, sx, sy, x, y, d, poss, w=state->w, nlinks = 0; + + /* The from array is a list of 'which square can link _to_ us'; + * we start off with from as '-1' (meaning 'not found'); if we find + * something that can link to us it is set to that index, and then if + * we find another we set it to -2. */ + + memset(from, -1, state->n*sizeof(int)); + + /* poss is 'can I link to anything' with the same meanings. */ + + for (i = 0; i < state->n; i++) { + if (state->next[i] != -1) continue; + if (state->nums[i] == state->n) continue; /* no next from last no. */ + + d = state->dirs[i]; + poss = -1; + sx = x = i%w; sy = y = i/w; + while (1) { + x += dxs[d]; y += dys[d]; + if (!INGRID(state, x, y)) break; + if (!isvalidmove(state, 1, sx, sy, x, y)) continue; + + /* can't link to somewhere with a back-link we would have to + * break (the solver just doesn't work like this). */ + j = y*w+x; + if (state->prev[j] != -1) continue; + + if (state->nums[i] > 0 && state->nums[j] > 0 && + state->nums[i] <= state->n && state->nums[j] <= state->n && + state->nums[j] == state->nums[i]+1) { + debug(("Solver: forcing link through existing consecutive numbers.")); + poss = j; + from[j] = i; + break; + } + + /* if there's been a valid move already, we have to move on; + * we can't make any deductions here. */ + poss = (poss == -1) ? j : -2; + + /* Modify the from array as described above (which is enumerating + * what points to 'j' in a similar way). */ + from[j] = (from[j] == -1) ? i : -2; + } + if (poss == -2) { + /*debug(("Solver: (%d,%d) has multiple possible next squares.", sx, sy));*/ + ; + } else if (poss == -1) { + debug(("Solver: nowhere possible for (%d,%d) to link to.", sx, sy)); + copy->impossible = 1; + return -1; + } else { + debug(("Solver: linking (%d,%d) to only possible next (%d,%d).", + sx, sy, poss%w, poss/w)); + makelink(copy, i, poss); + nlinks++; + } + } + + for (i = 0; i < state->n; i++) { + if (state->prev[i] != -1) continue; + if (state->nums[i] == 1) continue; /* no prev from 1st no. */ + + x = i%w; y = i/w; + if (from[i] == -1) { + debug(("Solver: nowhere possible to link to (%d,%d)", x, y)); + copy->impossible = 1; + return -1; + } else if (from[i] == -2) { + /*debug(("Solver: (%d,%d) has multiple possible prev squares.", x, y));*/ + ; + } else { + debug(("Solver: linking only possible prev (%d,%d) to (%d,%d).", + from[i]%w, from[i]/w, x, y)); + makelink(copy, from[i], i); + nlinks++; + } + } + + return nlinks; +} + +/* Returns 1 if we managed to solve it, 0 otherwise. */ +static int solve_state(game_state *state) +{ + game_state *copy = dup_game(state); + int *scratch = snewn(state->n, int), ret; + + debug_state("Before solver: ", state); + + while (1) { + update_numbers(state); + + if (solve_single(state, copy, scratch)) { + dup_game_to(state, copy); + if (state->impossible) break; else continue; + } + break; + } + free_game(copy); + sfree(scratch); + + update_numbers(state); + ret = state->impossible ? -1 : check_completion(state, 0); + debug(("Solver finished: %s", + ret < 0 ? "impossible" : ret > 0 ? "solved" : "not solved")); + debug_state("After solver: ", state); + return ret; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *tosolve; + char *ret = NULL; + int result; + + tosolve = dup_game(currstate); + result = solve_state(tosolve); + if (result > 0) + ret = generate_desc(tosolve, 1); + free_game(tosolve); + if (ret) return ret; + + tosolve = dup_game(state); + result = solve_state(tosolve); + if (result < 0) + *error = "Puzzle is impossible."; + else if (result == 0) + *error = "Unable to solve puzzle."; + else + ret = generate_desc(tosolve, 1); + + free_game(tosolve); + + return ret; +} + +/* --- UI and move routines. --- */ + + +struct game_ui { + int cx, cy, cshow; + + int dragging, drag_is_from; + int sx, sy; /* grid coords of start cell */ + int dx, dy; /* pixel coords of drag posn */ +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + /* NB: if this is ever changed to as to require more than a structure + * copy to clone, there's code that needs fixing in game_redraw too. */ + + ui->cx = ui->cy = ui->cshow = 0; + + ui->dragging = 0; + ui->sx = ui->sy = ui->dx = ui->dy = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (!oldstate->completed && newstate->completed) + ui->cshow = ui->dragging = 0; +} + +struct game_drawstate { + int tilesize, started, solved; + int w, h, n; + int *nums, *dirp; + unsigned int *f; + double angle_offset; + + int dragging, dx, dy; + blitter *dragb; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int mx, int my, int button) +{ + int x = FROMCOORD(mx), y = FROMCOORD(my), w = state->w; + char buf[80]; + + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cx, &ui->cy, state->w, state->h, 0); + ui->cshow = 1; + if (ui->dragging) { + ui->dx = COORD(ui->cx) + TILE_SIZE/2; + ui->dy = COORD(ui->cy) + TILE_SIZE/2; + } + return ""; + } else if (IS_CURSOR_SELECT(button)) { + if (!ui->cshow) + ui->cshow = 1; + else if (ui->dragging) { + ui->dragging = FALSE; + if (ui->sx == ui->cx && ui->sy == ui->cy) return ""; + if (ui->drag_is_from) { + if (!isvalidmove(state, 0, ui->sx, ui->sy, ui->cx, ui->cy)) return ""; + sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, ui->cx, ui->cy); + } else { + if (!isvalidmove(state, 0, ui->cx, ui->cy, ui->sx, ui->sy)) return ""; + sprintf(buf, "L%d,%d-%d,%d", ui->cx, ui->cy, ui->sx, ui->sy); + } + return dupstr(buf); + } else { + ui->dragging = TRUE; + ui->sx = ui->cx; + ui->sy = ui->cy; + ui->dx = COORD(ui->cx) + TILE_SIZE/2; + ui->dy = COORD(ui->cy) + TILE_SIZE/2; + ui->drag_is_from = (button == CURSOR_SELECT) ? 1 : 0; + } + return ""; + } + if (IS_MOUSE_DOWN(button)) { + if (ui->cshow) { + ui->cshow = ui->dragging = 0; + } + assert(!ui->dragging); + if (!INGRID(state, x, y)) return NULL; + + if (button == LEFT_BUTTON) { + /* disallow dragging from the final number. */ + if ((state->nums[y*w+x] == state->n) && + (state->flags[y*w+x] & FLAG_IMMUTABLE)) + return NULL; + } else if (button == RIGHT_BUTTON) { + /* disallow dragging to the first number. */ + if ((state->nums[y*w+x] == 1) && + (state->flags[y*w+x] & FLAG_IMMUTABLE)) + return NULL; + } + + ui->dragging = TRUE; + ui->drag_is_from = (button == LEFT_BUTTON) ? 1 : 0; + ui->sx = x; + ui->sy = y; + ui->dx = mx; + ui->dy = my; + ui->cshow = 0; + return ""; + } else if (IS_MOUSE_DRAG(button) && ui->dragging) { + ui->dx = mx; + ui->dy = my; + return ""; + } else if (IS_MOUSE_RELEASE(button) && ui->dragging) { + ui->dragging = FALSE; + if (ui->sx == x && ui->sy == y) return ""; /* single click */ + + if (!INGRID(state, x, y)) { + int si = ui->sy*w+ui->sx; + if (state->prev[si] == -1 && state->next[si] == -1) + return ""; + sprintf(buf, "%c%d,%d", + (int)(ui->drag_is_from ? 'C' : 'X'), ui->sx, ui->sy); + return dupstr(buf); + } + + if (ui->drag_is_from) { + if (!isvalidmove(state, 0, ui->sx, ui->sy, x, y)) return ""; + sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, x, y); + } else { + if (!isvalidmove(state, 0, x, y, ui->sx, ui->sy)) return ""; + sprintf(buf, "L%d,%d-%d,%d", x, y, ui->sx, ui->sy); + } + return dupstr(buf); + } /* else if (button == 'H' || button == 'h') + return dupstr("H"); */ + else if ((button == 'x' || button == 'X') && ui->cshow) { + int si = ui->cy*w + ui->cx; + if (state->prev[si] == -1 && state->next[si] == -1) + return ""; + sprintf(buf, "%c%d,%d", + (int)((button == 'x') ? 'C' : 'X'), ui->cx, ui->cy); + return dupstr(buf); + } + + return NULL; +} + +static void unlink_cell(game_state *state, int si) +{ + debug(("Unlinking (%d,%d).", si%state->w, si/state->w)); + if (state->prev[si] != -1) { + debug((" ... removing prev link from (%d,%d).", + state->prev[si]%state->w, state->prev[si]/state->w)); + state->next[state->prev[si]] = -1; + state->prev[si] = -1; + } + if (state->next[si] != -1) { + debug((" ... removing next link to (%d,%d).", + state->next[si]%state->w, state->next[si]/state->w)); + state->prev[state->next[si]] = -1; + state->next[si] = -1; + } +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *ret = NULL; + int sx, sy, ex, ey, si, ei, w = state->w; + char c; + + debug(("move: %s", move)); + + if (move[0] == 'S') { + game_params p; + game_state *tmp; + char *valid; + int i; + + p.w = state->w; p.h = state->h; + valid = validate_desc(&p, move+1); + if (valid) { + debug(("execute_move: move not valid: %s", valid)); + return NULL; + } + ret = dup_game(state); + tmp = new_game(NULL, &p, move+1); + for (i = 0; i < state->n; i++) { + ret->prev[i] = tmp->prev[i]; + ret->next[i] = tmp->next[i]; + } + free_game(tmp); + ret->used_solve = 1; + } else if (sscanf(move, "L%d,%d-%d,%d", &sx, &sy, &ex, &ey) == 4) { + if (!isvalidmove(state, 0, sx, sy, ex, ey)) return NULL; + + ret = dup_game(state); + + si = sy*w+sx; ei = ey*w+ex; + makelink(ret, si, ei); + } else if (sscanf(move, "%c%d,%d", &c, &sx, &sy) == 3) { + int sset; + + if (c != 'C' && c != 'X') return NULL; + if (!INGRID(state, sx, sy)) return NULL; + si = sy*w+sx; + if (state->prev[si] == -1 && state->next[si] == -1) + return NULL; + + ret = dup_game(state); + + sset = state->nums[si] / (state->n+1); + if (c == 'C' || (c == 'X' && sset == 0)) { + /* Unlink the single cell we dragged from the board. */ + unlink_cell(ret, si); + } else { + int i, set; + for (i = 0; i < state->n; i++) { + /* Unlink all cells in the same set as the one we dragged + * from the board. */ + + if (state->nums[i] == 0) continue; + set = state->nums[i] / (state->n+1); + if (set != sset) continue; + + unlink_cell(ret, i); + } + } + } else if (strcmp(move, "H") == 0) { + ret = dup_game(state); + solve_state(ret); + } + if (ret) { + update_numbers(ret); + if (check_completion(ret, 1)) ret->completed = 1; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize, order; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * params->w + 2 * BORDER; + *y = TILE_SIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + assert(TILE_SIZE > 0); + + assert(!ds->dragb); + ds->dragb = blitter_new(dr, BLITTER_SIZE, BLITTER_SIZE); +} + +/* Colours chosen from the webby palette to work as a background to black text, + * W then some plausible approximation to pastelly ROYGBIV; we then interpolate + * between consecutive pairs to give another 8 (and then the drawing routine + * will reuse backgrounds). */ +static const unsigned long bgcols[8] = { + 0xffffff, /* white */ + 0xffa07a, /* lightsalmon */ + 0x98fb98, /* green */ + 0x7fffd4, /* aquamarine */ + 0x9370db, /* medium purple */ + 0xffa500, /* orange */ + 0x87cefa, /* lightskyblue */ + 0xffff00, /* yellow */ +}; + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int c, i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) { + ret[COL_NUMBER * 3 + i] = 0.0F; + ret[COL_ARROW * 3 + i] = 0.0F; + ret[COL_CURSOR * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 2.0F; + ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.3F; + } + ret[COL_NUMBER_SET * 3 + 0] = 0.0F; + ret[COL_NUMBER_SET * 3 + 1] = 0.0F; + ret[COL_NUMBER_SET * 3 + 2] = 0.9F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_DRAG_ORIGIN * 3 + 0] = 0.2F; + ret[COL_DRAG_ORIGIN * 3 + 1] = 1.0F; + ret[COL_DRAG_ORIGIN * 3 + 2] = 0.2F; + + for (c = 0; c < 8; c++) { + ret[(COL_B0 + c) * 3 + 0] = (float)((bgcols[c] & 0xff0000) >> 16) / 256.0F; + ret[(COL_B0 + c) * 3 + 1] = (float)((bgcols[c] & 0xff00) >> 8) / 256.0F; + ret[(COL_B0 + c) * 3 + 2] = (float)((bgcols[c] & 0xff)) / 256.0F; + } + for (c = 0; c < 8; c++) { + for (i = 0; i < 3; i++) { + ret[(COL_B0 + 8 + c) * 3 + i] = + (ret[(COL_B0 + c) * 3 + i] + ret[(COL_B0 + c + 1) * 3 + i]) / 2.0F; + } + } + +#define average(r,a,b,w) do { \ + for (i = 0; i < 3; i++) \ + ret[(r)*3+i] = ret[(a)*3+i] + w * (ret[(b)*3+i] - ret[(a)*3+i]); \ +} while (0) + average(COL_ARROW_BG_DIM, COL_BACKGROUND, COL_ARROW, 0.1F); + average(COL_NUMBER_SET_MID, COL_B0, COL_NUMBER_SET, 0.3F); + for (c = 0; c < NBACKGROUNDS; c++) { + /* I assume here that COL_ARROW and COL_NUMBER are the same. + * Otherwise I'd need two sets of COL_M*. */ + average(COL_M0 + c, COL_B0 + c, COL_NUMBER, 0.3F); + average(COL_D0 + c, COL_B0 + c, COL_NUMBER, 0.1F); + average(COL_X0 + c, COL_BACKGROUND, COL_B0 + c, 0.5F); + } + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = ds->started = ds->solved = 0; + ds->w = state->w; + ds->h = state->h; + ds->n = state->n; + + ds->nums = snewn(state->n, int); + ds->dirp = snewn(state->n, int); + ds->f = snewn(state->n, unsigned int); + for (i = 0; i < state->n; i++) { + ds->nums[i] = 0; + ds->dirp[i] = -1; + ds->f[i] = 0; + } + + ds->angle_offset = 0.0F; + + ds->dragging = ds->dx = ds->dy = 0; + ds->dragb = NULL; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->nums); + sfree(ds->dirp); + sfree(ds->f); + if (ds->dragb) blitter_free(dr, ds->dragb); + + sfree(ds); +} + +/* cx, cy are top-left corner. sz is the 'radius' of the arrow. + * ang is in radians, clockwise from 0 == straight up. */ +static void draw_arrow(drawing *dr, int cx, int cy, int sz, double ang, + int cfill, int cout) +{ + int coords[14]; + int xdx, ydx, xdy, ydy, xdx3, xdy3; + double s = sin(ang), c = cos(ang); + + xdx3 = (int)(sz * (c/3 + 1) + 0.5) - sz; + xdy3 = (int)(sz * (s/3 + 1) + 0.5) - sz; + xdx = (int)(sz * (c + 1) + 0.5) - sz; + xdy = (int)(sz * (s + 1) + 0.5) - sz; + ydx = -xdy; + ydy = xdx; + + + coords[2*0 + 0] = cx - ydx; + coords[2*0 + 1] = cy - ydy; + coords[2*1 + 0] = cx + xdx; + coords[2*1 + 1] = cy + xdy; + coords[2*2 + 0] = cx + xdx3; + coords[2*2 + 1] = cy + xdy3; + coords[2*3 + 0] = cx + xdx3 + ydx; + coords[2*3 + 1] = cy + xdy3 + ydy; + coords[2*4 + 0] = cx - xdx3 + ydx; + coords[2*4 + 1] = cy - xdy3 + ydy; + coords[2*5 + 0] = cx - xdx3; + coords[2*5 + 1] = cy - xdy3; + coords[2*6 + 0] = cx - xdx; + coords[2*6 + 1] = cy - xdy; + + draw_polygon(dr, coords, 7, cfill, cout); +} + +static void draw_arrow_dir(drawing *dr, int cx, int cy, int sz, int dir, + int cfill, int cout, double angle_offset) +{ + double ang = 2.0 * PI * (double)dir / 8.0 + angle_offset; + draw_arrow(dr, cx, cy, sz, ang, cfill, cout); +} + +/* cx, cy are centre coordinates.. */ +static void draw_star(drawing *dr, int cx, int cy, int rad, int npoints, + int cfill, int cout, double angle_offset) +{ + int *coords, n; + double a, r; + + assert(npoints > 0); + + coords = snewn(npoints * 2 * 2, int); + + for (n = 0; n < npoints * 2; n++) { + a = 2.0 * PI * ((double)n / ((double)npoints * 2.0)) + angle_offset; + r = (n % 2) ? (double)rad/2.0 : (double)rad; + + /* We're rotating the point at (0, -r) by a degrees */ + coords[2*n+0] = cx + (int)( r * sin(a)); + coords[2*n+1] = cy + (int)(-r * cos(a)); + } + draw_polygon(dr, coords, npoints*2, cfill, cout); + sfree(coords); +} + +static int num2col(game_drawstate *ds, int num) +{ + int set = num / (ds->n+1); + + if (num <= 0 || set == 0) return COL_B0; + return COL_B0 + 1 + ((set-1) % 15); +} + +#define ARROW_HALFSZ (7 * TILE_SIZE / 32) + +#define F_CUR 0x001 /* Cursor on this tile. */ +#define F_DRAG_SRC 0x002 /* Tile is source of a drag. */ +#define F_ERROR 0x004 /* Tile marked in error. */ +#define F_IMMUTABLE 0x008 /* Tile (number) is immutable. */ +#define F_ARROW_POINT 0x010 /* Tile points to other tile */ +#define F_ARROW_INPOINT 0x020 /* Other tile points in here. */ +#define F_DIM 0x040 /* Tile is dim */ + +static void tile_redraw(drawing *dr, game_drawstate *ds, int tx, int ty, + int dir, int dirp, int num, unsigned int f, + double angle_offset, int print_ink) +{ + int cb = TILE_SIZE / 16, textsz; + char buf[20]; + int arrowcol, sarrowcol, setcol, textcol; + int acx, acy, asz, empty = 0; + + if (num == 0 && !(f & F_ARROW_POINT) && !(f & F_ARROW_INPOINT)) { + empty = 1; + /* + * We don't display text in empty cells: typically these are + * signified by num=0. However, in some cases a cell could + * have had the number 0 assigned to it if the user made an + * error (e.g. tried to connect a chain of length 5 to the + * immutable number 4) so we _do_ display the 0 if the cell + * has a link in or a link out. + */ + } + + /* Calculate colours. */ + + if (print_ink >= 0) { + /* + * We're printing, so just do everything in black. + */ + arrowcol = textcol = print_ink; + setcol = sarrowcol = -1; /* placate optimiser */ + } else { + + setcol = empty ? COL_BACKGROUND : num2col(ds, num); + +#define dim(fg,bg) ( \ + (bg)==COL_BACKGROUND ? COL_ARROW_BG_DIM : \ + (bg) + COL_D0 - COL_B0 \ + ) + +#define mid(fg,bg) ( \ + (fg)==COL_NUMBER_SET ? COL_NUMBER_SET_MID : \ + (bg) + COL_M0 - COL_B0 \ + ) + +#define dimbg(bg) ( \ + (bg)==COL_BACKGROUND ? COL_BACKGROUND : \ + (bg) + COL_X0 - COL_B0 \ + ) + + if (f & F_DRAG_SRC) arrowcol = COL_DRAG_ORIGIN; + else if (f & F_DIM) arrowcol = dim(COL_ARROW, setcol); + else if (f & F_ARROW_POINT) arrowcol = mid(COL_ARROW, setcol); + else arrowcol = COL_ARROW; + + if ((f & F_ERROR) && !(f & F_IMMUTABLE)) textcol = COL_ERROR; + else { + if (f & F_IMMUTABLE) textcol = COL_NUMBER_SET; + else textcol = COL_NUMBER; + + if (f & F_DIM) textcol = dim(textcol, setcol); + else if (((f & F_ARROW_POINT) || num==ds->n) && + ((f & F_ARROW_INPOINT) || num==1)) + textcol = mid(textcol, setcol); + } + + if (f & F_DIM) sarrowcol = dim(COL_ARROW, setcol); + else sarrowcol = COL_ARROW; + } + + /* Clear tile background */ + + if (print_ink < 0) { + draw_rect(dr, tx, ty, TILE_SIZE, TILE_SIZE, + (f & F_DIM) ? dimbg(setcol) : setcol); + } + + /* Draw large (outwards-pointing) arrow. */ + + asz = ARROW_HALFSZ; /* 'radius' of arrow/star. */ + acx = tx+TILE_SIZE/2+asz; /* centre x */ + acy = ty+TILE_SIZE/2+asz; /* centre y */ + + if (num == ds->n && (f & F_IMMUTABLE)) + draw_star(dr, acx, acy, asz, 5, arrowcol, arrowcol, angle_offset); + else + draw_arrow_dir(dr, acx, acy, asz, dir, arrowcol, arrowcol, angle_offset); + if (print_ink < 0 && (f & F_CUR)) + draw_rect_corners(dr, acx, acy, asz+1, COL_CURSOR); + + /* Draw dot iff this tile requires a predecessor and doesn't have one. */ + + if (print_ink < 0) { + acx = tx+TILE_SIZE/2-asz; + acy = ty+TILE_SIZE/2+asz; + + if (!(f & F_ARROW_INPOINT) && num != 1) { + draw_circle(dr, acx, acy, asz / 4, sarrowcol, sarrowcol); + } + } + + /* Draw text (number or set). */ + + if (!empty) { + int set = (num <= 0) ? 0 : num / (ds->n+1); + + char *p = buf; + if (set == 0 || num <= 0) { + sprintf(buf, "%d", num); + } else { + int n = num % (ds->n+1); + p += sizeof(buf) - 1; + + if (n != 0) { + sprintf(buf, "+%d", n); /* Just to get the length... */ + p -= strlen(buf); + sprintf(p, "+%d", n); + } else { + *p = '\0'; + } + do { + set--; + p--; + *p = (char)((set % 26)+'a'); + set /= 26; + } while (set); + } + textsz = min(2*asz, (TILE_SIZE - 2 * cb) / (int)strlen(p)); + draw_text(dr, tx + cb, ty + TILE_SIZE/4, FONT_VARIABLE, textsz, + ALIGN_VCENTRE | ALIGN_HLEFT, textcol, p); + } + + if (print_ink < 0) { + draw_rect_outline(dr, tx, ty, TILE_SIZE, TILE_SIZE, COL_GRID); + draw_update(dr, tx, ty, TILE_SIZE, TILE_SIZE); + } +} + +static void draw_drag_indicator(drawing *dr, game_drawstate *ds, + const game_state *state, const game_ui *ui, + int validdrag) +{ + int dir, w = ds->w, acol = COL_ARROW; + int fx = FROMCOORD(ui->dx), fy = FROMCOORD(ui->dy); + double ang; + + if (validdrag) { + /* If we could move here, lock the arrow to the appropriate direction. */ + dir = ui->drag_is_from ? state->dirs[ui->sy*w+ui->sx] : state->dirs[fy*w+fx]; + + ang = (2.0 * PI * dir) / 8.0; /* similar to calculation in draw_arrow_dir. */ + } else { + /* Draw an arrow pointing away from/towards the origin cell. */ + int ox = COORD(ui->sx) + TILE_SIZE/2, oy = COORD(ui->sy) + TILE_SIZE/2; + double tana, offset; + double xdiff = abs(ox - ui->dx), ydiff = abs(oy - ui->dy); + + if (xdiff == 0) { + ang = (oy > ui->dy) ? 0.0F : PI; + } else if (ydiff == 0) { + ang = (ox > ui->dx) ? 3.0F*PI/2.0F : PI/2.0F; + } else { + if (ui->dx > ox && ui->dy < oy) { + tana = xdiff / ydiff; + offset = 0.0F; + } else if (ui->dx > ox && ui->dy > oy) { + tana = ydiff / xdiff; + offset = PI/2.0F; + } else if (ui->dx < ox && ui->dy > oy) { + tana = xdiff / ydiff; + offset = PI; + } else { + tana = ydiff / xdiff; + offset = 3.0F * PI / 2.0F; + } + ang = atan(tana) + offset; + } + + if (!ui->drag_is_from) ang += PI; /* point to origin, not away from. */ + + } + draw_arrow(dr, ui->dx, ui->dy, ARROW_HALFSZ, ang, acol, acol); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y, i, w = ds->w, dirp, force = 0; + unsigned int f; + double angle_offset = 0.0; + game_state *postdrop = NULL; + + if (flashtime > 0.0F) + angle_offset = 2.0 * PI * (flashtime / FLASH_SPIN); + if (angle_offset != ds->angle_offset) { + ds->angle_offset = angle_offset; + force = 1; + } + + if (ds->dragging) { + assert(ds->dragb); + blitter_load(dr, ds->dragb, ds->dx, ds->dy); + draw_update(dr, ds->dx, ds->dy, BLITTER_SIZE, BLITTER_SIZE); + ds->dragging = FALSE; + } + + /* If an in-progress drag would make a valid move if finished, we + * reflect that move in the board display. We let interpret_move do + * most of the heavy lifting for us: we have to copy the game_ui so + * as not to stomp on the real UI's drag state. */ + if (ui->dragging) { + game_ui uicopy = *ui; + char *movestr = interpret_move(state, &uicopy, ds, ui->dx, ui->dy, LEFT_RELEASE); + + if (movestr != NULL && strcmp(movestr, "") != 0) { + postdrop = execute_move(state, movestr); + sfree(movestr); + + state = postdrop; + } + } + + if (!ds->started) { + int aw = TILE_SIZE * state->w; + int ah = TILE_SIZE * state->h; + draw_rect(dr, 0, 0, aw + 2 * BORDER, ah + 2 * BORDER, COL_BACKGROUND); + draw_rect_outline(dr, BORDER - 1, BORDER - 1, aw + 2, ah + 2, COL_GRID); + draw_update(dr, 0, 0, aw + 2 * BORDER, ah + 2 * BORDER); + } + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*w + x; + f = 0; + dirp = -1; + + if (ui->cshow && x == ui->cx && y == ui->cy) + f |= F_CUR; + + if (ui->dragging) { + if (x == ui->sx && y == ui->sy) + f |= F_DRAG_SRC; + else if (ui->drag_is_from) { + if (!ispointing(state, ui->sx, ui->sy, x, y)) + f |= F_DIM; + } else { + if (!ispointing(state, x, y, ui->sx, ui->sy)) + f |= F_DIM; + } + } + + if (state->impossible || + state->nums[i] < 0 || state->flags[i] & FLAG_ERROR) + f |= F_ERROR; + if (state->flags[i] & FLAG_IMMUTABLE) + f |= F_IMMUTABLE; + + if (state->next[i] != -1) + f |= F_ARROW_POINT; + + if (state->prev[i] != -1) { + /* Currently the direction here is from our square _back_ + * to its previous. We could change this to give the opposite + * sense to the direction. */ + f |= F_ARROW_INPOINT; + dirp = whichdir(x, y, state->prev[i]%w, state->prev[i]/w); + } + + if (state->nums[i] != ds->nums[i] || + f != ds->f[i] || dirp != ds->dirp[i] || + force || !ds->started) { + int sign; + { + /* + * Trivial and foolish configurable option done on + * purest whim. With this option enabled, the + * victory flash is done by rotating each square + * in the opposite direction from its immediate + * neighbours, so that they behave like a field of + * interlocking gears. With it disabled, they all + * rotate in the same direction. Choose for + * yourself which is more brain-twisting :-) + */ + static int gear_mode = -1; + if (gear_mode < 0) { + char *env = getenv("SIGNPOST_GEARS"); + gear_mode = (env && (env[0] == 'y' || env[0] == 'Y')); + } + if (gear_mode) + sign = 1 - 2 * ((x ^ y) & 1); + else + sign = 1; + } + tile_redraw(dr, ds, + BORDER + x * TILE_SIZE, + BORDER + y * TILE_SIZE, + state->dirs[i], dirp, state->nums[i], f, + sign * angle_offset, -1); + ds->nums[i] = state->nums[i]; + ds->f[i] = f; + ds->dirp[i] = dirp; + } + } + } + if (ui->dragging) { + ds->dragging = TRUE; + ds->dx = ui->dx - BLITTER_SIZE/2; + ds->dy = ui->dy - BLITTER_SIZE/2; + blitter_save(dr, ds->dragb, ds->dx, ds->dy); + + draw_drag_indicator(dr, ds, state, ui, postdrop ? 1 : 0); + } + if (postdrop) free_game(postdrop); + if (!ds->started) ds->started = TRUE; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && + newstate->completed && !newstate->used_solve) + return FLASH_SPIN; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + game_compute_size(params, 1300, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int ink = print_mono_colour(dr, 0); + int x, y; + + /* Fake up just enough of a drawstate */ + game_drawstate ads, *ds = &ads; + ds->tilesize = tilesize; + ds->n = state->n; + + /* + * Border and grid. + */ + print_line_width(dr, TILE_SIZE / 40); + for (x = 1; x < state->w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(state->h), ink); + for (y = 1; y < state->h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(state->w), COORD(y), ink); + print_line_width(dr, 2*TILE_SIZE / 40); + draw_rect_outline(dr, COORD(0), COORD(0), TILE_SIZE*state->w, + TILE_SIZE*state->h, ink); + + /* + * Arrows and numbers. + */ + print_line_width(dr, 0); + for (y = 0; y < state->h; y++) + for (x = 0; x < state->w; x++) + tile_redraw(dr, ds, COORD(x), COORD(y), state->dirs[y*state->w+x], + 0, state->nums[y*state->w+x], 0, 0.0, ink); +} + +#ifdef COMBINED +#define thegame signpost +#endif + +const struct game thegame = { + "Signpost", "games.signpost", "signpost", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include +#include + +const char *quis = NULL; +int verbose = 0; + +void usage(FILE *out) { + fprintf(out, "usage: %s [--stdin] [--soak] [--seed SEED] |\n", quis); +} + +static void cycle_seed(char **seedstr, random_state *rs) +{ + char newseed[16]; + int j; + + newseed[15] = '\0'; + newseed[0] = '1' + (char)random_upto(rs, 9); + for (j = 1; j < 15; j++) + newseed[j] = '0' + (char)random_upto(rs, 10); + sfree(*seedstr); + *seedstr = dupstr(newseed); +} + +static void start_soak(game_params *p, char *seedstr) +{ + time_t tt_start, tt_now, tt_last; + char *desc, *aux; + random_state *rs; + long n = 0, nnums = 0, i; + game_state *state; + + tt_start = tt_now = time(NULL); + printf("Soak-generating a %dx%d grid.\n", p->w, p->h); + + while (1) { + rs = random_new(seedstr, strlen(seedstr)); + desc = thegame.new_desc(p, rs, &aux, 0); + + state = thegame.new_game(NULL, p, desc); + for (i = 0; i < state->n; i++) { + if (state->flags[i] & FLAG_IMMUTABLE) + nnums++; + } + thegame.free_game(state); + + sfree(desc); + cycle_seed(&seedstr, rs); + random_free(rs); + + n++; + tt_last = time(NULL); + if (tt_last > tt_now) { + tt_now = tt_last; + printf("%ld total, %3.1f/s, %3.1f nums/grid (%3.1f%%).\n", + n, + (double)n / ((double)tt_now - tt_start), + (double)nnums / (double)n, + ((double)nnums * 100.0) / ((double)n * (double)p->w * (double)p->h) ); + } + } +} + +static void process_desc(char *id) +{ + char *desc, *err, *solvestr; + game_params *p; + game_state *s; + + printf("%s\n ", id); + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: expecting game description.", quis); + exit(1); + } + + *desc++ = '\0'; + + p = thegame.default_params(); + thegame.decode_params(p, id); + err = thegame.validate_params(p, 1); + if (err) { + fprintf(stderr, "%s: %s", quis, err); + thegame.free_params(p); + return; + } + + err = thegame.validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\nDescription: %s\n", quis, err, desc); + thegame.free_params(p); + return; + } + + s = thegame.new_game(NULL, p, desc); + + solvestr = thegame.solve(s, s, NULL, &err); + if (!solvestr) + fprintf(stderr, "%s\n", err); + else + printf("Puzzle is soluble.\n"); + + thegame.free_game(s); + thegame.free_params(p); +} + +int main(int argc, const char *argv[]) +{ + char *id = NULL, *desc, *err, *aux = NULL; + int soak = 0, verbose = 0, stdin_desc = 0, n = 1, i; + char *seedstr = NULL, newseed[16]; + + setvbuf(stdout, NULL, _IONBF, 0); + + quis = argv[0]; + while (--argc > 0) { + char *p = (char*)(*++argv); + if (!strcmp(p, "-v") || !strcmp(p, "--verbose")) + verbose = 1; + else if (!strcmp(p, "--stdin")) + stdin_desc = 1; + else if (!strcmp(p, "-e") || !strcmp(p, "--seed")) { + seedstr = dupstr(*++argv); + argc--; + } else if (!strcmp(p, "-n") || !strcmp(p, "--number")) { + n = atoi(*++argv); + argc--; + } else if (!strcmp(p, "-s") || !strcmp(p, "--soak")) { + soak = 1; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + usage(stderr); + exit(1); + } else { + id = p; + } + } + + sprintf(newseed, "%lu", (unsigned long) time(NULL)); + seedstr = dupstr(newseed); + + if (id || !stdin_desc) { + if (id && strchr(id, ':')) { + /* Parameters and description passed on cmd-line: + * try and solve it. */ + process_desc(id); + } else { + /* No description passed on cmd-line: decode parameters + * (with optional seed too) */ + + game_params *p = thegame.default_params(); + + if (id) { + char *cmdseed = strchr(id, '#'); + if (cmdseed) { + *cmdseed++ = '\0'; + sfree(seedstr); + seedstr = dupstr(cmdseed); + } + + thegame.decode_params(p, id); + } + + err = thegame.validate_params(p, 1); + if (err) { + fprintf(stderr, "%s: %s", quis, err); + thegame.free_params(p); + exit(1); + } + + /* We have a set of valid parameters; either soak with it + * or generate a single game description and print to stdout. */ + if (soak) + start_soak(p, seedstr); + else { + char *pstring = thegame.encode_params(p, 0); + + for (i = 0; i < n; i++) { + random_state *rs = random_new(seedstr, strlen(seedstr)); + + if (verbose) printf("%s#%s\n", pstring, seedstr); + desc = thegame.new_desc(p, rs, &aux, 0); + printf("%s:%s\n", pstring, desc); + sfree(desc); + + cycle_seed(&seedstr, rs); + + random_free(rs); + } + + sfree(pstring); + } + thegame.free_params(p); + } + } + + if (stdin_desc) { + char buf[4096]; + + while (fgets(buf, sizeof(buf), stdin)) { + buf[strcspn(buf, "\r\n")] = '\0'; + process_desc(buf); + } + } + sfree(seedstr); + + return 0; +} + +#endif + + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/singles.R b/apps/plugins/puzzles/singles.R new file mode 100644 index 0000000000..2d10c4b388 --- /dev/null +++ b/apps/plugins/puzzles/singles.R @@ -0,0 +1,23 @@ +# -*- makefile -*- + +SINGLES_EXTRA = dsf latin maxflow tree234 + +singles : [X] GTK COMMON singles SINGLES_EXTRA singles-icon|no-icon +singles : [G] WINDOWS COMMON singles SINGLES_EXTRA singles.res|noicon.res + +ALL += singles[COMBINED] SINGLES_EXTRA + +singlessolver : [U] singles[STANDALONE_SOLVER] SINGLES_EXTRA STANDALONE +singlessolver : [C] singles[STANDALONE_SOLVER] SINGLES_EXTRA STANDALONE + +!begin am gtk +GAMES += singles +!end + +!begin >list.c + A(singles) \ +!end + +!begin >gamedesc.txt +singles:singles.exe:Singles:Number-removing puzzle:Black out the right set of duplicate numbers. +!end diff --git a/apps/plugins/puzzles/singles.c b/apps/plugins/puzzles/singles.c new file mode 100644 index 0000000000..119be29122 --- /dev/null +++ b/apps/plugins/puzzles/singles.c @@ -0,0 +1,2004 @@ +/* + * singles.c: implementation of Hitori ('let me alone') from Nikoli. + * + * Make single-get able to fetch a specific puzzle ID from menneske.no? + * + * www.menneske.no solving methods: + * + * Done: + * SC: if you circle a cell, any cells in same row/col with same no --> black + * -- solver_op_circle + * SB: if you make a cell black, any cells around it --> white + * -- solver_op_blacken + * ST: 3 identical cells in row, centre is white and outer two black. + * SP: 2 identical cells with single-cell gap, middle cell is white. + * -- solver_singlesep (both ST and SP) + * PI: if you have a pair of same number in row/col, any other + * cells of same number must be black. + * -- solve_doubles + * CC: if you have a black on edge one cell away from corner, cell + * on edge diag. adjacent must be white. + * CE: if you have 2 black cells of triangle on edge, third cell must + * be white. + * QM: if you have 3 black cells of diagonal square in middle, fourth + * cell must be white. + * -- solve_allblackbutone (CC, CE, and QM). + * QC: a corner with 4 identical numbers (or 2 and 2) must have the + * corner cell (and cell diagonal to that) black. + * TC: a corner with 3 identical numbers (with the L either way) + * must have the apex of L black, and other two white. + * DC: a corner with 2 identical numbers in domino can set a white + * cell along wall. + * -- solve_corners (QC, TC, DC) + * IP: pair with one-offset-pair force whites by offset pair + * -- solve_offsetpair + * MC: any cells diag. adjacent to black cells that would split board + * into separate white regions must be white. + * -- solve_removesplits + * + * Still to do: + * + * TEP: 3 pairs of dominos parallel to side, can mark 4 white cells + * alongside. + * DEP: 2 pairs of dominos parallel to side, can mark 2 white cells. + * FI: if you have two sets of double-cells packed together, singles + * in that row/col must be white (qv. PI) + * QuM: four identical cells (or 2 and 2) in middle of grid only have + * two possible solutions each. + * FDE: doubles one row/column away from edge can force a white cell. + * FDM: doubles in centre (next to bits of diag. square) can force a white cell. + * MP: two pairs with same number between force number to black. + * CnC: if circling a cell leads to impossible board, cell is black. + * MC: if we have two possiblilities, can we force a white circle? + * + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "latin.h" + +#ifdef STANDALONE_SOLVER +int verbose = 0; +#endif + +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 2) + +#define CRAD ((TILE_SIZE / 2) - 1) +#define TEXTSZ ((14*CRAD/10) - 1) /* 2 * sqrt(2) of CRAD */ + +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h) + +#define FLASH_TIME 0.7F + +enum { + COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT, + COL_BLACK, COL_WHITE, COL_BLACKNUM, COL_GRID, + COL_CURSOR, COL_ERROR, + NCOLOURS +}; + +struct game_params { + int w, h, diff; +}; + +#define F_BLACK 0x1 +#define F_CIRCLE 0x2 +#define F_ERROR 0x4 +#define F_SCRATCH 0x8 + +struct game_state { + int w, h, n, o; /* n = w*h; o = max(w, h) */ + int completed, used_solve, impossible; + int *nums; /* size w*h */ + unsigned int *flags; /* size w*h */ +}; + +/* top, right, bottom, left */ +static const int dxs[4] = { 0, 1, 0, -1 }; +static const int dys[4] = { -1, 0, 1, 0 }; + +/* --- Game parameters and preset functions --- */ + +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(TRICKY,Tricky,k) + +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title + +enum { DIFFLIST(ENUM) DIFF_MAX, DIFF_ANY }; +static char const *const singles_diffnames[] = { DIFFLIST(TITLE) }; +static char const singles_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCOUNT lenof(singles_diffchars) +#define DIFFCONFIG DIFFLIST(CONFIG) + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + ret->w = ret->h = 5; + ret->diff = DIFF_EASY; + + return ret; +} + +static const struct game_params singles_presets[] = { + { 5, 5, DIFF_EASY }, + { 5, 5, DIFF_TRICKY }, + { 6, 6, DIFF_EASY }, + { 6, 6, DIFF_TRICKY }, + { 8, 8, DIFF_EASY }, + { 8, 8, DIFF_TRICKY }, + { 10, 10, DIFF_EASY }, + { 10, 10, DIFF_TRICKY }, + { 12, 12, DIFF_EASY }, + { 12, 12, DIFF_TRICKY } +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(singles_presets)) + return FALSE; + + ret = default_params(); + *ret = singles_presets[i]; + *params = ret; + + sprintf(buf, "%dx%d %s", ret->w, ret->h, singles_diffnames[ret->diff]); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + char const *p = string; + int i; + + ret->w = ret->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + ret->h = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } + if (*p == 'd') { + ret->diff = DIFF_MAX; /* which is invalid */ + p++; + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == singles_diffchars[i]) + ret->diff = i; + } + p++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + if (full) + sprintf(data, "%dx%dd%c", params->w, params->h, singles_diffchars[params->diff]); + else + sprintf(data, "%dx%d", params->w, params->h); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 2 || params->h < 2) + return "Width and neight must be at least two"; + if (params->w > 10+26+26 || params->h > 10+26+26) + return "Puzzle is too large"; + if (full) { + if (params->diff < 0 || params->diff >= DIFF_MAX) + return "Unknown difficulty rating"; + } + + return NULL; +} + +/* --- Game description string generation and unpicking --- */ + +static game_state *blank_game(int w, int h) +{ + game_state *state = snew(game_state); + + memset(state, 0, sizeof(game_state)); + state->w = w; + state->h = h; + state->n = w*h; + state->o = max(w,h); + + state->completed = state->used_solve = state->impossible = 0; + + state->nums = snewn(state->n, int); + state->flags = snewn(state->n, unsigned int); + + memset(state->nums, 0, state->n*sizeof(int)); + memset(state->flags, 0, state->n*sizeof(unsigned int)); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = blank_game(state->w, state->h); + + ret->completed = state->completed; + ret->used_solve = state->used_solve; + ret->impossible = state->impossible; + + memcpy(ret->nums, state->nums, state->n*sizeof(int)); + memcpy(ret->flags, state->flags, state->n*sizeof(unsigned int)); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->nums); + sfree(state->flags); + sfree(state); +} + +static char n2c(int num) { + if (num < 10) + return '0' + num; + else if (num < 10+26) + return 'a' + num - 10; + else + return 'A' + num - 10 - 26; + return '?'; +} + +static int c2n(char c) { + if (isdigit((unsigned char)c)) + return (int)(c - '0'); + else if (c >= 'a' && c <= 'z') + return (int)(c - 'a' + 10); + else if (c >= 'A' && c <= 'Z') + return (int)(c - 'A' + 10 + 26); + return -1; +} + +static void unpick_desc(const game_params *params, const char *desc, + game_state **sout, char **mout) +{ + game_state *state = blank_game(params->w, params->h); + char *msg = NULL; + int num = 0, i = 0; + + if (strlen(desc) != state->n) { + msg = "Game description is wrong length"; + goto done; + } + for (i = 0; i < state->n; i++) { + num = c2n(desc[i]); + if (num <= 0 || num > state->o) { + msg = "Game description contains unexpected characters"; + goto done; + } + state->nums[i] = num; + } +done: + if (msg) { /* sth went wrong. */ + if (mout) *mout = msg; + free_game(state); + } else { + if (mout) *mout = NULL; + if (sout) *sout = state; + else free_game(state); + } +} + +static char *generate_desc(game_state *state, int issolve) +{ + char *ret = snewn(state->n+1+(issolve?1:0), char); + int i, p=0; + + if (issolve) + ret[p++] = 'S'; + for (i = 0; i < state->n; i++) + ret[p++] = n2c(state->nums[i]); + ret[p] = '\0'; + return ret; +} + +/* --- Useful game functions (completion, etc.) --- */ + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int len, x, y, i; + char *ret, *p; + + len = (state->w)*2; /* one row ... */ + len = len * (state->h*2); /* ... h rows, including gaps ... */ + len += 1; /* ... final NL */ + p = ret = snewn(len, char); + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + i = y*state->w + x; + if (x > 0) *p++ = ' '; + *p++ = (state->flags[i] & F_BLACK) ? '*' : n2c(state->nums[i]); + } + *p++ = '\n'; + for (x = 0; x < state->w; x++) { + i = y*state->w + x; + if (x > 0) *p++ = ' '; + *p++ = (state->flags[i] & F_CIRCLE) ? '~' : ' '; + } + *p++ = '\n'; + } + *p++ = '\0'; + assert(p - ret == len); + + return ret; +} + +static void debug_state(const char *desc, game_state *state) { + char *dbg = game_text_format(state); + debug(("%s:\n%s", desc, dbg)); + sfree(dbg); +} + +static void connect_if_same(game_state *state, int *dsf, int i1, int i2) +{ + int c1, c2; + + if ((state->flags[i1] & F_BLACK) != (state->flags[i2] & F_BLACK)) + return; + + c1 = dsf_canonify(dsf, i1); + c2 = dsf_canonify(dsf, i2); + dsf_merge(dsf, c1, c2); +} + +static void connect_dsf(game_state *state, int *dsf) +{ + int x, y, i; + + /* Construct a dsf array for connected blocks; connections + * tracked to right and down. */ + dsf_init(dsf, state->n); + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*state->w + x; + + if (x < state->w-1) + connect_if_same(state, dsf, i, i+1); /* right */ + if (y < state->h-1) + connect_if_same(state, dsf, i, i+state->w); /* down */ + } + } +} + +#define CC_MARK_ERRORS 1 +#define CC_MUST_FILL 2 + +static int check_rowcol(game_state *state, int starti, int di, int sz, unsigned flags) +{ + int nerr = 0, n, m, i, j; + + /* if any circled numbers have identical non-circled numbers on + * same row/column, error (non-circled) + * if any circled numbers in same column are same number, highlight them. + * if any rows/columns have >1 of same number, not complete. */ + + for (n = 0, i = starti; n < sz; n++, i += di) { + if (state->flags[i] & F_BLACK) continue; + for (m = n+1, j = i+di; m < sz; m++, j += di) { + if (state->flags[j] & F_BLACK) continue; + if (state->nums[i] != state->nums[j]) continue; + + nerr++; /* ok, we have two numbers the same in a row. */ + if (!(flags & CC_MARK_ERRORS)) continue; + + /* If we have two circles in the same row around + * two identical numbers, they are _both_ wrong. */ + if ((state->flags[i] & F_CIRCLE) && + (state->flags[j] & F_CIRCLE)) { + state->flags[i] |= F_ERROR; + state->flags[j] |= F_ERROR; + } + /* Otherwise, if we have a circle, any other identical + * numbers in that row are obviously wrong. We don't + * highlight this, however, since it makes the process + * of solving the puzzle too easy (you circle a number + * and it promptly tells you which numbers to blacken! */ +#if 0 + else if (state->flags[i] & F_CIRCLE) + state->flags[j] |= F_ERROR; + else if (state->flags[j] & F_CIRCLE) + state->flags[i] |= F_ERROR; +#endif + } + } + return nerr; +} + +static int check_complete(game_state *state, unsigned flags) +{ + int *dsf = snewn(state->n, int); + int x, y, i, error = 0, nwhite, w = state->w, h = state->h; + + if (flags & CC_MARK_ERRORS) { + for (i = 0; i < state->n; i++) + state->flags[i] &= ~F_ERROR; + } + connect_dsf(state, dsf); + + /* If we're the solver we need the grid all to be definitively + * black or definitively white (i.e. circled) otherwise the solver + * has found an ambiguous grid. */ + if (flags & CC_MUST_FILL) { + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & F_BLACK) && !(state->flags[i] & F_CIRCLE)) + error += 1; + } + } + + /* Mark any black squares in groups of >1 as errors. + * Count number of white squares. */ + nwhite = 0; + for (i = 0; i < state->n; i++) { + if (state->flags[i] & F_BLACK) { + if (dsf_size(dsf, i) > 1) { + error += 1; + if (flags & CC_MARK_ERRORS) + state->flags[i] |= F_ERROR; + } + } else + nwhite += 1; + } + + /* Check attributes of white squares, row- and column-wise. */ + for (x = 0; x < w; x++) /* check cols from (x,0) */ + error += check_rowcol(state, x, w, h, flags); + for (y = 0; y < h; y++) /* check rows from (0,y) */ + error += check_rowcol(state, y*w, 1, w, flags); + + /* If there's more than one white region, pick the largest one to + * be the canonical one (arbitrarily tie-breaking towards lower + * array indices), and mark all the others as erroneous. */ + { + int largest = 0, canonical = -1; + for (i = 0; i < state->n; i++) + if (!(state->flags[i] & F_BLACK)) { + int size = dsf_size(dsf, i); + if (largest < size) { + largest = size; + canonical = i; + } + } + + if (largest < nwhite) { + for (i = 0; i < state->n; i++) + if (!(state->flags[i] & F_BLACK) && + dsf_canonify(dsf, i) != canonical) { + error += 1; + if (flags & CC_MARK_ERRORS) + state->flags[i] |= F_ERROR; + } + } + } + + sfree(dsf); + return (error > 0) ? 0 : 1; +} + +static char *game_state_diff(const game_state *src, const game_state *dst, + int issolve) +{ + char *ret = NULL, buf[80], c; + int retlen = 0, x, y, i, k; + unsigned int fmask = F_BLACK | F_CIRCLE; + + assert(src->n == dst->n); + + if (issolve) { + ret = sresize(ret, 3, char); + ret[0] = 'S'; ret[1] = ';'; ret[2] = '\0'; + retlen += 2; + } + + for (x = 0; x < dst->w; x++) { + for (y = 0; y < dst->h; y++) { + i = y*dst->w + x; + if ((src->flags[i] & fmask) != (dst->flags[i] & fmask)) { + assert((dst->flags[i] & fmask) != fmask); + if (dst->flags[i] & F_BLACK) + c = 'B'; + else if (dst->flags[i] & F_CIRCLE) + c = 'C'; + else + c = 'E'; + k = sprintf(buf, "%c%d,%d;", (int)c, x, y); + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + } + } + return ret; +} + +/* --- Solver --- */ + +enum { BLACK, CIRCLE }; + +struct solver_op { + int x, y, op; /* op one of BLACK or CIRCLE. */ + const char *desc; /* must be non-malloced. */ +}; + +struct solver_state { + struct solver_op *ops; + int n_ops, n_alloc; + int *scratch; +}; + +static struct solver_state *solver_state_new(game_state *state) +{ + struct solver_state *ss = snew(struct solver_state); + + ss->ops = NULL; + ss->n_ops = ss->n_alloc = 0; + ss->scratch = snewn(state->n, int); + + return ss; +} + +static void solver_state_free(struct solver_state *ss) +{ + sfree(ss->scratch); + if (ss->ops) sfree(ss->ops); + sfree(ss); +} + +static void solver_op_add(struct solver_state *ss, int x, int y, int op, const char *desc) +{ + struct solver_op *sop; + + if (ss->n_alloc < ss->n_ops + 1) { + ss->n_alloc = (ss->n_alloc + 1) * 2; + ss->ops = sresize(ss->ops, ss->n_alloc, struct solver_op); + } + sop = &(ss->ops[ss->n_ops++]); + sop->x = x; sop->y = y; sop->op = op; sop->desc = desc; + debug(("added solver op %s ('%s') at (%d,%d)\n", + op == BLACK ? "BLACK" : "CIRCLE", desc, x, y)); +} + +static void solver_op_circle(game_state *state, struct solver_state *ss, + int x, int y) +{ + int i = y*state->w + x; + + if (!INGRID(state, x, y)) return; + if (state->flags[i] & F_BLACK) { + debug(("... solver wants to add auto-circle on black (%d,%d)\n", x, y)); + state->impossible = 1; + return; + } + /* Only add circle op if it's not already circled. */ + if (!(state->flags[i] & F_CIRCLE)) { + solver_op_add(ss, x, y, CIRCLE, "SB - adjacent to black square"); + } +} + +static void solver_op_blacken(game_state *state, struct solver_state *ss, + int x, int y, int num) +{ + int i = y*state->w + x; + + if (!INGRID(state, x, y)) return; + if (state->nums[i] != num) return; + if (state->flags[i] & F_CIRCLE) { + debug(("... solver wants to add auto-black on circled(%d,%d)\n", x, y)); + state->impossible = 1; + return; + } + /* Only add black op if it's not already black. */ + if (!(state->flags[i] & F_BLACK)) { + solver_op_add(ss, x, y, BLACK, "SC - number on same row/col as circled"); + } +} + +static int solver_ops_do(game_state *state, struct solver_state *ss) +{ + int next_op = 0, i, x, y, n_ops = 0; + struct solver_op op; + + /* Care here: solver_op_* may call solver_op_add which may extend the + * ss->n_ops. */ + + while (next_op < ss->n_ops) { + op = ss->ops[next_op++]; /* copy this away, it may get reallocated. */ + i = op.y*state->w + op.x; + + if (op.op == BLACK) { + if (state->flags[i] & F_CIRCLE) { + debug(("Solver wants to blacken circled square (%d,%d)!\n", op.x, op.y)); + state->impossible = 1; + return n_ops; + } + if (!(state->flags[i] & F_BLACK)) { + debug(("... solver adding black at (%d,%d): %s\n", op.x, op.y, op.desc)); +#ifdef STANDALONE_SOLVER + if (verbose) + printf("Adding black at (%d,%d): %s\n", op.x, op.y, op.desc); +#endif + state->flags[i] |= F_BLACK; + /*debug_state("State after adding black", state);*/ + n_ops++; + solver_op_circle(state, ss, op.x-1, op.y); + solver_op_circle(state, ss, op.x+1, op.y); + solver_op_circle(state, ss, op.x, op.y-1); + solver_op_circle(state, ss, op.x, op.y+1); + } + } else { + if (state->flags[i] & F_BLACK) { + debug(("Solver wants to circle blackened square (%d,%d)!\n", op.x, op.y)); + state->impossible = 1; + return n_ops; + } + if (!(state->flags[i] & F_CIRCLE)) { + debug(("... solver adding circle at (%d,%d): %s\n", op.x, op.y, op.desc)); +#ifdef STANDALONE_SOLVER + if (verbose) + printf("Adding circle at (%d,%d): %s\n", op.x, op.y, op.desc); +#endif + state->flags[i] |= F_CIRCLE; + /*debug_state("State after adding circle", state);*/ + n_ops++; + for (x = 0; x < state->w; x++) { + if (x != op.x) + solver_op_blacken(state, ss, x, op.y, state->nums[i]); + } + for (y = 0; y < state->h; y++) { + if (y != op.y) + solver_op_blacken(state, ss, op.x, y, state->nums[i]); + } + } + } + } + ss->n_ops = 0; + return n_ops; +} + +/* If the grid has two identical numbers with one cell between them, the inner + * cell _must_ be white (and thus circled); (at least) one of the two must be + * black (since they're in the same column or row) and thus the middle cell is + * next to a black cell. */ +static int solve_singlesep(game_state *state, struct solver_state *ss) +{ + int x, y, i, ir, irr, id, idd, n_ops = ss->n_ops; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*state->w + x; + + /* Cell two to our right? */ + ir = i + 1; irr = ir + 1; + if (x < (state->w-2) && + state->nums[i] == state->nums[irr] && + !(state->flags[ir] & F_CIRCLE)) { + solver_op_add(ss, x+1, y, CIRCLE, "SP/ST - between identical nums"); + } + /* Cell two below us? */ + id = i + state->w; idd = id + state->w; + if (y < (state->h-2) && + state->nums[i] == state->nums[idd] && + !(state->flags[id] & F_CIRCLE)) { + solver_op_add(ss, x, y+1, CIRCLE, "SP/ST - between identical nums"); + } + } + } + return ss->n_ops - n_ops; +} + +/* If we have two identical numbers next to each other (in a row or column), + * any other identical numbers in that column must be black. */ +static int solve_doubles(game_state *state, struct solver_state *ss) +{ + int x, y, i, ii, n_ops = ss->n_ops, xy; + + for (y = 0, i = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++, i++) { + assert(i == y*state->w+x); + if (state->flags[i] & F_BLACK) continue; + + ii = i+1; /* check cell to our right. */ + if (x < (state->w-1) && + !(state->flags[ii] & F_BLACK) && + state->nums[i] == state->nums[ii]) { + for (xy = 0; xy < state->w; xy++) { + if (xy == x || xy == (x+1)) continue; + if (state->nums[y*state->w + xy] == state->nums[i] && + !(state->flags[y*state->w + xy] & F_BLACK)) + solver_op_add(ss, xy, y, BLACK, "PI - same row as pair"); + } + } + + ii = i+state->w; /* check cell below us */ + if (y < (state->h-1) && + !(state->flags[ii] & F_BLACK) && + state->nums[i] == state->nums[ii]) { + for (xy = 0; xy < state->h; xy++) { + if (xy == y || xy == (y+1)) continue; + if (state->nums[xy*state->w + x] == state->nums[i] && + !(state->flags[xy*state->w + x] & F_BLACK)) + solver_op_add(ss, x, xy, BLACK, "PI - same col as pair"); + } + } + } + } + return ss->n_ops - n_ops; +} + +/* If a white square has all-but-one possible adjacent squares black, the + * one square left over must be white. */ +static int solve_allblackbutone(game_state *state, struct solver_state *ss) +{ + int x, y, i, n_ops = ss->n_ops, xd, yd, id, ifree; + int dis[4], d; + + dis[0] = -state->w; + dis[1] = 1; + dis[2] = state->w; + dis[3] = -1; + + for (y = 0, i = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++, i++) { + assert(i == y*state->w+x); + if (state->flags[i] & F_BLACK) continue; + + ifree = -1; + for (d = 0; d < 4; d++) { + xd = x + dxs[d]; yd = y + dys[d]; id = i + dis[d]; + if (!INGRID(state, xd, yd)) continue; + + if (state->flags[id] & F_CIRCLE) + goto skip; /* this cell already has a way out */ + if (!(state->flags[id] & F_BLACK)) { + if (ifree != -1) + goto skip; /* this cell has >1 white cell around it. */ + ifree = id; + } + } + if (ifree != -1) + solver_op_add(ss, ifree%state->w, ifree/state->w, CIRCLE, + "CC/CE/QM: white cell with single non-black around it"); + else { + debug(("White cell with no escape at (%d,%d)\n", x, y)); + state->impossible = 1; + return 0; + } +skip: ; + } + } + return ss->n_ops - n_ops; +} + +/* If we have 4 numbers the same in a 2x2 corner, the far corner and the + * diagonally-adjacent square must both be black. + * If we have 3 numbers the same in a 2x2 corner, the apex of the L + * thus formed must be black. + * If we have 2 numbers the same in a 2x2 corner, the non-same cell + * one away from the corner must be white. */ +static void solve_corner(game_state *state, struct solver_state *ss, + int x, int y, int dx, int dy) +{ + int is[4], ns[4], xx, yy, w = state->w; + + for (yy = 0; yy < 2; yy++) { + for (xx = 0; xx < 2; xx++) { + is[yy*2+xx] = (y + dy*yy) * w + (x + dx*xx); + ns[yy*2+xx] = state->nums[is[yy*2+xx]]; + } + } /* order is now (corner, side 1, side 2, inner) */ + + if (ns[0] == ns[1] && ns[0] == ns[2] && ns[0] == ns[3]) { + solver_op_add(ss, is[0]%w, is[0]/w, BLACK, "QC: corner with 4 matching"); + solver_op_add(ss, is[3]%w, is[3]/w, BLACK, "QC: corner with 4 matching"); + } else if (ns[0] == ns[1] && ns[0] == ns[2]) { + /* corner and 2 sides: apex is corner. */ + solver_op_add(ss, is[0]%w, is[0]/w, BLACK, "TC: corner apex from 3 matching"); + } else if (ns[1] == ns[2] && ns[1] == ns[3]) { + /* side, side, fourth: apex is fourth. */ + solver_op_add(ss, is[3]%w, is[3]/w, BLACK, "TC: inside apex from 3 matching"); + } else if (ns[0] == ns[1] || ns[1] == ns[3]) { + /* either way here we match the non-identical side. */ + solver_op_add(ss, is[2]%w, is[2]/w, CIRCLE, "DC: corner with 2 matching"); + } else if (ns[0] == ns[2] || ns[2] == ns[3]) { + /* ditto */ + solver_op_add(ss, is[1]%w, is[1]/w, CIRCLE, "DC: corner with 2 matching"); + } +} + +static int solve_corners(game_state *state, struct solver_state *ss) +{ + int n_ops = ss->n_ops; + + solve_corner(state, ss, 0, 0, 1, 1); + solve_corner(state, ss, state->w-1, 0, -1, 1); + solve_corner(state, ss, state->w-1, state->h-1, -1, -1); + solve_corner(state, ss, 0, state->h-1, 1, -1); + + return ss->n_ops - n_ops; +} + +/* If you have the following situation: + * ... + * ...x A x x y A x... + * ...x B x x B y x... + * ... + * then both squares marked 'y' must be white. One of the left-most A or B must + * be white (since two side-by-side black cells are disallowed), which means + * that the corresponding right-most A or B must be black (since you can't + * have two of the same number on one line); thus, the adjacent squares + * to that right-most A or B must be white, which include the two marked 'y' + * in either case. + * Obviously this works in any row or column. It also works if A == B. + * It doesn't work for the degenerate case: + * ...x A A x x + * ...x B y x x + * where the square marked 'y' isn't necessarily white (consider the left-most A + * is black). + * + * */ +static void solve_offsetpair_pair(game_state *state, struct solver_state *ss, + int x1, int y1, int x2, int y2) +{ + int ox, oy, w = state->w, ax, ay, an, d, dx[2], dy[2], dn, xd, yd; + + if (x1 == x2) { /* same column */ + ox = 1; oy = 0; + } else { + assert(y1 == y2); + ox = 0; oy = 1; + } + + /* We try adjacent to (x1,y1) and the two diag. adjacent to (x2, y2). + * We expect to be called twice, once each way around. */ + ax = x1+ox; ay = y1+oy; + assert(INGRID(state, ax, ay)); + an = state->nums[ay*w + ax]; + + dx[0] = x2 + ox + oy; dx[1] = x2 + ox - oy; + dy[0] = y2 + oy + ox; dy[1] = y2 + oy - ox; + + for (d = 0; d < 2; d++) { + if (INGRID(state, dx[d], dy[d]) && (dx[d] != ax || dy[d] != ay)) { + /* The 'dx != ax || dy != ay' removes the degenerate case, + * mentioned above. */ + dn = state->nums[dy[d]*w + dx[d]]; + if (an == dn) { + /* We have a match; so (WLOG) the 'A' marked above are at + * (x1,y1) and (x2,y2), and the 'B' are at (ax,ay) and (dx,dy). */ + debug(("Found offset-pair: %d at (%d,%d) and (%d,%d)\n", + state->nums[y1*w + x1], x1, y1, x2, y2)); + debug((" and: %d at (%d,%d) and (%d,%d)\n", + an, ax, ay, dx[d], dy[d])); + + xd = dx[d] - x2; yd = dy[d] - y2; + solver_op_add(ss, x2 + xd, y2, CIRCLE, "IP: next to offset-pair"); + solver_op_add(ss, x2, y2 + yd, CIRCLE, "IP: next to offset-pair"); + } + } + } +} + +static int solve_offsetpair(game_state *state, struct solver_state *ss) +{ + int n_ops = ss->n_ops, x, xx, y, yy, n1, n2; + + for (x = 0; x < state->w-1; x++) { + for (y = 0; y < state->h; y++) { + n1 = state->nums[y*state->w + x]; + for (yy = y+1; yy < state->h; yy++) { + n2 = state->nums[yy*state->w + x]; + if (n1 == n2) { + solve_offsetpair_pair(state, ss, x, y, x, yy); + solve_offsetpair_pair(state, ss, x, yy, x, y); + } + } + } + } + for (y = 0; y < state->h-1; y++) { + for (x = 0; x < state->w; x++) { + n1 = state->nums[y*state->w + x]; + for (xx = x+1; xx < state->w; xx++) { + n2 = state->nums[y*state->w + xx]; + if (n1 == n2) { + solve_offsetpair_pair(state, ss, x, y, xx, y); + solve_offsetpair_pair(state, ss, xx, y, x, y); + } + } + } + } + return ss->n_ops - n_ops; +} + +static int solve_hassinglewhiteregion(game_state *state, struct solver_state *ss) +{ + int i, j, nwhite = 0, lwhite = -1, szwhite, start, end, next, a, d, x, y; + + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & F_BLACK)) { + nwhite++; + lwhite = i; + } + state->flags[i] &= ~F_SCRATCH; + } + if (lwhite == -1) { + debug(("solve_hassinglewhite: no white squares found!\n")); + state->impossible = 1; + return 0; + } + /* We don't use connect_dsf here; it's too slow, and there's a quicker + * algorithm if all we want is the size of one region. */ + /* Having written this, this algorithm is only about 5% faster than + * using a dsf. */ + memset(ss->scratch, -1, state->n * sizeof(int)); + ss->scratch[0] = lwhite; + state->flags[lwhite] |= F_SCRATCH; + start = 0; end = next = 1; + while (start < end) { + for (a = start; a < end; a++) { + i = ss->scratch[a]; assert(i != -1); + for (d = 0; d < 4; d++) { + x = (i % state->w) + dxs[d]; + y = (i / state->w) + dys[d]; + j = y*state->w + x; + if (!INGRID(state, x, y)) continue; + if (state->flags[j] & (F_BLACK | F_SCRATCH)) continue; + ss->scratch[next++] = j; + state->flags[j] |= F_SCRATCH; + } + } + start = end; end = next; + } + szwhite = next; + return (szwhite == nwhite) ? 1 : 0; +} + +static void solve_removesplits_check(game_state *state, struct solver_state *ss, + int x, int y) +{ + int i = y*state->w + x, issingle; + + if (!INGRID(state, x, y)) return; + if ((state->flags[i] & F_CIRCLE) || (state->flags[i] & F_BLACK)) + return; + + /* If putting a black square at (x,y) would make the white region + * non-contiguous, it must be circled. */ + state->flags[i] |= F_BLACK; + issingle = solve_hassinglewhiteregion(state, ss); + state->flags[i] &= ~F_BLACK; + + if (!issingle) + solver_op_add(ss, x, y, CIRCLE, "MC: black square here would split white region"); +} + +/* For all black squares, search in squares diagonally adjacent to see if + * we can rule out putting a black square there (because it would make the + * white region non-contiguous). */ +/* This function is likely to be somewhat slow. */ +static int solve_removesplits(game_state *state, struct solver_state *ss) +{ + int i, x, y, n_ops = ss->n_ops; + + if (!solve_hassinglewhiteregion(state, ss)) { + debug(("solve_removesplits: white region is not contiguous at start!\n")); + state->impossible = 1; + return 0; + } + + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & F_BLACK)) continue; + + x = i%state->w; y = i/state->w; + solve_removesplits_check(state, ss, x-1, y-1); + solve_removesplits_check(state, ss, x+1, y-1); + solve_removesplits_check(state, ss, x+1, y+1); + solve_removesplits_check(state, ss, x-1, y+1); + } + return ss->n_ops - n_ops; +} + +/* + * This function performs a solver step that isn't implicit in the rules + * of the game and is thus treated somewhat differently. + * + * It marks cells whose number does not exist elsewhere in its row/column + * with circles. As it happens the game generator here does mean that this + * is always correct, but it's a solving method that people should not have + * to rely upon (except in the hidden 'sneaky' difficulty setting) and so + * all grids at 'tricky' and above are checked to make sure that the grid + * is no easier if this solving step is performed beforehand. + * + * Calling with ss=NULL just returns the number of sneaky deductions that + * would have been made. + */ +static int solve_sneaky(game_state *state, struct solver_state *ss) +{ + int i, ii, x, xx, y, yy, nunique = 0; + + /* Clear SCRATCH flags. */ + for (i = 0; i < state->n; i++) state->flags[i] &= ~F_SCRATCH; + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*state->w + x; + + /* Check for duplicate numbers on our row, mark (both) if so */ + for (xx = x; xx < state->w; xx++) { + ii = y*state->w + xx; + if (i == ii) continue; + + if (state->nums[i] == state->nums[ii]) { + state->flags[i] |= F_SCRATCH; + state->flags[ii] |= F_SCRATCH; + } + } + + /* Check for duplicate numbers on our col, mark (both) if so */ + for (yy = y; yy < state->h; yy++) { + ii = yy*state->w + x; + if (i == ii) continue; + + if (state->nums[i] == state->nums[ii]) { + state->flags[i] |= F_SCRATCH; + state->flags[ii] |= F_SCRATCH; + } + } + } + } + + /* Any cell with no marking has no duplicates on its row or column: + * set its CIRCLE. */ + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & F_SCRATCH)) { + if (ss) solver_op_add(ss, i%state->w, i/state->w, CIRCLE, + "SNEAKY: only one of its number in row and col"); + nunique += 1; + } else + state->flags[i] &= ~F_SCRATCH; + } + return nunique; +} + +static int solve_specific(game_state *state, int diff, int sneaky) +{ + struct solver_state *ss = solver_state_new(state); + + if (sneaky) solve_sneaky(state, ss); + + /* Some solver operations we only have to perform once -- + * they're only based on the numbers available, and not black + * squares or circles which may be added later. */ + + solve_singlesep(state, ss); /* never sets impossible */ + solve_doubles(state, ss); /* ditto */ + solve_corners(state, ss); /* ditto */ + + if (diff >= DIFF_TRICKY) + solve_offsetpair(state, ss); /* ditto */ + + while (1) { + if (ss->n_ops > 0) solver_ops_do(state, ss); + if (state->impossible) break; + + if (solve_allblackbutone(state, ss) > 0) continue; + if (state->impossible) break; + + if (diff >= DIFF_TRICKY) { + if (solve_removesplits(state, ss) > 0) continue; + if (state->impossible) break; + } + + break; + } + + solver_state_free(ss); + return state->impossible ? -1 : check_complete(state, CC_MUST_FILL); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved = dup_game(currstate); + char *move = NULL; + + if (solve_specific(solved, DIFF_ANY, 0) > 0) goto solved; + free_game(solved); + + solved = dup_game(state); + if (solve_specific(solved, DIFF_ANY, 0) > 0) goto solved; + free_game(solved); + + *error = "Unable to solve puzzle."; + return NULL; + +solved: + move = game_state_diff(currstate, solved, 1); + free_game(solved); + return move; +} + +/* --- Game generation --- */ + +/* A correctly completed Hitori board is essentially a latin square + * (no duplicated numbers in any row or column) with black squares + * added such that no black square touches another, and the white + * squares make a contiguous region. + * + * So we can generate it by: + * constructing a latin square + * adding black squares at random (minding the constraints) + * altering the numbers under the new black squares such that + the solver gets a headstart working out where they are. + */ + +static int new_game_is_good(const game_params *params, + game_state *state, game_state *tosolve) +{ + int sret, sret_easy = 0; + + memcpy(tosolve->nums, state->nums, state->n * sizeof(int)); + memset(tosolve->flags, 0, state->n * sizeof(unsigned int)); + tosolve->completed = tosolve->impossible = 0; + + /* + * We try and solve it twice, once at our requested difficulty level + * (ensuring it's soluble at all) and once at the level below (if + * it exists), which we hope to fail: if you can also solve it at + * the level below then it's too easy and we have to try again. + * + * With this puzzle in particular there's an extra finesse, which is + * that we check that the generated puzzle isn't too easy _with + * an extra solver step first_, which is the 'sneaky' mode of deductions + * (asserting that any number which fulfils the latin-square rules + * on its row/column must be white). This is an artefact of the + * generation process and not implicit in the rules, so we don't want + * people to be able to use it to make the puzzle easier. + */ + + assert(params->diff < DIFF_MAX); + sret = solve_specific(tosolve, params->diff, 0); + if (params->diff > DIFF_EASY) { + memset(tosolve->flags, 0, state->n * sizeof(unsigned int)); + tosolve->completed = tosolve->impossible = 0; + + /* this is the only time the 'sneaky' flag is set to 1. */ + sret_easy = solve_specific(tosolve, params->diff-1, 1); + } + + if (sret <= 0 || sret_easy > 0) { + debug(("Generated puzzle %s at chosen difficulty %s\n", + sret <= 0 ? "insoluble" : "too easy", + singles_diffnames[params->diff])); + return 0; + } + return 1; +} + +#define MAXTRIES 20 + +static int best_black_col(game_state *state, random_state *rs, int *scratch, + int i, int *rownums, int *colnums) +{ + int w = state->w, x = i%w, y = i/w, j, o = state->o; + + /* Randomise the list of numbers to try. */ + for (i = 0; i < o; i++) scratch[i] = i; + shuffle(scratch, o, sizeof(int), rs); + + /* Try each number in turn, first giving preference to removing + * latin-square characteristics (i.e. those numbers which only + * occur once in a row/column). The '&&' here, although intuitively + * wrong, results in a smaller number of 'sneaky' deductions on + * solvable boards. */ + for (i = 0; i < o; i++) { + j = scratch[i] + 1; + if (rownums[y*o + j-1] == 1 && colnums[x*o + j-1] == 1) + goto found; + } + + /* Then try each number in turn returning the first one that's + * not actually unique in its row/column (see comment below) */ + for (i = 0; i < o; i++) { + j = scratch[i] + 1; + if (rownums[y*o + j-1] != 0 || colnums[x*o + j-1] != 0) + goto found; + } + assert(!"unable to place number under black cell."); + return 0; + +found: + /* Update column and row counts assuming this number will be placed. */ + rownums[y*o + j-1] += 1; + colnums[x*o + j-1] += 1; + return j; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + game_state *state = blank_game(params->w, params->h); + game_state *tosolve = blank_game(params->w, params->h); + int i, j, *scratch, *rownums, *colnums, x, y, ntries; + int w = state->w, h = state->h, o = state->o; + char *ret; + digit *latin; + struct solver_state *ss = solver_state_new(state); + + scratch = snewn(state->n, int); + rownums = snewn(h*o, int); + colnums = snewn(w*o, int); + +generate: + ss->n_ops = 0; + debug(("Starting game generation, size %dx%d\n", w, h)); + + memset(state->flags, 0, state->n*sizeof(unsigned int)); + + /* First, generate the latin rectangle. + * The order of this, o, is max(w,h). */ + latin = latin_generate_rect(w, h, rs); + for (i = 0; i < state->n; i++) + state->nums[i] = (int)latin[i]; + sfree(latin); + debug_state("State after latin square", state); + + /* Add black squares at random, using bits of solver as we go (to lay + * white squares), until we can lay no more blacks. */ + for (i = 0; i < state->n; i++) + scratch[i] = i; + shuffle(scratch, state->n, sizeof(int), rs); + for (j = 0; j < state->n; j++) { + i = scratch[j]; + if ((state->flags[i] & F_CIRCLE) || (state->flags[i] & F_BLACK)) { + debug(("generator skipping (%d,%d): %s\n", i%w, i/w, + (state->flags[i] & F_CIRCLE) ? "CIRCLE" : "BLACK")); + continue; /* solver knows this must be one or the other already. */ + } + + /* Add a random black cell... */ + solver_op_add(ss, i%w, i/w, BLACK, "Generator: adding random black cell"); + solver_ops_do(state, ss); + + /* ... and do as well as we know how to lay down whites that are now forced. */ + solve_allblackbutone(state, ss); + solver_ops_do(state, ss); + + solve_removesplits(state, ss); + solver_ops_do(state, ss); + + if (state->impossible) { + debug(("generator made impossible, restarting...\n")); + goto generate; + } + } + debug_state("State after adding blacks", state); + + /* Now we know which squares are white and which are black, we lay numbers + * under black squares at random, except that the number must appear in + * white cells at least once more in the same column or row as that [black] + * square. That's necessary to avoid multiple solutions, where blackening + * squares in the finished puzzle becomes optional. We use two arrays: + * + * rownums[ROW * o + NUM-1] is the no. of white cells containing NUM in y=ROW + * colnums[COL * o + NUM-1] is the no. of white cells containing NUM in x=COL + */ + + memset(rownums, 0, h*o * sizeof(int)); + memset(colnums, 0, w*o * sizeof(int)); + for (i = 0; i < state->n; i++) { + if (state->flags[i] & F_BLACK) continue; + j = state->nums[i]; + x = i%w; y = i/w; + rownums[y * o + j-1] += 1; + colnums[x * o + j-1] += 1; + } + + ntries = 0; +randomise: + for (i = 0; i < state->n; i++) { + if (!(state->flags[i] & F_BLACK)) continue; + state->nums[i] = best_black_col(state, rs, scratch, i, rownums, colnums); + } + debug_state("State after adding numbers", state); + + /* DIFF_ANY just returns whatever we first generated, for testing purposes. */ + if (params->diff != DIFF_ANY && + !new_game_is_good(params, state, tosolve)) { + ntries++; + if (ntries > MAXTRIES) { + debug(("Ran out of randomisation attempts, re-generating.\n")); + goto generate; + } + debug(("Re-randomising numbers under black squares.\n")); + goto randomise; + } + + ret = generate_desc(state, 0); + + free_game(tosolve); + free_game(state); + solver_state_free(ss); + sfree(scratch); + sfree(rownums); + sfree(colnums); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + char *ret = NULL; + + unpick_desc(params, desc, NULL, &ret); + return ret; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = NULL; + + unpick_desc(params, desc, &state, NULL); + if (!state) assert(!"new_game failed to unpick"); + return state; +} + +/* --- Game UI and move routines --- */ + +struct game_ui { + int cx, cy, cshow; + int show_black_nums; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->cx = ui->cy = ui->cshow = 0; + ui->show_black_nums = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + if (!oldstate->completed && newstate->completed) + ui->cshow = 0; +} + +#define DS_BLACK 0x1 +#define DS_CIRCLE 0x2 +#define DS_CURSOR 0x4 +#define DS_BLACK_NUM 0x8 +#define DS_ERROR 0x10 +#define DS_FLASH 0x20 +#define DS_IMPOSSIBLE 0x40 + +struct game_drawstate { + int tilesize, started, solved; + int w, h, n; + + unsigned int *flags; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int mx, int my, int button) +{ + char buf[80], c; + int i, x = FROMCOORD(mx), y = FROMCOORD(my); + enum { NONE, TOGGLE_BLACK, TOGGLE_CIRCLE, UI } action = NONE; + + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cx, &ui->cy, state->w, state->h, 1); + ui->cshow = 1; + action = UI; + } else if (IS_CURSOR_SELECT(button)) { + x = ui->cx; y = ui->cy; + if (!ui->cshow) { + action = UI; + ui->cshow = 1; + } + if (button == CURSOR_SELECT) { + action = TOGGLE_BLACK; + } else if (button == CURSOR_SELECT2) { + action = TOGGLE_CIRCLE; + } + } else if (IS_MOUSE_DOWN(button)) { + if (ui->cshow) { + ui->cshow = 0; + action = UI; + } + if (!INGRID(state, x, y)) { + ui->show_black_nums = 1 - ui->show_black_nums; + action = UI; /* this wants to be a per-game option. */ + } else if (button == LEFT_BUTTON) { + action = TOGGLE_BLACK; + } else if (button == RIGHT_BUTTON) { + action = TOGGLE_CIRCLE; + } + } + if (action == UI) return ""; + + if (action == TOGGLE_BLACK || action == TOGGLE_CIRCLE) { + i = y * state->w + x; + if (state->flags[i] & (F_BLACK | F_CIRCLE)) + c = 'E'; + else + c = (action == TOGGLE_BLACK) ? 'B' : 'C'; + sprintf(buf, "%c%d,%d", (int)c, x, y); + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *ret = dup_game(state); + int x, y, i, n; + + debug(("move: %s\n", move)); + + while (*move) { + char c = *move; + if (c == 'B' || c == 'C' || c == 'E') { + move++; + if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || + !INGRID(state, x, y)) + goto badmove; + + i = y*ret->w + x; + ret->flags[i] &= ~(F_CIRCLE | F_BLACK); /* empty first, always. */ + if (c == 'B') + ret->flags[i] |= F_BLACK; + else if (c == 'C') + ret->flags[i] |= F_CIRCLE; + move += n; + } else if (c == 'S') { + move++; + ret->used_solve = 1; + } else + goto badmove; + + if (*move == ';') + move++; + else if (*move) + goto badmove; + } + if (check_complete(ret, CC_MARK_ERRORS)) ret->completed = 1; + return ret; + +badmove: + free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * params->w + 2 * BORDER; + *y = TILE_SIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + for (i = 0; i < 3; i++) { + ret[COL_BLACK * 3 + i] = 0.0F; + ret[COL_BLACKNUM * 3 + i] = 0.4F; + ret[COL_WHITE * 3 + i] = 1.0F; + ret[COL_GRID * 3 + i] = ret[COL_LOWLIGHT * 3 + i]; + } + ret[COL_CURSOR * 3 + 0] = 0.2F; + ret[COL_CURSOR * 3 + 1] = 0.8F; + ret[COL_CURSOR * 3 + 2] = 0.0F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = ds->started = ds->solved = 0; + ds->w = state->w; + ds->h = state->h; + ds->n = state->n; + + ds->flags = snewn(state->n, unsigned int); + + memset(ds->flags, 0, state->n*sizeof(unsigned int)); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->flags); + sfree(ds); +} + +static void tile_redraw(drawing *dr, game_drawstate *ds, int x, int y, + int num, unsigned int f) +{ + int tcol, bg, dnum, cx, cy, tsz; + char buf[32]; + + if (f & DS_BLACK) { + bg = (f & DS_ERROR) ? COL_ERROR : COL_BLACK; + tcol = COL_BLACKNUM; + dnum = (f & DS_BLACK_NUM) ? 1 : 0; + } else { + bg = (f & DS_FLASH) ? COL_LOWLIGHT : COL_BACKGROUND; + tcol = (f & DS_ERROR) ? COL_ERROR : COL_BLACK; + dnum = 1; + } + + cx = x + TILE_SIZE/2; cy = y + TILE_SIZE/2; + + draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, bg); + draw_rect_outline(dr, x, y, TILE_SIZE, TILE_SIZE, + (f & DS_IMPOSSIBLE) ? COL_ERROR : COL_GRID); + + if (f & DS_CIRCLE) { + draw_circle(dr, cx, cy, CRAD, tcol, tcol); + draw_circle(dr, cx, cy, CRAD-1, bg, tcol); + } + + if (dnum) { + sprintf(buf, "%d", num); + if (strlen(buf) == 1) + tsz = TEXTSZ; + else + tsz = (CRAD*2 - 1) / strlen(buf); + draw_text(dr, cx, cy, FONT_VARIABLE, tsz, + ALIGN_VCENTRE | ALIGN_HCENTRE, tcol, buf); + } + + if (f & DS_CURSOR) + draw_rect_corners(dr, cx, cy, TEXTSZ/2, COL_CURSOR); + + draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y, i, flash; + unsigned int f; + + flash = (int)(flashtime * 5 / FLASH_TIME) % 2; + + if (!ds->started) { + int wsz = TILE_SIZE * state->w + 2 * BORDER; + int hsz = TILE_SIZE * state->h + 2 * BORDER; + draw_rect(dr, 0, 0, wsz, hsz, COL_BACKGROUND); + draw_rect_outline(dr, COORD(0)-1, COORD(0)-1, + TILE_SIZE * state->w + 2, TILE_SIZE * state->h + 2, + COL_GRID); + draw_update(dr, 0, 0, wsz, hsz); + } + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + i = y*state->w + x; + f = 0; + + if (flash) f |= DS_FLASH; + if (state->impossible) f |= DS_IMPOSSIBLE; + + if (ui->cshow && x == ui->cx && y == ui->cy) + f |= DS_CURSOR; + if (state->flags[i] & F_BLACK) { + f |= DS_BLACK; + if (ui->show_black_nums) f |= DS_BLACK_NUM; + } + if (state->flags[i] & F_CIRCLE) + f |= DS_CIRCLE; + if (state->flags[i] & F_ERROR) + f |= DS_ERROR; + + if (!ds->started || ds->flags[i] != f) { + tile_redraw(dr, ds, COORD(x), COORD(y), + state->nums[i], f); + ds->flags[i] = f; + } + } + } + ds->started = 1; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && + newstate->completed && !newstate->used_solve) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* 8mm squares by default. */ + game_compute_size(params, 800, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int ink = print_mono_colour(dr, 0); + int paper = print_mono_colour(dr, 1); + int x, y, ox, oy, i; + char buf[32]; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + print_line_width(dr, 2 * TILE_SIZE / 40); + + for (x = 0; x < state->w; x++) { + for (y = 0; y < state->h; y++) { + ox = COORD(x); oy = COORD(y); + i = y*state->w+x; + + if (state->flags[i] & F_BLACK) { + draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink); + } else { + draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink); + + if (state->flags[i] & DS_CIRCLE) + draw_circle(dr, ox+TILE_SIZE/2, oy+TILE_SIZE/2, CRAD, + paper, ink); + + sprintf(buf, "%d", state->nums[i]); + draw_text(dr, ox+TILE_SIZE/2, oy+TILE_SIZE/2, FONT_VARIABLE, + TEXTSZ/strlen(buf), ALIGN_VCENTRE | ALIGN_HCENTRE, + ink, buf); + } + } + } +} + +#ifdef COMBINED +#define thegame singles +#endif + +const struct game thegame = { + "Singles", "games.singles", "singles", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include +#include + +static void start_soak(game_params *p, random_state *rs) +{ + time_t tt_start, tt_now, tt_last; + char *desc, *aux; + game_state *s; + int i, n = 0, ndiff[DIFF_MAX], diff, sret, nblack = 0, nsneaky = 0; + + tt_start = tt_now = time(NULL); + + printf("Soak-testing a %dx%d grid.\n", p->w, p->h); + p->diff = DIFF_ANY; + + memset(ndiff, 0, DIFF_MAX * sizeof(int)); + + while (1) { + n++; + desc = new_game_desc(p, rs, &aux, 0); + s = new_game(NULL, p, desc); + nsneaky += solve_sneaky(s, NULL); + + for (diff = 0; diff < DIFF_MAX; diff++) { + memset(s->flags, 0, s->n * sizeof(unsigned int)); + s->completed = s->impossible = 0; + sret = solve_specific(s, diff, 0); + if (sret > 0) { + ndiff[diff]++; + break; + } else if (sret < 0) + fprintf(stderr, "Impossible! %s\n", desc); + } + for (i = 0; i < s->n; i++) { + if (s->flags[i] & F_BLACK) nblack++; + } + free_game(s); + sfree(desc); + + tt_last = time(NULL); + if (tt_last > tt_now) { + tt_now = tt_last; + printf("%d total, %3.1f/s, bl/sn %3.1f%%/%3.1f%%: ", + n, (double)n / ((double)tt_now - tt_start), + ((double)nblack * 100.0) / (double)(n * p->w * p->h), + ((double)nsneaky * 100.0) / (double)(n * p->w * p->h)); + for (diff = 0; diff < DIFF_MAX; diff++) { + if (diff > 0) printf(", "); + printf("%d (%3.1f%%) %s", + ndiff[diff], (double)ndiff[diff] * 100.0 / (double)n, + singles_diffnames[diff]); + } + printf("\n"); + } + } +} + +int main(int argc, char **argv) +{ + char *id = NULL, *desc, *desc_gen = NULL, *tgame, *err, *aux; + game_state *s = NULL; + game_params *p = NULL; + int soln, soak = 0, ret = 1; + time_t seed = time(NULL); + random_state *rs = NULL; + + setvbuf(stdout, NULL, _IONBF, 0); + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + verbose = 1; + } else if (!strcmp(p, "--soak")) { + soak = 1; + } else if (!strcmp(p, "--seed")) { + if (argc == 0) { + fprintf(stderr, "%s: --seed needs an argument", argv[0]); + goto done; + } + seed = (time_t)atoi(*++argv); + argc--; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + rs = random_new((void*)&seed, sizeof(time_t)); + + if (!id) { + fprintf(stderr, "usage: %s [-v] [--soak] | \n", argv[0]); + goto done; + } + desc = strchr(id, ':'); + if (desc) *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_params(p, 1); + if (err) { + fprintf(stderr, "%s: %s", argv[0], err); + goto done; + } + + if (soak) { + if (desc) { + fprintf(stderr, "%s: --soak only needs params, not game desc.\n", argv[0]); + goto done; + } + start_soak(p, rs); + } else { + if (!desc) desc = desc_gen = new_game_desc(p, rs, &aux, 0); + + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + free_params(p); + goto done; + } + s = new_game(NULL, p, desc); + + if (verbose) { + tgame = game_text_format(s); + fputs(tgame, stdout); + sfree(tgame); + } + + soln = solve_specific(s, DIFF_ANY, 0); + tgame = game_text_format(s); + fputs(tgame, stdout); + sfree(tgame); + printf("Game was %s.\n\n", + soln < 0 ? "impossible" : soln > 0 ? "solved" : "not solved"); + } + ret = 0; + +done: + if (desc_gen) sfree(desc_gen); + if (p) free_params(p); + if (s) free_game(s); + if (rs) random_free(rs); + + return ret; +} + +#endif + + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/sixteen.R b/apps/plugins/puzzles/sixteen.R new file mode 100644 index 0000000000..c63a27cef8 --- /dev/null +++ b/apps/plugins/puzzles/sixteen.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +sixteen : [X] GTK COMMON sixteen sixteen-icon|no-icon + +sixteen : [G] WINDOWS COMMON sixteen sixteen.res|noicon.res + +ALL += sixteen[COMBINED] + +!begin am gtk +GAMES += sixteen +!end + +!begin >list.c + A(sixteen) \ +!end + +!begin >gamedesc.txt +sixteen:sixteen.exe:Sixteen:Toroidal sliding block puzzle:Slide a row at a time to arrange the tiles into order. +!end diff --git a/apps/plugins/puzzles/sixteen.c b/apps/plugins/puzzles/sixteen.c new file mode 100644 index 0000000000..1dd1d6b017 --- /dev/null +++ b/apps/plugins/puzzles/sixteen.c @@ -0,0 +1,1214 @@ +/* + * sixteen.c: `16-puzzle', a sliding-tiles jigsaw which differs + * from the 15-puzzle in that you toroidally rotate a row or column + * at a time. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) +#define BORDER TILE_SIZE +#define HIGHLIGHT_WIDTH (TILE_SIZE / 20) +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + 2*TILE_SIZE) / TILE_SIZE - 2 ) + +#define ANIM_TIME 0.13F +#define FLASH_FRAME 0.13F + +#define X(state, i) ( (i) % (state)->w ) +#define Y(state, i) ( (i) / (state)->w ) +#define C(state, x, y) ( (y) * (state)->w + (x) ) + +#define TILE_CURSOR(i, state, x, y) ((i) == C((state), (x), (y)) && \ + 0 <= (x) && (x) < (state)->w && \ + 0 <= (y) && (y) < (state)->h) +enum { + COL_BACKGROUND, + COL_TEXT, + COL_HIGHLIGHT, + COL_LOWLIGHT, + NCOLOURS +}; + +struct game_params { + int w, h; + int movetarget; +}; + +struct game_state { + int w, h, n; + int *tiles; + int completed; + int used_solve; /* used to suppress completion flash */ + int movecount, movetarget; + int last_movement_sense; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 4; + ret->movetarget = 0; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + int w, h; + char buf[80]; + + switch (i) { + case 0: w = 3, h = 3; break; + case 1: w = 4, h = 3; break; + case 2: w = 4, h = 4; break; + case 3: w = 5, h = 4; break; + case 4: w = 5, h = 5; break; + default: return FALSE; + } + + sprintf(buf, "%dx%d", w, h); + *name = dupstr(buf); + *params = ret = snew(game_params); + ret->w = w; + ret->h = h; + ret->movetarget = 0; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + ret->movetarget = 0; + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) + string++; + } + if (*string == 'm') { + string++; + ret->movetarget = atoi(string); + while (*string && isdigit((unsigned char)*string)) + string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + /* Shuffle limit is part of the limited parameters, because we have to + * supply the target move count. */ + if (params->movetarget) + sprintf(data + strlen(data), "m%d", params->movetarget); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Number of shuffling moves"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->movetarget); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->movetarget = atoi(cfg[2].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 2 || params->h < 2) + return "Width and height must both be at least two"; + + return NULL; +} + +static int perm_parity(int *perm, int n) +{ + int i, j, ret; + + ret = 0; + + for (i = 0; i < n-1; i++) + for (j = i+1; j < n; j++) + if (perm[i] > perm[j]) + ret = !ret; + + return ret; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int stop, n, i, x; + int x1, x2, p1, p2; + int *tiles, *used; + char *ret; + int retlen; + + n = params->w * params->h; + + tiles = snewn(n, int); + + if (params->movetarget) { + int prevoffset = -1; + int max = (params->w > params->h ? params->w : params->h); + int *prevmoves = snewn(max, int); + + /* + * Shuffle the old-fashioned way, by making a series of + * single moves on the grid. + */ + + for (i = 0; i < n; i++) + tiles[i] = i; + + for (i = 0; i < params->movetarget; i++) { + int start, offset, len, direction, index; + int j, tmp; + + /* + * Choose a move to make. We can choose from any row + * or any column. + */ + while (1) { + j = random_upto(rs, params->w + params->h); + + if (j < params->w) { + /* Column. */ + index = j; + start = j; + offset = params->w; + len = params->h; + } else { + /* Row. */ + index = j - params->w; + start = index * params->w; + offset = 1; + len = params->w; + } + + direction = -1 + 2 * random_upto(rs, 2); + + /* + * To at least _try_ to avoid boring cases, check + * that this move doesn't directly undo a previous + * one, or repeat it so many times as to turn it + * into fewer moves in the opposite direction. (For + * example, in a row of length 4, we're allowed to + * move it the same way twice, but not three + * times.) + * + * We track this for each individual row/column, + * and clear all the counters as soon as a + * perpendicular move is made. This isn't perfect + * (it _can't_ guaranteeably be perfect - there + * will always come a move count beyond which a + * shorter solution will be possible than the one + * which constructed the position) but it should + * sort out all the obvious cases. + */ + if (offset == prevoffset) { + tmp = prevmoves[index] + direction; + if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index])) + continue; + } + + /* If we didn't `continue', we've found an OK move to make. */ + if (offset != prevoffset) { + int i; + for (i = 0; i < max; i++) + prevmoves[i] = 0; + prevoffset = offset; + } + prevmoves[index] += direction; + break; + } + + /* + * Make the move. + */ + if (direction < 0) { + start += (len-1) * offset; + offset = -offset; + } + tmp = tiles[start]; + for (j = 0; j+1 < len; j++) + tiles[start + j*offset] = tiles[start + (j+1)*offset]; + tiles[start + (len-1) * offset] = tmp; + } + + sfree(prevmoves); + + } else { + + used = snewn(n, int); + + for (i = 0; i < n; i++) { + tiles[i] = -1; + used[i] = FALSE; + } + + /* + * If both dimensions are odd, there is a parity + * constraint. + */ + if (params->w & params->h & 1) + stop = 2; + else + stop = 0; + + /* + * Place everything except (possibly) the last two tiles. + */ + for (x = 0, i = n; i > stop; i--) { + int k = i > 1 ? random_upto(rs, i) : 0; + int j; + + for (j = 0; j < n; j++) + if (!used[j] && (k-- == 0)) + break; + + assert(j < n && !used[j]); + used[j] = TRUE; + + while (tiles[x] >= 0) + x++; + assert(x < n); + tiles[x] = j; + } + + if (stop) { + /* + * Find the last two locations, and the last two + * pieces. + */ + while (tiles[x] >= 0) + x++; + assert(x < n); + x1 = x; + x++; + while (tiles[x] >= 0) + x++; + assert(x < n); + x2 = x; + + for (i = 0; i < n; i++) + if (!used[i]) + break; + p1 = i; + for (i = p1+1; i < n; i++) + if (!used[i]) + break; + p2 = i; + + /* + * Try the last two tiles one way round. If that fails, + * swap them. + */ + tiles[x1] = p1; + tiles[x2] = p2; + if (perm_parity(tiles, n) != 0) { + tiles[x1] = p2; + tiles[x2] = p1; + assert(perm_parity(tiles, n) == 0); + } + } + + sfree(used); + } + + /* + * Now construct the game description, by describing the tile + * array as a simple sequence of comma-separated integers. + */ + ret = NULL; + retlen = 0; + for (i = 0; i < n; i++) { + char buf[80]; + int k; + + k = sprintf(buf, "%d,", tiles[i]+1); + + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + ret[retlen-1] = '\0'; /* delete last comma */ + + sfree(tiles); + + return ret; +} + + +static char *validate_desc(const game_params *params, const char *desc) +{ + const char *p; + char *err; + int i, area; + int *used; + + area = params->w * params->h; + p = desc; + err = NULL; + + used = snewn(area, int); + for (i = 0; i < area; i++) + used[i] = FALSE; + + for (i = 0; i < area; i++) { + const char *q = p; + int n; + + if (*p < '0' || *p > '9') { + err = "Not enough numbers in string"; + goto leave; + } + while (*p >= '0' && *p <= '9') + p++; + if (i < area-1 && *p != ',') { + err = "Expected comma after number"; + goto leave; + } + else if (i == area-1 && *p) { + err = "Excess junk at end of string"; + goto leave; + } + n = atoi(q); + if (n < 1 || n > area) { + err = "Number out of range"; + goto leave; + } + if (used[n-1]) { + err = "Number used twice"; + goto leave; + } + used[n-1] = TRUE; + + if (*p) p++; /* eat comma */ + } + + leave: + sfree(used); + return err; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int i; + const char *p; + + state->w = params->w; + state->h = params->h; + state->n = params->w * params->h; + state->tiles = snewn(state->n, int); + + p = desc; + i = 0; + for (i = 0; i < state->n; i++) { + assert(*p); + state->tiles[i] = atoi(p); + while (*p && *p != ',') + p++; + if (*p) p++; /* eat comma */ + } + assert(!*p); + + state->completed = state->movecount = 0; + state->movetarget = params->movetarget; + state->used_solve = FALSE; + state->last_movement_sense = 0; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + ret->n = state->n; + ret->tiles = snewn(state->w * state->h, int); + memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int)); + ret->completed = state->completed; + ret->movecount = state->movecount; + ret->movetarget = state->movetarget; + ret->used_solve = state->used_solve; + ret->last_movement_sense = state->last_movement_sense; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->tiles); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return dupstr("S"); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret, *p, buf[80]; + int x, y, col, maxlen; + + /* + * First work out how many characters we need to display each + * number. + */ + col = sprintf(buf, "%d", state->n); + + /* + * Now we know the exact total size of the grid we're going to + * produce: it's got h rows, each containing w lots of col, w-1 + * spaces and a trailing newline. + */ + maxlen = state->h * state->w * (col+1); + + ret = snewn(maxlen+1, char); + p = ret; + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + int v = state->tiles[state->w*y+x]; + sprintf(buf, "%*d", col, v); + memcpy(p, buf, col); + p += col; + if (x+1 == state->w) + *p++ = '\n'; + else + *p++ = ' '; + } + } + + assert(p - ret == maxlen); + *p = '\0'; + return ret; +} + +enum cursor_mode { unlocked, lock_tile, lock_position }; + +struct game_ui { + int cur_x, cur_y; + int cur_visible; + enum cursor_mode cur_mode; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = 0; + ui->cur_y = 0; + ui->cur_visible = FALSE; + ui->cur_mode = unlocked; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int w, h, bgcolour; + int *tiles; + int tilesize; + int cur_x, cur_y; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int cx = -1, cy = -1, dx, dy; + char buf[80]; + int shift = button & MOD_SHFT, control = button & MOD_CTRL, + pad = button & MOD_NUM_KEYPAD; + + button &= ~MOD_MASK; + + if (IS_CURSOR_MOVE(button) || pad) { + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + + if (control || shift || ui->cur_mode) { + int x = ui->cur_x, y = ui->cur_y, xwrap = x, ywrap = y; + if (x < 0 || x >= state->w || y < 0 || y >= state->h) + return NULL; + move_cursor(button | pad, &x, &y, + state->w, state->h, FALSE); + move_cursor(button | pad, &xwrap, &ywrap, + state->w, state->h, TRUE); + + if (x != xwrap) { + sprintf(buf, "R%d,%c1", y, x ? '+' : '-'); + } else if (y != ywrap) { + sprintf(buf, "C%d,%c1", x, y ? '+' : '-'); + } else if (x == ui->cur_x) + sprintf(buf, "C%d,%d", x, y - ui->cur_y); + else + sprintf(buf, "R%d,%d", y, x - ui->cur_x); + + if (control || (!shift && ui->cur_mode == lock_tile)) { + ui->cur_x = xwrap; + ui->cur_y = ywrap; + } + + return dupstr(buf); + } else { + int x = ui->cur_x + 1, y = ui->cur_y + 1; + + move_cursor(button | pad, &x, &y, + state->w + 2, state->h + 2, FALSE); + + if (x == 0 && y == 0) { + int t = ui->cur_x; + ui->cur_x = ui->cur_y; + ui->cur_y = t; + } else if (x == 0 && y == state->h + 1) { + int t = ui->cur_x; + ui->cur_x = (state->h - 1) - ui->cur_y; + ui->cur_y = (state->h - 1) - t; + } else if (x == state->w + 1 && y == 0) { + int t = ui->cur_x; + ui->cur_x = (state->w - 1) - ui->cur_y; + ui->cur_y = (state->w - 1) - t; + } else if (x == state->w + 1 && y == state->h + 1) { + int t = ui->cur_x; + ui->cur_x = state->w - state->h + ui->cur_y; + ui->cur_y = state->h - state->w + t; + } else { + ui->cur_x = x - 1; + ui->cur_y = y - 1; + } + + ui->cur_visible = 1; + return ""; + } + } + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + cx = FROMCOORD(x); + cy = FROMCOORD(y); + ui->cur_visible = 0; + } else if (IS_CURSOR_SELECT(button)) { + if (ui->cur_visible) { + if (ui->cur_x == -1 || ui->cur_x == state->w || + ui->cur_y == -1 || ui->cur_y == state->h) { + cx = ui->cur_x; + cy = ui->cur_y; + } else { + const enum cursor_mode m = (button == CURSOR_SELECT2 ? + lock_position : lock_tile); + ui->cur_mode = (ui->cur_mode == m ? unlocked : m); + return ""; + } + } else { + ui->cur_visible = 1; + return ""; + } + } else { + return NULL; + } + + if (cx == -1 && cy >= 0 && cy < state->h) + dx = -1, dy = 0; + else if (cx == state->w && cy >= 0 && cy < state->h) + dx = +1, dy = 0; + else if (cy == -1 && cx >= 0 && cx < state->w) + dy = -1, dx = 0; + else if (cy == state->h && cx >= 0 && cx < state->w) + dy = +1, dx = 0; + else + return ""; /* invalid click location */ + + /* reverse direction if right hand button is pressed */ + if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) { + dx = -dx; + dy = -dy; + } + + if (dx) + sprintf(buf, "R%d,%d", cy, dx); + else + sprintf(buf, "C%d,%d", cx, dy); + return dupstr(buf); +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int cx, cy, dx, dy; + int tx, ty, n; + game_state *ret; + + if (!strcmp(move, "S")) { + int i; + + ret = dup_game(from); + + /* + * Simply replace the grid with a solved one. For this game, + * this isn't a useful operation for actually telling the user + * what they should have done, but it is useful for + * conveniently being able to get hold of a clean state from + * which to practise manoeuvres. + */ + for (i = 0; i < ret->n; i++) + ret->tiles[i] = i+1; + ret->used_solve = TRUE; + ret->completed = ret->movecount = 1; + + return ret; + } + + if (move[0] == 'R' && sscanf(move+1, "%d,%d", &cy, &dx) == 2 && + cy >= 0 && cy < from->h) { + cx = dy = 0; + n = from->w; + } else if (move[0] == 'C' && sscanf(move+1, "%d,%d", &cx, &dy) == 2 && + cx >= 0 && cx < from->w) { + cy = dx = 0; + n = from->h; + } else + return NULL; + + ret = dup_game(from); + + do { + tx = (cx - dx + from->w) % from->w; + ty = (cy - dy + from->h) % from->h; + ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)]; + cx = tx; + cy = ty; + } while (--n > 0); + + ret->movecount++; + + ret->last_movement_sense = dx+dy; + + /* + * See if the game has been completed. + */ + if (!ret->completed) { + ret->completed = ret->movecount; + for (n = 0; n < ret->n; n++) + if (ret->tiles[n] != n+1) + ret->completed = FALSE; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * params->w + 2 * BORDER; + *y = TILE_SIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) + ret[COL_TEXT * 3 + i] = 0.0; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->started = FALSE; + ds->w = state->w; + ds->h = state->h; + ds->bgcolour = COL_BACKGROUND; + ds->tiles = snewn(ds->w*ds->h, int); + ds->tilesize = 0; /* haven't decided yet */ + for (i = 0; i < ds->w*ds->h; i++) + ds->tiles[i] = -1; + ds->cur_x = ds->cur_y = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->tiles); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y, + int tile, int flash_colour) +{ + if (tile == 0) { + draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE, + flash_colour); + } else { + int coords[6]; + char str[40]; + + coords[0] = x + TILE_SIZE - 1; + coords[1] = y + TILE_SIZE - 1; + coords[2] = x + TILE_SIZE - 1; + coords[3] = y; + coords[4] = x; + coords[5] = y + TILE_SIZE - 1; + draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); + + coords[0] = x; + coords[1] = y; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + + draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, + TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, + flash_colour); + + sprintf(str, "%d", tile); + draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE, + COL_TEXT, str); + } + draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); +} + +static void draw_arrow(drawing *dr, game_drawstate *ds, + int x, int y, int xdx, int xdy, int cur) +{ + int coords[14]; + int ydy = -xdx, ydx = xdy; + +#define POINT(n, xx, yy) ( \ + coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \ + coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy) + + POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4); /* top of arrow */ + POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2); /* right corner */ + POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2); /* right concave */ + POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom right */ + POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4); /* bottom left */ + POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2); /* left concave */ + POINT(6, TILE_SIZE / 4, TILE_SIZE / 2); /* left corner */ + + draw_polygon(dr, coords, 7, cur ? COL_HIGHLIGHT : COL_LOWLIGHT, COL_TEXT); +} + +static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds, + int cur_x, int cur_y, int cur) +{ + if (cur_x == -1 && cur_y == -1) + return; /* 'no cursur here */ + else if (cur_x == -1) /* LH column. */ + draw_arrow(dr, ds, COORD(0), COORD(cur_y+1), 0, -1, cur); + else if (cur_x == ds->w) /* RH column */ + draw_arrow(dr, ds, COORD(ds->w), COORD(cur_y), 0, +1, cur); + else if (cur_y == -1) /* Top row */ + draw_arrow(dr, ds, COORD(cur_x), COORD(0), +1, 0, cur); + else if (cur_y == ds->h) /* Bottom row */ + draw_arrow(dr, ds, COORD(cur_x+1), COORD(ds->h), -1, 0, cur); + else + return; + + draw_update(dr, COORD(cur_x), COORD(cur_y), + TILE_SIZE, TILE_SIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int i, bgcolour; + int cur_x = -1, cur_y = -1; + + if (flashtime > 0) { + int frame = (int)(flashtime / FLASH_FRAME); + bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); + } else + bgcolour = COL_BACKGROUND; + + if (!ds->started) { + int coords[10]; + + draw_rect(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); + draw_update(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER); + + /* + * Recessed area containing the whole puzzle. + */ + coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; + coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; + coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; + coords[3] = COORD(0) - HIGHLIGHT_WIDTH; + coords[4] = coords[2] - TILE_SIZE; + coords[5] = coords[3] + TILE_SIZE; + coords[8] = COORD(0) - HIGHLIGHT_WIDTH; + coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; + coords[6] = coords[8] + TILE_SIZE; + coords[7] = coords[9] - TILE_SIZE; + draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); + + coords[1] = COORD(0) - HIGHLIGHT_WIDTH; + coords[0] = COORD(0) - HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); + + /* + * Arrows for making moves. + */ + for (i = 0; i < state->w; i++) { + draw_arrow(dr, ds, COORD(i), COORD(0), +1, 0, 0); + draw_arrow(dr, ds, COORD(i+1), COORD(state->h), -1, 0, 0); + } + for (i = 0; i < state->h; i++) { + draw_arrow(dr, ds, COORD(state->w), COORD(i), 0, +1, 0); + draw_arrow(dr, ds, COORD(0), COORD(i+1), 0, -1, 0); + } + + ds->started = TRUE; + } + /* + * Cursor (highlighted arrow around edge) + */ + if (ui->cur_visible) { + cur_x = ui->cur_x; cur_y = ui->cur_y; + } + + if (cur_x != ds->cur_x || cur_y != ds->cur_y) { + /* Cursor has changed; redraw two (prev and curr) arrows. */ + draw_arrow_for_cursor(dr, ds, cur_x, cur_y, 1); + draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, 0); + } + + /* + * Now draw each tile. + */ + + clip(dr, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h); + + for (i = 0; i < state->n; i++) { + int t, t0; + /* + * Figure out what should be displayed at this + * location. It's either a simple tile, or it's a + * transition between two tiles (in which case we say + * -1 because it must always be drawn). + */ + + if (oldstate && oldstate->tiles[i] != state->tiles[i]) + t = -1; + else + t = state->tiles[i]; + + t0 = t; + + if (ds->bgcolour != bgcolour || /* always redraw when flashing */ + ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1 || + ((ds->cur_x != cur_x || ds->cur_y != cur_y) && /* cursor moved */ + (TILE_CURSOR(i, state, ds->cur_x, ds->cur_y) || + TILE_CURSOR(i, state, cur_x, cur_y)))) { + int x, y, x2, y2; + + /* + * Figure out what to _actually_ draw, and where to + * draw it. + */ + if (t == -1) { + int x0, y0, x1, y1, dx, dy; + int j; + float c; + int sense; + + if (dir < 0) { + assert(oldstate); + sense = -oldstate->last_movement_sense; + } else { + sense = state->last_movement_sense; + } + + t = state->tiles[i]; + + /* + * FIXME: must be prepared to draw a double + * tile in some situations. + */ + + /* + * Find the coordinates of this tile in the old and + * new states. + */ + x1 = COORD(X(state, i)); + y1 = COORD(Y(state, i)); + for (j = 0; j < oldstate->n; j++) + if (oldstate->tiles[j] == state->tiles[i]) + break; + assert(j < oldstate->n); + x0 = COORD(X(state, j)); + y0 = COORD(Y(state, j)); + + dx = (x1 - x0); + if (dx != 0 && + dx != TILE_SIZE * sense) { + dx = (dx < 0 ? dx + TILE_SIZE * state->w : + dx - TILE_SIZE * state->w); + assert(abs(dx) == TILE_SIZE); + } + dy = (y1 - y0); + if (dy != 0 && + dy != TILE_SIZE * sense) { + dy = (dy < 0 ? dy + TILE_SIZE * state->h : + dy - TILE_SIZE * state->h); + assert(abs(dy) == TILE_SIZE); + } + + c = (animtime / ANIM_TIME); + if (c < 0.0F) c = 0.0F; + if (c > 1.0F) c = 1.0F; + + x = x0 + (int)(c * dx); + y = y0 + (int)(c * dy); + x2 = x1 - dx + (int)(c * dx); + y2 = y1 - dy + (int)(c * dy); + } else { + x = COORD(X(state, i)); + y = COORD(Y(state, i)); + x2 = y2 = -1; + } + + draw_tile(dr, ds, state, x, y, t, + (x2 == -1 && TILE_CURSOR(i, state, cur_x, cur_y)) ? + COL_LOWLIGHT : bgcolour); + + if (x2 != -1 || y2 != -1) + draw_tile(dr, ds, state, x2, y2, t, bgcolour); + } + ds->tiles[i] = t0; + } + + ds->cur_x = cur_x; + ds->cur_y = cur_y; + + unclip(dr); + + ds->bgcolour = bgcolour; + + /* + * Update the status bar. + */ + { + char statusbuf[256]; + + /* + * Don't show the new status until we're also showing the + * new _state_ - after the game animation is complete. + */ + if (oldstate) + state = oldstate; + + if (state->used_solve) + sprintf(statusbuf, "Moves since auto-solve: %d", + state->movecount - state->completed); + else { + sprintf(statusbuf, "%sMoves: %d", + (state->completed ? "COMPLETED! " : ""), + (state->completed ? state->completed : state->movecount)); + if (state->movetarget) + sprintf(statusbuf+strlen(statusbuf), " (target %d)", + state->movetarget); + } + + status_bar(dr, statusbuf); + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return ANIM_TIME; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return 2 * FLASH_FRAME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame sixteen +#endif + +const struct game thegame = { + "Sixteen", "games.sixteen", "sixteen", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/slant.R b/apps/plugins/puzzles/slant.R new file mode 100644 index 0000000000..ff0d21f1eb --- /dev/null +++ b/apps/plugins/puzzles/slant.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +SLANT_EXTRA = dsf findloop + +slant : [X] GTK COMMON slant SLANT_EXTRA slant-icon|no-icon + +slant : [G] WINDOWS COMMON slant SLANT_EXTRA slant.res|noicon.res + +slantsolver : [U] slant[STANDALONE_SOLVER] SLANT_EXTRA STANDALONE +slantsolver : [C] slant[STANDALONE_SOLVER] SLANT_EXTRA STANDALONE + +ALL += slant[COMBINED] SLANT_EXTRA + +!begin am gtk +GAMES += slant +!end + +!begin >list.c + A(slant) \ +!end + +!begin >gamedesc.txt +slant:slant.exe:Slant:Maze-drawing puzzle:Draw a maze of slanting lines that matches the clues. +!end diff --git a/apps/plugins/puzzles/slant.c b/apps/plugins/puzzles/slant.c new file mode 100644 index 0000000000..3ab4d306ef --- /dev/null +++ b/apps/plugins/puzzles/slant.c @@ -0,0 +1,2278 @@ +/* + * slant.c: Puzzle from nikoli.co.jp involving drawing a diagonal + * line through each square of a grid. + */ + +/* + * In this puzzle you have a grid of squares, each of which must + * contain a diagonal line; you also have clue numbers placed at + * _points_ of that grid, which means there's a (w+1) x (h+1) array + * of possible clue positions. + * + * I'm therefore going to adopt a rigid convention throughout this + * source file of using w and h for the dimensions of the grid of + * squares, and W and H for the dimensions of the grid of points. + * Thus, W == w+1 and H == h+1 always. + * + * Clue arrays will be W*H `signed char's, and the clue at each + * point will be a number from 0 to 4, or -1 if there's no clue. + * + * Solution arrays will be W*H `signed char's, and the number at + * each point will be +1 for a forward slash (/), -1 for a + * backslash (\), and 0 for unknown. + */ + +#include +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + COL_GRID, + COL_INK, + COL_SLANT1, + COL_SLANT2, + COL_ERROR, + COL_CURSOR, + COL_FILLEDSQUARE, + NCOLOURS +}; + +/* + * In standalone solver mode, `verbose' is a variable which can be + * set by command-line option; in debugging mode it's simply always + * true. + */ +#if defined STANDALONE_SOLVER +#define SOLVER_DIAGNOSTICS +int verbose = FALSE; +#elif defined SOLVER_DIAGNOSTICS +#define verbose TRUE +#endif + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(HARD,Hard,h) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const slant_diffnames[] = { DIFFLIST(TITLE) }; +static char const slant_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +struct game_params { + int w, h, diff; +}; + +typedef struct game_clues { + int w, h; + signed char *clues; + int *tmpdsf; + int refcount; +} game_clues; + +#define ERR_VERTEX 1 +#define ERR_SQUARE 2 + +struct game_state { + struct game_params p; + game_clues *clues; + signed char *soln; + unsigned char *errors; + int completed; + int used_solve; /* used to suppress completion flash */ +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 8; + ret->diff = DIFF_EASY; + + return ret; +} + +static const struct game_params slant_presets[] = { + {5, 5, DIFF_EASY}, + {5, 5, DIFF_HARD}, + {8, 8, DIFF_EASY}, + {8, 8, DIFF_HARD}, + {12, 10, DIFF_EASY}, + {12, 10, DIFF_HARD}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(slant_presets)) + return FALSE; + + ret = snew(game_params); + *ret = slant_presets[i]; + + sprintf(str, "%dx%d %s", ret->w, ret->h, slant_diffnames[ret->diff]); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == slant_diffchars[i]) + ret->diff = i; + if (*string) string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + if (full) + sprintf(data + strlen(data), "d%c", slant_diffchars[params->diff]); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + /* + * (At least at the time of writing this comment) The grid + * generator is actually capable of handling even zero grid + * dimensions without crashing. Puzzles with a zero-area grid + * are a bit boring, though, because they're already solved :-) + * And puzzles with a dimension of 1 can't be made Hard, which + * means the simplest thing is to forbid them altogether. + */ + + if (params->w < 2 || params->h < 2) + return "Width and height must both be at least two"; + + return NULL; +} + +/* + * Scratch space for solver. + */ +struct solver_scratch { + /* + * Disjoint set forest which tracks the connected sets of + * points. + */ + int *connected; + + /* + * Counts the number of possible exits from each connected set + * of points. (That is, the number of possible _simultaneous_ + * exits: an unconnected point labelled 2 has an exit count of + * 2 even if all four possible edges are still under + * consideration.) + */ + int *exits; + + /* + * Tracks whether each connected set of points includes a + * border point. + */ + unsigned char *border; + + /* + * Another disjoint set forest. This one tracks _squares_ which + * are known to slant in the same direction. + */ + int *equiv; + + /* + * Stores slash values which we know for an equivalence class. + * When we fill in a square, we set slashval[canonify(x)] to + * the same value as soln[x], so that we can then spot other + * squares equivalent to it and fill them in immediately via + * their known equivalence. + */ + signed char *slashval; + + /* + * Stores possible v-shapes. This array is w by h in size, but + * not every bit of every entry is meaningful. The bits mean: + * + * - bit 0 for a square means that that square and the one to + * its right might form a v-shape between them + * - bit 1 for a square means that that square and the one to + * its right might form a ^-shape between them + * - bit 2 for a square means that that square and the one + * below it might form a >-shape between them + * - bit 3 for a square means that that square and the one + * below it might form a <-shape between them + * + * Any starting 1 or 3 clue rules out four bits in this array + * immediately; a 2 clue propagates any ruled-out bit past it + * (if the two squares on one side of a 2 cannot be a v-shape, + * then neither can the two on the other side be the same + * v-shape); we can rule out further bits during play using + * partially filled 2 clues; whenever a pair of squares is + * known not to be _either_ kind of v-shape, we can mark them + * as equivalent. + */ + unsigned char *vbitmap; + + /* + * Useful to have this information automatically passed to + * solver subroutines. (This pointer is not dynamically + * allocated by new_scratch and free_scratch.) + */ + const signed char *clues; +}; + +static struct solver_scratch *new_scratch(int w, int h) +{ + int W = w+1, H = h+1; + struct solver_scratch *ret = snew(struct solver_scratch); + ret->connected = snewn(W*H, int); + ret->exits = snewn(W*H, int); + ret->border = snewn(W*H, unsigned char); + ret->equiv = snewn(w*h, int); + ret->slashval = snewn(w*h, signed char); + ret->vbitmap = snewn(w*h, unsigned char); + return ret; +} + +static void free_scratch(struct solver_scratch *sc) +{ + sfree(sc->vbitmap); + sfree(sc->slashval); + sfree(sc->equiv); + sfree(sc->border); + sfree(sc->exits); + sfree(sc->connected); + sfree(sc); +} + +/* + * Wrapper on dsf_merge() which updates the `exits' and `border' + * arrays. + */ +static void merge_vertices(int *connected, + struct solver_scratch *sc, int i, int j) +{ + int exits = -1, border = FALSE; /* initialise to placate optimiser */ + + if (sc) { + i = dsf_canonify(connected, i); + j = dsf_canonify(connected, j); + + /* + * We have used one possible exit from each of the two + * classes. Thus, the viable exit count of the new class is + * the sum of the old exit counts minus two. + */ + exits = sc->exits[i] + sc->exits[j] - 2; + + border = sc->border[i] || sc->border[j]; + } + + dsf_merge(connected, i, j); + + if (sc) { + i = dsf_canonify(connected, i); + sc->exits[i] = exits; + sc->border[i] = border; + } +} + +/* + * Called when we have just blocked one way out of a particular + * point. If that point is a non-clue point (thus has a variable + * number of exits), we have therefore decreased its potential exit + * count, so we must decrement the exit count for the group as a + * whole. + */ +static void decr_exits(struct solver_scratch *sc, int i) +{ + if (sc->clues[i] < 0) { + i = dsf_canonify(sc->connected, i); + sc->exits[i]--; + } +} + +static void fill_square(int w, int h, int x, int y, int v, + signed char *soln, + int *connected, struct solver_scratch *sc) +{ + int W = w+1 /*, H = h+1 */; + + assert(x >= 0 && x < w && y >= 0 && y < h); + + if (soln[y*w+x] != 0) { + return; /* do nothing */ + } + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf(" placing %c in %d,%d\n", v == -1 ? '\\' : '/', x, y); +#endif + + soln[y*w+x] = v; + + if (sc) { + int c = dsf_canonify(sc->equiv, y*w+x); + sc->slashval[c] = v; + } + + if (v < 0) { + merge_vertices(connected, sc, y*W+x, (y+1)*W+(x+1)); + if (sc) { + decr_exits(sc, y*W+(x+1)); + decr_exits(sc, (y+1)*W+x); + } + } else { + merge_vertices(connected, sc, y*W+(x+1), (y+1)*W+x); + if (sc) { + decr_exits(sc, y*W+x); + decr_exits(sc, (y+1)*W+(x+1)); + } + } +} + +static int vbitmap_clear(int w, int h, struct solver_scratch *sc, + int x, int y, int vbits, char *reason, ...) +{ + int done_something = FALSE; + int vbit; + + for (vbit = 1; vbit <= 8; vbit <<= 1) + if (vbits & sc->vbitmap[y*w+x] & vbit) { + done_something = TRUE; +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + va_list ap; + + printf("ruling out %c shape at (%d,%d)-(%d,%d) (", + "!v^!>!!!<"[vbit], x, y, + x+((vbit&0x3)!=0), y+((vbit&0xC)!=0)); + + va_start(ap, reason); + vprintf(reason, ap); + va_end(ap); + + printf(")\n"); + } +#endif + sc->vbitmap[y*w+x] &= ~vbit; + } + + return done_something; +} + +/* + * Solver. Returns 0 for impossibility, 1 for success, 2 for + * ambiguity or failure to converge. + */ +static int slant_solve(int w, int h, const signed char *clues, + signed char *soln, struct solver_scratch *sc, + int difficulty) +{ + int W = w+1, H = h+1; + int x, y, i, j; + int done_something; + + /* + * Clear the output. + */ + memset(soln, 0, w*h); + + sc->clues = clues; + + /* + * Establish a disjoint set forest for tracking connectedness + * between grid points. + */ + dsf_init(sc->connected, W*H); + + /* + * Establish a disjoint set forest for tracking which squares + * are known to slant in the same direction. + */ + dsf_init(sc->equiv, w*h); + + /* + * Clear the slashval array. + */ + memset(sc->slashval, 0, w*h); + + /* + * Set up the vbitmap array. Initially all types of v are possible. + */ + memset(sc->vbitmap, 0xF, w*h); + + /* + * Initialise the `exits' and `border' arrays. These are used + * to do second-order loop avoidance: the dual of the no loops + * constraint is that every point must be somehow connected to + * the border of the grid (otherwise there would be a solid + * loop around it which prevented this). + * + * I define a `dead end' to be a connected group of points + * which contains no border point, and which can form at most + * one new connection outside itself. Then I forbid placing an + * edge so that it connects together two dead-end groups, since + * this would yield a non-border-connected isolated subgraph + * with no further scope to extend it. + */ + for (y = 0; y < H; y++) + for (x = 0; x < W; x++) { + if (y == 0 || y == H-1 || x == 0 || x == W-1) + sc->border[y*W+x] = TRUE; + else + sc->border[y*W+x] = FALSE; + + if (clues[y*W+x] < 0) + sc->exits[y*W+x] = 4; + else + sc->exits[y*W+x] = clues[y*W+x]; + } + + /* + * Repeatedly try to deduce something until we can't. + */ + do { + done_something = FALSE; + + /* + * Any clue point with the number of remaining lines equal + * to zero or to the number of remaining undecided + * neighbouring squares can be filled in completely. + */ + for (y = 0; y < H; y++) + for (x = 0; x < W; x++) { + struct { + int pos, slash; + } neighbours[4]; + int nneighbours; + int nu, nl, c, s, eq, eq2, last, meq, mj1, mj2; + + if ((c = clues[y*W+x]) < 0) + continue; + + /* + * We have a clue point. Start by listing its + * neighbouring squares, in order around the point, + * together with the type of slash that would be + * required in that square to connect to the point. + */ + nneighbours = 0; + if (x > 0 && y > 0) { + neighbours[nneighbours].pos = (y-1)*w+(x-1); + neighbours[nneighbours].slash = -1; + nneighbours++; + } + if (x > 0 && y < h) { + neighbours[nneighbours].pos = y*w+(x-1); + neighbours[nneighbours].slash = +1; + nneighbours++; + } + if (x < w && y < h) { + neighbours[nneighbours].pos = y*w+x; + neighbours[nneighbours].slash = -1; + nneighbours++; + } + if (x < w && y > 0) { + neighbours[nneighbours].pos = (y-1)*w+x; + neighbours[nneighbours].slash = +1; + nneighbours++; + } + + /* + * Count up the number of undecided neighbours, and + * also the number of lines already present. + * + * If we're not on DIFF_EASY, then in this loop we + * also track whether we've seen two adjacent empty + * squares belonging to the same equivalence class + * (meaning they have the same type of slash). If + * so, we count them jointly as one line. + */ + nu = 0; + nl = c; + last = neighbours[nneighbours-1].pos; + if (soln[last] == 0) + eq = dsf_canonify(sc->equiv, last); + else + eq = -1; + meq = mj1 = mj2 = -1; + for (i = 0; i < nneighbours; i++) { + j = neighbours[i].pos; + s = neighbours[i].slash; + if (soln[j] == 0) { + nu++; /* undecided */ + if (meq < 0 && difficulty > DIFF_EASY) { + eq2 = dsf_canonify(sc->equiv, j); + if (eq == eq2 && last != j) { + /* + * We've found an equivalent pair. + * Mark it. This also inhibits any + * further equivalence tracking + * around this square, since we can + * only handle one pair (and in + * particular we want to avoid + * being misled by two overlapping + * equivalence pairs). + */ + meq = eq; + mj1 = last; + mj2 = j; + nl--; /* count one line */ + nu -= 2; /* and lose two undecideds */ + } else + eq = eq2; + } + } else { + eq = -1; + if (soln[j] == s) + nl--; /* here's a line */ + } + last = j; + } + + /* + * Check the counts. + */ + if (nl < 0 || nl > nu) { + /* + * No consistent value for this at all! + */ +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("need %d / %d lines around clue point at %d,%d!\n", + nl, nu, x, y); +#endif + return 0; /* impossible */ + } + + if (nu > 0 && (nl == 0 || nl == nu)) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + if (meq >= 0) + printf("partially (since %d,%d == %d,%d) ", + mj1%w, mj1/w, mj2%w, mj2/w); + printf("%s around clue point at %d,%d\n", + nl ? "filling" : "emptying", x, y); + } +#endif + for (i = 0; i < nneighbours; i++) { + j = neighbours[i].pos; + s = neighbours[i].slash; + if (soln[j] == 0 && j != mj1 && j != mj2) + fill_square(w, h, j%w, j/w, (nl ? s : -s), soln, + sc->connected, sc); + } + + done_something = TRUE; + } else if (nu == 2 && nl == 1 && difficulty > DIFF_EASY) { + /* + * If we have precisely two undecided squares + * and precisely one line to place between + * them, _and_ those squares are adjacent, then + * we can mark them as equivalent to one + * another. + * + * This even applies if meq >= 0: if we have a + * 2 clue point and two of its neighbours are + * already marked equivalent, we can indeed + * mark the other two as equivalent. + * + * We don't bother with this on DIFF_EASY, + * since we wouldn't have used the results + * anyway. + */ + last = -1; + for (i = 0; i < nneighbours; i++) { + j = neighbours[i].pos; + if (soln[j] == 0 && j != mj1 && j != mj2) { + if (last < 0) + last = i; + else if (last == i-1 || (last == 0 && i == 3)) + break; /* found a pair */ + } + } + if (i < nneighbours) { + int sv1, sv2; + + assert(last >= 0); + /* + * neighbours[last] and neighbours[i] are + * the pair. Mark them equivalent. + */ +#ifdef SOLVER_DIAGNOSTICS + if (verbose) { + if (meq >= 0) + printf("since %d,%d == %d,%d, ", + mj1%w, mj1/w, mj2%w, mj2/w); + } +#endif + mj1 = neighbours[last].pos; + mj2 = neighbours[i].pos; +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("clue point at %d,%d implies %d,%d == %d," + "%d\n", x, y, mj1%w, mj1/w, mj2%w, mj2/w); +#endif + mj1 = dsf_canonify(sc->equiv, mj1); + sv1 = sc->slashval[mj1]; + mj2 = dsf_canonify(sc->equiv, mj2); + sv2 = sc->slashval[mj2]; + if (sv1 != 0 && sv2 != 0 && sv1 != sv2) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("merged two equivalence classes with" + " different slash values!\n"); +#endif + return 0; + } + sv1 = sv1 ? sv1 : sv2; + dsf_merge(sc->equiv, mj1, mj2); + mj1 = dsf_canonify(sc->equiv, mj1); + sc->slashval[mj1] = sv1; + } + } + } + + if (done_something) + continue; + + /* + * Failing that, we now apply the second condition, which + * is that no square may be filled in such a way as to form + * a loop. Also in this loop (since it's over squares + * rather than points), we check slashval to see if we've + * already filled in another square in the same equivalence + * class. + * + * The slashval check is disabled on DIFF_EASY, as is dead + * end avoidance. Only _immediate_ loop avoidance remains. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int fs, bs, v; + int c1, c2; +#ifdef SOLVER_DIAGNOSTICS + char *reason = ""; +#endif + + if (soln[y*w+x]) + continue; /* got this one already */ + + fs = FALSE; + bs = FALSE; + + if (difficulty > DIFF_EASY) + v = sc->slashval[dsf_canonify(sc->equiv, y*w+x)]; + else + v = 0; + + /* + * Try to rule out connectivity between (x,y) and + * (x+1,y+1); if successful, we will deduce that we + * must have a forward slash. + */ + c1 = dsf_canonify(sc->connected, y*W+x); + c2 = dsf_canonify(sc->connected, (y+1)*W+(x+1)); + if (c1 == c2) { + fs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "simple loop avoidance"; +#endif + } + if (difficulty > DIFF_EASY && + !sc->border[c1] && !sc->border[c2] && + sc->exits[c1] <= 1 && sc->exits[c2] <= 1) { + fs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "dead end avoidance"; +#endif + } + if (v == +1) { + fs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "equivalence to an already filled square"; +#endif + } + + /* + * Now do the same between (x+1,y) and (x,y+1), to + * see if we are required to have a backslash. + */ + c1 = dsf_canonify(sc->connected, y*W+(x+1)); + c2 = dsf_canonify(sc->connected, (y+1)*W+x); + if (c1 == c2) { + bs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "simple loop avoidance"; +#endif + } + if (difficulty > DIFF_EASY && + !sc->border[c1] && !sc->border[c2] && + sc->exits[c1] <= 1 && sc->exits[c2] <= 1) { + bs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "dead end avoidance"; +#endif + } + if (v == -1) { + bs = TRUE; +#ifdef SOLVER_DIAGNOSTICS + reason = "equivalence to an already filled square"; +#endif + } + + if (fs && bs) { + /* + * No consistent value for this at all! + */ +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%d,%d has no consistent slash!\n", x, y); +#endif + return 0; /* impossible */ + } + + if (fs) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("employing %s\n", reason); +#endif + fill_square(w, h, x, y, +1, soln, sc->connected, sc); + done_something = TRUE; + } else if (bs) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("employing %s\n", reason); +#endif + fill_square(w, h, x, y, -1, soln, sc->connected, sc); + done_something = TRUE; + } + } + + if (done_something) + continue; + + /* + * Now see what we can do with the vbitmap array. All + * vbitmap deductions are disabled at Easy level. + */ + if (difficulty <= DIFF_EASY) + continue; + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int s, c; + + /* + * Any line already placed in a square must rule + * out any type of v which contradicts it. + */ + if ((s = soln[y*w+x]) != 0) { + if (x > 0) + done_something |= + vbitmap_clear(w, h, sc, x-1, y, (s < 0 ? 0x1 : 0x2), + "contradicts known edge at (%d,%d)",x,y); + if (x+1 < w) + done_something |= + vbitmap_clear(w, h, sc, x, y, (s < 0 ? 0x2 : 0x1), + "contradicts known edge at (%d,%d)",x,y); + if (y > 0) + done_something |= + vbitmap_clear(w, h, sc, x, y-1, (s < 0 ? 0x4 : 0x8), + "contradicts known edge at (%d,%d)",x,y); + if (y+1 < h) + done_something |= + vbitmap_clear(w, h, sc, x, y, (s < 0 ? 0x8 : 0x4), + "contradicts known edge at (%d,%d)",x,y); + } + + /* + * If both types of v are ruled out for a pair of + * adjacent squares, mark them as equivalent. + */ + if (x+1 < w && !(sc->vbitmap[y*w+x] & 0x3)) { + int n1 = y*w+x, n2 = y*w+(x+1); + if (dsf_canonify(sc->equiv, n1) != + dsf_canonify(sc->equiv, n2)) { + dsf_merge(sc->equiv, n1, n2); + done_something = TRUE; +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("(%d,%d) and (%d,%d) must be equivalent" + " because both v-shapes are ruled out\n", + x, y, x+1, y); +#endif + } + } + if (y+1 < h && !(sc->vbitmap[y*w+x] & 0xC)) { + int n1 = y*w+x, n2 = (y+1)*w+x; + if (dsf_canonify(sc->equiv, n1) != + dsf_canonify(sc->equiv, n2)) { + dsf_merge(sc->equiv, n1, n2); + done_something = TRUE; +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("(%d,%d) and (%d,%d) must be equivalent" + " because both v-shapes are ruled out\n", + x, y, x, y+1); +#endif + } + } + + /* + * The remaining work in this loop only works + * around non-edge clue points. + */ + if (y == 0 || x == 0) + continue; + if ((c = clues[y*W+x]) < 0) + continue; + + /* + * x,y marks a clue point not on the grid edge. See + * if this clue point allows us to rule out any v + * shapes. + */ + + if (c == 1) { + /* + * A 1 clue can never have any v shape pointing + * at it. + */ + done_something |= + vbitmap_clear(w, h, sc, x-1, y-1, 0x5, + "points at 1 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x-1, y, 0x2, + "points at 1 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x, y-1, 0x8, + "points at 1 clue at (%d,%d)", x, y); + } else if (c == 3) { + /* + * A 3 clue can never have any v shape pointing + * away from it. + */ + done_something |= + vbitmap_clear(w, h, sc, x-1, y-1, 0xA, + "points away from 3 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x-1, y, 0x1, + "points away from 3 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x, y-1, 0x4, + "points away from 3 clue at (%d,%d)", x, y); + } else if (c == 2) { + /* + * If a 2 clue has any kind of v ruled out on + * one side of it, the same v is ruled out on + * the other side. + */ + done_something |= + vbitmap_clear(w, h, sc, x-1, y-1, + (sc->vbitmap[(y )*w+(x-1)] & 0x3) ^ 0x3, + "propagated by 2 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x-1, y-1, + (sc->vbitmap[(y-1)*w+(x )] & 0xC) ^ 0xC, + "propagated by 2 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x-1, y, + (sc->vbitmap[(y-1)*w+(x-1)] & 0x3) ^ 0x3, + "propagated by 2 clue at (%d,%d)", x, y); + done_something |= + vbitmap_clear(w, h, sc, x, y-1, + (sc->vbitmap[(y-1)*w+(x-1)] & 0xC) ^ 0xC, + "propagated by 2 clue at (%d,%d)", x, y); + } + +#undef CLEARBITS + + } + + } while (done_something); + + /* + * Solver can make no more progress. See if the grid is full. + */ + for (i = 0; i < w*h; i++) + if (!soln[i]) + return 2; /* failed to converge */ + return 1; /* success */ +} + +/* + * Filled-grid generator. + */ +static void slant_generate(int w, int h, signed char *soln, random_state *rs) +{ + int W = w+1, H = h+1; + int x, y, i; + int *connected, *indices; + + /* + * Clear the output. + */ + memset(soln, 0, w*h); + + /* + * Establish a disjoint set forest for tracking connectedness + * between grid points. + */ + connected = snew_dsf(W*H); + + /* + * Prepare a list of the squares in the grid, and fill them in + * in a random order. + */ + indices = snewn(w*h, int); + for (i = 0; i < w*h; i++) + indices[i] = i; + shuffle(indices, w*h, sizeof(*indices), rs); + + /* + * Fill in each one in turn. + */ + for (i = 0; i < w*h; i++) { + int fs, bs, v; + + y = indices[i] / w; + x = indices[i] % w; + + fs = (dsf_canonify(connected, y*W+x) == + dsf_canonify(connected, (y+1)*W+(x+1))); + bs = (dsf_canonify(connected, (y+1)*W+x) == + dsf_canonify(connected, y*W+(x+1))); + + /* + * It isn't possible to get into a situation where we + * aren't allowed to place _either_ type of slash in a + * square. Thus, filled-grid generation never has to + * backtrack. + * + * Proof (thanks to Gareth Taylor): + * + * If it were possible, it would have to be because there + * was an existing path (not using this square) between the + * top-left and bottom-right corners of this square, and + * another between the other two. These two paths would + * have to cross at some point. + * + * Obviously they can't cross in the middle of a square, so + * they must cross by sharing a point in common. But this + * isn't possible either: if you chessboard-colour all the + * points on the grid, you find that any continuous + * diagonal path is entirely composed of points of the same + * colour. And one of our two hypothetical paths is between + * two black points, and the other is between two white + * points - therefore they can have no point in common. [] + */ + assert(!(fs && bs)); + + v = fs ? +1 : bs ? -1 : 2 * random_upto(rs, 2) - 1; + fill_square(w, h, x, y, v, soln, connected, NULL); + } + + sfree(indices); + sfree(connected); +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h, W = w+1, H = h+1; + signed char *soln, *tmpsoln, *clues; + int *clueindices; + struct solver_scratch *sc; + int x, y, v, i, j; + char *desc; + + soln = snewn(w*h, signed char); + tmpsoln = snewn(w*h, signed char); + clues = snewn(W*H, signed char); + clueindices = snewn(W*H, int); + sc = new_scratch(w, h); + + do { + /* + * Create the filled grid. + */ + slant_generate(w, h, soln, rs); + + /* + * Fill in the complete set of clues. + */ + for (y = 0; y < H; y++) + for (x = 0; x < W; x++) { + v = 0; + + if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] == -1) v++; + if (x > 0 && y < h && soln[y*w+(x-1)] == +1) v++; + if (x < w && y > 0 && soln[(y-1)*w+x] == +1) v++; + if (x < w && y < h && soln[y*w+x] == -1) v++; + + clues[y*W+x] = v; + } + + /* + * With all clue points filled in, all puzzles are easy: we can + * simply process the clue points in lexicographic order, and + * at each clue point we will always have at most one square + * undecided, which we can then fill in uniquely. + */ + assert(slant_solve(w, h, clues, tmpsoln, sc, DIFF_EASY) == 1); + + /* + * Remove as many clues as possible while retaining solubility. + * + * In DIFF_HARD mode, we prioritise the removal of obvious + * starting points (4s, 0s, border 2s and corner 1s), on + * the grounds that having as few of these as possible + * seems like a good thing. In particular, we can often get + * away without _any_ completely obvious starting points, + * which is even better. + */ + for (i = 0; i < W*H; i++) + clueindices[i] = i; + shuffle(clueindices, W*H, sizeof(*clueindices), rs); + for (j = 0; j < 2; j++) { + for (i = 0; i < W*H; i++) { + int pass, yb, xb; + + y = clueindices[i] / W; + x = clueindices[i] % W; + v = clues[y*W+x]; + + /* + * Identify which pass we should process this point + * in. If it's an obvious start point, _or_ we're + * in DIFF_EASY, then it goes in pass 0; otherwise + * pass 1. + */ + xb = (x == 0 || x == W-1); + yb = (y == 0 || y == H-1); + if (params->diff == DIFF_EASY || v == 4 || v == 0 || + (v == 2 && (xb||yb)) || (v == 1 && xb && yb)) + pass = 0; + else + pass = 1; + + if (pass == j) { + clues[y*W+x] = -1; + if (slant_solve(w, h, clues, tmpsoln, sc, + params->diff) != 1) + clues[y*W+x] = v; /* put it back */ + } + } + } + + /* + * And finally, verify that the grid is of _at least_ the + * requested difficulty, by running the solver one level + * down and verifying that it can't manage it. + */ + } while (params->diff > 0 && + slant_solve(w, h, clues, tmpsoln, sc, params->diff - 1) <= 1); + + /* + * Now we have the clue set as it will be presented to the + * user. Encode it in a game desc. + */ + { + char *p; + int run, i; + + desc = snewn(W*H+1, char); + p = desc; + run = 0; + for (i = 0; i <= W*H; i++) { + int n = (i < W*H ? clues[i] : -2); + + if (n == -1) + run++; + else { + if (run) { + while (run > 0) { + int c = 'a' - 1 + run; + if (run > 26) + c = 'z'; + *p++ = c; + run -= c - ('a' - 1); + } + } + if (n >= 0) + *p++ = '0' + n; + run = 0; + } + } + assert(p - desc <= W*H); + *p++ = '\0'; + desc = sresize(desc, p - desc, char); + } + + /* + * Encode the solution as an aux_info. + */ + { + char *auxbuf; + *aux = auxbuf = snewn(w*h+1, char); + for (i = 0; i < w*h; i++) + auxbuf[i] = soln[i] < 0 ? '\\' : '/'; + auxbuf[w*h] = '\0'; + } + + free_scratch(sc); + sfree(clueindices); + sfree(clues); + sfree(tmpsoln); + sfree(soln); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h, W = w+1, H = h+1; + int area = W*H; + int squares = 0; + + while (*desc) { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + squares += n - 'a' + 1; + } else if (n >= '0' && n <= '4') { + squares++; + } else + return "Invalid character in game description"; + } + + if (squares < area) + return "Not enough data to fill grid"; + + if (squares > area) + return "Too much data to fit in grid"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h, W = w+1, H = h+1; + game_state *state = snew(game_state); + int area = W*H; + int squares = 0; + + state->p = *params; + state->soln = snewn(w*h, signed char); + memset(state->soln, 0, w*h); + state->completed = state->used_solve = FALSE; + state->errors = snewn(W*H, unsigned char); + memset(state->errors, 0, W*H); + + state->clues = snew(game_clues); + state->clues->w = w; + state->clues->h = h; + state->clues->clues = snewn(W*H, signed char); + state->clues->refcount = 1; + state->clues->tmpdsf = snewn(W*H*2+W+H, int); + memset(state->clues->clues, -1, W*H); + while (*desc) { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + squares += n - 'a' + 1; + } else if (n >= '0' && n <= '4') { + state->clues->clues[squares++] = n - '0'; + } else + assert(!"can't get here"); + } + assert(squares == area); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->p.w, h = state->p.h, W = w+1, H = h+1; + game_state *ret = snew(game_state); + + ret->p = state->p; + ret->clues = state->clues; + ret->clues->refcount++; + ret->completed = state->completed; + ret->used_solve = state->used_solve; + + ret->soln = snewn(w*h, signed char); + memcpy(ret->soln, state->soln, w*h); + + ret->errors = snewn(W*H, unsigned char); + memcpy(ret->errors, state->errors, W*H); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->errors); + sfree(state->soln); + assert(state->clues); + if (--state->clues->refcount <= 0) { + sfree(state->clues->clues); + sfree(state->clues->tmpdsf); + sfree(state->clues); + } + sfree(state); +} + +/* + * Utility function to return the current degree of a vertex. If + * `anti' is set, it returns the number of filled-in edges + * surrounding the point which _don't_ connect to it; thus 4 minus + * its anti-degree is the maximum degree it could have if all the + * empty spaces around it were filled in. + * + * (Yes, _4_ minus its anti-degree even if it's a border vertex.) + * + * If ret > 0, *sx and *sy are set to the coordinates of one of the + * squares that contributed to it. + */ +static int vertex_degree(int w, int h, signed char *soln, int x, int y, + int anti, int *sx, int *sy) +{ + int ret = 0; + + assert(x >= 0 && x <= w && y >= 0 && y <= h); + if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] - anti < 0) { + if (sx) *sx = x-1; + if (sy) *sy = y-1; + ret++; + } + if (x > 0 && y < h && soln[y*w+(x-1)] + anti > 0) { + if (sx) *sx = x-1; + if (sy) *sy = y; + ret++; + } + if (x < w && y > 0 && soln[(y-1)*w+x] + anti > 0) { + if (sx) *sx = x; + if (sy) *sy = y-1; + ret++; + } + if (x < w && y < h && soln[y*w+x] - anti < 0) { + if (sx) *sx = x; + if (sy) *sy = y; + ret++; + } + + return anti ? 4 - ret : ret; +} + +struct slant_neighbour_ctx { + const game_state *state; + int i, n, neighbours[4]; +}; +static int slant_neighbour(int vertex, void *vctx) +{ + struct slant_neighbour_ctx *ctx = (struct slant_neighbour_ctx *)vctx; + + if (vertex >= 0) { + int w = ctx->state->p.w, h = ctx->state->p.h, W = w+1; + int x = vertex % W, y = vertex / W; + ctx->n = ctx->i = 0; + if (x < w && y < h && ctx->state->soln[y*w+x] < 0) + ctx->neighbours[ctx->n++] = (y+1)*W+(x+1); + if (x > 0 && y > 0 && ctx->state->soln[(y-1)*w+(x-1)] < 0) + ctx->neighbours[ctx->n++] = (y-1)*W+(x-1); + if (x > 0 && y < h && ctx->state->soln[y*w+(x-1)] > 0) + ctx->neighbours[ctx->n++] = (y+1)*W+(x-1); + if (x < w && y > 0 && ctx->state->soln[(y-1)*w+x] > 0) + ctx->neighbours[ctx->n++] = (y-1)*W+(x+1); + } + + if (ctx->i < ctx->n) + return ctx->neighbours[ctx->i++]; + else + return -1; +} + +static int check_completion(game_state *state) +{ + int w = state->p.w, h = state->p.h, W = w+1, H = h+1; + int x, y, err = FALSE; + + memset(state->errors, 0, W*H); + + /* + * Detect and error-highlight loops in the grid. + */ + { + struct findloopstate *fls = findloop_new_state(W*H); + struct slant_neighbour_ctx ctx; + ctx.state = state; + + if (findloop_run(fls, W*H, slant_neighbour, &ctx)) + err = TRUE; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int u, v; + if (state->soln[y*w+x] == 0) { + continue; + } else if (state->soln[y*w+x] > 0) { + u = y*W+(x+1); + v = (y+1)*W+x; + } else { + u = (y+1)*W+(x+1); + v = y*W+x; + } + if (findloop_is_loop_edge(fls, u, v)) + state->errors[y*W+x] |= ERR_SQUARE; + } + } + + findloop_free_state(fls); + } + + /* + * Now go through and check the degree of each clue vertex, and + * mark it with ERR_VERTEX if it cannot be fulfilled. + */ + for (y = 0; y < H; y++) + for (x = 0; x < W; x++) { + int c; + + if ((c = state->clues->clues[y*W+x]) < 0) + continue; + + /* + * Check to see if there are too many connections to + * this vertex _or_ too many non-connections. Either is + * grounds for marking the vertex as erroneous. + */ + if (vertex_degree(w, h, state->soln, x, y, + FALSE, NULL, NULL) > c || + vertex_degree(w, h, state->soln, x, y, + TRUE, NULL, NULL) > 4-c) { + state->errors[y*W+x] |= ERR_VERTEX; + err = TRUE; + } + } + + /* + * Now our actual victory condition is that (a) none of the + * above code marked anything as erroneous, and (b) every + * square has an edge in it. + */ + + if (err) + return FALSE; + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->soln[y*w+x] == 0) + return FALSE; + + return TRUE; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->p.w, h = state->p.h; + signed char *soln; + int bs, ret; + int free_soln = FALSE; + char *move, buf[80]; + int movelen, movesize; + int x, y; + + if (aux) { + /* + * If we already have the solution, save ourselves some + * time. + */ + soln = (signed char *)aux; + bs = (signed char)'\\'; + free_soln = FALSE; + } else { + struct solver_scratch *sc = new_scratch(w, h); + soln = snewn(w*h, signed char); + bs = -1; + ret = slant_solve(w, h, state->clues->clues, soln, sc, DIFF_HARD); + free_scratch(sc); + if (ret != 1) { + sfree(soln); + if (ret == 0) + *error = "This puzzle is not self-consistent"; + else + *error = "Unable to find a unique solution for this puzzle"; + return NULL; + } + free_soln = TRUE; + } + + /* + * Construct a move string which turns the current state into + * the solved state. + */ + movesize = 256; + move = snewn(movesize, char); + movelen = 0; + move[movelen++] = 'S'; + move[movelen] = '\0'; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int v = (soln[y*w+x] == bs ? -1 : +1); + if (state->soln[y*w+x] != v) { + int len = sprintf(buf, ";%c%d,%d", (int)(v < 0 ? '\\' : '/'), x, y); + if (movelen + len >= movesize) { + movesize = movelen + len + 256; + move = sresize(move, movesize, char); + } + strcpy(move + movelen, buf); + movelen += len; + } + } + + if (free_soln) + sfree(soln); + + return move; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->p.w, h = state->p.h, W = w+1, H = h+1; + int x, y, len; + char *ret, *p; + + /* + * There are h+H rows of w+W columns. + */ + len = (h+H) * (w+W+1) + 1; + ret = snewn(len, char); + p = ret; + + for (y = 0; y < H; y++) { + for (x = 0; x < W; x++) { + if (state->clues->clues[y*W+x] >= 0) + *p++ = state->clues->clues[y*W+x] + '0'; + else + *p++ = '+'; + if (x < w) + *p++ = '-'; + } + *p++ = '\n'; + if (y < h) { + for (x = 0; x < W; x++) { + *p++ = '|'; + if (x < w) { + if (state->soln[y*w+x] != 0) + *p++ = (state->soln[y*w+x] < 0 ? '\\' : '/'); + else + *p++ = ' '; + } + } + *p++ = '\n'; + } + } + *p++ = '\0'; + + assert(p - ret == len); + return ret; +} + +struct game_ui { + int cur_x, cur_y, cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->cur_x = ui->cur_y = ui->cur_visible = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#define BORDER TILESIZE +#define CLUE_RADIUS (TILESIZE / 3) +#define CLUE_TEXTSIZE (TILESIZE / 2) +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) + +#define FLASH_TIME 0.30F + +/* + * Bit fields in the `grid' and `todraw' elements of the drawstate. + */ +#define BACKSLASH 0x00000001L +#define FORWSLASH 0x00000002L +#define L_T 0x00000004L +#define ERR_L_T 0x00000008L +#define L_B 0x00000010L +#define ERR_L_B 0x00000020L +#define T_L 0x00000040L +#define ERR_T_L 0x00000080L +#define T_R 0x00000100L +#define ERR_T_R 0x00000200L +#define C_TL 0x00000400L +#define ERR_C_TL 0x00000800L +#define FLASH 0x00001000L +#define ERRSLASH 0x00002000L +#define ERR_TL 0x00004000L +#define ERR_TR 0x00008000L +#define ERR_BL 0x00010000L +#define ERR_BR 0x00020000L +#define CURSOR 0x00040000L + +struct game_drawstate { + int tilesize; + int started; + long *grid; + long *todraw; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->p.w, h = state->p.h; + int v; + char buf[80]; + enum { CLOCKWISE, ANTICLOCKWISE, NONE } action = NONE; + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + /* + * This is an utterly awful hack which I should really sort out + * by means of a proper configuration mechanism. One Slant + * player has observed that they prefer the mouse buttons to + * function exactly the opposite way round, so here's a + * mechanism for environment-based configuration. I cache the + * result in a global variable - yuck! - to avoid repeated + * lookups. + */ + { + static int swap_buttons = -1; + if (swap_buttons < 0) { + char *env = getenv("SLANT_SWAP_BUTTONS"); + swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y')); + } + if (swap_buttons) { + if (button == LEFT_BUTTON) + button = RIGHT_BUTTON; + else + button = LEFT_BUTTON; + } + } + action = (button == LEFT_BUTTON) ? CLOCKWISE : ANTICLOCKWISE; + + x = FROMCOORD(x); + y = FROMCOORD(y); + if (x < 0 || y < 0 || x >= w || y >= h) + return NULL; + ui->cur_visible = 0; + } else if (IS_CURSOR_SELECT(button)) { + if (!ui->cur_visible) { + ui->cur_visible = 1; + return ""; + } + x = ui->cur_x; + y = ui->cur_y; + + action = (button == CURSOR_SELECT2) ? ANTICLOCKWISE : CLOCKWISE; + } else if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0); + ui->cur_visible = 1; + return ""; + } else if (button == '\\' || button == '\b' || button == '/') { + int x = ui->cur_x, y = ui->cur_y; + if (button == ("\\" "\b" "/")[state->soln[y*w + x] + 1]) return NULL; + sprintf(buf, "%c%d,%d", button == '\b' ? 'C' : button, x, y); + return dupstr(buf); + } + + if (action != NONE) { + if (action == CLOCKWISE) { + /* + * Left-clicking cycles blank -> \ -> / -> blank. + */ + v = state->soln[y*w+x] - 1; + if (v == -2) + v = +1; + } else { + /* + * Right-clicking cycles blank -> / -> \ -> blank. + */ + v = state->soln[y*w+x] + 1; + if (v == +2) + v = -1; + } + + sprintf(buf, "%c%d,%d", (int)(v==-1 ? '\\' : v==+1 ? '/' : 'C'), x, y); + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->p.w, h = state->p.h; + char c; + int x, y, n; + game_state *ret = dup_game(state); + + while (*move) { + c = *move; + if (c == 'S') { + ret->used_solve = TRUE; + move++; + } else if (c == '\\' || c == '/' || c == 'C') { + move++; + if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || + x < 0 || y < 0 || x >= w || y >= h) { + free_game(ret); + return NULL; + } + ret->soln[y*w+x] = (c == '\\' ? -1 : c == '/' ? +1 : 0); + move += n; + } else { + free_game(ret); + return NULL; + } + if (*move == ';') + move++; + else if (*move) { + free_game(ret); + return NULL; + } + } + + /* + * We never clear the `completed' flag, but we must always + * re-run the completion check because it also highlights + * errors in the grid. + */ + ret->completed = check_completion(ret) || ret->completed; + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* fool the macros */ + struct dummy { int tilesize; } dummy, *ds = &dummy; + dummy.tilesize = tilesize; + + *x = 2 * BORDER + params->w * TILESIZE + 1; + *y = 2 * BORDER + params->h * TILESIZE + 1; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + /* CURSOR colour is a background highlight. */ + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, -1); + + ret[COL_FILLEDSQUARE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_FILLEDSQUARE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1]; + ret[COL_FILLEDSQUARE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.7F; + ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.7F; + ret[COL_GRID * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.7F; + + ret[COL_INK * 3 + 0] = 0.0F; + ret[COL_INK * 3 + 1] = 0.0F; + ret[COL_INK * 3 + 2] = 0.0F; + + ret[COL_SLANT1 * 3 + 0] = 0.0F; + ret[COL_SLANT1 * 3 + 1] = 0.0F; + ret[COL_SLANT1 * 3 + 2] = 0.0F; + + ret[COL_SLANT2 * 3 + 0] = 0.0F; + ret[COL_SLANT2 * 3 + 1] = 0.0F; + ret[COL_SLANT2 * 3 + 2] = 0.0F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->p.w, h = state->p.h; + int i; + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; + ds->started = FALSE; + ds->grid = snewn((w+2)*(h+2), long); + ds->todraw = snewn((w+2)*(h+2), long); + for (i = 0; i < (w+2)*(h+2); i++) + ds->grid[i] = ds->todraw[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->todraw); + sfree(ds->grid); + sfree(ds); +} + +static void draw_clue(drawing *dr, game_drawstate *ds, + int x, int y, long v, long err, int bg, int colour) +{ + char p[2]; + int ccol = colour >= 0 ? colour : ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2; + int tcol = colour >= 0 ? colour : err ? COL_ERROR : COL_INK; + + if (v < 0) + return; + + p[0] = (char)v + '0'; + p[1] = '\0'; + draw_circle(dr, COORD(x), COORD(y), CLUE_RADIUS, + bg >= 0 ? bg : COL_BACKGROUND, ccol); + draw_text(dr, COORD(x), COORD(y), FONT_VARIABLE, + CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE, tcol, p); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, game_clues *clues, + int x, int y, long v) +{ + int w = clues->w, h = clues->h, W = w+1 /*, H = h+1 */; + int chesscolour = (x ^ y) & 1; + int fscol = chesscolour ? COL_SLANT2 : COL_SLANT1; + int bscol = chesscolour ? COL_SLANT1 : COL_SLANT2; + + clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); + + draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE, + (v & FLASH) ? COL_GRID : + (v & CURSOR) ? COL_CURSOR : + (v & (BACKSLASH | FORWSLASH)) ? COL_FILLEDSQUARE : + COL_BACKGROUND); + + /* + * Draw the grid lines. + */ + if (x >= 0 && x < w && y >= 0) + draw_rect(dr, COORD(x), COORD(y), TILESIZE+1, 1, COL_GRID); + if (x >= 0 && x < w && y < h) + draw_rect(dr, COORD(x), COORD(y+1), TILESIZE+1, 1, COL_GRID); + if (y >= 0 && y < h && x >= 0) + draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE+1, COL_GRID); + if (y >= 0 && y < h && x < w) + draw_rect(dr, COORD(x+1), COORD(y), 1, TILESIZE+1, COL_GRID); + if (x == -1 && y == -1) + draw_rect(dr, COORD(x+1), COORD(y+1), 1, 1, COL_GRID); + if (x == -1 && y == h) + draw_rect(dr, COORD(x+1), COORD(y), 1, 1, COL_GRID); + if (x == w && y == -1) + draw_rect(dr, COORD(x), COORD(y+1), 1, 1, COL_GRID); + if (x == w && y == h) + draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID); + + /* + * Draw the slash. + */ + if (v & BACKSLASH) { + int scol = (v & ERRSLASH) ? COL_ERROR : bscol; + draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol); + draw_line(dr, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1, + scol); + draw_line(dr, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1), + scol); + } else if (v & FORWSLASH) { + int scol = (v & ERRSLASH) ? COL_ERROR : fscol; + draw_line(dr, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol); + draw_line(dr, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1, + scol); + draw_line(dr, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1), + scol); + } + + /* + * Draw dots on the grid corners that appear if a slash is in a + * neighbouring cell. + */ + if (v & (L_T | BACKSLASH)) + draw_rect(dr, COORD(x), COORD(y)+1, 1, 1, + (v & ERR_L_T ? COL_ERROR : bscol)); + if (v & (L_B | FORWSLASH)) + draw_rect(dr, COORD(x), COORD(y+1)-1, 1, 1, + (v & ERR_L_B ? COL_ERROR : fscol)); + if (v & (T_L | BACKSLASH)) + draw_rect(dr, COORD(x)+1, COORD(y), 1, 1, + (v & ERR_T_L ? COL_ERROR : bscol)); + if (v & (T_R | FORWSLASH)) + draw_rect(dr, COORD(x+1)-1, COORD(y), 1, 1, + (v & ERR_T_R ? COL_ERROR : fscol)); + if (v & (C_TL | BACKSLASH)) + draw_rect(dr, COORD(x), COORD(y), 1, 1, + (v & ERR_C_TL ? COL_ERROR : bscol)); + + /* + * And finally the clues at the corners. + */ + if (x >= 0 && y >= 0) + draw_clue(dr, ds, x, y, clues->clues[y*W+x], v & ERR_TL, -1, -1); + if (x < w && y >= 0) + draw_clue(dr, ds, x+1, y, clues->clues[y*W+(x+1)], v & ERR_TR, -1, -1); + if (x >= 0 && y < h) + draw_clue(dr, ds, x, y+1, clues->clues[(y+1)*W+x], v & ERR_BL, -1, -1); + if (x < w && y < h) + draw_clue(dr, ds, x+1, y+1, clues->clues[(y+1)*W+(x+1)], v & ERR_BR, + -1, -1); + + unclip(dr); + draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->p.w, h = state->p.h, W = w+1, H = h+1; + int x, y; + int flashing; + + if (flashtime > 0) + flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; + else + flashing = FALSE; + + if (!ds->started) { + int ww, wh; + game_compute_size(&state->p, TILESIZE, &ww, &wh); + draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND); + draw_update(dr, 0, 0, ww, wh); + ds->started = TRUE; + } + + /* + * Loop over the grid and work out where all the slashes are. + * We need to do this because a slash in one square affects the + * drawing of the next one along. + */ + for (y = -1; y <= h; y++) + for (x = -1; x <= w; x++) { + if (x >= 0 && x < w && y >= 0 && y < h) + ds->todraw[(y+1)*(w+2)+(x+1)] = flashing ? FLASH : 0; + else + ds->todraw[(y+1)*(w+2)+(x+1)] = 0; + } + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int err = state->errors[y*W+x] & ERR_SQUARE; + + if (state->soln[y*w+x] < 0) { + ds->todraw[(y+1)*(w+2)+(x+1)] |= BACKSLASH; + ds->todraw[(y+2)*(w+2)+(x+1)] |= T_R; + ds->todraw[(y+1)*(w+2)+(x+2)] |= L_B; + ds->todraw[(y+2)*(w+2)+(x+2)] |= C_TL; + if (err) { + ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH | + ERR_T_L | ERR_L_T | ERR_C_TL; + ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_R; + ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_B; + ds->todraw[(y+2)*(w+2)+(x+2)] |= ERR_C_TL; + } + } else if (state->soln[y*w+x] > 0) { + ds->todraw[(y+1)*(w+2)+(x+1)] |= FORWSLASH; + ds->todraw[(y+1)*(w+2)+(x+2)] |= L_T | C_TL; + ds->todraw[(y+2)*(w+2)+(x+1)] |= T_L | C_TL; + if (err) { + ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH | + ERR_L_B | ERR_T_R; + ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_T | ERR_C_TL; + ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_L | ERR_C_TL; + } + } + if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y) + ds->todraw[(y+1)*(w+2)+(x+1)] |= CURSOR; + } + } + + for (y = 0; y < H; y++) + for (x = 0; x < W; x++) + if (state->errors[y*W+x] & ERR_VERTEX) { + ds->todraw[y*(w+2)+x] |= ERR_BR; + ds->todraw[y*(w+2)+(x+1)] |= ERR_BL; + ds->todraw[(y+1)*(w+2)+x] |= ERR_TR; + ds->todraw[(y+1)*(w+2)+(x+1)] |= ERR_TL; + } + + /* + * Now go through and draw the grid squares. + */ + for (y = -1; y <= h; y++) { + for (x = -1; x <= w; x++) { + if (ds->todraw[(y+1)*(w+2)+(x+1)] != ds->grid[(y+1)*(w+2)+(x+1)]) { + draw_tile(dr, ds, state->clues, x, y, + ds->todraw[(y+1)*(w+2)+(x+1)]); + ds->grid[(y+1)*(w+2)+(x+1)] = ds->todraw[(y+1)*(w+2)+(x+1)]; + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return FLASH_TIME; + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->p.w, h = state->p.h, W = w+1; + int ink = print_mono_colour(dr, 0); + int paper = print_mono_colour(dr, 1); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILESIZE / 16); + draw_rect_outline(dr, COORD(0), COORD(0), w*TILESIZE, h*TILESIZE, ink); + + /* + * Grid. + */ + print_line_width(dr, TILESIZE / 24); + for (x = 1; x < w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink); + for (y = 1; y < h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink); + + /* + * Solution. + */ + print_line_width(dr, TILESIZE / 12); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->soln[y*w+x]) { + int ly, ry; + /* + * To prevent nasty line-ending artefacts at + * corners, I'll do something slightly cunning + * here. + */ + clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); + if (state->soln[y*w+x] < 0) + ly = y-1, ry = y+2; + else + ry = y-1, ly = y+2; + draw_line(dr, COORD(x-1), COORD(ly), COORD(x+2), COORD(ry), + ink); + unclip(dr); + } + + /* + * Clues. + */ + print_line_width(dr, TILESIZE / 24); + for (y = 0; y <= h; y++) + for (x = 0; x <= w; x++) + draw_clue(dr, ds, x, y, state->clues->clues[y*W+x], + FALSE, paper, ink); +} + +#ifdef COMBINED +#define thegame slant +#endif + +const struct game thegame = { + "Slant", "games.slant", "slant", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff, really_verbose = FALSE; + struct solver_scratch *sc; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_verbose = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + sc = new_scratch(p->w, p->h); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + for (diff = 0; diff < DIFFCOUNT; diff++) { + ret = slant_solve(p->w, p->h, s->clues->clues, + s->soln, sc, diff); + if (ret < 2) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: harder than Hard, or ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == 0) + printf("Difficulty rating: impossible (no solution exists)\n"); + else if (ret == 1) + printf("Difficulty rating: %s\n", slant_diffnames[diff]); + } else { + verbose = really_verbose; + ret = slant_solve(p->w, p->h, s->clues->clues, + s->soln, sc, diff); + if (ret == 0) + printf("Puzzle is inconsistent\n"); + else + fputs(game_text_format(s), stdout); + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/solo.R b/apps/plugins/puzzles/solo.R new file mode 100644 index 0000000000..081a76147e --- /dev/null +++ b/apps/plugins/puzzles/solo.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +SOLO_EXTRA = divvy dsf + +solo : [X] GTK COMMON solo SOLO_EXTRA solo-icon|no-icon + +solo : [G] WINDOWS COMMON solo SOLO_EXTRA solo.res|noicon.res + +solosolver : [U] solo[STANDALONE_SOLVER] SOLO_EXTRA STANDALONE +solosolver : [C] solo[STANDALONE_SOLVER] SOLO_EXTRA STANDALONE + +ALL += solo[COMBINED] SOLO_EXTRA + +!begin am gtk +GAMES += solo +!end + +!begin >list.c + A(solo) \ +!end + +!begin >gamedesc.txt +solo:solo.exe:Solo:Number placement puzzle:Fill in the grid so that each row, column and square block contains one of every digit. +!end diff --git a/apps/plugins/puzzles/solo.c b/apps/plugins/puzzles/solo.c new file mode 100644 index 0000000000..26f0eddd1a --- /dev/null +++ b/apps/plugins/puzzles/solo.c @@ -0,0 +1,5656 @@ +/* + * solo.c: the number-placing puzzle most popularly known as `Sudoku'. + * + * TODO: + * + * - reports from users are that `Trivial'-mode puzzles are still + * rather hard compared to newspapers' easy ones, so some better + * low-end difficulty grading would be nice + * + it's possible that really easy puzzles always have + * _several_ things you can do, so don't make you hunt too + * hard for the one deduction you can currently make + * + it's also possible that easy puzzles require fewer + * cross-eliminations: perhaps there's a higher incidence of + * things you can deduce by looking only at (say) rows, + * rather than things you have to check both rows and columns + * for + * + but really, what I need to do is find some really easy + * puzzles and _play_ them, to see what's actually easy about + * them + * + while I'm revamping this area, filling in the _last_ + * number in a nearly-full row or column should certainly be + * permitted even at the lowest difficulty level. + * + also Owen noticed that `Basic' grids requiring numeric + * elimination are actually very hard, so I wonder if a + * difficulty gradation between that and positional- + * elimination-only might be in order + * + but it's not good to have _too_ many difficulty levels, or + * it'll take too long to randomly generate a given level. + * + * - it might still be nice to do some prioritisation on the + * removal of numbers from the grid + * + one possibility is to try to minimise the maximum number + * of filled squares in any block, which in particular ought + * to enforce never leaving a completely filled block in the + * puzzle as presented. + * + * - alternative interface modes + * + sudoku.com's Windows program has a palette of possible + * entries; you select a palette entry first and then click + * on the square you want it to go in, thus enabling + * mouse-only play. Useful for PDAs! I don't think it's + * actually incompatible with the current highlight-then-type + * approach: you _either_ highlight a palette entry and then + * click, _or_ you highlight a square and then type. At most + * one thing is ever highlighted at a time, so there's no way + * to confuse the two. + * + then again, I don't actually like sudoku.com's interface; + * it's too much like a paint package whereas I prefer to + * think of Solo as a text editor. + * + another PDA-friendly possibility is a drag interface: + * _drag_ numbers from the palette into the grid squares. + * Thought experiments suggest I'd prefer that to the + * sudoku.com approach, but I haven't actually tried it. + */ + +/* + * Solo puzzles need to be square overall (since each row and each + * column must contain one of every digit), but they need not be + * subdivided the same way internally. I am going to adopt a + * convention whereby I _always_ refer to `r' as the number of rows + * of _big_ divisions, and `c' as the number of columns of _big_ + * divisions. Thus, a 2c by 3r puzzle looks something like this: + * + * 4 5 1 | 2 6 3 + * 6 3 2 | 5 4 1 + * ------+------ (Of course, you can't subdivide it the other way + * 1 4 5 | 6 3 2 or you'll get clashes; observe that the 4 in the + * 3 2 6 | 4 1 5 top left would conflict with the 4 in the second + * ------+------ box down on the left-hand side.) + * 5 1 4 | 3 2 6 + * 2 6 3 | 1 5 4 + * + * The need for a strong naming convention should now be clear: + * each small box is two rows of digits by three columns, while the + * overall puzzle has three rows of small boxes by two columns. So + * I will (hopefully) consistently use `r' to denote the number of + * rows _of small boxes_ (here 3), which is also the number of + * columns of digits in each small box; and `c' vice versa (here + * 2). + * + * I'm also going to choose arbitrarily to list c first wherever + * possible: the above is a 2x3 puzzle, not a 3x2 one. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#ifdef STANDALONE_SOLVER +#include +int solver_show_working, solver_recurse_depth; +#endif + +#include "puzzles.h" + +/* + * To save space, I store digits internally as unsigned char. This + * imposes a hard limit of 255 on the order of the puzzle. Since + * even a 5x5 takes unacceptably long to generate, I don't see this + * as a serious limitation unless something _really_ impressive + * happens in computing technology; but here's a typedef anyway for + * general good practice. + */ +typedef unsigned char digit; +#define ORDER_MAX 255 + +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 2) +#define GRIDEXTRA max((TILE_SIZE / 32),1) + +#define FLASH_TIME 0.4F + +enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF2, SYMM_REF2D, SYMM_REF4, + SYMM_REF4D, SYMM_REF8 }; + +enum { DIFF_BLOCK, + DIFF_SIMPLE, DIFF_INTERSECT, DIFF_SET, DIFF_EXTREME, DIFF_RECURSIVE, + DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE }; + +enum { DIFF_KSINGLE, DIFF_KMINMAX, DIFF_KSUMS, DIFF_KINTERSECT }; + +enum { + COL_BACKGROUND, + COL_XDIAGONALS, + COL_GRID, + COL_CLUE, + COL_USER, + COL_HIGHLIGHT, + COL_ERROR, + COL_PENCIL, + COL_KILLER, + NCOLOURS +}; + +/* + * To determine all possible ways to reach a given sum by adding two or + * three numbers from 1..9, each of which occurs exactly once in the sum, + * these arrays contain a list of bitmasks for each sum value, where if + * bit N is set, it means that N occurs in the sum. Each list is + * terminated by a zero if it is shorter than the size of the array. + */ +#define MAX_2SUMS 5 +#define MAX_3SUMS 8 +#define MAX_4SUMS 12 +unsigned long sum_bits2[18][MAX_2SUMS]; +unsigned long sum_bits3[25][MAX_3SUMS]; +unsigned long sum_bits4[31][MAX_4SUMS]; + +static int find_sum_bits(unsigned long *array, int idx, int value_left, + int addends_left, int min_addend, + unsigned long bitmask_so_far) +{ + int i; + assert(addends_left >= 2); + + for (i = min_addend; i < value_left; i++) { + unsigned long new_bitmask = bitmask_so_far | (1L << i); + assert(bitmask_so_far != new_bitmask); + + if (addends_left == 2) { + int j = value_left - i; + if (j <= i) + break; + if (j > 9) + continue; + array[idx++] = new_bitmask | (1L << j); + } else + idx = find_sum_bits(array, idx, value_left - i, + addends_left - 1, i + 1, + new_bitmask); + } + return idx; +} + +static void precompute_sum_bits(void) +{ + int i; + for (i = 3; i < 31; i++) { + int j; + if (i < 18) { + j = find_sum_bits(sum_bits2[i], 0, i, 2, 1, 0); + assert (j <= MAX_2SUMS); + if (j < MAX_2SUMS) + sum_bits2[i][j] = 0; + } + if (i < 25) { + j = find_sum_bits(sum_bits3[i], 0, i, 3, 1, 0); + assert (j <= MAX_3SUMS); + if (j < MAX_3SUMS) + sum_bits3[i][j] = 0; + } + j = find_sum_bits(sum_bits4[i], 0, i, 4, 1, 0); + assert (j <= MAX_4SUMS); + if (j < MAX_4SUMS) + sum_bits4[i][j] = 0; + } +} + +struct game_params { + /* + * For a square puzzle, `c' and `r' indicate the puzzle + * parameters as described above. + * + * A jigsaw-style puzzle is indicated by r==1, in which case c + * can be whatever it likes (there is no constraint on + * compositeness - a 7x7 jigsaw sudoku makes perfect sense). + */ + int c, r, symm, diff, kdiff; + int xtype; /* require all digits in X-diagonals */ + int killer; +}; + +struct block_structure { + int refcount; + + /* + * For text formatting, we do need c and r here. + */ + int c, r, area; + + /* + * For any square index, whichblock[i] gives its block index. + * + * For 0 <= b,i < cr, blocks[b][i] gives the index of the ith + * square in block b. nr_squares[b] gives the number of squares + * in block b (also the number of valid elements in blocks[b]). + * + * blocks_data holds the data pointed to by blocks. + * + * nr_squares may be NULL for block structures where all blocks are + * the same size. + */ + int *whichblock, **blocks, *nr_squares, *blocks_data; + int nr_blocks, max_nr_squares; + +#ifdef STANDALONE_SOLVER + /* + * Textual descriptions of each block. For normal Sudoku these + * are of the form "(1,3)"; for jigsaw they are "starting at + * (5,7)". So the sensible usage in both cases is to say + * "elimination within block %s" with one of these strings. + * + * Only blocknames itself needs individually freeing; it's all + * one block. + */ + char **blocknames; +#endif +}; + +struct game_state { + /* + * For historical reasons, I use `cr' to denote the overall + * width/height of the puzzle. It was a natural notation when + * all puzzles were divided into blocks in a grid, but doesn't + * really make much sense given jigsaw puzzles. However, the + * obvious `n' is heavily used in the solver to describe the + * index of a number being placed, so `cr' will have to stay. + */ + int cr; + struct block_structure *blocks; + struct block_structure *kblocks; /* Blocks for killer puzzles. */ + int xtype, killer; + digit *grid, *kgrid; + unsigned char *pencil; /* c*r*c*r elements */ + unsigned char *immutable; /* marks which digits are clues */ + int completed, cheated; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->c = ret->r = 3; + ret->xtype = FALSE; + ret->killer = FALSE; + ret->symm = SYMM_ROT2; /* a plausible default */ + ret->diff = DIFF_BLOCK; /* so is this */ + ret->kdiff = DIFF_KINTERSECT; /* so is this */ + + return ret; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + static struct { + char *title; + game_params params; + } presets[] = { + { "2x2 Trivial", { 2, 2, SYMM_ROT2, DIFF_BLOCK, DIFF_KMINMAX, FALSE, FALSE } }, + { "2x3 Basic", { 2, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Trivial", { 3, 3, SYMM_ROT2, DIFF_BLOCK, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Basic", { 3, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Basic X", { 3, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, TRUE } }, + { "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Advanced X", { 3, 3, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, TRUE } }, + { "3x3 Extreme", { 3, 3, SYMM_ROT2, DIFF_EXTREME, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Unreasonable", { 3, 3, SYMM_ROT2, DIFF_RECURSIVE, DIFF_KMINMAX, FALSE, FALSE } }, + { "3x3 Killer", { 3, 3, SYMM_NONE, DIFF_BLOCK, DIFF_KINTERSECT, FALSE, TRUE } }, + { "9 Jigsaw Basic", { 9, 1, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } }, + { "9 Jigsaw Basic X", { 9, 1, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, TRUE } }, + { "9 Jigsaw Advanced", { 9, 1, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, FALSE, FALSE } }, +#ifndef SLOW_SYSTEM + { "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } }, + { "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } }, +#endif + }; + + if (i < 0 || i >= lenof(presets)) + return FALSE; + + *name = dupstr(presets[i].title); + *params = dup_params(&presets[i].params); + + return TRUE; +} + +static void decode_params(game_params *ret, char const *string) +{ + int seen_r = FALSE; + + ret->c = ret->r = atoi(string); + ret->xtype = FALSE; + ret->killer = FALSE; + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->r = atoi(string); + seen_r = TRUE; + while (*string && isdigit((unsigned char)*string)) string++; + } + while (*string) { + if (*string == 'j') { + string++; + if (seen_r) + ret->c *= ret->r; + ret->r = 1; + } else if (*string == 'x') { + string++; + ret->xtype = TRUE; + } else if (*string == 'k') { + string++; + ret->killer = TRUE; + } else if (*string == 'r' || *string == 'm' || *string == 'a') { + int sn, sc, sd; + sc = *string++; + if (sc == 'm' && *string == 'd') { + sd = TRUE; + string++; + } else { + sd = FALSE; + } + sn = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (sc == 'm' && sn == 8) + ret->symm = SYMM_REF8; + if (sc == 'm' && sn == 4) + ret->symm = sd ? SYMM_REF4D : SYMM_REF4; + if (sc == 'm' && sn == 2) + ret->symm = sd ? SYMM_REF2D : SYMM_REF2; + if (sc == 'r' && sn == 4) + ret->symm = SYMM_ROT4; + if (sc == 'r' && sn == 2) + ret->symm = SYMM_ROT2; + if (sc == 'a') + ret->symm = SYMM_NONE; + } else if (*string == 'd') { + string++; + if (*string == 't') /* trivial */ + string++, ret->diff = DIFF_BLOCK; + else if (*string == 'b') /* basic */ + string++, ret->diff = DIFF_SIMPLE; + else if (*string == 'i') /* intermediate */ + string++, ret->diff = DIFF_INTERSECT; + else if (*string == 'a') /* advanced */ + string++, ret->diff = DIFF_SET; + else if (*string == 'e') /* extreme */ + string++, ret->diff = DIFF_EXTREME; + else if (*string == 'u') /* unreasonable */ + string++, ret->diff = DIFF_RECURSIVE; + } else + string++; /* eat unknown character */ + } +} + +static char *encode_params(const game_params *params, int full) +{ + char str[80]; + + if (params->r > 1) + sprintf(str, "%dx%d", params->c, params->r); + else + sprintf(str, "%dj", params->c); + if (params->xtype) + strcat(str, "x"); + if (params->killer) + strcat(str, "k"); + + if (full) { + switch (params->symm) { + case SYMM_REF8: strcat(str, "m8"); break; + case SYMM_REF4: strcat(str, "m4"); break; + case SYMM_REF4D: strcat(str, "md4"); break; + case SYMM_REF2: strcat(str, "m2"); break; + case SYMM_REF2D: strcat(str, "md2"); break; + case SYMM_ROT4: strcat(str, "r4"); break; + /* case SYMM_ROT2: strcat(str, "r2"); break; [default] */ + case SYMM_NONE: strcat(str, "a"); break; + } + switch (params->diff) { + /* case DIFF_BLOCK: strcat(str, "dt"); break; [default] */ + case DIFF_SIMPLE: strcat(str, "db"); break; + case DIFF_INTERSECT: strcat(str, "di"); break; + case DIFF_SET: strcat(str, "da"); break; + case DIFF_EXTREME: strcat(str, "de"); break; + case DIFF_RECURSIVE: strcat(str, "du"); break; + } + } + return dupstr(str); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(8, config_item); + + ret[0].name = "Columns of sub-blocks"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->c); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Rows of sub-blocks"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->r); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "\"X\" (require every number in each main diagonal)"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->xtype; + + ret[3].name = "Jigsaw (irregularly shaped sub-blocks)"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = (params->r == 1); + + ret[4].name = "Killer (digit sums)"; + ret[4].type = C_BOOLEAN; + ret[4].sval = NULL; + ret[4].ival = params->killer; + + ret[5].name = "Symmetry"; + ret[5].type = C_CHOICES; + ret[5].sval = ":None:2-way rotation:4-way rotation:2-way mirror:" + "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:" + "8-way mirror"; + ret[5].ival = params->symm; + + ret[6].name = "Difficulty"; + ret[6].type = C_CHOICES; + ret[6].sval = ":Trivial:Basic:Intermediate:Advanced:Extreme:Unreasonable"; + ret[6].ival = params->diff; + + ret[7].name = NULL; + ret[7].type = C_END; + ret[7].sval = NULL; + ret[7].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->c = atoi(cfg[0].sval); + ret->r = atoi(cfg[1].sval); + ret->xtype = cfg[2].ival; + if (cfg[3].ival) { + ret->c *= ret->r; + ret->r = 1; + } + ret->killer = cfg[4].ival; + ret->symm = cfg[5].ival; + ret->diff = cfg[6].ival; + ret->kdiff = DIFF_KINTERSECT; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->c < 2) + return "Both dimensions must be at least 2"; + if (params->c > ORDER_MAX || params->r > ORDER_MAX) + return "Dimensions greater than "STR(ORDER_MAX)" are not supported"; + if ((params->c * params->r) > 31) + return "Unable to support more than 31 distinct symbols in a puzzle"; + if (params->killer && params->c * params->r > 9) + return "Killer puzzle dimensions must be smaller than 10."; + return NULL; +} + +/* + * ---------------------------------------------------------------------- + * Block structure functions. + */ + +static struct block_structure *alloc_block_structure(int c, int r, int area, + int max_nr_squares, + int nr_blocks) +{ + int i; + struct block_structure *b = snew(struct block_structure); + + b->refcount = 1; + b->nr_blocks = nr_blocks; + b->max_nr_squares = max_nr_squares; + b->c = c; b->r = r; b->area = area; + b->whichblock = snewn(area, int); + b->blocks_data = snewn(nr_blocks * max_nr_squares, int); + b->blocks = snewn(nr_blocks, int *); + b->nr_squares = snewn(nr_blocks, int); + + for (i = 0; i < nr_blocks; i++) + b->blocks[i] = b->blocks_data + i*max_nr_squares; + +#ifdef STANDALONE_SOLVER + b->blocknames = (char **)smalloc(c*r*(sizeof(char *)+80)); + for (i = 0; i < c * r; i++) + b->blocknames[i] = NULL; +#endif + return b; +} + +static void free_block_structure(struct block_structure *b) +{ + if (--b->refcount == 0) { + sfree(b->whichblock); + sfree(b->blocks); + sfree(b->blocks_data); +#ifdef STANDALONE_SOLVER + sfree(b->blocknames); +#endif + sfree(b->nr_squares); + sfree(b); + } +} + +static struct block_structure *dup_block_structure(struct block_structure *b) +{ + struct block_structure *nb; + int i; + + nb = alloc_block_structure(b->c, b->r, b->area, b->max_nr_squares, + b->nr_blocks); + memcpy(nb->nr_squares, b->nr_squares, b->nr_blocks * sizeof *b->nr_squares); + memcpy(nb->whichblock, b->whichblock, b->area * sizeof *b->whichblock); + memcpy(nb->blocks_data, b->blocks_data, + b->nr_blocks * b->max_nr_squares * sizeof *b->blocks_data); + for (i = 0; i < b->nr_blocks; i++) + nb->blocks[i] = nb->blocks_data + i*nb->max_nr_squares; + +#ifdef STANDALONE_SOLVER + memcpy(nb->blocknames, b->blocknames, b->c * b->r *(sizeof(char *)+80)); + { + int i; + for (i = 0; i < b->c * b->r; i++) + if (b->blocknames[i] == NULL) + nb->blocknames[i] = NULL; + else + nb->blocknames[i] = ((char *)nb->blocknames) + (b->blocknames[i] - (char *)b->blocknames); + } +#endif + return nb; +} + +static void split_block(struct block_structure *b, int *squares, int nr_squares) +{ + int i, j; + int previous_block = b->whichblock[squares[0]]; + int newblock = b->nr_blocks; + + assert(b->max_nr_squares >= nr_squares); + assert(b->nr_squares[previous_block] > nr_squares); + + b->nr_blocks++; + b->blocks_data = sresize(b->blocks_data, + b->nr_blocks * b->max_nr_squares, int); + b->nr_squares = sresize(b->nr_squares, b->nr_blocks, int); + sfree(b->blocks); + b->blocks = snewn(b->nr_blocks, int *); + for (i = 0; i < b->nr_blocks; i++) + b->blocks[i] = b->blocks_data + i*b->max_nr_squares; + for (i = 0; i < nr_squares; i++) { + assert(b->whichblock[squares[i]] == previous_block); + b->whichblock[squares[i]] = newblock; + b->blocks[newblock][i] = squares[i]; + } + for (i = j = 0; i < b->nr_squares[previous_block]; i++) { + int k; + int sq = b->blocks[previous_block][i]; + for (k = 0; k < nr_squares; k++) + if (squares[k] == sq) + break; + if (k == nr_squares) + b->blocks[previous_block][j++] = sq; + } + b->nr_squares[previous_block] -= nr_squares; + b->nr_squares[newblock] = nr_squares; +} + +static void remove_from_block(struct block_structure *blocks, int b, int n) +{ + int i, j; + blocks->whichblock[n] = -1; + for (i = j = 0; i < blocks->nr_squares[b]; i++) + if (blocks->blocks[b][i] != n) + blocks->blocks[b][j++] = blocks->blocks[b][i]; + assert(j+1 == i); + blocks->nr_squares[b]--; +} + +/* ---------------------------------------------------------------------- + * Solver. + * + * This solver is used for two purposes: + * + to check solubility of a grid as we gradually remove numbers + * from it + * + to solve an externally generated puzzle when the user selects + * `Solve'. + * + * It supports a variety of specific modes of reasoning. By + * enabling or disabling subsets of these modes we can arrange a + * range of difficulty levels. + */ + +/* + * Modes of reasoning currently supported: + * + * - Positional elimination: a number must go in a particular + * square because all the other empty squares in a given + * row/col/blk are ruled out. + * + * - Killer minmax elimination: for killer-type puzzles, a number + * is impossible if choosing it would cause the sum in a killer + * region to be guaranteed to be too large or too small. + * + * - Numeric elimination: a square must have a particular number + * in because all the other numbers that could go in it are + * ruled out. + * + * - Intersectional analysis: given two domains which overlap + * (hence one must be a block, and the other can be a row or + * col), if the possible locations for a particular number in + * one of the domains can be narrowed down to the overlap, then + * that number can be ruled out everywhere but the overlap in + * the other domain too. + * + * - Set elimination: if there is a subset of the empty squares + * within a domain such that the union of the possible numbers + * in that subset has the same size as the subset itself, then + * those numbers can be ruled out everywhere else in the domain. + * (For example, if there are five empty squares and the + * possible numbers in each are 12, 23, 13, 134 and 1345, then + * the first three empty squares form such a subset: the numbers + * 1, 2 and 3 _must_ be in those three squares in some + * permutation, and hence we can deduce none of them can be in + * the fourth or fifth squares.) + * + You can also see this the other way round, concentrating + * on numbers rather than squares: if there is a subset of + * the unplaced numbers within a domain such that the union + * of all their possible positions has the same size as the + * subset itself, then all other numbers can be ruled out for + * those positions. However, it turns out that this is + * exactly equivalent to the first formulation at all times: + * there is a 1-1 correspondence between suitable subsets of + * the unplaced numbers and suitable subsets of the unfilled + * places, found by taking the _complement_ of the union of + * the numbers' possible positions (or the spaces' possible + * contents). + * + * - Forcing chains (see comment for solver_forcing().) + * + * - Recursion. If all else fails, we pick one of the currently + * most constrained empty squares and take a random guess at its + * contents, then continue solving on that basis and see if we + * get any further. + */ + +struct solver_usage { + int cr; + struct block_structure *blocks, *kblocks, *extra_cages; + /* + * We set up a cubic array, indexed by x, y and digit; each + * element of this array is TRUE or FALSE according to whether + * or not that digit _could_ in principle go in that position. + * + * The way to index this array is cube[(y*cr+x)*cr+n-1]; there + * are macros below to help with this. + */ + unsigned char *cube; + /* + * This is the grid in which we write down our final + * deductions. y-coordinates in here are _not_ transformed. + */ + digit *grid; + /* + * For killer-type puzzles, kclues holds the secondary clue for + * each cage. For derived cages, the clue is in extra_clues. + */ + digit *kclues, *extra_clues; + /* + * Now we keep track, at a slightly higher level, of what we + * have yet to work out, to prevent doing the same deduction + * many times. + */ + /* row[y*cr+n-1] TRUE if digit n has been placed in row y */ + unsigned char *row; + /* col[x*cr+n-1] TRUE if digit n has been placed in row x */ + unsigned char *col; + /* blk[i*cr+n-1] TRUE if digit n has been placed in block i */ + unsigned char *blk; + /* diag[i*cr+n-1] TRUE if digit n has been placed in diagonal i */ + unsigned char *diag; /* diag 0 is \, 1 is / */ + + int *regions; + int nr_regions; + int **sq2region; +}; +#define cubepos2(xy,n) ((xy)*usage->cr+(n)-1) +#define cubepos(x,y,n) cubepos2((y)*usage->cr+(x),n) +#define cube(x,y,n) (usage->cube[cubepos(x,y,n)]) +#define cube2(xy,n) (usage->cube[cubepos2(xy,n)]) + +#define ondiag0(xy) ((xy) % (cr+1) == 0) +#define ondiag1(xy) ((xy) % (cr-1) == 0 && (xy) > 0 && (xy) < cr*cr-1) +#define diag0(i) ((i) * (cr+1)) +#define diag1(i) ((i+1) * (cr-1)) + +/* + * Function called when we are certain that a particular square has + * a particular number in it. The y-coordinate passed in here is + * transformed. + */ +static void solver_place(struct solver_usage *usage, int x, int y, int n) +{ + int cr = usage->cr; + int sqindex = y*cr+x; + int i, bi; + + assert(cube(x,y,n)); + + /* + * Rule out all other numbers in this square. + */ + for (i = 1; i <= cr; i++) + if (i != n) + cube(x,y,i) = FALSE; + + /* + * Rule out this number in all other positions in the row. + */ + for (i = 0; i < cr; i++) + if (i != y) + cube(x,i,n) = FALSE; + + /* + * Rule out this number in all other positions in the column. + */ + for (i = 0; i < cr; i++) + if (i != x) + cube(i,y,n) = FALSE; + + /* + * Rule out this number in all other positions in the block. + */ + bi = usage->blocks->whichblock[sqindex]; + for (i = 0; i < cr; i++) { + int bp = usage->blocks->blocks[bi][i]; + if (bp != sqindex) + cube2(bp,n) = FALSE; + } + + /* + * Enter the number in the result grid. + */ + usage->grid[sqindex] = n; + + /* + * Cross out this number from the list of numbers left to place + * in its row, its column and its block. + */ + usage->row[y*cr+n-1] = usage->col[x*cr+n-1] = + usage->blk[bi*cr+n-1] = TRUE; + + if (usage->diag) { + if (ondiag0(sqindex)) { + for (i = 0; i < cr; i++) + if (diag0(i) != sqindex) + cube2(diag0(i),n) = FALSE; + usage->diag[n-1] = TRUE; + } + if (ondiag1(sqindex)) { + for (i = 0; i < cr; i++) + if (diag1(i) != sqindex) + cube2(diag1(i),n) = FALSE; + usage->diag[cr+n-1] = TRUE; + } + } +} + +#if defined STANDALONE_SOLVER && defined __GNUC__ +/* + * Forward-declare the functions taking printf-like format arguments + * with __attribute__((format)) so as to ensure the argument syntax + * gets debugged. + */ +struct solver_scratch; +static int solver_elim(struct solver_usage *usage, int *indices, + char *fmt, ...) __attribute__((format(printf,3,4))); +static int solver_intersect(struct solver_usage *usage, + int *indices1, int *indices2, char *fmt, ...) + __attribute__((format(printf,4,5))); +static int solver_set(struct solver_usage *usage, + struct solver_scratch *scratch, + int *indices, char *fmt, ...) + __attribute__((format(printf,4,5))); +#endif + +static int solver_elim(struct solver_usage *usage, int *indices +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ) +{ + int cr = usage->cr; + int fpos, m, i; + + /* + * Count the number of set bits within this section of the + * cube. + */ + m = 0; + fpos = -1; + for (i = 0; i < cr; i++) + if (usage->cube[indices[i]]) { + fpos = indices[i]; + m++; + } + + if (m == 1) { + int x, y, n; + assert(fpos >= 0); + + n = 1 + fpos % cr; + x = fpos / cr; + y = x / cr; + x %= cr; + + if (!usage->grid[y*cr+x]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s placing %d at (%d,%d)\n", + solver_recurse_depth*4, "", n, 1+x, 1+y); + } +#endif + solver_place(usage, x, y, n); + return +1; + } + } else if (m == 0) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s no possibilities available\n", + solver_recurse_depth*4, ""); + } +#endif + return -1; + } + + return 0; +} + +static int solver_intersect(struct solver_usage *usage, + int *indices1, int *indices2 +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ) +{ + int cr = usage->cr; + int ret, i, j; + + /* + * Loop over the first domain and see if there's any set bit + * not also in the second. + */ + for (i = j = 0; i < cr; i++) { + int p = indices1[i]; + while (j < cr && indices2[j] < p) + j++; + if (usage->cube[p]) { + if (j < cr && indices2[j] == p) + continue; /* both domains contain this index */ + else + return 0; /* there is, so we can't deduce */ + } + } + + /* + * We have determined that all set bits in the first domain are + * within its overlap with the second. So loop over the second + * domain and remove all set bits that aren't also in that + * overlap; return +1 iff we actually _did_ anything. + */ + ret = 0; + for (i = j = 0; i < cr; i++) { + int p = indices2[i]; + while (j < cr && indices1[j] < p) + j++; + if (usage->cube[p] && (j >= cr || indices1[j] != p)) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int px, py, pn; + + if (!ret) { + va_list ap; + printf("%*s", solver_recurse_depth*4, ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n"); + } + + pn = 1 + p % cr; + px = p / cr; + py = px / cr; + px %= cr; + + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", pn, 1+px, 1+py); + } +#endif + ret = +1; /* we did something */ + usage->cube[p] = 0; + } + } + + return ret; +} + +struct solver_scratch { + unsigned char *grid, *rowidx, *colidx, *set; + int *neighbours, *bfsqueue; + int *indexlist, *indexlist2; +#ifdef STANDALONE_SOLVER + int *bfsprev; +#endif +}; + +static int solver_set(struct solver_usage *usage, + struct solver_scratch *scratch, + int *indices +#ifdef STANDALONE_SOLVER + , char *fmt, ... +#endif + ) +{ + int cr = usage->cr; + int i, j, n, count; + unsigned char *grid = scratch->grid; + unsigned char *rowidx = scratch->rowidx; + unsigned char *colidx = scratch->colidx; + unsigned char *set = scratch->set; + + /* + * We are passed a cr-by-cr matrix of booleans. Our first job + * is to winnow it by finding any definite placements - i.e. + * any row with a solitary 1 - and discarding that row and the + * column containing the 1. + */ + memset(rowidx, TRUE, cr); + memset(colidx, TRUE, cr); + for (i = 0; i < cr; i++) { + int count = 0, first = -1; + for (j = 0; j < cr; j++) + if (usage->cube[indices[i*cr+j]]) + first = j, count++; + + /* + * If count == 0, then there's a row with no 1s at all and + * the puzzle is internally inconsistent. However, we ought + * to have caught this already during the simpler reasoning + * methods, so we can safely fail an assertion if we reach + * this point here. + */ + assert(count > 0); + if (count == 1) + rowidx[i] = colidx[first] = FALSE; + } + + /* + * Convert each of rowidx/colidx from a list of 0s and 1s to a + * list of the indices of the 1s. + */ + for (i = j = 0; i < cr; i++) + if (rowidx[i]) + rowidx[j++] = i; + n = j; + for (i = j = 0; i < cr; i++) + if (colidx[i]) + colidx[j++] = i; + assert(n == j); + + /* + * And create the smaller matrix. + */ + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + grid[i*cr+j] = usage->cube[indices[rowidx[i]*cr+colidx[j]]]; + + /* + * Having done that, we now have a matrix in which every row + * has at least two 1s in. Now we search to see if we can find + * a rectangle of zeroes (in the set-theoretic sense of + * `rectangle', i.e. a subset of rows crossed with a subset of + * columns) whose width and height add up to n. + */ + + memset(set, 0, n); + count = 0; + while (1) { + /* + * We have a candidate set. If its size is <=1 or >=n-1 + * then we move on immediately. + */ + if (count > 1 && count < n-1) { + /* + * The number of rows we need is n-count. See if we can + * find that many rows which each have a zero in all + * the positions listed in `set'. + */ + int rows = 0; + for (i = 0; i < n; i++) { + int ok = TRUE; + for (j = 0; j < n; j++) + if (set[j] && grid[i*cr+j]) { + ok = FALSE; + break; + } + if (ok) + rows++; + } + + /* + * We expect never to be able to get _more_ than + * n-count suitable rows: this would imply that (for + * example) there are four numbers which between them + * have at most three possible positions, and hence it + * indicates a faulty deduction before this point or + * even a bogus clue. + */ + if (rows > n - count) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + va_list ap; + printf("%*s", solver_recurse_depth*4, + ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n%*s contradiction reached\n", + solver_recurse_depth*4, ""); + } +#endif + return -1; + } + + if (rows >= n - count) { + int progress = FALSE; + + /* + * We've got one! Now, for each row which _doesn't_ + * satisfy the criterion, eliminate all its set + * bits in the positions _not_ listed in `set'. + * Return +1 (meaning progress has been made) if we + * successfully eliminated anything at all. + * + * This involves referring back through + * rowidx/colidx in order to work out which actual + * positions in the cube to meddle with. + */ + for (i = 0; i < n; i++) { + int ok = TRUE; + for (j = 0; j < n; j++) + if (set[j] && grid[i*cr+j]) { + ok = FALSE; + break; + } + if (!ok) { + for (j = 0; j < n; j++) + if (!set[j] && grid[i*cr+j]) { + int fpos = indices[rowidx[i]*cr+colidx[j]]; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int px, py, pn; + + if (!progress) { + va_list ap; + printf("%*s", solver_recurse_depth*4, + ""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(":\n"); + } + + pn = 1 + fpos % cr; + px = fpos / cr; + py = px / cr; + px %= cr; + + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", + pn, 1+px, 1+py); + } +#endif + progress = TRUE; + usage->cube[fpos] = FALSE; + } + } + } + + if (progress) { + return +1; + } + } + } + + /* + * Binary increment: change the rightmost 0 to a 1, and + * change all 1s to the right of it to 0s. + */ + i = n; + while (i > 0 && set[i-1]) + set[--i] = 0, count--; + if (i > 0) + set[--i] = 1, count++; + else + break; /* done */ + } + + return 0; +} + +/* + * Look for forcing chains. A forcing chain is a path of + * pairwise-exclusive squares (i.e. each pair of adjacent squares + * in the path are in the same row, column or block) with the + * following properties: + * + * (a) Each square on the path has precisely two possible numbers. + * + * (b) Each pair of squares which are adjacent on the path share + * at least one possible number in common. + * + * (c) Each square in the middle of the path shares _both_ of its + * numbers with at least one of its neighbours (not the same + * one with both neighbours). + * + * These together imply that at least one of the possible number + * choices at one end of the path forces _all_ the rest of the + * numbers along the path. In order to make real use of this, we + * need further properties: + * + * (c) Ruling out some number N from the square at one end of the + * path forces the square at the other end to take the same + * number N. + * + * (d) The two end squares are both in line with some third + * square. + * + * (e) That third square currently has N as a possibility. + * + * If we can find all of that lot, we can deduce that at least one + * of the two ends of the forcing chain has number N, and that + * therefore the mutually adjacent third square does not. + * + * To find forcing chains, we're going to start a bfs at each + * suitable square, once for each of its two possible numbers. + */ +static int solver_forcing(struct solver_usage *usage, + struct solver_scratch *scratch) +{ + int cr = usage->cr; + int *bfsqueue = scratch->bfsqueue; +#ifdef STANDALONE_SOLVER + int *bfsprev = scratch->bfsprev; +#endif + unsigned char *number = scratch->grid; + int *neighbours = scratch->neighbours; + int x, y; + + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) { + int count, t, n; + + /* + * If this square doesn't have exactly two candidate + * numbers, don't try it. + * + * In this loop we also sum the candidate numbers, + * which is a nasty hack to allow us to quickly find + * `the other one' (since we will shortly know there + * are exactly two). + */ + for (count = t = 0, n = 1; n <= cr; n++) + if (cube(x, y, n)) + count++, t += n; + if (count != 2) + continue; + + /* + * Now attempt a bfs for each candidate. + */ + for (n = 1; n <= cr; n++) + if (cube(x, y, n)) { + int orign, currn, head, tail; + + /* + * Begin a bfs. + */ + orign = n; + + memset(number, cr+1, cr*cr); + head = tail = 0; + bfsqueue[tail++] = y*cr+x; +#ifdef STANDALONE_SOLVER + bfsprev[y*cr+x] = -1; +#endif + number[y*cr+x] = t - n; + + while (head < tail) { + int xx, yy, nneighbours, xt, yt, i; + + xx = bfsqueue[head++]; + yy = xx / cr; + xx %= cr; + + currn = number[yy*cr+xx]; + + /* + * Find neighbours of yy,xx. + */ + nneighbours = 0; + for (yt = 0; yt < cr; yt++) + neighbours[nneighbours++] = yt*cr+xx; + for (xt = 0; xt < cr; xt++) + neighbours[nneighbours++] = yy*cr+xt; + xt = usage->blocks->whichblock[yy*cr+xx]; + for (yt = 0; yt < cr; yt++) + neighbours[nneighbours++] = usage->blocks->blocks[xt][yt]; + if (usage->diag) { + int sqindex = yy*cr+xx; + if (ondiag0(sqindex)) { + for (i = 0; i < cr; i++) + neighbours[nneighbours++] = diag0(i); + } + if (ondiag1(sqindex)) { + for (i = 0; i < cr; i++) + neighbours[nneighbours++] = diag1(i); + } + } + + /* + * Try visiting each of those neighbours. + */ + for (i = 0; i < nneighbours; i++) { + int cc, tt, nn; + + xt = neighbours[i] % cr; + yt = neighbours[i] / cr; + + /* + * We need this square to not be + * already visited, and to include + * currn as a possible number. + */ + if (number[yt*cr+xt] <= cr) + continue; + if (!cube(xt, yt, currn)) + continue; + + /* + * Don't visit _this_ square a second + * time! + */ + if (xt == xx && yt == yy) + continue; + + /* + * To continue with the bfs, we need + * this square to have exactly two + * possible numbers. + */ + for (cc = tt = 0, nn = 1; nn <= cr; nn++) + if (cube(xt, yt, nn)) + cc++, tt += nn; + if (cc == 2) { + bfsqueue[tail++] = yt*cr+xt; +#ifdef STANDALONE_SOLVER + bfsprev[yt*cr+xt] = yy*cr+xx; +#endif + number[yt*cr+xt] = tt - currn; + } + + /* + * One other possibility is that this + * might be the square in which we can + * make a real deduction: if it's + * adjacent to x,y, and currn is equal + * to the original number we ruled out. + */ + if (currn == orign && + (xt == x || yt == y || + (usage->blocks->whichblock[yt*cr+xt] == usage->blocks->whichblock[y*cr+x]) || + (usage->diag && ((ondiag0(yt*cr+xt) && ondiag0(y*cr+x)) || + (ondiag1(yt*cr+xt) && ondiag1(y*cr+x)))))) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *sep = ""; + int xl, yl; + printf("%*sforcing chain, %d at ends of ", + solver_recurse_depth*4, "", orign); + xl = xx; + yl = yy; + while (1) { + printf("%s(%d,%d)", sep, 1+xl, + 1+yl); + xl = bfsprev[yl*cr+xl]; + if (xl < 0) + break; + yl = xl / cr; + xl %= cr; + sep = "-"; + } + printf("\n%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", + orign, 1+xt, 1+yt); + } +#endif + cube(xt, yt, orign) = FALSE; + return 1; + } + } + } + } + } + + return 0; +} + +static int solver_killer_minmax(struct solver_usage *usage, + struct block_structure *cages, digit *clues, + int b +#ifdef STANDALONE_SOLVER + , const char *extra +#endif + ) +{ + int cr = usage->cr; + int i; + int ret = 0; + int nsquares = cages->nr_squares[b]; + + if (clues[b] == 0) + return 0; + + for (i = 0; i < nsquares; i++) { + int n, x = cages->blocks[b][i]; + + for (n = 1; n <= cr; n++) + if (cube2(x, n)) { + int maxval = 0, minval = 0; + int j; + for (j = 0; j < nsquares; j++) { + int m; + int y = cages->blocks[b][j]; + if (i == j) + continue; + for (m = 1; m <= cr; m++) + if (cube2(y, m)) { + minval += m; + break; + } + for (m = cr; m > 0; m--) + if (cube2(y, m)) { + maxval += m; + break; + } + } + if (maxval + n < clues[b]) { + cube2(x, n) = FALSE; + ret = 1; +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s ruling out %d at (%d,%d) as too low %s\n", + solver_recurse_depth*4, "killer minmax analysis", + n, 1 + x%cr, 1 + x/cr, extra); +#endif + } + if (minval + n > clues[b]) { + cube2(x, n) = FALSE; + ret = 1; +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s ruling out %d at (%d,%d) as too high %s\n", + solver_recurse_depth*4, "killer minmax analysis", + n, 1 + x%cr, 1 + x/cr, extra); +#endif + } + } + } + return ret; +} + +static int solver_killer_sums(struct solver_usage *usage, int b, + struct block_structure *cages, int clue, + int cage_is_region +#ifdef STANDALONE_SOLVER + , const char *cage_type +#endif + ) +{ + int cr = usage->cr; + int i, ret, max_sums; + int nsquares = cages->nr_squares[b]; + unsigned long *sumbits, possible_addends; + + if (clue == 0) { + assert(nsquares == 0); + return 0; + } + assert(nsquares > 0); + + if (nsquares < 2 || nsquares > 4) + return 0; + + if (!cage_is_region) { + int known_row = -1, known_col = -1, known_block = -1; + /* + * Verify that the cage lies entirely within one region, + * so that using the precomputed sums is valid. + */ + for (i = 0; i < nsquares; i++) { + int x = cages->blocks[b][i]; + + assert(usage->grid[x] == 0); + + if (i == 0) { + known_row = x/cr; + known_col = x%cr; + known_block = usage->blocks->whichblock[x]; + } else { + if (known_row != x/cr) + known_row = -1; + if (known_col != x%cr) + known_col = -1; + if (known_block != usage->blocks->whichblock[x]) + known_block = -1; + } + } + if (known_block == -1 && known_col == -1 && known_row == -1) + return 0; + } + if (nsquares == 2) { + if (clue < 3 || clue > 17) + return -1; + + sumbits = sum_bits2[clue]; + max_sums = MAX_2SUMS; + } else if (nsquares == 3) { + if (clue < 6 || clue > 24) + return -1; + + sumbits = sum_bits3[clue]; + max_sums = MAX_3SUMS; + } else { + if (clue < 10 || clue > 30) + return -1; + + sumbits = sum_bits4[clue]; + max_sums = MAX_4SUMS; + } + /* + * For every possible way to get the sum, see if there is + * one square in the cage that disallows all the required + * addends. If we find one such square, this way to compute + * the sum is impossible. + */ + possible_addends = 0; + for (i = 0; i < max_sums; i++) { + int j; + unsigned long bits = sumbits[i]; + + if (bits == 0) + break; + + for (j = 0; j < nsquares; j++) { + int n; + unsigned long square_bits = bits; + int x = cages->blocks[b][j]; + for (n = 1; n <= cr; n++) + if (!cube2(x, n)) + square_bits &= ~(1L << n); + if (square_bits == 0) { + break; + } + } + if (j == nsquares) + possible_addends |= bits; + } + /* + * Now we know which addends can possibly be used to + * compute the sum. Remove all other digits from the + * set of possibilities. + */ + if (possible_addends == 0) + return -1; + + ret = 0; + for (i = 0; i < nsquares; i++) { + int n; + int x = cages->blocks[b][i]; + for (n = 1; n <= cr; n++) { + if (!cube2(x, n)) + continue; + if ((possible_addends & (1 << n)) == 0) { + cube2(x, n) = FALSE; + ret = 1; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*s using %s\n", + solver_recurse_depth*4, "killer sums analysis", + cage_type); + printf("%*s ruling out %d at (%d,%d) due to impossible %d-sum\n", + solver_recurse_depth*4, "", + n, 1 + x%cr, 1 + x/cr, nsquares); + } +#endif + } + } + } + return ret; +} + +static int filter_whole_cages(struct solver_usage *usage, int *squares, int n, + int *filtered_sum) +{ + int b, i, j, off; + *filtered_sum = 0; + + /* First, filter squares with a clue. */ + for (i = j = 0; i < n; i++) + if (usage->grid[squares[i]]) + *filtered_sum += usage->grid[squares[i]]; + else + squares[j++] = squares[i]; + n = j; + + /* + * Filter all cages that are covered entirely by the list of + * squares. + */ + off = 0; + for (b = 0; b < usage->kblocks->nr_blocks && off < n; b++) { + int b_squares = usage->kblocks->nr_squares[b]; + int matched = 0; + + if (b_squares == 0) + continue; + + /* + * Find all squares of block b that lie in our list, + * and make them contiguous at off, which is the current position + * in the output list. + */ + for (i = 0; i < b_squares; i++) { + for (j = off; j < n; j++) + if (squares[j] == usage->kblocks->blocks[b][i]) { + int t = squares[off + matched]; + squares[off + matched] = squares[j]; + squares[j] = t; + matched++; + break; + } + } + /* If so, filter out all squares of b from the list. */ + if (matched != usage->kblocks->nr_squares[b]) { + off += matched; + continue; + } + memmove(squares + off, squares + off + matched, + (n - off - matched) * sizeof *squares); + n -= matched; + + *filtered_sum += usage->kclues[b]; + } + assert(off == n); + return off; +} + +static struct solver_scratch *solver_new_scratch(struct solver_usage *usage) +{ + struct solver_scratch *scratch = snew(struct solver_scratch); + int cr = usage->cr; + scratch->grid = snewn(cr*cr, unsigned char); + scratch->rowidx = snewn(cr, unsigned char); + scratch->colidx = snewn(cr, unsigned char); + scratch->set = snewn(cr, unsigned char); + scratch->neighbours = snewn(5*cr, int); + scratch->bfsqueue = snewn(cr*cr, int); +#ifdef STANDALONE_SOLVER + scratch->bfsprev = snewn(cr*cr, int); +#endif + scratch->indexlist = snewn(cr*cr, int); /* used for set elimination */ + scratch->indexlist2 = snewn(cr, int); /* only used for intersect() */ + return scratch; +} + +static void solver_free_scratch(struct solver_scratch *scratch) +{ +#ifdef STANDALONE_SOLVER + sfree(scratch->bfsprev); +#endif + sfree(scratch->bfsqueue); + sfree(scratch->neighbours); + sfree(scratch->set); + sfree(scratch->colidx); + sfree(scratch->rowidx); + sfree(scratch->grid); + sfree(scratch->indexlist); + sfree(scratch->indexlist2); + sfree(scratch); +} + +/* + * Used for passing information about difficulty levels between the solver + * and its callers. + */ +struct difficulty { + /* Maximum levels allowed. */ + int maxdiff, maxkdiff; + /* Levels reached by the solver. */ + int diff, kdiff; +}; + +static void solver(int cr, struct block_structure *blocks, + struct block_structure *kblocks, int xtype, + digit *grid, digit *kgrid, struct difficulty *dlev) +{ + struct solver_usage *usage; + struct solver_scratch *scratch; + int x, y, b, i, n, ret; + int diff = DIFF_BLOCK; + int kdiff = DIFF_KSINGLE; + + /* + * Set up a usage structure as a clean slate (everything + * possible). + */ + usage = snew(struct solver_usage); + usage->cr = cr; + usage->blocks = blocks; + if (kblocks) { + usage->kblocks = dup_block_structure(kblocks); + usage->extra_cages = alloc_block_structure (kblocks->c, kblocks->r, + cr * cr, cr, cr * cr); + usage->extra_clues = snewn(cr*cr, digit); + } else { + usage->kblocks = usage->extra_cages = NULL; + usage->extra_clues = NULL; + } + usage->cube = snewn(cr*cr*cr, unsigned char); + usage->grid = grid; /* write straight back to the input */ + if (kgrid) { + int nclues; + + assert(kblocks); + nclues = kblocks->nr_blocks; + /* + * Allow for expansion of the killer regions, the absolute + * limit is obviously one region per square. + */ + usage->kclues = snewn(cr*cr, digit); + for (i = 0; i < nclues; i++) { + for (n = 0; n < kblocks->nr_squares[i]; n++) + if (kgrid[kblocks->blocks[i][n]] != 0) + usage->kclues[i] = kgrid[kblocks->blocks[i][n]]; + assert(usage->kclues[i] > 0); + } + memset(usage->kclues + nclues, 0, cr*cr - nclues); + } else { + usage->kclues = NULL; + } + + memset(usage->cube, TRUE, cr*cr*cr); + + usage->row = snewn(cr * cr, unsigned char); + usage->col = snewn(cr * cr, unsigned char); + usage->blk = snewn(cr * cr, unsigned char); + memset(usage->row, FALSE, cr * cr); + memset(usage->col, FALSE, cr * cr); + memset(usage->blk, FALSE, cr * cr); + + if (xtype) { + usage->diag = snewn(cr * 2, unsigned char); + memset(usage->diag, FALSE, cr * 2); + } else + usage->diag = NULL; + + usage->nr_regions = cr * 3 + (xtype ? 2 : 0); + usage->regions = snewn(cr * usage->nr_regions, int); + usage->sq2region = snewn(cr * cr * 3, int *); + + for (n = 0; n < cr; n++) { + for (i = 0; i < cr; i++) { + x = n*cr+i; + y = i*cr+n; + b = usage->blocks->blocks[n][i]; + usage->regions[cr*n*3 + i] = x; + usage->regions[cr*n*3 + cr + i] = y; + usage->regions[cr*n*3 + 2*cr + i] = b; + usage->sq2region[x*3] = usage->regions + cr*n*3; + usage->sq2region[y*3 + 1] = usage->regions + cr*n*3 + cr; + usage->sq2region[b*3 + 2] = usage->regions + cr*n*3 + 2*cr; + } + } + + scratch = solver_new_scratch(usage); + + /* + * Place all the clue numbers we are given. + */ + for (x = 0; x < cr; x++) + for (y = 0; y < cr; y++) { + int n = grid[y*cr+x]; + if (n) { + if (!cube(x,y,n)) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + solver_place(usage, x, y, grid[y*cr+x]); + } + } + + /* + * Now loop over the grid repeatedly trying all permitted modes + * of reasoning. The loop terminates if we complete an + * iteration without making any progress; we then return + * failure or success depending on whether the grid is full or + * not. + */ + while (1) { + /* + * I'd like to write `continue;' inside each of the + * following loops, so that the solver returns here after + * making some progress. However, I can't specify that I + * want to continue an outer loop rather than the innermost + * one, so I'm apologetically resorting to a goto. + */ + cont: + + /* + * Blockwise positional elimination. + */ + for (b = 0; b < cr; b++) + for (n = 1; n <= cr; n++) + if (!usage->blk[b*cr+n-1]) { + for (i = 0; i < cr; i++) + scratch->indexlist[i] = cubepos2(usage->blocks->blocks[b][i],n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %d in block %s", n, + usage->blocks->blocknames[b] +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_BLOCK); + goto cont; + } + } + + if (usage->kclues != NULL) { + int changed = FALSE; + + /* + * First, bring the kblocks into a more useful form: remove + * all filled-in squares, and reduce the sum by their values. + * Walk in reverse order, since otherwise remove_from_block + * can move element past our loop counter. + */ + for (b = 0; b < usage->kblocks->nr_blocks; b++) + for (i = usage->kblocks->nr_squares[b] -1; i >= 0; i--) { + int x = usage->kblocks->blocks[b][i]; + int t = usage->grid[x]; + + if (t == 0) + continue; + remove_from_block(usage->kblocks, b, x); + if (t > usage->kclues[b]) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + usage->kclues[b] -= t; + /* + * Since cages are regions, this tells us something + * about the other squares in the cage. + */ + for (n = 0; n < usage->kblocks->nr_squares[b]; n++) { + cube2(usage->kblocks->blocks[b][n], t) = FALSE; + } + } + + /* + * The most trivial kind of solver for killer puzzles: fill + * single-square cages. + */ + for (b = 0; b < usage->kblocks->nr_blocks; b++) { + int squares = usage->kblocks->nr_squares[b]; + if (squares == 1) { + int v = usage->kclues[b]; + if (v < 1 || v > cr) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + x = usage->kblocks->blocks[b][0] % cr; + y = usage->kblocks->blocks[b][0] / cr; + if (!cube(x, y, v)) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + solver_place(usage, x, y, v); + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*s placing %d at (%d,%d)\n", + solver_recurse_depth*4, "killer single-square cage", + v, 1 + x%cr, 1 + x/cr); + } +#endif + changed = TRUE; + } + } + + if (changed) { + kdiff = max(kdiff, DIFF_KSINGLE); + goto cont; + } + } + if (dlev->maxkdiff >= DIFF_KINTERSECT && usage->kclues != NULL) { + int changed = FALSE; + /* + * Now, create the extra_cages information. Every full region + * (row, column, or block) has the same sum total (45 for 3x3 + * puzzles. After we try to cover these regions with cages that + * lie entirely within them, any squares that remain must bring + * the total to this known value, and so they form additional + * cages which aren't immediately evident in the displayed form + * of the puzzle. + */ + usage->extra_cages->nr_blocks = 0; + for (i = 0; i < 3; i++) { + for (n = 0; n < cr; n++) { + int *region = usage->regions + cr*n*3 + i*cr; + int sum = cr * (cr + 1) / 2; + int nsquares = cr; + int filtered; + int n_extra = usage->extra_cages->nr_blocks; + int *extra_list = usage->extra_cages->blocks[n_extra]; + memcpy(extra_list, region, cr * sizeof *extra_list); + + nsquares = filter_whole_cages(usage, extra_list, nsquares, &filtered); + sum -= filtered; + if (nsquares == cr || nsquares == 0) + continue; + if (dlev->maxdiff >= DIFF_RECURSIVE) { + if (sum <= 0) { + dlev->diff = DIFF_IMPOSSIBLE; + goto got_result; + } + } + assert(sum > 0); + + if (nsquares == 1) { + if (sum > cr) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + x = extra_list[0] % cr; + y = extra_list[0] / cr; + if (!cube(x, y, sum)) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + solver_place(usage, x, y, sum); + changed = TRUE; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*s placing %d at (%d,%d)\n", + solver_recurse_depth*4, "killer single-square deduced cage", + sum, 1 + x, 1 + y); + } +#endif + } + + b = usage->kblocks->whichblock[extra_list[0]]; + for (x = 1; x < nsquares; x++) + if (usage->kblocks->whichblock[extra_list[x]] != b) + break; + if (x == nsquares) { + assert(usage->kblocks->nr_squares[b] > nsquares); + split_block(usage->kblocks, extra_list, nsquares); + assert(usage->kblocks->nr_squares[usage->kblocks->nr_blocks - 1] == nsquares); + usage->kclues[usage->kblocks->nr_blocks - 1] = sum; + usage->kclues[b] -= sum; + } else { + usage->extra_cages->nr_squares[n_extra] = nsquares; + usage->extra_cages->nr_blocks++; + usage->extra_clues[n_extra] = sum; + } + } + } + if (changed) { + kdiff = max(kdiff, DIFF_KINTERSECT); + goto cont; + } + } + + /* + * Another simple killer-type elimination. For every square in a + * cage, find the minimum and maximum possible sums of all the + * other squares in the same cage, and rule out possibilities + * for the given square based on whether they are guaranteed to + * cause the sum to be either too high or too low. + * This is a special case of trying all possible sums across a + * region, which is a recursive algorithm. We should probably + * implement it for a higher difficulty level. + */ + if (dlev->maxkdiff >= DIFF_KMINMAX && usage->kclues != NULL) { + int changed = FALSE; + for (b = 0; b < usage->kblocks->nr_blocks; b++) { + int ret = solver_killer_minmax(usage, usage->kblocks, + usage->kclues, b +#ifdef STANDALONE_SOLVER + , "" +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) + changed = TRUE; + } + for (b = 0; b < usage->extra_cages->nr_blocks; b++) { + int ret = solver_killer_minmax(usage, usage->extra_cages, + usage->extra_clues, b +#ifdef STANDALONE_SOLVER + , "using deduced cages" +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) + changed = TRUE; + } + if (changed) { + kdiff = max(kdiff, DIFF_KMINMAX); + goto cont; + } + } + + /* + * Try to use knowledge of which numbers can be used to generate + * a given sum. + * This can only be used if a cage lies entirely within a region. + */ + if (dlev->maxkdiff >= DIFF_KSUMS && usage->kclues != NULL) { + int changed = FALSE; + + for (b = 0; b < usage->kblocks->nr_blocks; b++) { + int ret = solver_killer_sums(usage, b, usage->kblocks, + usage->kclues[b], TRUE +#ifdef STANDALONE_SOLVER + , "regular clues" +#endif + ); + if (ret > 0) { + changed = TRUE; + kdiff = max(kdiff, DIFF_KSUMS); + } else if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + } + + for (b = 0; b < usage->extra_cages->nr_blocks; b++) { + int ret = solver_killer_sums(usage, b, usage->extra_cages, + usage->extra_clues[b], FALSE +#ifdef STANDALONE_SOLVER + , "deduced clues" +#endif + ); + if (ret > 0) { + changed = TRUE; + kdiff = max(kdiff, DIFF_KSUMS); + } else if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } + } + + if (changed) + goto cont; + } + + if (dlev->maxdiff <= DIFF_BLOCK) + break; + + /* + * Row-wise positional elimination. + */ + for (y = 0; y < cr; y++) + for (n = 1; n <= cr; n++) + if (!usage->row[y*cr+n-1]) { + for (x = 0; x < cr; x++) + scratch->indexlist[x] = cubepos(x, y, n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %d in row %d", n, 1+y +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SIMPLE); + goto cont; + } + } + /* + * Column-wise positional elimination. + */ + for (x = 0; x < cr; x++) + for (n = 1; n <= cr; n++) + if (!usage->col[x*cr+n-1]) { + for (y = 0; y < cr; y++) + scratch->indexlist[y] = cubepos(x, y, n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %d in column %d", n, 1+x +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SIMPLE); + goto cont; + } + } + + /* + * X-diagonal positional elimination. + */ + if (usage->diag) { + for (n = 1; n <= cr; n++) + if (!usage->diag[n-1]) { + for (i = 0; i < cr; i++) + scratch->indexlist[i] = cubepos2(diag0(i), n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %d in \\-diagonal", n +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SIMPLE); + goto cont; + } + } + for (n = 1; n <= cr; n++) + if (!usage->diag[cr+n-1]) { + for (i = 0; i < cr; i++) + scratch->indexlist[i] = cubepos2(diag1(i), n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional elimination," + " %d in /-diagonal", n +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SIMPLE); + goto cont; + } + } + } + + /* + * Numeric elimination. + */ + for (x = 0; x < cr; x++) + for (y = 0; y < cr; y++) + if (!usage->grid[y*cr+x]) { + for (n = 1; n <= cr; n++) + scratch->indexlist[n-1] = cubepos(x, y, n); + ret = solver_elim(usage, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "numeric elimination at (%d,%d)", + 1+x, 1+y +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SIMPLE); + goto cont; + } + } + + if (dlev->maxdiff <= DIFF_SIMPLE) + break; + + /* + * Intersectional analysis, rows vs blocks. + */ + for (y = 0; y < cr; y++) + for (b = 0; b < cr; b++) + for (n = 1; n <= cr; n++) { + if (usage->row[y*cr+n-1] || + usage->blk[b*cr+n-1]) + continue; + for (i = 0; i < cr; i++) { + scratch->indexlist[i] = cubepos(i, y, n); + scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n); + } + /* + * solver_intersect() never returns -1. + */ + if (solver_intersect(usage, scratch->indexlist, + scratch->indexlist2 +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in row %d vs block %s", + n, 1+y, usage->blocks->blocknames[b] +#endif + ) || + solver_intersect(usage, scratch->indexlist2, + scratch->indexlist +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in block %s vs row %d", + n, usage->blocks->blocknames[b], 1+y +#endif + )) { + diff = max(diff, DIFF_INTERSECT); + goto cont; + } + } + + /* + * Intersectional analysis, columns vs blocks. + */ + for (x = 0; x < cr; x++) + for (b = 0; b < cr; b++) + for (n = 1; n <= cr; n++) { + if (usage->col[x*cr+n-1] || + usage->blk[b*cr+n-1]) + continue; + for (i = 0; i < cr; i++) { + scratch->indexlist[i] = cubepos(x, i, n); + scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n); + } + if (solver_intersect(usage, scratch->indexlist, + scratch->indexlist2 +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in column %d vs block %s", + n, 1+x, usage->blocks->blocknames[b] +#endif + ) || + solver_intersect(usage, scratch->indexlist2, + scratch->indexlist +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in block %s vs column %d", + n, usage->blocks->blocknames[b], 1+x +#endif + )) { + diff = max(diff, DIFF_INTERSECT); + goto cont; + } + } + + if (usage->diag) { + /* + * Intersectional analysis, \-diagonal vs blocks. + */ + for (b = 0; b < cr; b++) + for (n = 1; n <= cr; n++) { + if (usage->diag[n-1] || + usage->blk[b*cr+n-1]) + continue; + for (i = 0; i < cr; i++) { + scratch->indexlist[i] = cubepos2(diag0(i), n); + scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n); + } + if (solver_intersect(usage, scratch->indexlist, + scratch->indexlist2 +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in \\-diagonal vs block %s", + n, usage->blocks->blocknames[b] +#endif + ) || + solver_intersect(usage, scratch->indexlist2, + scratch->indexlist +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in block %s vs \\-diagonal", + n, usage->blocks->blocknames[b] +#endif + )) { + diff = max(diff, DIFF_INTERSECT); + goto cont; + } + } + + /* + * Intersectional analysis, /-diagonal vs blocks. + */ + for (b = 0; b < cr; b++) + for (n = 1; n <= cr; n++) { + if (usage->diag[cr+n-1] || + usage->blk[b*cr+n-1]) + continue; + for (i = 0; i < cr; i++) { + scratch->indexlist[i] = cubepos2(diag1(i), n); + scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n); + } + if (solver_intersect(usage, scratch->indexlist, + scratch->indexlist2 +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in /-diagonal vs block %s", + n, usage->blocks->blocknames[b] +#endif + ) || + solver_intersect(usage, scratch->indexlist2, + scratch->indexlist +#ifdef STANDALONE_SOLVER + , "intersectional analysis," + " %d in block %s vs /-diagonal", + n, usage->blocks->blocknames[b] +#endif + )) { + diff = max(diff, DIFF_INTERSECT); + goto cont; + } + } + } + + if (dlev->maxdiff <= DIFF_INTERSECT) + break; + + /* + * Blockwise set elimination. + */ + for (b = 0; b < cr; b++) { + for (i = 0; i < cr; i++) + for (n = 1; n <= cr; n++) + scratch->indexlist[i*cr+n-1] = cubepos2(usage->blocks->blocks[b][i], n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "set elimination, block %s", + usage->blocks->blocknames[b] +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SET); + goto cont; + } + } + + /* + * Row-wise set elimination. + */ + for (y = 0; y < cr; y++) { + for (x = 0; x < cr; x++) + for (n = 1; n <= cr; n++) + scratch->indexlist[x*cr+n-1] = cubepos(x, y, n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "set elimination, row %d", 1+y +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SET); + goto cont; + } + } + + /* + * Column-wise set elimination. + */ + for (x = 0; x < cr; x++) { + for (y = 0; y < cr; y++) + for (n = 1; n <= cr; n++) + scratch->indexlist[y*cr+n-1] = cubepos(x, y, n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "set elimination, column %d", 1+x +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SET); + goto cont; + } + } + + if (usage->diag) { + /* + * \-diagonal set elimination. + */ + for (i = 0; i < cr; i++) + for (n = 1; n <= cr; n++) + scratch->indexlist[i*cr+n-1] = cubepos2(diag0(i), n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "set elimination, \\-diagonal" +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SET); + goto cont; + } + + /* + * /-diagonal set elimination. + */ + for (i = 0; i < cr; i++) + for (n = 1; n <= cr; n++) + scratch->indexlist[i*cr+n-1] = cubepos2(diag1(i), n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "set elimination, /-diagonal" +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_SET); + goto cont; + } + } + + if (dlev->maxdiff <= DIFF_SET) + break; + + /* + * Row-vs-column set elimination on a single number. + */ + for (n = 1; n <= cr; n++) { + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + scratch->indexlist[y*cr+x] = cubepos(x, y, n); + ret = solver_set(usage, scratch, scratch->indexlist +#ifdef STANDALONE_SOLVER + , "positional set elimination, number %d", n +#endif + ); + if (ret < 0) { + diff = DIFF_IMPOSSIBLE; + goto got_result; + } else if (ret > 0) { + diff = max(diff, DIFF_EXTREME); + goto cont; + } + } + + /* + * Forcing chains. + */ + if (solver_forcing(usage, scratch)) { + diff = max(diff, DIFF_EXTREME); + goto cont; + } + + /* + * If we reach here, we have made no deductions in this + * iteration, so the algorithm terminates. + */ + break; + } + + /* + * Last chance: if we haven't fully solved the puzzle yet, try + * recursing based on guesses for a particular square. We pick + * one of the most constrained empty squares we can find, which + * has the effect of pruning the search tree as much as + * possible. + */ + if (dlev->maxdiff >= DIFF_RECURSIVE) { + int best, bestcount; + + best = -1; + bestcount = cr+1; + + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + if (!grid[y*cr+x]) { + int count; + + /* + * An unfilled square. Count the number of + * possible digits in it. + */ + count = 0; + for (n = 1; n <= cr; n++) + if (cube(x,y,n)) + count++; + + /* + * We should have found any impossibilities + * already, so this can safely be an assert. + */ + assert(count > 1); + + if (count < bestcount) { + bestcount = count; + best = y*cr+x; + } + } + + if (best != -1) { + int i, j; + digit *list, *ingrid, *outgrid; + + diff = DIFF_IMPOSSIBLE; /* no solution found yet */ + + /* + * Attempt recursion. + */ + y = best / cr; + x = best % cr; + + list = snewn(cr, digit); + ingrid = snewn(cr * cr, digit); + outgrid = snewn(cr * cr, digit); + memcpy(ingrid, grid, cr * cr); + + /* Make a list of the possible digits. */ + for (j = 0, n = 1; n <= cr; n++) + if (cube(x,y,n)) + list[j++] = n; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *sep = ""; + printf("%*srecursing on (%d,%d) [", + solver_recurse_depth*4, "", x + 1, y + 1); + for (i = 0; i < j; i++) { + printf("%s%d", sep, list[i]); + sep = " or "; + } + printf("]\n"); + } +#endif + + /* + * And step along the list, recursing back into the + * main solver at every stage. + */ + for (i = 0; i < j; i++) { + memcpy(outgrid, ingrid, cr * cr); + outgrid[y*cr+x] = list[i]; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*sguessing %d at (%d,%d)\n", + solver_recurse_depth*4, "", list[i], x + 1, y + 1); + solver_recurse_depth++; +#endif + + solver(cr, blocks, kblocks, xtype, outgrid, kgrid, dlev); + +#ifdef STANDALONE_SOLVER + solver_recurse_depth--; + if (solver_show_working) { + printf("%*sretracting %d at (%d,%d)\n", + solver_recurse_depth*4, "", list[i], x + 1, y + 1); + } +#endif + + /* + * If we have our first solution, copy it into the + * grid we will return. + */ + if (diff == DIFF_IMPOSSIBLE && dlev->diff != DIFF_IMPOSSIBLE) + memcpy(grid, outgrid, cr*cr); + + if (dlev->diff == DIFF_AMBIGUOUS) + diff = DIFF_AMBIGUOUS; + else if (dlev->diff == DIFF_IMPOSSIBLE) + /* do not change our return value */; + else { + /* the recursion turned up exactly one solution */ + if (diff == DIFF_IMPOSSIBLE) + diff = DIFF_RECURSIVE; + else + diff = DIFF_AMBIGUOUS; + } + + /* + * As soon as we've found more than one solution, + * give up immediately. + */ + if (diff == DIFF_AMBIGUOUS) + break; + } + + sfree(outgrid); + sfree(ingrid); + sfree(list); + } + + } else { + /* + * We're forbidden to use recursion, so we just see whether + * our grid is fully solved, and return DIFF_IMPOSSIBLE + * otherwise. + */ + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + if (!grid[y*cr+x]) + diff = DIFF_IMPOSSIBLE; + } + + got_result: + dlev->diff = diff; + dlev->kdiff = kdiff; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s%s found\n", + solver_recurse_depth*4, "", + diff == DIFF_IMPOSSIBLE ? "no solution" : + diff == DIFF_AMBIGUOUS ? "multiple solutions" : + "one solution"); +#endif + + sfree(usage->sq2region); + sfree(usage->regions); + sfree(usage->cube); + sfree(usage->row); + sfree(usage->col); + sfree(usage->blk); + if (usage->kblocks) { + free_block_structure(usage->kblocks); + free_block_structure(usage->extra_cages); + sfree(usage->extra_clues); + } + if (usage->kclues) sfree(usage->kclues); + sfree(usage); + + solver_free_scratch(scratch); +} + +/* ---------------------------------------------------------------------- + * End of solver code. + */ + +/* ---------------------------------------------------------------------- + * Killer set generator. + */ + +/* ---------------------------------------------------------------------- + * Solo filled-grid generator. + * + * This grid generator works by essentially trying to solve a grid + * starting from no clues, and not worrying that there's more than + * one possible solution. Unfortunately, it isn't computationally + * feasible to do this by calling the above solver with an empty + * grid, because that one needs to allocate a lot of scratch space + * at every recursion level. Instead, I have a much simpler + * algorithm which I shamelessly copied from a Python solver + * written by Andrew Wilkinson (which is GPLed, but I've reused + * only ideas and no code). It mostly just does the obvious + * recursive thing: pick an empty square, put one of the possible + * digits in it, recurse until all squares are filled, backtrack + * and change some choices if necessary. + * + * The clever bit is that every time it chooses which square to + * fill in next, it does so by counting the number of _possible_ + * numbers that can go in each square, and it prioritises so that + * it picks a square with the _lowest_ number of possibilities. The + * idea is that filling in lots of the obvious bits (particularly + * any squares with only one possibility) will cut down on the list + * of possibilities for other squares and hence reduce the enormous + * search space as much as possible as early as possible. + * + * The use of bit sets implies that we support puzzles up to a size of + * 32x32 (less if anyone finds a 16-bit machine to compile this on). + */ + +/* + * Internal data structure used in gridgen to keep track of + * progress. + */ +struct gridgen_coord { int x, y, r; }; +struct gridgen_usage { + int cr; + struct block_structure *blocks, *kblocks; + /* grid is a copy of the input grid, modified as we go along */ + digit *grid; + /* + * Bitsets. In each of them, bit n is set if digit n has been placed + * in the corresponding region. row, col and blk are used for all + * puzzles. cge is used only for killer puzzles, and diag is used + * only for x-type puzzles. + * All of these have cr entries, except diag which only has 2, + * and cge, which has as many entries as kblocks. + */ + unsigned int *row, *col, *blk, *cge, *diag; + /* This lists all the empty spaces remaining in the grid. */ + struct gridgen_coord *spaces; + int nspaces; + /* If we need randomisation in the solve, this is our random state. */ + random_state *rs; +}; + +static void gridgen_place(struct gridgen_usage *usage, int x, int y, digit n) +{ + unsigned int bit = 1 << n; + int cr = usage->cr; + usage->row[y] |= bit; + usage->col[x] |= bit; + usage->blk[usage->blocks->whichblock[y*cr+x]] |= bit; + if (usage->cge) + usage->cge[usage->kblocks->whichblock[y*cr+x]] |= bit; + if (usage->diag) { + if (ondiag0(y*cr+x)) + usage->diag[0] |= bit; + if (ondiag1(y*cr+x)) + usage->diag[1] |= bit; + } + usage->grid[y*cr+x] = n; +} + +static void gridgen_remove(struct gridgen_usage *usage, int x, int y, digit n) +{ + unsigned int mask = ~(1 << n); + int cr = usage->cr; + usage->row[y] &= mask; + usage->col[x] &= mask; + usage->blk[usage->blocks->whichblock[y*cr+x]] &= mask; + if (usage->cge) + usage->cge[usage->kblocks->whichblock[y*cr+x]] &= mask; + if (usage->diag) { + if (ondiag0(y*cr+x)) + usage->diag[0] &= mask; + if (ondiag1(y*cr+x)) + usage->diag[1] &= mask; + } + usage->grid[y*cr+x] = 0; +} + +#define N_SINGLE 32 + +/* + * The real recursive step in the generating function. + * + * Return values: 1 means solution found, 0 means no solution + * found on this branch. + */ +static int gridgen_real(struct gridgen_usage *usage, digit *grid, int *steps) +{ + int cr = usage->cr; + int i, j, n, sx, sy, bestm, bestr, ret; + int *digits; + unsigned int used; + + /* + * Firstly, check for completion! If there are no spaces left + * in the grid, we have a solution. + */ + if (usage->nspaces == 0) + return TRUE; + + /* + * Next, abandon generation if we went over our steps limit. + */ + if (*steps <= 0) + return FALSE; + (*steps)--; + + /* + * Otherwise, there must be at least one space. Find the most + * constrained space, using the `r' field as a tie-breaker. + */ + bestm = cr+1; /* so that any space will beat it */ + bestr = 0; + used = ~0; + i = sx = sy = -1; + for (j = 0; j < usage->nspaces; j++) { + int x = usage->spaces[j].x, y = usage->spaces[j].y; + unsigned int used_xy; + int m; + + m = usage->blocks->whichblock[y*cr+x]; + used_xy = usage->row[y] | usage->col[x] | usage->blk[m]; + if (usage->cge != NULL) + used_xy |= usage->cge[usage->kblocks->whichblock[y*cr+x]]; + if (usage->cge != NULL) + used_xy |= usage->cge[usage->kblocks->whichblock[y*cr+x]]; + if (usage->diag != NULL) { + if (ondiag0(y*cr+x)) + used_xy |= usage->diag[0]; + if (ondiag1(y*cr+x)) + used_xy |= usage->diag[1]; + } + + /* + * Find the number of digits that could go in this space. + */ + m = 0; + for (n = 1; n <= cr; n++) { + unsigned int bit = 1 << n; + if ((used_xy & bit) == 0) + m++; + } + if (m < bestm || (m == bestm && usage->spaces[j].r < bestr)) { + bestm = m; + bestr = usage->spaces[j].r; + sx = x; + sy = y; + i = j; + used = used_xy; + } + } + + /* + * Swap that square into the final place in the spaces array, + * so that decrementing nspaces will remove it from the list. + */ + if (i != usage->nspaces-1) { + struct gridgen_coord t; + t = usage->spaces[usage->nspaces-1]; + usage->spaces[usage->nspaces-1] = usage->spaces[i]; + usage->spaces[i] = t; + } + + /* + * Now we've decided which square to start our recursion at, + * simply go through all possible values, shuffling them + * randomly first if necessary. + */ + digits = snewn(bestm, int); + + j = 0; + for (n = 1; n <= cr; n++) { + unsigned int bit = 1 << n; + + if ((used & bit) == 0) + digits[j++] = n; + } + + if (usage->rs) + shuffle(digits, j, sizeof(*digits), usage->rs); + + /* And finally, go through the digit list and actually recurse. */ + ret = FALSE; + for (i = 0; i < j; i++) { + n = digits[i]; + + /* Update the usage structure to reflect the placing of this digit. */ + gridgen_place(usage, sx, sy, n); + usage->nspaces--; + + /* Call the solver recursively. Stop when we find a solution. */ + if (gridgen_real(usage, grid, steps)) { + ret = TRUE; + break; + } + + /* Revert the usage structure. */ + gridgen_remove(usage, sx, sy, n); + usage->nspaces++; + } + + sfree(digits); + return ret; +} + +/* + * Entry point to generator. You give it parameters and a starting + * grid, which is simply an array of cr*cr digits. + */ +static int gridgen(int cr, struct block_structure *blocks, + struct block_structure *kblocks, int xtype, + digit *grid, random_state *rs, int maxsteps) +{ + struct gridgen_usage *usage; + int x, y, ret; + + /* + * Clear the grid to start with. + */ + memset(grid, 0, cr*cr); + + /* + * Create a gridgen_usage structure. + */ + usage = snew(struct gridgen_usage); + + usage->cr = cr; + usage->blocks = blocks; + + usage->grid = grid; + + usage->row = snewn(cr, unsigned int); + usage->col = snewn(cr, unsigned int); + usage->blk = snewn(cr, unsigned int); + if (kblocks != NULL) { + usage->kblocks = kblocks; + usage->cge = snewn(usage->kblocks->nr_blocks, unsigned int); + memset(usage->cge, FALSE, kblocks->nr_blocks * sizeof *usage->cge); + } else { + usage->cge = NULL; + } + + memset(usage->row, 0, cr * sizeof *usage->row); + memset(usage->col, 0, cr * sizeof *usage->col); + memset(usage->blk, 0, cr * sizeof *usage->blk); + + if (xtype) { + usage->diag = snewn(2, unsigned int); + memset(usage->diag, 0, 2 * sizeof *usage->diag); + } else { + usage->diag = NULL; + } + + /* + * Begin by filling in the whole top row with randomly chosen + * numbers. This cannot introduce any bias or restriction on + * the available grids, since we already know those numbers + * are all distinct so all we're doing is choosing their + * labels. + */ + for (x = 0; x < cr; x++) + grid[x] = x+1; + shuffle(grid, cr, sizeof(*grid), rs); + for (x = 0; x < cr; x++) + gridgen_place(usage, x, 0, grid[x]); + + usage->spaces = snewn(cr * cr, struct gridgen_coord); + usage->nspaces = 0; + + usage->rs = rs; + + /* + * Initialise the list of grid spaces, taking care to leave + * out the row I've already filled in above. + */ + for (y = 1; y < cr; y++) { + for (x = 0; x < cr; x++) { + usage->spaces[usage->nspaces].x = x; + usage->spaces[usage->nspaces].y = y; + usage->spaces[usage->nspaces].r = random_bits(rs, 31); + usage->nspaces++; + } + } + + /* + * Run the real generator function. + */ + ret = gridgen_real(usage, grid, &maxsteps); + + /* + * Clean up the usage structure now we have our answer. + */ + sfree(usage->spaces); + sfree(usage->cge); + sfree(usage->blk); + sfree(usage->col); + sfree(usage->row); + sfree(usage); + + return ret; +} + +/* ---------------------------------------------------------------------- + * End of grid generator code. + */ + +static int check_killer_cage_sum(struct block_structure *kblocks, + digit *kgrid, digit *grid, int blk) +{ + /* + * Returns: -1 if the cage has any empty square; 0 if all squares + * are full but the sum is wrong; +1 if all squares are full and + * they have the right sum. + * + * Does not check uniqueness of numbers within the cage; that's + * done elsewhere (because in error highlighting it needs to be + * detected separately so as to flag the error in a visually + * different way). + */ + int n_squares = kblocks->nr_squares[blk]; + int sum = 0, clue = 0; + int i; + + for (i = 0; i < n_squares; i++) { + int xy = kblocks->blocks[blk][i]; + + if (grid[xy] == 0) + return -1; + sum += grid[xy]; + + if (kgrid[xy]) { + assert(clue == 0); + clue = kgrid[xy]; + } + } + + assert(clue != 0); + return sum == clue; +} + +/* + * Check whether a grid contains a valid complete puzzle. + */ +static int check_valid(int cr, struct block_structure *blocks, + struct block_structure *kblocks, + digit *kgrid, int xtype, digit *grid) +{ + unsigned char *used; + int x, y, i, j, n; + + used = snewn(cr, unsigned char); + + /* + * Check that each row contains precisely one of everything. + */ + for (y = 0; y < cr; y++) { + memset(used, FALSE, cr); + for (x = 0; x < cr; x++) + if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr) + used[grid[y*cr+x]-1] = TRUE; + for (n = 0; n < cr; n++) + if (!used[n]) { + sfree(used); + return FALSE; + } + } + + /* + * Check that each column contains precisely one of everything. + */ + for (x = 0; x < cr; x++) { + memset(used, FALSE, cr); + for (y = 0; y < cr; y++) + if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr) + used[grid[y*cr+x]-1] = TRUE; + for (n = 0; n < cr; n++) + if (!used[n]) { + sfree(used); + return FALSE; + } + } + + /* + * Check that each block contains precisely one of everything. + */ + for (i = 0; i < cr; i++) { + memset(used, FALSE, cr); + for (j = 0; j < cr; j++) + if (grid[blocks->blocks[i][j]] > 0 && + grid[blocks->blocks[i][j]] <= cr) + used[grid[blocks->blocks[i][j]]-1] = TRUE; + for (n = 0; n < cr; n++) + if (!used[n]) { + sfree(used); + return FALSE; + } + } + + /* + * Check that each Killer cage, if any, contains at most one of + * everything. If we also know the clues for those cages (which we + * might not, when this function is called early in puzzle + * generation), we also check that they all have the right sum. + */ + if (kblocks) { + for (i = 0; i < kblocks->nr_blocks; i++) { + memset(used, FALSE, cr); + for (j = 0; j < kblocks->nr_squares[i]; j++) + if (grid[kblocks->blocks[i][j]] > 0 && + grid[kblocks->blocks[i][j]] <= cr) { + if (used[grid[kblocks->blocks[i][j]]-1]) { + sfree(used); + return FALSE; + } + used[grid[kblocks->blocks[i][j]]-1] = TRUE; + } + + if (kgrid && check_killer_cage_sum(kblocks, kgrid, grid, i) != 1) { + sfree(used); + return FALSE; + } + } + } + + /* + * Check that each diagonal contains precisely one of everything. + */ + if (xtype) { + memset(used, FALSE, cr); + for (i = 0; i < cr; i++) + if (grid[diag0(i)] > 0 && grid[diag0(i)] <= cr) + used[grid[diag0(i)]-1] = TRUE; + for (n = 0; n < cr; n++) + if (!used[n]) { + sfree(used); + return FALSE; + } + for (i = 0; i < cr; i++) + if (grid[diag1(i)] > 0 && grid[diag1(i)] <= cr) + used[grid[diag1(i)]-1] = TRUE; + for (n = 0; n < cr; n++) + if (!used[n]) { + sfree(used); + return FALSE; + } + } + + sfree(used); + return TRUE; +} + +static int symmetries(const game_params *params, int x, int y, + int *output, int s) +{ + int c = params->c, r = params->r, cr = c*r; + int i = 0; + +#define ADD(x,y) (*output++ = (x), *output++ = (y), i++) + + ADD(x, y); + + switch (s) { + case SYMM_NONE: + break; /* just x,y is all we need */ + case SYMM_ROT2: + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_ROT4: + ADD(cr - 1 - y, x); + ADD(y, cr - 1 - x); + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_REF2: + ADD(cr - 1 - x, y); + break; + case SYMM_REF2D: + ADD(y, x); + break; + case SYMM_REF4: + ADD(cr - 1 - x, y); + ADD(x, cr - 1 - y); + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_REF4D: + ADD(y, x); + ADD(cr - 1 - x, cr - 1 - y); + ADD(cr - 1 - y, cr - 1 - x); + break; + case SYMM_REF8: + ADD(cr - 1 - x, y); + ADD(x, cr - 1 - y); + ADD(cr - 1 - x, cr - 1 - y); + ADD(y, x); + ADD(y, cr - 1 - x); + ADD(cr - 1 - y, x); + ADD(cr - 1 - y, cr - 1 - x); + break; + } + +#undef ADD + + return i; +} + +static char *encode_solve_move(int cr, digit *grid) +{ + int i, len; + char *ret, *p, *sep; + + /* + * It's surprisingly easy to work out _exactly_ how long this + * string needs to be. To decimal-encode all the numbers from 1 + * to n: + * + * - every number has a units digit; total is n. + * - all numbers above 9 have a tens digit; total is max(n-9,0). + * - all numbers above 99 have a hundreds digit; total is max(n-99,0). + * - and so on. + */ + len = 0; + for (i = 1; i <= cr; i *= 10) + len += max(cr - i + 1, 0); + len += cr; /* don't forget the commas */ + len *= cr; /* there are cr rows of these */ + + /* + * Now len is one bigger than the total size of the + * comma-separated numbers (because we counted an + * additional leading comma). We need to have a leading S + * and a trailing NUL, so we're off by one in total. + */ + len++; + + ret = snewn(len, char); + p = ret; + *p++ = 'S'; + sep = ""; + for (i = 0; i < cr*cr; i++) { + p += sprintf(p, "%s%d", sep, grid[i]); + sep = ","; + } + *p++ = '\0'; + assert(p - ret == len); + + return ret; +} + +static void dsf_to_blocks(int *dsf, struct block_structure *blocks, + int min_expected, int max_expected) +{ + int cr = blocks->c * blocks->r, area = cr * cr; + int i, nb = 0; + + for (i = 0; i < area; i++) + blocks->whichblock[i] = -1; + for (i = 0; i < area; i++) { + int j = dsf_canonify(dsf, i); + if (blocks->whichblock[j] < 0) + blocks->whichblock[j] = nb++; + blocks->whichblock[i] = blocks->whichblock[j]; + } + assert(nb >= min_expected && nb <= max_expected); + blocks->nr_blocks = nb; +} + +static void make_blocks_from_whichblock(struct block_structure *blocks) +{ + int i; + + for (i = 0; i < blocks->nr_blocks; i++) { + blocks->blocks[i][blocks->max_nr_squares-1] = 0; + blocks->nr_squares[i] = 0; + } + for (i = 0; i < blocks->area; i++) { + int b = blocks->whichblock[i]; + int j = blocks->blocks[b][blocks->max_nr_squares-1]++; + assert(j < blocks->max_nr_squares); + blocks->blocks[b][j] = i; + blocks->nr_squares[b]++; + } +} + +static char *encode_block_structure_desc(char *p, struct block_structure *blocks) +{ + int i, currrun = 0; + int c = blocks->c, r = blocks->r, cr = c * r; + + /* + * Encode the block structure. We do this by encoding + * the pattern of dividing lines: first we iterate + * over the cr*(cr-1) internal vertical grid lines in + * ordinary reading order, then over the cr*(cr-1) + * internal horizontal ones in transposed reading + * order. + * + * We encode the number of non-lines between the + * lines; _ means zero (two adjacent divisions), a + * means 1, ..., y means 25, and z means 25 non-lines + * _and no following line_ (so that za means 26, zb 27 + * etc). + */ + for (i = 0; i <= 2*cr*(cr-1); i++) { + int x, y, p0, p1, edge; + + if (i == 2*cr*(cr-1)) { + edge = TRUE; /* terminating virtual edge */ + } else { + if (i < cr*(cr-1)) { + y = i/(cr-1); + x = i%(cr-1); + p0 = y*cr+x; + p1 = y*cr+x+1; + } else { + x = i/(cr-1) - cr; + y = i%(cr-1); + p0 = y*cr+x; + p1 = (y+1)*cr+x; + } + edge = (blocks->whichblock[p0] != blocks->whichblock[p1]); + } + + if (edge) { + while (currrun > 25) + *p++ = 'z', currrun -= 25; + if (currrun) + *p++ = 'a'-1 + currrun; + else + *p++ = '_'; + currrun = 0; + } else + currrun++; + } + return p; +} + +static char *encode_grid(char *desc, digit *grid, int area) +{ + int run, i; + char *p = desc; + + run = 0; + for (i = 0; i <= area; i++) { + int n = (i < area ? grid[i] : -1); + + if (!n) + run++; + else { + if (run) { + while (run > 0) { + int c = 'a' - 1 + run; + if (run > 26) + c = 'z'; + *p++ = c; + run -= c - ('a' - 1); + } + } else { + /* + * If there's a number in the very top left or + * bottom right, there's no point putting an + * unnecessary _ before or after it. + */ + if (p > desc && n > 0) + *p++ = '_'; + } + if (n > 0) + p += sprintf(p, "%d", n); + run = 0; + } + } + return p; +} + +/* + * Conservatively stimate the number of characters required for + * encoding a grid of a certain area. + */ +static int grid_encode_space (int area) +{ + int t, count; + for (count = 1, t = area; t > 26; t -= 26) + count++; + return count * area; +} + +/* + * Conservatively stimate the number of characters required for + * encoding a given blocks structure. + */ +static int blocks_encode_space(struct block_structure *blocks) +{ + int cr = blocks->c * blocks->r, area = cr * cr; + return grid_encode_space(area); +} + +static char *encode_puzzle_desc(const game_params *params, digit *grid, + struct block_structure *blocks, + digit *kgrid, + struct block_structure *kblocks) +{ + int c = params->c, r = params->r, cr = c*r; + int area = cr*cr; + char *p, *desc; + int space; + + space = grid_encode_space(area) + 1; + if (r == 1) + space += blocks_encode_space(blocks) + 1; + if (params->killer) { + space += blocks_encode_space(kblocks) + 1; + space += grid_encode_space(area) + 1; + } + desc = snewn(space, char); + p = encode_grid(desc, grid, area); + + if (r == 1) { + *p++ = ','; + p = encode_block_structure_desc(p, blocks); + } + if (params->killer) { + *p++ = ','; + p = encode_block_structure_desc(p, kblocks); + *p++ = ','; + p = encode_grid(p, kgrid, area); + } + assert(p - desc < space); + *p++ = '\0'; + desc = sresize(desc, p - desc, char); + + return desc; +} + +static void merge_blocks(struct block_structure *b, int n1, int n2) +{ + int i; + /* Move data towards the lower block number. */ + if (n2 < n1) { + int t = n2; + n2 = n1; + n1 = t; + } + + /* Merge n2 into n1, and move the last block into n2's position. */ + for (i = 0; i < b->nr_squares[n2]; i++) + b->whichblock[b->blocks[n2][i]] = n1; + memcpy(b->blocks[n1] + b->nr_squares[n1], b->blocks[n2], + b->nr_squares[n2] * sizeof **b->blocks); + b->nr_squares[n1] += b->nr_squares[n2]; + + n1 = b->nr_blocks - 1; + if (n2 != n1) { + memcpy(b->blocks[n2], b->blocks[n1], + b->nr_squares[n1] * sizeof **b->blocks); + for (i = 0; i < b->nr_squares[n1]; i++) + b->whichblock[b->blocks[n1][i]] = n2; + b->nr_squares[n2] = b->nr_squares[n1]; + } + b->nr_blocks = n1; +} + +static int merge_some_cages(struct block_structure *b, int cr, int area, + digit *grid, random_state *rs) +{ + /* + * Make a list of all the pairs of adjacent blocks. + */ + int i, j, k; + struct pair { + int b1, b2; + } *pairs; + int npairs; + + pairs = snewn(b->nr_blocks * b->nr_blocks, struct pair); + npairs = 0; + + for (i = 0; i < b->nr_blocks; i++) { + for (j = i+1; j < b->nr_blocks; j++) { + + /* + * Rule the merger out of consideration if it's + * obviously not viable. + */ + if (b->nr_squares[i] + b->nr_squares[j] > b->max_nr_squares) + continue; /* we couldn't merge these anyway */ + + /* + * See if these two blocks have a pair of squares + * adjacent to each other. + */ + for (k = 0; k < b->nr_squares[i]; k++) { + int xy = b->blocks[i][k]; + int y = xy / cr, x = xy % cr; + if ((y > 0 && b->whichblock[xy - cr] == j) || + (y+1 < cr && b->whichblock[xy + cr] == j) || + (x > 0 && b->whichblock[xy - 1] == j) || + (x+1 < cr && b->whichblock[xy + 1] == j)) { + /* + * Yes! Add this pair to our list. + */ + pairs[npairs].b1 = i; + pairs[npairs].b2 = j; + break; + } + } + } + } + + /* + * Now go through that list in random order until we find a pair + * of blocks we can merge. + */ + while (npairs > 0) { + int n1, n2; + unsigned int digits_found; + + /* + * Pick a random pair, and remove it from the list. + */ + i = random_upto(rs, npairs); + n1 = pairs[i].b1; + n2 = pairs[i].b2; + if (i != npairs-1) + pairs[i] = pairs[npairs-1]; + npairs--; + + /* Guarantee that the merged cage would still be a region. */ + digits_found = 0; + for (i = 0; i < b->nr_squares[n1]; i++) + digits_found |= 1 << grid[b->blocks[n1][i]]; + for (i = 0; i < b->nr_squares[n2]; i++) + if (digits_found & (1 << grid[b->blocks[n2][i]])) + break; + if (i != b->nr_squares[n2]) + continue; + + /* + * Got one! Do the merge. + */ + merge_blocks(b, n1, n2); + sfree(pairs); + return TRUE; + } + + sfree(pairs); + return FALSE; +} + +static void compute_kclues(struct block_structure *cages, digit *kclues, + digit *grid, int area) +{ + int i; + memset(kclues, 0, area * sizeof *kclues); + for (i = 0; i < cages->nr_blocks; i++) { + int j, sum = 0; + for (j = 0; j < area; j++) + if (cages->whichblock[j] == i) + sum += grid[j]; + for (j = 0; j < area; j++) + if (cages->whichblock[j] == i) + break; + assert (j != area); + kclues[j] = sum; + } +} + +static struct block_structure *gen_killer_cages(int cr, random_state *rs, + int remove_singletons) +{ + int nr; + int x, y, area = cr * cr; + int n_singletons = 0; + struct block_structure *b = alloc_block_structure (1, cr, area, cr, area); + + for (x = 0; x < area; x++) + b->whichblock[x] = -1; + nr = 0; + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) { + int rnd; + int xy = y*cr+x; + if (b->whichblock[xy] != -1) + continue; + b->whichblock[xy] = nr; + + rnd = random_bits(rs, 4); + if (xy + 1 < area && (rnd >= 4 || (!remove_singletons && rnd >= 1))) { + int xy2 = xy + 1; + if (x + 1 == cr || b->whichblock[xy2] != -1 || + (xy + cr < area && random_bits(rs, 1) == 0)) + xy2 = xy + cr; + if (xy2 >= area) + n_singletons++; + else + b->whichblock[xy2] = nr; + } else + n_singletons++; + nr++; + } + + b->nr_blocks = nr; + make_blocks_from_whichblock(b); + + for (x = y = 0; x < b->nr_blocks; x++) + if (b->nr_squares[x] == 1) + y++; + assert(y == n_singletons); + + if (n_singletons > 0 && remove_singletons) { + int n; + for (n = 0; n < b->nr_blocks;) { + int xy, x, y, xy2, other; + if (b->nr_squares[n] > 1) { + n++; + continue; + } + xy = b->blocks[n][0]; + x = xy % cr; + y = xy / cr; + if (xy + 1 == area) + xy2 = xy - 1; + else if (x + 1 < cr && (y + 1 == cr || random_bits(rs, 1) == 0)) + xy2 = xy + 1; + else + xy2 = xy + cr; + other = b->whichblock[xy2]; + + if (b->nr_squares[other] == 1) + n_singletons--; + n_singletons--; + merge_blocks(b, n, other); + if (n < other) + n++; + } + assert(n_singletons == 0); + } + return b; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int c = params->c, r = params->r, cr = c*r; + int area = cr*cr; + struct block_structure *blocks, *kblocks; + digit *grid, *grid2, *kgrid; + struct xy { int x, y; } *locs; + int nlocs; + char *desc; + int coords[16], ncoords; + int x, y, i, j; + struct difficulty dlev; + + precompute_sum_bits(); + + /* + * Adjust the maximum difficulty level to be consistent with + * the puzzle size: all 2x2 puzzles appear to be Trivial + * (DIFF_BLOCK) so we cannot hold out for even a Basic + * (DIFF_SIMPLE) one. + */ + dlev.maxdiff = params->diff; + dlev.maxkdiff = params->kdiff; + if (c == 2 && r == 2) + dlev.maxdiff = DIFF_BLOCK; + + grid = snewn(area, digit); + locs = snewn(area, struct xy); + grid2 = snewn(area, digit); + + blocks = alloc_block_structure (c, r, area, cr, cr); + + kblocks = NULL; + kgrid = (params->killer) ? snewn(area, digit) : NULL; + +#ifdef STANDALONE_SOLVER + assert(!"This should never happen, so we don't need to create blocknames"); +#endif + + /* + * Loop until we get a grid of the required difficulty. This is + * nasty, but it seems to be unpleasantly hard to generate + * difficult grids otherwise. + */ + while (1) { + /* + * Generate a random solved state, starting by + * constructing the block structure. + */ + if (r == 1) { /* jigsaw mode */ + int *dsf = divvy_rectangle(cr, cr, cr, rs); + + dsf_to_blocks (dsf, blocks, cr, cr); + + sfree(dsf); + } else { /* basic Sudoku mode */ + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + blocks->whichblock[y*cr+x] = (y/c) * c + (x/r); + } + make_blocks_from_whichblock(blocks); + + if (params->killer) { + if (kblocks) free_block_structure(kblocks); + kblocks = gen_killer_cages(cr, rs, params->kdiff > DIFF_KSINGLE); + } + + if (!gridgen(cr, blocks, kblocks, params->xtype, grid, rs, area*area)) + continue; + assert(check_valid(cr, blocks, kblocks, NULL, params->xtype, grid)); + + /* + * Save the solved grid in aux. + */ + { + /* + * We might already have written *aux the last time we + * went round this loop, in which case we should free + * the old aux before overwriting it with the new one. + */ + if (*aux) { + sfree(*aux); + } + + *aux = encode_solve_move(cr, grid); + } + + /* + * Now we have a solved grid. For normal puzzles, we start removing + * things from it while preserving solubility. Killer puzzles are + * different: we just pass the empty grid to the solver, and use + * the puzzle if it comes back solved. + */ + + if (params->killer) { + struct block_structure *good_cages = NULL; + struct block_structure *last_cages = NULL; + int ntries = 0; + + memcpy(grid2, grid, area); + + for (;;) { + compute_kclues(kblocks, kgrid, grid2, area); + + memset(grid, 0, area * sizeof *grid); + solver(cr, blocks, kblocks, params->xtype, grid, kgrid, &dlev); + if (dlev.diff == dlev.maxdiff && dlev.kdiff == dlev.maxkdiff) { + /* + * We have one that matches our difficulty. Store it for + * later, but keep going. + */ + if (good_cages) + free_block_structure(good_cages); + ntries = 0; + good_cages = dup_block_structure(kblocks); + if (!merge_some_cages(kblocks, cr, area, grid2, rs)) + break; + } else if (dlev.diff > dlev.maxdiff || dlev.kdiff > dlev.maxkdiff) { + /* + * Give up after too many tries and either use the good one we + * found, or generate a new grid. + */ + if (++ntries > 50) + break; + /* + * The difficulty level got too high. If we have a good + * one, use it, otherwise go back to the last one that + * was at a lower difficulty and restart the process from + * there. + */ + if (good_cages != NULL) { + free_block_structure(kblocks); + kblocks = dup_block_structure(good_cages); + if (!merge_some_cages(kblocks, cr, area, grid2, rs)) + break; + } else { + if (last_cages == NULL) + break; + free_block_structure(kblocks); + kblocks = last_cages; + last_cages = NULL; + } + } else { + if (last_cages) + free_block_structure(last_cages); + last_cages = dup_block_structure(kblocks); + if (!merge_some_cages(kblocks, cr, area, grid2, rs)) + break; + } + } + if (last_cages) + free_block_structure(last_cages); + if (good_cages != NULL) { + free_block_structure(kblocks); + kblocks = good_cages; + compute_kclues(kblocks, kgrid, grid2, area); + memset(grid, 0, area * sizeof *grid); + break; + } + continue; + } + + /* + * Find the set of equivalence classes of squares permitted + * by the selected symmetry. We do this by enumerating all + * the grid squares which have no symmetric companion + * sorting lower than themselves. + */ + nlocs = 0; + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) { + int i = y*cr+x; + int j; + + ncoords = symmetries(params, x, y, coords, params->symm); + for (j = 0; j < ncoords; j++) + if (coords[2*j+1]*cr+coords[2*j] < i) + break; + if (j == ncoords) { + locs[nlocs].x = x; + locs[nlocs].y = y; + nlocs++; + } + } + + /* + * Now shuffle that list. + */ + shuffle(locs, nlocs, sizeof(*locs), rs); + + /* + * Now loop over the shuffled list and, for each element, + * see whether removing that element (and its reflections) + * from the grid will still leave the grid soluble. + */ + for (i = 0; i < nlocs; i++) { + x = locs[i].x; + y = locs[i].y; + + memcpy(grid2, grid, area); + ncoords = symmetries(params, x, y, coords, params->symm); + for (j = 0; j < ncoords; j++) + grid2[coords[2*j+1]*cr+coords[2*j]] = 0; + + solver(cr, blocks, kblocks, params->xtype, grid2, kgrid, &dlev); + if (dlev.diff <= dlev.maxdiff && + (!params->killer || dlev.kdiff <= dlev.maxkdiff)) { + for (j = 0; j < ncoords; j++) + grid[coords[2*j+1]*cr+coords[2*j]] = 0; + } + } + + memcpy(grid2, grid, area); + + solver(cr, blocks, kblocks, params->xtype, grid2, kgrid, &dlev); + if (dlev.diff == dlev.maxdiff && + (!params->killer || dlev.kdiff == dlev.maxkdiff)) + break; /* found one! */ + } + + sfree(grid2); + sfree(locs); + + /* + * Now we have the grid as it will be presented to the user. + * Encode it in a game desc. + */ + desc = encode_puzzle_desc(params, grid, blocks, kgrid, kblocks); + + sfree(grid); + free_block_structure(blocks); + if (params->killer) { + free_block_structure(kblocks); + sfree(kgrid); + } + + return desc; +} + +static const char *spec_to_grid(const char *desc, digit *grid, int area) +{ + int i = 0; + while (*desc && *desc != ',') { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + int run = n - 'a' + 1; + assert(i + run <= area); + while (run-- > 0) + grid[i++] = 0; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + assert(i < area); + grid[i++] = atoi(desc-1); + while (*desc >= '0' && *desc <= '9') + desc++; + } else { + assert(!"We can't get here"); + } + } + assert(i == area); + return desc; +} + +/* + * Create a DSF from a spec found in *pdesc. Update this to point past the + * end of the block spec, and return an error string or NULL if everything + * is OK. The DSF is stored in *PDSF. + */ +static char *spec_to_dsf(const char **pdesc, int **pdsf, int cr, int area) +{ + const char *desc = *pdesc; + int pos = 0; + int *dsf; + + *pdsf = dsf = snew_dsf(area); + + while (*desc && *desc != ',') { + int c, adv; + + if (*desc == '_') + c = 0; + else if (*desc >= 'a' && *desc <= 'z') + c = *desc - 'a' + 1; + else { + sfree(dsf); + return "Invalid character in game description"; + } + desc++; + + adv = (c != 26); /* 'z' is a special case */ + + while (c-- > 0) { + int p0, p1; + + /* + * Non-edge; merge the two dsf classes on either + * side of it. + */ + if (pos >= 2*cr*(cr-1)) { + sfree(dsf); + return "Too much data in block structure specification"; + } + + if (pos < cr*(cr-1)) { + int y = pos/(cr-1); + int x = pos%(cr-1); + p0 = y*cr+x; + p1 = y*cr+x+1; + } else { + int x = pos/(cr-1) - cr; + int y = pos%(cr-1); + p0 = y*cr+x; + p1 = (y+1)*cr+x; + } + dsf_merge(dsf, p0, p1); + + pos++; + } + if (adv) + pos++; + } + *pdesc = desc; + + /* + * When desc is exhausted, we expect to have gone exactly + * one space _past_ the end of the grid, due to the dummy + * edge at the end. + */ + if (pos != 2*cr*(cr-1)+1) { + sfree(dsf); + return "Not enough data in block structure specification"; + } + + return NULL; +} + +static char *validate_grid_desc(const char **pdesc, int range, int area) +{ + const char *desc = *pdesc; + int squares = 0; + while (*desc && *desc != ',') { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + squares += n - 'a' + 1; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + int val = atoi(desc-1); + if (val < 1 || val > range) + return "Out-of-range number in game description"; + squares++; + while (*desc >= '0' && *desc <= '9') + desc++; + } else + return "Invalid character in game description"; + } + + if (squares < area) + return "Not enough data to fill grid"; + + if (squares > area) + return "Too much data to fit in grid"; + *pdesc = desc; + return NULL; +} + +static char *validate_block_desc(const char **pdesc, int cr, int area, + int min_nr_blocks, int max_nr_blocks, + int min_nr_squares, int max_nr_squares) +{ + char *err; + int *dsf; + + err = spec_to_dsf(pdesc, &dsf, cr, area); + if (err) { + return err; + } + + if (min_nr_squares == max_nr_squares) { + assert(min_nr_blocks == max_nr_blocks); + assert(min_nr_blocks * min_nr_squares == area); + } + /* + * Now we've got our dsf. Verify that it matches + * expectations. + */ + { + int *canons, *counts; + int i, j, c, ncanons = 0; + + canons = snewn(max_nr_blocks, int); + counts = snewn(max_nr_blocks, int); + + for (i = 0; i < area; i++) { + j = dsf_canonify(dsf, i); + + for (c = 0; c < ncanons; c++) + if (canons[c] == j) { + counts[c]++; + if (counts[c] > max_nr_squares) { + sfree(dsf); + sfree(canons); + sfree(counts); + return "A jigsaw block is too big"; + } + break; + } + + if (c == ncanons) { + if (ncanons >= max_nr_blocks) { + sfree(dsf); + sfree(canons); + sfree(counts); + return "Too many distinct jigsaw blocks"; + } + canons[ncanons] = j; + counts[ncanons] = 1; + ncanons++; + } + } + + if (ncanons < min_nr_blocks) { + sfree(dsf); + sfree(canons); + sfree(counts); + return "Not enough distinct jigsaw blocks"; + } + for (c = 0; c < ncanons; c++) { + if (counts[c] < min_nr_squares) { + sfree(dsf); + sfree(canons); + sfree(counts); + return "A jigsaw block is too small"; + } + } + sfree(canons); + sfree(counts); + } + + sfree(dsf); + return NULL; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int cr = params->c * params->r, area = cr*cr; + char *err; + + err = validate_grid_desc(&desc, cr, area); + if (err) + return err; + + if (params->r == 1) { + /* + * Now we expect a suffix giving the jigsaw block + * structure. Parse it and validate that it divides the + * grid into the right number of regions which are the + * right size. + */ + if (*desc != ',') + return "Expected jigsaw block structure in game description"; + desc++; + err = validate_block_desc(&desc, cr, area, cr, cr, cr, cr); + if (err) + return err; + + } + if (params->killer) { + if (*desc != ',') + return "Expected killer block structure in game description"; + desc++; + err = validate_block_desc(&desc, cr, area, cr, area, 2, cr); + if (err) + return err; + if (*desc != ',') + return "Expected killer clue grid in game description"; + desc++; + err = validate_grid_desc(&desc, cr * area, area); + if (err) + return err; + } + if (*desc) + return "Unexpected data at end of game description"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int c = params->c, r = params->r, cr = c*r, area = cr * cr; + int i; + + precompute_sum_bits(); + + state->cr = cr; + state->xtype = params->xtype; + state->killer = params->killer; + + state->grid = snewn(area, digit); + state->pencil = snewn(area * cr, unsigned char); + memset(state->pencil, 0, area * cr); + state->immutable = snewn(area, unsigned char); + memset(state->immutable, FALSE, area); + + state->blocks = alloc_block_structure (c, r, area, cr, cr); + + if (params->killer) { + state->kblocks = alloc_block_structure (c, r, area, cr, area); + state->kgrid = snewn(area, digit); + } else { + state->kblocks = NULL; + state->kgrid = NULL; + } + state->completed = state->cheated = FALSE; + + desc = spec_to_grid(desc, state->grid, area); + for (i = 0; i < area; i++) + if (state->grid[i] != 0) + state->immutable[i] = TRUE; + + if (r == 1) { + char *err; + int *dsf; + assert(*desc == ','); + desc++; + err = spec_to_dsf(&desc, &dsf, cr, area); + assert(err == NULL); + dsf_to_blocks(dsf, state->blocks, cr, cr); + sfree(dsf); + } else { + int x, y; + + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + state->blocks->whichblock[y*cr+x] = (y/c) * c + (x/r); + } + make_blocks_from_whichblock(state->blocks); + + if (params->killer) { + char *err; + int *dsf; + assert(*desc == ','); + desc++; + err = spec_to_dsf(&desc, &dsf, cr, area); + assert(err == NULL); + dsf_to_blocks(dsf, state->kblocks, cr, area); + sfree(dsf); + make_blocks_from_whichblock(state->kblocks); + + assert(*desc == ','); + desc++; + desc = spec_to_grid(desc, state->kgrid, area); + } + assert(!*desc); + +#ifdef STANDALONE_SOLVER + /* + * Set up the block names for solver diagnostic output. + */ + { + char *p = (char *)(state->blocks->blocknames + cr); + + if (r == 1) { + for (i = 0; i < area; i++) { + int j = state->blocks->whichblock[i]; + if (!state->blocks->blocknames[j]) { + state->blocks->blocknames[j] = p; + p += 1 + sprintf(p, "starting at (%d,%d)", + 1 + i%cr, 1 + i/cr); + } + } + } else { + int bx, by; + for (by = 0; by < r; by++) + for (bx = 0; bx < c; bx++) { + state->blocks->blocknames[by*c+bx] = p; + p += 1 + sprintf(p, "(%d,%d)", bx+1, by+1); + } + } + assert(p - (char *)state->blocks->blocknames < (int)(cr*(sizeof(char *)+80))); + for (i = 0; i < cr; i++) + assert(state->blocks->blocknames[i]); + } +#endif + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + int cr = state->cr, area = cr * cr; + + ret->cr = state->cr; + ret->xtype = state->xtype; + ret->killer = state->killer; + + ret->blocks = state->blocks; + ret->blocks->refcount++; + + ret->kblocks = state->kblocks; + if (ret->kblocks) + ret->kblocks->refcount++; + + ret->grid = snewn(area, digit); + memcpy(ret->grid, state->grid, area); + + if (state->killer) { + ret->kgrid = snewn(area, digit); + memcpy(ret->kgrid, state->kgrid, area); + } else + ret->kgrid = NULL; + + ret->pencil = snewn(area * cr, unsigned char); + memcpy(ret->pencil, state->pencil, area * cr); + + ret->immutable = snewn(area, unsigned char); + memcpy(ret->immutable, state->immutable, area); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + free_block_structure(state->blocks); + if (state->kblocks) + free_block_structure(state->kblocks); + + sfree(state->immutable); + sfree(state->pencil); + sfree(state->grid); + if (state->kgrid) sfree(state->kgrid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *ai, char **error) +{ + int cr = state->cr; + char *ret; + digit *grid; + struct difficulty dlev; + + /* + * If we already have the solution in ai, save ourselves some + * time. + */ + if (ai) + return dupstr(ai); + + grid = snewn(cr*cr, digit); + memcpy(grid, state->grid, cr*cr); + dlev.maxdiff = DIFF_RECURSIVE; + dlev.maxkdiff = DIFF_KINTERSECT; + solver(cr, state->blocks, state->kblocks, state->xtype, grid, + state->kgrid, &dlev); + + *error = NULL; + + if (dlev.diff == DIFF_IMPOSSIBLE) + *error = "No solution exists for this puzzle"; + else if (dlev.diff == DIFF_AMBIGUOUS) + *error = "Multiple solutions exist for this puzzle"; + + if (*error) { + sfree(grid); + return NULL; + } + + ret = encode_solve_move(cr, grid); + + sfree(grid); + + return ret; +} + +static char *grid_text_format(int cr, struct block_structure *blocks, + int xtype, digit *grid) +{ + int vmod, hmod; + int x, y; + int totallen, linelen, nlines; + char *ret, *p, ch; + + /* + * For non-jigsaw Sudoku, we format in the way we always have, + * by having the digits unevenly spaced so that the dividing + * lines can fit in: + * + * . . | . . + * . . | . . + * ----+---- + * . . | . . + * . . | . . + * + * For jigsaw puzzles, however, we must leave space between + * _all_ pairs of digits for an optional dividing line, so we + * have to move to the rather ugly + * + * . . . . + * ------+------ + * . . | . . + * +---+ + * . . | . | . + * ------+ | + * . . . | . + * + * We deal with both cases using the same formatting code; we + * simply invent a vmod value such that there's a vertical + * dividing line before column i iff i is divisible by vmod + * (so it's r in the first case and 1 in the second), and hmod + * likewise for horizontal dividing lines. + */ + + if (blocks->r != 1) { + vmod = blocks->r; + hmod = blocks->c; + } else { + vmod = hmod = 1; + } + + /* + * Line length: we have cr digits, each with a space after it, + * and (cr-1)/vmod dividing lines, each with a space after it. + * The final space is replaced by a newline, but that doesn't + * affect the length. + */ + linelen = 2*(cr + (cr-1)/vmod); + + /* + * Number of lines: we have cr rows of digits, and (cr-1)/hmod + * dividing rows. + */ + nlines = cr + (cr-1)/hmod; + + /* + * Allocate the space. + */ + totallen = linelen * nlines; + ret = snewn(totallen+1, char); /* leave room for terminating NUL */ + + /* + * Write the text. + */ + p = ret; + for (y = 0; y < cr; y++) { + /* + * Row of digits. + */ + for (x = 0; x < cr; x++) { + /* + * Digit. + */ + digit d = grid[y*cr+x]; + + if (d == 0) { + /* + * Empty space: we usually write a dot, but we'll + * highlight spaces on the X-diagonals (in X mode) + * by using underscores instead. + */ + if (xtype && (ondiag0(y*cr+x) || ondiag1(y*cr+x))) + ch = '_'; + else + ch = '.'; + } else if (d <= 9) { + ch = '0' + d; + } else { + ch = 'a' + d-10; + } + + *p++ = ch; + if (x == cr-1) { + *p++ = '\n'; + continue; + } + *p++ = ' '; + + if ((x+1) % vmod) + continue; + + /* + * Optional dividing line. + */ + if (blocks->whichblock[y*cr+x] != blocks->whichblock[y*cr+x+1]) + ch = '|'; + else + ch = ' '; + *p++ = ch; + *p++ = ' '; + } + if (y == cr-1 || (y+1) % hmod) + continue; + + /* + * Dividing row. + */ + for (x = 0; x < cr; x++) { + int dwid; + int tl, tr, bl, br; + + /* + * Division between two squares. This varies + * complicatedly in length. + */ + dwid = 2; /* digit and its following space */ + if (x == cr-1) + dwid--; /* no following space at end of line */ + if (x > 0 && x % vmod == 0) + dwid++; /* preceding space after a divider */ + + if (blocks->whichblock[y*cr+x] != blocks->whichblock[(y+1)*cr+x]) + ch = '-'; + else + ch = ' '; + + while (dwid-- > 0) + *p++ = ch; + + if (x == cr-1) { + *p++ = '\n'; + break; + } + + if ((x+1) % vmod) + continue; + + /* + * Corner square. This is: + * - a space if all four surrounding squares are in + * the same block + * - a vertical line if the two left ones are in one + * block and the two right in another + * - a horizontal line if the two top ones are in one + * block and the two bottom in another + * - a plus sign in all other cases. (If we had a + * richer character set available we could break + * this case up further by doing fun things with + * line-drawing T-pieces.) + */ + tl = blocks->whichblock[y*cr+x]; + tr = blocks->whichblock[y*cr+x+1]; + bl = blocks->whichblock[(y+1)*cr+x]; + br = blocks->whichblock[(y+1)*cr+x+1]; + + if (tl == tr && tr == bl && bl == br) + ch = ' '; + else if (tl == bl && tr == br) + ch = '|'; + else if (tl == tr && bl == br) + ch = '-'; + else + ch = '+'; + + *p++ = ch; + } + } + + assert(p - ret == totallen); + *p = '\0'; + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + /* + * Formatting Killer puzzles as text is currently unsupported. I + * can't think of any sensible way of doing it which doesn't + * involve expanding the puzzle to such a large scale as to make + * it unusable. + */ + if (params->killer) + return FALSE; + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + assert(!state->kblocks); + return grid_text_format(state->cr, state->blocks, state->xtype, + state->grid); +} + +struct game_ui { + /* + * These are the coordinates of the currently highlighted + * square on the grid, if hshow = 1. + */ + int hx, hy; + /* + * This indicates whether the current highlight is a + * pencil-mark one or a real one. + */ + int hpencil; + /* + * This indicates whether or not we're showing the highlight + * (used to be hx = hy = -1); important so that when we're + * using the cursor keys it doesn't keep coming back at a + * fixed position. When hshow = 1, pressing a valid number + * or letter key or Space will enter that number or letter in the grid. + */ + int hshow; + /* + * This indicates whether we're using the highlight as a cursor; + * it means that it doesn't vanish on a keypress, and that it is + * allowed on immutable squares. + */ + int hcursor; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + int cr = newstate->cr; + /* + * We prevent pencil-mode highlighting of a filled square, unless + * we're using the cursor keys. So if the user has just filled in + * a square which we had a pencil-mode highlight in (by Undo, or + * by Redo, or by Solve), then we cancel the highlight. + */ + if (ui->hshow && ui->hpencil && !ui->hcursor && + newstate->grid[ui->hy * cr + ui->hx] != 0) { + ui->hshow = 0; + } +} + +struct game_drawstate { + int started; + int cr, xtype; + int tilesize; + digit *grid; + unsigned char *pencil; + unsigned char *hl; + /* This is scratch space used within a single call to game_redraw. */ + int nregions, *entered_items; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int cr = state->cr; + int tx, ty; + char buf[80]; + + button &= ~MOD_MASK; + + tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1; + ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1; + + if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) { + if (button == LEFT_BUTTON) { + if (state->immutable[ty*cr+tx]) { + ui->hshow = 0; + } else if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil == 0) { + ui->hshow = 0; + } else { + ui->hx = tx; + ui->hy = ty; + ui->hshow = 1; + ui->hpencil = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + if (button == RIGHT_BUTTON) { + /* + * Pencil-mode highlighting for non filled squares. + */ + if (state->grid[ty*cr+tx] == 0) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil) { + ui->hshow = 0; + } else { + ui->hpencil = 1; + ui->hx = tx; + ui->hy = ty; + ui->hshow = 1; + } + } else { + ui->hshow = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + } + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->hx, &ui->hy, cr, cr, 0); + ui->hshow = ui->hcursor = 1; + return ""; + } + if (ui->hshow && + (button == CURSOR_SELECT)) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + if (ui->hshow && + ((button >= '0' && button <= '9' && button - '0' <= cr) || + (button >= 'a' && button <= 'z' && button - 'a' + 10 <= cr) || + (button >= 'A' && button <= 'Z' && button - 'A' + 10 <= cr) || + button == CURSOR_SELECT2 || button == '\b')) { + int n = button - '0'; + if (button >= 'A' && button <= 'Z') + n = button - 'A' + 10; + if (button >= 'a' && button <= 'z') + n = button - 'a' + 10; + if (button == CURSOR_SELECT2 || button == '\b') + n = 0; + + /* + * Can't overwrite this square. This can only happen here + * if we're using the cursor keys. + */ + if (state->immutable[ui->hy*cr+ui->hx]) + return NULL; + + /* + * Can't make pencil marks in a filled square. Again, this + * can only become highlighted if we're using cursor keys. + */ + if (ui->hpencil && state->grid[ui->hy*cr+ui->hx]) + return NULL; + + sprintf(buf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); + + if (!ui->hcursor) ui->hshow = 0; + + return dupstr(buf); + } + + if (button == 'M' || button == 'm') + return dupstr("M"); + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int cr = from->cr; + game_state *ret; + int x, y, n; + + if (move[0] == 'S') { + const char *p; + + ret = dup_game(from); + ret->completed = ret->cheated = TRUE; + + p = move+1; + for (n = 0; n < cr*cr; n++) { + ret->grid[n] = atoi(p); + + if (!*p || ret->grid[n] < 1 || ret->grid[n] > cr) { + free_game(ret); + return NULL; + } + + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == ',') p++; + } + + return ret; + } else if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < cr && y >= 0 && y < cr && n >= 0 && n <= cr) { + + ret = dup_game(from); + if (move[0] == 'P' && n > 0) { + int index = (y*cr+x) * cr + (n-1); + ret->pencil[index] = !ret->pencil[index]; + } else { + ret->grid[y*cr+x] = n; + memset(ret->pencil + (y*cr+x)*cr, 0, cr); + + /* + * We've made a real change to the grid. Check to see + * if the game has been completed. + */ + if (!ret->completed && check_valid( + cr, ret->blocks, ret->kblocks, ret->kgrid, + ret->xtype, ret->grid)) { + ret->completed = TRUE; + } + } + return ret; + } else if (move[0] == 'M') { + /* + * Fill in absolutely all pencil marks in unfilled squares, + * for those who like to play by the rigorous approach of + * starting off in that state and eliminating things. + */ + ret = dup_game(from); + for (y = 0; y < cr; y++) { + for (x = 0; x < cr; x++) { + if (!ret->grid[y*cr+x]) { + memset(ret->pencil + (y*cr+x)*cr, 1, cr); + } + } + } + return ret; + } else + return NULL; /* couldn't parse move string */ +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1) +#define GETTILESIZE(cr, w) ( (double)(w-1) / (double)(cr+1) ) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = SIZE(params->c * params->r); + *y = SIZE(params->c * params->r); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_XDIAGONALS * 3 + 0] = 0.9F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_XDIAGONALS * 3 + 1] = 0.9F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_XDIAGONALS * 3 + 2] = 0.9F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_CLUE * 3 + 0] = 0.0F; + ret[COL_CLUE * 3 + 1] = 0.0F; + ret[COL_CLUE * 3 + 2] = 0.0F; + + ret[COL_USER * 3 + 0] = 0.0F; + ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_USER * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_KILLER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_KILLER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_KILLER * 3 + 2] = 0.1F * ret[COL_BACKGROUND * 3 + 2]; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int cr = state->cr; + + ds->started = FALSE; + ds->cr = cr; + ds->xtype = state->xtype; + ds->grid = snewn(cr*cr, digit); + memset(ds->grid, cr+2, cr*cr); + ds->pencil = snewn(cr*cr*cr, digit); + memset(ds->pencil, 0, cr*cr*cr); + ds->hl = snewn(cr*cr, unsigned char); + memset(ds->hl, 0, cr*cr); + /* + * ds->entered_items needs one row of cr entries per entity in + * which digits may not be duplicated. That's one for each row, + * each column, each block, each diagonal, and each Killer cage. + */ + ds->nregions = cr*3 + 2; + if (state->kblocks) + ds->nregions += state->kblocks->nr_blocks; + ds->entered_items = snewn(cr * ds->nregions, int); + ds->tilesize = 0; /* not decided yet */ + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->hl); + sfree(ds->pencil); + sfree(ds->grid); + sfree(ds->entered_items); + sfree(ds); +} + +static void draw_number(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y, int hl) +{ + int cr = state->cr; + int tx, ty, tw, th; + int cx, cy, cw, ch; + int col_killer = (hl & 32 ? COL_ERROR : COL_KILLER); + char str[20]; + + if (ds->grid[y*cr+x] == state->grid[y*cr+x] && + ds->hl[y*cr+x] == hl && + !memcmp(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr)) + return; /* no change required */ + + tx = BORDER + x * TILE_SIZE + 1 + GRIDEXTRA; + ty = BORDER + y * TILE_SIZE + 1 + GRIDEXTRA; + + cx = tx; + cy = ty; + cw = tw = TILE_SIZE-1-2*GRIDEXTRA; + ch = th = TILE_SIZE-1-2*GRIDEXTRA; + + if (x > 0 && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[y*cr+x-1]) + cx -= GRIDEXTRA, cw += GRIDEXTRA; + if (x+1 < cr && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[y*cr+x+1]) + cw += GRIDEXTRA; + if (y > 0 && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[(y-1)*cr+x]) + cy -= GRIDEXTRA, ch += GRIDEXTRA; + if (y+1 < cr && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[(y+1)*cr+x]) + ch += GRIDEXTRA; + + clip(dr, cx, cy, cw, ch); + + /* background needs erasing */ + draw_rect(dr, cx, cy, cw, ch, + ((hl & 15) == 1 ? COL_HIGHLIGHT : + (ds->xtype && (ondiag0(y*cr+x) || ondiag1(y*cr+x))) ? COL_XDIAGONALS : + COL_BACKGROUND)); + + /* + * Draw the corners of thick lines in corner-adjacent squares, + * which jut into this square by one pixel. + */ + if (x > 0 && y > 0 && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y-1)*cr+x-1]) + draw_rect(dr, tx-GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x+1 < cr && y > 0 && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y-1)*cr+x+1]) + draw_rect(dr, tx+TILE_SIZE-1-2*GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x > 0 && y+1 < cr && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y+1)*cr+x-1]) + draw_rect(dr, tx-GRIDEXTRA, ty+TILE_SIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + if (x+1 < cr && y+1 < cr && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y+1)*cr+x+1]) + draw_rect(dr, tx+TILE_SIZE-1-2*GRIDEXTRA, ty+TILE_SIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID); + + /* pencil-mode highlight */ + if ((hl & 15) == 2) { + int coords[6]; + coords[0] = cx; + coords[1] = cy; + coords[2] = cx+cw/2; + coords[3] = cy; + coords[4] = cx; + coords[5] = cy+ch/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + if (state->kblocks) { + int t = GRIDEXTRA * 3; + int kcx, kcy, kcw, kch; + int kl, kt, kr, kb; + int has_left = 0, has_right = 0, has_top = 0, has_bottom = 0; + + /* + * In non-jigsaw mode, the Killer cages are placed at a + * fixed offset from the outer edge of the cell dividing + * lines, so that they look right whether those lines are + * thick or thin. In jigsaw mode, however, doing this will + * sometimes cause the cage outlines in adjacent squares to + * fail to match up with each other, so we must offset a + * fixed amount from the _centre_ of the cell dividing + * lines. + */ + if (state->blocks->r == 1) { + kcx = tx; + kcy = ty; + kcw = tw; + kch = th; + } else { + kcx = cx; + kcy = cy; + kcw = cw; + kch = ch; + } + kl = kcx - 1; + kt = kcy - 1; + kr = kcx + kcw; + kb = kcy + kch; + + /* + * First, draw the lines dividing this area from neighbouring + * different areas. + */ + if (x == 0 || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[y*cr+x-1]) + has_left = 1, kl += t; + if (x+1 >= cr || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[y*cr+x+1]) + has_right = 1, kr -= t; + if (y == 0 || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x]) + has_top = 1, kt += t; + if (y+1 >= cr || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x]) + has_bottom = 1, kb -= t; + if (has_top) + draw_line(dr, kl, kt, kr, kt, col_killer); + if (has_bottom) + draw_line(dr, kl, kb, kr, kb, col_killer); + if (has_left) + draw_line(dr, kl, kt, kl, kb, col_killer); + if (has_right) + draw_line(dr, kr, kt, kr, kb, col_killer); + /* + * Now, take care of the corners (just as for the normal borders). + * We only need a corner if there wasn't a full edge. + */ + if (x > 0 && y > 0 && !has_left && !has_top + && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x-1]) + { + draw_line(dr, kl, kt + t, kl + t, kt + t, col_killer); + draw_line(dr, kl + t, kt, kl + t, kt + t, col_killer); + } + if (x+1 < cr && y > 0 && !has_right && !has_top + && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x+1]) + { + draw_line(dr, kcx + kcw - t, kt + t, kcx + kcw, kt + t, col_killer); + draw_line(dr, kcx + kcw - t, kt, kcx + kcw - t, kt + t, col_killer); + } + if (x > 0 && y+1 < cr && !has_left && !has_bottom + && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x-1]) + { + draw_line(dr, kl, kcy + kch - t, kl + t, kcy + kch - t, col_killer); + draw_line(dr, kl + t, kcy + kch - t, kl + t, kcy + kch, col_killer); + } + if (x+1 < cr && y+1 < cr && !has_right && !has_bottom + && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x+1]) + { + draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw - t, kcy + kch, col_killer); + draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw, kcy + kch - t, col_killer); + } + + } + + if (state->killer && state->kgrid[y*cr+x]) { + sprintf (str, "%d", state->kgrid[y*cr+x]); + draw_text(dr, tx + GRIDEXTRA * 4, ty + GRIDEXTRA * 4 + TILE_SIZE/4, + FONT_VARIABLE, TILE_SIZE/4, ALIGN_VNORMAL | ALIGN_HLEFT, + col_killer, str); + } + + /* new number needs drawing? */ + if (state->grid[y*cr+x]) { + str[1] = '\0'; + str[0] = state->grid[y*cr+x] + '0'; + if (str[0] > '9') + str[0] += 'a' - ('9'+1); + draw_text(dr, tx + TILE_SIZE/2, ty + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, + state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str); + } else { + int i, j, npencil; + int pl, pr, pt, pb; + float bestsize; + int pw, ph, minph, pbest, fontsize; + + /* Count the pencil marks required. */ + for (i = npencil = 0; i < cr; i++) + if (state->pencil[(y*cr+x)*cr+i]) + npencil++; + if (npencil) { + + minph = 2; + + /* + * Determine the bounding rectangle within which we're going + * to put the pencil marks. + */ + /* Start with the whole square */ + pl = tx + GRIDEXTRA; + pr = pl + TILE_SIZE - GRIDEXTRA; + pt = ty + GRIDEXTRA; + pb = pt + TILE_SIZE - GRIDEXTRA; + if (state->killer) { + /* + * Make space for the Killer cages. We do this + * unconditionally, for uniformity between squares, + * rather than making it depend on whether a Killer + * cage edge is actually present on any given side. + */ + pl += GRIDEXTRA * 3; + pr -= GRIDEXTRA * 3; + pt += GRIDEXTRA * 3; + pb -= GRIDEXTRA * 3; + if (state->kgrid[y*cr+x] != 0) { + /* Make further space for the Killer number. */ + pt += TILE_SIZE/4; + /* minph--; */ + } + } + + /* + * We arrange our pencil marks in a grid layout, with + * the number of rows and columns adjusted to allow the + * maximum font size. + * + * So now we work out what the grid size ought to be. + */ + bestsize = 0.0; + pbest = 0; + /* Minimum */ + for (pw = 3; pw < max(npencil,4); pw++) { + float fw, fh, fs; + + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + fw = (pr - pl) / (float)pw; + fh = (pb - pt) / (float)ph; + fs = min(fw, fh); + if (fs > bestsize) { + bestsize = fs; + pbest = pw; + } + } + assert(pbest > 0); + pw = pbest; + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + + /* + * Now we've got our grid dimensions, work out the pixel + * size of a grid element, and round it to the nearest + * pixel. (We don't want rounding errors to make the + * grid look uneven at low pixel sizes.) + */ + fontsize = min((pr - pl) / pw, (pb - pt) / ph); + + /* + * Centre the resulting figure in the square. + */ + pl = tx + (TILE_SIZE - fontsize * pw) / 2; + pt = ty + (TILE_SIZE - fontsize * ph) / 2; + + /* + * And move it down a bit if it's collided with the + * Killer cage number. + */ + if (state->killer && state->kgrid[y*cr+x] != 0) { + pt = max(pt, ty + GRIDEXTRA * 3 + TILE_SIZE/4); + } + + /* + * Now actually draw the pencil marks. + */ + for (i = j = 0; i < cr; i++) + if (state->pencil[(y*cr+x)*cr+i]) { + int dx = j % pw, dy = j / pw; + + str[1] = '\0'; + str[0] = i + '1'; + if (str[0] > '9') + str[0] += 'a' - ('9'+1); + draw_text(dr, pl + fontsize * (2*dx+1) / 2, + pt + fontsize * (2*dy+1) / 2, + FONT_VARIABLE, fontsize, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } + } + } + + unclip(dr); + + draw_update(dr, cx, cy, cw, ch); + + ds->grid[y*cr+x] = state->grid[y*cr+x]; + memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr); + ds->hl[y*cr+x] = hl; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int cr = state->cr; + int x, y; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed + * and can vary with front ends. To be on the safe side, + * all games should start by drawing a big + * background-colour rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND); + + /* + * Draw the grid. We draw it as a big thick rectangle of + * COL_GRID initially; individual calls to draw_number() + * will poke the right-shaped holes in it. + */ + draw_rect(dr, BORDER-GRIDEXTRA, BORDER-GRIDEXTRA, + cr*TILE_SIZE+1+2*GRIDEXTRA, cr*TILE_SIZE+1+2*GRIDEXTRA, + COL_GRID); + } + + /* + * This array is used to keep track of rows, columns and boxes + * which contain a number more than once. + */ + for (x = 0; x < cr * ds->nregions; x++) + ds->entered_items[x] = 0; + for (x = 0; x < cr; x++) + for (y = 0; y < cr; y++) { + digit d = state->grid[y*cr+x]; + if (d) { + int box, kbox; + + /* Rows */ + ds->entered_items[x*cr+d-1]++; + + /* Columns */ + ds->entered_items[(y+cr)*cr+d-1]++; + + /* Blocks */ + box = state->blocks->whichblock[y*cr+x]; + ds->entered_items[(box+2*cr)*cr+d-1]++; + + /* Diagonals */ + if (ds->xtype) { + if (ondiag0(y*cr+x)) + ds->entered_items[(3*cr)*cr+d-1]++; + if (ondiag1(y*cr+x)) + ds->entered_items[(3*cr+1)*cr+d-1]++; + } + + /* Killer cages */ + if (state->kblocks) { + kbox = state->kblocks->whichblock[y*cr+x]; + ds->entered_items[(kbox+3*cr+2)*cr+d-1]++; + } + } + } + + /* + * Draw any numbers which need redrawing. + */ + for (x = 0; x < cr; x++) { + for (y = 0; y < cr; y++) { + int highlight = 0; + digit d = state->grid[y*cr+x]; + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + highlight = 1; + + /* Highlight active input areas. */ + if (x == ui->hx && y == ui->hy && ui->hshow) + highlight = ui->hpencil ? 2 : 1; + + /* Mark obvious errors (ie, numbers which occur more than once + * in a single row, column, or box). */ + if (d && (ds->entered_items[x*cr+d-1] > 1 || + ds->entered_items[(y+cr)*cr+d-1] > 1 || + ds->entered_items[(state->blocks->whichblock[y*cr+x] + +2*cr)*cr+d-1] > 1 || + (ds->xtype && ((ondiag0(y*cr+x) && + ds->entered_items[(3*cr)*cr+d-1] > 1) || + (ondiag1(y*cr+x) && + ds->entered_items[(3*cr+1)*cr+d-1]>1)))|| + (state->kblocks && + ds->entered_items[(state->kblocks->whichblock[y*cr+x] + +3*cr+2)*cr+d-1] > 1))) + highlight |= 16; + + if (d && state->kblocks) { + if (check_killer_cage_sum( + state->kblocks, state->kgrid, state->grid, + state->kblocks->whichblock[y*cr+x]) == 0) + highlight |= 32; + } + + draw_number(dr, ds, state, x, y, highlight); + } + } + + /* + * Update the _entire_ grid if necessary. + */ + if (!ds->started) { + draw_update(dr, 0, 0, SIZE(cr), SIZE(cr)); + ds->started = TRUE; + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + if (state->completed) + return FALSE; + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 9mm squares by default. They should be quite big + * for this game, because players will want to jot down no end + * of pencil marks in the squares. + */ + game_compute_size(params, 900, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +/* + * Subfunction to draw the thick lines between cells. In order to do + * this using the line-drawing rather than rectangle-drawing API (so + * as to get line thicknesses to scale correctly) and yet have + * correctly mitred joins between lines, we must do this by tracing + * the boundary of each sub-block and drawing it in one go as a + * single polygon. + * + * This subfunction is also reused with thinner dotted lines to + * outline the Killer cages, this time offsetting the outline toward + * the interior of the affected squares. + */ +static void outline_block_structure(drawing *dr, game_drawstate *ds, + const game_state *state, + struct block_structure *blocks, + int ink, int inset) +{ + int cr = state->cr; + int *coords; + int bi, i, n; + int x, y, dx, dy, sx, sy, sdx, sdy; + + /* + * Maximum perimeter of a k-omino is 2k+2. (Proof: start + * with k unconnected squares, with total perimeter 4k. + * Now repeatedly join two disconnected components + * together into a larger one; every time you do so you + * remove at least two unit edges, and you require k-1 of + * these operations to create a single connected piece, so + * you must have at most 4k-2(k-1) = 2k+2 unit edges left + * afterwards.) + */ + coords = snewn(4*cr+4, int); /* 2k+2 points, 2 coords per point */ + + /* + * Iterate over all the blocks. + */ + for (bi = 0; bi < blocks->nr_blocks; bi++) { + if (blocks->nr_squares[bi] == 0) + continue; + + /* + * For each block, find a starting square within it + * which has a boundary at the left. + */ + for (i = 0; i < cr; i++) { + int j = blocks->blocks[bi][i]; + if (j % cr == 0 || blocks->whichblock[j-1] != bi) + break; + } + assert(i < cr); /* every block must have _some_ leftmost square */ + x = blocks->blocks[bi][i] % cr; + y = blocks->blocks[bi][i] / cr; + dx = -1; + dy = 0; + + /* + * Now begin tracing round the perimeter. At all + * times, (x,y) describes some square within the + * block, and (x+dx,y+dy) is some adjacent square + * outside it; so the edge between those two squares + * is always an edge of the block. + */ + sx = x, sy = y, sdx = dx, sdy = dy; /* save starting position */ + n = 0; + do { + int cx, cy, tx, ty, nin; + + /* + * Advance to the next edge, by looking at the two + * squares beyond it. If they're both outside the block, + * we turn right (by leaving x,y the same and rotating + * dx,dy clockwise); if they're both inside, we turn + * left (by rotating dx,dy anticlockwise and contriving + * to leave x+dx,y+dy unchanged); if one of each, we go + * straight on (and may enforce by assertion that + * they're one of each the _right_ way round). + */ + nin = 0; + tx = x - dy + dx; + ty = y + dx + dy; + nin += (tx >= 0 && tx < cr && ty >= 0 && ty < cr && + blocks->whichblock[ty*cr+tx] == bi); + tx = x - dy; + ty = y + dx; + nin += (tx >= 0 && tx < cr && ty >= 0 && ty < cr && + blocks->whichblock[ty*cr+tx] == bi); + if (nin == 0) { + /* + * Turn right. + */ + int tmp; + tmp = dx; + dx = -dy; + dy = tmp; + } else if (nin == 2) { + /* + * Turn left. + */ + int tmp; + + x += dx; + y += dy; + + tmp = dx; + dx = dy; + dy = -tmp; + + x -= dx; + y -= dy; + } else { + /* + * Go straight on. + */ + x -= dy; + y += dx; + } + + /* + * Now enforce by assertion that we ended up + * somewhere sensible. + */ + assert(x >= 0 && x < cr && y >= 0 && y < cr && + blocks->whichblock[y*cr+x] == bi); + assert(x+dx < 0 || x+dx >= cr || y+dy < 0 || y+dy >= cr || + blocks->whichblock[(y+dy)*cr+(x+dx)] != bi); + + /* + * Record the point we just went past at one end of the + * edge. To do this, we translate (x,y) down and right + * by half a unit (so they're describing a point in the + * _centre_ of the square) and then translate back again + * in a manner rotated by dy and dx. + */ + assert(n < 2*cr+2); + cx = ((2*x+1) + dy + dx) / 2; + cy = ((2*y+1) - dx + dy) / 2; + coords[2*n+0] = BORDER + cx * TILE_SIZE; + coords[2*n+1] = BORDER + cy * TILE_SIZE; + coords[2*n+0] -= dx * inset; + coords[2*n+1] -= dy * inset; + if (nin == 0) { + /* + * We turned right, so inset this corner back along + * the edge towards the centre of the square. + */ + coords[2*n+0] -= dy * inset; + coords[2*n+1] += dx * inset; + } else if (nin == 2) { + /* + * We turned left, so inset this corner further + * _out_ along the edge into the next square. + */ + coords[2*n+0] += dy * inset; + coords[2*n+1] -= dx * inset; + } + n++; + + } while (x != sx || y != sy || dx != sdx || dy != sdy); + + /* + * That's our polygon; now draw it. + */ + draw_polygon(dr, coords, n, -1, ink); + } + + sfree(coords); +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int cr = state->cr; + int ink = print_mono_colour(dr, 0); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, 3 * TILE_SIZE / 40); + draw_rect_outline(dr, BORDER, BORDER, cr*TILE_SIZE, cr*TILE_SIZE, ink); + + /* + * Highlight X-diagonal squares. + */ + if (state->xtype) { + int i; + int xhighlight = print_grey_colour(dr, 0.90F); + + for (i = 0; i < cr; i++) + draw_rect(dr, BORDER + i*TILE_SIZE, BORDER + i*TILE_SIZE, + TILE_SIZE, TILE_SIZE, xhighlight); + for (i = 0; i < cr; i++) + if (i*2 != cr-1) /* avoid redoing centre square, just for fun */ + draw_rect(dr, BORDER + i*TILE_SIZE, + BORDER + (cr-1-i)*TILE_SIZE, + TILE_SIZE, TILE_SIZE, xhighlight); + } + + /* + * Main grid. + */ + for (x = 1; x < cr; x++) { + print_line_width(dr, TILE_SIZE / 40); + draw_line(dr, BORDER+x*TILE_SIZE, BORDER, + BORDER+x*TILE_SIZE, BORDER+cr*TILE_SIZE, ink); + } + for (y = 1; y < cr; y++) { + print_line_width(dr, TILE_SIZE / 40); + draw_line(dr, BORDER, BORDER+y*TILE_SIZE, + BORDER+cr*TILE_SIZE, BORDER+y*TILE_SIZE, ink); + } + + /* + * Thick lines between cells. + */ + print_line_width(dr, 3 * TILE_SIZE / 40); + outline_block_structure(dr, ds, state, state->blocks, ink, 0); + + /* + * Killer cages and their totals. + */ + if (state->kblocks) { + print_line_width(dr, TILE_SIZE / 40); + print_line_dotted(dr, TRUE); + outline_block_structure(dr, ds, state, state->kblocks, ink, + 5 * TILE_SIZE / 40); + print_line_dotted(dr, FALSE); + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + if (state->kgrid[y*cr+x]) { + char str[20]; + sprintf(str, "%d", state->kgrid[y*cr+x]); + draw_text(dr, + BORDER+x*TILE_SIZE + 7*TILE_SIZE/40, + BORDER+y*TILE_SIZE + 16*TILE_SIZE/40, + FONT_VARIABLE, TILE_SIZE/4, + ALIGN_VNORMAL | ALIGN_HLEFT, + ink, str); + } + } + + /* + * Standard (non-Killer) clue numbers. + */ + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) + if (state->grid[y*cr+x]) { + char str[2]; + str[1] = '\0'; + str[0] = state->grid[y*cr+x] + '0'; + if (str[0] > '9') + str[0] += 'a' - ('9'+1); + draw_text(dr, BORDER + x*TILE_SIZE + TILE_SIZE/2, + BORDER + y*TILE_SIZE + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } +} + +#ifdef COMBINED +#define thegame solo +#endif + +const struct game thegame = { + "Solo", "games.solo", "solo", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + struct difficulty dlev; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + solver_show_working = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + dlev.maxdiff = DIFF_RECURSIVE; + dlev.maxkdiff = DIFF_KINTERSECT; + solver(s->cr, s->blocks, s->kblocks, s->xtype, s->grid, s->kgrid, &dlev); + if (grade) { + printf("Difficulty rating: %s\n", + dlev.diff==DIFF_BLOCK ? "Trivial (blockwise positional elimination only)": + dlev.diff==DIFF_SIMPLE ? "Basic (row/column/number elimination required)": + dlev.diff==DIFF_INTERSECT ? "Intermediate (intersectional analysis required)": + dlev.diff==DIFF_SET ? "Advanced (set elimination required)": + dlev.diff==DIFF_EXTREME ? "Extreme (complex non-recursive techniques required)": + dlev.diff==DIFF_RECURSIVE ? "Unreasonable (guesswork and backtracking required)": + dlev.diff==DIFF_AMBIGUOUS ? "Ambiguous (multiple solutions exist)": + dlev.diff==DIFF_IMPOSSIBLE ? "Impossible (no solution exists)": + "INTERNAL ERROR: unrecognised difficulty code"); + if (p->killer) + printf("Killer difficulty: %s\n", + dlev.kdiff==DIFF_KSINGLE ? "Trivial (single square cages only)": + dlev.kdiff==DIFF_KMINMAX ? "Simple (maximum sum analysis required)": + dlev.kdiff==DIFF_KSUMS ? "Intermediate (sum possibilities)": + dlev.kdiff==DIFF_KINTERSECT ? "Advanced (sum region intersections)": + "INTERNAL ERROR: unrecognised difficulty code"); + } else { + printf("%s\n", grid_text_format(s->cr, s->blocks, s->xtype, s->grid)); + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/tdq.c b/apps/plugins/puzzles/tdq.c new file mode 100644 index 0000000000..43c9c35de5 --- /dev/null +++ b/apps/plugins/puzzles/tdq.c @@ -0,0 +1,88 @@ +/* + * tdq.c: implement a 'to-do queue', a simple de-duplicating to-do + * list mechanism. + */ + +#include "rbassert.h" + +#include "puzzles.h" + +/* + * Implementation: a tdq consists of a circular buffer of size n + * storing the integers currently in the queue, plus an array of n + * booleans indicating whether each integer is already there. + * + * Using a circular buffer of size n to store between 0 and n items + * inclusive has an obvious failure mode: if the input and output + * pointers are the same, how do you know whether that means the + * buffer is full or empty? + * + * In this application we have a simple way to tell: in the former + * case, the flags array is all 1s, and in the latter case it's all + * 0s. So we could spot that case and check, say, flags[0]. + * + * However, it's even easier to simply determine whether the queue is + * non-empty by testing flags[buffer[op]] - that way we don't even + * _have_ to compare ip against op. + */ + +struct tdq { + int n; + int *queue; + int ip, op; /* in pointer, out pointer */ + char *flags; +}; + +tdq *tdq_new(int n) +{ + int i; + tdq *tdq = snew(struct tdq); + tdq->queue = snewn(n, int); + tdq->flags = snewn(n, char); + for (i = 0; i < n; i++) { + tdq->queue[i] = 0; + tdq->flags[i] = 0; + } + tdq->n = n; + tdq->ip = tdq->op = 0; + return tdq; +} + +void tdq_free(tdq *tdq) +{ + sfree(tdq->queue); + sfree(tdq->flags); + sfree(tdq); +} + +void tdq_add(tdq *tdq, int k) +{ + assert((unsigned)k < (unsigned)tdq->n); + if (!tdq->flags[k]) { + tdq->queue[tdq->ip] = k; + tdq->flags[k] = 1; + if (++tdq->ip == tdq->n) + tdq->ip = 0; + } +} + +int tdq_remove(tdq *tdq) +{ + int ret = tdq->queue[tdq->op]; + + if (!tdq->flags[ret]) + return -1; + + tdq->flags[ret] = 0; + if (++tdq->op == tdq->n) + tdq->op = 0; + + return ret; +} + +void tdq_fill(tdq *tdq) +{ + int i; + for (i = 0; i < tdq->n; i++) + tdq_add(tdq, i); +} diff --git a/apps/plugins/puzzles/tents.R b/apps/plugins/puzzles/tents.R new file mode 100644 index 0000000000..557f929840 --- /dev/null +++ b/apps/plugins/puzzles/tents.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +TENTS_EXTRA = maxflow dsf + +tents : [X] GTK COMMON tents TENTS_EXTRA tents-icon|no-icon + +tents : [G] WINDOWS COMMON tents TENTS_EXTRA tents.res|noicon.res + +ALL += tents[COMBINED] TENTS_EXTRA + +tentssolver : [U] tents[STANDALONE_SOLVER] TENTS_EXTRA STANDALONE +tentssolver : [C] tents[STANDALONE_SOLVER] TENTS_EXTRA STANDALONE + +!begin am gtk +GAMES += tents +!end + +!begin >list.c + A(tents) \ +!end + +!begin >gamedesc.txt +tents:tents.exe:Tents:Tent-placing puzzle:Place a tent next to each tree. +!end diff --git a/apps/plugins/puzzles/tents.c b/apps/plugins/puzzles/tents.c new file mode 100644 index 0000000000..1bc7a85188 --- /dev/null +++ b/apps/plugins/puzzles/tents.c @@ -0,0 +1,2740 @@ +/* + * tents.c: Puzzle involving placing tents next to trees subject to + * some confusing conditions. + * + * TODO: + * + * - it might be nice to make setter-provided tent/nontent clues + * inviolable? + * * on the other hand, this would introduce considerable extra + * complexity and size into the game state; also inviolable + * clues would have to be marked as such somehow, in an + * intrusive and annoying manner. Since they're never + * generated by _my_ generator, I'm currently more inclined + * not to bother. + * + * - more difficult levels at the top end? + * * for example, sometimes we can deduce that two BLANKs in + * the same row are each adjacent to the same unattached tree + * and to nothing else, implying that they can't both be + * tents; this enables us to rule out some extra combinations + * in the row-based deduction loop, and hence deduce more + * from the number in that row than we could otherwise do. + * * that by itself doesn't seem worth implementing a new + * difficulty level for, but if I can find a few more things + * like that then it might become worthwhile. + * * I wonder if there's a sensible heuristic for where to + * guess which would make a recursive solver viable? + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "maxflow.h" + +/* + * Design discussion + * ----------------- + * + * The rules of this puzzle as available on the WWW are poorly + * specified. The bits about tents having to be orthogonally + * adjacent to trees, tents not being even diagonally adjacent to + * one another, and the number of tents in each row and column + * being given are simple enough; the difficult bit is the + * tent-to-tree matching. + * + * Some sources use simplistic wordings such as `each tree is + * exactly connected to only one tent', which is extremely unclear: + * it's easy to read erroneously as `each tree is _orthogonally + * adjacent_ to exactly one tent', which is definitely incorrect. + * Even the most coherent sources I've found don't do a much better + * job of stating the rule. + * + * A more precise statement of the rule is that it must be possible + * to find a bijection f between tents and trees such that each + * tree T is orthogonally adjacent to the tent f(T), but that a + * tent is permitted to be adjacent to other trees in addition to + * its own. This slightly non-obvious criterion is what gives this + * puzzle most of its subtlety. + * + * However, there's a particularly subtle ambiguity left over. Is + * the bijection between tents and trees required to be _unique_? + * In other words, is that bijection conceptually something the + * player should be able to exhibit as part of the solution (even + * if they aren't actually required to do so)? Or is it sufficient + * to have a unique _placement_ of the tents which gives rise to at + * least one suitable bijection? + * + * The puzzle shown to the right of this .T. 2 *T* 2 + * paragraph illustrates the problem. There T.T 0 -> T-T 0 + * are two distinct bijections available. .T. 2 *T* 2 + * The answer to the above question will + * determine whether it's a valid puzzle. 202 202 + * + * This is an important question, because it affects both the + * player and the generator. Eventually I found all the instances + * of this puzzle I could Google up, solved them all by hand, and + * verified that in all cases the tree/tent matching was uniquely + * determined given the tree and tent positions. Therefore, the + * puzzle as implemented in this source file takes the following + * policy: + * + * - When checking a user-supplied solution for correctness, only + * verify that there exists _at least_ one matching. + * - When generating a puzzle, enforce that there must be + * _exactly_ one. + * + * Algorithmic implications + * ------------------------ + * + * Another way of phrasing the tree/tent matching criterion is to + * say that the bipartite adjacency graph between trees and tents + * has a perfect matching. That is, if you construct a graph which + * has a vertex per tree and a vertex per tent, and an edge between + * any tree and tent which are orthogonally adjacent, it is + * possible to find a set of N edges of that graph (where N is the + * number of trees and also the number of tents) which between them + * connect every tree to every tent. + * + * The most efficient known algorithms for finding such a matching + * given a graph, as far as I'm aware, are the Munkres assignment + * algorithm (also known as the Hungarian algorithm) and the + * Ford-Fulkerson algorithm (for finding optimal flows in + * networks). Each of these takes O(N^3) running time; so we're + * talking O(N^3) time to verify any candidate solution to this + * puzzle. That's just about OK if you're doing it once per mouse + * click (and in fact not even that, since the sensible thing to do + * is check all the _other_ puzzle criteria and only wade into this + * quagmire if none are violated); but if the solver had to keep + * doing N^3 work internally, then it would probably end up with + * more like N^5 or N^6 running time, and grid generation would + * become very clunky. + * + * Fortunately, I've been able to prove a very useful property of + * _unique_ perfect matchings, by adapting the proof of Hall's + * Marriage Theorem. For those unaware of Hall's Theorem, I'll + * recap it and its proof: it states that a bipartite graph + * contains a perfect matching iff every set of vertices on the + * left side of the graph have a neighbourhood _at least_ as big on + * the right. + * + * This condition is obviously satisfied if a perfect matching does + * exist; each left-side node has a distinct right-side node which + * is the one assigned to it by the matching, and thus any set of n + * left vertices must have a combined neighbourhood containing at + * least the n corresponding right vertices, and possibly others + * too. Alternatively, imagine if you had (say) three left-side + * nodes all of which were connected to only two right-side nodes + * between them: any perfect matching would have to assign one of + * those two right nodes to each of the three left nodes, and still + * give the three left nodes a different right node each. This is + * of course impossible. + * + * To prove the converse (that if every subset of left vertices + * satisfies the Hall condition then a perfect matching exists), + * consider trying to find a proper subset of the left vertices + * which _exactly_ satisfies the Hall condition: that is, its right + * neighbourhood is precisely the same size as it. If we can find + * such a subset, then we can split the bipartite graph into two + * smaller ones: one consisting of the left subset and its right + * neighbourhood, the other consisting of everything else. Edges + * from the left side of the former graph to the right side of the + * latter do not exist, by construction; edges from the right side + * of the former to the left of the latter cannot be part of any + * perfect matching because otherwise the left subset would not be + * left with enough distinct right vertices to connect to (this is + * exactly the same deduction used in Solo's set analysis). You can + * then prove (left as an exercise) that both these smaller graphs + * still satisfy the Hall condition, and therefore the proof will + * follow by induction. + * + * There's one other possibility, which is the case where _no_ + * proper subset of the left vertices has a right neighbourhood of + * exactly the same size. That is, every left subset has a strictly + * _larger_ right neighbourhood. In this situation, we can simply + * remove an _arbitrary_ edge from the graph. This cannot reduce + * the size of any left subset's right neighbourhood by more than + * one, so if all neighbourhoods were strictly bigger than they + * needed to be initially, they must now still be _at least as big_ + * as they need to be. So we can keep throwing out arbitrary edges + * until we find a set which exactly satisfies the Hall condition, + * and then proceed as above. [] + * + * That's Hall's theorem. I now build on this by examining the + * circumstances in which a bipartite graph can have a _unique_ + * perfect matching. It is clear that in the second case, where no + * left subset exactly satisfies the Hall condition and so we can + * remove an arbitrary edge, there cannot be a unique perfect + * matching: given one perfect matching, we choose our arbitrary + * removed edge to be one of those contained in it, and then we can + * still find a perfect matching in the remaining graph, which will + * be a distinct perfect matching in the original. + * + * So it is a necessary condition for a unique perfect matching + * that there must be at least one proper left subset which + * _exactly_ satisfies the Hall condition. But now consider the + * smaller graph constructed by taking that left subset and its + * neighbourhood: if the graph as a whole had a unique perfect + * matching, then so must this smaller one, which means we can find + * a proper left subset _again_, and so on. Repeating this process + * must eventually reduce us to a graph with only one left-side + * vertex (so there are no proper subsets at all); this vertex must + * be connected to only one right-side vertex, and hence must be so + * in the original graph as well (by construction). So we can + * discard this vertex pair from the graph, and any other edges + * that involved it (which will by construction be from other left + * vertices only), and the resulting smaller graph still has a + * unique perfect matching which means we can do the same thing + * again. + * + * In other words, given any bipartite graph with a unique perfect + * matching, we can find that matching by the following extremely + * simple algorithm: + * + * - Find a left-side vertex which is only connected to one + * right-side vertex. + * - Assign those vertices to one another, and therefore discard + * any other edges connecting to that right vertex. + * - Repeat until all vertices have been matched. + * + * This algorithm can be run in O(V+E) time (where V is the number + * of vertices and E is the number of edges in the graph), and the + * only way it can fail is if there is not a unique perfect + * matching (either because there is no matching at all, or because + * it isn't unique; but it can't distinguish those cases). + * + * Thus, the internal solver in this source file can be confident + * that if the tree/tent matching is uniquely determined by the + * tree and tent positions, it can find it using only this kind of + * obvious and simple operation: assign a tree to a tent if it + * cannot possibly belong to any other tent, and vice versa. If the + * solver were _only_ trying to determine the matching, even that + * `vice versa' wouldn't be required; but it can come in handy when + * not all the tents have been placed yet. I can therefore be + * reasonably confident that as long as my solver doesn't need to + * cope with grids that have a non-unique matching, it will also + * not need to do anything complicated like set analysis between + * trees and tents. + */ + +/* + * In standalone solver mode, `verbose' is a variable which can be + * set by command-line option; in debugging mode it's simply always + * true. + */ +#if defined STANDALONE_SOLVER +#define SOLVER_DIAGNOSTICS +int verbose = FALSE; +#elif defined SOLVER_DIAGNOSTICS +#define verbose TRUE +#endif + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(TRICKY,Tricky,t) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const tents_diffnames[] = { DIFFLIST(TITLE) }; +static char const tents_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +enum { + COL_BACKGROUND, + COL_GRID, + COL_GRASS, + COL_TREETRUNK, + COL_TREELEAF, + COL_TENT, + COL_ERROR, + COL_ERRTEXT, + COL_ERRTRUNK, + NCOLOURS +}; + +enum { BLANK, TREE, TENT, NONTENT, MAGIC }; + +struct game_params { + int w, h; + int diff; +}; + +struct numbers { + int refcount; + int *numbers; +}; + +struct game_state { + game_params p; + char *grid; + struct numbers *numbers; + int completed, used_solve; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 8; + ret->diff = DIFF_EASY; + + return ret; +} + +static const struct game_params tents_presets[] = { + {8, 8, DIFF_EASY}, + {8, 8, DIFF_TRICKY}, + {10, 10, DIFF_EASY}, + {10, 10, DIFF_TRICKY}, + {15, 15, DIFF_EASY}, + {15, 15, DIFF_TRICKY}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(tents_presets)) + return FALSE; + + ret = snew(game_params); + *ret = tents_presets[i]; + + sprintf(str, "%dx%d %s", ret->w, ret->h, tents_diffnames[ret->diff]); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == tents_diffchars[i]) + params->diff = i; + if (*string) string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[120]; + + sprintf(buf, "%dx%d", params->w, params->h); + if (full) + sprintf(buf + strlen(buf), "d%c", + tents_diffchars[params->diff]); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + /* + * Generating anything under 4x4 runs into trouble of one kind + * or another. + */ + if (params->w < 4 || params->h < 4) + return "Width and height must both be at least four"; + return NULL; +} + +/* + * Scratch space for solver. + */ +enum { N, U, L, R, D, MAXDIR }; /* link directions */ +#define dx(d) ( ((d)==R) - ((d)==L) ) +#define dy(d) ( ((d)==D) - ((d)==U) ) +#define F(d) ( U + D - (d) ) +struct solver_scratch { + char *links; /* mapping between trees and tents */ + int *locs; + char *place, *mrows, *trows; +}; + +static struct solver_scratch *new_scratch(int w, int h) +{ + struct solver_scratch *ret = snew(struct solver_scratch); + + ret->links = snewn(w*h, char); + ret->locs = snewn(max(w, h), int); + ret->place = snewn(max(w, h), char); + ret->mrows = snewn(3 * max(w, h), char); + ret->trows = snewn(3 * max(w, h), char); + + return ret; +} + +static void free_scratch(struct solver_scratch *sc) +{ + sfree(sc->trows); + sfree(sc->mrows); + sfree(sc->place); + sfree(sc->locs); + sfree(sc->links); + sfree(sc); +} + +/* + * Solver. Returns 0 for impossibility, 1 for success, 2 for + * ambiguity or failure to converge. + */ +static int tents_solve(int w, int h, const char *grid, int *numbers, + char *soln, struct solver_scratch *sc, int diff) +{ + int x, y, d, i, j; + char *mrow, *trow, *trow1, *trow2; + + /* + * Set up solver data. + */ + memset(sc->links, N, w*h); + + /* + * Set up solution array. + */ + memcpy(soln, grid, w*h); + + /* + * Main solver loop. + */ + while (1) { + int done_something = FALSE; + + /* + * Any tent which has only one unattached tree adjacent to + * it can be tied to that tree. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (soln[y*w+x] == TENT && !sc->links[y*w+x]) { + int linkd = 0; + + for (d = 1; d < MAXDIR; d++) { + int x2 = x + dx(d), y2 = y + dy(d); + if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && + soln[y2*w+x2] == TREE && + !sc->links[y2*w+x2]) { + if (linkd) + break; /* found more than one */ + else + linkd = d; + } + } + + if (d == MAXDIR && linkd == 0) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("tent at %d,%d cannot link to anything\n", + x, y); +#endif + return 0; /* no solution exists */ + } else if (d == MAXDIR) { + int x2 = x + dx(linkd), y2 = y + dy(linkd); + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("tent at %d,%d can only link to tree at" + " %d,%d\n", x, y, x2, y2); +#endif + + sc->links[y*w+x] = linkd; + sc->links[y2*w+x2] = F(linkd); + done_something = TRUE; + } + } + + if (done_something) + continue; + if (diff < 0) + break; /* don't do anything else! */ + + /* + * Mark a blank square as NONTENT if it is not orthogonally + * adjacent to any unmatched tree. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (soln[y*w+x] == BLANK) { + int can_be_tent = FALSE; + + for (d = 1; d < MAXDIR; d++) { + int x2 = x + dx(d), y2 = y + dy(d); + if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && + soln[y2*w+x2] == TREE && + !sc->links[y2*w+x2]) + can_be_tent = TRUE; + } + + if (!can_be_tent) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%d,%d cannot be a tent (no adjacent" + " unmatched tree)\n", x, y); +#endif + soln[y*w+x] = NONTENT; + done_something = TRUE; + } + } + + if (done_something) + continue; + + /* + * Mark a blank square as NONTENT if it is (perhaps + * diagonally) adjacent to any other tent. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (soln[y*w+x] == BLANK) { + int dx, dy, imposs = FALSE; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (dy || dx) { + int x2 = x + dx, y2 = y + dy; + if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && + soln[y2*w+x2] == TENT) + imposs = TRUE; + } + + if (imposs) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%d,%d cannot be a tent (adjacent tent)\n", + x, y); +#endif + soln[y*w+x] = NONTENT; + done_something = TRUE; + } + } + + if (done_something) + continue; + + /* + * Any tree which has exactly one {unattached tent, BLANK} + * adjacent to it must have its tent in that square. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (soln[y*w+x] == TREE && !sc->links[y*w+x]) { + int linkd = 0, linkd2 = 0, nd = 0; + + for (d = 1; d < MAXDIR; d++) { + int x2 = x + dx(d), y2 = y + dy(d); + if (!(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h)) + continue; + if (soln[y2*w+x2] == BLANK || + (soln[y2*w+x2] == TENT && !sc->links[y2*w+x2])) { + if (linkd) + linkd2 = d; + else + linkd = d; + nd++; + } + } + + if (nd == 0) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("tree at %d,%d cannot link to anything\n", + x, y); +#endif + return 0; /* no solution exists */ + } else if (nd == 1) { + int x2 = x + dx(linkd), y2 = y + dy(linkd); + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("tree at %d,%d can only link to tent at" + " %d,%d\n", x, y, x2, y2); +#endif + soln[y2*w+x2] = TENT; + sc->links[y*w+x] = linkd; + sc->links[y2*w+x2] = F(linkd); + done_something = TRUE; + } else if (nd == 2 && (!dx(linkd) != !dx(linkd2)) && + diff >= DIFF_TRICKY) { + /* + * If there are two possible places where + * this tree's tent can go, and they are + * diagonally separated rather than being + * on opposite sides of the tree, then the + * square (other than the tree square) + * which is adjacent to both of them must + * be a non-tent. + */ + int x2 = x + dx(linkd) + dx(linkd2); + int y2 = y + dy(linkd) + dy(linkd2); + assert(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h); + if (soln[y2*w+x2] == BLANK) { +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("possible tent locations for tree at" + " %d,%d rule out tent at %d,%d\n", + x, y, x2, y2); +#endif + soln[y2*w+x2] = NONTENT; + done_something = TRUE; + } + } + } + + if (done_something) + continue; + + /* + * If localised deductions about the trees and tents + * themselves haven't helped us, it's time to resort to the + * numbers round the grid edge. For each row and column, we + * go through all possible combinations of locations for + * the unplaced tents, rule out any which have adjacent + * tents, and spot any square which is given the same state + * by all remaining combinations. + */ + for (i = 0; i < w+h; i++) { + int start, step, len, start1, start2, n, k; + + if (i < w) { + /* + * This is the number for a column. + */ + start = i; + step = w; + len = h; + if (i > 0) + start1 = start - 1; + else + start1 = -1; + if (i+1 < w) + start2 = start + 1; + else + start2 = -1; + } else { + /* + * This is the number for a row. + */ + start = (i-w)*w; + step = 1; + len = w; + if (i > w) + start1 = start - w; + else + start1 = -1; + if (i+1 < w+h) + start2 = start + w; + else + start2 = -1; + } + + if (diff < DIFF_TRICKY) { + /* + * In Easy mode, we don't look at the effect of one + * row on the next (i.e. ruling out a square if all + * possibilities for an adjacent row place a tent + * next to it). + */ + start1 = start2 = -1; + } + + k = numbers[i]; + + /* + * Count and store the locations of the free squares, + * and also count the number of tents already placed. + */ + n = 0; + for (j = 0; j < len; j++) { + if (soln[start+j*step] == TENT) + k--; /* one fewer tent to place */ + else if (soln[start+j*step] == BLANK) + sc->locs[n++] = j; + } + + if (n == 0) + continue; /* nothing left to do here */ + + /* + * Now we know we're placing k tents in n squares. Set + * up the first possibility. + */ + for (j = 0; j < n; j++) + sc->place[j] = (j < k ? TENT : NONTENT); + + /* + * We're aiming to find squares in this row which are + * invariant over all valid possibilities. Thus, we + * maintain the current state of that invariance. We + * start everything off at MAGIC to indicate that it + * hasn't been set up yet. + */ + mrow = sc->mrows; + trow = sc->trows; + trow1 = sc->trows + len; + trow2 = sc->trows + 2*len; + memset(mrow, MAGIC, 3*len); + + /* + * And iterate over all possibilities. + */ + while (1) { + int p, valid; + + /* + * See if this possibility is valid. The only way + * it can fail to be valid is if it contains two + * adjacent tents. (Other forms of invalidity, such + * as containing a tent adjacent to one already + * placed, will have been dealt with already by + * other parts of the solver.) + */ + valid = TRUE; + for (j = 0; j+1 < n; j++) + if (sc->place[j] == TENT && + sc->place[j+1] == TENT && + sc->locs[j+1] == sc->locs[j]+1) { + valid = FALSE; + break; + } + + if (valid) { + /* + * Merge this valid combination into mrow. + */ + memset(trow, MAGIC, len); + memset(trow+len, BLANK, 2*len); + for (j = 0; j < n; j++) { + trow[sc->locs[j]] = sc->place[j]; + if (sc->place[j] == TENT) { + int jj; + for (jj = sc->locs[j]-1; jj <= sc->locs[j]+1; jj++) + if (jj >= 0 && jj < len) + trow1[jj] = trow2[jj] = NONTENT; + } + } + + for (j = 0; j < 3*len; j++) { + if (trow[j] == MAGIC) + continue; + if (mrow[j] == MAGIC || mrow[j] == trow[j]) { + /* + * Either this is the first valid + * placement we've found at all, or + * this square's contents are + * consistent with every previous valid + * combination. + */ + mrow[j] = trow[j]; + } else { + /* + * This square's contents fail to match + * what they were in a different + * combination, so we cannot deduce + * anything about this square. + */ + mrow[j] = BLANK; + } + } + } + + /* + * Find the next combination of k choices from n. + * We do this by finding the rightmost tent which + * can be moved one place right, doing so, and + * shunting all tents to the right of that as far + * left as they can go. + */ + p = 0; + for (j = n-1; j > 0; j--) { + if (sc->place[j] == TENT) + p++; + if (sc->place[j] == NONTENT && sc->place[j-1] == TENT) { + sc->place[j-1] = NONTENT; + sc->place[j] = TENT; + while (p--) + sc->place[++j] = TENT; + while (++j < n) + sc->place[j] = NONTENT; + break; + } + } + if (j <= 0) + break; /* we've finished */ + } + + /* + * It's just possible that _no_ placement was valid, in + * which case we have an internally inconsistent + * puzzle. + */ + if (mrow[sc->locs[0]] == MAGIC) + return 0; /* inconsistent */ + + /* + * Now go through mrow and see if there's anything + * we've deduced which wasn't already mentioned in soln. + */ + for (j = 0; j < len; j++) { + int whichrow; + + for (whichrow = 0; whichrow < 3; whichrow++) { + char *mthis = mrow + whichrow * len; + int tstart = (whichrow == 0 ? start : + whichrow == 1 ? start1 : start2); + if (tstart >= 0 && + mthis[j] != MAGIC && mthis[j] != BLANK && + soln[tstart+j*step] == BLANK) { + int pos = tstart+j*step; + +#ifdef SOLVER_DIAGNOSTICS + if (verbose) + printf("%s %d forces %s at %d,%d\n", + step==1 ? "row" : "column", + step==1 ? start/w : start, + mthis[j] == TENT ? "tent" : "non-tent", + pos % w, pos / w); +#endif + soln[pos] = mthis[j]; + done_something = TRUE; + } + } + } + } + + if (done_something) + continue; + + if (!done_something) + break; + } + + /* + * The solver has nothing further it can do. Return 1 if both + * soln and sc->links are completely filled in, or 2 otherwise. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + if (soln[y*w+x] == BLANK) + return 2; + if (soln[y*w+x] != NONTENT && sc->links[y*w+x] == 0) + return 2; + } + + return 1; +} + +static char *new_game_desc(const game_params *params_in, random_state *rs, + char **aux, int interactive) +{ + game_params params_copy = *params_in; /* structure copy */ + game_params *params = ¶ms_copy; + int w = params->w, h = params->h; + int ntrees = w * h / 5; + char *grid = snewn(w*h, char); + char *puzzle = snewn(w*h, char); + int *numbers = snewn(w+h, int); + char *soln = snewn(w*h, char); + int *temp = snewn(2*w*h, int); + int maxedges = ntrees*4 + w*h; + int *edges = snewn(2*maxedges, int); + int *capacity = snewn(maxedges, int); + int *flow = snewn(maxedges, int); + struct solver_scratch *sc = new_scratch(w, h); + char *ret, *p; + int i, j, nedges; + + /* + * Since this puzzle has many global deductions and doesn't + * permit limited clue sets, generating grids for this puzzle + * is hard enough that I see no better option than to simply + * generate a solution and see if it's unique and has the + * required difficulty. This turns out to be computationally + * plausible as well. + * + * We chose our tree count (hence also tent count) by dividing + * the total grid area by five above. Why five? Well, w*h/4 is + * the maximum number of tents you can _possibly_ fit into the + * grid without violating the separation criterion, and to + * achieve that you are constrained to a very small set of + * possible layouts (the obvious one with a tent at every + * (even,even) coordinate, and trivial variations thereon). So + * if we reduce the tent count a bit more, we enable more + * random-looking placement; 5 turns out to be a plausible + * figure which yields sensible puzzles. Increasing the tent + * count would give puzzles whose solutions were too regimented + * and could be solved by the use of that knowledge (and would + * also take longer to find a viable placement); decreasing it + * would make the grids emptier and more boring. + * + * Actually generating a grid is a matter of first placing the + * tents, and then placing the trees by the use of maxflow + * (finding a distinct square adjacent to every tent). We do it + * this way round because otherwise satisfying the tent + * separation condition would become onerous: most randomly + * chosen tent layouts do not satisfy this condition, so we'd + * have gone to a lot of work before finding that a candidate + * layout was unusable. Instead, we place the tents first and + * ensure they meet the separation criterion _before_ doing + * lots of computation; this works much better. + * + * The maxflow algorithm is not randomised, so employed naively + * it would give rise to grids with clear structure and + * directional bias. Hence, I assign the network nodes as seen + * by maxflow to be a _random_ permutation of the squares of + * the grid, so that any bias shown by maxflow towards + * low-numbered nodes is turned into a random bias. + * + * This generation strategy can fail at many points, including + * as early as tent placement (if you get a bad random order in + * which to greedily try the grid squares, you won't even + * manage to find enough mutually non-adjacent squares to put + * the tents in). Then it can fail if maxflow doesn't manage to + * find a good enough matching (i.e. the tent placements don't + * admit any adequate tree placements); and finally it can fail + * if the solver finds that the problem has the wrong + * difficulty (including being actually non-unique). All of + * these, however, are insufficiently frequent to cause + * trouble. + */ + + if (params->diff > DIFF_EASY && params->w <= 4 && params->h <= 4) + params->diff = DIFF_EASY; /* downgrade to prevent tight loop */ + + while (1) { + /* + * Arrange the grid squares into a random order. + */ + for (i = 0; i < w*h; i++) + temp[i] = i; + shuffle(temp, w*h, sizeof(*temp), rs); + + /* + * The first `ntrees' entries in temp which we can get + * without making two tents adjacent will be the tent + * locations. + */ + memset(grid, BLANK, w*h); + j = ntrees; + for (i = 0; i < w*h && j > 0; i++) { + int x = temp[i] % w, y = temp[i] / w; + int dy, dx, ok = TRUE; + + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) + if (x+dx >= 0 && x+dx < w && + y+dy >= 0 && y+dy < h && + grid[(y+dy)*w+(x+dx)] == TENT) + ok = FALSE; + + if (ok) { + grid[temp[i]] = TENT; + j--; + } + } + if (j > 0) + continue; /* couldn't place all the tents */ + + /* + * Now we build up the list of graph edges. + */ + nedges = 0; + for (i = 0; i < w*h; i++) { + if (grid[temp[i]] == TENT) { + for (j = 0; j < w*h; j++) { + if (grid[temp[j]] != TENT) { + int xi = temp[i] % w, yi = temp[i] / w; + int xj = temp[j] % w, yj = temp[j] / w; + if (abs(xi-xj) + abs(yi-yj) == 1) { + edges[nedges*2] = i; + edges[nedges*2+1] = j; + capacity[nedges] = 1; + nedges++; + } + } + } + } else { + /* + * Special node w*h is the sink node; any non-tent node + * has an edge going to it. + */ + edges[nedges*2] = i; + edges[nedges*2+1] = w*h; + capacity[nedges] = 1; + nedges++; + } + } + + /* + * Special node w*h+1 is the source node, with an edge going to + * every tent. + */ + for (i = 0; i < w*h; i++) { + if (grid[temp[i]] == TENT) { + edges[nedges*2] = w*h+1; + edges[nedges*2+1] = i; + capacity[nedges] = 1; + nedges++; + } + } + + assert(nedges <= maxedges); + + /* + * Now we're ready to call the maxflow algorithm to place the + * trees. + */ + j = maxflow(w*h+2, w*h+1, w*h, nedges, edges, capacity, flow, NULL); + + if (j < ntrees) + continue; /* couldn't place all the trees */ + + /* + * We've placed the trees. Now we need to work out _where_ + * we've placed them, which is a matter of reading back out + * from the `flow' array. + */ + for (i = 0; i < nedges; i++) { + if (edges[2*i] < w*h && edges[2*i+1] < w*h && flow[i] > 0) + grid[temp[edges[2*i+1]]] = TREE; + } + + /* + * I think it looks ugly if there isn't at least one of + * _something_ (tent or tree) in each row and each column + * of the grid. This doesn't give any information away + * since a completely empty row/column is instantly obvious + * from the clues (it has no trees and a zero). + */ + for (i = 0; i < w; i++) { + for (j = 0; j < h; j++) { + if (grid[j*w+i] != BLANK) + break; /* found something in this column */ + } + if (j == h) + break; /* found empty column */ + } + if (i < w) + continue; /* a column was empty */ + + for (j = 0; j < h; j++) { + for (i = 0; i < w; i++) { + if (grid[j*w+i] != BLANK) + break; /* found something in this row */ + } + if (i == w) + break; /* found empty row */ + } + if (j < h) + continue; /* a row was empty */ + + /* + * Now set up the numbers round the edge. + */ + for (i = 0; i < w; i++) { + int n = 0; + for (j = 0; j < h; j++) + if (grid[j*w+i] == TENT) + n++; + numbers[i] = n; + } + for (i = 0; i < h; i++) { + int n = 0; + for (j = 0; j < w; j++) + if (grid[i*w+j] == TENT) + n++; + numbers[w+i] = n; + } + + /* + * And now actually solve the puzzle, to see whether it's + * unique and has the required difficulty. + */ + for (i = 0; i < w*h; i++) + puzzle[i] = grid[i] == TREE ? TREE : BLANK; + i = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff-1); + j = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff); + + /* + * We expect solving with difficulty params->diff to have + * succeeded (otherwise the problem is too hard), and + * solving with diff-1 to have failed (otherwise it's too + * easy). + */ + if (i == 2 && j == 1) + break; + } + + /* + * That's it. Encode as a game ID. + */ + ret = snewn((w+h)*40 + ntrees + (w*h)/26 + 1, char); + p = ret; + j = 0; + for (i = 0; i <= w*h; i++) { + int c = (i < w*h ? grid[i] == TREE : 1); + if (c) { + *p++ = (j == 0 ? '_' : j-1 + 'a'); + j = 0; + } else { + j++; + while (j > 25) { + *p++ = 'z'; + j -= 25; + } + } + } + for (i = 0; i < w+h; i++) + p += sprintf(p, ",%d", numbers[i]); + *p++ = '\0'; + ret = sresize(ret, p - ret, char); + + /* + * And encode the solution as an aux_info. + */ + *aux = snewn(ntrees * 40, char); + p = *aux; + *p++ = 'S'; + for (i = 0; i < w*h; i++) + if (grid[i] == TENT) + p += sprintf(p, ";T%d,%d", i%w, i/w); + *p++ = '\0'; + *aux = sresize(*aux, p - *aux, char); + + free_scratch(sc); + sfree(flow); + sfree(capacity); + sfree(edges); + sfree(temp); + sfree(soln); + sfree(numbers); + sfree(puzzle); + sfree(grid); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h; + int area, i; + + area = 0; + while (*desc && *desc != ',') { + if (*desc == '_') + area++; + else if (*desc >= 'a' && *desc < 'z') + area += *desc - 'a' + 2; + else if (*desc == 'z') + area += 25; + else if (*desc == '!' || *desc == '-') + /* do nothing */; + else + return "Invalid character in grid specification"; + + desc++; + } + if (area < w * h + 1) + return "Not enough data to fill grid"; + else if (area > w * h + 1) + return "Too much data to fill grid"; + + for (i = 0; i < w+h; i++) { + if (!*desc) + return "Not enough numbers given after grid specification"; + else if (*desc != ',') + return "Invalid character in number list"; + desc++; + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + + if (*desc) + return "Unexpected additional data at end of game description"; + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h; + game_state *state = snew(game_state); + int i; + + state->p = *params; /* structure copy */ + state->grid = snewn(w*h, char); + state->numbers = snew(struct numbers); + state->numbers->refcount = 1; + state->numbers->numbers = snewn(w+h, int); + state->completed = state->used_solve = FALSE; + + i = 0; + memset(state->grid, BLANK, w*h); + + while (*desc) { + int run, type; + + type = TREE; + + if (*desc == '_') + run = 0; + else if (*desc >= 'a' && *desc < 'z') + run = *desc - ('a'-1); + else if (*desc == 'z') { + run = 25; + type = BLANK; + } else { + assert(*desc == '!' || *desc == '-'); + run = -1; + type = (*desc == '!' ? TENT : NONTENT); + } + + desc++; + + i += run; + assert(i >= 0 && i <= w*h); + if (i == w*h) { + assert(type == TREE); + break; + } else { + if (type != BLANK) + state->grid[i++] = type; + } + } + + for (i = 0; i < w+h; i++) { + assert(*desc == ','); + desc++; + state->numbers->numbers[i] = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + + assert(!*desc); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->p.w, h = state->p.h; + game_state *ret = snew(game_state); + + ret->p = state->p; /* structure copy */ + ret->grid = snewn(w*h, char); + memcpy(ret->grid, state->grid, w*h); + ret->numbers = state->numbers; + state->numbers->refcount++; + ret->completed = state->completed; + ret->used_solve = state->used_solve; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->numbers->refcount <= 0) { + sfree(state->numbers->numbers); + sfree(state->numbers); + } + sfree(state->grid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->p.w, h = state->p.h; + + if (aux) { + /* + * If we already have the solution, save ourselves some + * time. + */ + return dupstr(aux); + } else { + struct solver_scratch *sc = new_scratch(w, h); + char *soln; + int ret; + char *move, *p; + int i; + + soln = snewn(w*h, char); + ret = tents_solve(w, h, state->grid, state->numbers->numbers, + soln, sc, DIFFCOUNT-1); + free_scratch(sc); + if (ret != 1) { + sfree(soln); + if (ret == 0) + *error = "This puzzle is not self-consistent"; + else + *error = "Unable to find a unique solution for this puzzle"; + return NULL; + } + + /* + * Construct a move string which turns the current state + * into the solved state. + */ + move = snewn(w*h * 40, char); + p = move; + *p++ = 'S'; + for (i = 0; i < w*h; i++) + if (soln[i] == TENT) + p += sprintf(p, ";T%d,%d", i%w, i/w); + *p++ = '\0'; + move = sresize(move, p - move, char); + + sfree(soln); + + return move; + } +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return params->w <= 1998 && params->h <= 1998; /* 999 tents */ +} + +static char *game_text_format(const game_state *state) +{ + int w = state->p.w, h = state->p.h, r, c; + int cw = 4, ch = 2, gw = (w+1)*cw + 2, gh = (h+1)*ch + 1, len = gw * gh; + char *board = snewn(len + 1, char); + + sprintf(board, "%*s\n", len - 2, ""); + for (r = 0; r <= h; ++r) { + for (c = 0; c <= w; ++c) { + int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; + int i = r*w + c, n = 1000; + + if (r == h && c == w) /* NOP */; + else if (c == w) n = state->numbers->numbers[w + r]; + else if (r == h) n = state->numbers->numbers[c]; + else switch (state->grid[i]) { + case BLANK: board[center] = '.'; break; + case TREE: board[center] = 'T'; break; + case TENT: memcpy(board + center - 1, "//\\", 3); break; + case NONTENT: break; + default: memcpy(board + center - 1, "wtf", 3); + } + + if (n < 100) { + board[center] = '0' + n % 10; + if (n >= 10) board[center - 1] = '0' + n / 10; + } else if (n < 1000) { + board[center + 1] = '0' + n % 10; + board[center] = '0' + n / 10 % 10; + board[center - 1] = '0' + n / 100; + } + + board[cell] = '+'; + memset(board + cell + 1, '-', cw - 1); + for (i = 1; i < ch; ++i) board[cell + i*gw] = '|'; + } + + for (c = 0; c < ch; ++c) { + board[(r*ch+c)*gw + gw - 2] = + c == 0 ? '+' : r < h ? '|' : ' '; + board[(r*ch+c)*gw + gw - 1] = '\n'; + } + } + + memset(board + len - gw, '-', gw - 2 - cw); + for (c = 0; c <= w; ++c) board[len - gw + cw*c] = '+'; + + return board; +} + +struct game_ui { + int dsx, dsy; /* coords of drag start */ + int dex, dey; /* coords of drag end */ + int drag_button; /* -1 for none, or a button code */ + int drag_ok; /* dragged off the window, to cancel */ + + int cx, cy, cdisp; /* cursor position, and ?display. */ +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->dsx = ui->dsy = -1; + ui->dex = ui->dey = -1; + ui->drag_button = -1; + ui->drag_ok = FALSE; + ui->cx = ui->cy = ui->cdisp = 0; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int tilesize; + int started; + game_params p; + int *drawn, *numbersdrawn; + int cx, cy; /* last-drawn cursor pos, or (-1,-1) if absent. */ +}; + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#define TLBORDER (TILESIZE/2) +#define BRBORDER (TILESIZE*3/2) +#define COORD(x) ( (x) * TILESIZE + TLBORDER ) +#define FROMCOORD(x) ( ((x) - TLBORDER + TILESIZE) / TILESIZE - 1 ) + +#define FLASH_TIME 0.30F + +static int drag_xform(const game_ui *ui, int x, int y, int v) +{ + int xmin, ymin, xmax, ymax; + + xmin = min(ui->dsx, ui->dex); + xmax = max(ui->dsx, ui->dex); + ymin = min(ui->dsy, ui->dey); + ymax = max(ui->dsy, ui->dey); + +#ifndef STYLUS_BASED + /* + * Left-dragging has no effect, so we treat a left-drag as a + * single click on dsx,dsy. + */ + if (ui->drag_button == LEFT_BUTTON) { + xmin = xmax = ui->dsx; + ymin = ymax = ui->dsy; + } +#endif + + if (x < xmin || x > xmax || y < ymin || y > ymax) + return v; /* no change outside drag area */ + + if (v == TREE) + return v; /* trees are inviolate always */ + + if (xmin == xmax && ymin == ymax) { + /* + * Results of a simple click. Left button sets blanks to + * tents; right button sets blanks to non-tents; either + * button clears a non-blank square. + * If stylus-based however, it loops instead. + */ + if (ui->drag_button == LEFT_BUTTON) +#ifdef STYLUS_BASED + v = (v == BLANK ? TENT : (v == TENT ? NONTENT : BLANK)); + else + v = (v == BLANK ? NONTENT : (v == NONTENT ? TENT : BLANK)); +#else + v = (v == BLANK ? TENT : BLANK); + else + v = (v == BLANK ? NONTENT : BLANK); +#endif + } else { + /* + * Results of a drag. Left-dragging has no effect. + * Right-dragging sets all blank squares to non-tents and + * has no effect on anything else. + */ + if (ui->drag_button == RIGHT_BUTTON) + v = (v == BLANK ? NONTENT : v); + else +#ifdef STYLUS_BASED + v = (v == BLANK ? NONTENT : v); +#else + /* do nothing */; +#endif + } + + return v; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->p.w, h = state->p.h; + char tmpbuf[80]; + int shift = button & MOD_SHFT, control = button & MOD_CTRL; + + button &= ~MOD_MASK; + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + x = FROMCOORD(x); + y = FROMCOORD(y); + if (x < 0 || y < 0 || x >= w || y >= h) + return NULL; + + ui->drag_button = button; + ui->dsx = ui->dex = x; + ui->dsy = ui->dey = y; + ui->drag_ok = TRUE; + ui->cdisp = 0; + return ""; /* ui updated */ + } + + if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) && + ui->drag_button > 0) { + int xmin, ymin, xmax, ymax; + char *buf, *sep; + int buflen, bufsize, tmplen; + + x = FROMCOORD(x); + y = FROMCOORD(y); + if (x < 0 || y < 0 || x >= w || y >= h) { + ui->drag_ok = FALSE; + } else { + /* + * Drags are limited to one row or column. Hence, we + * work out which coordinate is closer to the drag + * start, and move it _to_ the drag start. + */ + if (abs(x - ui->dsx) < abs(y - ui->dsy)) + x = ui->dsx; + else + y = ui->dsy; + + ui->dex = x; + ui->dey = y; + + ui->drag_ok = TRUE; + } + + if (IS_MOUSE_DRAG(button)) + return ""; /* ui updated */ + + /* + * The drag has been released. Enact it. + */ + if (!ui->drag_ok) { + ui->drag_button = -1; + return ""; /* drag was just cancelled */ + } + + xmin = min(ui->dsx, ui->dex); + xmax = max(ui->dsx, ui->dex); + ymin = min(ui->dsy, ui->dey); + ymax = max(ui->dsy, ui->dey); + assert(0 <= xmin && xmin <= xmax && xmax < w); + assert(0 <= ymin && ymin <= ymax && ymax < h); + + buflen = 0; + bufsize = 256; + buf = snewn(bufsize, char); + sep = ""; + for (y = ymin; y <= ymax; y++) + for (x = xmin; x <= xmax; x++) { + int v = drag_xform(ui, x, y, state->grid[y*w+x]); + if (state->grid[y*w+x] != v) { + tmplen = sprintf(tmpbuf, "%s%c%d,%d", sep, + (int)(v == BLANK ? 'B' : + v == TENT ? 'T' : 'N'), + x, y); + sep = ";"; + + if (buflen + tmplen >= bufsize) { + bufsize = buflen + tmplen + 256; + buf = sresize(buf, bufsize, char); + } + + strcpy(buf+buflen, tmpbuf); + buflen += tmplen; + } + } + + ui->drag_button = -1; /* drag is terminated */ + + if (buflen == 0) { + sfree(buf); + return ""; /* ui updated (drag was terminated) */ + } else { + buf[buflen] = '\0'; + return buf; + } + } + + if (IS_CURSOR_MOVE(button)) { + ui->cdisp = 1; + if (shift || control) { + int len = 0, i, indices[2]; + indices[0] = ui->cx + w * ui->cy; + move_cursor(button, &ui->cx, &ui->cy, w, h, 0); + indices[1] = ui->cx + w * ui->cy; + + /* NONTENTify all unique traversed eligible squares */ + for (i = 0; i <= (indices[0] != indices[1]); ++i) + if (state->grid[indices[i]] == BLANK || + (control && state->grid[indices[i]] == TENT)) { + len += sprintf(tmpbuf + len, "%sN%d,%d", len ? ";" : "", + indices[i] % w, indices[i] / w); + assert(len < lenof(tmpbuf)); + } + + tmpbuf[len] = '\0'; + if (len) return dupstr(tmpbuf); + } else + move_cursor(button, &ui->cx, &ui->cy, w, h, 0); + return ""; + } + if (ui->cdisp) { + char rep = 0; + int v = state->grid[ui->cy*w+ui->cx]; + + if (v != TREE) { +#ifdef SINGLE_CURSOR_SELECT + if (button == CURSOR_SELECT) + /* SELECT cycles T, N, B */ + rep = v == BLANK ? 'T' : v == TENT ? 'N' : 'B'; +#else + if (button == CURSOR_SELECT) + rep = v == BLANK ? 'T' : 'B'; + else if (button == CURSOR_SELECT2) + rep = v == BLANK ? 'N' : 'B'; + else if (button == 'T' || button == 'N' || button == 'B') + rep = (char)button; +#endif + } + + if (rep) { + sprintf(tmpbuf, "%c%d,%d", (int)rep, ui->cx, ui->cy); + return dupstr(tmpbuf); + } + } else if (IS_CURSOR_SELECT(button)) { + ui->cdisp = 1; + return ""; + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->p.w, h = state->p.h; + char c; + int x, y, m, n, i, j; + game_state *ret = dup_game(state); + + while (*move) { + c = *move; + if (c == 'S') { + int i; + ret->used_solve = TRUE; + /* + * Set all non-tree squares to NONTENT. The rest of the + * solve move will fill the tents in over the top. + */ + for (i = 0; i < w*h; i++) + if (ret->grid[i] != TREE) + ret->grid[i] = NONTENT; + move++; + } else if (c == 'B' || c == 'T' || c == 'N') { + move++; + if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || + x < 0 || y < 0 || x >= w || y >= h) { + free_game(ret); + return NULL; + } + if (ret->grid[y*w+x] == TREE) { + free_game(ret); + return NULL; + } + ret->grid[y*w+x] = (c == 'B' ? BLANK : c == 'T' ? TENT : NONTENT); + move += n; + } else { + free_game(ret); + return NULL; + } + if (*move == ';') + move++; + else if (*move) { + free_game(ret); + return NULL; + } + } + + /* + * Check for completion. + */ + for (i = n = m = 0; i < w*h; i++) { + if (ret->grid[i] == TENT) + n++; + else if (ret->grid[i] == TREE) + m++; + } + if (n == m) { + int nedges, maxedges, *edges, *capacity, *flow; + + /* + * We have the right number of tents, which is a + * precondition for the game being complete. Now check that + * the numbers add up. + */ + for (i = 0; i < w; i++) { + n = 0; + for (j = 0; j < h; j++) + if (ret->grid[j*w+i] == TENT) + n++; + if (ret->numbers->numbers[i] != n) + goto completion_check_done; + } + for (i = 0; i < h; i++) { + n = 0; + for (j = 0; j < w; j++) + if (ret->grid[i*w+j] == TENT) + n++; + if (ret->numbers->numbers[w+i] != n) + goto completion_check_done; + } + /* + * Also, check that no two tents are adjacent. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + if (x+1 < w && + ret->grid[y*w+x] == TENT && ret->grid[y*w+x+1] == TENT) + goto completion_check_done; + if (y+1 < h && + ret->grid[y*w+x] == TENT && ret->grid[(y+1)*w+x] == TENT) + goto completion_check_done; + if (x+1 < w && y+1 < h) { + if (ret->grid[y*w+x] == TENT && + ret->grid[(y+1)*w+(x+1)] == TENT) + goto completion_check_done; + if (ret->grid[(y+1)*w+x] == TENT && + ret->grid[y*w+(x+1)] == TENT) + goto completion_check_done; + } + } + + /* + * OK; we have the right number of tents, they match the + * numeric clues, and they satisfy the non-adjacency + * criterion. Finally, we need to verify that they can be + * placed in a one-to-one matching with the trees such that + * every tent is orthogonally adjacent to its tree. + * + * This bit is where the hard work comes in: we have to do + * it by finding such a matching using maxflow. + * + * So we construct a network with one special source node, + * one special sink node, one node per tent, and one node + * per tree. + */ + maxedges = 6 * m; + edges = snewn(2 * maxedges, int); + capacity = snewn(maxedges, int); + flow = snewn(maxedges, int); + nedges = 0; + /* + * Node numbering: + * + * 0..w*h trees/tents + * w*h source + * w*h+1 sink + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (ret->grid[y*w+x] == TREE) { + int d; + + /* + * Here we use the direction enum declared for + * the solver. We make use of the fact that the + * directions are declared in the order + * U,L,R,D, meaning that we go through the four + * neighbours of any square in numerically + * increasing order. + */ + for (d = 1; d < MAXDIR; d++) { + int x2 = x + dx(d), y2 = y + dy(d); + if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && + ret->grid[y2*w+x2] == TENT) { + assert(nedges < maxedges); + edges[nedges*2] = y*w+x; + edges[nedges*2+1] = y2*w+x2; + capacity[nedges] = 1; + nedges++; + } + } + } else if (ret->grid[y*w+x] == TENT) { + assert(nedges < maxedges); + edges[nedges*2] = y*w+x; + edges[nedges*2+1] = w*h+1; /* edge going to sink */ + capacity[nedges] = 1; + nedges++; + } + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (ret->grid[y*w+x] == TREE) { + assert(nedges < maxedges); + edges[nedges*2] = w*h; /* edge coming from source */ + edges[nedges*2+1] = y*w+x; + capacity[nedges] = 1; + nedges++; + } + n = maxflow(w*h+2, w*h, w*h+1, nedges, edges, capacity, flow, NULL); + + sfree(flow); + sfree(capacity); + sfree(edges); + + if (n != m) + goto completion_check_done; + + /* + * We haven't managed to fault the grid on any count. Score! + */ + ret->completed = TRUE; + } + completion_check_done: + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* fool the macros */ + struct dummy { int tilesize; } dummy, *ds = &dummy; + dummy.tilesize = tilesize; + + *x = TLBORDER + BRBORDER + TILESIZE * params->w; + *y = TLBORDER + BRBORDER + TILESIZE * params->h; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_GRASS * 3 + 0] = 0.7F; + ret[COL_GRASS * 3 + 1] = 1.0F; + ret[COL_GRASS * 3 + 2] = 0.5F; + + ret[COL_TREETRUNK * 3 + 0] = 0.6F; + ret[COL_TREETRUNK * 3 + 1] = 0.4F; + ret[COL_TREETRUNK * 3 + 2] = 0.0F; + + ret[COL_TREELEAF * 3 + 0] = 0.0F; + ret[COL_TREELEAF * 3 + 1] = 0.7F; + ret[COL_TREELEAF * 3 + 2] = 0.0F; + + ret[COL_TENT * 3 + 0] = 0.8F; + ret[COL_TENT * 3 + 1] = 0.7F; + ret[COL_TENT * 3 + 2] = 0.0F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_ERRTEXT * 3 + 0] = 1.0F; + ret[COL_ERRTEXT * 3 + 1] = 1.0F; + ret[COL_ERRTEXT * 3 + 2] = 1.0F; + + ret[COL_ERRTRUNK * 3 + 0] = 0.6F; + ret[COL_ERRTRUNK * 3 + 1] = 0.0F; + ret[COL_ERRTRUNK * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->p.w, h = state->p.h; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->started = FALSE; + ds->p = state->p; /* structure copy */ + ds->drawn = snewn(w*h, int); + for (i = 0; i < w*h; i++) + ds->drawn[i] = MAGIC; + ds->numbersdrawn = snewn(w+h, int); + for (i = 0; i < w+h; i++) + ds->numbersdrawn[i] = 2; + ds->cx = ds->cy = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->drawn); + sfree(ds->numbersdrawn); + sfree(ds); +} + +enum { + ERR_ADJ_TOPLEFT = 4, + ERR_ADJ_TOP, + ERR_ADJ_TOPRIGHT, + ERR_ADJ_LEFT, + ERR_ADJ_RIGHT, + ERR_ADJ_BOTLEFT, + ERR_ADJ_BOT, + ERR_ADJ_BOTRIGHT, + ERR_OVERCOMMITTED +}; + +static int *find_errors(const game_state *state, char *grid) +{ + int w = state->p.w, h = state->p.h; + int *ret = snewn(w*h + w + h, int); + int *tmp = snewn(w*h*2, int), *dsf = tmp + w*h; + int x, y; + + /* + * This function goes through a grid and works out where to + * highlight play errors in red. The aim is that it should + * produce at least one error highlight for any complete grid + * (or complete piece of grid) violating a puzzle constraint, so + * that a grid containing no BLANK squares is either a win or is + * marked up in some way that indicates why not. + * + * So it's easy enough to highlight errors in the numeric clues + * - just light up any row or column number which is not + * fulfilled - and it's just as easy to highlight adjacent + * tents. The difficult bit is highlighting failures in the + * tent/tree matching criterion. + * + * A natural approach would seem to be to apply the maxflow + * algorithm to find the tent/tree matching; if this fails, it + * must necessarily terminate with a min-cut which can be + * reinterpreted as some set of trees which have too few tents + * between them (or vice versa). However, it's bad for + * localising errors, because it's not easy to make the + * algorithm narrow down to the _smallest_ such set of trees: if + * trees A and B have only one tent between them, for instance, + * it might perfectly well highlight not only A and B but also + * trees C and D which are correctly matched on the far side of + * the grid, on the grounds that those four trees between them + * have only three tents. + * + * Also, that approach fares badly when you introduce the + * additional requirement that incomplete grids should have + * errors highlighted only when they can be proved to be errors + * - so that trees should not be marked as having too few tents + * if there are enough BLANK squares remaining around them that + * could be turned into the missing tents (to do so would be + * patronising, since the overwhelming likelihood is not that + * the player has forgotten to put a tree there but that they + * have merely not put one there _yet_). However, tents with too + * few trees can be marked immediately, since those are + * definitely player error. + * + * So I adopt an alternative approach, which is to consider the + * bipartite adjacency graph between trees and tents + * ('bipartite' in the sense that for these purposes I + * deliberately ignore two adjacent trees or two adjacent + * tents), divide that graph up into its connected components + * using a dsf, and look for components which contain different + * numbers of trees and tents. This allows me to highlight + * groups of tents with too few trees between them immediately, + * and then in order to find groups of trees with too few tents + * I redo the same process but counting BLANKs as potential + * tents (so that the only trees highlighted are those + * surrounded by enough NONTENTs to make it impossible to give + * them enough tents). + * + * However, this technique is incomplete: it is not a sufficient + * condition for the existence of a perfect matching that every + * connected component of the graph has the same number of tents + * and trees. An example of a graph which satisfies the latter + * condition but still has no perfect matching is + * + * A B C + * | / ,/| + * | / ,'/ | + * | / ,' / | + * |/,' / | + * 1 2 3 + * + * which can be realised in Tents as + * + * B + * A 1 C 2 + * 3 + * + * The matching-error highlighter described above will not mark + * this construction as erroneous. However, something else will: + * the three tents in the above diagram (let us suppose A,B,C + * are the tents, though it doesn't matter which) contain two + * diagonally adjacent pairs. So there will be _an_ error + * highlighted for the above layout, even though not all types + * of error will be highlighted. + * + * And in fact we can prove that this will always be the case: + * that the shortcomings of the matching-error highlighter will + * always be made up for by the easy tent adjacency highlighter. + * + * Lemma: Let G be a bipartite graph between n trees and n + * tents, which is connected, and in which no tree has degree + * more than two (but a tent may). Then G has a perfect matching. + * + * (Note: in the statement and proof of the Lemma I will + * consistently use 'tree' to indicate a type of graph vertex as + * opposed to a tent, and not to indicate a tree in the graph- + * theoretic sense.) + * + * Proof: + * + * If we can find a tent of degree 1 joined to a tree of degree + * 2, then any perfect matching must pair that tent with that + * tree. Hence, we can remove both, leaving a smaller graph G' + * which still satisfies all the conditions of the Lemma, and + * which has a perfect matching iff G does. + * + * So, wlog, we may assume G contains no tent of degree 1 joined + * to a tree of degree 2; if it does, we can reduce it as above. + * + * If G has no tent of degree 1 at all, then every tent has + * degree at least two, so there are at least 2n edges in the + * graph. But every tree has degree at most two, so there are at + * most 2n edges. Hence there must be exactly 2n edges, so every + * tree and every tent must have degree exactly two, which means + * that the whole graph consists of a single loop (by + * connectedness), and therefore certainly has a perfect + * matching. + * + * Alternatively, if G does have a tent of degree 1 but it is + * not connected to a tree of degree 2, then the tree it is + * connected to must have degree 1 - and, by connectedness, that + * must mean that that tent and that tree between them form the + * entire graph. This trivial graph has a trivial perfect + * matching. [] + * + * That proves the lemma. Hence, in any case where the matching- + * error highlighter fails to highlight an erroneous component + * (because it has the same number of tents as trees, but they + * cannot be matched up), the above lemma tells us that there + * must be a tree with degree more than 2, i.e. a tree + * orthogonally adjacent to at least three tents. But in that + * case, there must be some pair of those three tents which are + * diagonally adjacent to each other, so the tent-adjacency + * highlighter will necessarily show an error. So any filled + * layout in Tents which is not a correct solution to the puzzle + * must have _some_ error highlighted by the subroutine below. + * + * (Of course it would be nicer if we could highlight all + * errors: in the above example layout, we would like to + * highlight tents A,B as having too few trees between them, and + * trees 2,3 as having too few tents, in addition to marking the + * adjacency problems. But I can't immediately think of any way + * to find the smallest sets of such tents and trees without an + * O(2^N) loop over all subsets of a given component.) + */ + + /* + * ret[0] through to ret[w*h-1] give error markers for the grid + * squares. After that, ret[w*h] to ret[w*h+w-1] give error + * markers for the column numbers, and ret[w*h+w] to + * ret[w*h+w+h-1] for the row numbers. + */ + + /* + * Spot tent-adjacency violations. + */ + for (x = 0; x < w*h; x++) + ret[x] = 0; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (y+1 < h && x+1 < w && + ((grid[y*w+x] == TENT && + grid[(y+1)*w+(x+1)] == TENT) || + (grid[(y+1)*w+x] == TENT && + grid[y*w+(x+1)] == TENT))) { + ret[y*w+x] |= 1 << ERR_ADJ_BOTRIGHT; + ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOPRIGHT; + ret[y*w+(x+1)] |= 1 << ERR_ADJ_BOTLEFT; + ret[(y+1)*w+(x+1)] |= 1 << ERR_ADJ_TOPLEFT; + } + if (y+1 < h && + grid[y*w+x] == TENT && + grid[(y+1)*w+x] == TENT) { + ret[y*w+x] |= 1 << ERR_ADJ_BOT; + ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOP; + } + if (x+1 < w && + grid[y*w+x] == TENT && + grid[y*w+(x+1)] == TENT) { + ret[y*w+x] |= 1 << ERR_ADJ_RIGHT; + ret[y*w+(x+1)] |= 1 << ERR_ADJ_LEFT; + } + } + } + + /* + * Spot numeric clue violations. + */ + for (x = 0; x < w; x++) { + int tents = 0, maybetents = 0; + for (y = 0; y < h; y++) { + if (grid[y*w+x] == TENT) + tents++; + else if (grid[y*w+x] == BLANK) + maybetents++; + } + ret[w*h+x] = (tents > state->numbers->numbers[x] || + tents + maybetents < state->numbers->numbers[x]); + } + for (y = 0; y < h; y++) { + int tents = 0, maybetents = 0; + for (x = 0; x < w; x++) { + if (grid[y*w+x] == TENT) + tents++; + else if (grid[y*w+x] == BLANK) + maybetents++; + } + ret[w*h+w+y] = (tents > state->numbers->numbers[w+y] || + tents + maybetents < state->numbers->numbers[w+y]); + } + + /* + * Identify groups of tents with too few trees between them, + * which we do by constructing the connected components of the + * bipartite adjacency graph between tents and trees + * ('bipartite' in the sense that we deliberately ignore + * adjacency between tents or between trees), and highlighting + * all the tents in any component which has a smaller tree + * count. + */ + dsf_init(dsf, w*h); + /* Construct the equivalence classes. */ + for (y = 0; y < h; y++) { + for (x = 0; x < w-1; x++) { + if ((grid[y*w+x] == TREE && grid[y*w+x+1] == TENT) || + (grid[y*w+x] == TENT && grid[y*w+x+1] == TREE)) + dsf_merge(dsf, y*w+x, y*w+x+1); + } + } + for (y = 0; y < h-1; y++) { + for (x = 0; x < w; x++) { + if ((grid[y*w+x] == TREE && grid[(y+1)*w+x] == TENT) || + (grid[y*w+x] == TENT && grid[(y+1)*w+x] == TREE)) + dsf_merge(dsf, y*w+x, (y+1)*w+x); + } + } + /* Count up the tent/tree difference in each one. */ + for (x = 0; x < w*h; x++) + tmp[x] = 0; + for (x = 0; x < w*h; x++) { + y = dsf_canonify(dsf, x); + if (grid[x] == TREE) + tmp[y]++; + else if (grid[x] == TENT) + tmp[y]--; + } + /* And highlight any tent belonging to an equivalence class with + * a score less than zero. */ + for (x = 0; x < w*h; x++) { + y = dsf_canonify(dsf, x); + if (grid[x] == TENT && tmp[y] < 0) + ret[x] |= 1 << ERR_OVERCOMMITTED; + } + + /* + * Identify groups of trees with too few tents between them. + * This is done similarly, except that we now count BLANK as + * equivalent to TENT, i.e. we only highlight such trees when + * the user hasn't even left _room_ to provide tents for them + * all. (Otherwise, we'd highlight all trees red right at the + * start of the game, before the user had done anything wrong!) + */ +#define TENT(x) ((x)==TENT || (x)==BLANK) + dsf_init(dsf, w*h); + /* Construct the equivalence classes. */ + for (y = 0; y < h; y++) { + for (x = 0; x < w-1; x++) { + if ((grid[y*w+x] == TREE && TENT(grid[y*w+x+1])) || + (TENT(grid[y*w+x]) && grid[y*w+x+1] == TREE)) + dsf_merge(dsf, y*w+x, y*w+x+1); + } + } + for (y = 0; y < h-1; y++) { + for (x = 0; x < w; x++) { + if ((grid[y*w+x] == TREE && TENT(grid[(y+1)*w+x])) || + (TENT(grid[y*w+x]) && grid[(y+1)*w+x] == TREE)) + dsf_merge(dsf, y*w+x, (y+1)*w+x); + } + } + /* Count up the tent/tree difference in each one. */ + for (x = 0; x < w*h; x++) + tmp[x] = 0; + for (x = 0; x < w*h; x++) { + y = dsf_canonify(dsf, x); + if (grid[x] == TREE) + tmp[y]++; + else if (TENT(grid[x])) + tmp[y]--; + } + /* And highlight any tree belonging to an equivalence class with + * a score more than zero. */ + for (x = 0; x < w*h; x++) { + y = dsf_canonify(dsf, x); + if (grid[x] == TREE && tmp[y] > 0) + ret[x] |= 1 << ERR_OVERCOMMITTED; + } +#undef TENT + + sfree(tmp); + return ret; +} + +static void draw_err_adj(drawing *dr, game_drawstate *ds, int x, int y) +{ + int coords[8]; + int yext, xext; + + /* + * Draw a diamond. + */ + coords[0] = x - TILESIZE*2/5; + coords[1] = y; + coords[2] = x; + coords[3] = y - TILESIZE*2/5; + coords[4] = x + TILESIZE*2/5; + coords[5] = y; + coords[6] = x; + coords[7] = y + TILESIZE*2/5; + draw_polygon(dr, coords, 4, COL_ERROR, COL_GRID); + + /* + * Draw an exclamation mark in the diamond. This turns out to + * look unpleasantly off-centre if done via draw_text, so I do + * it by hand on the basis that exclamation marks aren't that + * difficult to draw... + */ + xext = TILESIZE/16; + yext = TILESIZE*2/5 - (xext*2+2); + draw_rect(dr, x-xext, y-yext, xext*2+1, yext*2+1 - (xext*3), + COL_ERRTEXT); + draw_rect(dr, x-xext, y+yext-xext*2+1, xext*2+1, xext*2, COL_ERRTEXT); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, + int x, int y, int v, int cur, int printing) +{ + int err; + int tx = COORD(x), ty = COORD(y); + int cx = tx + TILESIZE/2, cy = ty + TILESIZE/2; + + err = v & ~15; + v &= 15; + + clip(dr, tx, ty, TILESIZE, TILESIZE); + + if (!printing) { + draw_rect(dr, tx, ty, TILESIZE, TILESIZE, COL_GRID); + draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, + (v == BLANK ? COL_BACKGROUND : COL_GRASS)); + } + + if (v == TREE) { + int i; + + (printing ? draw_rect_outline : draw_rect) + (dr, cx-TILESIZE/15, ty+TILESIZE*3/10, + 2*(TILESIZE/15)+1, (TILESIZE*9/10 - TILESIZE*3/10), + (err & (1<p.w, h = state->p.h; + int x, y, flashing; + int cx = -1, cy = -1; + int cmoved = 0; + char *tmpgrid; + int *errors; + + if (ui) { + if (ui->cdisp) { cx = ui->cx; cy = ui->cy; } + if (cx != ds->cx || cy != ds->cy) cmoved = 1; + } + + if (printing || !ds->started) { + if (!printing) { + int ww, wh; + game_compute_size(&state->p, TILESIZE, &ww, &wh); + draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND); + draw_update(dr, 0, 0, ww, wh); + ds->started = TRUE; + } + + if (printing) + print_line_width(dr, TILESIZE/64); + + /* + * Draw the grid. + */ + for (y = 0; y <= h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), COL_GRID); + for (x = 0; x <= w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), COL_GRID); + } + + if (flashtime > 0) + flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; + else + flashing = FALSE; + + /* + * Find errors. For this we use _part_ of the information from a + * currently active drag: we transform dsx,dsy but not anything + * else. (This seems to strike a good compromise between having + * the error highlights respond instantly to single clicks, but + * not giving constant feedback during a right-drag.) + */ + if (ui && ui->drag_button >= 0) { + tmpgrid = snewn(w*h, char); + memcpy(tmpgrid, state->grid, w*h); + tmpgrid[ui->dsy * w + ui->dsx] = + drag_xform(ui, ui->dsx, ui->dsy, tmpgrid[ui->dsy * w + ui->dsx]); + errors = find_errors(state, tmpgrid); + sfree(tmpgrid); + } else { + errors = find_errors(state, state->grid); + } + + /* + * Draw the grid. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int v = state->grid[y*w+x]; + int credraw = 0; + + /* + * We deliberately do not take drag_ok into account + * here, because user feedback suggests that it's + * marginally nicer not to have the drag effects + * flickering on and off disconcertingly. + */ + if (ui && ui->drag_button >= 0) + v = drag_xform(ui, x, y, v); + + if (flashing && (v == TREE || v == TENT)) + v = NONTENT; + + if (cmoved) { + if ((x == cx && y == cy) || + (x == ds->cx && y == ds->cy)) credraw = 1; + } + + v |= errors[y*w+x]; + + if (printing || ds->drawn[y*w+x] != v || credraw) { + draw_tile(dr, ds, x, y, v, (x == cx && y == cy), printing); + if (!printing) + ds->drawn[y*w+x] = v; + } + } + } + + /* + * Draw (or redraw, if their error-highlighted state has + * changed) the numbers. + */ + for (x = 0; x < w; x++) { + if (printing || ds->numbersdrawn[x] != errors[w*h+x]) { + char buf[80]; + draw_rect(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1, + COL_BACKGROUND); + sprintf(buf, "%d", state->numbers->numbers[x]); + draw_text(dr, COORD(x) + TILESIZE/2, COORD(h+1), + FONT_VARIABLE, TILESIZE/2, ALIGN_HCENTRE|ALIGN_VNORMAL, + (errors[w*h+x] ? COL_ERROR : COL_GRID), buf); + draw_update(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1); + if (!printing) + ds->numbersdrawn[x] = errors[w*h+x]; + } + } + for (y = 0; y < h; y++) { + if (printing || ds->numbersdrawn[w+y] != errors[w*h+w+y]) { + char buf[80]; + draw_rect(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE, + COL_BACKGROUND); + sprintf(buf, "%d", state->numbers->numbers[w+y]); + draw_text(dr, COORD(w+1), COORD(y) + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, ALIGN_HRIGHT|ALIGN_VCENTRE, + (errors[w*h+w+y] ? COL_ERROR : COL_GRID), buf); + draw_update(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE); + if (!printing) + ds->numbersdrawn[w+y] = errors[w*h+w+y]; + } + } + + if (cmoved) { + ds->cx = cx; + ds->cy = cy; + } + + sfree(errors); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int_redraw(dr, ds, oldstate, state, dir, ui, animtime, flashtime, FALSE); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return FLASH_TIME; + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int c; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND); + c = print_mono_colour(dr, 0); assert(c == COL_GRID); + c = print_mono_colour(dr, 1); assert(c == COL_GRASS); + c = print_mono_colour(dr, 0); assert(c == COL_TREETRUNK); + c = print_mono_colour(dr, 0); assert(c == COL_TREELEAF); + c = print_mono_colour(dr, 0); assert(c == COL_TENT); + + int_redraw(dr, ds, NULL, state, +1, NULL, 0.0F, 0.0F, TRUE); +} + +#ifdef COMBINED +#define thegame tents +#endif + +const struct game thegame = { + "Tents", "games.tents", "tents", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s, *s2; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff, really_verbose = FALSE; + struct solver_scratch *sc; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_verbose = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + s2 = new_game(NULL, p, desc); + + sc = new_scratch(p->w, p->h); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + for (diff = 0; diff < DIFFCOUNT; diff++) { + ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers, + s2->grid, sc, diff); + if (ret < 2) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: too hard to solve internally\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == 0) + printf("Difficulty rating: impossible (no solution exists)\n"); + else if (ret == 1) + printf("Difficulty rating: %s\n", tents_diffnames[diff]); + } else { + verbose = really_verbose; + ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers, + s2->grid, sc, diff); + if (ret == 0) + printf("Puzzle is inconsistent\n"); + else + fputs(game_text_format(s2), stdout); + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/towers.R b/apps/plugins/puzzles/towers.R new file mode 100644 index 0000000000..c060c697a7 --- /dev/null +++ b/apps/plugins/puzzles/towers.R @@ -0,0 +1,25 @@ +# -*- makefile -*- + +TOWERS_LATIN_EXTRA = tree234 maxflow +TOWERS_EXTRA = latin TOWERS_LATIN_EXTRA + +towers : [X] GTK COMMON towers TOWERS_EXTRA towers-icon|no-icon + +towers : [G] WINDOWS COMMON towers TOWERS_EXTRA towers.res|noicon.res + +towerssolver : [U] towers[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] TOWERS_LATIN_EXTRA STANDALONE +towerssolver : [C] towers[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] TOWERS_LATIN_EXTRA STANDALONE + +ALL += towers[COMBINED] TOWERS_EXTRA + +!begin am gtk +GAMES += towers +!end + +!begin >list.c + A(towers) \ +!end + +!begin >gamedesc.txt +towers:towers.exe:Towers:Tower-placing Latin square puzzle:Complete the latin square of towers in accordance with the clues. +!end diff --git a/apps/plugins/puzzles/towers.c b/apps/plugins/puzzles/towers.c new file mode 100644 index 0000000000..d8e087a4bc --- /dev/null +++ b/apps/plugins/puzzles/towers.c @@ -0,0 +1,2104 @@ +/* + * towers.c: the puzzle also known as 'Skyscrapers'. + * + * Possible future work: + * + * - Relax the upper bound on grid size at 9? + * + I'd need TOCHAR and FROMCHAR macros a bit like group's, to + * be used wherever this code has +'0' or -'0' + * + the pencil marks in the drawstate would need a separate + * word to live in + * + the clues outside the grid would have to cope with being + * multi-digit, meaning in particular that the text formatting + * would become more unpleasant + * + most importantly, though, the solver just isn't fast + * enough. Even at size 9 it can't really do the solver_hard + * factorial-time enumeration at a sensible rate. Easy puzzles + * higher than that would be possible, but more latin-squarey + * than skyscrapery, as it were. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "latin.h" + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,solver_easy,e) \ + A(HARD,Hard,solver_hard,h) \ + A(EXTREME,Extreme,NULL,x) \ + A(UNREASONABLE,Unreasonable,NULL,u) +#define ENUM(upper,title,func,lower) DIFF_ ## upper, +#define TITLE(upper,title,func,lower) #title, +#define ENCODE(upper,title,func,lower) #lower +#define CONFIG(upper,title,func,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const towers_diffnames[] = { DIFFLIST(TITLE) }; +static char const towers_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +enum { + COL_BACKGROUND, + COL_GRID, + COL_USER, + COL_HIGHLIGHT, + COL_ERROR, + COL_PENCIL, + COL_DONE, + NCOLOURS +}; + +struct game_params { + int w, diff; +}; + +struct clues { + int refcount; + int w; + /* + * An array of 4w integers, of which: + * - the first w run across the top + * - the next w across the bottom + * - the third w down the left + * - the last w down the right. + */ + int *clues; + + /* + * An array of w*w digits. + */ + digit *immutable; +}; + +/* + * Macros to compute clue indices and coordinates. + */ +#define STARTSTEP(start, step, index, w) do { \ + if (index < w) \ + start = index, step = w; \ + else if (index < 2*w) \ + start = (w-1)*w+(index-w), step = -w; \ + else if (index < 3*w) \ + start = w*(index-2*w), step = 1; \ + else \ + start = w*(index-3*w)+(w-1), step = -1; \ +} while (0) +#define CSTARTSTEP(start, step, index, w) \ + STARTSTEP(start, step, (((index)+2*w)%(4*w)), w) +#define CLUEPOS(x, y, index, w) do { \ + if (index < w) \ + x = index, y = -1; \ + else if (index < 2*w) \ + x = index-w, y = w; \ + else if (index < 3*w) \ + x = -1, y = index-2*w; \ + else \ + x = w, y = index-3*w; \ +} while (0) + +#ifdef STANDALONE_SOLVER +static const char *const cluepos[] = { + "above column", "below column", "left of row", "right of row" +}; +#endif + +struct game_state { + game_params par; + struct clues *clues; + unsigned char *clues_done; + digit *grid; + int *pencil; /* bitmaps using bits 1<<1..1<w = 5; + ret->diff = DIFF_EASY; + + return ret; +} + +const static struct game_params towers_presets[] = { + { 4, DIFF_EASY }, + { 5, DIFF_EASY }, + { 5, DIFF_HARD }, + { 6, DIFF_EASY }, + { 6, DIFF_HARD }, + { 6, DIFF_EXTREME }, + { 6, DIFF_UNREASONABLE }, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(towers_presets)) + return FALSE; + + ret = snew(game_params); + *ret = towers_presets[i]; /* structure copy */ + + sprintf(buf, "%dx%d %s", ret->w, ret->w, towers_diffnames[ret->diff]); + + *name = dupstr(buf); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + + if (*p == 'd') { + int i; + p++; + params->diff = DIFFCOUNT+1; /* ...which is invalid */ + if (*p) { + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == towers_diffchars[i]) + params->diff = i; + } + p++; + } + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[80]; + + sprintf(ret, "%d", params->w); + if (full) + sprintf(ret + strlen(ret), "d%c", towers_diffchars[params->diff]); + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Grid size"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Difficulty"; + ret[1].type = C_CHOICES; + ret[1].sval = DIFFCONFIG; + ret[1].ival = params->diff; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->diff = cfg[1].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 3 || params->w > 9) + return "Grid size must be between 3 and 9"; + if (params->diff >= DIFFCOUNT) + return "Unknown difficulty rating"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +struct solver_ctx { + int w, diff; + int started; + int *clues; + long *iscratch; + int *dscratch; +}; + +static int solver_easy(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int w = ctx->w; + int c, i, j, n, m, furthest; + int start, step, cstart, cstep, clue, pos, cpos; + int ret = 0; +#ifdef STANDALONE_SOLVER + char prefix[256]; +#endif + + if (!ctx->started) { + ctx->started = TRUE; + /* + * One-off loop to help get started: when a pair of facing + * clues sum to w+1, it must mean that the row consists of + * two increasing sequences back to back, so we can + * immediately place the highest digit by knowing the + * lengths of those two sequences. + */ + for (c = 0; c < 3*w; c = (c == w-1 ? 2*w : c+1)) { + int c2 = c + w; + + if (ctx->clues[c] && ctx->clues[c2] && + ctx->clues[c] + ctx->clues[c2] == w+1) { + STARTSTEP(start, step, c, w); + CSTARTSTEP(cstart, cstep, c, w); + pos = start + (ctx->clues[c]-1)*step; + cpos = cstart + (ctx->clues[c]-1)*cstep; + if (solver->cube[cpos*w+w-1]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*sfacing clues on %s %d are maximal:\n", + solver_recurse_depth*4, "", + c>=2*w ? "row" : "column", c % w + 1); + printf("%*s placing %d at (%d,%d)\n", + solver_recurse_depth*4, "", + w, pos%w+1, pos/w+1); + } +#endif + latin_solver_place(solver, pos%w, pos/w, w); + ret = 1; + } else { + ret = -1; + } + } + } + + if (ret) + return ret; + } + + /* + * Go over every clue doing reasonably simple heuristic + * deductions. + */ + for (c = 0; c < 4*w; c++) { + clue = ctx->clues[c]; + if (!clue) + continue; + STARTSTEP(start, step, c, w); + CSTARTSTEP(cstart, cstep, c, w); + + /* Find the location of each number in the row. */ + for (i = 0; i < w; i++) + ctx->dscratch[i] = w; + for (i = 0; i < w; i++) + if (solver->grid[start+i*step]) + ctx->dscratch[solver->grid[start+i*step]-1] = i; + + n = m = 0; + furthest = w; + for (i = w; i >= 1; i--) { + if (ctx->dscratch[i-1] == w) { + break; + } else if (ctx->dscratch[i-1] < furthest) { + furthest = ctx->dscratch[i-1]; + m = i; + n++; + } + } + if (clue == n+1 && furthest > 1) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + sprintf(prefix, "%*sclue %s %d is nearly filled:\n", + solver_recurse_depth*4, "", + cluepos[c/w], c%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ +#endif + /* + * We can already see an increasing sequence of the very + * highest numbers, of length one less than that + * specified in the clue. All of those numbers _must_ be + * part of the clue sequence, so the number right next + * to the clue must be the final one - i.e. it must be + * bigger than any of the numbers between it and m. This + * allows us to rule out small numbers in that square. + * + * (This is a generalisation of the obvious deduction + * that when you see a clue saying 1, it must be right + * next to the largest possible number; and similarly, + * when you see a clue saying 2 opposite that, it must + * be right next to the second-largest.) + */ + j = furthest-1; /* number of small numbers we can rule out */ + for (i = 1; i <= w && j > 0; i++) { + if (ctx->dscratch[i-1] < w && ctx->dscratch[i-1] >= furthest) + continue; /* skip this number, it's elsewhere */ + j--; + if (solver->cube[cstart*w+i-1]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%s%*s ruling out %d at (%d,%d)\n", + prefix, solver_recurse_depth*4, "", + i, start%w+1, start/w+1); + prefix[0] = '\0'; + } +#endif + solver->cube[cstart*w+i-1] = 0; + ret = 1; + } + } + } + + if (ret) + return ret; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + sprintf(prefix, "%*slower bounds for clue %s %d:\n", + solver_recurse_depth*4, "", + cluepos[c/w], c%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ +#endif + + i = 0; + for (n = w; n > 0; n--) { + /* + * The largest number cannot occur in the first (clue-1) + * squares of the row, or else there wouldn't be space + * for a sufficiently long increasing sequence which it + * terminated. The second-largest number (not counting + * any that are known to be on the far side of a larger + * number and hence excluded from this sequence) cannot + * occur in the first (clue-2) squares, similarly, and + * so on. + */ + + if (ctx->dscratch[n-1] < w) { + for (m = n+1; m < w; m++) + if (ctx->dscratch[m] < ctx->dscratch[n-1]) + break; + if (m < w) + continue; /* this number doesn't count */ + } + + for (j = 0; j < clue - i - 1; j++) + if (solver->cube[(cstart + j*cstep)*w+n-1]) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int pos = start+j*step; + printf("%s%*s ruling out %d at (%d,%d)\n", + prefix, solver_recurse_depth*4, "", + n, pos%w+1, pos/w+1); + prefix[0] = '\0'; + } +#endif + solver->cube[(cstart + j*cstep)*w+n-1] = 0; + ret = 1; + } + i++; + } + } + + if (ret) + return ret; + + return 0; +} + +static int solver_hard(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int w = ctx->w; + int c, i, j, n, best, clue, start, step, ret; + long bitmap; +#ifdef STANDALONE_SOLVER + char prefix[256]; +#endif + + /* + * Go over every clue analysing all possibilities. + */ + for (c = 0; c < 4*w; c++) { + clue = ctx->clues[c]; + if (!clue) + continue; + CSTARTSTEP(start, step, c, w); + + for (i = 0; i < w; i++) + ctx->iscratch[i] = 0; + + /* + * Instead of a tedious physical recursion, I iterate in the + * scratch array through all possibilities. At any given + * moment, i indexes the element of the box that will next + * be incremented. + */ + i = 0; + ctx->dscratch[i] = 0; + best = n = 0; + bitmap = 0; + + while (1) { + if (i < w) { + /* + * Find the next valid value for cell i. + */ + int limit = (n == clue ? best : w); + int pos = start + step * i; + for (j = ctx->dscratch[i] + 1; j <= limit; j++) { + if (bitmap & (1L << j)) + continue; /* used this one already */ + if (!solver->cube[pos*w+j-1]) + continue; /* ruled out already */ + + /* Found one. */ + break; + } + + if (j > limit) { + /* No valid values left; drop back. */ + i--; + if (i < 0) + break; /* overall iteration is finished */ + bitmap &= ~(1L << ctx->dscratch[i]); + if (ctx->dscratch[i] == best) { + n--; + best = 0; + for (j = 0; j < i; j++) + if (best < ctx->dscratch[j]) + best = ctx->dscratch[j]; + } + } else { + /* Got a valid value; store it and move on. */ + bitmap |= 1L << j; + ctx->dscratch[i++] = j; + if (j > best) { + best = j; + n++; + } + ctx->dscratch[i] = 0; + } + } else { + if (n == clue) { + for (j = 0; j < w; j++) + ctx->iscratch[j] |= 1L << ctx->dscratch[j]; + } + i--; + bitmap &= ~(1L << ctx->dscratch[i]); + if (ctx->dscratch[i] == best) { + n--; + best = 0; + for (j = 0; j < i; j++) + if (best < ctx->dscratch[j]) + best = ctx->dscratch[j]; + } + } + } + +#ifdef STANDALONE_SOLVER + if (solver_show_working) + sprintf(prefix, "%*sexhaustive analysis of clue %s %d:\n", + solver_recurse_depth*4, "", + cluepos[c/w], c%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ +#endif + + ret = 0; + + for (i = 0; i < w; i++) { + int pos = start + step * i; + for (j = 1; j <= w; j++) { + if (solver->cube[pos*w+j-1] && + !(ctx->iscratch[i] & (1L << j))) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%s%*s ruling out %d at (%d,%d)\n", + prefix, solver_recurse_depth*4, "", + j, pos/w+1, pos%w+1); + prefix[0] = '\0'; + } +#endif + solver->cube[pos*w+j-1] = 0; + ret = 1; + } + } + + /* + * Once we find one clue we can do something with in + * this way, revert to trying easier deductions, so as + * not to generate solver diagnostics that make the + * problem look harder than it is. + */ + if (ret) + return ret; + } + } + + return 0; +} + +#define SOLVER(upper,title,func,lower) func, +static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) }; + +static int solver(int w, int *clues, digit *soln, int maxdiff) +{ + int ret; + struct solver_ctx ctx; + + ctx.w = w; + ctx.diff = maxdiff; + ctx.clues = clues; + ctx.started = FALSE; + ctx.iscratch = snewn(w, long); + ctx.dscratch = snewn(w+1, int); + + ret = latin_solver(soln, w, maxdiff, + DIFF_EASY, DIFF_HARD, DIFF_EXTREME, + DIFF_EXTREME, DIFF_UNREASONABLE, + towers_solvers, &ctx, NULL, NULL); + + sfree(ctx.iscratch); + sfree(ctx.dscratch); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Grid generation. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, a = w*w; + digit *grid, *soln, *soln2; + int *clues, *order; + int i, ret; + int diff = params->diff; + char *desc, *p; + + /* + * Difficulty exceptions: some combinations of size and + * difficulty cannot be satisfied, because all puzzles of at + * most that difficulty are actually even easier. + * + * Remember to re-test this whenever a change is made to the + * solver logic! + * + * I tested it using the following shell command: + +for d in e h x u; do + for i in {3..9}; do + echo -n "./towers --generate 1 ${i}d${d}: " + perl -e 'alarm 30; exec @ARGV' ./towers --generate 1 ${i}d${d} >/dev/null \ + && echo ok + done +done + + * Of course, it's better to do that after taking the exceptions + * _out_, so as to detect exceptions that should be removed as + * well as those which should be added. + */ + if (diff > DIFF_HARD && w <= 3) + diff = DIFF_HARD; + + grid = NULL; + clues = snewn(4*w, int); + soln = snewn(a, digit); + soln2 = snewn(a, digit); + order = snewn(max(4*w,a), int); + + while (1) { + /* + * Construct a latin square to be the solution. + */ + sfree(grid); + grid = latin_generate(w, rs); + + /* + * Fill in the clues. + */ + for (i = 0; i < 4*w; i++) { + int start, step, j, k, best; + STARTSTEP(start, step, i, w); + k = best = 0; + for (j = 0; j < w; j++) { + if (grid[start+j*step] > best) { + best = grid[start+j*step]; + k++; + } + } + clues[i] = k; + } + + /* + * Remove the grid numbers and then the clues, one by one, + * for as long as the game remains soluble at the given + * difficulty. + */ + memcpy(soln, grid, a); + + if (diff == DIFF_EASY && w <= 5) { + /* + * Special case: for Easy-mode grids that are small + * enough, it's nice to be able to find completely empty + * grids. + */ + memset(soln2, 0, a); + ret = solver(w, clues, soln2, diff); + if (ret > diff) + continue; + } + + for (i = 0; i < a; i++) + order[i] = i; + shuffle(order, a, sizeof(*order), rs); + for (i = 0; i < a; i++) { + int j = order[i]; + + memcpy(soln2, grid, a); + soln2[j] = 0; + ret = solver(w, clues, soln2, diff); + if (ret <= diff) + grid[j] = 0; + } + + if (diff > DIFF_EASY) { /* leave all clues on Easy mode */ + for (i = 0; i < 4*w; i++) + order[i] = i; + shuffle(order, 4*w, sizeof(*order), rs); + for (i = 0; i < 4*w; i++) { + int j = order[i]; + int clue = clues[j]; + + memcpy(soln2, grid, a); + clues[j] = 0; + ret = solver(w, clues, soln2, diff); + if (ret > diff) + clues[j] = clue; + } + } + + /* + * See if the game can be solved at the specified difficulty + * level, but not at the one below. + */ + memcpy(soln2, grid, a); + ret = solver(w, clues, soln2, diff); + if (ret != diff) + continue; /* go round again */ + + /* + * We've got a usable puzzle! + */ + break; + } + + /* + * Encode the puzzle description. + */ + desc = snewn(40*a, char); + p = desc; + for (i = 0; i < 4*w; i++) { + if (i) + *p++ = '/'; + if (clues[i]) + p += sprintf(p, "%d", clues[i]); + } + for (i = 0; i < a; i++) + if (grid[i]) + break; + if (i < a) { + int run = 0; + + *p++ = ','; + + for (i = 0; i <= a; i++) { + int n = (i < a ? grid[i] : -1); + + if (!n) + run++; + else { + if (run) { + while (run > 0) { + int thisrun = min(run, 26); + *p++ = thisrun - 1 + 'a'; + run -= thisrun; + } + } else { + /* + * If there's a number in the very top left or + * bottom right, there's no point putting an + * unnecessary _ before or after it. + */ + if (i > 0 && n > 0) + *p++ = '_'; + } + if (n > 0) + p += sprintf(p, "%d", n); + run = 0; + } + } + } + *p++ = '\0'; + desc = sresize(desc, p - desc, char); + + /* + * Encode the solution. + */ + *aux = snewn(a+2, char); + (*aux)[0] = 'S'; + for (i = 0; i < a; i++) + (*aux)[i+1] = '0' + soln[i]; + (*aux)[a+1] = '\0'; + + sfree(grid); + sfree(clues); + sfree(soln); + sfree(soln2); + sfree(order); + + return desc; +} + +/* ---------------------------------------------------------------------- + * Gameplay. + */ + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, a = w*w; + const char *p = desc; + int i, clue; + + /* + * Verify that the right number of clues are given, and that + * they're in range. + */ + for (i = 0; i < 4*w; i++) { + if (!*p) + return "Too few clues for grid size"; + + if (i > 0) { + if (*p != '/') + return "Expected commas between clues"; + p++; + } + + if (isdigit((unsigned char)*p)) { + clue = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + + if (clue <= 0 || clue > w) + return "Clue number out of range"; + } + } + if (*p == '/') + return "Too many clues for grid size"; + + if (*p == ',') { + /* + * Verify that the right amount of grid data is given, and + * that any grid elements provided are in range. + */ + int squares = 0; + + p++; + while (*p) { + int c = *p++; + if (c >= 'a' && c <= 'z') { + squares += c - 'a' + 1; + } else if (c == '_') { + /* do nothing */; + } else if (c > '0' && c <= '9') { + int val = atoi(p-1); + if (val < 1 || val > w) + return "Out-of-range number in grid description"; + squares++; + while (*p && isdigit((unsigned char)*p)) p++; + } else + return "Invalid character in game description"; + } + + if (squares < a) + return "Not enough data to fill grid"; + + if (squares > a) + return "Too much data to fit in grid"; + } + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, a = w*w; + game_state *state = snew(game_state); + const char *p = desc; + int i; + + state->par = *params; /* structure copy */ + state->clues = snew(struct clues); + state->clues->refcount = 1; + state->clues->w = w; + state->clues->clues = snewn(4*w, int); + state->clues->immutable = snewn(a, digit); + state->grid = snewn(a, digit); + state->clues_done = snewn(4*w, unsigned char); + state->pencil = snewn(a, int); + + for (i = 0; i < a; i++) { + state->grid[i] = 0; + state->pencil[i] = 0; + } + + memset(state->clues->immutable, 0, a); + memset(state->clues_done, 0, 4*w*sizeof(unsigned char)); + + for (i = 0; i < 4*w; i++) { + if (i > 0) { + assert(*p == '/'); + p++; + } + if (*p && isdigit((unsigned char)*p)) { + state->clues->clues[i] = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else + state->clues->clues[i] = 0; + } + + if (*p == ',') { + int pos = 0; + p++; + while (*p) { + int c = *p++; + if (c >= 'a' && c <= 'z') { + pos += c - 'a' + 1; + } else if (c == '_') { + /* do nothing */; + } else if (c > '0' && c <= '9') { + int val = atoi(p-1); + assert(val >= 1 && val <= w); + assert(pos < a); + state->grid[pos] = state->clues->immutable[pos] = val; + pos++; + while (*p && isdigit((unsigned char)*p)) p++; + } else + assert(!"Corrupt game description"); + } + assert(pos == a); + } + assert(!*p); + + state->completed = state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->par.w, a = w*w; + game_state *ret = snew(game_state); + + ret->par = state->par; /* structure copy */ + + ret->clues = state->clues; + ret->clues->refcount++; + + ret->grid = snewn(a, digit); + ret->pencil = snewn(a, int); + ret->clues_done = snewn(4*w, unsigned char); + memcpy(ret->grid, state->grid, a*sizeof(digit)); + memcpy(ret->pencil, state->pencil, a*sizeof(int)); + memcpy(ret->clues_done, state->clues_done, 4*w*sizeof(unsigned char)); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->pencil); + sfree(state->clues_done); + if (--state->clues->refcount <= 0) { + sfree(state->clues->immutable); + sfree(state->clues->clues); + sfree(state->clues); + } + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->par.w, a = w*w; + int i, ret; + digit *soln; + char *out; + + if (aux) + return dupstr(aux); + + soln = snewn(a, digit); + memcpy(soln, state->clues->immutable, a); + + ret = solver(w, state->clues->clues, soln, DIFFCOUNT-1); + + if (ret == diff_impossible) { + *error = "No solution exists for this puzzle"; + out = NULL; + } else if (ret == diff_ambiguous) { + *error = "Multiple solutions exist for this puzzle"; + out = NULL; + } else { + out = snewn(a+2, char); + out[0] = 'S'; + for (i = 0; i < a; i++) + out[i+1] = '0' + soln[i]; + out[a+1] = '\0'; + } + + sfree(soln); + return out; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->par.w /* , a = w*w */; + char *ret; + char *p; + int x, y; + int total; + + /* + * We have: + * - a top clue row, consisting of three spaces, then w clue + * digits with spaces between (total 2*w+3 chars including + * newline) + * - a blank line (one newline) + * - w main rows, consisting of a left clue digit, two spaces, + * w grid digits with spaces between, two spaces and a right + * clue digit (total 2*w+6 chars each including newline) + * - a blank line (one newline) + * - a bottom clue row (same as top clue row) + * - terminating NUL. + * + * Total size is therefore 2*(2*w+3) + 2 + w*(2*w+6) + 1 + * = 2w^2+10w+9. + */ + total = 2*w*w + 10*w + 9; + ret = snewn(total, char); + p = ret; + + /* Top clue row. */ + *p++ = ' '; *p++ = ' '; + for (x = 0; x < w; x++) { + *p++ = ' '; + *p++ = (state->clues->clues[x] ? '0' + state->clues->clues[x] : ' '); + } + *p++ = '\n'; + + /* Blank line. */ + *p++ = '\n'; + + /* Main grid. */ + for (y = 0; y < w; y++) { + *p++ = (state->clues->clues[y+2*w] ? '0' + state->clues->clues[y+2*w] : + ' '); + *p++ = ' '; + for (x = 0; x < w; x++) { + *p++ = ' '; + *p++ = (state->grid[y*w+x] ? '0' + state->grid[y*w+x] : ' '); + } + *p++ = ' '; *p++ = ' '; + *p++ = (state->clues->clues[y+3*w] ? '0' + state->clues->clues[y+3*w] : + ' '); + *p++ = '\n'; + } + + /* Blank line. */ + *p++ = '\n'; + + /* Bottom clue row. */ + *p++ = ' '; *p++ = ' '; + for (x = 0; x < w; x++) { + *p++ = ' '; + *p++ = (state->clues->clues[x+w] ? '0' + state->clues->clues[x+w] : + ' '); + } + *p++ = '\n'; + + *p++ = '\0'; + assert(p == ret + total); + + return ret; +} + +struct game_ui { + /* + * These are the coordinates of the currently highlighted + * square on the grid, if hshow = 1. + */ + int hx, hy; + /* + * This indicates whether the current highlight is a + * pencil-mark one or a real one. + */ + int hpencil; + /* + * This indicates whether or not we're showing the highlight + * (used to be hx = hy = -1); important so that when we're + * using the cursor keys it doesn't keep coming back at a + * fixed position. When hshow = 1, pressing a valid number + * or letter key or Space will enter that number or letter in the grid. + */ + int hshow; + /* + * This indicates whether we're using the highlight as a cursor; + * it means that it doesn't vanish on a keypress, and that it is + * allowed on immutable squares. + */ + int hcursor; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + int w = newstate->par.w; + /* + * We prevent pencil-mode highlighting of a filled square, unless + * we're using the cursor keys. So if the user has just filled in + * a square which we had a pencil-mode highlight in (by Undo, or + * by Redo, or by Solve), then we cancel the highlight. + */ + if (ui->hshow && ui->hpencil && !ui->hcursor && + newstate->grid[ui->hy * w + ui->hx] != 0) { + ui->hshow = 0; + } +} + +#define PREFERRED_TILESIZE 48 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE * 9 / 8) +#define COORD(x) ((x)*TILESIZE + BORDER) +#define FROMCOORD(x) (((x)+(TILESIZE-BORDER)) / TILESIZE - 1) + +/* These always return positive values, though y offsets are actually -ve */ +#define X_3D_DISP(height, w) ((height) * TILESIZE / (8 * (w))) +#define Y_3D_DISP(height, w) ((height) * TILESIZE / (4 * (w))) + +#define FLASH_TIME 0.4F + +#define DF_PENCIL_SHIFT 16 +#define DF_CLUE_DONE 0x10000 +#define DF_ERROR 0x8000 +#define DF_HIGHLIGHT 0x4000 +#define DF_HIGHLIGHT_PENCIL 0x2000 +#define DF_IMMUTABLE 0x1000 +#define DF_PLAYAREA 0x0800 +#define DF_DIGIT_MASK 0x00FF + +struct game_drawstate { + int tilesize; + int three_d; /* default 3D graphics are user-disableable */ + int started; + long *tiles; /* (w+2)*(w+2) temp space */ + long *drawn; /* (w+2)*(w+2)*4: current drawn data */ + int *errtmp; +}; + +static int check_errors(const game_state *state, int *errors) +{ + int w = state->par.w /*, a = w*w */; + int W = w+2, A = W*W; /* the errors array is (w+2) square */ + int *clues = state->clues->clues; + digit *grid = state->grid; + int i, x, y, errs = FALSE; + int tmp[32]; + + assert(w < lenof(tmp)); + + if (errors) + for (i = 0; i < A; i++) + errors[i] = 0; + + for (y = 0; y < w; y++) { + unsigned long mask = 0, errmask = 0; + for (x = 0; x < w; x++) { + unsigned long bit = 1UL << grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1L << (w+1)) - (1L << 1)) { + errs = TRUE; + errmask &= ~1UL; + if (errors) { + for (x = 0; x < w; x++) + if (errmask & (1UL << grid[y*w+x])) + errors[(y+1)*W+(x+1)] = TRUE; + } + } + } + + for (x = 0; x < w; x++) { + unsigned long mask = 0, errmask = 0; + for (y = 0; y < w; y++) { + unsigned long bit = 1UL << grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1 << (w+1)) - (1 << 1)) { + errs = TRUE; + errmask &= ~1UL; + if (errors) { + for (y = 0; y < w; y++) + if (errmask & (1UL << grid[y*w+x])) + errors[(y+1)*W+(x+1)] = TRUE; + } + } + } + + for (i = 0; i < 4*w; i++) { + int start, step, j, n, best; + STARTSTEP(start, step, i, w); + + if (!clues[i]) + continue; + + best = n = 0; + for (j = 0; j < w; j++) { + int number = grid[start+j*step]; + if (!number) + break; /* can't tell what happens next */ + if (number > best) { + best = number; + n++; + } + } + + if (n > clues[i] || (best == w && n < clues[i]) || + (best < w && n == clues[i])) { + if (errors) { + int x, y; + CLUEPOS(x, y, i, w); + errors[(y+1)*W+(x+1)] = TRUE; + } + errs = TRUE; + } + } + + return errs; +} + +static int clue_index(const game_state *state, int x, int y) +{ + int w = state->par.w; + + if (x == -1 || x == w) + return w * (x == -1 ? 2 : 3) + y; + else if (y == -1 || y == w) + return (y == -1 ? 0 : w) + x; + + return -1; +} + +static int is_clue(const game_state *state, int x, int y) +{ + int w = state->par.w; + + if (((x == -1 || x == w) && y >= 0 && y < w) || + ((y == -1 || y == w) && x >= 0 && x < w)) + { + if (state->clues->clues[clue_index(state, x, y)] & DF_DIGIT_MASK) + return TRUE; + } + + return FALSE; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->par.w; + int shift_or_control = button & (MOD_SHFT | MOD_CTRL); + int tx, ty; + char buf[80]; + + button &= ~MOD_MASK; + + tx = FROMCOORD(x); + ty = FROMCOORD(y); + + if (ds->three_d) { + /* + * In 3D mode, just locating the mouse click in the natural + * square grid may not be sufficient to tell which tower the + * user clicked on. Investigate the _tops_ of the nearby + * towers to see if a click on one grid square was actually + * a click on a tower protruding into that region from + * another. + */ + int dx, dy; + for (dy = 0; dy <= 1; dy++) + for (dx = 0; dx >= -1; dx--) { + int cx = tx + dx, cy = ty + dy; + if (cx >= 0 && cx < w && cy >= 0 && cy < w) { + int height = state->grid[cy*w+cx]; + int bx = COORD(cx), by = COORD(cy); + int ox = bx + X_3D_DISP(height, w); + int oy = by - Y_3D_DISP(height, w); + if (/* on top face? */ + (x - ox >= 0 && x - ox < TILESIZE && + y - oy >= 0 && y - oy < TILESIZE) || + /* in triangle between top-left corners? */ + (ox > bx && x >= bx && x <= ox && y <= by && + (by-y) * (ox-bx) <= (by-oy) * (x-bx)) || + /* in triangle between bottom-right corners? */ + (ox > bx && x >= bx+TILESIZE && x <= ox+TILESIZE && + y >= oy+TILESIZE && + (by-y+TILESIZE)*(ox-bx) >= (by-oy)*(x-bx-TILESIZE))) { + tx = cx; + ty = cy; + } + } + } + } + + if (tx >= 0 && tx < w && ty >= 0 && ty < w) { + if (button == LEFT_BUTTON) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil == 0) { + ui->hshow = 0; + } else { + ui->hx = tx; + ui->hy = ty; + ui->hshow = !state->clues->immutable[ty*w+tx]; + ui->hpencil = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + if (button == RIGHT_BUTTON) { + /* + * Pencil-mode highlighting for non filled squares. + */ + if (state->grid[ty*w+tx] == 0) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil) { + ui->hshow = 0; + } else { + ui->hpencil = 1; + ui->hx = tx; + ui->hy = ty; + ui->hshow = 1; + } + } else { + ui->hshow = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + } else if (button == LEFT_BUTTON) { + if (is_clue(state, tx, ty)) { + sprintf(buf, "%c%d,%d", 'D', tx, ty); + return dupstr(buf); + } + } + if (IS_CURSOR_MOVE(button)) { + if (shift_or_control) { + int x = ui->hx, y = ui->hy; + switch (button) { + case CURSOR_LEFT: x = -1; break; + case CURSOR_RIGHT: x = w; break; + case CURSOR_UP: y = -1; break; + case CURSOR_DOWN: y = w; break; + } + if (is_clue(state, x, y)) { + sprintf(buf, "%c%d,%d", 'D', x, y); + return dupstr(buf); + } + return NULL; + } + move_cursor(button, &ui->hx, &ui->hy, w, w, 0); + ui->hshow = ui->hcursor = 1; + return ""; + } + if (ui->hshow && + (button == CURSOR_SELECT)) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + if (ui->hshow && + ((button >= '0' && button <= '9' && button - '0' <= w) || + button == CURSOR_SELECT2 || button == '\b')) { + int n = button - '0'; + if (button == CURSOR_SELECT2 || button == '\b') + n = 0; + + /* + * Can't make pencil marks in a filled square. This can only + * become highlighted if we're using cursor keys. + */ + if (ui->hpencil && state->grid[ui->hy*w+ui->hx]) + return NULL; + + /* + * Can't do anything to an immutable square. + */ + if (state->clues->immutable[ui->hy*w+ui->hx]) + return NULL; + + sprintf(buf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); + + if (!ui->hcursor) ui->hshow = 0; + + return dupstr(buf); + } + + if (button == 'M' || button == 'm') + return dupstr("M"); + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int w = from->par.w, a = w*w; + game_state *ret = dup_game(from); + int x, y, i, n; + + if (move[0] == 'S') { + ret->completed = ret->cheated = TRUE; + + for (i = 0; i < a; i++) { + if (move[i+1] < '1' || move[i+1] > '0'+w) + goto badmove; + ret->grid[i] = move[i+1] - '0'; + ret->pencil[i] = 0; + } + + if (move[a+1] != '\0') + goto badmove; + + return ret; + } else if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) { + if (from->clues->immutable[y*w+x]) + goto badmove; + + if (move[0] == 'P' && n > 0) { + ret->pencil[y*w+x] ^= 1L << n; + } else { + ret->grid[y*w+x] = n; + ret->pencil[y*w+x] = 0; + + if (!ret->completed && !check_errors(ret, NULL)) + ret->completed = TRUE; + } + return ret; + } else if (move[0] == 'M') { + /* + * Fill in absolutely all pencil marks everywhere. (I + * wouldn't use this for actual play, but it's a handy + * starting point when following through a set of + * diagnostics output by the standalone solver.) + */ + for (i = 0; i < a; i++) { + if (!ret->grid[i]) + ret->pencil[i] = (1L << (w+1)) - (1L << 1); + } + return ret; + } else if (move[0] == 'D' && sscanf(move+1, "%d,%d", &x, &y) == 2 && + is_clue(from, x, y)) { + int index = clue_index(from, x, y); + ret->clues_done[index] = !ret->clues_done[index]; + return ret; + } + + badmove: + /* couldn't parse move string */ + free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define SIZE(w) ((w) * TILESIZE + 2*BORDER) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = *y = SIZE(params->w); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_USER * 3 + 0] = 0.0F; + ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_USER * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F; + ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F; + ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->par.w /*, a = w*w */; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->three_d = !getenv("TOWERS_2D"); + ds->started = FALSE; + ds->tiles = snewn((w+2)*(w+2), long); + ds->drawn = snewn((w+2)*(w+2)*4, long); + for (i = 0; i < (w+2)*(w+2)*4; i++) + ds->drawn[i] = -1; + ds->errtmp = snewn((w+2)*(w+2), int); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->errtmp); + sfree(ds->tiles); + sfree(ds->drawn); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, + int x, int y, long tile) +{ + int w = clues->w /* , a = w*w */; + int tx, ty, bg; + char str[64]; + + tx = COORD(x); + ty = COORD(y); + + bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND; + + /* draw tower */ + if (ds->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) { + int coords[8]; + int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w); + int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w); + + /* left face of tower */ + coords[0] = tx; + coords[1] = ty - 1; + coords[2] = tx; + coords[3] = ty + TILESIZE - 1; + coords[4] = coords[2] + xoff; + coords[5] = coords[3] - yoff; + coords[6] = coords[0] + xoff; + coords[7] = coords[1] - yoff; + draw_polygon(dr, coords, 4, bg, COL_GRID); + + /* bottom face of tower */ + coords[0] = tx + TILESIZE; + coords[1] = ty + TILESIZE - 1; + coords[2] = tx; + coords[3] = ty + TILESIZE - 1; + coords[4] = coords[2] + xoff; + coords[5] = coords[3] - yoff; + coords[6] = coords[0] + xoff; + coords[7] = coords[1] - yoff; + draw_polygon(dr, coords, 4, bg, COL_GRID); + + /* now offset all subsequent drawing to the top of the tower */ + tx += xoff; + ty -= yoff; + } + + /* erase background */ + draw_rect(dr, tx, ty, TILESIZE, TILESIZE, bg); + + /* pencil-mode highlight */ + if (tile & DF_HIGHLIGHT_PENCIL) { + int coords[6]; + coords[0] = tx; + coords[1] = ty; + coords[2] = tx+TILESIZE/2; + coords[3] = ty; + coords[4] = tx; + coords[5] = ty+TILESIZE/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + /* draw box outline */ + if (tile & DF_PLAYAREA) { + int coords[8]; + coords[0] = tx; + coords[1] = ty - 1; + coords[2] = tx + TILESIZE; + coords[3] = ty - 1; + coords[4] = tx + TILESIZE; + coords[5] = ty + TILESIZE - 1; + coords[6] = tx; + coords[7] = ty + TILESIZE - 1; + draw_polygon(dr, coords, 4, -1, COL_GRID); + } + + /* new number needs drawing? */ + if (tile & DF_DIGIT_MASK) { + int color; + + str[1] = '\0'; + str[0] = (tile & DF_DIGIT_MASK) + '0'; + + if (tile & DF_ERROR) + color = COL_ERROR; + else if (tile & DF_CLUE_DONE) + color = COL_DONE; + else if (x < 0 || y < 0 || x >= w || y >= w) + color = COL_GRID; + else if (tile & DF_IMMUTABLE) + color = COL_GRID; + else + color = COL_USER; + + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, FONT_VARIABLE, + (tile & DF_PLAYAREA ? TILESIZE/2 : TILESIZE*2/5), + ALIGN_VCENTRE | ALIGN_HCENTRE, color, str); + } else { + int i, j, npencil; + int pl, pr, pt, pb; + float bestsize; + int pw, ph, minph, pbest, fontsize; + + /* Count the pencil marks required. */ + for (i = 1, npencil = 0; i <= w; i++) + if (tile & (1L << (i + DF_PENCIL_SHIFT))) + npencil++; + if (npencil) { + + minph = 2; + + /* + * Determine the bounding rectangle within which we're going + * to put the pencil marks. + */ + /* Start with the whole square, minus space for impinging towers */ + pl = tx + (ds->three_d ? X_3D_DISP(w,w) : 0); + pr = tx + TILESIZE; + pt = ty; + pb = ty + TILESIZE - (ds->three_d ? Y_3D_DISP(w,w) : 0); + + /* + * We arrange our pencil marks in a grid layout, with + * the number of rows and columns adjusted to allow the + * maximum font size. + * + * So now we work out what the grid size ought to be. + */ + bestsize = 0.0; + pbest = 0; + /* Minimum */ + for (pw = 3; pw < max(npencil,4); pw++) { + float fw, fh, fs; + + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + fw = (pr - pl) / (float)pw; + fh = (pb - pt) / (float)ph; + fs = min(fw, fh); + if (fs > bestsize) { + bestsize = fs; + pbest = pw; + } + } + assert(pbest > 0); + pw = pbest; + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + + /* + * Now we've got our grid dimensions, work out the pixel + * size of a grid element, and round it to the nearest + * pixel. (We don't want rounding errors to make the + * grid look uneven at low pixel sizes.) + */ + fontsize = min((pr - pl) / pw, (pb - pt) / ph); + + /* + * Centre the resulting figure in the square. + */ + pl = pl + (pr - pl - fontsize * pw) / 2; + pt = pt + (pb - pt - fontsize * ph) / 2; + + /* + * Now actually draw the pencil marks. + */ + for (i = 1, j = 0; i <= w; i++) + if (tile & (1L << (i + DF_PENCIL_SHIFT))) { + int dx = j % pw, dy = j / pw; + + str[1] = '\0'; + str[0] = i + '0'; + draw_text(dr, pl + fontsize * (2*dx+1) / 2, + pt + fontsize * (2*dy+1) / 2, + FONT_VARIABLE, fontsize, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } + } + } +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->par.w /*, a = w*w */; + int i, x, y; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all + * games should start by drawing a big background-colour + * rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND); + + draw_update(dr, 0, 0, SIZE(w), SIZE(w)); + + ds->started = TRUE; + } + + check_errors(state, ds->errtmp); + + /* + * Work out what data each tile should contain. + */ + for (i = 0; i < (w+2)*(w+2); i++) + ds->tiles[i] = 0; /* completely blank square */ + /* The clue squares... */ + for (i = 0; i < 4*w; i++) { + long tile = state->clues->clues[i]; + + CLUEPOS(x, y, i, w); + + if (ds->errtmp[(y+1)*(w+2)+(x+1)]) + tile |= DF_ERROR; + else if (state->clues_done[i]) + tile |= DF_CLUE_DONE; + + ds->tiles[(y+1)*(w+2)+(x+1)] = tile; + } + /* ... and the main grid. */ + for (y = 0; y < w; y++) { + for (x = 0; x < w; x++) { + long tile = DF_PLAYAREA; + + if (state->grid[y*w+x]) + tile |= state->grid[y*w+x]; + else + tile |= (long)state->pencil[y*w+x] << DF_PENCIL_SHIFT; + + if (ui->hshow && ui->hx == x && ui->hy == y) + tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT); + + if (state->clues->immutable[y*w+x]) + tile |= DF_IMMUTABLE; + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + tile |= DF_HIGHLIGHT; /* completion flash */ + + if (ds->errtmp[(y+1)*(w+2)+(x+1)]) + tile |= DF_ERROR; + + ds->tiles[(y+1)*(w+2)+(x+1)] = tile; + } + } + + /* + * Now actually draw anything that needs to be changed. + */ + for (y = 0; y < w+2; y++) { + for (x = 0; x < w+2; x++) { + long tl, tr, bl, br; + int i = y*(w+2)+x; + + tr = ds->tiles[y*(w+2)+x]; + tl = (x == 0 ? 0 : ds->tiles[y*(w+2)+(x-1)]); + br = (y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+x]); + bl = (x == 0 || y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+(x-1)]); + + if (ds->drawn[i*4] != tl || ds->drawn[i*4+1] != tr || + ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) { + clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); + + draw_tile(dr, ds, state->clues, x-1, y-1, tr); + if (x > 0) + draw_tile(dr, ds, state->clues, x-2, y-1, tl); + if (y <= w) + draw_tile(dr, ds, state->clues, x-1, y, br); + if (x > 0 && y <= w) + draw_tile(dr, ds, state->clues, x-2, y, bl); + + unclip(dr); + draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); + + ds->drawn[i*4] = tl; + ds->drawn[i*4+1] = tr; + ds->drawn[i*4+2] = bl; + ds->drawn[i*4+3] = br; + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + if (state->completed) + return FALSE; + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * We use 9mm squares by default, like Solo. + */ + game_compute_size(params, 900, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->par.w; + int ink = print_mono_colour(dr, 0); + int i, x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, 3 * TILESIZE / 40); + draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, w*TILESIZE, ink); + + /* + * Main grid. + */ + for (x = 1; x < w; x++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER+x*TILESIZE, BORDER, + BORDER+x*TILESIZE, BORDER+w*TILESIZE, ink); + } + for (y = 1; y < w; y++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER, BORDER+y*TILESIZE, + BORDER+w*TILESIZE, BORDER+y*TILESIZE, ink); + } + + /* + * Clues. + */ + for (i = 0; i < 4*w; i++) { + char str[128]; + + if (!state->clues->clues[i]) + continue; + + CLUEPOS(x, y, i, w); + + sprintf (str, "%d", state->clues->clues[i]); + + draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2, + BORDER + y*TILESIZE + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } + + /* + * Numbers for the solution, if any. + */ + for (y = 0; y < w; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x]) { + char str[2]; + str[1] = '\0'; + str[0] = state->grid[y*w+x] + '0'; + draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2, + BORDER + y*TILESIZE + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } +} + +#ifdef COMBINED +#define thegame towers +#endif + +const struct game thegame = { + "Towers", "games.towers", "towers", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int grade = FALSE; + int ret, diff, really_show_working = FALSE; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_show_working = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + /* + * When solving an Easy puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + solver_show_working = FALSE; + for (diff = 0; diff < DIFFCOUNT; diff++) { + memcpy(s->grid, s->clues->immutable, p->w * p->w); + ret = solver(p->w, s->clues->clues, s->grid, diff); + if (ret <= diff) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == diff_impossible) + printf("Difficulty rating: impossible (no solution exists)\n"); + else + printf("Difficulty rating: %s\n", towers_diffnames[ret]); + } else { + solver_show_working = really_show_working; + memcpy(s->grid, s->clues->immutable, p->w * p->w); + ret = solver(p->w, s->clues->clues, s->grid, diff); + if (ret != diff) + printf("Puzzle is inconsistent\n"); + else + fputs(game_text_format(s), stdout); + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/tracks.R b/apps/plugins/puzzles/tracks.R new file mode 100644 index 0000000000..f88dfb03eb --- /dev/null +++ b/apps/plugins/puzzles/tracks.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +TRACKS_EXTRA = dsf findloop + +tracks : [X] GTK COMMON tracks TRACKS_EXTRA tracks-icon|no-icon + +tracks : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res + +ALL += tracks[COMBINED] TRACKS_EXTRA + +!begin am gtk +GAMES += tracks +!end + +!begin >list.c + A(tracks) \ +!end + +!begin >gamedesc.txt +tracks:tracks.exe:Tracks:Path-finding railway track puzzle:Fill in the railway track according to the clues. +!end diff --git a/apps/plugins/puzzles/tracks.c b/apps/plugins/puzzles/tracks.c new file mode 100644 index 0000000000..dd00e32b9a --- /dev/null +++ b/apps/plugins/puzzles/tracks.c @@ -0,0 +1,2660 @@ +/* + * Implementation of 'Train Tracks', a puzzle from the Times on Saturday. + * + * "Lay tracks to enable the train to travel from village A to village B. + * The numbers indicate how many sections of rail go in each row and + * column. There are only straight rails and curved rails. The track + * cannot cross itself." + * + * Puzzles: + * #9 8x8:d9s5c6zgAa,1,4,1,4,4,3,S3,5,2,2,4,S5,3,3,5,1 + * #112 8x8:w6x5mAa,1,3,1,4,6,4,S4,3,3,4,5,2,4,2,S5,1 + * #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1 + * #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1 + * #115 8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1 + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +/* --- Game parameters --- */ + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(TRICKY,Tricky,t) + +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const tracks_diffnames[] = { DIFFLIST(TITLE) }; +static char const tracks_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +struct game_params { + int w, h, diff, single_ones; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 8; + ret->diff = DIFF_TRICKY; + ret->single_ones = TRUE; + + return ret; +} + +static const struct game_params tracks_presets[] = { + {8, 8, DIFF_EASY, 1}, + {8, 8, DIFF_TRICKY, 1}, + {10, 8, DIFF_EASY, 1}, + {10, 8, DIFF_TRICKY, 1 }, + {10, 10, DIFF_EASY, 1}, + {10, 10, DIFF_TRICKY, 1}, + {15, 10, DIFF_EASY, 1}, + {15, 10, DIFF_TRICKY, 1}, + {15, 15, DIFF_EASY, 1}, + {15, 15, DIFF_TRICKY, 1}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(tracks_presets)) + return FALSE; + + ret = snew(game_params); + *ret = tracks_presets[i]; + + sprintf(str, "%dx%d %s", ret->w, ret->h, tracks_diffnames[ret->diff]); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'd') { + int i; + string++; + params->diff = DIFF_TRICKY; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == tracks_diffchars[i]) + params->diff = i; + if (*string) string++; + } + params->single_ones = TRUE; + if (*string == 'o') { + params->single_ones = FALSE; + string++; + } + +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[120]; + + sprintf(buf, "%dx%d", params->w, params->h); + if (full) + sprintf(buf + strlen(buf), "d%c%s", + tracks_diffchars[params->diff], + params->single_ones ? "" : "o"); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = "Disallow consecutive 1 clues"; + ret[3].type = C_BOOLEAN; + ret[3].ival = params->single_ones; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + ret->single_ones = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + /* + * Generating anything under 4x4 runs into trouble of one kind + * or another. + */ + if (params->w < 4 || params->h < 4) + return "Width and height must both be at least four"; + return NULL; +} + +/* --- Game state --- */ + +/* flag usage copied from pearl */ + +#define R 1 +#define U 2 +#define L 4 +#define D 8 + +#define MOVECHAR(m) ((m==R)?'R':(m==U)?'U':(m==L)?'L':(m==D)?'D':'?') + +#define DX(d) ( ((d)==R) - ((d)==L) ) +#define DY(d) ( ((d)==D) - ((d)==U) ) + +#define F(d) (((d << 2) | (d >> 2)) & 0xF) +#define C(d) (((d << 3) | (d >> 1)) & 0xF) +#define A(d) (((d << 1) | (d >> 3)) & 0xF) + +#define LR (L | R) +#define RL (R | L) +#define UD (U | D) +#define DU (D | U) +#define LU (L | U) +#define UL (U | L) +#define LD (L | D) +#define DL (D | L) +#define RU (R | U) +#define UR (U | R) +#define RD (R | D) +#define DR (D | R) +#define ALLDIR 15 +#define BLANK 0 +#define UNKNOWN 15 + +int nbits[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + +/* square grid flags */ +#define S_TRACK 1 /* a track passes through this square (--> 2 edges) */ +#define S_NOTRACK 2 /* no track passes through this square */ +#define S_ERROR 4 +#define S_CLUE 8 +#define S_MARK 16 + +#define S_TRACK_SHIFT 16 /* U/D/L/R flags for edge track indicators */ +#define S_NOTRACK_SHIFT 20 /* U/D/L/R flags for edge no-track indicators */ + +/* edge grid flags */ +#define E_TRACK 1 /* a track passes through this edge */ +#define E_NOTRACK 2 /* no track passes through this edge */ + +struct numbers { + int refcount; + int *numbers; /* sz w+h */ + int row_s, col_s; /* stations: TODO think about multiple lines + (for bigger grids)? */ +}; + +#define INGRID(state, gx, gy) ((gx) >= 0 && (gx) < (state)->p.w && \ + (gy) >= 0 && (gy) < (state)->p.h) + +struct game_state { + game_params p; + unsigned int *sflags; /* size w*h */ + struct numbers *numbers; + int *num_errors; /* size w+h */ + int completed, used_solve, impossible; +}; + +/* Return the four directions in which a particular edge flag is set, around a square. */ +int S_E_DIRS(const game_state *state, int sx, int sy, unsigned int eflag) { + return (state->sflags[sy*state->p.w+sx] >> + ((eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT)) & ALLDIR; +} + +/* Count the number of a particular edge flag around a grid square. */ +int S_E_COUNT(const game_state *state, int sx, int sy, unsigned int eflag) { + return nbits[S_E_DIRS(state, sx, sy, eflag)]; +} + +/* Return the two flags (E_TRACK and/or E_NOTRACK) set on a specific + * edge of a square. */ +unsigned S_E_FLAGS(const game_state *state, int sx, int sy, int d) { + unsigned f = state->sflags[sy*state->p.w+sx]; + int t = (f & (d << S_TRACK_SHIFT)), nt = (f & (d << S_NOTRACK_SHIFT)); + return (t ? E_TRACK : 0) | (nt ? E_NOTRACK : 0); +} + +int S_E_ADJ(const game_state *state, int sx, int sy, int d, int *ax, int *ay, unsigned int *ad) { + if (d == L && sx > 0) { *ax = sx-1; *ay = sy; *ad = R; return 1; } + if (d == R && sx < state->p.w-1) { *ax = sx+1; *ay = sy; *ad = L; return 1; } + if (d == U && sy > 0) { *ax = sx; *ay = sy-1; *ad = D; return 1; } + if (d == D && sy < state->p.h-1) { *ax = sx; *ay = sy+1; *ad = U; return 1; } + + return 0; +} + +/* Sets flag (E_TRACK or E_NOTRACK) on a given edge of a square. */ +void S_E_SET(game_state *state, int sx, int sy, int d, unsigned int eflag) { + unsigned shift = (eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT, ad; + int ax, ay; + + state->sflags[sy*state->p.w+sx] |= (d << shift); + + if (S_E_ADJ(state, sx, sy, d, &ax, &ay, &ad)) { + state->sflags[ay*state->p.w+ax] |= (ad << shift); + } +} + +/* Clears flag (E_TRACK or E_NOTRACK) on a given edge of a square. */ +void S_E_CLEAR(game_state *state, int sx, int sy, int d, unsigned int eflag) { + unsigned shift = (eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT, ad; + int ax, ay; + + state->sflags[sy*state->p.w+sx] &= ~(d << shift); + + if (S_E_ADJ(state, sx, sy, d, &ax, &ay, &ad)) { + state->sflags[ay*state->p.w+ax] &= ~(ad << shift); + } +} + +static void clear_game(game_state *state) +{ + int w = state->p.w, h = state->p.h; + + memset(state->sflags, 0, w*h * sizeof(unsigned int)); + + memset(state->numbers->numbers, 0, (w+h) * sizeof(int)); + state->numbers->col_s = state->numbers->row_s = -1; + + memset(state->num_errors, 0, (w+h) * sizeof(int)); + + state->completed = state->used_solve = state->impossible = FALSE; +} + +static game_state *blank_game(const game_params *params) +{ + game_state *state = snew(game_state); + int w = params->w, h = params->h; + + state->p = *params; + + state->sflags = snewn(w*h, unsigned int); + + state->numbers = snew(struct numbers); + state->numbers->refcount = 1; + state->numbers->numbers = snewn(w+h, int); + + state->num_errors = snewn(w+h, int); + + clear_game(state); + + return state; +} + +static void copy_game_flags(const game_state *src, game_state *dest) +{ + int w = src->p.w, h = src->p.h; + + memcpy(dest->sflags, src->sflags, w*h*sizeof(unsigned int)); +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->p.w, h = state->p.h; + game_state *ret = snew(game_state); + + ret->p = state->p; /* structure copy */ + + ret->sflags = snewn(w*h, unsigned int); + copy_game_flags(state, ret); + + ret->numbers = state->numbers; + state->numbers->refcount++; + ret->num_errors = snewn(w+h, int); + memcpy(ret->num_errors, state->num_errors, (w+h)*sizeof(int)); + + ret->completed = state->completed; + ret->used_solve = state->used_solve; + ret->impossible = state->impossible; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->numbers->refcount <= 0) { + sfree(state->numbers->numbers); + sfree(state->numbers); + } + sfree(state->num_errors); + sfree(state->sflags); + sfree(state); +} + +#define NDIRS 4 +const unsigned int dirs_const[] = { U, D, L, R }; + +static unsigned int find_direction(game_state *state, random_state *rs, + int x, int y) +{ + int i, nx, ny, w=state->p.w, h=state->p.h; + unsigned int dirs[NDIRS]; + + memcpy(dirs, dirs_const, sizeof(dirs)); + shuffle(dirs, NDIRS, sizeof(*dirs), rs); + for (i = 0; i < NDIRS; i++) { + nx = x + DX(dirs[i]); + ny = y + DY(dirs[i]); + if (nx >= 0 && nx < w && ny == h) { + /* off the bottom of the board: we've finished the path. */ + return dirs[i]; + } else if (!INGRID(state, nx, ny)) { + /* off the board: can't move here */ + continue; + } else if (S_E_COUNT(state, nx, ny, E_TRACK) > 0) { + /* already tracks here: can't move */ + continue; + } + return dirs[i]; + } + return 0; /* no possible directions left. */ +} + +static int check_completion(game_state *state, int mark); + +static void lay_path(game_state *state, random_state *rs) +{ + int px, py, w=state->p.w, h=state->p.h; + unsigned int d; + +start: + clear_game(state); + + /* pick a random entry point, lay its left edge */ + state->numbers->row_s = py = random_upto(rs, h); + px = 0; + S_E_SET(state, px, py, L, E_TRACK); + + while (INGRID(state, px, py)) { + d = find_direction(state, rs, px, py); + if (d == 0) + goto start; /* nowhere else to go, restart */ + + S_E_SET(state, px, py, d, E_TRACK); + px += DX(d); + py += DY(d); + } + /* double-check we got to the right place */ + assert(px >= 0 && px < w && py == h); + + state->numbers->col_s = px; +} + +static int tracks_solve(game_state *state, int diff); +static void debug_state(game_state *state, const char *what); + +/* Clue-setting algorithm: + + - first lay clues randomly until it's soluble + - then remove clues randomly if removing them doesn't affect solubility + + - We start with two clues, one at each path entrance. + + More details: + - start with an array of all square i positions + - if the grid is already soluble by a level easier than we've requested, + go back and make a new grid + - if the grid is already soluble by our requested difficulty level, skip + the clue-laying step + - count the number of flags the solver managed to place, remember this. + + - to lay clues: + - shuffle the i positions + - for each possible clue position: + - copy the solved board, strip it + - take the next position, add a clue there on the copy + - try and solve the copy + - if it's soluble by a level easier than we've requested, continue (on + to next clue position: putting a clue here makes it too easy) + - if it's soluble by our difficulty level, we're done: + - put the clue flag into the solved board + - go to strip-clues. + - if the solver didn't manage to place any more flags, continue (on to next + clue position: putting a clue here didn't help he solver) + - otherwise put the clue flag in the original board, and go on to the next + clue position + - if we get here and we've not solved it yet, we never will (did we really + fill _all_ the clues in?!). Go back and make a new grid. + + - to strip clues: + - shuffle the i positions + - for each possible clue position: + - if the solved grid doesn't have a clue here, skip + - copy the solved board, remove this clue, strip it + - try and solve the copy + - assert that it is not soluble by a level easier than we've requested + - (because this should never happen) + - if this is (still) soluble by our difficulty level: + - remove this clue from the solved board, it's redundant (with the other + clues) + + - that should be it. +*/ + +static game_state *copy_and_strip(const game_state *state, game_state *ret, int flipcluei) +{ + int i, j, w = state->p.w, h = state->p.h; + + copy_game_flags(state, ret); + + /* Add/remove a clue before stripping, if required */ + + if (flipcluei != -1) + ret->sflags[flipcluei] ^= S_CLUE; + + /* All squares that are not clue squares have square track info erased, and some edge flags.. */ + + for (i = 0; i < w*h; i++) { + if (!(ret->sflags[i] & S_CLUE)) { + ret->sflags[i] &= ~(S_TRACK|S_NOTRACK|S_ERROR|S_MARK); + for (j = 0; j < 4; j++) { + unsigned f = 1<sflags[yy*w+xx] & S_CLUE)) { + /* only erase an edge flag if neither side of the edge is S_CLUE. */ + S_E_CLEAR(ret, i%w, i/w, f, E_TRACK); + S_E_CLEAR(ret, i%w, i/w, f, E_NOTRACK); + } + } + } + } + return ret; +} + +static int solve_progress(const game_state *state) { + int i, w = state->p.w, h = state->p.h, progress = 0; + + /* Work out how many flags the solver managed to set (either TRACK + or NOTRACK) and return this as a progress measure, to check whether + a partially-solved board gets any further than a previous partially- + solved board. */ + + for (i = 0; i < w*h; i++) { + if (state->sflags[i] & S_TRACK) progress++; + if (state->sflags[i] & S_NOTRACK) progress++; + progress += S_E_COUNT(state, i%w, i/w, E_TRACK); + progress += S_E_COUNT(state, i%w, i/w, E_NOTRACK); + } + return progress; +} + +static int check_phantom_moves(const game_state *state) { + int x, y, i; + + /* Check that this state won't show 'phantom moves' at the start of the + * game: squares which have multiple edge flags set but no clue flag + * cause a piece of track to appear that isn't on a clue square. */ + + for (x = 0; x < state->p.w; x++) { + for (y = 0; y < state->p.h; y++) { + i = y*state->p.w+x; + if (state->sflags[i] & S_CLUE) + continue; + if (S_E_COUNT(state, x, y, E_TRACK) > 1) + return 1; /* found one! */ + } + } + return 0; +} + +static int add_clues(game_state *state, random_state *rs, int diff) +{ + int i, j, pi, w = state->p.w, h = state->p.h, progress, ret = 0, sr; + int *positions = snewn(w*h, int), npositions = 0; + int *nedges_previous_solve = snewn(w*h, int); + game_state *scratch = dup_game(state); + + debug_state(state, "gen: Initial board"); + + debug(("gen: Adding clues...")); + + /* set up the shuffly-position grid for later, used for adding clues: + * we only bother adding clues where any edges are set. */ + for (i = 0; i < w*h; i++) { + if (S_E_DIRS(state, i%w, i/w, E_TRACK) != 0) { + positions[npositions++] = i; + } + nedges_previous_solve[i] = 0; + } + + /* First, check whether the puzzle is already either too easy, or just right */ + scratch = copy_and_strip(state, scratch, -1); + if (diff > 0) { + sr = tracks_solve(scratch, diff-1); + if (sr < 0) + assert(!"Generator should not have created impossible puzzle"); + if (sr > 0) { + ret = -1; /* already too easy, even without adding clues. */ + debug(("gen: ...already too easy, need new board.")); + goto done; + } + } + sr = tracks_solve(scratch, diff); + if (sr < 0) + assert(!"Generator should not have created impossible puzzle"); + if (sr > 0) { + ret = 1; /* already soluble without any extra clues. */ + debug(("gen: ...soluble without clues, nothing to do.")); + goto done; + } + debug_state(scratch, "gen: Initial part-solved state: "); + progress = solve_progress(scratch); + debug(("gen: Initial solve progress is %d", progress)); + + /* First, lay clues until we're soluble. */ + shuffle(positions, npositions, sizeof(int), rs); + for (pi = 0; pi < npositions; pi++) { + i = positions[pi]; /* pick a random position */ + if (state->sflags[i] & S_CLUE) + continue; /* already a clue here (entrance location?) */ + if (nedges_previous_solve[i] == 2) + continue; /* no point putting a clue here, we could solve both edges + with the previous set of clues */ + + /* set a clue in that position (on a copy of the board) and test solubility */ + scratch = copy_and_strip(state, scratch, i); + + if (check_phantom_moves(scratch)) + continue; /* adding a clue here would add phantom track */ + + if (diff > 0) { + if (tracks_solve(scratch, diff-1) > 0) { + continue; /* adding a clue here makes it too easy */ + } + } + if (tracks_solve(scratch, diff) > 0) { + /* we're now soluble (and we weren't before): add this clue, and then + start stripping clues */ + debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w)); + state->sflags[i] |= S_CLUE; + goto strip_clues; + } + if (solve_progress(scratch) > progress) { + /* We've made more progress solving: add this clue, then. */ + progress = solve_progress(scratch); + debug(("gen: ... adding clue at (%d,%d), new progress %d", i%w, i/w, progress)); + state->sflags[i] |= S_CLUE; + + for (j = 0; j < w*h; j++) + nedges_previous_solve[j] = S_E_COUNT(scratch, j%w, j/w, E_TRACK); + } + } + /* If we got here we didn't ever manage to make the puzzle soluble + (without making it too easily soluble, that is): give up. */ + + debug(("gen: Unable to make soluble with clues, need new board.")); + ret = -1; + goto done; + +strip_clues: + debug(("gen: Stripping clues.")); + + /* Now, strip redundant clues (i.e. those without which the puzzle is still + soluble) */ + shuffle(positions, npositions, sizeof(int), rs); + for (pi = 0; pi < npositions; pi++) { + i = positions[pi]; /* pick a random position */ + if (!(state->sflags[i] & S_CLUE)) + continue; /* no clue here to strip */ + if ((i%w == 0 && i/w == state->numbers->row_s) || + (i/w == (h-1) && i%w == state->numbers->col_s)) + continue; /* don't strip clues at entrance/exit */ + + scratch = copy_and_strip(state, scratch, i); + if (check_phantom_moves(scratch)) + continue; /* removing a clue here would add phantom track */ + + if (tracks_solve(scratch, diff) > 0) { + debug(("gen: ... removing clue at (%d,%d), still soluble without it", i%w, i/w)); + state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */ + } + } + debug(("gen: Finished stripping clues.")); + ret = 1; + +done: + sfree(positions); + free_game(scratch); + return ret; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int i, j, w = params->w, h = params->h, x, y, ret; + game_state *state; + char *desc, *p; + game_params adjusted_params; + + /* + * 4x4 Tricky cannot be generated, so fall back to Easy. + */ + if (w == 4 && h == 4 && params->diff > DIFF_EASY) { + adjusted_params = *params; /* structure copy */ + adjusted_params.diff = DIFF_EASY; + params = &adjusted_params; + } + + state = blank_game(params); + + /* --- lay the random path */ + +newpath: + lay_path(state, rs); + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + if (S_E_COUNT(state, x, y, E_TRACK) > 0) { + state->sflags[y*w + x] |= S_TRACK; + } + if ((x == 0 && y == state->numbers->row_s) || + (y == (h-1) && x == state->numbers->col_s)) { + state->sflags[y*w + x] |= S_CLUE; + } + } + } + + /* --- Update the clue numbers based on the tracks we have generated. */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + if (state->sflags[y*w + x] & S_TRACK) { + state->numbers->numbers[x]++; + state->numbers->numbers[y+w]++; + } + } + } + for (i = 0; i < w+h; i++) { + if (state->numbers->numbers[i] == 0) + goto newpath; /* too boring */ + } + + if (params->single_ones) { + int last_was_one = 1, is_one; /* (disallow 1 clue at entry point) */ + for (i = 0; i < w+h; i++) { + is_one = (state->numbers->numbers[i] == 1); + if (is_one && last_was_one) + goto newpath; /* disallow consecutive 1 clues. */ + last_was_one = is_one; + } + if (state->numbers->numbers[w+h-1] == 1) + goto newpath; /* (disallow 1 clue at exit point) */ + } + + /* --- Add clues to make a soluble puzzle */ + ret = add_clues(state, rs, params->diff); + if (ret != 1) goto newpath; /* couldn't make it soluble, or too easy */ + + /* --- Generate the game desc based on the generated grid. */ + desc = snewn(w*h*3 + (w+h)*5, char); + for (i = j = 0; i < w*h; i++) { + if (!(state->sflags[i] & S_CLUE) && j > 0 && + desc[j-1] >= 'a' && desc[j-1] < 'z') + desc[j-1]++; + else if (!(state->sflags[i] & S_CLUE)) + desc[j++] = 'a'; + else { + unsigned int f = S_E_DIRS(state, i%w, i/w, E_TRACK); + desc[j++] = (f < 10) ? ('0' + f) : ('A' + (f-10)); + } + } + + p = desc + j; + for (x = 0; x < w; x++) { + p += sprintf(p, ",%s%d", x == state->numbers->col_s ? "S" : "", + state->numbers->numbers[x]); + } + for (y = 0; y < h; y++) { + p += sprintf(p, ",%s%d", y == state->numbers->row_s ? "S" : "", + state->numbers->numbers[y+w]); + } + *p++ = '\0'; + + ret = tracks_solve(state, DIFFCOUNT); + assert(ret >= 0); + free_game(state); + + debug(("new_game_desc: %s", desc)); + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int i = 0, w = params->w, h = params->h, in = 0, out = 0; + + while (*desc) { + unsigned int f = 0; + if (*desc >= '0' && *desc <= '9') + f = (*desc - '0'); + else if (*desc >= 'A' && *desc <= 'F') + f = (*desc - 'A' + 10); + else if (*desc >= 'a' && *desc <= 'z') + i += *desc - 'a'; + else + return "Game description contained unexpected characters"; + + if (f != 0) { + if (nbits[f] != 2) + return "Clue did not provide 2 direction flags"; + } + i++; + desc++; + if (i == w*h) break; + } + for (i = 0; i < w+h; i++) { + if (!*desc) + return "Not enough numbers given after grid specification"; + else if (*desc != ',') + return "Invalid character in number list"; + desc++; + if (*desc == 'S') { + if (i < w) + out++; + else + in++; + desc++; + } + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + if (in != 1 || out != 1) + return "Puzzle must have one entrance and one exit"; + if (*desc) + return "Unexpected additional character at end of game description"; + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, const char *desc) +{ + game_state *state = blank_game(params); + int w = params->w, h = params->h, i = 0; + + while (*desc) { + unsigned int f = 0; + if (*desc >= '0' && *desc <= '9') + f = (*desc - '0'); + else if (*desc >= 'A' && *desc <= 'F') + f = (*desc - 'A' + 10); + else if (*desc >= 'a' && *desc <= 'z') + i += *desc - 'a'; + + if (f != 0) { + int x = i % w, y = i / w; + assert(f < 16); + assert(nbits[f] == 2); + + state->sflags[i] |= (S_TRACK | S_CLUE); + if (f & U) S_E_SET(state, x, y, U, E_TRACK); + if (f & D) S_E_SET(state, x, y, D, E_TRACK); + if (f & L) S_E_SET(state, x, y, L, E_TRACK); + if (f & R) S_E_SET(state, x, y, R, E_TRACK); + } + i++; + desc++; + if (i == w*h) break; + } + for (i = 0; i < w+h; i++) { + assert(*desc == ','); + desc++; + + if (*desc == 'S') { + if (i < w) + state->numbers->col_s = i; + else + state->numbers->row_s = i-w; + desc++; + } + state->numbers->numbers[i] = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + + assert(!*desc); + + return state; +} + +static int solve_set_sflag(game_state *state, int x, int y, + unsigned int f, const char *why) +{ + int w = state->p.w, i = y*w + x; + + if (state->sflags[i] & f) + return 0; + debug(("solve: square (%d,%d) -> %s: %s", + x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); + if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) { + debug(("solve: opposite flag already set there, marking IMPOSSIBLE")); + state->impossible = TRUE; + } + state->sflags[i] |= f; + return 1; +} + +static int solve_set_eflag(game_state *state, int x, int y, int d, + unsigned int f, const char *why) +{ + int sf = S_E_FLAGS(state, x, y, d); + + if (sf & f) + return 0; + debug(("solve: edge (%d,%d)/%c -> %s: %s", x, y, + (d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R', + (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); + if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) { + debug(("solve: opposite flag already set there, marking IMPOSSIBLE")); + state->impossible = TRUE; + } + S_E_SET(state, x, y, d, f); + return 1; +} + +static int solve_update_flags(game_state *state) +{ + int x, y, i, w = state->p.w, h = state->p.h, did = 0; + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + /* If a square is NOTRACK, all four edges must be. */ + if (state->sflags[y*w + x] & S_NOTRACK) { + for (i = 0; i < 4; i++) { + unsigned int d = 1<= 3) { + did += solve_set_sflag(state, x, y, S_NOTRACK, "square has >2 NOTRACK edges"); + } + + /* If any edge around a square is TRACK, the square is. */ + if (S_E_COUNT(state, x, y, E_TRACK) > 0) { + did += solve_set_sflag(state, x, y, S_TRACK, "square has TRACK edge"); + } + + /* If a square is TRACK and 2 edges are NOTRACK, + the other two edges must be TRACK. */ + if ((state->sflags[y*w + x] & S_TRACK) && + (S_E_COUNT(state, x, y, E_NOTRACK) == 2) && + (S_E_COUNT(state, x, y, E_TRACK) < 2)) { + for (i = 0; i < 4; i++) { + unsigned int d = 1<sflags[y*w + x] & S_TRACK) && + (S_E_COUNT(state, x, y, E_TRACK) == 2) && + (S_E_COUNT(state, x, y, E_NOTRACK) < 2)) { + for (i = 0; i < 4; i++) { + unsigned int d = 1<p.h, w = state->p.w; + for (n = 0, i = col; n < h; n++, i += w) { + if (state->sflags[i] & f) c++; + } + return c; +} + +static int solve_count_row(game_state *state, int row, unsigned int f) +{ + int i, n, c = 0, w = state->p.w; + for (n = 0, i = w*row; n < state->p.w; n++, i++) { + if (state->sflags[i] & f) c++; + } + return c; +} + +static int solve_count_clues_sub(game_state *state, int si, int id, int n, + int target, const char *what) +{ + int ctrack = 0, cnotrack = 0, did = 0, j, i, w = state->p.w; + + for (j = 0, i = si; j < n; j++, i += id) { + if (state->sflags[i] & S_TRACK) + ctrack++; + if (state->sflags[i] & S_NOTRACK) + cnotrack++; + } + if (ctrack == target) { + /* everything that's not S_TRACK must be S_NOTRACK. */ + for (j = 0, i = si; j < n; j++, i += id) { + if (!(state->sflags[i] & S_TRACK)) + did += solve_set_sflag(state, i%w, i/w, S_NOTRACK, what); + } + } + if (cnotrack == (n-target)) { + /* everything that's not S_NOTRACK must be S_TRACK. */ + for (j = 0, i = si; j < n; j++, i += id) { + if (!(state->sflags[i] & S_NOTRACK)) + did += solve_set_sflag(state, i%w, i/w, S_TRACK, what); + } + } + return did; +} + +static int solve_count_clues(game_state *state) +{ + int w = state->p.w, h = state->p.h, x, y, target, did = 0; + + for (x = 0; x < w; x++) { + target = state->numbers->numbers[x]; + did += solve_count_clues_sub(state, x, w, h, target, "col count"); + } + for (y = 0; y < h; y++) { + target = state->numbers->numbers[w+y]; + did += solve_count_clues_sub(state, y*w, 1, w, target, "row count"); + } + return did; +} + +static int solve_check_single_sub(game_state *state, int si, int id, int n, + int target, unsigned int perpf, + const char *what) +{ + int ctrack = 0, nperp = 0, did = 0, j, i, w = state->p.w; + int n1edge = 0, i1edge = 0, ox, oy, x, y; + unsigned int impossible = 0; + + /* For rows or columns which only have one more square to put a track in, we + know the only way a new track section could be there would be to run + perpendicular to the track (otherwise we'd need at least two free squares). + So, if there is nowhere we can run perpendicular to the track (e.g. because + we're on an edge) we know the extra track section much be on one end of an + existing section. */ + + for (j = 0, i = si; j < n; j++, i += id) { + if (state->sflags[i] & S_TRACK) + ctrack++; + impossible = S_E_DIRS(state, i%w, i/w, E_NOTRACK); + if ((perpf & impossible) == 0) + nperp++; + if (S_E_COUNT(state, i%w, i/w, E_TRACK) <= 1) { + n1edge++; + i1edge = i; + } + } + if (ctrack != (target-1)) return 0; + if (nperp > 0 || n1edge != 1) return 0; + + debug(("check_single from (%d,%d): 1 match from (%d,%d)", + si%w, si/w, i1edge%w, i1edge/w)); + + /* We have a match: anything that's more than 1 away from this square + cannot now contain a track. */ + ox = i1edge%w; + oy = i1edge/w; + for (j = 0, i = si; j < n; j++, i += id) { + x = i%w; + y = i/w; + if (abs(ox-x) > 1 || abs(oy-y) > 1) { + if (!state->sflags[i] & S_TRACK) + did += solve_set_sflag(state, x, y, S_NOTRACK, what); + } + } + + return did; +} + +static int solve_check_single(game_state *state) +{ + int w = state->p.w, h = state->p.h, x, y, target, did = 0; + + for (x = 0; x < w; x++) { + target = state->numbers->numbers[x]; + did += solve_check_single_sub(state, x, w, h, target, R|L, "single on col"); + } + for (y = 0; y < h; y++) { + target = state->numbers->numbers[w+y]; + did += solve_check_single_sub(state, y*w, 1, w, target, U|D, "single on row"); + } + return did; +} + +static int solve_check_loose_sub(game_state *state, int si, int id, int n, + int target, unsigned int perpf, + const char *what) +{ + int nperp = 0, nloose = 0, e2count = 0, did = 0, i, j, k; + int w = state->p.w; + unsigned int parf = ALLDIR & (~perpf); + + for (j = 0, i = si; j < n; j++, i += id) { + int fcount = S_E_COUNT(state, i%w, i/w, E_TRACK); + if (fcount == 2) + e2count++; /* this cell has 2 definite edges */ + state->sflags[i] &= ~S_MARK; + if (fcount == 1 && (parf & S_E_DIRS(state, i%w, i/w, E_TRACK))) { + nloose++; /* this cell has a loose end (single flag set parallel + to the direction of this row/column) */ + state->sflags[i] |= S_MARK; /* mark loose ends */ + } + if (fcount != 2 && !(perpf & S_E_DIRS(state, i%w, i/w, E_NOTRACK))) + nperp++; /* we could lay perpendicular across this cell */ + } + + if (nloose > (target - e2count)) { + debug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE", + what, si%w, si/w, nloose, target-e2count)); + state->impossible = TRUE; + } + if (nloose > 0 && nloose == (target - e2count)) { + debug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.", + what, si%w, si/w, nloose)); + for (j = 0, i = si; j < n; j++, i += id) { + if (!(state->sflags[i] & S_MARK)) + continue; /* skip non-loose ends */ + if (j > 0 && state->sflags[i-id] & S_MARK) + continue; /* next to other loose end, could join up */ + if (j < (n-1) && state->sflags[i+id] & S_MARK) + continue; /* ditto */ + + for (k = 0; k < 4; k++) { + if ((parf & (1<sflags[i] & S_MARK)) + continue; /* skip non-loose ends */ + for (k = 0; k < 4; k++) { + if (parf & (1<p.w, h = state->p.h, x, y, target, did = 0; + + for (x = 0; x < w; x++) { + target = state->numbers->numbers[x]; + did += solve_check_loose_sub(state, x, w, h, target, R|L, "loose on col"); + } + for (y = 0; y < h; y++) { + target = state->numbers->numbers[w+y]; + did += solve_check_loose_sub(state, y*w, 1, w, target, U|D, "loose on row"); + } + return did; +} + +static int solve_check_loop_sub(game_state *state, int x, int y, int dir, + int *dsf, int startc, int endc) +{ + int w = state->p.w, h = state->p.h, i = y*w+x, j, k, satisfied = 1; + + j = (y+DY(dir))*w + (x+DX(dir)); + + assert(i < w*h && j < w*h); + + if ((state->sflags[i] & S_TRACK) && + (state->sflags[j] & S_TRACK) && + !(S_E_DIRS(state, x, y, E_TRACK) & dir) && + !(S_E_DIRS(state, x, y, E_NOTRACK) & dir)) { + int ic = dsf_canonify(dsf, i), jc = dsf_canonify(dsf, j); + if (ic == jc) { + return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop"); + } + if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) { + debug(("Adding link at (%d,%d) would join start to end", x, y)); + /* We mustn't join the start to the end if: + - there are other bits of track that aren't attached to either end + - the clues are not fully satisfied yet + */ + for (k = 0; k < w*h; k++) { + if (state->sflags[k] & S_TRACK && + dsf_canonify(dsf, k) != startc && dsf_canonify(dsf, k) != endc) { + return solve_set_eflag(state, x, y, dir, E_NOTRACK, + "joins start to end but misses tracks"); + } + } + for (k = 0; k < w; k++) { + int target = state->numbers->numbers[k]; + int ntracks = solve_count_col(state, k, S_TRACK); + if (ntracks < target) satisfied = 0; + } + for (k = 0; k < h; k++) { + int target = state->numbers->numbers[w+k]; + int ntracks = solve_count_row(state, k, S_TRACK); + if (ntracks < target) satisfied = 0; + } + if (!satisfied) { + return solve_set_eflag(state, x, y, dir, E_NOTRACK, + "joins start to end with incomplete clues"); + } + } + } + return 0; +} + +static int solve_check_loop(game_state *state) +{ + int w = state->p.w, h = state->p.h, x, y, i, j, did = 0; + int *dsf, startc, endc; + + /* TODO eventually we should pull this out into a solver struct and keep it + updated as we connect squares. For now we recreate it every time we try + this particular solver step. */ + dsf = snewn(w*h, int); + dsf_init(dsf, w*h); + + /* Work out the connectedness of the current loop set. */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + i = y*w + x; + if (x < (w-1) && S_E_DIRS(state, x, y, E_TRACK) & R) { + /* connection to the right... */ + j = y*w + (x+1); + assert(i < w*h && j < w*h); + dsf_merge(dsf, i, j); + } + if (y < (h-1) && S_E_DIRS(state, x, y, E_TRACK) & D) { + /* connection down... */ + j = (y+1)*w + x; + assert(i < w*h && j < w*h); + dsf_merge(dsf, i, j); + } + /* NB no need to check up and left because they'll have been checked + by the other side. */ + } + } + + startc = dsf_canonify(dsf, state->numbers->row_s*w); + endc = dsf_canonify(dsf, (h-1)*w+state->numbers->col_s); + + /* Now look at all adjacent squares that are both S_TRACK: if connecting + any of them would complete a loop (i.e. they're both the same dsf class + already) then that edge must be NOTRACK. */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + if (x < (w-1)) + did += solve_check_loop_sub(state, x, y, R, dsf, startc, endc); + if (y < (h-1)) + did += solve_check_loop_sub(state, x, y, D, dsf, startc, endc); + } + } + + sfree(dsf); + + return did; +} + +static void solve_discount_edge(game_state *state, int x, int y, int d) +{ + if (S_E_DIRS(state, x, y, E_TRACK) & d) { + assert(state->sflags[y*state->p.w + x] & S_CLUE); + return; /* (only) clue squares can have outer edges set. */ + } + solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge"); +} + +static int tracks_solve(game_state *state, int diff) +{ + int didsth, x, y, w = state->p.w, h = state->p.h; + + debug(("solve...")); + state->impossible = FALSE; + + /* Set all the outer border edges as no-track. */ + for (x = 0; x < w; x++) { + solve_discount_edge(state, x, 0, U); + solve_discount_edge(state, x, h-1, D); + } + for (y = 0; y < h; y++) { + solve_discount_edge(state, 0, y, L); + solve_discount_edge(state, w-1, y, R); + } + + while (1) { + didsth = 0; + + didsth += solve_update_flags(state); + didsth += solve_count_clues(state); + didsth += solve_check_loop(state); + + if (diff >= DIFF_TRICKY) { + didsth += solve_check_single(state); + didsth += solve_check_loose_ends(state); + } + + if (!didsth || state->impossible) break; + } + + return state->impossible ? -1 : check_completion(state, FALSE) ? 1 : 0; +} + +static char *move_string_diff(const game_state *before, const game_state *after, int issolve) +{ + int w = after->p.w, h = after->p.h, i, j; + char *move = snewn(w*h*40, char), *p = move; + const char *sep = ""; + unsigned int otf, ntf, onf, nnf; + + if (issolve) { + *p++ = 'S'; + sep = ";"; + } + for (i = 0; i < w*h; i++) { + otf = S_E_DIRS(before, i%w, i/w, E_TRACK); + ntf = S_E_DIRS(after, i%w, i/w, E_TRACK); + onf = S_E_DIRS(before, i%w, i/w, E_NOTRACK); + nnf = S_E_DIRS(after, i%w, i/w, E_NOTRACK); + + for (j = 0; j < 4; j++) { + unsigned df = 1<sflags[i] & S_NOTRACK) != (after->sflags[i] & S_NOTRACK)) { + p += sprintf(p, "%s%cS%d,%d", sep, + (after->sflags[i] & S_NOTRACK) ? 'N' : 'n', i%w, i/w); + sep = ";"; + } + if ((before->sflags[i] & S_TRACK) != (after->sflags[i] & S_TRACK)) { + p += sprintf(p, "%s%cS%d,%d", sep, + (after->sflags[i] & S_TRACK) ? 'T' : 't', i%w, i/w); + sep = ";"; + } + } + *p++ = '\0'; + move = sresize(move, p - move, char); + + return move; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved; + int ret; + char *move; + + solved = dup_game(currstate); + ret = tracks_solve(solved, DIFFCOUNT); + if (ret < 1) { + free_game(solved); + solved = dup_game(state); + ret = tracks_solve(solved, DIFFCOUNT); + } + + if (ret < 1) { + *error = "Unable to find solution"; + move = NULL; + } else { + move = move_string_diff(currstate, solved, TRUE); + } + + free_game(solved); + return move; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret, *p; + int x, y, len, w = state->p.w, h = state->p.h; + + len = ((w*2) + 4) * ((h*2)+4) + 2; + ret = snewn(len+1, char); + p = ret; + + /* top line: column clues */ + *p++ = ' '; + *p++ = ' '; + for (x = 0; x < w; x++) { + *p++ = (state->numbers->numbers[x] < 10 ? + '0' + state->numbers->numbers[x] : + 'A' + state->numbers->numbers[x] - 10); + *p++ = ' '; + } + *p++ = '\n'; + + /* second line: top edge */ + *p++ = ' '; + *p++ = '+'; + for (x = 0; x < w*2-1; x++) + *p++ = '-'; + *p++ = '+'; + *p++ = '\n'; + + /* grid rows: one line of squares, one line of edges. */ + for (y = 0; y < h; y++) { + /* grid square line */ + *p++ = (y == state->numbers->row_s) ? 'A' : ' '; + *p++ = (y == state->numbers->row_s) ? '-' : '|'; + + for (x = 0; x < w; x++) { + unsigned int f = S_E_DIRS(state, x, y, E_TRACK); + if (state->sflags[y*w+x] & S_CLUE) *p++ = 'C'; + else if (f == LU || f == RD) *p++ = '/'; + else if (f == LD || f == RU) *p++ = '\\'; + else if (f == UD) *p++ = '|'; + else if (f == RL) *p++ = '-'; + else if (state->sflags[y*w+x] & S_NOTRACK) *p++ = 'x'; + else *p++ = ' '; + + if (x < w-1) { + *p++ = (f & R) ? '-' : ' '; + } else + *p++ = '|'; + } + *p++ = (state->numbers->numbers[w+y] < 10 ? + '0' + state->numbers->numbers[w+y] : + 'A' + state->numbers->numbers[w+y] - 10); + *p++ = '\n'; + + if (y == h-1) continue; + + /* edges line */ + *p++ = ' '; + *p++ = '|'; + for (x = 0; x < w; x++) { + unsigned int f = S_E_DIRS(state, x, y, E_TRACK); + *p++ = (f & D) ? '|' : ' '; + *p++ = (x < w-1) ? ' ' : '|'; + } + *p++ = '\n'; + } + + /* next line: bottom edge */ + *p++ = ' '; + *p++ = '+'; + for (x = 0; x < w*2-1; x++) + *p++ = (x == state->numbers->col_s*2) ? '|' : '-'; + *p++ = '+'; + *p++ = '\n'; + + /* final line: bottom clue */ + *p++ = ' '; + *p++ = ' '; + for (x = 0; x < w*2-1; x++) + *p++ = (x == state->numbers->col_s*2) ? 'B' : ' '; + *p++ = '\n'; + + *p = '\0'; + return ret; +} + +static void debug_state(game_state *state, const char *what) { + char *sstring = game_text_format(state); + debug(("%s: %s", what, sstring)); + sfree(sstring); +} + +static void dsf_update_completion(game_state *state, int ax, int ay, + char dir, int *dsf) +{ + int w = state->p.w, ai = ay*w+ax, bx, by, bi; + + if (!(S_E_DIRS(state, ax, ay, E_TRACK) & dir)) return; + bx = ax + DX(dir); + by = ay + DY(dir); + + if (!INGRID(state, bx, by)) return; + bi = by*w+bx; + + dsf_merge(dsf, ai, bi); +} + +struct tracks_neighbour_ctx { + game_state *state; + int i, n, neighbours[4]; +}; +static int tracks_neighbour(int vertex, void *vctx) +{ + struct tracks_neighbour_ctx *ctx = (struct tracks_neighbour_ctx *)vctx; + if (vertex >= 0) { + game_state *state = ctx->state; + int w = state->p.w, x = vertex % w, y = vertex / w; + int dirs = S_E_DIRS(state, x, y, E_TRACK); + int j; + + ctx->i = ctx->n = 0; + + for (j = 0; j < 4; j++) { + int dir = 1<neighbours[ctx->n++] = ny * w + nx; + } + } + } + + if (ctx->i < ctx->n) + return ctx->neighbours[ctx->i++]; + else + return -1; +} + +static int check_completion(game_state *state, int mark) +{ + int w = state->p.w, h = state->p.h, x, y, i, target, ret = TRUE; + int ntrack, nnotrack, ntrackcomplete; + int *dsf, pathclass; + struct findloopstate *fls; + struct tracks_neighbour_ctx ctx; + + if (mark) { + for (i = 0; i < w+h; i++) { + state->num_errors[i] = 0; + } + for (i = 0; i < w*h; i++) { + state->sflags[i] &= ~S_ERROR; + if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0) { + if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 2) { + ret = FALSE; + state->sflags[i] |= S_ERROR; + } + } + } + } + + /* A cell is 'complete', for the purposes of marking the game as + * finished, if it has two edges marked as TRACK. But it only has + * to have one edge marked as TRACK, or be filled in as trackful + * without any specific edges known, to count towards checking + * row/column clue errors. */ + for (x = 0; x < w; x++) { + target = state->numbers->numbers[x]; + ntrack = nnotrack = ntrackcomplete = 0; + for (y = 0; y < h; y++) { + if (S_E_COUNT(state, x, y, E_TRACK) > 0 || + state->sflags[y*w+x] & S_TRACK) + ntrack++; + if (S_E_COUNT(state, x, y, E_TRACK) == 2) + ntrackcomplete++; + if (state->sflags[y*w+x] & S_NOTRACK) + nnotrack++; + } + if (mark) { + if (ntrack > target || nnotrack > (h-target)) { + debug(("col %d error: target %d, track %d, notrack %d", + x, target, ntrack, nnotrack)); + state->num_errors[x] = 1; + ret = FALSE; + } + } + if (ntrackcomplete != target) + ret = FALSE; + } + for (y = 0; y < h; y++) { + target = state->numbers->numbers[w+y]; + ntrack = nnotrack = ntrackcomplete = 0; + for (x = 0; x < w; x++) { + if (S_E_COUNT(state, x, y, E_TRACK) > 0 || + state->sflags[y*w+x] & S_TRACK) + ntrack++; + if (S_E_COUNT(state, x, y, E_TRACK) == 2) + ntrackcomplete++; + if (state->sflags[y*w+x] & S_NOTRACK) + nnotrack++; + } + if (mark) { + if (ntrack > target || nnotrack > (w-target)) { + debug(("row %d error: target %d, track %d, notrack %d", + y, target, ntrack, nnotrack)); + state->num_errors[w+y] = 1; + ret = FALSE; + } + } + if (ntrackcomplete != target) + ret = FALSE; + } + + dsf = snewn(w*h, int); + dsf_init(dsf, w*h); + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + dsf_update_completion(state, x, y, R, dsf); + dsf_update_completion(state, x, y, D, dsf); + } + } + + fls = findloop_new_state(w*h); + ctx.state = state; + if (findloop_run(fls, w*h, tracks_neighbour, &ctx)) { + debug(("loop detected, not complete")); + ret = FALSE; /* no loop allowed */ + if (mark) { + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + int u, v; + + u = y*w + x; + for (v = tracks_neighbour(u, &ctx); v >= 0; + v = tracks_neighbour(-1, &ctx)) + if (findloop_is_loop_edge(fls, u, v)) + state->sflags[y*w+x] |= S_ERROR; + } + } + } + } + findloop_free_state(fls); + + if (mark) { + pathclass = dsf_canonify(dsf, state->numbers->row_s*w); + if (pathclass == dsf_canonify(dsf, (h-1)*w + state->numbers->col_s)) { + /* We have a continuous path between the entrance and the exit: any + other path must be in error. */ + for (i = 0; i < w*h; i++) { + if ((dsf_canonify(dsf, i) != pathclass) && + ((state->sflags[i] & S_TRACK) || + (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0))) { + ret = FALSE; + state->sflags[i] |= S_ERROR; + } + } + } else { + /* If we _don't_ have such a path, then certainly the game + * can't be in a winning state. So even if we're not + * highlighting any _errors_, we certainly shouldn't + * return true. */ + ret = FALSE; + } + } + + if (mark) + state->completed = ret; + sfree(dsf); + return ret; +} + +/* Code borrowed from Pearl. */ + +struct game_ui { + int dragging, clearing, notrack; + int drag_sx, drag_sy, drag_ex, drag_ey; /* drag start and end grid coords */ + int clickx, clicky; /* pixel position of initial click */ + + int curx, cury; /* grid position of keyboard cursor; uses half-size grid */ + int cursor_active; /* TRUE iff cursor is shown */ +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->clearing = ui->notrack = ui->dragging = 0; + ui->drag_sx = ui->drag_sy = ui->drag_ex = ui->drag_ey = -1; + ui->cursor_active = FALSE; + ui->curx = ui->cury = 1; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +#define PREFERRED_TILE_SIZE 30 +#define HALFSZ (ds->sz6*3) +#define THIRDSZ (ds->sz6*2) +#define TILE_SIZE (ds->sz6*6) + +#define BORDER (TILE_SIZE/8) +#define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) + +#define COORD(x) ( (x+1) * TILE_SIZE + BORDER ) +#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 ) +#define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) - 1 ) + +#define DS_DSHIFT 4 /* R/U/L/D shift, for drag-in-progress flags */ + +#define DS_ERROR (1 << 8) +#define DS_CLUE (1 << 9) +#define DS_NOTRACK (1 << 10) +#define DS_FLASH (1 << 11) +#define DS_CURSOR (1 << 12) /* cursor in square (centre, or on edge) */ +#define DS_TRACK (1 << 13) +#define DS_CLEARING (1 << 14) + +#define DS_NSHIFT 16 /* R/U/L/D shift, for no-track edge flags */ +#define DS_CSHIFT 20 /* R/U/L/D shift, for cursor-on-edge */ + +struct game_drawstate { + int sz6; + int started; + + int w, h, sz; + unsigned int *flags, *flags_drag; + int *num_errors; +}; + +static void update_ui_drag(const game_state *state, game_ui *ui, int gx, int gy) +{ + int w = state->p.w, h = state->p.h; + int dx = abs(ui->drag_sx - gx), dy = abs(ui->drag_sy - gy); + + if (dy == 0) { + ui->drag_ex = gx < 0 ? 0 : gx >= w ? w-1 : gx; + ui->drag_ey = ui->drag_sy; + ui->dragging = TRUE; + } else if (dx == 0) { + ui->drag_ex = ui->drag_sx; + ui->drag_ey = gy < 0 ? 0 : gy >= h ? h-1 : gy; + ui->dragging = TRUE; + } else { + ui->drag_ex = ui->drag_sx; + ui->drag_ey = ui->drag_sy; + ui->dragging = FALSE; + } +} + +static int ui_can_flip_edge(const game_state *state, int x, int y, int dir, + int notrack) +{ + int w = state->p.w /*, h = state->shared->h, sz = state->shared->sz */; + int x2 = x + DX(dir); + int y2 = y + DY(dir); + unsigned int sf1, sf2, ef; + + if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) + return FALSE; + + sf1 = state->sflags[y*w + x]; + sf2 = state->sflags[y2*w + x2]; + if ( !notrack && ((sf1 & S_CLUE) || (sf2 & S_CLUE)) ) + return FALSE; + + ef = S_E_FLAGS(state, x, y, dir); + if (notrack) { + /* if we're going to _set_ NOTRACK (i.e. the flag is currently unset), + make sure the edge is not already set to TRACK. The adjacent squares + could be set to TRACK, because we don't know which edges the general + square setting refers to. */ + if (!(ef & E_NOTRACK) && (ef & E_TRACK)) + return FALSE; + } else { + if (!(ef & E_TRACK)) { + /* if we're going to _set_ TRACK, make sure neither adjacent square nor + the edge itself is already set to NOTRACK. */ + if ((sf1 & S_NOTRACK) || (sf2 & S_NOTRACK) || (ef & E_NOTRACK)) + return FALSE; + /* if we're going to _set_ TRACK, make sure neither adjacent square has + 2 track flags already. */ + if ((S_E_COUNT(state, x, y, E_TRACK) >= 2) || + (S_E_COUNT(state, x2, y2, E_TRACK) >= 2)) + return FALSE; + } + } + return TRUE; +} + +static int ui_can_flip_square(const game_state *state, int x, int y, int notrack) +{ + int w = state->p.w, trackc; + unsigned sf; + + if (!INGRID(state, x, y)) return FALSE; + sf = state->sflags[y*w+x]; + trackc = S_E_COUNT(state, x, y, E_TRACK); + + if (sf & S_CLUE) return FALSE; + + if (notrack) { + /* If we're setting S_NOTRACK, we cannot have either S_TRACK or any E_TRACK. */ + if (!(sf & S_NOTRACK) && ((sf & S_TRACK) || (trackc > 0))) + return FALSE; + } else { + /* If we're setting S_TRACK, we cannot have any S_NOTRACK (we could have + E_NOTRACK, though, because one or two wouldn't rule out a track) */ + if (!(sf & S_TRACK) && (sf & S_NOTRACK)) + return FALSE; + } + return TRUE; +} + +static char *edge_flip_str(const game_state *state, int x, int y, int dir, int notrack, char *buf) { + unsigned ef = S_E_FLAGS(state, x, y, dir); + char c; + + if (notrack) + c = (ef & E_NOTRACK) ? 'n' : 'N'; + else + c = (ef & E_TRACK) ? 't' : 'T'; + + sprintf(buf, "%c%c%d,%d", c, MOVECHAR(dir), x, y); + return dupstr(buf); +} + +static char *square_flip_str(const game_state *state, int x, int y, int notrack, char *buf) { + unsigned f = state->sflags[y*state->p.w+x]; + char c; + + if (notrack) + c = (f & E_NOTRACK) ? 'n' : 'N'; + else + c = (f & E_TRACK) ? 't' : 'T'; + + sprintf(buf, "%cS%d,%d", c, x, y); + return dupstr(buf); +} + +#define SIGN(x) ((x<0) ? -1 : (x>0)) + +static game_state *copy_and_apply_drag(const game_state *state, const game_ui *ui) +{ + game_state *after = dup_game(state); + int x1, y1, x2, y2, x, y, w = state->p.w; + unsigned f = ui->notrack ? S_NOTRACK : S_TRACK, ff; + + x1 = min(ui->drag_sx, ui->drag_ex); x2 = max(ui->drag_sx, ui->drag_ex); + y1 = min(ui->drag_sy, ui->drag_ey); y2 = max(ui->drag_sy, ui->drag_ey); + + /* actually either x1 == x2, or y1 == y2, but it's easier just to code + the nested loop. */ + for (x = x1; x <= x2; x++) { + for (y = y1; y <= y2; y++) { + ff = state->sflags[y*w+x]; + if (ui->clearing && !(ff & f)) + continue; /* nothing to do, clearing and already clear */ + else if (!ui->clearing && (ff & f)) + continue; /* nothing to do, setting and already set */ + else if (ui_can_flip_square(state, x, y, ui->notrack)) + after->sflags[y*w+x] ^= f; + } + } + return after; +} + +#define KEY_DIRECTION(btn) (\ + (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\ + (btn) == CURSOR_LEFT ? L : R) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->p.w, h = state->p.h, direction; + int gx = FROMCOORD(x), gy = FROMCOORD(y); + char tmpbuf[80]; + + /* --- mouse operations --- */ + + if (IS_MOUSE_DOWN(button)) { + ui->cursor_active = FALSE; + ui->dragging = FALSE; + + if (!INGRID(state, gx, gy)) { + /* can't drag from off grid */ + return NULL; + } + + if (button == RIGHT_BUTTON) { + ui->notrack = TRUE; + ui->clearing = state->sflags[gy*w+gx] & S_NOTRACK; + } else { + ui->notrack = FALSE; + ui->clearing = state->sflags[gy*w+gx] & S_TRACK; + } + + ui->clickx = x; + ui->clicky = y; + ui->drag_sx = ui->drag_ex = gx; + ui->drag_sy = ui->drag_ey = gy; + + return ""; + } + + if (IS_MOUSE_DRAG(button)) { + ui->cursor_active = FALSE; + update_ui_drag(state, ui, gx, gy); + return ""; + } + + if (IS_MOUSE_RELEASE(button)) { + ui->cursor_active = FALSE; + if (ui->dragging && + (ui->drag_sx != ui->drag_ex || ui->drag_sy != ui->drag_ey)) { + game_state *dragged = copy_and_apply_drag(state, ui); + char *ret = move_string_diff(state, dragged, FALSE); + + ui->dragging = 0; + free_game(dragged); + + return ret; + } else { + int cx, cy; + + /* We might still have been dragging (and just done a one- + * square drag): cancel drag, so undo doesn't make it like + * a drag-in-progress. */ + ui->dragging = 0; + + /* Click (or tiny drag). Work out which edge we were + * closest to. */ + + /* + * We process clicks based on the mouse-down location, + * because that's more natural for a user to carefully + * control than the mouse-up. + */ + x = ui->clickx; + y = ui->clicky; + + cx = CENTERED_COORD(gx); + cy = CENTERED_COORD(gy); + + if (!INGRID(state, gx, gy) || FROMCOORD(x) != gx || FROMCOORD(y) != gy) + return ""; + + if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { + if (ui_can_flip_square(state, gx, gy, button == RIGHT_RELEASE)) + return square_flip_str(state, gx, gy, button == RIGHT_RELEASE, tmpbuf); + return ""; + } else { + if (abs(x-cx) < abs(y-cy)) { + /* Closest to top/bottom edge. */ + direction = (y < cy) ? U : D; + } else { + /* Closest to left/right edge. */ + direction = (x < cx) ? L : R; + } + if (ui_can_flip_edge(state, gx, gy, direction, + button == RIGHT_RELEASE)) + return edge_flip_str(state, gx, gy, direction, + button == RIGHT_RELEASE, tmpbuf); + else + return ""; + } + } + } + + /* --- cursor/keyboard operations --- */ + + if (IS_CURSOR_MOVE(button)) { + int dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0); + int dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP) ? -1 : 0); + + if (!ui->cursor_active) { + ui->cursor_active = TRUE; + return ""; + } + + ui->curx = ui->curx + dx; + ui->cury = ui->cury + dy; + if ((ui->curx % 2 == 0) && (ui->cury % 2 == 0)) { + /* disallow cursor on square corners: centres and edges only */ + ui->curx += dx; ui->cury += dy; + } + ui->curx = min(max(ui->curx, 1), 2*w-1); + ui->cury = min(max(ui->cury, 1), 2*h-1); + return ""; + } + + if (IS_CURSOR_SELECT(button)) { + if (!ui->cursor_active) { + ui->cursor_active = TRUE; + return ""; + } + /* click on square corner does nothing (shouldn't get here) */ + if ((ui->curx % 2) == 0 && (ui->cury % 2 == 0)) + return ""; + + gx = ui->curx / 2; + gy = ui->cury / 2; + direction = ((ui->curx % 2) == 0) ? L : ((ui->cury % 2) == 0) ? U : 0; + + if (direction && + ui_can_flip_edge(state, gx, gy, direction, button == CURSOR_SELECT2)) + return edge_flip_str(state, gx, gy, direction, button == CURSOR_SELECT2, tmpbuf); + else if (!direction && + ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2)) + return square_flip_str(state, gx, gy, button == CURSOR_SELECT2, tmpbuf); + return ""; + } + +#if 0 + /* helps to debug the solver */ + if (button == 'H' || button == 'h') + return dupstr("H"); +#endif + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->p.w, x, y, n, i; + char c, d; + unsigned f; + game_state *ret = dup_game(state); + + /* this is breaking the bank on GTK, which vsprintf's into a fixed-size buffer + * which is 4096 bytes long. vsnprintf needs a feature-test macro to use, faff. */ + /*debug(("move: %s\n", move));*/ + + while (*move) { + c = *move; + if (c == 'S') { + ret->used_solve = TRUE; + move++; + } else if (c == 'T' || c == 't' || c == 'N' || c == 'n') { + /* set track, clear track; set notrack, clear notrack */ + move++; + if (sscanf(move, "%c%d,%d%n", &d, &x, &y, &n) != 3) + goto badmove; + if (!INGRID(state, x, y)) goto badmove; + + f = (c == 'T' || c == 't') ? S_TRACK : S_NOTRACK; + + if (d == 'S') { + if (c == 'T' || c == 'N') + ret->sflags[y*w+x] |= f; + else + ret->sflags[y*w+x] &= ~f; + } else if (d == 'U' || d == 'D' || d == 'L' || d == 'R') { + for (i = 0; i < 4; i++) { + unsigned df = 1<tilesize' for macro expansion purposes */ + struct { + int sz6; + } ads, *ds = &ads; + ads.sz6 = tilesize/6; + + *x = (params->w+2) * TILE_SIZE + 2 * BORDER; + *y = (params->h+2) * TILE_SIZE + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->sz6 = tilesize/6; +} + +enum { + COL_BACKGROUND, COL_LOWLIGHT, COL_HIGHLIGHT, + COL_TRACK_BACKGROUND = COL_LOWLIGHT, + COL_GRID, COL_CLUE, COL_CURSOR, + COL_TRACK, COL_TRACK_CLUE, COL_SLEEPER, + COL_DRAGON, COL_DRAGOFF, + COL_ERROR, COL_FLASH, + NCOLOURS +}; + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) { + ret[COL_TRACK_CLUE * 3 + i] = 0.0F; + ret[COL_TRACK * 3 + i] = 0.5F; + ret[COL_CLUE * 3 + i] = 0.0F; + ret[COL_GRID * 3 + i] = 0.75F; + ret[COL_CURSOR * 3 + i] = 0.6F; + } + + ret[COL_SLEEPER * 3 + 0] = 0.5F; + ret[COL_SLEEPER * 3 + 1] = 0.4F; + ret[COL_SLEEPER * 3 + 2] = 0.1F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_DRAGON * 3 + 0] = 0.0F; + ret[COL_DRAGON * 3 + 1] = 0.0F; + ret[COL_DRAGON * 3 + 2] = 1.0F; + + ret[COL_DRAGOFF * 3 + 0] = 0.8F; + ret[COL_DRAGOFF * 3 + 1] = 0.8F; + ret[COL_DRAGOFF * 3 + 2] = 1.0F; + + ret[COL_FLASH * 3 + 0] = 1.0F; + ret[COL_FLASH * 3 + 1] = 1.0F; + ret[COL_FLASH * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->sz6 = 0; + ds->started = FALSE; + + ds->w = state->p.w; + ds->h = state->p.h; + ds->sz = ds->w*ds->h; + ds->flags = snewn(ds->sz, unsigned int); + ds->flags_drag = snewn(ds->sz, unsigned int); + for (i = 0; i < ds->sz; i++) + ds->flags[i] = ds->flags_drag[i] = 0; + + ds->num_errors = snewn(ds->w+ds->h, int); + for (i = 0; i < ds->w+ds->h; i++) + ds->num_errors[i] = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->flags); + sfree(ds->flags_drag); + sfree(ds->num_errors); + sfree(ds); +} + +static void draw_circle_sleepers(drawing *dr, game_drawstate *ds, + float cx, float cy, float r2, float thickness, int c) +{ + float qr6 = (float)PI/12, qr3 = (float)PI/6, th, x1, y1, x2, y2; + float t6 = THIRDSZ/2.0F, r1 = t6; + int i; + + for (i = 0; i < 12; i++) { + th = qr6 + (i*qr3); + x1 = r1*(float)cos(th); + x2 = r2*(float)cos(th); + y1 = r1*(float)sin(th); + y2 = r2*(float)sin(th); + draw_thick_line(dr, thickness, cx+x1, cy+y1, cx+x2, cy+y2, c); + } +} + +static void draw_thick_circle_outline(drawing *dr, float thickness, + float cx, float cy, float r, + int colour) +{ + float circ4 = 0.5F * (float)PI * r, ang, x1, y1, x2, y2; + int i, nseg; + + nseg = (int)(circ4 / 4.0F)*4; /* ensure a quarter-circle has a whole #segs */ + ang = 2.0F*(float)PI / nseg; + + for (i = 0; i < nseg; i++) { + float th = ang * i, th2 = ang * (i+1); + x1 = cx + r*(float)cos(th); + x2 = cx + r*(float)cos(th2); + y1 = cy + r*(float)sin(th); + y2 = cy + r*(float)sin(th2); + debug(("circ outline: x=%.2f -> %.2f, thick=%.2f", x1, x2, thickness)); + draw_thick_line(dr, thickness, x1, y1, x2, y2, colour); + } +} + +static void draw_tracks_specific(drawing *dr, game_drawstate *ds, + int x, int y, unsigned int flags, + int ctrack, int csleeper) +{ + float ox = (float)COORD(x), oy = (float)COORD(y), cx, cy; + float t1 = (float)TILE_SIZE, t3 = TILE_SIZE/3.0F, t6 = TILE_SIZE/6.0F; + int d, i; + float thick_track = TILE_SIZE/8.0F, thick_sleeper = TILE_SIZE/12.0F; + + if (flags == LR) { + for (i = 1; i <= 7; i+=2) { + cx = ox + TILE_SIZE/8.0F*i; + draw_thick_line(dr, thick_sleeper, + cx, oy+t6, cx, oy+t6+2*t3, csleeper); + } + draw_thick_line(dr, thick_track, ox, oy + t3, ox + TILE_SIZE, oy + t3, ctrack); + draw_thick_line(dr, thick_track, ox, oy + 2*t3, ox + TILE_SIZE, oy + 2*t3, ctrack); + return; + } + if (flags == UD) { + for (i = 1; i <= 7; i+=2) { + cy = oy + TILE_SIZE/8.0F*i; + draw_thick_line(dr, thick_sleeper, + ox+t6, cy, ox+t6+2*t3, cy, csleeper); + } + debug(("vert line: x=%.2f, thick=%.2f", ox + t3, thick_track)); + draw_thick_line(dr, thick_track, ox + t3, oy, ox + t3, oy + TILE_SIZE, ctrack); + draw_thick_line(dr, thick_track, ox + 2*t3, oy, ox + 2*t3, oy + TILE_SIZE, ctrack); + return; + } + if (flags == UL || flags == DL || flags == UR || flags == DR) { + cx = (flags & L) ? ox : ox + TILE_SIZE; + cy = (flags & U) ? oy : oy + TILE_SIZE; + + draw_circle_sleepers(dr, ds, cx, cy, (float)(5*t6), thick_sleeper, csleeper); + + draw_thick_circle_outline(dr, thick_track, (float)cx, (float)cy, + 2*t3, ctrack); + draw_thick_circle_outline(dr, thick_track, (float)cx, (float)cy, + t3, ctrack); + + return; + } + + for (d = 1; d < 16; d *= 2) { + float ox1 = 0, ox2 = 0, oy1 = 0, oy2 = 0; + + if (!(flags & d)) continue; + + for (i = 1; i <= 2; i++) { + if (d == L) { + ox1 = 0; + ox2 = thick_track; + oy1 = oy2 = i*t3; + } else if (d == R) { + ox1 = t1; + ox2 = t1 - thick_track; + oy1 = oy2 = i*t3; + } else if (d == U) { + ox1 = ox2 = i*t3; + oy1 = 0; + oy2 = thick_track; + } else if (d == D) { + ox1 = ox2 = i*t3; + oy1 = t1; + oy2 = t1 - thick_track; + } + draw_thick_line(dr, thick_track, ox+ox1, oy+oy1, ox+ox2, oy+oy2, ctrack); + } + } +} + +static unsigned int best_bits(unsigned int flags, unsigned int flags_drag, int *col) +{ + int nb_orig = nbits[flags & ALLDIR], nb_drag = nbits[flags_drag & ALLDIR]; + + if (nb_orig > nb_drag) { + *col = COL_DRAGOFF; + return flags & ALLDIR; + } else if (nb_orig < nb_drag) { + *col = COL_DRAGON; + return flags_drag & ALLDIR; + } + return flags & ALLDIR; /* same number of bits: no special colour. */ +} + +static void draw_square(drawing *dr, game_drawstate *ds, + int x, int y, unsigned int flags, unsigned int flags_drag) +{ + int t2 = HALFSZ, t16 = HALFSZ/4, off; + int ox = COORD(x), oy = COORD(y), cx = ox + t2, cy = oy + t2, d, c; + int bg = (flags & DS_TRACK) ? COL_TRACK_BACKGROUND : COL_BACKGROUND; + unsigned int flags_best; + + assert(dr); + + /* Clip to the grid square. */ + clip(dr, ox, oy, TILE_SIZE, TILE_SIZE); + + /* Clear the square. */ + best_bits((flags & DS_TRACK) == DS_TRACK, + (flags_drag & DS_TRACK) == DS_TRACK, &bg); + draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg); + + /* Draw outline of grid square */ + draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID); + draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID); + + /* More outlines for clue squares. */ + if (flags & DS_CURSOR) { + int curx, cury, curw, curh; + + off = t16; + curx = ox + off; cury = oy + off; + curw = curh = TILE_SIZE - (2*off) + 1; + + if (flags & (U << DS_CSHIFT)) { + cury = oy - off; curh = 2*off + 1; + } else if (flags & (D << DS_CSHIFT)) { + cury = oy + TILE_SIZE - off; curh = 2*off + 1; + } else if (flags & (L << DS_CSHIFT)) { + curx = ox - off; curw = 2*off + 1; + } else if (flags & (R << DS_CSHIFT)) { + curx = ox + TILE_SIZE - off; curw = 2*off + 1; + } + + draw_rect_outline(dr, curx, cury, curw, curh, COL_GRID); + } + + /* Draw tracks themselves */ + c = (flags & DS_ERROR) ? COL_ERROR : + (flags & DS_FLASH) ? COL_FLASH : + (flags & DS_CLUE) ? COL_TRACK_CLUE : COL_TRACK; + flags_best = best_bits(flags, flags_drag, &c); + draw_tracks_specific(dr, ds, x, y, flags_best, c, COL_SLEEPER); + + /* Draw no-track marks, if present, in square and on edges. */ + c = COL_TRACK; + flags_best = best_bits((flags & DS_NOTRACK) == DS_NOTRACK, + (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c); + if (flags_best) { + off = HALFSZ/2; + draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); + draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); + } + + c = COL_TRACK; + flags_best = best_bits(flags >> DS_NSHIFT, flags_drag >> DS_NSHIFT, &c); + for (d = 1; d < 16; d *= 2) { + off = t16; + cx = ox + t2; + cy = oy + t2; + + if (flags_best & d) { + cx += (d == R) ? t2 : (d == L) ? -t2 : 0; + cy += (d == D) ? t2 : (d == U) ? -t2 : 0; + + draw_line(dr, cx - off, cy - off, cx + off, cy + off, c); + draw_line(dr, cx - off, cy + off, cx + off, cy - off, c); + } + } + + unclip(dr); + draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE); +} + +static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, int col) +{ + int cx, cy, tsz = TILE_SIZE/2; + char buf[20]; + + if (i < w) { + cx = CENTERED_COORD(i); + cy = CENTERED_COORD(-1); + } else { + cx = CENTERED_COORD(w); + cy = CENTERED_COORD(i-w); + } + + draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER, + TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND); + sprintf(buf, "%d", clue); + draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, + col, buf); + draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE); +} + +static void draw_loop_ends(drawing *dr, game_drawstate *ds, + const game_state *state, int c) +{ + int tsz = TILE_SIZE/2; + + draw_text(dr, CENTERED_COORD(-1), CENTERED_COORD(state->numbers->row_s), + FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, + c, "A"); + + draw_text(dr, CENTERED_COORD(state->numbers->col_s), CENTERED_COORD(state->p.h), + FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE, + c, "B"); +} + +static unsigned int s2d_flags(const game_state *state, int x, int y, const game_ui *ui) +{ + unsigned int f; + int w = state->p.w; + + f = S_E_DIRS(state, x, y, E_TRACK); + f |= (S_E_DIRS(state, x, y, E_NOTRACK) << DS_NSHIFT); + + if (state->sflags[y*w+x] & S_ERROR) + f |= DS_ERROR; + if (state->sflags[y*w+x] & S_CLUE) + f |= DS_CLUE; + if (state->sflags[y*w+x] & S_NOTRACK) + f |= DS_NOTRACK; + if ((state->sflags[y*w+x] & S_TRACK) || (S_E_COUNT(state, x, y, E_TRACK) > 0)) + f |= DS_TRACK; + + if (ui->cursor_active) { + if (ui->curx >= x*2 && ui->curx <= (x+1)*2 && + ui->cury >= y*2 && ui->cury <= (y+1)*2) { + f |= DS_CURSOR; + if (ui->curx == x*2) f |= (L << DS_CSHIFT); + if (ui->curx == (x+1)*2) f |= (R << DS_CSHIFT); + if (ui->cury == y*2) f |= (U << DS_CSHIFT); + if (ui->cury == (y+1)*2) f |= (D << DS_CSHIFT); + } + } + + return f; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, + const game_state *state, int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int i, x, y, force = 0, flashing = 0, w = ds->w, h = ds->h; + game_state *drag_state = NULL; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + draw_rect(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER, + COL_BACKGROUND); + + draw_loop_ends(dr, ds, state, COL_CLUE); + + draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID); + draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID); + + draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER); + + ds->started = TRUE; + force = 1; + } + + for (i = 0; i < w+h; i++) { + if (force || (state->num_errors[i] != ds->num_errors[i])) { + ds->num_errors[i] = state->num_errors[i]; + draw_clue(dr, ds, w, state->numbers->numbers[i], i, + ds->num_errors[i] ? COL_ERROR : COL_CLUE); + } + } + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + flashing = DS_FLASH; + + if (ui->dragging) + drag_state = copy_and_apply_drag(state, ui); + + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + unsigned int f, f_d; + + f = s2d_flags(state, x, y, ui) | flashing; + f_d = drag_state ? s2d_flags(drag_state, x, y, ui) : f; + + if (f != ds->flags[y*w+x] || f_d != ds->flags_drag[y*w+x] || force) { + ds->flags[y*w+x] = f; + ds->flags_drag[y*w+x] = f_d; + draw_square(dr, ds, x, y, f, f_d); + } + } + } + + if (drag_state) free_game(drag_state); +} + +static float game_anim_length(const game_state *oldstate, const game_state *newstate, + int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, const game_state *newstate, + int dir, game_ui *ui) +{ + if (!oldstate->completed && + newstate->completed && !newstate->used_solve) + return FLASH_TIME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* The Times uses 7mm squares */ + game_compute_size(params, 700, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->p.w, h = state->p.h; + int black = print_mono_colour(dr, 0), grey = print_grey_colour(dr, 0.5F); + int x, y, i; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* Grid, then border (second so it is on top) */ + print_line_width(dr, TILE_SIZE / 24); + for (x = 1; x < w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), grey); + for (y = 1; y < h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), grey); + + print_line_width(dr, TILE_SIZE / 16); + draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, black); + + print_line_width(dr, TILE_SIZE / 24); + + /* clue numbers, and loop ends */ + for (i = 0; i < w+h; i++) + draw_clue(dr, ds, w, state->numbers->numbers[i], i, black); + draw_loop_ends(dr, ds, state, black); + + /* clue tracks / solution */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + clip(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE); + draw_tracks_specific(dr, ds, x, y, S_E_DIRS(state, x, y, E_TRACK), + black, grey); + unclip(dr); + } + } +} + +#ifdef COMBINED +#define thegame tracks +#endif + +const struct game thegame = { + "Train Tracks", "games.tracks", "tracks", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/tree234.c b/apps/plugins/puzzles/tree234.c new file mode 100644 index 0000000000..71c3be242a --- /dev/null +++ b/apps/plugins/puzzles/tree234.c @@ -0,0 +1,2200 @@ +/* + * tree234.c: reasonably generic counted 2-3-4 tree routines. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "rbassert.h" + +#include "tree234.h" + +#include "puzzles.h" /* for smalloc/sfree */ + +#ifdef TEST +#define LOG(x) (printf x) +#define smalloc malloc +#define srealloc realloc +#define sfree free +#else +#define LOG(x) +#endif + +typedef struct node234_Tag node234; + +struct tree234_Tag { + node234 *root; + cmpfn234 cmp; +}; + +struct node234_Tag { + node234 *parent; + node234 *kids[4]; + int counts[4]; + void *elems[3]; +}; + +/* + * Create a 2-3-4 tree. + */ +tree234 *newtree234(cmpfn234 cmp) { + tree234 *ret = snew(tree234); + LOG(("created tree %p\n", ret)); + ret->root = NULL; + ret->cmp = cmp; + return ret; +} + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +static void freenode234(node234 *n) { + if (!n) + return; + freenode234(n->kids[0]); + freenode234(n->kids[1]); + freenode234(n->kids[2]); + freenode234(n->kids[3]); + sfree(n); +} +void freetree234(tree234 *t) { + freenode234(t->root); + sfree(t); +} + +/* + * Internal function to count a node. + */ +static int countnode234(node234 *n) { + int count = 0; + int i; + if (!n) + return 0; + for (i = 0; i < 4; i++) + count += n->counts[i]; + for (i = 0; i < 3; i++) + if (n->elems[i]) + count++; + return count; +} + +/* + * Count the elements in a tree. + */ +int count234(tree234 *t) { + if (t->root) + return countnode234(t->root); + else + return 0; +} + +/* + * Propagate a node overflow up a tree until it stops. Returns 0 or + * 1, depending on whether the root had to be split or not. + */ +static int add234_insert(node234 *left, void *e, node234 *right, + node234 **root, node234 *n, int ki) { + int lcount, rcount; + /* + * We need to insert the new left/element/right set in n at + * child position ki. + */ + lcount = countnode234(left); + rcount = countnode234(right); + while (n) { + LOG((" at %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" need to insert %p/%d \"%s\" %p/%d at position %d\n", + left, lcount, e, right, rcount, ki)); + if (n->elems[1] == NULL) { + /* + * Insert in a 2-node; simple. + */ + if (ki == 0) { + LOG((" inserting on left of 2-node\n")); + n->kids[2] = n->kids[1]; n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; n->counts[0] = lcount; + } else { /* ki == 1 */ + LOG((" inserting on right of 2-node\n")); + n->kids[2] = right; n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; n->counts[1] = lcount; + } + if (n->kids[0]) n->kids[0]->parent = n; + if (n->kids[1]) n->kids[1]->parent = n; + if (n->kids[2]) n->kids[2]->parent = n; + LOG((" done\n")); + break; + } else if (n->elems[2] == NULL) { + /* + * Insert in a 3-node; simple. + */ + if (ki == 0) { + LOG((" inserting on left of 3-node\n")); + n->kids[3] = n->kids[2]; n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = n->kids[1]; n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; n->counts[0] = lcount; + } else if (ki == 1) { + LOG((" inserting in middle of 3-node\n")); + n->kids[3] = n->kids[2]; n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = right; n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; n->counts[1] = lcount; + } else { /* ki == 2 */ + LOG((" inserting on right of 3-node\n")); + n->kids[3] = right; n->counts[3] = rcount; + n->elems[2] = e; + n->kids[2] = left; n->counts[2] = lcount; + } + if (n->kids[0]) n->kids[0]->parent = n; + if (n->kids[1]) n->kids[1]->parent = n; + if (n->kids[2]) n->kids[2]->parent = n; + if (n->kids[3]) n->kids[3]->parent = n; + LOG((" done\n")); + break; + } else { + node234 *m = snew(node234); + m->parent = n->parent; + LOG((" splitting a 4-node; created new node %p\n", m)); + /* + * Insert in a 4-node; split into a 2-node and a + * 3-node, and move focus up a level. + * + * I don't think it matters which way round we put the + * 2 and the 3. For simplicity, we'll put the 3 first + * always. + */ + if (ki == 0) { + m->kids[0] = left; m->counts[0] = lcount; + m->elems[0] = e; + m->kids[1] = right; m->counts[1] = rcount; + m->elems[1] = n->elems[0]; + m->kids[2] = n->kids[1]; m->counts[2] = n->counts[1]; + e = n->elems[1]; + n->kids[0] = n->kids[2]; n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; + } else if (ki == 1) { + m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = left; m->counts[1] = lcount; + m->elems[1] = e; + m->kids[2] = right; m->counts[2] = rcount; + e = n->elems[1]; + n->kids[0] = n->kids[2]; n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; + } else if (ki == 2) { + m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = left; m->counts[2] = lcount; + /* e = e; */ + n->kids[0] = right; n->counts[0] = rcount; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; + } else { /* ki == 3 */ + m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = n->kids[2]; m->counts[2] = n->counts[2]; + n->kids[0] = left; n->counts[0] = lcount; + n->elems[0] = e; + n->kids[1] = right; n->counts[1] = rcount; + e = n->elems[2]; + } + m->kids[3] = n->kids[3] = n->kids[2] = NULL; + m->counts[3] = n->counts[3] = n->counts[2] = 0; + m->elems[2] = n->elems[2] = n->elems[1] = NULL; + if (m->kids[0]) m->kids[0]->parent = m; + if (m->kids[1]) m->kids[1]->parent = m; + if (m->kids[2]) m->kids[2]->parent = m; + if (n->kids[0]) n->kids[0]->parent = n; + if (n->kids[1]) n->kids[1]->parent = n; + LOG((" left (%p): %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", m, + m->kids[0], m->counts[0], m->elems[0], + m->kids[1], m->counts[1], m->elems[1], + m->kids[2], m->counts[2])); + LOG((" right (%p): %p/%d \"%s\" %p/%d\n", n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1])); + left = m; lcount = countnode234(left); + right = n; rcount = countnode234(right); + } + if (n->parent) + ki = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n = n->parent; + } + + /* + * If we've come out of here by `break', n will still be + * non-NULL and all we need to do is go back up the tree + * updating counts. If we've come here because n is NULL, we + * need to create a new root for the tree because the old one + * has just split into two. */ + if (n) { + while (n->parent) { + int count = countnode234(n); + int childnum; + childnum = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n->parent->counts[childnum] = count; + n = n->parent; + } + return 0; /* root unchanged */ + } else { + LOG((" root is overloaded, split into two\n")); + (*root) = snew(node234); + (*root)->kids[0] = left; (*root)->counts[0] = lcount; + (*root)->elems[0] = e; + (*root)->kids[1] = right; (*root)->counts[1] = rcount; + (*root)->elems[1] = NULL; + (*root)->kids[2] = NULL; (*root)->counts[2] = 0; + (*root)->elems[2] = NULL; + (*root)->kids[3] = NULL; (*root)->counts[3] = 0; + (*root)->parent = NULL; + if ((*root)->kids[0]) (*root)->kids[0]->parent = (*root); + if ((*root)->kids[1]) (*root)->kids[1]->parent = (*root); + LOG((" new root is %p/%d \"%s\" %p/%d\n", + (*root)->kids[0], (*root)->counts[0], + (*root)->elems[0], + (*root)->kids[1], (*root)->counts[1])); + return 1; /* root moved */ + } +} + +/* + * Add an element e to a 2-3-4 tree t. Returns e on success, or if + * an existing element compares equal, returns that. + */ +static void *add234_internal(tree234 *t, void *e, int index) { + node234 *n; + int ki; + void *orig_e = e; + int c; + + LOG(("adding element \"%s\" to tree %p\n", e, t)); + if (t->root == NULL) { + t->root = snew(node234); + t->root->elems[1] = t->root->elems[2] = NULL; + t->root->kids[0] = t->root->kids[1] = NULL; + t->root->kids[2] = t->root->kids[3] = NULL; + t->root->counts[0] = t->root->counts[1] = 0; + t->root->counts[2] = t->root->counts[3] = 0; + t->root->parent = NULL; + t->root->elems[0] = e; + LOG((" created root %p\n", t->root)); + return orig_e; + } + + n = t->root; + while (n) { + LOG((" node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + if (index >= 0) { + if (!n->kids[0]) { + /* + * Leaf node. We want to insert at kid position + * equal to the index: + * + * 0 A 1 B 2 C 3 + */ + ki = index; + } else { + /* + * Internal node. We always descend through it (add + * always starts at the bottom, never in the + * middle). + */ + if (index <= n->counts[0]) { + ki = 0; + } else if (index -= n->counts[0] + 1, index <= n->counts[1]) { + ki = 1; + } else if (index -= n->counts[1] + 1, index <= n->counts[2]) { + ki = 2; + } else if (index -= n->counts[2] + 1, index <= n->counts[3]) { + ki = 3; + } else + return NULL; /* error: index out of range */ + } + } else { + if ((c = t->cmp(e, n->elems[0])) < 0) + ki = 0; + else if (c == 0) + return n->elems[0]; /* already exists */ + else if (n->elems[1] == NULL || (c = t->cmp(e, n->elems[1])) < 0) + ki = 1; + else if (c == 0) + return n->elems[1]; /* already exists */ + else if (n->elems[2] == NULL || (c = t->cmp(e, n->elems[2])) < 0) + ki = 2; + else if (c == 0) + return n->elems[2]; /* already exists */ + else + ki = 3; + } + LOG((" moving to child %d (%p)\n", ki, n->kids[ki])); + if (!n->kids[ki]) + break; + n = n->kids[ki]; + } + + add234_insert(NULL, e, NULL, &t->root, n, ki); + + return orig_e; +} + +void *add234(tree234 *t, void *e) { + if (!t->cmp) /* tree is unsorted */ + return NULL; + + return add234_internal(t, e, -1); +} +void *addpos234(tree234 *t, void *e, int index) { + if (index < 0 || /* index out of range */ + t->cmp) /* tree is sorted */ + return NULL; /* return failure */ + + return add234_internal(t, e, index); /* this checks the upper bound */ +} + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + */ +void *index234(tree234 *t, int index) { + node234 *n; + + if (!t->root) + return NULL; /* tree is empty */ + + if (index < 0 || index >= countnode234(t->root)) + return NULL; /* out of range */ + + n = t->root; + + while (n) { + if (index < n->counts[0]) + n = n->kids[0]; + else if (index -= n->counts[0] + 1, index < 0) + return n->elems[0]; + else if (index < n->counts[1]) + n = n->kids[1]; + else if (index -= n->counts[1] + 1, index < 0) + return n->elems[1]; + else if (index < n->counts[2]) + n = n->kids[2]; + else if (index -= n->counts[2] + 1, index < 0) + return n->elems[2]; + else + n = n->kids[3]; + } + + /* We shouldn't ever get here. I wonder how we did. */ + return NULL; +} + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + */ +void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, + int relation, int *index) { + node234 *n; + void *ret; + int c; + int idx, ecount, kcount, cmpret; + + if (t->root == NULL) + return NULL; + + if (cmp == NULL) + cmp = t->cmp; + + n = t->root; + /* + * Attempt to find the element itself. + */ + idx = 0; + ecount = -1; + /* + * Prepare a fake `cmp' result if e is NULL. + */ + cmpret = 0; + if (e == NULL) { + assert(relation == REL234_LT || relation == REL234_GT); + if (relation == REL234_LT) + cmpret = +1; /* e is a max: always greater */ + else if (relation == REL234_GT) + cmpret = -1; /* e is a min: always smaller */ + } + while (1) { + for (kcount = 0; kcount < 4; kcount++) { + if (kcount >= 3 || n->elems[kcount] == NULL || + (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) { + break; + } + if (n->kids[kcount]) idx += n->counts[kcount]; + if (c == 0) { + ecount = kcount; + break; + } + idx++; + } + if (ecount >= 0) + break; + if (n->kids[kcount]) + n = n->kids[kcount]; + else + break; + } + + if (ecount >= 0) { + /* + * We have found the element we're looking for. It's + * n->elems[ecount], at tree index idx. If our search + * relation is EQ, LE or GE we can now go home. + */ + if (relation != REL234_LT && relation != REL234_GT) { + if (index) *index = idx; + return n->elems[ecount]; + } + + /* + * Otherwise, we'll do an indexed lookup for the previous + * or next element. (It would be perfectly possible to + * implement these search types in a non-counted tree by + * going back up from where we are, but far more fiddly.) + */ + if (relation == REL234_LT) + idx--; + else + idx++; + } else { + /* + * We've found our way to the bottom of the tree and we + * know where we would insert this node if we wanted to: + * we'd put it in in place of the (empty) subtree + * n->kids[kcount], and it would have index idx + * + * But the actual element isn't there. So if our search + * relation is EQ, we're doomed. + */ + if (relation == REL234_EQ) + return NULL; + + /* + * Otherwise, we must do an index lookup for index idx-1 + * (if we're going left - LE or LT) or index idx (if we're + * going right - GE or GT). + */ + if (relation == REL234_LT || relation == REL234_LE) { + idx--; + } + } + + /* + * We know the index of the element we want; just call index234 + * to do the rest. This will return NULL if the index is out of + * bounds, which is exactly what we want. + */ + ret = index234(t, idx); + if (ret && index) *index = idx; + return ret; +} +void *find234(tree234 *t, void *e, cmpfn234 cmp) { + return findrelpos234(t, e, cmp, REL234_EQ, NULL); +} +void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation) { + return findrelpos234(t, e, cmp, relation, NULL); +} +void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index) { + return findrelpos234(t, e, cmp, REL234_EQ, index); +} + +/* + * Tree transformation used in delete and split: move a subtree + * right, from child ki of a node to the next child. Update k and + * index so that they still point to the same place in the + * transformed tree. Assumes the destination child is not full, and + * that the source child does have a subtree to spare. Can cope if + * the destination child is undersized. + * + * . C . . B . + * / \ -> / \ + * [more] a A b B c d D e [more] a A b c C d D e + * + * . C . . B . + * / \ -> / \ + * [more] a A b B c d [more] a A b c C d + */ +static void trans234_subtree_right(node234 *n, int ki, int *k, int *index) { + node234 *src, *dest; + int i, srclen, adjust; + + src = n->kids[ki]; + dest = n->kids[ki+1]; + + LOG((" trans234_subtree_right(%p, %d):\n", n, ki)); + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + src, + src->kids[0], src->counts[0], src->elems[0], + src->kids[1], src->counts[1], src->elems[1], + src->kids[2], src->counts[2], src->elems[2], + src->kids[3], src->counts[3])); + LOG((" dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + dest, + dest->kids[0], dest->counts[0], dest->elems[0], + dest->kids[1], dest->counts[1], dest->elems[1], + dest->kids[2], dest->counts[2], dest->elems[2], + dest->kids[3], dest->counts[3])); + /* + * Move over the rest of the destination node to make space. + */ + dest->kids[3] = dest->kids[2]; dest->counts[3] = dest->counts[2]; + dest->elems[2] = dest->elems[1]; + dest->kids[2] = dest->kids[1]; dest->counts[2] = dest->counts[1]; + dest->elems[1] = dest->elems[0]; + dest->kids[1] = dest->kids[0]; dest->counts[1] = dest->counts[0]; + + /* which element to move over */ + i = (src->elems[2] ? 2 : src->elems[1] ? 1 : 0); + + dest->elems[0] = n->elems[ki]; + n->elems[ki] = src->elems[i]; + src->elems[i] = NULL; + + dest->kids[0] = src->kids[i+1]; dest->counts[0] = src->counts[i+1]; + src->kids[i+1] = NULL; src->counts[i+1] = 0; + + if (dest->kids[0]) dest->kids[0]->parent = dest; + + adjust = dest->counts[0] + 1; + + n->counts[ki] -= adjust; + n->counts[ki+1] += adjust; + + srclen = n->counts[ki]; + + if (k) { + LOG((" before: k,index = %d,%d\n", (*k), (*index))); + if ((*k) == ki && (*index) > srclen) { + (*index) -= srclen + 1; + (*k)++; + } else if ((*k) == ki+1) { + (*index) += adjust; + } + LOG((" after: k,index = %d,%d\n", (*k), (*index))); + } + + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + src, + src->kids[0], src->counts[0], src->elems[0], + src->kids[1], src->counts[1], src->elems[1], + src->kids[2], src->counts[2], src->elems[2], + src->kids[3], src->counts[3])); + LOG((" dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + dest, + dest->kids[0], dest->counts[0], dest->elems[0], + dest->kids[1], dest->counts[1], dest->elems[1], + dest->kids[2], dest->counts[2], dest->elems[2], + dest->kids[3], dest->counts[3])); +} + +/* + * Tree transformation used in delete and split: move a subtree + * left, from child ki of a node to the previous child. Update k + * and index so that they still point to the same place in the + * transformed tree. Assumes the destination child is not full, and + * that the source child does have a subtree to spare. Can cope if + * the destination child is undersized. + * + * . B . . C . + * / \ -> / \ + * a A b c C d D e [more] a A b B c d D e [more] + * + * . A . . B . + * / \ -> / \ + * a b B c C d [more] a A b c C d [more] + */ +static void trans234_subtree_left(node234 *n, int ki, int *k, int *index) { + node234 *src, *dest; + int i, adjust; + + src = n->kids[ki]; + dest = n->kids[ki-1]; + + LOG((" trans234_subtree_left(%p, %d):\n", n, ki)); + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + dest, + dest->kids[0], dest->counts[0], dest->elems[0], + dest->kids[1], dest->counts[1], dest->elems[1], + dest->kids[2], dest->counts[2], dest->elems[2], + dest->kids[3], dest->counts[3])); + LOG((" src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + src, + src->kids[0], src->counts[0], src->elems[0], + src->kids[1], src->counts[1], src->elems[1], + src->kids[2], src->counts[2], src->elems[2], + src->kids[3], src->counts[3])); + + /* where in dest to put it */ + i = (dest->elems[1] ? 2 : dest->elems[0] ? 1 : 0); + dest->elems[i] = n->elems[ki-1]; + n->elems[ki-1] = src->elems[0]; + + dest->kids[i+1] = src->kids[0]; dest->counts[i+1] = src->counts[0]; + + if (dest->kids[i+1]) dest->kids[i+1]->parent = dest; + + /* + * Move over the rest of the source node. + */ + src->kids[0] = src->kids[1]; src->counts[0] = src->counts[1]; + src->elems[0] = src->elems[1]; + src->kids[1] = src->kids[2]; src->counts[1] = src->counts[2]; + src->elems[1] = src->elems[2]; + src->kids[2] = src->kids[3]; src->counts[2] = src->counts[3]; + src->elems[2] = NULL; + src->kids[3] = NULL; src->counts[3] = 0; + + adjust = dest->counts[i+1] + 1; + + n->counts[ki] -= adjust; + n->counts[ki-1] += adjust; + + if (k) { + LOG((" before: k,index = %d,%d\n", (*k), (*index))); + if ((*k) == ki) { + (*index) -= adjust; + if ((*index) < 0) { + (*index) += n->counts[ki-1] + 1; + (*k)--; + } + } + LOG((" after: k,index = %d,%d\n", (*k), (*index))); + } + + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + dest, + dest->kids[0], dest->counts[0], dest->elems[0], + dest->kids[1], dest->counts[1], dest->elems[1], + dest->kids[2], dest->counts[2], dest->elems[2], + dest->kids[3], dest->counts[3])); + LOG((" src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + src, + src->kids[0], src->counts[0], src->elems[0], + src->kids[1], src->counts[1], src->elems[1], + src->kids[2], src->counts[2], src->elems[2], + src->kids[3], src->counts[3])); +} + +/* + * Tree transformation used in delete and split: merge child nodes + * ki and ki+1 of a node. Update k and index so that they still + * point to the same place in the transformed tree. Assumes both + * children _are_ sufficiently small. + * + * . B . . + * / \ -> | + * a A b c C d a A b B c C d + * + * This routine can also cope with either child being undersized: + * + * . A . . + * / \ -> | + * a b B c a A b B c + * + * . A . . + * / \ -> | + * a b B c C d a A b B c C d + */ +static void trans234_subtree_merge(node234 *n, int ki, int *k, int *index) { + node234 *left, *right; + int i, leftlen, rightlen, lsize, rsize; + + left = n->kids[ki]; leftlen = n->counts[ki]; + right = n->kids[ki+1]; rightlen = n->counts[ki+1]; + + LOG((" trans234_subtree_merge(%p, %d):\n", n, ki)); + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" left %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + left, + left->kids[0], left->counts[0], left->elems[0], + left->kids[1], left->counts[1], left->elems[1], + left->kids[2], left->counts[2], left->elems[2], + left->kids[3], left->counts[3])); + LOG((" right %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + right, + right->kids[0], right->counts[0], right->elems[0], + right->kids[1], right->counts[1], right->elems[1], + right->kids[2], right->counts[2], right->elems[2], + right->kids[3], right->counts[3])); + + assert(!left->elems[2] && !right->elems[2]); /* neither is large! */ + lsize = (left->elems[1] ? 2 : left->elems[0] ? 1 : 0); + rsize = (right->elems[1] ? 2 : right->elems[0] ? 1 : 0); + + left->elems[lsize] = n->elems[ki]; + + for (i = 0; i < rsize+1; i++) { + left->kids[lsize+1+i] = right->kids[i]; + left->counts[lsize+1+i] = right->counts[i]; + if (left->kids[lsize+1+i]) + left->kids[lsize+1+i]->parent = left; + if (i < rsize) + left->elems[lsize+1+i] = right->elems[i]; + } + + n->counts[ki] += rightlen + 1; + + sfree(right); + + /* + * Move the rest of n up by one. + */ + for (i = ki+1; i < 3; i++) { + n->kids[i] = n->kids[i+1]; + n->counts[i] = n->counts[i+1]; + } + for (i = ki; i < 2; i++) { + n->elems[i] = n->elems[i+1]; + } + n->kids[3] = NULL; + n->counts[3] = 0; + n->elems[2] = NULL; + + if (k) { + LOG((" before: k,index = %d,%d\n", (*k), (*index))); + if ((*k) == ki+1) { + (*k)--; + (*index) += leftlen + 1; + } else if ((*k) > ki+1) { + (*k)--; + } + LOG((" after: k,index = %d,%d\n", (*k), (*index))); + } + + LOG((" parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" merged %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + left, + left->kids[0], left->counts[0], left->elems[0], + left->kids[1], left->counts[1], left->elems[1], + left->kids[2], left->counts[2], left->elems[2], + left->kids[3], left->counts[3])); + +} + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + */ +static void *delpos234_internal(tree234 *t, int index) { + node234 *n; + void *retval; + int ki, i; + + retval = NULL; + + n = t->root; /* by assumption this is non-NULL */ + LOG(("deleting item %d from tree %p\n", index, t)); + while (1) { + node234 *sub; + + LOG((" node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d index=%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3], + index)); + if (index <= n->counts[0]) { + ki = 0; + } else if (index -= n->counts[0]+1, index <= n->counts[1]) { + ki = 1; + } else if (index -= n->counts[1]+1, index <= n->counts[2]) { + ki = 2; + } else if (index -= n->counts[2]+1, index <= n->counts[3]) { + ki = 3; + } else { + assert(0); /* can't happen */ + } + + if (!n->kids[0]) + break; /* n is a leaf node; we're here! */ + + /* + * Check to see if we've found our target element. If so, + * we must choose a new target (we'll use the old target's + * successor, which will be in a leaf), move it into the + * place of the old one, continue down to the leaf and + * delete the old copy of the new target. + */ + if (index == n->counts[ki]) { + node234 *m; + LOG((" found element in internal node, index %d\n", ki)); + assert(n->elems[ki]); /* must be a kid _before_ an element */ + ki++; index = 0; + for (m = n->kids[ki]; m->kids[0]; m = m->kids[0]) + continue; + LOG((" replacing with element \"%s\" from leaf node %p\n", + m->elems[0], m)); + retval = n->elems[ki-1]; + n->elems[ki-1] = m->elems[0]; + } + + /* + * Recurse down to subtree ki. If it has only one element, + * we have to do some transformation to start with. + */ + LOG((" moving to subtree %d\n", ki)); + sub = n->kids[ki]; + if (!sub->elems[1]) { + LOG((" subtree has only one element!\n")); + if (ki > 0 && n->kids[ki-1]->elems[1]) { + /* + * Child ki has only one element, but child + * ki-1 has two or more. So we need to move a + * subtree from ki-1 to ki. + */ + trans234_subtree_right(n, ki-1, &ki, &index); + } else if (ki < 3 && n->kids[ki+1] && + n->kids[ki+1]->elems[1]) { + /* + * Child ki has only one element, but ki+1 has + * two or more. Move a subtree from ki+1 to ki. + */ + trans234_subtree_left(n, ki+1, &ki, &index); + } else { + /* + * ki is small with only small neighbours. Pick a + * neighbour and merge with it. + */ + trans234_subtree_merge(n, ki>0 ? ki-1 : ki, &ki, &index); + sub = n->kids[ki]; + + if (!n->elems[0]) { + /* + * The root is empty and needs to be + * removed. + */ + LOG((" shifting root!\n")); + t->root = sub; + sub->parent = NULL; + sfree(n); + n = NULL; + } + } + } + + if (n) + n->counts[ki]--; + n = sub; + } + + /* + * Now n is a leaf node, and ki marks the element number we + * want to delete. We've already arranged for the leaf to be + * bigger than minimum size, so let's just go to it. + */ + assert(!n->kids[0]); + if (!retval) + retval = n->elems[ki]; + + for (i = ki; i < 2 && n->elems[i+1]; i++) + n->elems[i] = n->elems[i+1]; + n->elems[i] = NULL; + + /* + * It's just possible that we have reduced the leaf to zero + * size. This can only happen if it was the root - so destroy + * it and make the tree empty. + */ + if (!n->elems[0]) { + LOG((" removed last element in tree, destroying empty root\n")); + assert(n == t->root); + sfree(n); + t->root = NULL; + } + + return retval; /* finished! */ +} +void *delpos234(tree234 *t, int index) { + if (index < 0 || index >= countnode234(t->root)) + return NULL; + return delpos234_internal(t, index); +} +void *del234(tree234 *t, void *e) { + int index; + if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) + return NULL; /* it wasn't in there anyway */ + return delpos234_internal(t, index); /* it's there; delete it. */ +} + +/* + * Join two subtrees together with a separator element between + * them, given their relative height. + * + * (Height<0 means the left tree is shorter, >0 means the right + * tree is shorter, =0 means (duh) they're equal.) + * + * It is assumed that any checks needed on the ordering criterion + * have _already_ been done. + * + * The value returned in `height' is 0 or 1 depending on whether the + * resulting tree is the same height as the original larger one, or + * one higher. + */ +static node234 *join234_internal(node234 *left, void *sep, + node234 *right, int *height) { + node234 *root, *node; + int relht = *height; + int ki; + + LOG((" join: joining %p \"%s\" %p, relative height is %d\n", + left, sep, right, relht)); + if (relht == 0) { + /* + * The trees are the same height. Create a new one-element + * root containing the separator and pointers to the two + * nodes. + */ + node234 *newroot; + newroot = snew(node234); + newroot->kids[0] = left; newroot->counts[0] = countnode234(left); + newroot->elems[0] = sep; + newroot->kids[1] = right; newroot->counts[1] = countnode234(right); + newroot->elems[1] = NULL; + newroot->kids[2] = NULL; newroot->counts[2] = 0; + newroot->elems[2] = NULL; + newroot->kids[3] = NULL; newroot->counts[3] = 0; + newroot->parent = NULL; + if (left) left->parent = newroot; + if (right) right->parent = newroot; + *height = 1; + LOG((" join: same height, brand new root\n")); + return newroot; + } + + /* + * This now works like the addition algorithm on the larger + * tree. We're replacing a single kid pointer with two kid + * pointers separated by an element; if that causes the node to + * overload, we split it in two, move a separator element up to + * the next node, and repeat. + */ + if (relht < 0) { + /* + * Left tree is shorter. Search down the right tree to find + * the pointer we're inserting at. + */ + node = root = right; + while (++relht < 0) { + node = node->kids[0]; + } + ki = 0; + right = node->kids[ki]; + } else { + /* + * Right tree is shorter; search down the left to find the + * pointer we're inserting at. + */ + node = root = left; + while (--relht > 0) { + if (node->elems[2]) + node = node->kids[3]; + else if (node->elems[1]) + node = node->kids[2]; + else + node = node->kids[1]; + } + if (node->elems[2]) + ki = 3; + else if (node->elems[1]) + ki = 2; + else + ki = 1; + left = node->kids[ki]; + } + + /* + * Now proceed as for addition. + */ + *height = add234_insert(left, sep, right, &root, node, ki); + + return root; +} +static int height234(tree234 *t) { + int level = 0; + node234 *n = t->root; + while (n) { + level++; + n = n->kids[0]; + } + return level; +} +tree234 *join234(tree234 *t1, tree234 *t2) { + int size2 = countnode234(t2->root); + if (size2 > 0) { + void *element; + int relht; + + if (t1->cmp) { + element = index234(t2, 0); + element = findrelpos234(t1, element, NULL, REL234_GE, NULL); + if (element) + return NULL; + } + + element = delpos234(t2, 0); + relht = height234(t1) - height234(t2); + t1->root = join234_internal(t1->root, element, t2->root, &relht); + t2->root = NULL; + } + return t1; +} +tree234 *join234r(tree234 *t1, tree234 *t2) { + int size1 = countnode234(t1->root); + if (size1 > 0) { + void *element; + int relht; + + if (t2->cmp) { + element = index234(t1, size1-1); + element = findrelpos234(t2, element, NULL, REL234_LE, NULL); + if (element) + return NULL; + } + + element = delpos234(t1, size1-1); + relht = height234(t1) - height234(t2); + t2->root = join234_internal(t1->root, element, t2->root, &relht); + t1->root = NULL; + } + return t2; +} + +/* + * Split out the first elements in a tree and return a + * pointer to the root node. Leave the root node of the remainder + * in t. + */ +static node234 *split234_internal(tree234 *t, int index) { + node234 *halves[2] = { NULL, NULL }, *n, *sib, *sub; + node234 *lparent, *rparent; + int ki, pki, i, half, lcount, rcount; + + n = t->root; + LOG(("splitting tree %p at point %d\n", t, index)); + + /* + * Easy special cases. After this we have also dealt completely + * with the empty-tree case and we can assume the root exists. + */ + if (index == 0) /* return nothing */ + return NULL; + if (index == countnode234(t->root)) { /* return the whole tree */ + node234 *ret = t->root; + t->root = NULL; + return ret; + } + + /* + * Search down the tree to find the split point. + */ + halves[0] = halves[1] = NULL; + lparent = rparent = NULL; + pki = -1; + while (n) { + LOG((" node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d index=%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3], + index)); + lcount = index; + rcount = countnode234(n) - lcount; + if (index <= n->counts[0]) { + ki = 0; + } else if (index -= n->counts[0]+1, index <= n->counts[1]) { + ki = 1; + } else if (index -= n->counts[1]+1, index <= n->counts[2]) { + ki = 2; + } else { + index -= n->counts[2]+1; + ki = 3; + } + + LOG((" splitting at subtree %d\n", ki)); + sub = n->kids[ki]; + + LOG((" splitting at child index %d\n", ki)); + + /* + * Split the node, put halves[0] on the right of the left + * one and halves[1] on the left of the right one, put the + * new node pointers in halves[0] and halves[1], and go up + * a level. + */ + sib = snew(node234); + for (i = 0; i < 3; i++) { + if (i+ki < 3 && n->elems[i+ki]) { + sib->elems[i] = n->elems[i+ki]; + sib->kids[i+1] = n->kids[i+ki+1]; + if (sib->kids[i+1]) sib->kids[i+1]->parent = sib; + sib->counts[i+1] = n->counts[i+ki+1]; + n->elems[i+ki] = NULL; + n->kids[i+ki+1] = NULL; + n->counts[i+ki+1] = 0; + } else { + sib->elems[i] = NULL; + sib->kids[i+1] = NULL; + sib->counts[i+1] = 0; + } + } + if (lparent) { + lparent->kids[pki] = n; + lparent->counts[pki] = lcount; + n->parent = lparent; + rparent->kids[0] = sib; + rparent->counts[0] = rcount; + sib->parent = rparent; + } else { + halves[0] = n; + n->parent = NULL; + halves[1] = sib; + sib->parent = NULL; + } + lparent = n; + rparent = sib; + pki = ki; + LOG((" left node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" right node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + sib, + sib->kids[0], sib->counts[0], sib->elems[0], + sib->kids[1], sib->counts[1], sib->elems[1], + sib->kids[2], sib->counts[2], sib->elems[2], + sib->kids[3], sib->counts[3])); + + n = sub; + } + + /* + * We've come off the bottom here, so we've successfully split + * the tree into two equally high subtrees. The only problem is + * that some of the nodes down the fault line will be smaller + * than the minimum permitted size. (Since this is a 2-3-4 + * tree, that means they'll be zero-element one-child nodes.) + */ + LOG((" fell off bottom, lroot is %p, rroot is %p\n", + halves[0], halves[1])); + assert(halves[0] != NULL); + assert(halves[1] != NULL); + lparent->counts[pki] = rparent->counts[0] = 0; + lparent->kids[pki] = rparent->kids[0] = NULL; + + /* + * So now we go back down the tree from each of the two roots, + * fixing up undersize nodes. + */ + for (half = 0; half < 2; half++) { + /* + * Remove the root if it's undersize (it will contain only + * one child pointer, so just throw it away and replace it + * with its child). This might happen several times. + */ + while (halves[half] && !halves[half]->elems[0]) { + LOG((" root %p is undersize, throwing away\n", halves[half])); + halves[half] = halves[half]->kids[0]; + sfree(halves[half]->parent); + halves[half]->parent = NULL; + LOG((" new root is %p\n", halves[half])); + } + + n = halves[half]; + while (n) { + void (*toward)(node234 *n, int ki, int *k, int *index); + int ni, merge; + + /* + * Now we have a potentially undersize node on the + * right (if half==0) or left (if half==1). Sort it + * out, by merging with a neighbour or by transferring + * subtrees over. At this time we must also ensure that + * nodes are bigger than minimum, in case we need an + * element to merge two nodes below. + */ + LOG((" node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + if (half == 1) { + ki = 0; /* the kid we're interested in */ + ni = 1; /* the neighbour */ + merge = 0; /* for merge: leftmost of the two */ + toward = trans234_subtree_left; + } else { + ki = (n->kids[3] ? 3 : n->kids[2] ? 2 : 1); + ni = ki-1; + merge = ni; + toward = trans234_subtree_right; + } + + sub = n->kids[ki]; + if (sub && !sub->elems[1]) { + /* + * This node is undersized or minimum-size. If we + * can merge it with its neighbour, we do so; + * otherwise we must be able to transfer subtrees + * over to it until it is greater than minimum + * size. + */ + int undersized = (!sub->elems[0]); + LOG((" child %d is %ssize\n", ki, + undersized ? "under" : "minimum-")); + LOG((" neighbour is %s\n", + n->kids[ni]->elems[2] ? "large" : + n->kids[ni]->elems[1] ? "medium" : "small")); + if (!n->kids[ni]->elems[1] || + (undersized && !n->kids[ni]->elems[2])) { + /* + * Neighbour is small, or possibly neighbour is + * medium and we are undersize. + */ + trans234_subtree_merge(n, merge, NULL, NULL); + sub = n->kids[merge]; + if (!n->elems[0]) { + /* + * n is empty, and hence must have been the + * root and needs to be removed. + */ + assert(!n->parent); + LOG((" shifting root!\n")); + halves[half] = sub; + halves[half]->parent = NULL; + sfree(n); + } + } else { + /* Neighbour is big enough to move trees over. */ + toward(n, ni, NULL, NULL); + if (undersized) + toward(n, ni, NULL, NULL); + } + } + n = sub; + } + } + + t->root = halves[1]; + return halves[0]; +} +tree234 *splitpos234(tree234 *t, int index, int before) { + tree234 *ret; + node234 *n; + int count; + + count = countnode234(t->root); + if (index < 0 || index > count) + return NULL; /* error */ + ret = newtree234(t->cmp); + n = split234_internal(t, index); + if (before) { + /* We want to return the ones before the index. */ + ret->root = n; + } else { + /* + * We want to keep the ones before the index and return the + * ones after. + */ + ret->root = t->root; + t->root = n; + } + return ret; +} +tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel) { + int before; + int index; + + assert(rel != REL234_EQ); + + if (rel == REL234_GT || rel == REL234_GE) { + before = 1; + rel = (rel == REL234_GT ? REL234_LE : REL234_LT); + } else { + before = 0; + } + if (!findrelpos234(t, e, cmp, rel, &index)) + index = 0; + + return splitpos234(t, index+1, before); +} + +static node234 *copynode234(node234 *n, copyfn234 copyfn, void *copyfnstate) { + int i; + node234 *n2 = snew(node234); + + for (i = 0; i < 3; i++) { + if (n->elems[i] && copyfn) + n2->elems[i] = copyfn(copyfnstate, n->elems[i]); + else + n2->elems[i] = n->elems[i]; + } + + for (i = 0; i < 4; i++) { + if (n->kids[i]) { + n2->kids[i] = copynode234(n->kids[i], copyfn, copyfnstate); + n2->kids[i]->parent = n2; + } else { + n2->kids[i] = NULL; + } + n2->counts[i] = n->counts[i]; + } + + return n2; +} +tree234 *copytree234(tree234 *t, copyfn234 copyfn, void *copyfnstate) { + tree234 *t2; + + t2 = newtree234(t->cmp); + if (t->root) { + t2->root = copynode234(t->root, copyfn, copyfnstate); + t2->root->parent = NULL; + } else + t2->root = NULL; + + return t2; +} + +#ifdef TEST + +/* + * Test code for the 2-3-4 tree. This code maintains an alternative + * representation of the data in the tree, in an array (using the + * obvious and slow insert and delete functions). After each tree + * operation, the verify() function is called, which ensures all + * the tree properties are preserved: + * - node->child->parent always equals node + * - tree->root->parent always equals NULL + * - number of kids == 0 or number of elements + 1; + * - tree has the same depth everywhere + * - every node has at least one element + * - subtree element counts are accurate + * - any NULL kid pointer is accompanied by a zero count + * - in a sorted tree: ordering property between elements of a + * node and elements of its children is preserved + * and also ensures the list represented by the tree is the same + * list it should be. (This last check also doubly verifies the + * ordering properties, because the `same list it should be' is by + * definition correctly ordered. It also ensures all nodes are + * distinct, because the enum functions would get caught in a loop + * if not.) + */ + +#include +#include + +#define srealloc realloc + +/* + * Error reporting function. + */ +void error(char *fmt, ...) { + va_list ap; + printf("ERROR: "); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf("\n"); +} + +/* The array representation of the data. */ +void **array; +int arraylen, arraysize; +cmpfn234 cmp; + +/* The tree representation of the same data. */ +tree234 *tree; + +/* + * Routines to provide a diagnostic printout of a tree. Currently + * relies on every element in the tree being a one-character string + * :-) + */ +typedef struct { + char **levels; +} dispctx; + +int dispnode(node234 *n, int level, dispctx *ctx) { + if (level == 0) { + int xpos = strlen(ctx->levels[0]); + int len; + + if (n->elems[2]) + len = sprintf(ctx->levels[0]+xpos, " %s%s%s", + n->elems[0], n->elems[1], n->elems[2]); + else if (n->elems[1]) + len = sprintf(ctx->levels[0]+xpos, " %s%s", + n->elems[0], n->elems[1]); + else + len = sprintf(ctx->levels[0]+xpos, " %s", + n->elems[0]); + return xpos + 1 + (len-1) / 2; + } else { + int xpos[4], nkids; + int nodelen, mypos, myleft, x, i; + + xpos[0] = dispnode(n->kids[0], level-3, ctx); + xpos[1] = dispnode(n->kids[1], level-3, ctx); + nkids = 2; + if (n->kids[2]) { + xpos[2] = dispnode(n->kids[2], level-3, ctx); + nkids = 3; + } + if (n->kids[3]) { + xpos[3] = dispnode(n->kids[3], level-3, ctx); + nkids = 4; + } + + if (nkids == 4) + mypos = (xpos[1] + xpos[2]) / 2; + else if (nkids == 3) + mypos = xpos[1]; + else + mypos = (xpos[0] + xpos[1]) / 2; + nodelen = nkids * 2 - 1; + myleft = mypos - ((nodelen-1)/2); + assert(myleft >= xpos[0]); + assert(myleft + nodelen-1 <= xpos[nkids-1]); + + x = strlen(ctx->levels[level]); + while (x <= xpos[0] && x < myleft) + ctx->levels[level][x++] = ' '; + while (x < myleft) + ctx->levels[level][x++] = '_'; + if (nkids==4) + x += sprintf(ctx->levels[level]+x, ".%s.%s.%s.", + n->elems[0], n->elems[1], n->elems[2]); + else if (nkids==3) + x += sprintf(ctx->levels[level]+x, ".%s.%s.", + n->elems[0], n->elems[1]); + else + x += sprintf(ctx->levels[level]+x, ".%s.", + n->elems[0]); + while (x < xpos[nkids-1]) + ctx->levels[level][x++] = '_'; + ctx->levels[level][x] = '\0'; + + x = strlen(ctx->levels[level-1]); + for (i = 0; i < nkids; i++) { + int rpos, pos; + rpos = xpos[i]; + if (i > 0 && i < nkids-1) + pos = myleft + 2*i; + else + pos = rpos; + if (rpos < pos) + rpos++; + while (x < pos && x < rpos) + ctx->levels[level-1][x++] = ' '; + if (x == pos) + ctx->levels[level-1][x++] = '|'; + while (x < pos || x < rpos) + ctx->levels[level-1][x++] = '_'; + if (x == pos) + ctx->levels[level-1][x++] = '|'; + } + ctx->levels[level-1][x] = '\0'; + + x = strlen(ctx->levels[level-2]); + for (i = 0; i < nkids; i++) { + int rpos = xpos[i]; + + while (x < rpos) + ctx->levels[level-2][x++] = ' '; + ctx->levels[level-2][x++] = '|'; + } + ctx->levels[level-2][x] = '\0'; + + return mypos; + } +} + +void disptree(tree234 *t) { + dispctx ctx; + char *leveldata; + int width = count234(t); + int ht = height234(t) * 3 - 2; + int i; + + if (!t->root) { + printf("[empty tree]\n"); + } + + leveldata = smalloc(ht * (width+2)); + ctx.levels = smalloc(ht * sizeof(char *)); + for (i = 0; i < ht; i++) { + ctx.levels[i] = leveldata + i * (width+2); + ctx.levels[i][0] = '\0'; + } + + (void) dispnode(t->root, ht-1, &ctx); + + for (i = ht; i-- ;) + printf("%s\n", ctx.levels[i]); + + sfree(ctx.levels); + sfree(leveldata); +} + +typedef struct { + int treedepth; + int elemcount; +} chkctx; + +int chknode(chkctx *ctx, int level, node234 *node, + void *lowbound, void *highbound) { + int nkids, nelems; + int i; + int count; + + /* Count the non-NULL kids. */ + for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++); + /* Ensure no kids beyond the first NULL are non-NULL. */ + for (i = nkids; i < 4; i++) + if (node->kids[i]) { + error("node %p: nkids=%d but kids[%d] non-NULL", + node, nkids, i); + } else if (node->counts[i]) { + error("node %p: kids[%d] NULL but count[%d]=%d nonzero", + node, i, i, node->counts[i]); + } + + /* Count the non-NULL elements. */ + for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++); + /* Ensure no elements beyond the first NULL are non-NULL. */ + for (i = nelems; i < 3; i++) + if (node->elems[i]) { + error("node %p: nelems=%d but elems[%d] non-NULL", + node, nelems, i); + } + + if (nkids == 0) { + /* + * If nkids==0, this is a leaf node; verify that the tree + * depth is the same everywhere. + */ + if (ctx->treedepth < 0) + ctx->treedepth = level; /* we didn't know the depth yet */ + else if (ctx->treedepth != level) + error("node %p: leaf at depth %d, previously seen depth %d", + node, level, ctx->treedepth); + } else { + /* + * If nkids != 0, then it should be nelems+1, unless nelems + * is 0 in which case nkids should also be 0 (and so we + * shouldn't be in this condition at all). + */ + int shouldkids = (nelems ? nelems+1 : 0); + if (nkids != shouldkids) { + error("node %p: %d elems should mean %d kids but has %d", + node, nelems, shouldkids, nkids); + } + } + + /* + * nelems should be at least 1. + */ + if (nelems == 0) { + error("node %p: no elems", node, nkids); + } + + /* + * Add nelems to the running element count of the whole tree. + */ + ctx->elemcount += nelems; + + /* + * Check ordering property: all elements should be strictly > + * lowbound, strictly < highbound, and strictly < each other in + * sequence. (lowbound and highbound are NULL at edges of tree + * - both NULL at root node - and NULL is considered to be < + * everything and > everything. IYSWIM.) + */ + if (cmp) { + for (i = -1; i < nelems; i++) { + void *lower = (i == -1 ? lowbound : node->elems[i]); + void *higher = (i+1 == nelems ? highbound : node->elems[i+1]); + if (lower && higher && cmp(lower, higher) >= 0) { + error("node %p: kid comparison [%d=%s,%d=%s] failed", + node, i, lower, i+1, higher); + } + } + } + + /* + * Check parent pointers: all non-NULL kids should have a + * parent pointer coming back to this node. + */ + for (i = 0; i < nkids; i++) + if (node->kids[i]->parent != node) { + error("node %p kid %d: parent ptr is %p not %p", + node, i, node->kids[i]->parent, node); + } + + + /* + * Now (finally!) recurse into subtrees. + */ + count = nelems; + + for (i = 0; i < nkids; i++) { + void *lower = (i == 0 ? lowbound : node->elems[i-1]); + void *higher = (i >= nelems ? highbound : node->elems[i]); + int subcount = chknode(ctx, level+1, node->kids[i], lower, higher); + if (node->counts[i] != subcount) { + error("node %p kid %d: count says %d, subtree really has %d", + node, i, node->counts[i], subcount); + } + count += subcount; + } + + return count; +} + +void verifytree(tree234 *tree, void **array, int arraylen) { + chkctx ctx; + int i; + void *p; + + ctx.treedepth = -1; /* depth unknown yet */ + ctx.elemcount = 0; /* no elements seen yet */ + /* + * Verify validity of tree properties. + */ + if (tree->root) { + if (tree->root->parent != NULL) + error("root->parent is %p should be null", tree->root->parent); + chknode(&ctx, 0, tree->root, NULL, NULL); + } + printf("tree depth: %d\n", ctx.treedepth); + /* + * Enumerate the tree and ensure it matches up to the array. + */ + for (i = 0; NULL != (p = index234(tree, i)); i++) { + if (i >= arraylen) + error("tree contains more than %d elements", arraylen); + if (array[i] != p) + error("enum at position %d: array says %s, tree says %s", + i, array[i], p); + } + if (ctx.elemcount != i) { + error("tree really contains %d elements, enum gave %d", + ctx.elemcount, i); + } + if (i < arraylen) { + error("enum gave only %d elements, array has %d", i, arraylen); + } + i = count234(tree); + if (ctx.elemcount != i) { + error("tree really contains %d elements, count234 gave %d", + ctx.elemcount, i); + } +} +void verify(void) { verifytree(tree, array, arraylen); } + +void internal_addtest(void *elem, int index, void *realret) { + int i, j; + void *retval; + + if (arraysize < arraylen+1) { + arraysize = arraylen+1+256; + array = (array == NULL ? smalloc(arraysize*sizeof(*array)) : + srealloc(array, arraysize*sizeof(*array))); + } + + i = index; + /* now i points to the first element >= elem */ + retval = elem; /* expect elem returned (success) */ + for (j = arraylen; j > i; j--) + array[j] = array[j-1]; + array[i] = elem; /* add elem to array */ + arraylen++; + + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + + verify(); +} + +void addtest(void *elem) { + int i; + void *realret; + + realret = add234(tree, elem); + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i < arraylen && !cmp(elem, array[i])) { + void *retval = array[i]; /* expect that returned not elem */ + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + } else + internal_addtest(elem, i, realret); +} + +void addpostest(void *elem, int i) { + void *realret; + + realret = addpos234(tree, elem, i); + + internal_addtest(elem, i, realret); +} + +void delpostest(int i) { + int index = i; + void *elem = array[i], *ret; + + /* i points to the right element */ + while (i < arraylen-1) { + array[i] = array[i+1]; + i++; + } + arraylen--; /* delete elem from array */ + + if (tree->cmp) + ret = del234(tree, elem); + else + ret = delpos234(tree, index); + + if (ret != elem) { + error("del returned %p, expected %p", ret, elem); + } + + verify(); +} + +void deltest(void *elem) { + int i; + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i >= arraylen || cmp(elem, array[i]) != 0) + return; /* don't do it! */ + delpostest(i); +} + +/* A sample data set and test utility. Designed for pseudo-randomness, + * and yet repeatability. */ + +/* + * This random number generator uses the `portable implementation' + * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; + * change it if not. + */ +int randomnumber(unsigned *seed) { + *seed *= 1103515245; + *seed += 12345; + return ((*seed) / 65536) % 32768; +} + +int mycmp(void *av, void *bv) { + char const *a = (char const *)av; + char const *b = (char const *)bv; + return strcmp(a, b); +} + +char *strings[] = { + "0", "2", "3", "I", "K", "d", "H", "J", "Q", "N", "n", "q", "j", "i", + "7", "G", "F", "D", "b", "x", "g", "B", "e", "v", "V", "T", "f", "E", + "S", "8", "A", "k", "X", "p", "C", "R", "a", "o", "r", "O", "Z", "u", + "6", "1", "w", "L", "P", "M", "c", "U", "h", "9", "t", "5", "W", "Y", + "m", "s", "l", "4", +#if 0 + "a", "ab", "absque", "coram", "de", + "palam", "clam", "cum", "ex", "e", + "sine", "tenus", "pro", "prae", + "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", + "penguin", "blancmange", "pangolin", "whale", "hedgehog", + "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", + "murfl", "spoo", "breen", "flarn", "octothorpe", + "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", + "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", + "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", + "wand", "ring", "amulet" +#endif +}; + +#define NSTR lenof(strings) + +void findtest(void) { + static const int rels[] = { + REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT + }; + static const char *const relnames[] = { + "EQ", "GE", "LE", "LT", "GT" + }; + int i, j, rel, index; + char *p, *ret, *realret, *realret2; + int lo, hi, mid, c; + + for (i = 0; i < (int)NSTR; i++) { + p = strings[i]; + for (j = 0; j < (int)(sizeof(rels)/sizeof(*rels)); j++) { + rel = rels[j]; + + lo = 0; hi = arraylen-1; + while (lo <= hi) { + mid = (lo + hi) / 2; + c = strcmp(p, array[mid]); + if (c < 0) + hi = mid-1; + else if (c > 0) + lo = mid+1; + else + break; + } + + if (c == 0) { + if (rel == REL234_LT) + ret = (mid > 0 ? array[--mid] : NULL); + else if (rel == REL234_GT) + ret = (mid < arraylen-1 ? array[++mid] : NULL); + else + ret = array[mid]; + } else { + assert(lo == hi+1); + if (rel == REL234_LT || rel == REL234_LE) { + mid = hi; + ret = (hi >= 0 ? array[hi] : NULL); + } else if (rel == REL234_GT || rel == REL234_GE) { + mid = lo; + ret = (lo < arraylen ? array[lo] : NULL); + } else + ret = NULL; + } + + realret = findrelpos234(tree, p, NULL, rel, &index); + if (realret != ret) { + error("find(\"%s\",%s) gave %s should be %s", + p, relnames[j], realret, ret); + } + if (realret && index != mid) { + error("find(\"%s\",%s) gave %d should be %d", + p, relnames[j], index, mid); + } + if (realret && rel == REL234_EQ) { + realret2 = index234(tree, index); + if (realret2 != realret) { + error("find(\"%s\",%s) gave %s(%d) but %d -> %s", + p, relnames[j], realret, index, index, realret2); + } + } +#if 0 + printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], + realret, index); +#endif + } + } + + realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index); + if (arraylen && (realret != array[0] || index != 0)) { + error("find(NULL,GT) gave %s(%d) should be %s(0)", + realret, index, array[0]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,GT) gave %s(%d) should be NULL", + realret, index); + } + + realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index); + if (arraylen && (realret != array[arraylen-1] || index != arraylen-1)) { + error("find(NULL,LT) gave %s(%d) should be %s(0)", + realret, index, array[arraylen-1]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,LT) gave %s(%d) should be NULL", + realret, index); + } +} + +void splittest(tree234 *tree, void **array, int arraylen) { + int i; + tree234 *tree3, *tree4; + for (i = 0; i <= arraylen; i++) { + tree3 = copytree234(tree, NULL, NULL); + tree4 = splitpos234(tree3, i, 0); + verifytree(tree3, array, i); + verifytree(tree4, array+i, arraylen-i); + join234(tree3, tree4); + freetree234(tree4); /* left empty by join */ + verifytree(tree3, array, arraylen); + freetree234(tree3); + } +} + +int main(void) { + int in[NSTR]; + int i, j, k; + int tworoot, tmplen; + unsigned seed = 0; + tree234 *tree2, *tree3, *tree4; + int c; + + setvbuf(stdout, NULL, _IOLBF, 0); + + for (i = 0; i < (int)NSTR; i++) in[i] = 0; + array = NULL; + arraylen = arraysize = 0; + tree = newtree234(mycmp); + cmp = mycmp; + + verify(); + for (i = 0; i < 10000; i++) { + j = randomnumber(&seed); + j %= NSTR; + printf("trial: %d\n", i); + if (in[j]) { + printf("deleting %s (%d)\n", strings[j], j); + deltest(strings[j]); + in[j] = 0; + } else { + printf("adding %s (%d)\n", strings[j], j); + addtest(strings[j]); + in[j] = 1; + } + disptree(tree); + findtest(); + } + + while (arraylen > 0) { + j = randomnumber(&seed); + j %= arraylen; + deltest(array[j]); + } + + freetree234(tree); + + /* + * Now try an unsorted tree. We don't really need to test + * delpos234 because we know del234 is based on it, so it's + * already been tested in the above sorted-tree code; but for + * completeness we'll use it to tear down our unsorted tree + * once we've built it. + */ + tree = newtree234(NULL); + cmp = NULL; + verify(); + for (i = 0; i < 1000; i++) { + printf("trial: %d\n", i); + j = randomnumber(&seed); + j %= NSTR; + k = randomnumber(&seed); + k %= count234(tree)+1; + printf("adding string %s at index %d\n", strings[j], k); + addpostest(strings[j], k); + } + + /* + * While we have this tree in its full form, we'll take a copy + * of it to use in split and join testing. + */ + tree2 = copytree234(tree, NULL, NULL); + verifytree(tree2, array, arraylen);/* check the copy is accurate */ + /* + * Split tests. Split the tree at every possible point and + * check the resulting subtrees. + */ + tworoot = (!tree2->root->elems[1]);/* see if it has a 2-root */ + splittest(tree2, array, arraylen); + /* + * Now do the split test again, but on a tree that has a 2-root + * (if the previous one didn't) or doesn't (if the previous one + * did). + */ + tmplen = arraylen; + while ((!tree2->root->elems[1]) == tworoot) { + delpos234(tree2, --tmplen); + } + printf("now trying splits on second tree\n"); + splittest(tree2, array, tmplen); + freetree234(tree2); + + /* + * Back to the main testing of uncounted trees. + */ + while (count234(tree) > 0) { + printf("cleanup: tree size %d\n", count234(tree)); + j = randomnumber(&seed); + j %= count234(tree); + printf("deleting string %s from index %d\n", (char *)array[j], j); + delpostest(j); + } + freetree234(tree); + + /* + * Finally, do some testing on split/join on _sorted_ trees. At + * the same time, we'll be testing split on very small trees. + */ + tree = newtree234(mycmp); + cmp = mycmp; + arraylen = 0; + for (i = 0; i < 17; i++) { + tree2 = copytree234(tree, NULL, NULL); + splittest(tree2, array, arraylen); + freetree234(tree2); + if (i < 16) + addtest(strings[i]); + } + freetree234(tree); + + /* + * Test silly cases of join: join(emptytree, emptytree), and + * also ensure join correctly spots when sorted trees fail the + * ordering constraint. + */ + tree = newtree234(mycmp); + tree2 = newtree234(mycmp); + tree3 = newtree234(mycmp); + tree4 = newtree234(mycmp); + assert(mycmp(strings[0], strings[1]) < 0); /* just in case :-) */ + add234(tree2, strings[1]); + add234(tree4, strings[0]); + array[0] = strings[0]; + array[1] = strings[1]; + verifytree(tree, array, 0); + verifytree(tree2, array+1, 1); + verifytree(tree3, array, 0); + verifytree(tree4, array, 1); + + /* + * So: + * - join(tree,tree3) should leave both tree and tree3 unchanged. + * - joinr(tree,tree2) should leave both tree and tree2 unchanged. + * - join(tree4,tree3) should leave both tree3 and tree4 unchanged. + * - join(tree, tree2) should move the element from tree2 to tree. + * - joinr(tree4, tree3) should move the element from tree4 to tree3. + * - join(tree,tree3) should return NULL and leave both unchanged. + * - join(tree3,tree) should work and create a bigger tree in tree3. + */ + assert(tree == join234(tree, tree3)); + verifytree(tree, array, 0); + verifytree(tree3, array, 0); + assert(tree2 == join234r(tree, tree2)); + verifytree(tree, array, 0); + verifytree(tree2, array+1, 1); + assert(tree4 == join234(tree4, tree3)); + verifytree(tree3, array, 0); + verifytree(tree4, array, 1); + assert(tree == join234(tree, tree2)); + verifytree(tree, array+1, 1); + verifytree(tree2, array, 0); + assert(tree3 == join234r(tree4, tree3)); + verifytree(tree3, array, 1); + verifytree(tree4, array, 0); + assert(NULL == join234(tree, tree3)); + verifytree(tree, array+1, 1); + verifytree(tree3, array, 1); + assert(tree3 == join234(tree3, tree)); + verifytree(tree3, array, 2); + verifytree(tree, array, 0); + + return 0; +} + +#endif + +#if 0 /* sorted list of strings might be useful */ +{ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", +} +#endif diff --git a/apps/plugins/puzzles/tree234.h b/apps/plugins/puzzles/tree234.h new file mode 100644 index 0000000000..f75c8f7fb3 --- /dev/null +++ b/apps/plugins/puzzles/tree234.h @@ -0,0 +1,202 @@ +/* + * tree234.h: header defining functions in tree234.c. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TREE234_H +#define TREE234_H + +/* + * This typedef is opaque outside tree234.c itself. + */ +typedef struct tree234_Tag tree234; + +typedef int (*cmpfn234)(void *, void *); + +typedef void *(*copyfn234)(void *state, void *element); + +/* + * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and + * lookups by key will fail: you can only look things up by numeric + * index, and you have to use addpos234() and delpos234(). + */ +tree234 *newtree234(cmpfn234 cmp); + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +void freetree234(tree234 *t); + +/* + * Add an element e to a sorted 2-3-4 tree t. Returns e on success, + * or if an existing element compares equal, returns that. + */ +void *add234(tree234 *t, void *e); + +/* + * Add an element e to an unsorted 2-3-4 tree t. Returns e on + * success, NULL on failure. (Failure should only occur if the + * index is out of range or the tree is sorted.) + * + * Index range can be from 0 to the tree's current element count, + * inclusive. + */ +void *addpos234(tree234 *t, void *e, int index); + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + * + * One obvious use for this function is in iterating over the whole + * of a tree (sorted or unsorted): + * + * for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p); + * + * or + * + * int maxcount = count234(tree); + * for (i = 0; i < maxcount; i++) { + * p = index234(tree, i); + * assert(p != NULL); + * consume(p); + * } + */ +void *index234(tree234 *t, int index); + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + * + * Three of these functions are special cases of findrelpos234. The + * non-`pos' variants lack the `index' parameter: if the parameter + * is present and non-NULL, it must point to an integer variable + * which will be filled with the numeric index of the returned + * element. + * + * The non-`rel' variants lack the `relation' parameter. This + * parameter allows you to specify what relation the element you + * provide has to the element you're looking for. This parameter + * can be: + * + * REL234_EQ - find only an element that compares equal to e + * REL234_LT - find the greatest element that compares < e + * REL234_LE - find the greatest element that compares <= e + * REL234_GT - find the smallest element that compares > e + * REL234_GE - find the smallest element that compares >= e + * + * Non-`rel' variants assume REL234_EQ. + * + * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be + * NULL. In this case, REL234_GT will return the smallest element + * in the tree, and REL234_LT will return the greatest. This gives + * an alternative means of iterating over a sorted tree, instead of + * using index234: + * + * // to loop forwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;) + * consume(p); + * + * // to loop backwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;) + * consume(p); + */ +enum { + REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE +}; +void *find234(tree234 *t, void *e, cmpfn234 cmp); +void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation); +void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index); +void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation, + int *index); + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + * + * delpos234 deletes the element at a particular tree index: it + * works on both sorted and unsorted trees. + * + * del234 deletes the element passed to it, so it only works on + * sorted trees. (It's equivalent to using findpos234 to determine + * the index of an element, and then passing that index to + * delpos234.) + * + * Both functions return a pointer to the element they delete, for + * the user to free or pass on elsewhere or whatever. If the index + * is out of range (delpos234) or the element is already not in the + * tree (del234) then they return NULL. + */ +void *del234(tree234 *t, void *e); +void *delpos234(tree234 *t, int index); + +/* + * Return the total element count of a tree234. + */ +int count234(tree234 *t); + +/* + * Split a tree234 into two valid tree234s. + * + * splitpos234 splits at a given index. If `before' is TRUE, the + * items at and after that index are left in t and the ones before + * are returned; if `before' is FALSE, the items before that index + * are left in t and the rest are returned. + * + * split234 splits at a given key. You can pass any of the + * relations used with findrel234, except for REL234_EQ. The items + * in the tree that satisfy the relation are returned; the + * remainder are left. + */ +tree234 *splitpos234(tree234 *t, int index, int before); +tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel); + +/* + * Join two tree234s together into a single one. + * + * All the elements in t1 are placed to the left of all the + * elements in t2. If the trees are sorted, there will be a test to + * ensure that this satisfies the ordering criterion, and NULL will + * be returned otherwise. If the trees are unsorted, there is no + * restriction on the use of join234. + * + * The tree returned is t1 (join234) or t2 (join234r), if the + * operation is successful. + */ +tree234 *join234(tree234 *t1, tree234 *t2); +tree234 *join234r(tree234 *t1, tree234 *t2); + +/* + * Make a complete copy of a tree234. Element pointers will be + * reused unless copyfn is non-NULL, in which case it will be used + * to copy each element. (copyfn takes two `void *' parameters; the + * first is private state and the second is the element. A simple + * copy routine probably won't need private state.) + */ +tree234 *copytree234(tree234 *t, copyfn234 copyfn, void *copyfnstate); + +#endif /* TREE234_H */ diff --git a/apps/plugins/puzzles/twiddle.R b/apps/plugins/puzzles/twiddle.R new file mode 100644 index 0000000000..1495c33181 --- /dev/null +++ b/apps/plugins/puzzles/twiddle.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +twiddle : [X] GTK COMMON twiddle twiddle-icon|no-icon + +twiddle : [G] WINDOWS COMMON twiddle twiddle.res|noicon.res + +ALL += twiddle[COMBINED] + +!begin am gtk +GAMES += twiddle +!end + +!begin >list.c + A(twiddle) \ +!end + +!begin >gamedesc.txt +twiddle:twiddle.exe:Twiddle:Rotational sliding block puzzle:Rotate the tiles around themselves to arrange them into order. +!end diff --git a/apps/plugins/puzzles/twiddle.c b/apps/plugins/puzzles/twiddle.c new file mode 100644 index 0000000000..2a2ab668ca --- /dev/null +++ b/apps/plugins/puzzles/twiddle.c @@ -0,0 +1,1319 @@ +/* + * twiddle.c: Puzzle involving rearranging a grid of squares by + * rotating subsquares. Adapted and generalised from a + * door-unlocking puzzle in Metroid Prime 2 (the one in the Main + * Gyro Chamber). + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#define PREFERRED_TILE_SIZE 48 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 2) +#define HIGHLIGHT_WIDTH (TILE_SIZE / 20) +#define COORD(x) ( (x) * TILE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 ) + +#define ANIM_PER_BLKSIZE_UNIT 0.13F +#define FLASH_FRAME 0.13F + +enum { + COL_BACKGROUND, + COL_TEXT, + COL_HIGHLIGHT, + COL_HIGHLIGHT_GENTLE, + COL_LOWLIGHT, + COL_LOWLIGHT_GENTLE, + COL_HIGHCURSOR, COL_LOWCURSOR, + NCOLOURS +}; + +struct game_params { + int w, h, n; + int rowsonly; + int orientable; + int movetarget; +}; + +struct game_state { + int w, h, n; + int orientable; + int *grid; + int completed; + int used_solve; /* used to suppress completion flash */ + int movecount, movetarget; + int lastx, lasty, lastr; /* coordinates of last rotation */ +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 3; + ret->n = 2; + ret->rowsonly = ret->orientable = FALSE; + ret->movetarget = 0; + + return ret; +} + + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + static struct { + char *title; + game_params params; + } presets[] = { + { "3x3 rows only", { 3, 3, 2, TRUE, FALSE } }, + { "3x3 normal", { 3, 3, 2, FALSE, FALSE } }, + { "3x3 orientable", { 3, 3, 2, FALSE, TRUE } }, + { "4x4 normal", { 4, 4, 2, FALSE } }, + { "4x4 orientable", { 4, 4, 2, FALSE, TRUE } }, + { "4x4, rotating 3x3 blocks", { 4, 4, 3, FALSE } }, + { "5x5, rotating 3x3 blocks", { 5, 5, 3, FALSE } }, + { "6x6, rotating 4x4 blocks", { 6, 6, 4, FALSE } }, + }; + + if (i < 0 || i >= lenof(presets)) + return FALSE; + + *name = dupstr(presets[i].title); + *params = dup_params(&presets[i].params); + + return TRUE; +} + +static void decode_params(game_params *ret, char const *string) +{ + ret->w = ret->h = atoi(string); + ret->n = 2; + ret->rowsonly = ret->orientable = FALSE; + ret->movetarget = 0; + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + ret->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'n') { + string++; + ret->n = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + while (*string) { + if (*string == 'r') { + ret->rowsonly = TRUE; + } else if (*string == 'o') { + ret->orientable = TRUE; + } else if (*string == 'm') { + string++; + ret->movetarget = atoi(string); + while (string[1] && isdigit((unsigned char)string[1])) string++; + } + string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[256]; + sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n, + params->rowsonly ? "r" : "", + params->orientable ? "o" : ""); + /* Shuffle limit is part of the limited parameters, because we have to + * supply the target move count. */ + if (params->movetarget) + sprintf(buf + strlen(buf), "m%d", params->movetarget); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(7, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Rotating block size"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->n); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = "One number per row"; + ret[3].type = C_BOOLEAN; + ret[3].sval = NULL; + ret[3].ival = params->rowsonly; + + ret[4].name = "Orientation matters"; + ret[4].type = C_BOOLEAN; + ret[4].sval = NULL; + ret[4].ival = params->orientable; + + ret[5].name = "Number of shuffling moves"; + ret[5].type = C_STRING; + sprintf(buf, "%d", params->movetarget); + ret[5].sval = dupstr(buf); + ret[5].ival = 0; + + ret[6].name = NULL; + ret[6].type = C_END; + ret[6].sval = NULL; + ret[6].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->n = atoi(cfg[2].sval); + ret->rowsonly = cfg[3].ival; + ret->orientable = cfg[4].ival; + ret->movetarget = atoi(cfg[5].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->n < 2) + return "Rotating block size must be at least two"; + if (params->w < params->n) + return "Width must be at least the rotating block size"; + if (params->h < params->n) + return "Height must be at least the rotating block size"; + return NULL; +} + +/* + * This function actually performs a rotation on a grid. The `x' + * and `y' coordinates passed in are the coordinates of the _top + * left corner_ of the rotated region. (Using the centre would have + * involved half-integers and been annoyingly fiddly. Clicking in + * the centre is good for a user interface, but too inconvenient to + * use internally.) + */ +static void do_rotate(int *grid, int w, int h, int n, int orientable, + int x, int y, int dir) +{ + int i, j; + + assert(x >= 0 && x+n <= w); + assert(y >= 0 && y+n <= h); + dir &= 3; + if (dir == 0) + return; /* nothing to do */ + + grid += y*w+x; /* translate region to top corner */ + + /* + * If we were leaving the result of the rotation in a separate + * grid, the simple thing to do would be to loop over each + * square within the rotated region and assign it from its + * source square. However, to do it in place without taking + * O(n^2) memory, we need to be marginally more clever. What + * I'm going to do is loop over about one _quarter_ of the + * rotated region and permute each element within that quarter + * with its rotational coset. + * + * The size of the region I need to loop over is (n+1)/2 by + * n/2, which is an obvious exact quarter for even n and is a + * rectangle for odd n. (For odd n, this technique leaves out + * one element of the square, which is of course the central + * one that never moves anyway.) + */ + for (i = 0; i < (n+1)/2; i++) { + for (j = 0; j < n/2; j++) { + int k; + int g[4]; + int p[4]; + + p[0] = j*w+i; + p[1] = i*w+(n-j-1); + p[2] = (n-j-1)*w+(n-i-1); + p[3] = (n-i-1)*w+j; + + for (k = 0; k < 4; k++) + g[k] = grid[p[k]]; + + for (k = 0; k < 4; k++) { + int v = g[(k+dir) & 3]; + if (orientable) + v ^= ((v+dir) ^ v) & 3; /* alter orientation */ + grid[p[k]] = v; + } + } + } + + /* + * Don't forget the orientation on the centre square, if n is + * odd. + */ + if (orientable && (n & 1)) { + int v = grid[n/2*(w+1)]; + v ^= ((v+dir) ^ v) & 3; /* alter orientation */ + grid[n/2*(w+1)] = v; + } +} + +static int grid_complete(int *grid, int wh, int orientable) +{ + int ok = TRUE; + int i; + for (i = 1; i < wh; i++) + if (grid[i] < grid[i-1]) + ok = FALSE; + if (orientable) { + for (i = 0; i < wh; i++) + if (grid[i] & 3) + ok = FALSE; + } + return ok; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int *grid; + int w = params->w, h = params->h, n = params->n, wh = w*h; + int i; + char *ret; + int retlen; + int total_moves; + + /* + * Set up a solved grid. + */ + grid = snewn(wh, int); + for (i = 0; i < wh; i++) + grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4; + + /* + * Shuffle it. This game is complex enough that I don't feel up + * to analysing its full symmetry properties (particularly at + * n=4 and above!), so I'm going to do it the pedestrian way + * and simply shuffle the grid by making a long sequence of + * randomly chosen moves. + */ + total_moves = params->movetarget; + if (!total_moves) + /* Add a random move to avoid parity issues. */ + total_moves = w*h*n*n*2 + random_upto(rs, 2); + + do { + int *prevmoves; + int rw, rh; /* w/h of rotation centre space */ + + rw = w - n + 1; + rh = h - n + 1; + prevmoves = snewn(rw * rh, int); + for (i = 0; i < rw * rh; i++) + prevmoves[i] = 0; + + for (i = 0; i < total_moves; i++) { + int x, y, r, oldtotal, newtotal, dx, dy; + + do { + x = random_upto(rs, w - n + 1); + y = random_upto(rs, h - n + 1); + r = 2 * random_upto(rs, 2) - 1; + + /* + * See if any previous rotations has happened at + * this point which nothing has overlapped since. + * If so, ensure we haven't either undone a + * previous move or repeated one so many times that + * it turns into fewer moves in the inverse + * direction (i.e. three identical rotations). + */ + oldtotal = prevmoves[y*rw+x]; + newtotal = oldtotal + r; + + /* + * Special case here for w==h==n, in which case + * there is actually no way to _avoid_ all moves + * repeating or undoing previous ones. + */ + } while ((w != n || h != n) && + (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2)); + + do_rotate(grid, w, h, n, params->orientable, x, y, r); + + /* + * Log the rotation we've just performed at this point, + * for inversion detection in the next move. + * + * Also zero a section of the prevmoves array, because + * any rotation area which _overlaps_ this one is now + * entirely safe to perform further moves in. + * + * Two rotation areas overlap if their top left + * coordinates differ by strictly less than n in both + * directions + */ + prevmoves[y*rw+x] += r; + for (dy = -n+1; dy <= n-1; dy++) { + if (y + dy < 0 || y + dy >= rh) + continue; + for (dx = -n+1; dx <= n-1; dx++) { + if (x + dx < 0 || x + dx >= rw) + continue; + if (dx == 0 && dy == 0) + continue; + prevmoves[(y+dy)*rw+(x+dx)] = 0; + } + } + } + + sfree(prevmoves); + + } while (grid_complete(grid, wh, params->orientable)); + + /* + * Now construct the game description, by describing the grid + * as a simple sequence of integers. They're comma-separated, + * unless the puzzle is orientable in which case they're + * separated by orientation letters `u', `d', `l' and `r'. + */ + ret = NULL; + retlen = 0; + for (i = 0; i < wh; i++) { + char buf[80]; + int k; + + k = sprintf(buf, "%d%c", grid[i] / 4, + (char)(params->orientable ? "uldr"[grid[i] & 3] : ',')); + + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + if (!params->orientable) + ret[retlen-1] = '\0'; /* delete last comma */ + + sfree(grid); + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + const char *p; + int w = params->w, h = params->h, wh = w*h; + int i; + + p = desc; + + for (i = 0; i < wh; i++) { + if (*p < '0' || *p > '9') + return "Not enough numbers in string"; + while (*p >= '0' && *p <= '9') + p++; + if (!params->orientable && i < wh-1) { + if (*p != ',') + return "Expected comma after number"; + } else if (params->orientable && i < wh) { + if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd') + return "Expected orientation letter after number"; + } else if (i == wh-1 && *p) { + return "Excess junk at end of string"; + } + + if (*p) p++; /* eat comma */ + } + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + int w = params->w, h = params->h, n = params->n, wh = w*h; + int i; + const char *p; + + state->w = w; + state->h = h; + state->n = n; + state->orientable = params->orientable; + state->completed = 0; + state->used_solve = FALSE; + state->movecount = 0; + state->movetarget = params->movetarget; + state->lastx = state->lasty = state->lastr = -1; + + state->grid = snewn(wh, int); + + p = desc; + + for (i = 0; i < wh; i++) { + state->grid[i] = 4 * atoi(p); + while (*p >= '0' && *p <= '9') + p++; + if (*p) { + if (params->orientable) { + switch (*p) { + case 'l': state->grid[i] |= 1; break; + case 'd': state->grid[i] |= 2; break; + case 'r': state->grid[i] |= 3; break; + } + } + p++; + } + } + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + ret->n = state->n; + ret->orientable = state->orientable; + ret->completed = state->completed; + ret->movecount = state->movecount; + ret->movetarget = state->movetarget; + ret->lastx = state->lastx; + ret->lasty = state->lasty; + ret->lastr = state->lastr; + ret->used_solve = state->used_solve; + + ret->grid = snewn(ret->w * ret->h, int); + memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int)); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state); +} + +static int compare_int(const void *av, const void *bv) +{ + const int *a = (const int *)av; + const int *b = (const int *)bv; + if (*a < *b) + return -1; + else if (*a > *b) + return +1; + else + return 0; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return dupstr("S"); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + char *ret, *p, buf[80]; + int i, x, y, col, o, maxlen; + + /* + * First work out how many characters we need to display each + * number. We're pretty flexible on grid contents here, so we + * have to scan the entire grid. + */ + col = 0; + for (i = 0; i < state->w * state->h; i++) { + x = sprintf(buf, "%d", state->grid[i] / 4); + if (col < x) col = x; + } + o = (state->orientable ? 1 : 0); + + /* + * Now we know the exact total size of the grid we're going to + * produce: it's got h rows, each containing w lots of col+o, + * w-1 spaces and a trailing newline. + */ + maxlen = state->h * state->w * (col+o+1); + + ret = snewn(maxlen+1, char); + p = ret; + + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + int v = state->grid[state->w*y+x]; + sprintf(buf, "%*d", col, v/4); + memcpy(p, buf, col); + p += col; + if (o) + *p++ = "^"[v & 3]; + if (x+1 == state->w) + *p++ = '\n'; + else + *p++ = ' '; + } + } + + assert(p - ret == maxlen); + *p = '\0'; + return ret; +} + +struct game_ui { + int cur_x, cur_y; + int cur_visible; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->cur_x = 0; + ui->cur_y = 0; + ui->cur_visible = FALSE; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int started; + int w, h, bgcolour; + int *grid; + int tilesize; + int cur_x, cur_y; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->w, h = state->h, n = state->n /* , wh = w*h */; + char buf[80]; + int dir; + + button = button & (~MOD_MASK | MOD_NUM_KEYPAD); + + if (IS_CURSOR_MOVE(button)) { + if (button == CURSOR_LEFT && ui->cur_x > 0) + ui->cur_x--; + if (button == CURSOR_RIGHT && (ui->cur_x+n) < (w)) + ui->cur_x++; + if (button == CURSOR_UP && ui->cur_y > 0) + ui->cur_y--; + if (button == CURSOR_DOWN && (ui->cur_y+n) < (h)) + ui->cur_y++; + ui->cur_visible = 1; + return ""; + } + + if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + /* + * Determine the coordinates of the click. We offset by n-1 + * half-blocks so that the user must click at the centre of + * a rotation region rather than at the corner. + */ + x -= (n-1) * TILE_SIZE / 2; + y -= (n-1) * TILE_SIZE / 2; + x = FROMCOORD(x); + y = FROMCOORD(y); + dir = (button == LEFT_BUTTON ? 1 : -1); + if (x < 0 || x > w-n || y < 0 || y > h-n) + return NULL; + ui->cur_visible = 0; + } else if (IS_CURSOR_SELECT(button)) { + if (ui->cur_visible) { + x = ui->cur_x; + y = ui->cur_y; + dir = (button == CURSOR_SELECT2) ? -1 : +1; + } else { + ui->cur_visible = 1; + return ""; + } + } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') { + x = y = 0; + dir = (button == 'A' ? -1 : +1); + } else if (button == 'b' || button == 'B' || button==MOD_NUM_KEYPAD+'9') { + x = w-n; + y = 0; + dir = (button == 'B' ? -1 : +1); + } else if (button == 'c' || button == 'C' || button==MOD_NUM_KEYPAD+'1') { + x = 0; + y = h-n; + dir = (button == 'C' ? -1 : +1); + } else if (button == 'd' || button == 'D' || button==MOD_NUM_KEYPAD+'3') { + x = w-n; + y = h-n; + dir = (button == 'D' ? -1 : +1); + } else if (button==MOD_NUM_KEYPAD+'8' && (w-n) % 2 == 0) { + x = (w-n) / 2; + y = 0; + dir = +1; + } else if (button==MOD_NUM_KEYPAD+'2' && (w-n) % 2 == 0) { + x = (w-n) / 2; + y = h-n; + dir = +1; + } else if (button==MOD_NUM_KEYPAD+'4' && (h-n) % 2 == 0) { + x = 0; + y = (h-n) / 2; + dir = +1; + } else if (button==MOD_NUM_KEYPAD+'6' && (h-n) % 2 == 0) { + x = w-n; + y = (h-n) / 2; + dir = +1; + } else if (button==MOD_NUM_KEYPAD+'5' && (w-n) % 2 == 0 && (h-n) % 2 == 0){ + x = (w-n) / 2; + y = (h-n) / 2; + dir = +1; + } else { + return NULL; /* no move to be made */ + } + + /* + * If we reach here, we have a valid move. + */ + sprintf(buf, "M%d,%d,%d", x, y, dir); + return dupstr(buf); +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + game_state *ret; + int w = from->w, h = from->h, n = from->n, wh = w*h; + int x, y, dir; + + if (!strcmp(move, "S")) { + int i; + ret = dup_game(from); + + /* + * Simply replace the grid with a solved one. For this game, + * this isn't a useful operation for actually telling the user + * what they should have done, but it is useful for + * conveniently being able to get hold of a clean state from + * which to practise manoeuvres. + */ + qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_int); + for (i = 0; i < ret->w*ret->h; i++) + ret->grid[i] &= ~3; + ret->used_solve = TRUE; + ret->completed = ret->movecount = 1; + + return ret; + } + + if (move[0] != 'M' || + sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 || + x < 0 || y < 0 || x > from->w - n || y > from->h - n) + return NULL; /* can't parse this move string */ + + ret = dup_game(from); + ret->movecount++; + do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir); + ret->lastx = x; + ret->lasty = y; + ret->lastr = dir; + + /* + * See if the game has been completed. To do this we simply + * test that the grid contents are in increasing order. + */ + if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable)) + ret->completed = ret->movecount; + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = TILE_SIZE * params->w + 2 * BORDER; + *y = TILE_SIZE * params->h + 2 * BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + /* cursor is light-background with a red tinge. */ + ret[COL_HIGHCURSOR * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 1.0F; + ret[COL_HIGHCURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.5F; + ret[COL_HIGHCURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.5F; + + for (i = 0; i < 3; i++) { + ret[COL_HIGHLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.1F; + ret[COL_LOWLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F; + ret[COL_TEXT * 3 + i] = 0.0; + ret[COL_LOWCURSOR * 3 + i] = ret[COL_HIGHCURSOR * 3 + i] * 0.6F; + } + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->started = FALSE; + ds->w = state->w; + ds->h = state->h; + ds->bgcolour = COL_BACKGROUND; + ds->grid = snewn(ds->w*ds->h, int); + ds->tilesize = 0; /* haven't decided yet */ + for (i = 0; i < ds->w*ds->h; i++) + ds->grid[i] = -1; + ds->cur_x = ds->cur_y = -state->n; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +struct rotation { + int cx, cy, cw, ch; /* clip region */ + int ox, oy; /* rotation origin */ + float c, s; /* cos and sin of rotation angle */ + int lc, rc, tc, bc; /* colours of tile edges */ +}; + +static void rotate(int *xy, struct rotation *rot) +{ + if (rot) { + float xf = (float)xy[0] - rot->ox, yf = (float)xy[1] - rot->oy; + float xf2, yf2; + + xf2 = rot->c * xf + rot->s * yf; + yf2 = - rot->s * xf + rot->c * yf; + + xy[0] = (int)(xf2 + rot->ox + 0.5); /* round to nearest */ + xy[1] = (int)(yf2 + rot->oy + 0.5); /* round to nearest */ + } +} + +#define CUR_TOP 1 +#define CUR_RIGHT 2 +#define CUR_BOTTOM 4 +#define CUR_LEFT 8 + +static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state, + int x, int y, int tile, int flash_colour, + struct rotation *rot, unsigned cedges) +{ + int coords[8]; + char str[40]; + + /* + * If we've been passed a rotation region but we're drawing a + * tile which is outside it, we must draw it normally. This can + * occur if we're cleaning up after a completion flash while a + * new move is also being made. + */ + if (rot && (x < rot->cx || y < rot->cy || + x >= rot->cx+rot->cw || y >= rot->cy+rot->ch)) + rot = NULL; + + if (rot) + clip(dr, rot->cx, rot->cy, rot->cw, rot->ch); + + /* + * We must draw each side of the tile's highlight separately, + * because in some cases (during rotation) they will all need + * to be different colours. + */ + + /* The centre point is common to all sides. */ + coords[4] = x + TILE_SIZE / 2; + coords[5] = y + TILE_SIZE / 2; + rotate(coords+4, rot); + + /* Right side. */ + coords[0] = x + TILE_SIZE - 1; + coords[1] = y + TILE_SIZE - 1; + rotate(coords+0, rot); + coords[2] = x + TILE_SIZE - 1; + coords[3] = y; + rotate(coords+2, rot); + draw_polygon(dr, coords, 3, rot ? rot->rc : COL_LOWLIGHT, + rot ? rot->rc : (cedges & CUR_RIGHT) ? COL_LOWCURSOR : COL_LOWLIGHT); + + /* Bottom side. */ + coords[2] = x; + coords[3] = y + TILE_SIZE - 1; + rotate(coords+2, rot); + draw_polygon(dr, coords, 3, rot ? rot->bc : COL_LOWLIGHT, + rot ? rot->bc : (cedges & CUR_BOTTOM) ? COL_LOWCURSOR : COL_LOWLIGHT); + + /* Left side. */ + coords[0] = x; + coords[1] = y; + rotate(coords+0, rot); + draw_polygon(dr, coords, 3, rot ? rot->lc : COL_HIGHLIGHT, + rot ? rot->lc : (cedges & CUR_LEFT) ? COL_HIGHCURSOR : COL_HIGHLIGHT); + + /* Top side. */ + coords[2] = x + TILE_SIZE - 1; + coords[3] = y; + rotate(coords+2, rot); + draw_polygon(dr, coords, 3, rot ? rot->tc : COL_HIGHLIGHT, + rot ? rot->tc : (cedges & CUR_TOP) ? COL_HIGHCURSOR : COL_HIGHLIGHT); + + /* + * Now the main blank area in the centre of the tile. + */ + if (rot) { + coords[0] = x + HIGHLIGHT_WIDTH; + coords[1] = y + HIGHLIGHT_WIDTH; + rotate(coords+0, rot); + coords[2] = x + HIGHLIGHT_WIDTH; + coords[3] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; + rotate(coords+2, rot); + coords[4] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; + coords[5] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; + rotate(coords+4, rot); + coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH; + coords[7] = y + HIGHLIGHT_WIDTH; + rotate(coords+6, rot); + draw_polygon(dr, coords, 4, flash_colour, flash_colour); + } else { + draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH, + TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH, + flash_colour); + } + + /* + * Next, the triangles for orientation. + */ + if (state->orientable) { + int xdx, xdy, ydx, ydy; + int cx, cy, displ, displ2; + switch (tile & 3) { + case 0: + xdx = 1, xdy = 0; + ydx = 0, ydy = 1; + break; + case 1: + xdx = 0, xdy = -1; + ydx = 1, ydy = 0; + break; + case 2: + xdx = -1, xdy = 0; + ydx = 0, ydy = -1; + break; + default /* case 3 */: + xdx = 0, xdy = 1; + ydx = -1, ydy = 0; + break; + } + + cx = x + TILE_SIZE / 2; + cy = y + TILE_SIZE / 2; + displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2; + displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH; + + coords[0] = cx - displ * xdx + displ2 * ydx; + coords[1] = cy - displ * xdy + displ2 * ydy; + rotate(coords+0, rot); + coords[2] = cx + displ * xdx + displ2 * ydx; + coords[3] = cy + displ * xdy + displ2 * ydy; + rotate(coords+2, rot); + coords[4] = cx - displ * ydx; + coords[5] = cy - displ * ydy; + rotate(coords+4, rot); + draw_polygon(dr, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE); + } + + coords[0] = x + TILE_SIZE/2; + coords[1] = y + TILE_SIZE/2; + rotate(coords+0, rot); + sprintf(str, "%d", tile / 4); + draw_text(dr, coords[0], coords[1], + FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE, + COL_TEXT, str); + + if (rot) + unclip(dr); + + draw_update(dr, x, y, TILE_SIZE, TILE_SIZE); +} + +static int highlight_colour(float angle) +{ + int colours[32] = { + COL_LOWLIGHT, + COL_LOWLIGHT_GENTLE, + COL_LOWLIGHT_GENTLE, + COL_LOWLIGHT_GENTLE, + COL_HIGHLIGHT_GENTLE, + COL_HIGHLIGHT_GENTLE, + COL_HIGHLIGHT_GENTLE, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT, + COL_HIGHLIGHT_GENTLE, + COL_HIGHLIGHT_GENTLE, + COL_HIGHLIGHT_GENTLE, + COL_LOWLIGHT_GENTLE, + COL_LOWLIGHT_GENTLE, + COL_LOWLIGHT_GENTLE, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + COL_LOWLIGHT, + }; + + return colours[(int)((angle + 2*PI) / (PI/16)) & 31]; +} + +static float game_anim_length_real(const game_state *oldstate, + const game_state *newstate, int dir, + const game_ui *ui) +{ + /* + * Our game_anim_length doesn't need to modify its game_ui, so + * this is the real function which declares ui as const. We must + * wrap this for the backend structure with a version that has ui + * non-const, but we still need this version to call from within + * game_redraw which only has a const ui available. + */ + return (float)(ANIM_PER_BLKSIZE_UNIT * sqrt(newstate->n-1)); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return game_anim_length_real(oldstate, newstate, dir, ui); + +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->used_solve && !newstate->used_solve) + return 2 * FLASH_FRAME; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int i, bgcolour; + struct rotation srot, *rot; + int lastx = -1, lasty = -1, lastr = -1; + int cx, cy, cmoved = 0, n = state->n; + + cx = ui->cur_visible ? ui->cur_x : -state->n; + cy = ui->cur_visible ? ui->cur_y : -state->n; + if (cx != ds->cur_x || cy != ds->cur_y) + cmoved = 1; + + if (flashtime > 0) { + int frame = (int)(flashtime / FLASH_FRAME); + bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT); + } else + bgcolour = COL_BACKGROUND; + + if (!ds->started) { + int coords[10]; + + draw_rect(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND); + draw_update(dr, 0, 0, + TILE_SIZE * state->w + 2 * BORDER, + TILE_SIZE * state->h + 2 * BORDER); + + /* + * Recessed area containing the whole puzzle. + */ + coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; + coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; + coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1; + coords[3] = COORD(0) - HIGHLIGHT_WIDTH; + coords[4] = coords[2] - TILE_SIZE; + coords[5] = coords[3] + TILE_SIZE; + coords[8] = COORD(0) - HIGHLIGHT_WIDTH; + coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1; + coords[6] = coords[8] + TILE_SIZE; + coords[7] = coords[9] - TILE_SIZE; + draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT); + + coords[1] = COORD(0) - HIGHLIGHT_WIDTH; + coords[0] = COORD(0) - HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT); + + ds->started = TRUE; + } + + /* + * If we're drawing any rotated tiles, sort out the rotation + * parameters, and also zap the rotation region to the + * background colour before doing anything else. + */ + if (oldstate) { + float angle; + float anim_max = game_anim_length_real(oldstate, state, dir, ui); + + if (dir > 0) { + lastx = state->lastx; + lasty = state->lasty; + lastr = state->lastr; + } else { + lastx = oldstate->lastx; + lasty = oldstate->lasty; + lastr = -oldstate->lastr; + } + + rot = &srot; + rot->cx = COORD(lastx); + rot->cy = COORD(lasty); + rot->cw = rot->ch = TILE_SIZE * state->n; + rot->ox = rot->cx + rot->cw/2; + rot->oy = rot->cy + rot->ch/2; + angle = (float)((-PI/2 * lastr) * (1.0 - animtime / anim_max)); + rot->c = (float)cos(angle); + rot->s = (float)sin(angle); + + /* + * Sort out the colours of the various sides of the tile. + */ + rot->lc = highlight_colour((float)PI + angle); + rot->rc = highlight_colour(angle); + rot->tc = highlight_colour((float)(PI/2.0) + angle); + rot->bc = highlight_colour((float)(-PI/2.0) + angle); + + draw_rect(dr, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour); + } else + rot = NULL; + + /* + * Now draw each tile. + */ + for (i = 0; i < state->w * state->h; i++) { + int t, cc = 0; + int tx = i % state->w, ty = i / state->w; + + /* + * Figure out what should be displayed at this location. + * Usually it will be state->grid[i], unless we're in the + * middle of animating an actual rotation and this cell is + * within the rotation region, in which case we set -1 + * (always display). + */ + if (oldstate && lastx >= 0 && lasty >= 0 && + tx >= lastx && tx < lastx + state->n && + ty >= lasty && ty < lasty + state->n) + t = -1; + else + t = state->grid[i]; + + if (cmoved) { + /* cursor has moved (or changed visibility)... */ + if (tx == cx || tx == cx+n-1 || ty == cy || ty == cy+n-1) + cc = 1; /* ...we're on new cursor, redraw */ + if (tx == ds->cur_x || tx == ds->cur_x+n-1 || + ty == ds->cur_y || ty == ds->cur_y+n-1) + cc = 1; /* ...we were on old cursor, redraw */ + } + + if (ds->bgcolour != bgcolour || /* always redraw when flashing */ + ds->grid[i] != t || ds->grid[i] == -1 || t == -1 || cc) { + int x = COORD(tx), y = COORD(ty); + unsigned cedges = 0; + + if (tx == cx && ty >= cy && ty <= cy+n-1) cedges |= CUR_LEFT; + if (ty == cy && tx >= cx && tx <= cx+n-1) cedges |= CUR_TOP; + if (tx == cx+n-1 && ty >= cy && ty <= cy+n-1) cedges |= CUR_RIGHT; + if (ty == cy+n-1 && tx >= cx && tx <= cx+n-1) cedges |= CUR_BOTTOM; + + draw_tile(dr, ds, state, x, y, state->grid[i], bgcolour, rot, cedges); + ds->grid[i] = t; + } + } + ds->bgcolour = bgcolour; + ds->cur_x = cx; ds->cur_y = cy; + + /* + * Update the status bar. + */ + { + char statusbuf[256]; + + /* + * Don't show the new status until we're also showing the + * new _state_ - after the game animation is complete. + */ + if (oldstate) + state = oldstate; + + if (state->used_solve) + sprintf(statusbuf, "Moves since auto-solve: %d", + state->movecount - state->completed); + else { + sprintf(statusbuf, "%sMoves: %d", + (state->completed ? "COMPLETED! " : ""), + (state->completed ? state->completed : state->movecount)); + if (state->movetarget) + sprintf(statusbuf+strlen(statusbuf), " (target %d)", + state->movetarget); + } + + status_bar(dr, statusbuf); + } +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame twiddle +#endif + +const struct game thegame = { + "Twiddle", "games.twiddle", "twiddle", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/undead.R b/apps/plugins/puzzles/undead.R new file mode 100644 index 0000000000..5907ed6b74 --- /dev/null +++ b/apps/plugins/puzzles/undead.R @@ -0,0 +1,18 @@ +# -*- makefile -*- + +undead : [X] GTK COMMON undead undead-icon|no-icon +undead : [G] WINDOWS COMMON undead undead.res|noicon.res + +ALL += undead[COMBINED] + +!begin am gtk +GAMES += undead +!end + +!begin >list.c + A(undead) \ +!end + +!begin >gamedesc.txt +undead:undead.exe:Undead:Monster-placing puzzle:Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors. +!end diff --git a/apps/plugins/puzzles/undead.c b/apps/plugins/puzzles/undead.c new file mode 100644 index 0000000000..28fd0f35fe --- /dev/null +++ b/apps/plugins/puzzles/undead.c @@ -0,0 +1,2738 @@ +/* + * undead: Implementation of Haunted Mirror Mazes + * + * http://www.janko.at/Raetsel/Spukschloss/index.htm + * + * Puzzle definition is the total number of each monster type, the + * grid definition, and the list of sightings (clockwise, starting + * from top left corner) + * + * Example: (Janko puzzle No. 1, + * http://www.janko.at/Raetsel/Spukschloss/001.a.htm ) + * + * Ghosts: 0 Vampires: 2 Zombies: 6 + * + * 2 1 1 1 + * 1 \ \ . / 2 + * 0 \ . / . 2 + * 0 / . / . 2 + * 3 . . . \ 2 + * 3 3 2 2 + * + * would be encoded into: + * 4x4:0,2,6,LLaRLaRaRaRdL,2,1,1,1,2,2,2,2,2,2,3,3,3,0,0,1 + * + * Additionally, the game description can contain monsters fixed at a + * certain grid position. The internal generator does not (yet) use + * this feature, but this is needed to enter puzzles like Janko No. + * 14, which is encoded as: + * 8x5:12,12,0,LaRbLaRaLaRLbRaVaVaGRaRaRaLbLaRbRLb,0,2,0,2,2,1,2,1,3,1,0,1,8,4,3,0,0,2,3,2,7,2,1,6,2,1 + * + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + COL_GRID, + COL_TEXT, + COL_ERROR, + COL_HIGHLIGHT, + COL_FLASH, + COL_GHOST, + COL_ZOMBIE, + COL_VAMPIRE, + COL_DONE, + NCOLOURS +}; + +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(NORMAL,Normal,n) \ + A(TRICKY,Tricky,t) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const undead_diffnames[] = { DIFFLIST(TITLE) "(count)" }; +static char const undead_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +struct game_params { + int w; /* Grid width */ + int h; /* Grid height */ + int diff; /* Puzzle difficulty */ +}; + +static const struct game_params undead_presets[] = { + { 4, 4, DIFF_EASY }, + { 4, 4, DIFF_NORMAL }, + { 4, 4, DIFF_TRICKY }, + { 5, 5, DIFF_EASY }, + { 5, 5, DIFF_NORMAL }, + { 5, 5, DIFF_TRICKY }, + { 7, 7, DIFF_EASY }, + { 7, 7, DIFF_NORMAL } +}; + +#define DEFAULT_PRESET 1 + +static game_params *default_params(void) { + game_params *ret = snew(game_params); + + *ret = undead_presets[DEFAULT_PRESET]; + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) { + game_params *ret; + char buf[64]; + + if (i < 0 || i >= lenof(undead_presets)) return FALSE; + + ret = default_params(); + *ret = undead_presets[i]; /* struct copy */ + *params = ret; + + sprintf(buf, "%dx%d %s", + undead_presets[i].w, undead_presets[i].h, + undead_diffnames[undead_presets[i].diff]); + *name = dupstr(buf); + + return TRUE; +} + +static void free_params(game_params *params) { + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) { + params->w = params->h = atoi(string); + + while (*string && isdigit((unsigned char) *string)) ++string; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + + params->diff = DIFF_NORMAL; + if (*string == 'd') { + int i; + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == undead_diffchars[i]) + params->diff = i; + if (*string) string++; + } + + return; +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[256]; + sprintf(buf, "%dx%d", params->w, params->h); + if (full) + sprintf(buf + strlen(buf), "d%c", undead_diffchars[params->diff]); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[64]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if ((params->w * params->h ) > 54) return "Grid is too big"; + if (params->w < 3) return "Width must be at least 3"; + if (params->h < 3) return "Height must be at least 3"; + if (params->diff >= DIFFCOUNT) return "Unknown difficulty rating"; + return NULL; +} + +/* --------------------------------------------------------------- */ +/* Game state allocation, deallocation. */ + +struct path { + int length; + int *p; + int grid_start; + int grid_end; + int num_monsters; + int *mapping; + int sightings_start; + int sightings_end; + int *xy; +}; + +struct game_common { + int refcount; + struct game_params params; + int wh; + int num_ghosts,num_vampires,num_zombies,num_total; + int num_paths; + struct path *paths; + int *grid; + int *xinfo; + int *fixed; + int solved; +}; + +struct game_state { + struct game_common *common; + int *guess; + unsigned char *pencils; + unsigned char *cell_errors; + unsigned char *hint_errors; + unsigned char *hints_done; + unsigned char count_errors[3]; + int solved; + int cheated; +}; + +static game_state *new_state(const game_params *params) { + int i; + game_state *state = snew(game_state); + state->common = snew(struct game_common); + + state->common->refcount = 1; + state->common->params.w = params->w; + state->common->params.h = params->h; + state->common->params.diff = params->diff; + + state->common->wh = (state->common->params.w +2) * (state->common->params.h +2); + + state->common->num_ghosts = 0; + state->common->num_vampires = 0; + state->common->num_zombies = 0; + state->common->num_total = 0; + + state->common->grid = snewn(state->common->wh, int); + state->common->xinfo = snewn(state->common->wh, int); + state->common->fixed = NULL; + state->common->solved = FALSE; + + state->common->num_paths = + state->common->params.w + state->common->params.h; + state->common->paths = snewn(state->common->num_paths, struct path); + + for (i=0;icommon->num_paths;i++) { + state->common->paths[i].length = 0; + state->common->paths[i].grid_start = -1; + state->common->paths[i].grid_end = -1; + state->common->paths[i].num_monsters = 0; + state->common->paths[i].sightings_start = 0; + state->common->paths[i].sightings_end = 0; + state->common->paths[i].p = snewn(state->common->wh,int); + state->common->paths[i].xy = snewn(state->common->wh,int); + state->common->paths[i].mapping = snewn(state->common->wh,int); + } + + state->guess = NULL; + state->pencils = NULL; + + state->cell_errors = snewn(state->common->wh, unsigned char); + for (i=0;icommon->wh;i++) + state->cell_errors[i] = FALSE; + state->hint_errors = snewn(2*state->common->num_paths, unsigned char); + for (i=0;i<2*state->common->num_paths;i++) + state->hint_errors[i] = FALSE; + state->hints_done = snewn(2 * state->common->num_paths, unsigned char); + memset(state->hints_done, 0, + 2 * state->common->num_paths * sizeof(unsigned char)); + for (i=0;i<3;i++) + state->count_errors[i] = FALSE; + + state->solved = FALSE; + state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->common = state->common; + ret->common->refcount++; + + if (state->guess != NULL) { + ret->guess = snewn(ret->common->num_total,int); + memcpy(ret->guess, state->guess, ret->common->num_total*sizeof(int)); + } + else ret->guess = NULL; + + if (state->pencils != NULL) { + ret->pencils = snewn(ret->common->num_total,unsigned char); + memcpy(ret->pencils, state->pencils, + ret->common->num_total*sizeof(unsigned char)); + } + else ret->pencils = NULL; + + if (state->cell_errors != NULL) { + ret->cell_errors = snewn(ret->common->wh,unsigned char); + memcpy(ret->cell_errors, state->cell_errors, + ret->common->wh*sizeof(unsigned char)); + } + else ret->cell_errors = NULL; + + if (state->hint_errors != NULL) { + ret->hint_errors = snewn(2*ret->common->num_paths,unsigned char); + memcpy(ret->hint_errors, state->hint_errors, + 2*ret->common->num_paths*sizeof(unsigned char)); + } + else ret->hint_errors = NULL; + + if (state->hints_done != NULL) { + ret->hints_done = snewn(2 * state->common->num_paths, unsigned char); + memcpy(ret->hints_done, state->hints_done, + 2 * state->common->num_paths * sizeof(unsigned char)); + } + else ret->hints_done = NULL; + + ret->count_errors[0] = state->count_errors[0]; + ret->count_errors[1] = state->count_errors[1]; + ret->count_errors[2] = state->count_errors[2]; + + ret->solved = state->solved; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) { + int i; + + state->common->refcount--; + if (state->common->refcount == 0) { + for (i=0;icommon->num_paths;i++) { + sfree(state->common->paths[i].mapping); + sfree(state->common->paths[i].xy); + sfree(state->common->paths[i].p); + } + sfree(state->common->paths); + sfree(state->common->xinfo); + sfree(state->common->grid); + if (state->common->fixed != NULL) sfree(state->common->fixed); + sfree(state->common); + } + if (state->hints_done != NULL) sfree(state->hints_done); + if (state->hint_errors != NULL) sfree(state->hint_errors); + if (state->cell_errors != NULL) sfree(state->cell_errors); + if (state->pencils != NULL) sfree(state->pencils); + if (state->guess != NULL) sfree(state->guess); + sfree(state); + + return; +} + +/* --------------------------------------------------------------- */ +/* Puzzle generator */ + +/* cell states */ +enum { + CELL_EMPTY, + CELL_MIRROR_L, + CELL_MIRROR_R, + CELL_GHOST, + CELL_VAMPIRE, + CELL_ZOMBIE, + CELL_UNDEF +}; + +/* grid walk directions */ +enum { + DIRECTION_NONE, + DIRECTION_UP, + DIRECTION_RIGHT, + DIRECTION_LEFT, + DIRECTION_DOWN +}; + +int range2grid(int rangeno, int width, int height, int *x, int *y) { + + if (rangeno < 0) { + *x = 0; *y = 0; return DIRECTION_NONE; + } + if (rangeno < width) { + *x = rangeno+1; *y = 0; return DIRECTION_DOWN; + } + rangeno = rangeno - width; + if (rangeno < height) { + *x = width+1; *y = rangeno+1; return DIRECTION_LEFT; + } + rangeno = rangeno - height; + if (rangeno < width) { + *x = width-rangeno; *y = height+1; return DIRECTION_UP; + } + rangeno = rangeno - width; + if (rangeno < height) { + *x = 0; *y = height-rangeno; return DIRECTION_RIGHT; + } + *x = 0; *y = 0; + return DIRECTION_NONE; +} + +int grid2range(int x, int y, int w, int h) { + if (x>0 && x0 && yw+1 || y<0 || y>h+1) return -1; + if ((x == 0 || x==w+1) && (y==0 || y==h+1)) return -1; + if (y==0) return x-1; + if (x==(w+1)) return y-1+w; + if (y==(h+1)) return 2*w + h - x; + return 2*(w+h) - y; +} + +void make_paths(game_state *state) { + int i; + int count = 0; + + for (i=0;i<2*(state->common->params.w + state->common->params.h);i++) { + int x,y,dir; + int j,k,num_monsters; + int found; + int c,p; + found = FALSE; + /* Check whether inverse path is already in list */ + for (j=0;jcommon->paths[j].grid_end) { + found = TRUE; + break; + } + } + if (found) continue; + + /* We found a new path through the mirror maze */ + state->common->paths[count].grid_start = i; + dir = range2grid(i, state->common->params.w, + state->common->params.h,&x,&y); + state->common->paths[count].sightings_start = + state->common->grid[x+y*(state->common->params.w +2)]; + while (TRUE) { + int c,r; + + if (dir == DIRECTION_DOWN) y++; + else if (dir == DIRECTION_LEFT) x--; + else if (dir == DIRECTION_UP) y--; + else if (dir == DIRECTION_RIGHT) x++; + + r = grid2range(x, y, state->common->params.w, + state->common->params.h); + if (r != -1) { + state->common->paths[count].grid_end = r; + state->common->paths[count].sightings_end = + state->common->grid[x+y*(state->common->params.w +2)]; + break; + } + + c = state->common->grid[x+y*(state->common->params.w+2)]; + state->common->paths[count].xy[state->common->paths[count].length] = + x+y*(state->common->params.w+2); + if (c == CELL_MIRROR_L) { + state->common->paths[count].p[state->common->paths[count].length] = -1; + if (dir == DIRECTION_DOWN) dir = DIRECTION_RIGHT; + else if (dir == DIRECTION_LEFT) dir = DIRECTION_UP; + else if (dir == DIRECTION_UP) dir = DIRECTION_LEFT; + else if (dir == DIRECTION_RIGHT) dir = DIRECTION_DOWN; + } + else if (c == CELL_MIRROR_R) { + state->common->paths[count].p[state->common->paths[count].length] = -1; + if (dir == DIRECTION_DOWN) dir = DIRECTION_LEFT; + else if (dir == DIRECTION_LEFT) dir = DIRECTION_DOWN; + else if (dir == DIRECTION_UP) dir = DIRECTION_RIGHT; + else if (dir == DIRECTION_RIGHT) dir = DIRECTION_UP; + } + else { + state->common->paths[count].p[state->common->paths[count].length] = + state->common->xinfo[x+y*(state->common->params.w+2)]; + } + state->common->paths[count].length++; + } + /* Count unique monster entries in each path */ + state->common->paths[count].num_monsters = 0; + for (j=0;jcommon->num_total;j++) { + num_monsters = 0; + for (k=0;kcommon->paths[count].length;k++) + if (state->common->paths[count].p[k] == j) + num_monsters++; + if (num_monsters > 0) + state->common->paths[count].num_monsters++; + } + + /* Generate mapping vector */ + c = 0; + for (p=0;pcommon->paths[count].length;p++) { + int m; + m = state->common->paths[count].p[p]; + if (m == -1) continue; + found = FALSE; + for (j=0; jcommon->paths[count].mapping[j] == m) found = TRUE; + if (!found) state->common->paths[count].mapping[c++] = m; + } + count++; + } + return; +} + +struct guess { + int length; + int *guess; + int *possible; +}; + +int next_list(struct guess *g, int pos) { + + if (pos == 0) { + if ((g->guess[pos] == 1 && g->possible[pos] == 1) || + (g->guess[pos] == 2 && (g->possible[pos] == 3 || + g->possible[pos] == 2)) || + g->guess[pos] == 4) + return FALSE; + if (g->guess[pos] == 1 && (g->possible[pos] == 3 || + g->possible[pos] == 7)) { + g->guess[pos] = 2; return TRUE; + } + if (g->guess[pos] == 1 && g->possible[pos] == 5) { + g->guess[pos] = 4; return TRUE; + } + if (g->guess[pos] == 2 && (g->possible[pos] == 6 || g->possible[pos] == 7)) { + g->guess[pos] = 4; return TRUE; + } + } + + if (g->guess[pos] == 1) { + if (g->possible[pos] == 1) { + return next_list(g,pos-1); + } + if (g->possible[pos] == 3 || g->possible[pos] == 7) { + g->guess[pos] = 2; return TRUE; + } + if (g->possible[pos] == 5) { + g->guess[pos] = 4; return TRUE; + } + } + + if (g->guess[pos] == 2) { + if (g->possible[pos] == 2) { + return next_list(g,pos-1); + } + if (g->possible[pos] == 3) { + g->guess[pos] = 1; return next_list(g,pos-1); + } + if (g->possible[pos] == 6 || g->possible[pos] == 7) { + g->guess[pos] = 4; return TRUE; + } + } + + if (g->guess[pos] == 4) { + if (g->possible[pos] == 5 || g->possible[pos] == 7) { + g->guess[pos] = 1; return next_list(g,pos-1); + } + if (g->possible[pos] == 6) { + g->guess[pos] = 2; return next_list(g,pos-1); + } + if (g->possible[pos] == 4) { + return next_list(g,pos-1); + } + } + return FALSE; +} + +void get_unique(game_state *state, int counter, random_state *rs) { + + int p,i,c,pathlimit,count_uniques; + struct guess path_guess; + int *view_count; + + struct entry { + struct entry *link; + int *guess; + int start_view; + int end_view; + }; + + struct { + struct entry *head; + struct entry *node; + } views, single_views, test_views; + + struct entry test_entry; + + path_guess.length = state->common->paths[counter].num_monsters; + path_guess.guess = snewn(path_guess.length,int); + path_guess.possible = snewn(path_guess.length,int); + for (i=0;iguess[state->common->paths[counter].mapping[p]]; + switch (path_guess.possible[p]) { + case 1: path_guess.guess[p] = 1; break; + case 2: path_guess.guess[p] = 2; break; + case 3: path_guess.guess[p] = 1; break; + case 4: path_guess.guess[p] = 4; break; + case 5: path_guess.guess[p] = 1; break; + case 6: path_guess.guess[p] = 2; break; + case 7: path_guess.guess[p] = 1; break; + } + } + + views.head = NULL; + views.node = NULL; + + pathlimit = state->common->paths[counter].length + 1; + view_count = snewn(pathlimit*pathlimit, int); + for (i = 0; i < pathlimit*pathlimit; i++) + view_count[i] = 0; + + do { + int mirror, start_view, end_view; + + mirror = FALSE; + start_view = 0; + for (p=0;pcommon->paths[counter].length;p++) { + if (state->common->paths[counter].p[p] == -1) mirror = TRUE; + else { + for (i=0;icommon->paths[counter].p[p] == + state->common->paths[counter].mapping[i]) { + if (path_guess.guess[i] == 1 && mirror == TRUE) + start_view++; + if (path_guess.guess[i] == 2 && mirror == FALSE) + start_view++; + if (path_guess.guess[i] == 4) + start_view++; + break; + } + } + } + } + mirror = FALSE; + end_view = 0; + for (p=state->common->paths[counter].length-1;p>=0;p--) { + if (state->common->paths[counter].p[p] == -1) mirror = TRUE; + else { + for (i=0;icommon->paths[counter].p[p] == + state->common->paths[counter].mapping[i]) { + if (path_guess.guess[i] == 1 && mirror == TRUE) + end_view++; + if (path_guess.guess[i] == 2 && mirror == FALSE) + end_view++; + if (path_guess.guess[i] == 4) + end_view++; + break; + } + } + } + } + + assert(start_view >= 0 && start_view < pathlimit); + assert(end_view >= 0 && end_view < pathlimit); + i = start_view * pathlimit + end_view; + view_count[i]++; + if (view_count[i] == 1) { + views.node = snewn(1,struct entry); + views.node->link = views.head; + views.node->guess = snewn(path_guess.length,int); + views.head = views.node; + views.node->start_view = start_view; + views.node->end_view = end_view; + memcpy(views.node->guess, path_guess.guess, + path_guess.length*sizeof(int)); + } + } while (next_list(&path_guess, path_guess.length-1)); + + /* extract single entries from view list */ + + test_views.head = views.head; + test_views.node = views.node; + + test_entry.guess = snewn(path_guess.length,int); + + single_views.head = NULL; + single_views.node = NULL; + + count_uniques = 0; + while (test_views.head != NULL) { + test_views.node = test_views.head; + test_views.head = test_views.head->link; + i = test_views.node->start_view * pathlimit + test_views.node->end_view; + if (view_count[i] == 1) { + single_views.node = snewn(1,struct entry); + single_views.node->link = single_views.head; + single_views.node->guess = snewn(path_guess.length,int); + single_views.head = single_views.node; + single_views.node->start_view = test_views.node->start_view; + single_views.node->end_view = test_views.node->end_view; + memcpy(single_views.node->guess, test_views.node->guess, + path_guess.length*sizeof(int)); + count_uniques++; + } + } + + sfree(view_count); + + if (count_uniques > 0) { + test_entry.start_view = 0; + test_entry.end_view = 0; + /* Choose one unique guess per random */ + /* While we are busy with looping through single_views, we + * conveniently free the linked list single_view */ + c = random_upto(rs,count_uniques); + while(single_views.head != NULL) { + single_views.node = single_views.head; + single_views.head = single_views.head->link; + if (c-- == 0) { + memcpy(test_entry.guess, single_views.node->guess, + path_guess.length*sizeof(int)); + test_entry.start_view = single_views.node->start_view; + test_entry.end_view = single_views.node->end_view; + } + sfree(single_views.node->guess); + sfree(single_views.node); + } + + /* Modify state_guess according to path_guess.mapping */ + for (i=0;iguess[state->common->paths[counter].mapping[i]] = + test_entry.guess[i]; + } + + sfree(test_entry.guess); + + while (views.head != NULL) { + views.node = views.head; + views.head = views.head->link; + sfree(views.node->guess); + sfree(views.node); + } + + sfree(path_guess.possible); + sfree(path_guess.guess); + + return; +} + +int count_monsters(game_state *state, + int *cGhost, int *cVampire, int *cZombie) { + int cNone; + int i; + + *cGhost = *cVampire = *cZombie = cNone = 0; + + for (i=0;icommon->num_total;i++) { + if (state->guess[i] == 1) (*cGhost)++; + else if (state->guess[i] == 2) (*cVampire)++; + else if (state->guess[i] == 4) (*cZombie)++; + else cNone++; + } + + return cNone; +} + +int check_numbers(game_state *state, int *guess) { + int valid; + int i; + int count_ghosts, count_vampires, count_zombies; + + count_ghosts = count_vampires = count_zombies = 0; + for (i=0;icommon->num_total;i++) { + if (guess[i] == 1) count_ghosts++; + if (guess[i] == 2) count_vampires++; + if (guess[i] == 4) count_zombies++; + } + + valid = TRUE; + + if (count_ghosts > state->common->num_ghosts) valid = FALSE; + if (count_vampires > state->common->num_vampires) valid = FALSE; + if (count_zombies > state->common->num_zombies) valid = FALSE; + + return valid; +} + +int check_solution(int *g, struct path path) { + int i; + int mirror; + int count; + + count = 0; + mirror = FALSE; + for (i=0;i=0;i--) { + if (path.p[i] == -1) mirror = TRUE; + else { + if (g[path.p[i]] == 1 && mirror) count++; + else if (g[path.p[i]] == 2 && !mirror) count++; + else if (g[path.p[i]] == 4) count++; + } + } + if (count != path.sightings_end) return FALSE; + + return TRUE; +} + +int solve_iterative(game_state *state, struct path *paths) { + int solved; + int p,i,j,count; + + int *guess; + int *possible; + + struct guess loop; + + solved = TRUE; + loop.length = state->common->num_total; + guess = snewn(state->common->num_total,int); + possible = snewn(state->common->num_total,int); + + for (i=0;icommon->num_total;i++) { + guess[i] = state->guess[i]; + possible[i] = 0; + } + + for (p=0;pcommon->num_paths;p++) { + if (paths[p].num_monsters > 0) { + loop.length = paths[p].num_monsters; + loop.guess = snewn(paths[p].num_monsters,int); + loop.possible = snewn(paths[p].num_monsters,int); + + for (i=0;iguess[paths[p].mapping[i]]) { + case 1: loop.guess[i] = 1; break; + case 2: loop.guess[i] = 2; break; + case 3: loop.guess[i] = 1; break; + case 4: loop.guess[i] = 4; break; + case 5: loop.guess[i] = 1; break; + case 6: loop.guess[i] = 2; break; + case 7: loop.guess[i] = 1; break; + } + loop.possible[i] = state->guess[paths[p].mapping[i]]; + possible[paths[p].mapping[i]] = 0; + } + + while(TRUE) { + for (i=0;icommon->num_total;i++) { + guess[i] = state->guess[i]; + } + count = 0; + for (i=0;iguess[paths[p].mapping[i]] &= + possible[paths[p].mapping[i]]; + sfree(loop.possible); + sfree(loop.guess); + } + } + + for (i=0;icommon->num_total;i++) { + if (state->guess[i] == 3 || state->guess[i] == 5 || + state->guess[i] == 6 || state->guess[i] == 7) { + solved = FALSE; break; + } + } + + sfree(possible); + sfree(guess); + + return solved; +} + +int solve_bruteforce(game_state *state, struct path *paths) { + int solved, correct; + int number_solutions; + int p,i; + + struct guess loop; + + loop.guess = snewn(state->common->num_total,int); + loop.possible = snewn(state->common->num_total,int); + + for (i=0;icommon->num_total;i++) { + loop.possible[i] = state->guess[i]; + switch (state->guess[i]) { + case 1: loop.guess[i] = 1; break; + case 2: loop.guess[i] = 2; break; + case 3: loop.guess[i] = 1; break; + case 4: loop.guess[i] = 4; break; + case 5: loop.guess[i] = 1; break; + case 6: loop.guess[i] = 2; break; + case 7: loop.guess[i] = 1; break; + } + } + + solved = FALSE; + number_solutions = 0; + + while (TRUE) { + + correct = TRUE; + if (!check_numbers(state,loop.guess)) correct = FALSE; + else + for (p=0;pcommon->num_paths;p++) + if (!check_solution(loop.guess,paths[p])) { + correct = FALSE; break; + } + if (correct) { + number_solutions++; + solved = TRUE; + if(number_solutions > 1) { + solved = FALSE; + break; + } + for (i=0;icommon->num_total;i++) + state->guess[i] = loop.guess[i]; + } + if (!next_list(&loop,state->common->num_total -1)) { + break; + } + } + + sfree(loop.possible); + sfree(loop.guess); + + return solved; +} + +int path_cmp(const void *a, const void *b) { + const struct path *pa = (const struct path *)a; + const struct path *pb = (const struct path *)b; + return pa->num_monsters - pb->num_monsters; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) { + int i,count,c,w,h,r,p,g; + game_state *new; + + /* Variables for puzzle generation algorithm */ + int filling; + int max_length; + int count_ghosts, count_vampires, count_zombies; + int abort; + float ratio; + + /* Variables for solver algorithm */ + int solved_iterative, solved_bruteforce, contains_inconsistency, + count_ambiguous; + int iterative_depth; + int *old_guess; + + /* Variables for game description generation */ + int x,y; + char *e; + char *desc; + + i = 0; + while (TRUE) { + new = new_state(params); + abort = FALSE; + + /* Fill grid with random mirrors and (later to be populated) + * empty monster cells */ + count = 0; + for (h=1;hcommon->params.h+1;h++) + for (w=1;wcommon->params.w+1;w++) { + c = random_upto(rs,5); + if (c >= 2) { + new->common->grid[w+h*(new->common->params.w+2)] = CELL_EMPTY; + new->common->xinfo[w+h*(new->common->params.w+2)] = count++; + } + else if (c == 0) { + new->common->grid[w+h*(new->common->params.w+2)] = + CELL_MIRROR_L; + new->common->xinfo[w+h*(new->common->params.w+2)] = -1; + } + else { + new->common->grid[w+h*(new->common->params.w+2)] = + CELL_MIRROR_R; + new->common->xinfo[w+h*(new->common->params.w+2)] = -1; + } + } + new->common->num_total = count; /* Total number of monsters in maze */ + + /* Puzzle is boring if it has too few monster cells. Discard + * grid, make new grid */ + if (new->common->num_total <= 4) { + free_game(new); + continue; + } + + /* Monsters / Mirrors ratio should be balanced */ + ratio = (float)new->common->num_total / + (float)(new->common->params.w * new->common->params.h); + if (ratio < 0.48 || ratio > 0.78) { + free_game(new); + continue; + } + + /* Assign clue identifiers */ + for (r=0;r<2*(new->common->params.w+new->common->params.h);r++) { + int x,y,gridno; + gridno = range2grid(r,new->common->params.w,new->common->params.h, + &x,&y); + new->common->grid[x+y*(new->common->params.w +2)] = gridno; + new->common->xinfo[x+y*(new->common->params.w +2)] = 0; + } + /* The four corners don't matter at all for the game. Set them + * all to zero, just to have a nice data structure */ + new->common->grid[0] = 0; + new->common->xinfo[0] = 0; + new->common->grid[new->common->params.w+1] = 0; + new->common->xinfo[new->common->params.w+1] = 0; + new->common->grid[new->common->params.w+1 + (new->common->params.h+1)*(new->common->params.w+2)] = 0; + new->common->xinfo[new->common->params.w+1 + (new->common->params.h+1)*(new->common->params.w+2)] = 0; + new->common->grid[(new->common->params.h+1)*(new->common->params.w+2)] = 0; + new->common->xinfo[(new->common->params.h+1)*(new->common->params.w+2)] = 0; + + /* Initialize solution vector */ + new->guess = snewn(new->common->num_total,int); + for (g=0;gcommon->num_total;g++) new->guess[g] = 7; + + /* Initialize fixed flag from common. Not needed for the + * puzzle generator; initialize it for having clean code */ + new->common->fixed = snewn(new->common->num_total,int); + for (g=0;gcommon->num_total;g++) + new->common->fixed[g] = FALSE; + + /* paths generation */ + make_paths(new); + + /* Grid is invalid if max. path length > threshold. Discard + * grid, make new one */ + switch (new->common->params.diff) { + case DIFF_EASY: max_length = min(new->common->params.w,new->common->params.h) + 1; break; + case DIFF_NORMAL: max_length = (max(new->common->params.w,new->common->params.h) * 3) / 2; break; + case DIFF_TRICKY: max_length = 9; break; + default: max_length = 9; break; + } + + for (p=0;pcommon->num_paths;p++) { + if (new->common->paths[p].num_monsters > max_length) { + abort = TRUE; + } + } + if (abort) { + free_game(new); + continue; + } + + qsort(new->common->paths, new->common->num_paths, + sizeof(struct path), path_cmp); + + /* Grid monster initialization */ + /* For easy puzzles, we try to fill nearly the whole grid + with unique solution paths (up to 2) For more difficult + puzzles, we fill only roughly half the grid, and choose + random monsters for the rest For hard puzzles, we fill + even less paths with unique solutions */ + + switch (new->common->params.diff) { + case DIFF_EASY: filling = 2; break; + case DIFF_NORMAL: filling = min( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break; + case DIFF_TRICKY: filling = max( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break; + default: filling = 0; break; + } + + count = 0; + while ( (count_monsters(new, &count_ghosts, &count_vampires, + &count_zombies)) > filling) { + if ((count) >= new->common->num_paths) break; + if (new->common->paths[count].num_monsters == 0) { + count++; + continue; + } + get_unique(new,count,rs); + count++; + } + + /* Fill any remaining ambiguous entries with random monsters */ + for(g=0;gcommon->num_total;g++) { + if (new->guess[g] == 7) { + r = random_upto(rs,3); + new->guess[g] = (r == 0) ? 1 : ( (r == 1) ? 2 : 4 ); + } + } + + /* Determine all hints */ + count_monsters(new, &new->common->num_ghosts, + &new->common->num_vampires, &new->common->num_zombies); + + /* Puzzle is trivial if it has only one type of monster. Discard. */ + if ((new->common->num_ghosts == 0 && new->common->num_vampires == 0) || + (new->common->num_ghosts == 0 && new->common->num_zombies == 0) || + (new->common->num_vampires == 0 && new->common->num_zombies == 0)) { + free_game(new); + continue; + } + + /* Discard puzzle if difficulty Tricky, and it has only 1 + * member of any monster type */ + if (new->common->params.diff == DIFF_TRICKY && + (new->common->num_ghosts <= 1 || + new->common->num_vampires <= 1 || new->common->num_zombies <= 1)) { + free_game(new); + continue; + } + + for (w=1;wcommon->params.w+1;w++) + for (h=1;hcommon->params.h+1;h++) { + c = new->common->xinfo[w+h*(new->common->params.w+2)]; + if (c >= 0) { + if (new->guess[c] == 1) new->common->grid[w+h*(new->common->params.w+2)] = CELL_GHOST; + if (new->guess[c] == 2) new->common->grid[w+h*(new->common->params.w+2)] = CELL_VAMPIRE; + if (new->guess[c] == 4) new->common->grid[w+h*(new->common->params.w+2)] = CELL_ZOMBIE; + } + } + + /* Prepare path information needed by the solver (containing all hints) */ + for (p=0;pcommon->num_paths;p++) { + int mirror,x,y; + + new->common->paths[p].sightings_start = 0; + new->common->paths[p].sightings_end = 0; + + mirror = FALSE; + for (g=0;gcommon->paths[p].length;g++) { + + if (new->common->paths[p].p[g] == -1) mirror = TRUE; + else { + if (new->guess[new->common->paths[p].p[g]] == 1 && mirror == TRUE) (new->common->paths[p].sightings_start)++; + else if (new->guess[new->common->paths[p].p[g]] == 2 && mirror == FALSE) (new->common->paths[p].sightings_start)++; + else if (new->guess[new->common->paths[p].p[g]] == 4) (new->common->paths[p].sightings_start)++; + } + } + + mirror = FALSE; + for (g=new->common->paths[p].length-1;g>=0;g--) { + if (new->common->paths[p].p[g] == -1) mirror = TRUE; + else { + if (new->guess[new->common->paths[p].p[g]] == 1 && mirror == TRUE) (new->common->paths[p].sightings_end)++; + else if (new->guess[new->common->paths[p].p[g]] == 2 && mirror == FALSE) (new->common->paths[p].sightings_end)++; + else if (new->guess[new->common->paths[p].p[g]] == 4) (new->common->paths[p].sightings_end)++; + } + } + + range2grid(new->common->paths[p].grid_start, + new->common->params.w,new->common->params.h,&x,&y); + new->common->grid[x+y*(new->common->params.w +2)] = + new->common->paths[p].sightings_start; + range2grid(new->common->paths[p].grid_end, + new->common->params.w,new->common->params.h,&x,&y); + new->common->grid[x+y*(new->common->params.w +2)] = + new->common->paths[p].sightings_end; + } + + /* Try to solve the puzzle with the iterative solver */ + old_guess = snewn(new->common->num_total,int); + for (p=0;pcommon->num_total;p++) { + new->guess[p] = 7; + old_guess[p] = 7; + } + iterative_depth = 0; + solved_iterative = FALSE; + contains_inconsistency = FALSE; + count_ambiguous = 0; + + while (TRUE) { + int no_change; + no_change = TRUE; + solved_iterative = solve_iterative(new,new->common->paths); + iterative_depth++; + for (p=0;pcommon->num_total;p++) { + if (new->guess[p] != old_guess[p]) no_change = FALSE; + old_guess[p] = new->guess[p]; + if (new->guess[p] == 0) contains_inconsistency = TRUE; + } + if (solved_iterative || no_change) break; + } + + /* If necessary, try to solve the puzzle with the brute-force solver */ + solved_bruteforce = FALSE; + if (new->common->params.diff != DIFF_EASY && + !solved_iterative && !contains_inconsistency) { + for (p=0;pcommon->num_total;p++) + if (new->guess[p] != 1 && new->guess[p] != 2 && + new->guess[p] != 4) count_ambiguous++; + + solved_bruteforce = solve_bruteforce(new, new->common->paths); + } + + /* Determine puzzle difficulty level */ + if (new->common->params.diff == DIFF_EASY && solved_iterative && + iterative_depth <= 3 && !contains_inconsistency) { +/* printf("Puzzle level: EASY Level %d Ratio %f Ambiguous %d (Found after %i tries)\n",iterative_depth, ratio, count_ambiguous, i); */ + break; + } + + if (new->common->params.diff == DIFF_NORMAL && + ((solved_iterative && iterative_depth > 3) || + (solved_bruteforce && count_ambiguous < 4)) && + !contains_inconsistency) { +/* printf("Puzzle level: NORMAL Level %d Ratio %f Ambiguous %d (Found after %d tries)\n", iterative_depth, ratio, count_ambiguous, i); */ + break; + } + if (new->common->params.diff == DIFF_TRICKY && + solved_bruteforce && iterative_depth > 0 && + count_ambiguous >= 4 && !contains_inconsistency) { +/* printf("Puzzle level: TRICKY Level %d Ratio %f Ambiguous %d (Found after %d tries)\n", iterative_depth, ratio, count_ambiguous, i); */ + break; + } + + /* If puzzle is not solvable or does not satisfy the desired + * difficulty level, free memory and start from scratch */ + sfree(old_guess); + free_game(new); + i++; + } + + /* We have a valid puzzle! */ + + desc = snewn(10 + new->common->wh + + 6*(new->common->params.w + new->common->params.h), char); + e = desc; + + /* Encode monster counts */ + e += sprintf(e, "%d,", new->common->num_ghosts); + e += sprintf(e, "%d,", new->common->num_vampires); + e += sprintf(e, "%d,", new->common->num_zombies); + + /* Encode grid */ + count = 0; + for (y=1;ycommon->params.h+1;y++) + for (x=1;xcommon->params.w+1;x++) { + c = new->common->grid[x+y*(new->common->params.w+2)]; + if (count > 25) { + *e++ = 'z'; + count -= 26; + } + if (c != CELL_MIRROR_L && c != CELL_MIRROR_R) { + count++; + } + else if (c == CELL_MIRROR_L) { + if (count > 0) *e++ = (count-1 + 'a'); + *e++ = 'L'; + count = 0; + } + else { + if (count > 0) *e++ = (count-1 + 'a'); + *e++ = 'R'; + count = 0; + } + } + if (count > 0) *e++ = (count-1 + 'a'); + + /* Encode hints */ + for (p=0;p<2*(new->common->params.w + new->common->params.h);p++) { + range2grid(p,new->common->params.w,new->common->params.h,&x,&y); + e += sprintf(e, ",%d", new->common->grid[x+y*(new->common->params.w+2)]); + } + + *e++ = '\0'; + desc = sresize(desc, e - desc, char); + + sfree(old_guess); + free_game(new); + + return desc; +} + +void num2grid(int num, int width, int height, int *x, int *y) { + *x = 1+(num%width); + *y = 1+(num/width); + return; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int i; + int n; + int count; + + game_state *state = new_state(params); + + state->common->num_ghosts = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + desc++; + state->common->num_vampires = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + desc++; + state->common->num_zombies = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + desc++; + + state->common->num_total = state->common->num_ghosts + state->common->num_vampires + state->common->num_zombies; + + state->guess = snewn(state->common->num_total,int); + state->pencils = snewn(state->common->num_total,unsigned char); + state->common->fixed = snewn(state->common->num_total,int); + for (i=0;icommon->num_total;i++) { + state->guess[i] = 7; + state->pencils[i] = 0; + state->common->fixed[i] = FALSE; + } + for (i=0;icommon->wh;i++) + state->cell_errors[i] = FALSE; + for (i=0;i<2*state->common->num_paths;i++) + state->hint_errors[i] = FALSE; + for (i=0;i<3;i++) + state->count_errors[i] = FALSE; + + count = 0; + n = 0; + while (*desc != ',') { + int c; + int x,y; + + if (*desc == 'L') { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_MIRROR_L; + state->common->xinfo[x+y*(state->common->params.w+2)] = -1; + n++; + } + else if (*desc == 'R') { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_MIRROR_R; + state->common->xinfo[x+y*(state->common->params.w+2)] = -1; + n++; + } + else if (*desc == 'G') { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_GHOST; + state->common->xinfo[x+y*(state->common->params.w+2)] = count; + state->guess[count] = 1; + state->common->fixed[count++] = TRUE; + n++; + } + else if (*desc == 'V') { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_VAMPIRE; + state->common->xinfo[x+y*(state->common->params.w+2)] = count; + state->guess[count] = 2; + state->common->fixed[count++] = TRUE; + n++; + } + else if (*desc == 'Z') { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_ZOMBIE; + state->common->xinfo[x+y*(state->common->params.w+2)] = count; + state->guess[count] = 4; + state->common->fixed[count++] = TRUE; + n++; + } + else { + c = *desc - ('a' -1); + while (c-- > 0) { + num2grid(n,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = CELL_EMPTY; + state->common->xinfo[x+y*(state->common->params.w+2)] = count; + state->guess[count] = 7; + state->common->fixed[count++] = FALSE; + n++; + } + } + desc++; + } + desc++; + + for (i=0;i<2*(state->common->params.w + state->common->params.h);i++) { + int x,y; + int sights; + + sights = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + desc++; + + + range2grid(i,state->common->params.w,state->common->params.h,&x,&y); + state->common->grid[x+y*(state->common->params.w +2)] = sights; + state->common->xinfo[x+y*(state->common->params.w +2)] = -2; + } + + state->common->grid[0] = 0; + state->common->xinfo[0] = -2; + state->common->grid[state->common->params.w+1] = 0; + state->common->xinfo[state->common->params.w+1] = -2; + state->common->grid[state->common->params.w+1 + (state->common->params.h+1)*(state->common->params.w+2)] = 0; + state->common->xinfo[state->common->params.w+1 + (state->common->params.h+1)*(state->common->params.w+2)] = -2; + state->common->grid[(state->common->params.h+1)*(state->common->params.w+2)] = 0; + state->common->xinfo[(state->common->params.h+1)*(state->common->params.w+2)] = -2; + + make_paths(state); + qsort(state->common->paths, state->common->num_paths, sizeof(struct path), path_cmp); + + return state; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int i; + int w = params->w, h = params->h; + int wh = w*h; + int area; + int monsters; + int monster_count; + const char *desc_s = desc; + + for (i=0;i<3;i++) { + if (!*desc) return "Faulty game description"; + while (*desc && isdigit((unsigned char)*desc)) { desc++; } + if (*desc != ',') return "Invalid character in number list"; + desc++; + } + desc = desc_s; + + area = monsters = monster_count = 0; + for (i=0;i<3;i++) { + monster_count += atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + desc++; + } + while (*desc && *desc != ',') { + if (*desc >= 'a' && *desc <= 'z') { + area += *desc - 'a' +1; monsters += *desc - 'a' +1; + } else if (*desc == 'G' || *desc == 'V' || *desc == 'Z') { + area++; monsters++; + } else if (*desc == 'L' || *desc == 'R') { + area++; + } else + return "Invalid character in grid specification"; + desc++; + } + if (area < wh) return "Not enough data to fill grid"; + else if (area > wh) return "Too much data to fill grid"; + if (monsters != monster_count) + return "Monster numbers do not match grid spaces"; + + for (i = 0; i < 2*(w+h); i++) { + if (!*desc) return "Not enough numbers given after grid specification"; + else if (*desc != ',') return "Invalid character in number list"; + desc++; + while (*desc && isdigit((unsigned char)*desc)) { desc++; } + } + + if (*desc) return "Unexpected additional data at end of game description"; + + return NULL; +} + +static char *solve_game(const game_state *state_start, const game_state *currstate, + const char *aux, char **error) +{ + int p; + int *old_guess; + int iterative_depth; + int solved_iterative, solved_bruteforce, contains_inconsistency, + count_ambiguous; + + int i; + char *move, *c; + + game_state *solve_state = dup_game(currstate); + + old_guess = snewn(solve_state->common->num_total,int); + for (p=0;pcommon->num_total;p++) { + if (solve_state->common->fixed[p]) { + old_guess[p] = solve_state->guess[p] = state_start->guess[p]; + } + else { + old_guess[p] = solve_state->guess[p] = 7; + } + } + iterative_depth = 0; + solved_iterative = FALSE; + contains_inconsistency = FALSE; + count_ambiguous = 0; + + /* Try to solve the puzzle with the iterative solver */ + while (TRUE) { + int no_change; + no_change = TRUE; + solved_iterative = + solve_iterative(solve_state,solve_state->common->paths); + iterative_depth++; + for (p=0;pcommon->num_total;p++) { + if (solve_state->guess[p] != old_guess[p]) no_change = FALSE; + old_guess[p] = solve_state->guess[p]; + if (solve_state->guess[p] == 0) contains_inconsistency = TRUE; + } + if (solved_iterative || no_change || contains_inconsistency) break; + } + + if (contains_inconsistency) { + *error = "Puzzle is inconsistent"; + sfree(old_guess); + free_game(solve_state); + return NULL; + } + + /* If necessary, try to solve the puzzle with the brute-force solver */ + solved_bruteforce = FALSE; + if (!solved_iterative) { + for (p=0;pcommon->num_total;p++) + if (solve_state->guess[p] != 1 && solve_state->guess[p] != 2 && + solve_state->guess[p] != 4) count_ambiguous++; + solved_bruteforce = + solve_bruteforce(solve_state, solve_state->common->paths); + } + + if (!solved_iterative && !solved_bruteforce) { + *error = "Puzzle is unsolvable"; + sfree(old_guess); + free_game(solve_state); + return NULL; + } + +/* printf("Puzzle solved at level %s, iterations %d, ambiguous %d\n", (solved_bruteforce ? "TRICKY" : "NORMAL"), iterative_depth, count_ambiguous); */ + + move = snewn(solve_state->common->num_total * 4 +2, char); + c = move; + *c++='S'; + for (i = 0; i < solve_state->common->num_total; i++) { + if (solve_state->guess[i] == 1) c += sprintf(c, ";G%d", i); + if (solve_state->guess[i] == 2) c += sprintf(c, ";V%d", i); + if (solve_state->guess[i] == 4) c += sprintf(c, ";Z%d", i); + } + *c++ = '\0'; + move = sresize(move, c - move, char); + + sfree(old_guess); + free_game(solve_state); + return move; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w,h,c,r,xi,g; + char *ret; + char buf[120]; + + ret = snewn(50 + 6*(state->common->params.w +2) + + 6*(state->common->params.h+2) + + 3*(state->common->params.w * state->common->params.h), char); + + sprintf(ret,"G: %d V: %d Z: %d\n\n",state->common->num_ghosts, + state->common->num_vampires, state->common->num_zombies); + + for (h=0;hcommon->params.h+2;h++) { + for (w=0;wcommon->params.w+2;w++) { + c = state->common->grid[w+h*(state->common->params.w+2)]; + xi = state->common->xinfo[w+h*(state->common->params.w+2)]; + r = grid2range(w,h,state->common->params.w,state->common->params.h); + if (r != -1) { + sprintf(buf,"%2d", c); strcat(ret,buf); + } else if (c == CELL_MIRROR_L) { + sprintf(buf," \\"); strcat(ret,buf); + } else if (c == CELL_MIRROR_R) { + sprintf(buf," /"); strcat(ret,buf); + } else if (xi >= 0) { + g = state->guess[xi]; + if (g == 1) { sprintf(buf," G"); strcat(ret,buf); } + else if (g == 2) { sprintf(buf," V"); strcat(ret,buf); } + else if (g == 4) { sprintf(buf," Z"); strcat(ret,buf); } + else { sprintf(buf," ."); strcat(ret,buf); } + } else { + sprintf(buf," "); strcat(ret,buf); + } + } + sprintf(buf,"\n"); strcat(ret,buf); + } + + return ret; +} + +struct game_ui { + int hx, hy; /* as for solo.c, highlight pos */ + int hshow, hpencil, hcursor; /* show state, type, and ?cursor. */ + int ascii; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + ui->ascii = FALSE; + return ui; +} + +static void free_ui(game_ui *ui) { + sfree(ui); + return; +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ + return; +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + /* See solo.c; if we were pencil-mode highlighting and + * somehow a square has just been properly filled, cancel + * pencil mode. */ + if (ui->hshow && ui->hpencil && !ui->hcursor) { + int g = newstate->guess[newstate->common->xinfo[ui->hx + ui->hy*(newstate->common->params.w+2)]]; + if (g == 1 || g == 2 || g == 4) + ui->hshow = 0; + } +} + +struct game_drawstate { + int tilesize, started, solved; + int w, h; + + int *monsters; + unsigned char *pencils; + + unsigned char count_errors[3]; + unsigned char *cell_errors; + unsigned char *hint_errors; + unsigned char *hints_done; + + int hx, hy, hshow, hpencil; /* as for game_ui. */ + int hflash; + int ascii; +}; + +static int is_clue(const game_state *state, int x, int y) +{ + int h = state->common->params.h, w = state->common->params.w; + + if (((x == 0 || x == w + 1) && y > 0 && y <= h) || + ((y == 0 || y == h + 1) && x > 0 && x <= w)) + return TRUE; + + return FALSE; +} + +static int clue_index(const game_state *state, int x, int y) +{ + int h = state->common->params.h, w = state->common->params.w; + + if (y == 0) + return x - 1; + else if (x == w + 1) + return w + y - 1; + else if (y == h + 1) + return 2 * w + h - x; + else if (x == 0) + return 2 * (w + h) - y; + + return -1; +} + +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE/4) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int gx,gy; + int g,xi; + char buf[80]; + + gx = ((x-BORDER-1) / TILESIZE ); + gy = ((y-BORDER-2) / TILESIZE ) - 1; + + if (button == 'a' || button == 'A') { + ui->ascii = !ui->ascii; + return ""; + } + + if (button == 'm' || button == 'M') { + return dupstr("M"); + } + + if (ui->hshow == 1 && ui->hpencil == 0) { + xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)]; + if (xi >= 0 && !state->common->fixed[xi]) { + if (button == 'g' || button == 'G' || button == '1') { + if (!ui->hcursor) ui->hshow = 0; + sprintf(buf,"G%d",xi); + return dupstr(buf); + } + if (button == 'v' || button == 'V' || button == '2') { + if (!ui->hcursor) ui->hshow = 0; + sprintf(buf,"V%d",xi); + return dupstr(buf); + } + if (button == 'z' || button == 'Z' || button == '3') { + if (!ui->hcursor) ui->hshow = 0; + sprintf(buf,"Z%d",xi); + return dupstr(buf); + } + if (button == 'e' || button == 'E' || button == CURSOR_SELECT2 || + button == '0' || button == '\b' ) { + if (!ui->hcursor) ui->hshow = 0; + sprintf(buf,"E%d",xi); + return dupstr(buf); + } + } + } + + if (IS_CURSOR_MOVE(button)) { + if (ui->hx == 0 && ui->hy == 0) { + ui->hx = 1; + ui->hy = 1; + } + else switch (button) { + case CURSOR_UP: ui->hy -= (ui->hy > 1) ? 1 : 0; break; + case CURSOR_DOWN: ui->hy += (ui->hy < ds->h) ? 1 : 0; break; + case CURSOR_RIGHT: ui->hx += (ui->hx < ds->w) ? 1 : 0; break; + case CURSOR_LEFT: ui->hx -= (ui->hx > 1) ? 1 : 0; break; + } + ui->hshow = ui->hcursor = 1; + return ""; + } + if (ui->hshow && button == CURSOR_SELECT) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + if (ui->hshow == 1 && ui->hpencil == 1) { + xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)]; + if (xi >= 0 && !state->common->fixed[xi]) { + if (button == 'g' || button == 'G' || button == '1') { + sprintf(buf,"g%d",xi); + if (!ui->hcursor) ui->hpencil = ui->hshow = 0; + return dupstr(buf); + } + if (button == 'v' || button == 'V' || button == '2') { + sprintf(buf,"v%d",xi); + if (!ui->hcursor) ui->hpencil = ui->hshow = 0; + return dupstr(buf); + } + if (button == 'z' || button == 'Z' || button == '3') { + sprintf(buf,"z%d",xi); + if (!ui->hcursor) ui->hpencil = ui->hshow = 0; + return dupstr(buf); + } + if (button == 'e' || button == 'E' || button == CURSOR_SELECT2 || + button == '0' || button == '\b') { + sprintf(buf,"E%d",xi); + if (!ui->hcursor) ui->hpencil = ui->hshow = 0; + return dupstr(buf); + } + } + } + + if (gx > 0 && gx < ds->w+1 && gy > 0 && gy < ds->h+1) { + xi = state->common->xinfo[gx+gy*(state->common->params.w+2)]; + if (xi >= 0 && !state->common->fixed[xi]) { + g = state->guess[xi]; + if (ui->hshow == 0) { + if (button == LEFT_BUTTON) { + ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + else if (button == RIGHT_BUTTON && g == 7) { + ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + } + else if (ui->hshow == 1) { + if (button == LEFT_BUTTON) { + if (ui->hpencil == 0) { + if (gx == ui->hx && gy == ui->hy) { + ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0; + ui->hx = 0; ui->hy = 0; + return ""; + } + else { + ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + } + else { + ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + } + else if (button == RIGHT_BUTTON) { + if (ui->hpencil == 0 && g == 7) { + ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + else { + if (gx == ui->hx && gy == ui->hy) { + ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0; + ui->hx = 0; ui->hy = 0; + return ""; + } + else if (g == 7) { + ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0; + ui->hx = gx; ui->hy = gy; + return ""; + } + } + } + } + } + } else if (button == LEFT_BUTTON) { + if (is_clue(state, gx, gy)) { + sprintf(buf, "D%d,%d", gx, gy); + return dupstr(buf); + } + } + + return NULL; +} + +int check_numbers_draw(game_state *state, int *guess) { + int valid, filled; + int i,x,y,xy; + int count_ghosts, count_vampires, count_zombies; + + count_ghosts = count_vampires = count_zombies = 0; + for (i=0;icommon->num_total;i++) { + if (guess[i] == 1) count_ghosts++; + if (guess[i] == 2) count_vampires++; + if (guess[i] == 4) count_zombies++; + } + + valid = TRUE; + filled = (count_ghosts + count_vampires + count_zombies >= + state->common->num_total); + + if (count_ghosts > state->common->num_ghosts || + (filled && count_ghosts != state->common->num_ghosts) ) { + valid = FALSE; + state->count_errors[0] = TRUE; + for (x=1;xcommon->params.w+1;x++) + for (y=1;ycommon->params.h+1;y++) { + xy = x+y*(state->common->params.w+2); + if (state->common->xinfo[xy] >= 0 && + guess[state->common->xinfo[xy]] == 1) + state->cell_errors[xy] = TRUE; + } + } + if (count_vampires > state->common->num_vampires || + (filled && count_vampires != state->common->num_vampires) ) { + valid = FALSE; + state->count_errors[1] = TRUE; + for (x=1;xcommon->params.w+1;x++) + for (y=1;ycommon->params.h+1;y++) { + xy = x+y*(state->common->params.w+2); + if (state->common->xinfo[xy] >= 0 && + guess[state->common->xinfo[xy]] == 2) + state->cell_errors[xy] = TRUE; + } + } + if (count_zombies > state->common->num_zombies || + (filled && count_zombies != state->common->num_zombies) ) { + valid = FALSE; + state->count_errors[2] = TRUE; + for (x=1;xcommon->params.w+1;x++) + for (y=1;ycommon->params.h+1;y++) { + xy = x+y*(state->common->params.w+2); + if (state->common->xinfo[xy] >= 0 && + guess[state->common->xinfo[xy]] == 4) + state->cell_errors[xy] = TRUE; + } + } + + return valid; +} + +int check_path_solution(game_state *state, int p) { + int i; + int mirror; + int count; + int correct; + int unfilled; + + count = 0; + mirror = FALSE; + correct = TRUE; + + unfilled = 0; + for (i=0;icommon->paths[p].length;i++) { + if (state->common->paths[p].p[i] == -1) mirror = TRUE; + else { + if (state->guess[state->common->paths[p].p[i]] == 1 && mirror) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 2 && !mirror) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 4) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 7) + unfilled++; + } + } + + if (count > state->common->paths[p].sightings_start || + count + unfilled < state->common->paths[p].sightings_start) + { + correct = FALSE; + state->hint_errors[state->common->paths[p].grid_start] = TRUE; + } + + count = 0; + mirror = FALSE; + unfilled = 0; + for (i=state->common->paths[p].length-1;i>=0;i--) { + if (state->common->paths[p].p[i] == -1) mirror = TRUE; + else { + if (state->guess[state->common->paths[p].p[i]] == 1 && mirror) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 2 && !mirror) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 4) + count++; + else if (state->guess[state->common->paths[p].p[i]] == 7) + unfilled++; + } + } + + if (count > state->common->paths[p].sightings_end || + count + unfilled < state->common->paths[p].sightings_end) + { + correct = FALSE; + state->hint_errors[state->common->paths[p].grid_end] = TRUE; + } + + if (!correct) { + for (i=0;icommon->paths[p].length;i++) + state->cell_errors[state->common->paths[p].xy[i]] = TRUE; + } + + return correct; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int x,y,n,p,i; + char c; + int correct; + int solver; + + game_state *ret = dup_game(state); + solver = FALSE; + + while (*move) { + c = *move; + if (c == 'S') { + move++; + solver = TRUE; + } + if (c == 'G' || c == 'V' || c == 'Z' || c == 'E' || + c == 'g' || c == 'v' || c == 'z') { + move++; + sscanf(move, "%d%n", &x, &n); + if (c == 'G') ret->guess[x] = 1; + if (c == 'V') ret->guess[x] = 2; + if (c == 'Z') ret->guess[x] = 4; + if (c == 'E') { ret->guess[x] = 7; ret->pencils[x] = 0; } + if (c == 'g') ret->pencils[x] ^= 1; + if (c == 'v') ret->pencils[x] ^= 2; + if (c == 'z') ret->pencils[x] ^= 4; + move += n; + } + if (c == 'D' && sscanf(move + 1, "%d,%d%n", &x, &y, &n) == 2 && + is_clue(ret, x, y)) { + ret->hints_done[clue_index(ret, x, y)] ^= 1; + move += n + 1; + } + if (c == 'M') { + /* + * Fill in absolutely all pencil marks in unfilled + * squares, for those who like to play by the rigorous + * approach of starting off in that state and eliminating + * things. + */ + for (i = 0; i < ret->common->wh; i++) + if (ret->guess[i] == 7) + ret->pencils[i] = 7; + move++; + } + if (*move == ';') move++; + } + + correct = TRUE; + + for (i=0;icommon->wh;i++) ret->cell_errors[i] = FALSE; + for (i=0;i<2*ret->common->num_paths;i++) ret->hint_errors[i] = FALSE; + for (i=0;i<3;i++) ret->count_errors[i] = FALSE; + + if (!check_numbers_draw(ret,ret->guess)) correct = FALSE; + + for (p=0;pcommon->num_paths;p++) + if (!check_path_solution(ret,p)) correct = FALSE; + + for (i=0;icommon->num_total;i++) + if (!(ret->guess[i] == 1 || ret->guess[i] == 2 || + ret->guess[i] == 4)) correct = FALSE; + + if (correct && !solver) ret->solved = TRUE; + if (solver) ret->cheated = TRUE; + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define PREFERRED_TILE_SIZE 64 + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = 2*BORDER+(params->w+2)*TILESIZE; + *y = 2*BORDER+(params->h+3)*TILESIZE; + return; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; + return; +} + +#define COLOUR(ret, i, r, g, b) ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b))) + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_TEXT * 3 + 0] = 0.0F; + ret[COL_TEXT * 3 + 1] = 0.0F; + ret[COL_TEXT * 3 + 2] = 0.0F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_FLASH * 3 + 0] = 1.0F; + ret[COL_FLASH * 3 + 1] = 1.0F; + ret[COL_FLASH * 3 + 2] = 1.0F; + + ret[COL_GHOST * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.5F; + ret[COL_GHOST * 3 + 1] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_GHOST * 3 + 2] = ret[COL_BACKGROUND * 3 + 0]; + + ret[COL_ZOMBIE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.5F; + ret[COL_ZOMBIE * 3 + 1] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_ZOMBIE * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.5F; + + ret[COL_VAMPIRE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_VAMPIRE * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.9F; + ret[COL_VAMPIRE * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.9F; + + ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F; + ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F; + ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int i; + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; + ds->started = ds->solved = FALSE; + ds->w = state->common->params.w; + ds->h = state->common->params.h; + ds->ascii = FALSE; + + ds->count_errors[0] = FALSE; + ds->count_errors[1] = FALSE; + ds->count_errors[2] = FALSE; + + ds->monsters = snewn(state->common->num_total,int); + for (i=0;i<(state->common->num_total);i++) + ds->monsters[i] = 7; + ds->pencils = snewn(state->common->num_total,unsigned char); + for (i=0;icommon->num_total;i++) + ds->pencils[i] = 0; + + ds->cell_errors = snewn(state->common->wh,unsigned char); + for (i=0;icommon->wh;i++) + ds->cell_errors[i] = FALSE; + ds->hint_errors = snewn(2*state->common->num_paths,unsigned char); + for (i=0;i<2*state->common->num_paths;i++) + ds->hint_errors[i] = FALSE; + ds->hints_done = snewn(2 * state->common->num_paths, unsigned char); + memset(ds->hints_done, 0, + 2 * state->common->num_paths * sizeof(unsigned char)); + + ds->hshow = ds->hpencil = ds->hflash = 0; + ds->hx = ds->hy = 0; + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) { + sfree(ds->hints_done); + sfree(ds->hint_errors); + sfree(ds->cell_errors); + sfree(ds->pencils); + sfree(ds->monsters); + sfree(ds); + return; +} + +static void draw_cell_background(drawing *dr, game_drawstate *ds, + const game_state *state, const game_ui *ui, + int x, int y) { + + int hon; + int dx,dy; + dx = BORDER+(x* ds->tilesize)+(TILESIZE/2); + dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE; + + hon = (ui->hshow && x == ui->hx && y == ui->hy); + draw_rect(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1,(hon && !ui->hpencil) ? COL_HIGHLIGHT : COL_BACKGROUND); + + if (hon && ui->hpencil) { + int coords[6]; + coords[0] = dx-(TILESIZE/2)+1; + coords[1] = dy-(TILESIZE/2)+1; + coords[2] = coords[0] + TILESIZE/2; + coords[3] = coords[1]; + coords[4] = coords[0]; + coords[5] = coords[1] + TILESIZE/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + draw_update(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1); + + return; +} + +static void draw_circle_or_point(drawing *dr, int cx, int cy, int radius, + int colour) +{ + if (radius > 0) + draw_circle(dr, cx, cy, radius, colour, colour); + else + draw_rect(dr, cx, cy, 1, 1, colour); +} + +static void draw_monster(drawing *dr, game_drawstate *ds, int x, int y, + int tilesize, int hflash, int monster) +{ + int black = (hflash ? COL_FLASH : COL_TEXT); + + if (monster == 1) { /* ghost */ + int poly[80], i, j; + + clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize/2+1); + draw_circle(dr,x,y,2*tilesize/5, COL_GHOST,black); + unclip(dr); + + i = 0; + poly[i++] = x - 2*tilesize/5; + poly[i++] = y-2; + poly[i++] = x - 2*tilesize/5; + poly[i++] = y + 2*tilesize/5; + + for (j = 0; j < 3; j++) { + int total = (2*tilesize/5) * 2; + int before = total * j / 3; + int after = total * (j+1) / 3; + int mid = (before + after) / 2; + poly[i++] = x - 2*tilesize/5 + mid; + poly[i++] = y + 2*tilesize/5 - (total / 6); + poly[i++] = x - 2*tilesize/5 + after; + poly[i++] = y + 2*tilesize/5; + } + + poly[i++] = x + 2*tilesize/5; + poly[i++] = y-2; + + clip(dr,x-(tilesize/2)+2,y,tilesize-3,tilesize-(tilesize/2)-1); + draw_polygon(dr, poly, i/2, COL_GHOST, black); + unclip(dr); + + draw_circle(dr,x-tilesize/6,y-tilesize/12,tilesize/10, + COL_BACKGROUND,black); + draw_circle(dr,x+tilesize/6,y-tilesize/12,tilesize/10, + COL_BACKGROUND,black); + + draw_circle_or_point(dr,x-tilesize/6+1+tilesize/48,y-tilesize/12, + tilesize/48,black); + draw_circle_or_point(dr,x+tilesize/6+1+tilesize/48,y-tilesize/12, + tilesize/48,black); + + } else if (monster == 2) { /* vampire */ + int poly[80], i; + + clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize/2); + draw_circle(dr,x,y,2*tilesize/5,black,black); + unclip(dr); + + clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize/2+1,tilesize/2); + draw_circle(dr,x-tilesize/7,y,2*tilesize/5-tilesize/7, + COL_VAMPIRE,black); + unclip(dr); + clip(dr,x,y-(tilesize/2)+2,tilesize/2+1,tilesize/2); + draw_circle(dr,x+tilesize/7,y,2*tilesize/5-tilesize/7, + COL_VAMPIRE,black); + unclip(dr); + + clip(dr,x-(tilesize/2)+2,y,tilesize-3,tilesize/2); + draw_circle(dr,x,y,2*tilesize/5, COL_VAMPIRE,black); + unclip(dr); + + draw_circle(dr, x-tilesize/7, y-tilesize/16, tilesize/16, + COL_BACKGROUND, black); + draw_circle(dr, x+tilesize/7, y-tilesize/16, tilesize/16, + COL_BACKGROUND, black); + draw_circle_or_point(dr, x-tilesize/7, y-tilesize/16, tilesize/48, + black); + draw_circle_or_point(dr, x+tilesize/7, y-tilesize/16, tilesize/48, + black); + + clip(dr, x-(tilesize/2)+2, y+tilesize/8, tilesize-3, tilesize/4); + + i = 0; + poly[i++] = x-3*tilesize/16; + poly[i++] = y+1*tilesize/8; + poly[i++] = x-2*tilesize/16; + poly[i++] = y+7*tilesize/24; + poly[i++] = x-1*tilesize/16; + poly[i++] = y+1*tilesize/8; + draw_polygon(dr, poly, i/2, COL_BACKGROUND, black); + i = 0; + poly[i++] = x+3*tilesize/16; + poly[i++] = y+1*tilesize/8; + poly[i++] = x+2*tilesize/16; + poly[i++] = y+7*tilesize/24; + poly[i++] = x+1*tilesize/16; + poly[i++] = y+1*tilesize/8; + draw_polygon(dr, poly, i/2, COL_BACKGROUND, black); + + draw_circle(dr, x, y-tilesize/5, 2*tilesize/5, COL_VAMPIRE, black); + unclip(dr); + + } else if (monster == 4) { /* zombie */ + draw_circle(dr,x,y,2*tilesize/5, COL_ZOMBIE,black); + + draw_line(dr, + x-tilesize/7-tilesize/16, y-tilesize/12-tilesize/16, + x-tilesize/7+tilesize/16, y-tilesize/12+tilesize/16, + black); + draw_line(dr, + x-tilesize/7+tilesize/16, y-tilesize/12-tilesize/16, + x-tilesize/7-tilesize/16, y-tilesize/12+tilesize/16, + black); + draw_line(dr, + x+tilesize/7-tilesize/16, y-tilesize/12-tilesize/16, + x+tilesize/7+tilesize/16, y-tilesize/12+tilesize/16, + black); + draw_line(dr, + x+tilesize/7+tilesize/16, y-tilesize/12-tilesize/16, + x+tilesize/7-tilesize/16, y-tilesize/12+tilesize/16, + black); + + clip(dr, x-tilesize/5, y+tilesize/6, 2*tilesize/5+1, tilesize/2); + draw_circle(dr, x-tilesize/15, y+tilesize/6, tilesize/12, + COL_BACKGROUND, black); + unclip(dr); + + draw_line(dr, x-tilesize/5, y+tilesize/6, x+tilesize/5, y+tilesize/6, + black); + } + + draw_update(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize-3); +} + +static void draw_monster_count(drawing *dr, game_drawstate *ds, + const game_state *state, int c, int hflash) { + int dx,dy; + char buf[8]; + char bufm[8]; + + dy = TILESIZE/4; + dx = BORDER+(ds->w+2)*TILESIZE/2+TILESIZE/4; + switch (c) { + case 0: + sprintf(buf,"%d",state->common->num_ghosts); + sprintf(bufm,"G"); + dx -= 3*TILESIZE/2; + break; + case 1: + sprintf(buf,"%d",state->common->num_vampires); + sprintf(bufm,"V"); + break; + case 2: + sprintf(buf,"%d",state->common->num_zombies); + sprintf(bufm,"Z"); + dx += 3*TILESIZE/2; + break; + } + + draw_rect(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE, + COL_BACKGROUND); + if (!ds->ascii) { + draw_monster(dr, ds, dx-TILESIZE/3, dy+TILESIZE/2, + 2*TILESIZE/3, hflash, 1<count_errors[c] ? COL_ERROR : + hflash ? COL_FLASH : COL_TEXT), buf); + draw_update(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE); + + return; +} + +static void draw_path_hint(drawing *dr, game_drawstate *ds, + const struct game_params *params, + int hint_index, int hflash, int hint) { + int x, y, color, dx, dy, text_dx, text_dy, text_size; + char buf[4]; + + if (ds->hint_errors[hint_index]) + color = COL_ERROR; + else if (hflash) + color = COL_FLASH; + else if (ds->hints_done[hint_index]) + color = COL_DONE; + else + color = COL_TEXT; + + range2grid(hint_index, params->w, params->h, &x, &y); + /* Upper-left corner of the "tile" */ + dx = BORDER + x * TILESIZE; + dy = BORDER + y * TILESIZE + TILESIZE; + /* Center of the "tile" */ + text_dx = dx + TILESIZE / 2; + text_dy = dy + TILESIZE / 2; + /* Avoid wiping out the borders of the puzzle */ + dx += 2; + dy += 2; + text_size = TILESIZE - 3; + + sprintf(buf,"%d", hint); + draw_rect(dr, dx, dy, text_size, text_size, COL_BACKGROUND); + draw_text(dr, text_dx, text_dy, FONT_FIXED, TILESIZE / 2, + ALIGN_HCENTRE | ALIGN_VCENTRE, color, buf); + draw_update(dr, dx, dy, text_size, text_size); + + return; +} + +static void draw_mirror(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y, + int hflash, int mirror) { + int dx,dy,mx1,my1,mx2,my2; + dx = BORDER+(x* ds->tilesize)+(TILESIZE/2); + dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE; + + if (mirror == CELL_MIRROR_L) { + mx1 = dx-(TILESIZE/4); + my1 = dy-(TILESIZE/4); + mx2 = dx+(TILESIZE/4); + my2 = dy+(TILESIZE/4); + } + else { + mx1 = dx-(TILESIZE/4); + my1 = dy+(TILESIZE/4); + mx2 = dx+(TILESIZE/4); + my2 = dy-(TILESIZE/4); + } + draw_thick_line(dr,(float)(TILESIZE/16),mx1,my1,mx2,my2, + hflash ? COL_FLASH : COL_TEXT); + draw_update(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1); + + return; +} + +static void draw_big_monster(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y, + int hflash, int monster) +{ + int dx,dy; + char buf[10]; + dx = BORDER+(x* ds->tilesize)+(TILESIZE/2); + dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE; + if (ds->ascii) { + if (monster == 1) sprintf(buf,"G"); + else if (monster == 2) sprintf(buf,"V"); + else if (monster == 4) sprintf(buf,"Z"); + else sprintf(buf," "); + draw_text(dr,dx,dy,FONT_FIXED,TILESIZE/2,ALIGN_HCENTRE|ALIGN_VCENTRE, + hflash ? COL_FLASH : COL_TEXT,buf); + draw_update(dr,dx-(TILESIZE/2)+2,dy-(TILESIZE/2)+2,TILESIZE-3, + TILESIZE-3); + } + else { + draw_monster(dr, ds, dx, dy, 3*TILESIZE/4, hflash, monster); + } + return; +} + +static void draw_pencils(drawing *dr, game_drawstate *ds, + const game_state *state, int x, int y, int pencil) +{ + int dx, dy; + int monsters[4]; + int i, j, px, py; + char buf[10]; + dx = BORDER+(x* ds->tilesize)+(TILESIZE/4); + dy = BORDER+(y* ds->tilesize)+(TILESIZE/4)+TILESIZE; + + for (i = 0, j = 1; j < 8; j *= 2) + if (pencil & j) + monsters[i++] = j; + while (i < 4) + monsters[i++] = 0; + + for (py = 0; py < 2; py++) + for (px = 0; px < 2; px++) + if (monsters[py*2+px]) { + if (!ds->ascii) { + draw_monster(dr, ds, + dx + TILESIZE/2 * px, dy + TILESIZE/2 * py, + TILESIZE/2, 0, monsters[py*2+px]); + } + else { + switch (monsters[py*2+px]) { + case 1: sprintf(buf,"G"); break; + case 2: sprintf(buf,"V"); break; + case 4: sprintf(buf,"Z"); break; + } + draw_text(dr,dx + TILESIZE/2 * px,dy + TILESIZE/2 * py, + FONT_FIXED,TILESIZE/4,ALIGN_HCENTRE|ALIGN_VCENTRE, + COL_TEXT,buf); + } + } + draw_update(dr,dx-(TILESIZE/4)+2,dy-(TILESIZE/4)+2, + (TILESIZE/2)-3,(TILESIZE/2)-3); + + return; +} + +#define FLASH_TIME 0.7F + +static int is_hint_stale(const game_drawstate *ds, int hflash, + const game_state *state, int index) +{ + int ret = FALSE; + if (!ds->started) ret = TRUE; + if (ds->hflash != hflash) ret = TRUE; + + if (ds->hint_errors[index] != state->hint_errors[index]) { + ds->hint_errors[index] = state->hint_errors[index]; + ret = TRUE; + } + + if (ds->hints_done[index] != state->hints_done[index]) { + ds->hints_done[index] = state->hints_done[index]; + ret = TRUE; + } + + return ret; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int i,j,x,y,xy; + int stale, xi, c, hflash, hchanged, changed_ascii; + + hflash = (int)(flashtime * 5 / FLASH_TIME) % 2; + + /* Draw static grid components at startup */ + if (!ds->started) { + draw_rect(dr, 0, 0, 2*BORDER+(ds->w+2)*TILESIZE, + 2*BORDER+(ds->h+3)*TILESIZE, COL_BACKGROUND); + draw_rect(dr, BORDER+TILESIZE-1, BORDER+2*TILESIZE-1, + (ds->w)*TILESIZE +3, (ds->h)*TILESIZE +3, COL_GRID); + for (i=0;iw;i++) + for (j=0;jh;j++) + draw_rect(dr, BORDER+(ds->tilesize*(i+1))+1, + BORDER+(ds->tilesize*(j+2))+1, ds->tilesize-1, + ds->tilesize-1, COL_BACKGROUND); + draw_update(dr, 0, 0, 2*BORDER+(ds->w+2)*TILESIZE, + 2*BORDER+(ds->h+3)*TILESIZE); + } + + hchanged = FALSE; + if (ds->hx != ui->hx || ds->hy != ui->hy || + ds->hshow != ui->hshow || ds->hpencil != ui->hpencil) + hchanged = TRUE; + + if (ds->ascii != ui->ascii) { + ds->ascii = ui->ascii; + changed_ascii = TRUE; + } else + changed_ascii = FALSE; + + /* Draw monster count hints */ + + for (i=0;i<3;i++) { + stale = FALSE; + if (!ds->started) stale = TRUE; + if (ds->hflash != hflash) stale = TRUE; + if (changed_ascii) stale = TRUE; + if (ds->count_errors[i] != state->count_errors[i]) { + stale = TRUE; + ds->count_errors[i] = state->count_errors[i]; + } + + if (stale) { + draw_monster_count(dr, ds, state, i, hflash); + } + } + + /* Draw path count hints */ + for (i=0;icommon->num_paths;i++) { + struct path *path = &state->common->paths[i]; + + if (is_hint_stale(ds, hflash, state, path->grid_start)) { + draw_path_hint(dr, ds, &state->common->params, path->grid_start, + hflash, path->sightings_start); + } + + if (is_hint_stale(ds, hflash, state, path->grid_end)) { + draw_path_hint(dr, ds, &state->common->params, path->grid_end, + hflash, path->sightings_end); + } + } + + /* Draw puzzle grid contents */ + for (x = 1; x < ds->w+1; x++) + for (y = 1; y < ds->h+1; y++) { + stale = FALSE; + xy = x+y*(state->common->params.w+2); + xi = state->common->xinfo[xy]; + c = state->common->grid[xy]; + + if (!ds->started) stale = TRUE; + if (ds->hflash != hflash) stale = TRUE; + if (changed_ascii) stale = TRUE; + + if (hchanged) { + if ((x == ui->hx && y == ui->hy) || + (x == ds->hx && y == ds->hy)) + stale = TRUE; + } + + if (xi >= 0 && (state->guess[xi] != ds->monsters[xi]) ) { + stale = TRUE; + ds->monsters[xi] = state->guess[xi]; + } + + if (xi >= 0 && (state->pencils[xi] != ds->pencils[xi]) ) { + stale = TRUE; + ds->pencils[xi] = state->pencils[xi]; + } + + if (state->cell_errors[xy] != ds->cell_errors[xy]) { + stale = TRUE; + ds->cell_errors[xy] = state->cell_errors[xy]; + } + + if (stale) { + draw_cell_background(dr, ds, state, ui, x, y); + if (xi < 0) + draw_mirror(dr, ds, state, x, y, hflash, c); + else if (state->guess[xi] == 1 || state->guess[xi] == 2 || + state->guess[xi] == 4) + draw_big_monster(dr, ds, state, x, y, hflash, state->guess[xi]); + else + draw_pencils(dr, ds, state, x, y, state->pencils[xi]); + } + } + + ds->hx = ui->hx; ds->hy = ui->hy; + ds->hshow = ui->hshow; + ds->hpencil = ui->hpencil; + ds->hflash = hflash; + ds->started = TRUE; + return; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return (!oldstate->solved && newstate->solved && !oldstate->cheated && + !newstate->cheated) ? FLASH_TIME : 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->solved; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame undead +#endif + +const struct game thegame = { + "Undead", "games.undead", "undead", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/unequal.R b/apps/plugins/puzzles/unequal.R new file mode 100644 index 0000000000..a061582768 --- /dev/null +++ b/apps/plugins/puzzles/unequal.R @@ -0,0 +1,27 @@ +# -*- makefile -*- + +UNEQUAL_EXTRA = latin tree234 maxflow + +unequal : [X] GTK COMMON unequal UNEQUAL_EXTRA unequal-icon|no-icon + +unequal : [G] WINDOWS COMMON unequal UNEQUAL_EXTRA unequal.res|noicon.res + +unequalsolver : [U] unequal[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] tree234 maxflow STANDALONE +unequalsolver : [C] unequal[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] tree234 maxflow STANDALONE + +latincheck : [U] latin[STANDALONE_LATIN_TEST] tree234 maxflow STANDALONE +latincheck : [C] latin[STANDALONE_LATIN_TEST] tree234 maxflow STANDALONE + +ALL += unequal[COMBINED] UNEQUAL_EXTRA + +!begin am gtk +GAMES += unequal +!end + +!begin >list.c + A(unequal) \ +!end + +!begin >gamedesc.txt +unequal:unequal.exe:Unequal:Latin square puzzle:Complete the latin square in accordance with the > signs. +!end diff --git a/apps/plugins/puzzles/unequal.c b/apps/plugins/puzzles/unequal.c new file mode 100644 index 0000000000..457965b4ff --- /dev/null +++ b/apps/plugins/puzzles/unequal.c @@ -0,0 +1,2267 @@ +/* + * unequal.c + * + * Implementation of 'Futoshiki', a puzzle featured in the Guardian. + * + * TTD: + * add multiple-links-on-same-col/row solver nous + * Optimise set solver to use bit operations instead + * + * Guardian puzzles of note: + * #1: 5:0,0L,0L,0,0,0R,0,0L,0D,0L,0R,0,2,0D,0,0,0,0,0,0,0U,0,0,0,0U, + * #2: 5:0,0,0,4L,0L,0,2LU,0L,0U,0,0,0U,0,0,0,0,0D,0,3LRUD,0,0R,3,0L,0,0, + * #3: (reprint of #2) + * #4: + * #5: 5:0,0,0,0,0,0,2,0U,3U,0U,0,0,3,0,0,0,3,0D,4,0,0,0L,0R,0,0, + * #6: 5:0D,0L,0,0R,0,0,0D,0,3,0D,0,0R,0,0R,0D,0U,0L,0,1,2,0,0,0U,0,0L, + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "latin.h" /* contains typedef for digit */ + +/* ---------------------------------------------------------- + * Constant and structure definitions + */ + +#define FLASH_TIME 0.4F + +#define PREFERRED_TILE_SIZE 32 + +#define TILE_SIZE (ds->tilesize) +#define GAP_SIZE (TILE_SIZE/2) +#define SQUARE_SIZE (TILE_SIZE + GAP_SIZE) + +#define BORDER (TILE_SIZE / 2) + +#define COORD(x) ( (x) * SQUARE_SIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + SQUARE_SIZE) / SQUARE_SIZE - 1 ) + +#define GRID(p,w,x,y) ((p)->w[((y)*(p)->order)+(x)]) +#define GRID3(p,w,x,y,z) ((p)->w[ (((x)*(p)->order+(y))*(p)->order+(z)) ]) +#define HINT(p,x,y,n) GRID3(p, hints, x, y, n) + +enum { + COL_BACKGROUND, + COL_GRID, + COL_TEXT, COL_GUESS, COL_ERROR, COL_PENCIL, + COL_HIGHLIGHT, COL_LOWLIGHT, COL_SPENT = COL_LOWLIGHT, + NCOLOURS +}; + +struct game_params { + int order; /* Size of latin square */ + int diff; /* Difficulty */ + int adjacent; /* Puzzle indicators are 'adjacent number' + not 'greater-than'. */ +}; + +#define F_IMMUTABLE 1 /* passed in as game description */ +#define F_ADJ_UP 2 +#define F_ADJ_RIGHT 4 +#define F_ADJ_DOWN 8 +#define F_ADJ_LEFT 16 +#define F_ERROR 32 +#define F_ERROR_UP 64 +#define F_ERROR_RIGHT 128 +#define F_ERROR_DOWN 256 +#define F_ERROR_LEFT 512 +#define F_SPENT_UP 1024 +#define F_SPENT_RIGHT 2048 +#define F_SPENT_DOWN 4096 +#define F_SPENT_LEFT 8192 + +#define ADJ_TO_SPENT(x) ((x) << 9) + +#define F_ERROR_MASK (F_ERROR|F_ERROR_UP|F_ERROR_RIGHT|F_ERROR_DOWN|F_ERROR_LEFT) + +struct game_state { + int order, completed, cheated, adjacent; + digit *nums; /* actual numbers (size order^2) */ + unsigned char *hints; /* remaining possiblities (size order^3) */ + unsigned int *flags; /* flags (size order^2) */ +}; + +/* ---------------------------------------------------------- + * Game parameters and presets + */ + +/* Steal the method from map.c for difficulty levels. */ +#define DIFFLIST(A) \ + A(LATIN,Trivial,NULL,t) \ + A(EASY,Easy,solver_easy, e) \ + A(SET,Tricky,solver_set, k) \ + A(EXTREME,Extreme,NULL,x) \ + A(RECURSIVE,Recursive,NULL,r) + +#define ENUM(upper,title,func,lower) DIFF_ ## upper, +#define TITLE(upper,title,func,lower) #title, +#define ENCODE(upper,title,func,lower) #lower +#define CONFIG(upper,title,func,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT, DIFF_IMPOSSIBLE = diff_impossible, DIFF_AMBIGUOUS = diff_ambiguous, DIFF_UNFINISHED = diff_unfinished }; +static char const *const unequal_diffnames[] = { DIFFLIST(TITLE) }; +static char const unequal_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +#define DEFAULT_PRESET 0 + +const static struct game_params unequal_presets[] = { + { 4, DIFF_EASY, 0 }, + { 5, DIFF_EASY, 0 }, + { 5, DIFF_SET, 0 }, + { 5, DIFF_SET, 1 }, + { 5, DIFF_EXTREME, 0 }, + { 6, DIFF_EASY, 0 }, + { 6, DIFF_SET, 0 }, + { 6, DIFF_SET, 1 }, + { 6, DIFF_EXTREME, 0 }, + { 7, DIFF_SET, 0 }, + { 7, DIFF_SET, 1 }, + { 7, DIFF_EXTREME, 0 } +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(unequal_presets)) + return FALSE; + + ret = snew(game_params); + *ret = unequal_presets[i]; /* structure copy */ + + sprintf(buf, "%s: %dx%d %s", + ret->adjacent ? "Adjacent" : "Unequal", + ret->order, ret->order, + unequal_diffnames[ret->diff]); + + *name = dupstr(buf); + *params = ret; + return TRUE; +} + +static game_params *default_params(void) +{ + game_params *ret; + char *name; + + if (!game_fetch_preset(DEFAULT_PRESET, &name, &ret)) return NULL; + sfree(name); + return ret; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *ret, char const *string) +{ + char const *p = string; + + ret->order = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + + if (*p == 'a') { + p++; + ret->adjacent = 1; + } else + ret->adjacent = 0; + + if (*p == 'd') { + int i; + p++; + ret->diff = DIFFCOUNT+1; /* ...which is invalid */ + if (*p) { + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == unequal_diffchars[i]) + ret->diff = i; + } + p++; + } + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[80]; + + sprintf(ret, "%d", params->order); + if (params->adjacent) + sprintf(ret + strlen(ret), "a"); + if (full) + sprintf(ret + strlen(ret), "d%c", unequal_diffchars[params->diff]); + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Mode"; + ret[0].type = C_CHOICES; + ret[0].sval = ":Unequal:Adjacent"; + ret[0].ival = params->adjacent; + + ret[1].name = "Size (s*s)"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->order); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->adjacent = cfg[0].ival; + ret->order = atoi(cfg[1].sval); + ret->diff = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->order < 3 || params->order > 32) + return "Order must be between 3 and 32"; + if (params->diff >= DIFFCOUNT) + return "Unknown difficulty rating"; + if (params->order < 5 && params->adjacent && + params->diff >= DIFF_SET) + return "Order must be at least 5 for Adjacent puzzles of this difficulty."; + return NULL; +} + +/* ---------------------------------------------------------- + * Various utility functions + */ + +static const struct { unsigned int f, fo, fe; int dx, dy; char c, ac; } adjthan[] = { + { F_ADJ_UP, F_ADJ_DOWN, F_ERROR_UP, 0, -1, '^', '-' }, + { F_ADJ_RIGHT, F_ADJ_LEFT, F_ERROR_RIGHT, 1, 0, '>', '|' }, + { F_ADJ_DOWN, F_ADJ_UP, F_ERROR_DOWN, 0, 1, 'v', '-' }, + { F_ADJ_LEFT, F_ADJ_RIGHT, F_ERROR_LEFT, -1, 0, '<', '|' } +}; + +static game_state *blank_game(int order, int adjacent) +{ + game_state *state = snew(game_state); + int o2 = order*order, o3 = o2*order; + + state->order = order; + state->adjacent = adjacent; + state->completed = state->cheated = 0; + + state->nums = snewn(o2, digit); + state->hints = snewn(o3, unsigned char); + state->flags = snewn(o2, unsigned int); + + memset(state->nums, 0, o2 * sizeof(digit)); + memset(state->hints, 0, o3); + memset(state->flags, 0, o2 * sizeof(unsigned int)); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = blank_game(state->order, state->adjacent); + int o2 = state->order*state->order, o3 = o2*state->order; + + memcpy(ret->nums, state->nums, o2 * sizeof(digit)); + memcpy(ret->hints, state->hints, o3); + memcpy(ret->flags, state->flags, o2 * sizeof(unsigned int)); + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->nums); + sfree(state->hints); + sfree(state->flags); + sfree(state); +} + +#define CHECKG(x,y) grid[(y)*o+(x)] + +/* Returns 0 if it finds an error, 1 otherwise. */ +static int check_num_adj(digit *grid, game_state *state, + int x, int y, int me) +{ + unsigned int f = GRID(state, flags, x, y); + int ret = 1, i, o = state->order; + + for (i = 0; i < 4; i++) { + int dx = adjthan[i].dx, dy = adjthan[i].dy, n, dn; + + if (x+dx < 0 || x+dx >= o || y+dy < 0 || y+dy >= o) + continue; + + n = CHECKG(x, y); + dn = CHECKG(x+dx, y+dy); + + assert (n != 0); + if (dn == 0) continue; + + if (state->adjacent) { + int gd = abs(n-dn); + + if ((f & adjthan[i].f) && (gd != 1)) { + debug(("check_adj error (%d,%d):%d should be | (%d,%d):%d", + x, y, n, x+dx, y+dy, dn)); + if (me) GRID(state, flags, x, y) |= adjthan[i].fe; + ret = 0; + } + if (!(f & adjthan[i].f) && (gd == 1)) { + debug(("check_adj error (%d,%d):%d should not be | (%d,%d):%d", + x, y, n, x+dx, y+dy, dn)); + if (me) GRID(state, flags, x, y) |= adjthan[i].fe; + ret = 0; + } + + } else { + if ((f & adjthan[i].f) && (n <= dn)) { + debug(("check_adj error (%d,%d):%d not > (%d,%d):%d", + x, y, n, x+dx, y+dy, dn)); + if (me) GRID(state, flags, x, y) |= adjthan[i].fe; + ret = 0; + } + } + } + return ret; +} + +/* Returns 0 if it finds an error, 1 otherwise. */ +static int check_num_error(digit *grid, game_state *state, + int x, int y, int mark_errors) +{ + int o = state->order; + int xx, yy, val = CHECKG(x,y), ret = 1; + + assert(val != 0); + + /* check for dups in same column. */ + for (yy = 0; yy < state->order; yy++) { + if (yy == y) continue; + if (CHECKG(x,yy) == val) ret = 0; + } + + /* check for dups in same row. */ + for (xx = 0; xx < state->order; xx++) { + if (xx == x) continue; + if (CHECKG(xx,y) == val) ret = 0; + } + + if (!ret) { + debug(("check_num_error (%d,%d) duplicate %d", x, y, val)); + if (mark_errors) GRID(state, flags, x, y) |= F_ERROR; + } + return ret; +} + +/* Returns: -1 for 'wrong' + * 0 for 'incomplete' + * 1 for 'complete and correct' + */ +static int check_complete(digit *grid, game_state *state, int mark_errors) +{ + int x, y, ret = 1, o = state->order; + + if (mark_errors) + assert(grid == state->nums); + + for (x = 0; x < state->order; x++) { + for (y = 0; y < state->order; y++) { + if (mark_errors) + GRID(state, flags, x, y) &= ~F_ERROR_MASK; + if (grid[y*o+x] == 0) { + ret = 0; + } else { + if (!check_num_error(grid, state, x, y, mark_errors)) ret = -1; + if (!check_num_adj(grid, state, x, y, mark_errors)) ret = -1; + } + } + } + if (ret == 1 && latin_check(grid, o)) + ret = -1; + return ret; +} + +static char n2c(digit n, int order) { + if (n == 0) return ' '; + if (order < 10) { + if (n < 10) return '0' + n; + } else { + if (n < 11) return '0' + n-1; + n -= 11; + if (n <= 26) return 'A' + n; + } + return '?'; +} + +/* should be 'digit', but includes -1 for 'not a digit'. + * Includes keypresses (0 especially) for interpret_move. */ +static int c2n(int c, int order) { + if (c < 0 || c > 0xff) + return -1; + if (c == ' ' || c == '\b') + return 0; + if (order < 10) { + if (c >= '0' && c <= '9') + return (int)(c - '0'); + } else { + if (c >= '0' && c <= '9') + return (int)(c - '0' + 1); + if (c >= 'A' && c <= 'Z') + return (int)(c - 'A' + 11); + if (c >= 'a' && c <= 'z') + return (int)(c - 'a' + 11); + } + return -1; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int x, y, len, n; + char *ret, *p; + + len = (state->order*2) * (state->order*2-1) + 1; + ret = snewn(len, char); + p = ret; + + for (y = 0; y < state->order; y++) { + for (x = 0; x < state->order; x++) { + n = GRID(state, nums, x, y); + *p++ = n > 0 ? n2c(n, state->order) : '.'; + + if (x < (state->order-1)) { + if (state->adjacent) { + *p++ = (GRID(state, flags, x, y) & F_ADJ_RIGHT) ? '|' : ' '; + } else { + if (GRID(state, flags, x, y) & F_ADJ_RIGHT) + *p++ = '>'; + else if (GRID(state, flags, x+1, y) & F_ADJ_LEFT) + *p++ = '<'; + else + *p++ = ' '; + } + } + } + *p++ = '\n'; + + if (y < (state->order-1)) { + for (x = 0; x < state->order; x++) { + if (state->adjacent) { + *p++ = (GRID(state, flags, x, y) & F_ADJ_DOWN) ? '-' : ' '; + } else { + if (GRID(state, flags, x, y) & F_ADJ_DOWN) + *p++ = 'v'; + else if (GRID(state, flags, x, y+1) & F_ADJ_UP) + *p++ = '^'; + else + *p++ = ' '; + } + + if (x < state->order-1) + *p++ = ' '; + } + *p++ = '\n'; + } + } + *p++ = '\0'; + + assert(p - ret == len); + return ret; +} + +#ifdef STANDALONE_SOLVER +static void game_debug(game_state *state) +{ + char *dbg = game_text_format(state); + printf("%s", dbg); + sfree(dbg); +} +#endif + +/* ---------------------------------------------------------- + * Solver. + */ + +struct solver_link { + int len, gx, gy, lx, ly; +}; + +struct solver_ctx { + game_state *state; + + int nlinks, alinks; + struct solver_link *links; +}; + +static void solver_add_link(struct solver_ctx *ctx, + int gx, int gy, int lx, int ly, int len) +{ + if (ctx->alinks < ctx->nlinks+1) { + ctx->alinks = ctx->alinks*2 + 1; + /*debug(("resizing ctx->links, new size %d", ctx->alinks));*/ + ctx->links = sresize(ctx->links, ctx->alinks, struct solver_link); + } + ctx->links[ctx->nlinks].gx = gx; + ctx->links[ctx->nlinks].gy = gy; + ctx->links[ctx->nlinks].lx = lx; + ctx->links[ctx->nlinks].ly = ly; + ctx->links[ctx->nlinks].len = len; + ctx->nlinks++; + /*debug(("Adding new link: len %d (%d,%d) < (%d,%d), nlinks now %d", + len, lx, ly, gx, gy, ctx->nlinks));*/ +} + +static struct solver_ctx *new_ctx(game_state *state) +{ + struct solver_ctx *ctx = snew(struct solver_ctx); + int o = state->order; + int i, x, y; + unsigned int f; + + ctx->nlinks = ctx->alinks = 0; + ctx->links = NULL; + ctx->state = state; + + if (state->adjacent) return ctx; /* adjacent mode doesn't use links. */ + + for (x = 0; x < o; x++) { + for (y = 0; y < o; y++) { + f = GRID(state, flags, x, y); + for (i = 0; i < 4; i++) { + if (f & adjthan[i].f) + solver_add_link(ctx, x, y, x+adjthan[i].dx, y+adjthan[i].dy, 1); + } + } + } + + return ctx; +} + +static void *clone_ctx(void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + return new_ctx(ctx->state); +} + +static void free_ctx(void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + if (ctx->links) sfree(ctx->links); + sfree(ctx); +} + +static void solver_nminmax(struct latin_solver *solver, + int x, int y, int *min_r, int *max_r, + unsigned char **ns_r) +{ + int o = solver->o, min = o, max = 0, n; + unsigned char *ns; + + assert(x >= 0 && y >= 0 && x < o && y < o); + + ns = solver->cube + cubepos(x,y,1); + + if (grid(x,y) > 0) { + min = max = grid(x,y)-1; + } else { + for (n = 0; n < o; n++) { + if (ns[n]) { + if (n > max) max = n; + if (n < min) min = n; + } + } + } + if (min_r) *min_r = min; + if (max_r) *max_r = max; + if (ns_r) *ns_r = ns; +} + +static int solver_links(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int i, j, lmin, gmax, nchanged = 0; + unsigned char *gns, *lns; + struct solver_link *link; + + for (i = 0; i < ctx->nlinks; i++) { + link = &ctx->links[i]; + solver_nminmax(solver, link->gx, link->gy, NULL, &gmax, &gns); + solver_nminmax(solver, link->lx, link->ly, &lmin, NULL, &lns); + + for (j = 0; j < solver->o; j++) { + /* For the 'greater' end of the link, discount all numbers + * too small to satisfy the inequality. */ + if (gns[j]) { + if (j < (lmin+link->len)) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*slink elimination, (%d,%d) > (%d,%d):\n", + solver_recurse_depth*4, "", + link->gx+1, link->gy+1, link->lx+1, link->ly+1); + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", + j+1, link->gx+1, link->gy+1); + } +#endif + cube(link->gx, link->gy, j+1) = FALSE; + nchanged++; + } + } + /* For the 'lesser' end of the link, discount all numbers + * too large to satisfy inequality. */ + if (lns[j]) { + if (j > (gmax-link->len)) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*slink elimination, (%d,%d) > (%d,%d):\n", + solver_recurse_depth*4, "", + link->gx+1, link->gy+1, link->lx+1, link->ly+1); + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", + j+1, link->lx+1, link->ly+1); + } +#endif + cube(link->lx, link->ly, j+1) = FALSE; + nchanged++; + } + } + } + } + return nchanged; +} + +static int solver_adjacent(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int nchanged = 0, x, y, i, n, o = solver->o, nx, ny, gd; + + /* Update possible values based on known values and adjacency clues. */ + + for (x = 0; x < o; x++) { + for (y = 0; y < o; y++) { + if (grid(x, y) == 0) continue; + + /* We have a definite number here. Make sure that any + * adjacent possibles reflect the adjacent/non-adjacent clue. */ + + for (i = 0; i < 4; i++) { + int isadjacent = (GRID(ctx->state, flags, x, y) & adjthan[i].f); + + nx = x + adjthan[i].dx, ny = y + adjthan[i].dy; + if (nx < 0 || ny < 0 || nx >= o || ny >= o) + continue; + + for (n = 0; n < o; n++) { + /* Continue past numbers the adjacent square _could_ be, + * given the clue we have. */ + gd = abs((n+1) - grid(x, y)); + if (isadjacent && (gd == 1)) continue; + if (!isadjacent && (gd != 1)) continue; + + if (cube(nx, ny, n+1) == FALSE) + continue; /* already discounted this possibility. */ + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*sadjacent elimination, (%d,%d):%d %s (%d,%d):\n", + solver_recurse_depth*4, "", + x+1, y+1, grid(x, y), isadjacent ? "|" : "!|", nx+1, ny+1); + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", n+1, nx+1, ny+1); + } +#endif + cube(nx, ny, n+1) = FALSE; + nchanged++; + } + } + } + } + + return nchanged; +} + +static int solver_adjacent_set(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + int x, y, i, n, nn, o = solver->o, nx, ny, gd; + int nchanged = 0, *scratch = snewn(o, int); + + /* Update possible values based on other possible values + * of adjacent squares, and adjacency clues. */ + + for (x = 0; x < o; x++) { + for (y = 0; y < o; y++) { + for (i = 0; i < 4; i++) { + int isadjacent = (GRID(ctx->state, flags, x, y) & adjthan[i].f); + + nx = x + adjthan[i].dx, ny = y + adjthan[i].dy; + if (nx < 0 || ny < 0 || nx >= o || ny >= o) + continue; + + /* We know the current possibles for the square (x,y) + * and also the adjacency clue from (x,y) to (nx,ny). + * Construct a maximum set of possibles for (nx,ny) + * in scratch, based on these constraints... */ + + memset(scratch, 0, o*sizeof(int)); + + for (n = 0; n < o; n++) { + if (cube(x, y, n+1) == FALSE) continue; + + for (nn = 0; nn < o; nn++) { + if (n == nn) continue; + + gd = abs(nn - n); + if (isadjacent && (gd != 1)) continue; + if (!isadjacent && (gd == 1)) continue; + + scratch[nn] = 1; + } + } + + /* ...and remove any possibilities for (nx,ny) that are + * currently set but are not indicated in scratch. */ + for (n = 0; n < o; n++) { + if (scratch[n] == 1) continue; + if (cube(nx, ny, n+1) == FALSE) continue; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*sadjacent possible elimination, (%d,%d) %s (%d,%d):\n", + solver_recurse_depth*4, "", + x+1, y+1, isadjacent ? "|" : "!|", nx+1, ny+1); + printf("%*s ruling out %d at (%d,%d)\n", + solver_recurse_depth*4, "", n+1, nx+1, ny+1); + } +#endif + cube(nx, ny, n+1) = FALSE; + nchanged++; + } + } + } + } + + return nchanged; +} + +static int solver_easy(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + if (ctx->state->adjacent) + return solver_adjacent(solver, vctx); + else + return solver_links(solver, vctx); +} + +static int solver_set(struct latin_solver *solver, void *vctx) +{ + struct solver_ctx *ctx = (struct solver_ctx *)vctx; + if (ctx->state->adjacent) + return solver_adjacent_set(solver, vctx); + else + return 0; +} + +#define SOLVER(upper,title,func,lower) func, +static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) }; + +static int solver_state(game_state *state, int maxdiff) +{ + struct solver_ctx *ctx = new_ctx(state); + struct latin_solver solver; + int diff; + + latin_solver_alloc(&solver, state->nums, state->order); + + diff = latin_solver_main(&solver, maxdiff, + DIFF_LATIN, DIFF_SET, DIFF_EXTREME, + DIFF_EXTREME, DIFF_RECURSIVE, + unequal_solvers, ctx, clone_ctx, free_ctx); + + memcpy(state->hints, solver.cube, state->order*state->order*state->order); + + free_ctx(ctx); + + latin_solver_free(&solver); + + if (diff == DIFF_IMPOSSIBLE) + return -1; + if (diff == DIFF_UNFINISHED) + return 0; + if (diff == DIFF_AMBIGUOUS) + return 2; + return 1; +} + +static game_state *solver_hint(const game_state *state, int *diff_r, + int mindiff, int maxdiff) +{ + game_state *ret = dup_game(state); + int diff, r = 0; + + for (diff = mindiff; diff <= maxdiff; diff++) { + r = solver_state(ret, diff); + debug(("solver_state after %s %d", unequal_diffnames[diff], r)); + if (r != 0) goto done; + } + +done: + if (diff_r) *diff_r = (r > 0) ? diff : -1; + return ret; +} + +/* ---------------------------------------------------------- + * Game generation. + */ + +static char *latin_desc(digit *sq, size_t order) +{ + int o2 = order*order, i; + char *soln = snewn(o2+2, char); + + soln[0] = 'S'; + for (i = 0; i < o2; i++) + soln[i+1] = n2c(sq[i], order); + soln[o2+1] = '\0'; + + return soln; +} + +/* returns non-zero if it placed (or could have placed) clue. */ +static int gg_place_clue(game_state *state, int ccode, digit *latin, int checkonly) +{ + int loc = ccode / 5, which = ccode % 5; + int x = loc % state->order, y = loc / state->order; + + assert(loc < state->order*state->order); + + if (which == 4) { /* add number */ + if (state->nums[loc] != 0) { +#ifdef STANDALONE_SOLVER + if (state->nums[loc] != latin[loc]) { + printf("inconsistency for (%d,%d): state %d latin %d\n", + x+1, y+1, state->nums[loc], latin[loc]); + } +#endif + assert(state->nums[loc] == latin[loc]); + return 0; + } + if (!checkonly) { + state->nums[loc] = latin[loc]; + } + } else { /* add flag */ + int lx, ly, lloc; + + if (state->adjacent) + return 0; /* never add flag clues in adjacent mode (they're always + all present) */ + + if (state->flags[loc] & adjthan[which].f) + return 0; /* already has flag. */ + + lx = x + adjthan[which].dx; + ly = y + adjthan[which].dy; + if (lx < 0 || ly < 0 || lx >= state->order || ly >= state->order) + return 0; /* flag compares to off grid */ + + lloc = loc + adjthan[which].dx + adjthan[which].dy*state->order; + if (latin[loc] <= latin[lloc]) + return 0; /* flag would be incorrect */ + + if (!checkonly) { + state->flags[loc] |= adjthan[which].f; + } + } + return 1; +} + +/* returns non-zero if it removed (or could have removed) the clue. */ +static int gg_remove_clue(game_state *state, int ccode, int checkonly) +{ + int loc = ccode / 5, which = ccode % 5; +#ifdef STANDALONE_SOLVER + int x = loc % state->order, y = loc / state->order; +#endif + + assert(loc < state->order*state->order); + + if (which == 4) { /* remove number. */ + if (state->nums[loc] == 0) return 0; + if (!checkonly) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("gg_remove_clue: removing %d at (%d,%d)", + state->nums[loc], x+1, y+1); +#endif + state->nums[loc] = 0; + } + } else { /* remove flag */ + if (state->adjacent) + return 0; /* never remove clues in adjacent mode. */ + + if (!(state->flags[loc] & adjthan[which].f)) return 0; + if (!checkonly) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("gg_remove_clue: removing %c at (%d,%d)", + adjthan[which].c, x+1, y+1); +#endif + state->flags[loc] &= ~adjthan[which].f; + } + } + return 1; +} + +static int gg_best_clue(game_state *state, int *scratch, digit *latin) +{ + int ls = state->order * state->order * 5; + int maxposs = 0, minclues = 5, best = -1, i, j; + int nposs, nclues, loc; + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + game_debug(state); + latin_solver_debug(state->hints, state->order); + } +#endif + + for (i = ls; i-- > 0 ;) { + if (!gg_place_clue(state, scratch[i], latin, 1)) continue; + + loc = scratch[i] / 5; + for (j = nposs = 0; j < state->order; j++) { + if (state->hints[loc*state->order + j]) nposs++; + } + for (j = nclues = 0; j < 4; j++) { + if (state->flags[loc] & adjthan[j].f) nclues++; + } + if ((nposs > maxposs) || + (nposs == maxposs && nclues < minclues)) { + best = i; maxposs = nposs; minclues = nclues; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + int x = loc % state->order, y = loc / state->order; + printf("gg_best_clue: b%d (%d,%d) new best [%d poss, %d clues].\n", + best, x+1, y+1, nposs, nclues); + } +#endif + } + } + /* if we didn't solve, we must have 1 clue to place! */ + assert(best != -1); + return best; +} + +#ifdef STANDALONE_SOLVER +int maxtries; +#define MAXTRIES maxtries +#else +#define MAXTRIES 50 +#endif +int gg_solved; + +static int game_assemble(game_state *new, int *scratch, digit *latin, + int difficulty) +{ + game_state *copy = dup_game(new); + int best; + + if (difficulty >= DIFF_RECURSIVE) { + /* We mustn't use any solver that might guess answers; + * if it guesses wrongly but solves, gg_place_clue will + * get mighty confused. We will always trim clues down + * (making it more difficult) in game_strip, which doesn't + * have this problem. */ + difficulty = DIFF_RECURSIVE-1; + } + +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + game_debug(new); + latin_solver_debug(new->hints, new->order); + } +#endif + + while(1) { + gg_solved++; + if (solver_state(copy, difficulty) == 1) break; + + best = gg_best_clue(copy, scratch, latin); + gg_place_clue(new, scratch[best], latin, 0); + gg_place_clue(copy, scratch[best], latin, 0); + } + free_game(copy); +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *dbg = game_text_format(new); + printf("game_assemble: done, %d solver iterations:\n%s\n", gg_solved, dbg); + sfree(dbg); + } +#endif + return 0; +} + +static void game_strip(game_state *new, int *scratch, digit *latin, + int difficulty) +{ + int o = new->order, o2 = o*o, lscratch = o2*5, i; + game_state *copy = blank_game(new->order, new->adjacent); + + /* For each symbol (if it exists in new), try and remove it and + * solve again; if we couldn't solve without it put it back. */ + for (i = 0; i < lscratch; i++) { + if (!gg_remove_clue(new, scratch[i], 0)) continue; + + memcpy(copy->nums, new->nums, o2 * sizeof(digit)); + memcpy(copy->flags, new->flags, o2 * sizeof(unsigned int)); + gg_solved++; + if (solver_state(copy, difficulty) != 1) { + /* put clue back, we can't solve without it. */ + int ret = gg_place_clue(new, scratch[i], latin, 0); + assert(ret == 1); + } else { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("game_strip: clue was redundant."); +#endif + } + } + free_game(copy); +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + char *dbg = game_text_format(new); + debug(("game_strip: done, %d solver iterations.", gg_solved)); + debug(("%s", dbg)); + sfree(dbg); + } +#endif +} + +static void add_adjacent_flags(game_state *state, digit *latin) +{ + int x, y, o = state->order; + + /* All clues in adjacent mode are always present (the only variables are + * the numbers). This adds all the flags to state based on the supplied + * latin square. */ + + for (y = 0; y < o; y++) { + for (x = 0; x < o; x++) { + if (x < (o-1) && (abs(latin[y*o+x] - latin[y*o+x+1]) == 1)) { + GRID(state, flags, x, y) |= F_ADJ_RIGHT; + GRID(state, flags, x+1, y) |= F_ADJ_LEFT; + } + if (y < (o-1) && (abs(latin[y*o+x] - latin[(y+1)*o+x]) == 1)) { + GRID(state, flags, x, y) |= F_ADJ_DOWN; + GRID(state, flags, x, y+1) |= F_ADJ_UP; + } + } + } +} + +static char *new_game_desc(const game_params *params_in, random_state *rs, + char **aux, int interactive) +{ + game_params params_copy = *params_in; /* structure copy */ + game_params *params = ¶ms_copy; + digit *sq = NULL; + int i, x, y, retlen, k, nsol; + int o2 = params->order * params->order, ntries = 1; + int *scratch, lscratch = o2*5; + char *ret, buf[80]; + game_state *state = blank_game(params->order, params->adjacent); + + /* Generate a list of 'things to strip' (randomised later) */ + scratch = snewn(lscratch, int); + /* Put the numbers (4 mod 5) before the inequalities (0-3 mod 5) */ + for (i = 0; i < lscratch; i++) scratch[i] = (i%o2)*5 + 4 - (i/o2); + +generate: +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("new_game_desc: generating %s puzzle, ntries so far %d\n", + unequal_diffnames[params->diff], ntries); +#endif + if (sq) sfree(sq); + sq = latin_generate(params->order, rs); + latin_debug(sq, params->order); + /* Separately shuffle the numeric and inequality clues */ + shuffle(scratch, lscratch/5, sizeof(int), rs); + shuffle(scratch+lscratch/5, 4*lscratch/5, sizeof(int), rs); + + memset(state->nums, 0, o2 * sizeof(digit)); + memset(state->flags, 0, o2 * sizeof(unsigned int)); + + if (state->adjacent) { + /* All adjacency flags are always present. */ + add_adjacent_flags(state, sq); + } + + gg_solved = 0; + if (game_assemble(state, scratch, sq, params->diff) < 0) + goto generate; + game_strip(state, scratch, sq, params->diff); + + if (params->diff > 0) { + game_state *copy = dup_game(state); + nsol = solver_state(copy, params->diff-1); + free_game(copy); + if (nsol > 0) { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("game_assemble: puzzle as generated is too easy.\n"); +#endif + if (ntries < MAXTRIES) { + ntries++; + goto generate; + } +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("Unable to generate %s %dx%d after %d attempts.\n", + unequal_diffnames[params->diff], + params->order, params->order, MAXTRIES); +#endif + params->diff--; + } + } +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("new_game_desc: generated %s puzzle; %d attempts (%d solver).\n", + unequal_diffnames[params->diff], ntries, gg_solved); +#endif + + ret = NULL; retlen = 0; + for (y = 0; y < params->order; y++) { + for (x = 0; x < params->order; x++) { + unsigned int f = GRID(state, flags, x, y); + k = sprintf(buf, "%d%s%s%s%s,", + GRID(state, nums, x, y), + (f & F_ADJ_UP) ? "U" : "", + (f & F_ADJ_RIGHT) ? "R" : "", + (f & F_ADJ_DOWN) ? "D" : "", + (f & F_ADJ_LEFT) ? "L" : ""); + + ret = sresize(ret, retlen + k + 1, char); + strcpy(ret + retlen, buf); + retlen += k; + } + } + *aux = latin_desc(sq, params->order); + + free_game(state); + sfree(sq); + sfree(scratch); + + return ret; +} + +static game_state *load_game(const game_params *params, const char *desc, + char **why_r) +{ + game_state *state = blank_game(params->order, params->adjacent); + const char *p = desc; + int i = 0, n, o = params->order, x, y; + char *why = NULL; + + while (*p) { + while (*p >= 'a' && *p <= 'z') { + i += *p - 'a' + 1; + p++; + } + if (i >= o*o) { + why = "Too much data to fill grid"; goto fail; + } + + if (*p < '0' && *p > '9') { + why = "Expecting number in game description"; goto fail; + } + n = atoi(p); + if (n < 0 || n > o) { + why = "Out-of-range number in game description"; goto fail; + } + state->nums[i] = (digit)n; + while (*p >= '0' && *p <= '9') p++; /* skip number */ + + if (state->nums[i] != 0) + state->flags[i] |= F_IMMUTABLE; /* === number set by game description */ + + while (*p == 'U' || *p == 'R' || *p == 'D' || *p == 'L') { + switch (*p) { + case 'U': state->flags[i] |= F_ADJ_UP; break; + case 'R': state->flags[i] |= F_ADJ_RIGHT; break; + case 'D': state->flags[i] |= F_ADJ_DOWN; break; + case 'L': state->flags[i] |= F_ADJ_LEFT; break; + default: why = "Expecting flag URDL in game description"; goto fail; + } + p++; + } + i++; + if (i < o*o && *p != ',') { + why = "Missing separator"; goto fail; + } + if (*p == ',') p++; + } + if (i < o*o) { + why = "Not enough data to fill grid"; goto fail; + } + i = 0; + for (y = 0; y < o; y++) { + for (x = 0; x < o; x++) { + for (n = 0; n < 4; n++) { + if (GRID(state, flags, x, y) & adjthan[n].f) { + int nx = x + adjthan[n].dx; + int ny = y + adjthan[n].dy; + /* a flag must not point us off the grid. */ + if (nx < 0 || ny < 0 || nx >= o || ny >= o) { + why = "Flags go off grid"; goto fail; + } + if (params->adjacent) { + /* if one cell is adjacent to another, the other must + * also be adjacent to the first. */ + if (!(GRID(state, flags, nx, ny) & adjthan[n].fo)) { + why = "Flags contradicting each other"; goto fail; + } + } else { + /* if one cell is GT another, the other must _not_ also + * be GT the first. */ + if (GRID(state, flags, nx, ny) & adjthan[n].fo) { + why = "Flags contradicting each other"; goto fail; + } + } + } + } + } + } + + return state; + +fail: + free_game(state); + if (why_r) *why_r = why; + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = load_game(params, desc, NULL); + if (!state) { + assert("Unable to load ?validated game."); + return NULL; + } + return state; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + char *why = NULL; + game_state *dummy = load_game(params, desc, &why); + if (dummy) { + free_game(dummy); + assert(!why); + } else + assert(why); + return why; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved; + int r; + char *ret = NULL; + + if (aux) return dupstr(aux); + + solved = dup_game(state); + for (r = 0; r < state->order*state->order; r++) { + if (!(solved->flags[r] & F_IMMUTABLE)) + solved->nums[r] = 0; + } + r = solver_state(solved, DIFFCOUNT-1); /* always use full solver */ + if (r > 0) ret = latin_desc(solved->nums, solved->order); + free_game(solved); + return ret; +} + +/* ---------------------------------------------------------- + * Game UI input processing. + */ + +struct game_ui { + int hx, hy; /* as for solo.c, highlight pos */ + int hshow, hpencil, hcursor; /* show state, type, and ?cursor. */ +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + /* See solo.c; if we were pencil-mode highlighting and + * somehow a square has just been properly filled, cancel + * pencil mode. */ + if (ui->hshow && ui->hpencil && !ui->hcursor && + GRID(newstate, nums, ui->hx, ui->hy) != 0) { + ui->hshow = 0; + } +} + +struct game_drawstate { + int tilesize, order, started, adjacent; + digit *nums; /* copy of nums, o^2 */ + unsigned char *hints; /* copy of hints, o^3 */ + unsigned int *flags; /* o^2 */ + + int hx, hy, hshow, hpencil; /* as for game_ui. */ + int hflash; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int ox, int oy, int button) +{ + int x = FROMCOORD(ox), y = FROMCOORD(oy), n; + char buf[80]; + int shift_or_control = button & (MOD_SHFT | MOD_CTRL); + + button &= ~MOD_MASK; + + if (x >= 0 && x < ds->order && y >= 0 && y < ds->order && IS_MOUSE_DOWN(button)) { + if (oy - COORD(y) > TILE_SIZE && ox - COORD(x) > TILE_SIZE) + return NULL; + + if (oy - COORD(y) > TILE_SIZE) { + if (GRID(state, flags, x, y) & F_ADJ_DOWN) + sprintf(buf, "F%d,%d,%d", x, y, F_SPENT_DOWN); + else if (y + 1 < ds->order && GRID(state, flags, x, y + 1) & F_ADJ_UP) + sprintf(buf, "F%d,%d,%d", x, y + 1, F_SPENT_UP); + else return NULL; + return dupstr(buf); + } + + if (ox - COORD(x) > TILE_SIZE) { + if (GRID(state, flags, x, y) & F_ADJ_RIGHT) + sprintf(buf, "F%d,%d,%d", x, y, F_SPENT_RIGHT); + else if (x + 1 < ds->order && GRID(state, flags, x + 1, y) & F_ADJ_LEFT) + sprintf(buf, "F%d,%d,%d", x + 1, y, F_SPENT_LEFT); + else return NULL; + return dupstr(buf); + } + + if (button == LEFT_BUTTON) { + /* normal highlighting for non-immutable squares */ + if (GRID(state, flags, x, y) & F_IMMUTABLE) + ui->hshow = 0; + else if (x == ui->hx && y == ui->hy && + ui->hshow && ui->hpencil == 0) + ui->hshow = 0; + else { + ui->hx = x; ui->hy = y; ui->hpencil = 0; + ui->hshow = 1; + } + ui->hcursor = 0; + return ""; + } + if (button == RIGHT_BUTTON) { + /* pencil highlighting for non-filled squares */ + if (GRID(state, nums, x, y) != 0) + ui->hshow = 0; + else if (x == ui->hx && y == ui->hy && + ui->hshow && ui->hpencil) + ui->hshow = 0; + else { + ui->hx = x; ui->hy = y; ui->hpencil = 1; + ui->hshow = 1; + } + ui->hcursor = 0; + return ""; + } + } + + if (IS_CURSOR_MOVE(button)) { + if (shift_or_control) { + int nx = ui->hx, ny = ui->hy, i, self; + move_cursor(button, &nx, &ny, ds->order, ds->order, FALSE); + ui->hshow = ui->hcursor = 1; + + for (i = 0; i < 4 && (nx != ui->hx + adjthan[i].dx || + ny != ui->hy + adjthan[i].dy); ++i); + + if (i == 4) + return ""; /* invalid direction, i.e. out of the board */ + + if (!(GRID(state, flags, ui->hx, ui->hy) & adjthan[i].f || + GRID(state, flags, nx, ny ) & adjthan[i].fo)) + return ""; /* no clue to toggle */ + + if (state->adjacent) + self = (adjthan[i].dx >= 0 && adjthan[i].dy >= 0); + else + self = (GRID(state, flags, ui->hx, ui->hy) & adjthan[i].f); + + if (self) + sprintf(buf, "F%d,%d,%d", ui->hx, ui->hy, + ADJ_TO_SPENT(adjthan[i].f)); + else + sprintf(buf, "F%d,%d,%d", nx, ny, + ADJ_TO_SPENT(adjthan[i].fo)); + + return dupstr(buf); + } else { + move_cursor(button, &ui->hx, &ui->hy, ds->order, ds->order, FALSE); + ui->hshow = ui->hcursor = 1; + return ""; + } + } + if (ui->hshow && IS_CURSOR_SELECT(button)) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + n = c2n(button, state->order); + if (ui->hshow && n >= 0 && n <= ds->order) { + debug(("button %d, cbutton %d", button, (int)((char)button))); + + debug(("n %d, h (%d,%d) p %d flags 0x%x nums %d", + n, ui->hx, ui->hy, ui->hpencil, + GRID(state, flags, ui->hx, ui->hy), + GRID(state, nums, ui->hx, ui->hy))); + + if (GRID(state, flags, ui->hx, ui->hy) & F_IMMUTABLE) + return NULL; /* can't edit immutable square (!) */ + if (ui->hpencil && GRID(state, nums, ui->hx, ui->hy) > 0) + return NULL; /* can't change hints on filled square (!) */ + + + sprintf(buf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); + + if (!ui->hcursor) ui->hshow = 0; + + return dupstr(buf); + } + + if (button == 'H' || button == 'h') + return dupstr("H"); + if (button == 'M' || button == 'm') + return dupstr("M"); + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + game_state *ret = NULL; + int x, y, n, i, rc; + + debug(("execute_move: %s", move)); + + if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < state->order && y >= 0 && y < state->order && + n >= 0 && n <= state->order) { + ret = dup_game(state); + if (move[0] == 'P' && n > 0) + HINT(ret, x, y, n-1) = !HINT(ret, x, y, n-1); + else { + GRID(ret, nums, x, y) = n; + for (i = 0; i < state->order; i++) + HINT(ret, x, y, i) = 0; + + /* real change to grid; check for completion */ + if (!ret->completed && check_complete(ret->nums, ret, 1) > 0) + ret->completed = TRUE; + } + return ret; + } else if (move[0] == 'S') { + const char *p; + + ret = dup_game(state); + ret->completed = ret->cheated = TRUE; + + p = move+1; + for (i = 0; i < state->order*state->order; i++) { + n = c2n((int)*p, state->order); + if (!*p || n <= 0 || n > state->order) + goto badmove; + ret->nums[i] = n; + p++; + } + if (*p) goto badmove; + rc = check_complete(ret->nums, ret, 1); + assert(rc > 0); + return ret; + } else if (move[0] == 'M') { + ret = dup_game(state); + for (x = 0; x < state->order; x++) { + for (y = 0; y < state->order; y++) { + for (n = 0; n < state->order; n++) { + HINT(ret, x, y, n) = 1; + } + } + } + return ret; + } else if (move[0] == 'H') { + return solver_hint(state, NULL, DIFF_EASY, DIFF_EASY); + } else if (move[0] == 'F' && sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < state->order && y >= 0 && y < state->order) { + ret = dup_game(state); + GRID(ret, flags, x, y) ^= n; + return ret; + } + +badmove: + if (ret) free_game(ret); + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing/printing routines. + */ + +#define DRAW_SIZE (TILE_SIZE*ds->order + GAP_SIZE*(ds->order-1) + BORDER*2) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize, order; } ads, *ds = &ads; + ads.tilesize = tilesize; + ads.order = params->order; + + *x = *y = DRAW_SIZE; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + for (i = 0; i < 3; i++) { + ret[COL_TEXT * 3 + i] = 0.0F; + ret[COL_GRID * 3 + i] = 0.5F; + } + + /* Lots of these were taken from solo.c. */ + ret[COL_GUESS * 3 + 0] = 0.0F; + ret[COL_GUESS * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_GUESS * 3 + 2] = 0.0F; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int o2 = state->order*state->order, o3 = o2*state->order; + + ds->tilesize = 0; + ds->order = state->order; + ds->adjacent = state->adjacent; + + ds->nums = snewn(o2, digit); + ds->hints = snewn(o3, unsigned char); + ds->flags = snewn(o2, unsigned int); + memset(ds->nums, 0, o2*sizeof(digit)); + memset(ds->hints, 0, o3); + memset(ds->flags, 0, o2*sizeof(unsigned int)); + + ds->hx = ds->hy = 0; + ds->started = ds->hshow = ds->hpencil = ds->hflash = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->nums); + sfree(ds->hints); + sfree(ds->flags); + sfree(ds); +} + +static void draw_gt(drawing *dr, int ox, int oy, + int dx1, int dy1, int dx2, int dy2, int col) +{ + int coords[12]; + int xdx = (dx1+dx2 ? 0 : 1), xdy = (dx1+dx2 ? 1 : 0); + coords[0] = ox + xdx; + coords[1] = oy + xdy; + coords[2] = ox + xdx + dx1; + coords[3] = oy + xdy + dy1; + coords[4] = ox + xdx + dx1 + dx2; + coords[5] = oy + xdy + dy1 + dy2; + coords[6] = ox - xdx + dx1 + dx2; + coords[7] = oy - xdy + dy1 + dy2; + coords[8] = ox - xdx + dx1; + coords[9] = oy - xdy + dy1; + coords[10] = ox - xdx; + coords[11] = oy - xdy; + draw_polygon(dr, coords, 6, col, col); +} + +#define COLOUR(direction) (f & (F_ERROR_##direction) ? COL_ERROR : \ + f & (F_SPENT_##direction) ? COL_SPENT : fg) + +static void draw_gts(drawing *dr, game_drawstate *ds, int ox, int oy, + unsigned int f, int bg, int fg) +{ + int g = GAP_SIZE, g2 = (g+1)/2, g4 = (g+1)/4; + + /* Draw all the greater-than signs emanating from this tile. */ + + if (f & F_ADJ_UP) { + if (bg >= 0) draw_rect(dr, ox, oy - g, TILE_SIZE, g, bg); + draw_gt(dr, ox+g2, oy-g4, g2, -g2, g2, g2, COLOUR(UP)); + draw_update(dr, ox, oy-g, TILE_SIZE, g); + } + if (f & F_ADJ_RIGHT) { + if (bg >= 0) draw_rect(dr, ox + TILE_SIZE, oy, g, TILE_SIZE, bg); + draw_gt(dr, ox+TILE_SIZE+g4, oy+g2, g2, g2, -g2, g2, COLOUR(RIGHT)); + draw_update(dr, ox+TILE_SIZE, oy, g, TILE_SIZE); + } + if (f & F_ADJ_DOWN) { + if (bg >= 0) draw_rect(dr, ox, oy + TILE_SIZE, TILE_SIZE, g, bg); + draw_gt(dr, ox+g2, oy+TILE_SIZE+g4, g2, g2, g2, -g2, COLOUR(DOWN)); + draw_update(dr, ox, oy+TILE_SIZE, TILE_SIZE, g); + } + if (f & F_ADJ_LEFT) { + if (bg >= 0) draw_rect(dr, ox - g, oy, g, TILE_SIZE, bg); + draw_gt(dr, ox-g4, oy+g2, -g2, g2, g2, g2, COLOUR(LEFT)); + draw_update(dr, ox-g, oy, g, TILE_SIZE); + } +} + +static void draw_adjs(drawing *dr, game_drawstate *ds, int ox, int oy, + unsigned int f, int bg, int fg) +{ + int g = GAP_SIZE, g38 = 3*(g+1)/8, g4 = (g+1)/4; + + /* Draw all the adjacency bars relevant to this tile; we only have + * to worry about F_ADJ_RIGHT and F_ADJ_DOWN. + * + * If we _only_ have the error flag set (i.e. it's not supposed to be + * adjacent, but adjacent numbers were entered) draw an outline red bar. + */ + + if (f & (F_ADJ_RIGHT|F_ERROR_RIGHT)) { + if (f & F_ADJ_RIGHT) { + draw_rect(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, COLOUR(RIGHT)); + } else { + draw_rect_outline(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, COL_ERROR); + } + } else if (bg >= 0) { + draw_rect(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, bg); + } + draw_update(dr, ox+TILE_SIZE, oy, g, TILE_SIZE); + + if (f & (F_ADJ_DOWN|F_ERROR_DOWN)) { + if (f & F_ADJ_DOWN) { + draw_rect(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, COLOUR(DOWN)); + } else { + draw_rect_outline(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, COL_ERROR); + } + } else if (bg >= 0) { + draw_rect(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, bg); + } + draw_update(dr, ox, oy+TILE_SIZE, TILE_SIZE, g); +} + +static void draw_furniture(drawing *dr, game_drawstate *ds, + const game_state *state, const game_ui *ui, + int x, int y, int hflash) +{ + int ox = COORD(x), oy = COORD(y), bg, hon; + unsigned int f = GRID(state, flags, x, y); + + bg = hflash ? COL_HIGHLIGHT : COL_BACKGROUND; + + hon = (ui->hshow && x == ui->hx && y == ui->hy); + + /* Clear square. */ + draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, + (hon && !ui->hpencil) ? COL_HIGHLIGHT : bg); + + /* Draw the highlight (pencil or full), if we're the highlight */ + if (hon && ui->hpencil) { + int coords[6]; + coords[0] = ox; + coords[1] = oy; + coords[2] = ox + TILE_SIZE/2; + coords[3] = oy; + coords[4] = ox; + coords[5] = oy + TILE_SIZE/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + /* Draw the square outline (which is the cursor, if we're the cursor). */ + draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID); + + draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE); + + /* Draw the adjacent clue signs. */ + if (ds->adjacent) + draw_adjs(dr, ds, ox, oy, f, COL_BACKGROUND, COL_GRID); + else + draw_gts(dr, ds, ox, oy, f, COL_BACKGROUND, COL_TEXT); +} + +static void draw_num(drawing *dr, game_drawstate *ds, int x, int y) +{ + int ox = COORD(x), oy = COORD(y); + unsigned int f = GRID(ds,flags,x,y); + char str[2]; + + /* (can assume square has just been cleared) */ + + /* Draw number, choosing appropriate colour */ + str[0] = n2c(GRID(ds, nums, x, y), ds->order); + str[1] = '\0'; + draw_text(dr, ox + TILE_SIZE/2, oy + TILE_SIZE/2, + FONT_VARIABLE, 3*TILE_SIZE/4, ALIGN_VCENTRE | ALIGN_HCENTRE, + (f & F_IMMUTABLE) ? COL_TEXT : (f & F_ERROR) ? COL_ERROR : COL_GUESS, str); +} + +static void draw_hints(drawing *dr, game_drawstate *ds, int x, int y) +{ + int ox = COORD(x), oy = COORD(y); + int nhints, i, j, hw, hh, hmax, fontsz; + char str[2]; + + /* (can assume square has just been cleared) */ + + /* Draw hints; steal ingenious algorithm (basically) + * from solo.c:draw_number() */ + for (i = nhints = 0; i < ds->order; i++) { + if (HINT(ds, x, y, i)) nhints++; + } + + for (hw = 1; hw * hw < nhints; hw++); + if (hw < 3) hw = 3; + hh = (nhints + hw - 1) / hw; + if (hh < 2) hh = 2; + hmax = max(hw, hh); + fontsz = TILE_SIZE/(hmax*(11-hmax)/8); + + for (i = j = 0; i < ds->order; i++) { + if (HINT(ds,x,y,i)) { + int hx = j % hw, hy = j / hw; + + str[0] = n2c(i+1, ds->order); + str[1] = '\0'; + draw_text(dr, + ox + (4*hx+3) * TILE_SIZE / (4*hw+2), + oy + (4*hy+3) * TILE_SIZE / (4*hh+2), + FONT_VARIABLE, fontsz, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } + } +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int x, y, i, hchanged = 0, stale, hflash = 0; + + debug(("highlight old (%d,%d), new (%d,%d)", ds->hx, ds->hy, ui->hx, ui->hy)); + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3)) + hflash = 1; + + if (!ds->started) { + draw_rect(dr, 0, 0, DRAW_SIZE, DRAW_SIZE, COL_BACKGROUND); + draw_update(dr, 0, 0, DRAW_SIZE, DRAW_SIZE); + } + if (ds->hx != ui->hx || ds->hy != ui->hy || + ds->hshow != ui->hshow || ds->hpencil != ui->hpencil) + hchanged = 1; + + for (x = 0; x < ds->order; x++) { + for (y = 0; y < ds->order; y++) { + if (!ds->started) + stale = 1; + else if (hflash != ds->hflash) + stale = 1; + else + stale = 0; + + if (hchanged) { + if ((x == ui->hx && y == ui->hy) || + (x == ds->hx && y == ds->hy)) + stale = 1; + } + + if (GRID(state, nums, x, y) != GRID(ds, nums, x, y)) { + GRID(ds, nums, x, y) = GRID(state, nums, x, y); + stale = 1; + } + if (GRID(state, flags, x, y) != GRID(ds, flags, x, y)) { + GRID(ds, flags, x, y) = GRID(state, flags, x, y); + stale = 1; + } + if (GRID(ds, nums, x, y) == 0) { + /* We're not a number square (therefore we might + * display hints); do we need to update? */ + for (i = 0; i < ds->order; i++) { + if (HINT(state, x, y, i) != HINT(ds, x, y, i)) { + HINT(ds, x, y, i) = HINT(state, x, y, i); + stale = 1; + } + } + } + if (stale) { + draw_furniture(dr, ds, state, ui, x, y, hflash); + if (GRID(ds, nums, x, y) > 0) + draw_num(dr, ds, x, y); + else + draw_hints(dr, ds, x, y); + } + } + } + ds->hx = ui->hx; ds->hy = ui->hy; + ds->hshow = ui->hshow; + ds->hpencil = ui->hpencil; + + ds->started = 1; + ds->hflash = hflash; +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* 10mm squares by default, roughly the same as Grauniad. */ + game_compute_size(params, 1000, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int ink = print_mono_colour(dr, 0); + int x, y, o = state->order, ox, oy, n; + char str[2]; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + print_line_width(dr, 2 * TILE_SIZE / 40); + + /* Squares, numbers, gt signs */ + for (y = 0; y < o; y++) { + for (x = 0; x < o; x++) { + ox = COORD(x); oy = COORD(y); + n = GRID(state, nums, x, y); + + draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink); + + str[0] = n ? n2c(n, state->order) : ' '; + str[1] = '\0'; + draw_text(dr, ox + TILE_SIZE/2, oy + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, + ink, str); + + if (state->adjacent) + draw_adjs(dr, ds, ox, oy, GRID(state, flags, x, y), -1, ink); + else + draw_gts(dr, ds, ox, oy, GRID(state, flags, x, y), -1, ink); + } + } +} + +/* ---------------------------------------------------------------------- + * Housekeeping. + */ + +#ifdef COMBINED +#define thegame unequal +#endif + +const struct game thegame = { + "Unequal", "games.unequal", "unequal", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ +}; + +/* ---------------------------------------------------------------------- + * Standalone solver. + */ + +#ifdef STANDALONE_SOLVER + +#include +#include + +const char *quis = NULL; + +#if 0 /* currently unused */ + +static void debug_printf(char *fmt, ...) +{ + char buf[4096]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + puts(buf); + va_end(ap); +} + +static void game_printf(game_state *state) +{ + char *dbg = game_text_format(state); + printf("%s", dbg); + sfree(dbg); +} + +static void game_printf_wide(game_state *state) +{ + int x, y, i, n; + + for (y = 0; y < state->order; y++) { + for (x = 0; x < state->order; x++) { + n = GRID(state, nums, x, y); + for (i = 0; i < state->order; i++) { + if (n > 0) + printf("%c", n2c(n, state->order)); + else if (HINT(state, x, y, i)) + printf("%c", n2c(i+1, state->order)); + else + printf("."); + } + printf(" "); + } + printf("\n"); + } + printf("\n"); +} + +#endif + +static void pdiff(int diff) +{ + if (diff == DIFF_IMPOSSIBLE) + printf("Game is impossible.\n"); + else if (diff == DIFF_UNFINISHED) + printf("Game has incomplete.\n"); + else if (diff == DIFF_AMBIGUOUS) + printf("Game has multiple solutions.\n"); + else + printf("Game has difficulty %s.\n", unequal_diffnames[diff]); +} + +static int solve(game_params *p, char *desc, int debug) +{ + game_state *state = new_game(NULL, p, desc); + struct solver_ctx *ctx = new_ctx(state); + struct latin_solver solver; + int diff; + + solver_show_working = debug; + game_debug(state); + + latin_solver_alloc(&solver, state->nums, state->order); + + diff = latin_solver_main(&solver, DIFF_RECURSIVE, + DIFF_LATIN, DIFF_SET, DIFF_EXTREME, + DIFF_EXTREME, DIFF_RECURSIVE, + unequal_solvers, ctx, clone_ctx, free_ctx); + + free_ctx(ctx); + + latin_solver_free(&solver); + + if (debug) pdiff(diff); + + game_debug(state); + free_game(state); + return diff; +} + +static void check(game_params *p) +{ + char *msg = validate_params(p, 1); + if (msg) { + fprintf(stderr, "%s: %s", quis, msg); + exit(1); + } +} + +static int gen(game_params *p, random_state *rs, int debug) +{ + char *desc, *aux; + int diff; + + check(p); + + solver_show_working = debug; + desc = new_game_desc(p, rs, &aux, 0); + diff = solve(p, desc, debug); + sfree(aux); + sfree(desc); + + return diff; +} + +static void soak(game_params *p, random_state *rs) +{ + time_t tt_start, tt_now, tt_last; + char *aux, *desc; + game_state *st; + int n = 0, neasy = 0, realdiff = p->diff; + + check(p); + + solver_show_working = 0; + maxtries = 1; + + tt_start = tt_now = time(NULL); + + printf("Soak-generating an %s %dx%d grid, difficulty %s.\n", + p->adjacent ? "adjacent" : "unequal", + p->order, p->order, unequal_diffnames[p->diff]); + + while (1) { + p->diff = realdiff; + desc = new_game_desc(p, rs, &aux, 0); + st = new_game(NULL, p, desc); + solver_state(st, DIFF_RECURSIVE); + free_game(st); + sfree(aux); + sfree(desc); + + n++; + if (realdiff != p->diff) neasy++; + + tt_last = time(NULL); + if (tt_last > tt_now) { + tt_now = tt_last; + printf("%d total, %3.1f/s; %d/%2.1f%% easy, %3.1f/s good.\n", + n, (double)n / ((double)tt_now - tt_start), + neasy, (double)neasy*100.0/(double)n, + (double)(n - neasy) / ((double)tt_now - tt_start)); + } + } +} + +static void usage_exit(const char *msg) +{ + if (msg) + fprintf(stderr, "%s: %s\n", quis, msg); + fprintf(stderr, "Usage: %s [--seed SEED] --soak | [game_id [game_id ...]]\n", quis); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + random_state *rs; + time_t seed = time(NULL); + int do_soak = 0, diff; + + game_params *p; + + maxtries = 50; + + quis = argv[0]; + while (--argc > 0) { + const char *p = *++argv; + if (!strcmp(p, "--soak")) + do_soak = 1; + else if (!strcmp(p, "--seed")) { + if (argc == 0) + usage_exit("--seed needs an argument"); + seed = (time_t)atoi(*++argv); + argc--; + } else if (*p == '-') + usage_exit("unrecognised option"); + else + break; + } + rs = random_new((void*)&seed, sizeof(time_t)); + + if (do_soak == 1) { + if (argc != 1) usage_exit("only one argument for --soak"); + p = default_params(); + decode_params(p, *argv); + soak(p, rs); + } else if (argc > 0) { + int i; + for (i = 0; i < argc; i++) { + const char *id = *argv++; + char *desc = strchr(id, ':'), *err; + p = default_params(); + if (desc) { + *desc++ = '\0'; + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", quis, err); + exit(1); + } + solve(p, desc, 1); + } else { + decode_params(p, id); + diff = gen(p, rs, 1); + } + } + } else { + while(1) { + p = default_params(); + p->order = random_upto(rs, 7) + 3; + p->diff = random_upto(rs, 4); + diff = gen(p, rs, 0); + pdiff(diff); + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/unfinished/README b/apps/plugins/puzzles/unfinished/README new file mode 100644 index 0000000000..0f8bb41d7c --- /dev/null +++ b/apps/plugins/puzzles/unfinished/README @@ -0,0 +1,9 @@ +This subdirectory contains puzzle implementations which are +half-written, fundamentally flawed, or in other ways unready to be +shipped as part of the polished Puzzles collection. + +Those puzzles which have .R files can be built as part of the +Puzzles collection by symlinking their source files into the parent +directory and re-running mkfiles.pl. Anything without a .R file +isn't even finished enough to do that, and you should read the +source file itself to find out the status. diff --git a/apps/plugins/puzzles/unfinished/group.R b/apps/plugins/puzzles/unfinished/group.R new file mode 100644 index 0000000000..a11d22e9b9 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/group.R @@ -0,0 +1,25 @@ +# -*- makefile -*- + +GROUP_LATIN_EXTRA = tree234 maxflow +GROUP_EXTRA = latin GROUP_LATIN_EXTRA + +group : [X] GTK COMMON group GROUP_EXTRA group-icon|no-icon + +group : [G] WINDOWS COMMON group GROUP_EXTRA group.res|noicon.res + +groupsolver : [U] group[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] GROUP_LATIN_EXTRA STANDALONE +groupsolver : [C] group[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] GROUP_LATIN_EXTRA STANDALONE + +ALL += group[COMBINED] GROUP_EXTRA + +!begin am gtk +GAMES += group +!end + +!begin >list.c + A(group) \ +!end + +!begin >gamedesc.txt +group:group.exe:Group:Group theory puzzle:Complete the unfinished Cayley table of a group. +!end diff --git a/apps/plugins/puzzles/unfinished/group.c b/apps/plugins/puzzles/unfinished/group.c new file mode 100644 index 0000000000..bec826e367 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/group.c @@ -0,0 +1,2198 @@ +/* + * group.c: a Latin-square puzzle, but played with groups' Cayley + * tables. That is, you are given a Cayley table of a group with + * most elements blank and a few clues, and you must fill it in + * so as to preserve the group axioms. + * + * This is a perfectly playable and fully working puzzle, but I'm + * leaving it for the moment in the 'unfinished' directory because + * it's just too esoteric (not to mention _hard_) for me to be + * comfortable presenting it to the general public as something they + * might (implicitly) actually want to play. + * + * TODO: + * + * - more solver techniques? + * * Inverses: once we know that gh = e, we can immediately + * deduce hg = e as well; then for any gx=y we can deduce + * hy=x, and for any xg=y we have yh=x. + * * Hard-mode associativity: we currently deduce based on + * definite numbers in the grid, but we could also winnow + * based on _possible_ numbers. + * * My overambitious original thoughts included wondering if we + * could infer that there must be elements of certain orders + * (e.g. a group of order divisible by 5 must contain an + * element of order 5), but I think in fact this is probably + * silly. + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" +#include "latin.h" + +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(TRIVIAL,Trivial,NULL,t) \ + A(NORMAL,Normal,solver_normal,n) \ + A(HARD,Hard,NULL,h) \ + A(EXTREME,Extreme,NULL,x) \ + A(UNREASONABLE,Unreasonable,NULL,u) +#define ENUM(upper,title,func,lower) DIFF_ ## upper, +#define TITLE(upper,title,func,lower) #title, +#define ENCODE(upper,title,func,lower) #lower +#define CONFIG(upper,title,func,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const group_diffnames[] = { DIFFLIST(TITLE) }; +static char const group_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +enum { + COL_BACKGROUND, + COL_GRID, + COL_USER, + COL_HIGHLIGHT, + COL_ERROR, + COL_PENCIL, + COL_DIAGONAL, + NCOLOURS +}; + +/* + * In identity mode, we number the elements e,a,b,c,d,f,g,h,... + * Otherwise, they're a,b,c,d,e,f,g,h,... in the obvious way. + */ +#define E_TO_FRONT(c,id) ( (id) && (c)<=5 ? (c) % 5 + 1 : (c) ) +#define E_FROM_FRONT(c,id) ( (id) && (c)<=5 ? ((c) + 3) % 5 + 1 : (c) ) + +#define FROMCHAR(c,id) E_TO_FRONT((((c)-('A'-1)) & ~0x20), id) +#define ISCHAR(c) (((c)>='A'&&(c)<='Z') || ((c)>='a'&&(c)<='z')) +#define TOCHAR(c,id) (E_FROM_FRONT(c,id) + ('a'-1)) + +struct game_params { + int w, diff, id; +}; + +struct game_state { + game_params par; + digit *grid; + unsigned char *immutable; + int *pencil; /* bitmaps using bits 1<<1..1<w = 6; + ret->diff = DIFF_NORMAL; + ret->id = TRUE; + + return ret; +} + +const static struct game_params group_presets[] = { + { 6, DIFF_NORMAL, TRUE }, + { 6, DIFF_NORMAL, FALSE }, + { 8, DIFF_NORMAL, TRUE }, + { 8, DIFF_NORMAL, FALSE }, + { 8, DIFF_HARD, TRUE }, + { 8, DIFF_HARD, FALSE }, + { 12, DIFF_NORMAL, TRUE }, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(group_presets)) + return FALSE; + + ret = snew(game_params); + *ret = group_presets[i]; /* structure copy */ + + sprintf(buf, "%dx%d %s%s", ret->w, ret->w, group_diffnames[ret->diff], + ret->id ? "" : ", identity hidden"); + + *name = dupstr(buf); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->w = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + params->diff = DIFF_NORMAL; + params->id = TRUE; + + while (*p) { + if (*p == 'd') { + int i; + p++; + params->diff = DIFFCOUNT+1; /* ...which is invalid */ + if (*p) { + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == group_diffchars[i]) + params->diff = i; + } + p++; + } + } else if (*p == 'i') { + params->id = FALSE; + p++; + } else { + /* unrecognised character */ + p++; + } + } +} + +static char *encode_params(const game_params *params, int full) +{ + char ret[80]; + + sprintf(ret, "%d", params->w); + if (full) + sprintf(ret + strlen(ret), "d%c", group_diffchars[params->diff]); + if (!params->id) + sprintf(ret + strlen(ret), "i"); + + return dupstr(ret); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Grid size"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Difficulty"; + ret[1].type = C_CHOICES; + ret[1].sval = DIFFCONFIG; + ret[1].ival = params->diff; + + ret[2].name = "Show identity"; + ret[2].type = C_BOOLEAN; + ret[2].sval = NULL; + ret[2].ival = params->id; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->diff = cfg[1].ival; + ret->id = cfg[2].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 3 || params->w > 26) + return "Grid size must be between 3 and 26"; + if (params->diff >= DIFFCOUNT) + return "Unknown difficulty rating"; + if (!params->id && params->diff == DIFF_TRIVIAL) { + /* + * We can't have a Trivial-difficulty puzzle (i.e. latin + * square deductions only) without a clear identity, because + * identityless puzzles always have two rows and two columns + * entirely blank, and no latin-square deduction permits the + * distinguishing of two such rows. + */ + return "Trivial puzzles must have an identity"; + } + if (!params->id && params->w == 3) { + /* + * We can't have a 3x3 puzzle without an identity either, + * because 3x3 puzzles can't ever be harder than Trivial + * (there are no 3x3 latin squares which aren't also valid + * group tables, so enabling group-based deductions doesn't + * rule out any possible solutions) and - as above - Trivial + * puzzles can't not have an identity. + */ + return "3x3 puzzles must have an identity"; + } + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +static int solver_normal(struct latin_solver *solver, void *vctx) +{ + int w = solver->o; +#ifdef STANDALONE_SOLVER + char **names = solver->names; +#endif + digit *grid = solver->grid; + int i, j, k; + + /* + * Deduce using associativity: (ab)c = a(bc). + * + * So we pick any a,b,c we like; then if we know ab, bc, and + * (ab)c we can fill in a(bc). + */ + for (i = 1; i < w; i++) + for (j = 1; j < w; j++) + for (k = 1; k < w; k++) { + if (!grid[i*w+j] || !grid[j*w+k]) + continue; + if (grid[(grid[i*w+j]-1)*w+k] && + !grid[i*w+(grid[j*w+k]-1)]) { + int x = grid[j*w+k]-1, y = i; + int n = grid[(grid[i*w+j]-1)*w+k]; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n", + solver_recurse_depth*4, "", + names[i], names[j], names[k], + names[grid[i*w+j]-1], names[k], + names[i], names[grid[j*w+k]-1]); + printf("%*s placing %s at (%d,%d)\n", + solver_recurse_depth*4, "", + names[n-1], x+1, y+1); + } +#endif + if (solver->cube[(x*w+y)*w+n-1]) { + latin_solver_place(solver, x, y, n); + return 1; + } else { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s contradiction!\n", + solver_recurse_depth*4, ""); + return -1; +#endif + } + } + if (!grid[(grid[i*w+j]-1)*w+k] && + grid[i*w+(grid[j*w+k]-1)]) { + int x = k, y = grid[i*w+j]-1; + int n = grid[i*w+(grid[j*w+k]-1)]; +#ifdef STANDALONE_SOLVER + if (solver_show_working) { + printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n", + solver_recurse_depth*4, "", + names[i], names[j], names[k], + names[grid[i*w+j]-1], names[k], + names[i], names[grid[j*w+k]-1]); + printf("%*s placing %s at (%d,%d)\n", + solver_recurse_depth*4, "", + names[n-1], x+1, y+1); + } +#endif + if (solver->cube[(x*w+y)*w+n-1]) { + latin_solver_place(solver, x, y, n); + return 1; + } else { +#ifdef STANDALONE_SOLVER + if (solver_show_working) + printf("%*s contradiction!\n", + solver_recurse_depth*4, ""); + return -1; +#endif + } + } + } + + return 0; +} + +#define SOLVER(upper,title,func,lower) func, +static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) }; + +static int solver(const game_params *params, digit *grid, int maxdiff) +{ + int w = params->w; + int ret; + struct latin_solver solver; +#ifdef STANDALONE_SOLVER + char *p, text[100], *names[50]; + int i; +#endif + + latin_solver_alloc(&solver, grid, w); +#ifdef STANDALONE_SOLVER + for (i = 0, p = text; i < w; i++) { + names[i] = p; + *p++ = TOCHAR(i+1, params->id); + *p++ = '\0'; + } + solver.names = names; +#endif + + ret = latin_solver_main(&solver, maxdiff, + DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME, + DIFF_EXTREME, DIFF_UNREASONABLE, + group_solvers, NULL, NULL, NULL); + + latin_solver_free(&solver); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Grid generation. + */ + +static char *encode_grid(char *desc, digit *grid, int area) +{ + int run, i; + char *p = desc; + + run = 0; + for (i = 0; i <= area; i++) { + int n = (i < area ? grid[i] : -1); + + if (!n) + run++; + else { + if (run) { + while (run > 0) { + int c = 'a' - 1 + run; + if (run > 26) + c = 'z'; + *p++ = c; + run -= c - ('a' - 1); + } + } else { + /* + * If there's a number in the very top left or + * bottom right, there's no point putting an + * unnecessary _ before or after it. + */ + if (p > desc && n > 0) + *p++ = '_'; + } + if (n > 0) + p += sprintf(p, "%d", n); + run = 0; + } + } + return p; +} + +/* ----- data generated by group.gap begins ----- */ + +struct group { + unsigned long autosize; + int order, ngens; + const char *gens; +}; +struct groups { + int ngroups; + const struct group *groups; +}; + +static const struct group groupdata[] = { + /* order 2 */ + {1L, 2, 1, "BA"}, + /* order 3 */ + {2L, 3, 1, "BCA"}, + /* order 4 */ + {2L, 4, 1, "BCDA"}, + {6L, 4, 2, "BADC" "CDAB"}, + /* order 5 */ + {4L, 5, 1, "BCDEA"}, + /* order 6 */ + {6L, 6, 2, "CFEBAD" "BADCFE"}, + {2L, 6, 1, "DCFEBA"}, + /* order 7 */ + {6L, 7, 1, "BCDEFGA"}, + /* order 8 */ + {4L, 8, 1, "BCEFDGHA"}, + {8L, 8, 2, "BDEFGAHC" "EGBHDCFA"}, + {8L, 8, 2, "EGBHDCFA" "BAEFCDHG"}, + {24L, 8, 2, "BDEFGAHC" "CHDGBEAF"}, + {168L, 8, 3, "BAEFCDHG" "CEAGBHDF" "DFGAHBCE"}, + /* order 9 */ + {6L, 9, 1, "BDECGHFIA"}, + {48L, 9, 2, "BDEAGHCIF" "CEFGHAIBD"}, + /* order 10 */ + {20L, 10, 2, "CJEBGDIFAH" "BADCFEHGJI"}, + {4L, 10, 1, "DCFEHGJIBA"}, + /* order 11 */ + {10L, 11, 1, "BCDEFGHIJKA"}, + /* order 12 */ + {12L, 12, 2, "GLDKJEHCBIAF" "BCEFAGIJDKLH"}, + {4L, 12, 1, "EHIJKCBLDGFA"}, + {24L, 12, 2, "BEFGAIJKCDLH" "FJBKHLEGDCIA"}, + {12L, 12, 2, "GLDKJEHCBIAF" "BAEFCDIJGHLK"}, + {12L, 12, 2, "FDIJGHLBKAEC" "GIDKFLHCJEAB"}, + /* order 13 */ + {12L, 13, 1, "BCDEFGHIJKLMA"}, + /* order 14 */ + {42L, 14, 2, "ELGNIBKDMFAHCJ" "BADCFEHGJILKNM"}, + {6L, 14, 1, "FEHGJILKNMBADC"}, + /* order 15 */ + {8L, 15, 1, "EGHCJKFMNIOBLDA"}, + /* order 16 */ + {8L, 16, 1, "MKNPFOADBGLCIEHJ"}, + {96L, 16, 2, "ILKCONFPEDJHGMAB" "BDFGHIAKLMNCOEPJ"}, + {32L, 16, 2, "MIHPFDCONBLAKJGE" "BEFGHJKALMNOCDPI"}, + {32L, 16, 2, "IFACOGLMDEJBNPKH" "BEFGHJKALMNOCDPI"}, + {16L, 16, 2, "MOHPFKCINBLADJGE" "BDFGHIEKLMNJOAPC"}, + {16L, 16, 2, "MIHPFDJONBLEKCGA" "BDFGHIEKLMNJOAPC"}, + {32L, 16, 2, "MOHPFDCINBLEKJGA" "BAFGHCDELMNIJKPO"}, + {16L, 16, 2, "MIHPFKJONBLADCGE" "GDPHNOEKFLBCIAMJ"}, + {32L, 16, 2, "MIBPFDJOGHLEKCNA" "CLEIJGMPKAOHNFDB"}, + {192L, 16, 3, + "MCHPFAIJNBLDEOGK" "BEFGHJKALMNOCDPI" "GKLBNOEDFPHJIAMC"}, + {64L, 16, 3, "MCHPFAIJNBLDEOGK" "LOGFPKJIBNMEDCHA" "CMAIJHPFDEONBLKG"}, + {192L, 16, 3, + "IPKCOGMLEDJBNFAH" "BEFGHJKALMNOCDPI" "CMEIJBPFKAOGHLDN"}, + {48L, 16, 3, "IPDJONFLEKCBGMAH" "FJBLMEOCGHPKAIND" "DGIEKLHNJOAMPBCF"}, + {20160L, 16, 4, + "EHJKAMNBOCDPFGIL" "BAFGHCDELMNIJKPO" "CFAIJBLMDEOGHPKN" + "DGIAKLBNCOEFPHJM"}, + /* order 17 */ + {16L, 17, 1, "EFGHIJKLMNOPQABCD"}, + /* order 18 */ + {54L, 18, 2, "MKIQOPNAGLRECDBJHF" "BAEFCDJKLGHIOPMNRQ"}, + {6L, 18, 1, "ECJKGHFOPDMNLRIQBA"}, + {12L, 18, 2, "ECJKGHBOPAMNFRDQLI" "KNOPQCFREIGHLJAMBD"}, + {432L, 18, 3, + "IFNAKLQCDOPBGHREMJ" "NOQCFRIGHKLJAMPBDE" "BAEFCDJKLGHIOPMNRQ"}, + {48L, 18, 2, "ECJKGHBOPAMNFRDQLI" "FDKLHIOPBMNAREQCJG"}, + /* order 19 */ + {18L, 19, 1, "EFGHIJKLMNOPQRSABCD"}, + /* order 20 */ + {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "EABICDFMGHJQKLNTOPRS"}, + {8L, 20, 1, "EHIJLCMNPGQRSKBTDOFA"}, + {20L, 20, 2, "DJSHQNCLTRGPEBKAIFOM" "EABICDFMGHJQKLNTOPRS"}, + {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "ECBIAGFMDKJQHONTLSRP"}, + {24L, 20, 2, "IGFMDKJQHONTLSREPCBA" "FDIJGHMNKLQROPTBSAEC"}, + /* order 21 */ + {42L, 21, 2, "ITLSBOUERDHAGKCJNFMQP" "EJHLMKOPNRSQAUTCDBFGI"}, + {12L, 21, 1, "EGHCJKFMNIPQLSTOUBRDA"}, + /* order 22 */ + {110L, 22, 2, "ETGVIBKDMFOHQJSLUNAPCR" "BADCFEHGJILKNMPORQTSVU"}, + {10L, 22, 1, "FEHGJILKNMPORQTSVUBADC"}, + /* order 23 */ + {22L, 23, 1, "EFGHIJKLMNOPQRSTUVWABCD"}, + /* order 24 */ + {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "HRNOPSWCTUVBLDIJXFGAKQME"}, + {8L, 24, 1, "MQBTUDRWFGHXJELINOPKSAVC"}, + {24L, 24, 2, "IOQRBEUVFWGHKLAXMNPSCDTJ" "NJXOVGDKSMTFIPQELCURBWAH"}, + {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "HSNOPWLDTUVBRIAKXFGCQEMJ"}, + {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "TWHNXLRIOPUMSACQVBFDEJGK"}, + {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "BAFGHCDEMNOPIJKLTUVQRSXW"}, + {48L, 24, 3, + "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO" + "HSNOPWLDTUVBRIAKXFGCQEMJ"}, + {24L, 24, 3, + "QUKJWPXFESRIVBMNLDCGHTAO" "JXEQRVUMKLWCPGFTSAIBONDH" + "TRONXLWCHVUMSAIJPGFDEQBK"}, + {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "VKXHOQASNTPBCWDEUFGIJLMR"}, + {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "RMLWIGTUSDJQOPFXEKCBVNAH"}, + {48L, 24, 2, "IULQRGXMSDCWOPNTEKJBVFAH" "GLMOPRSDTUBVWIEKFXHJQANC"}, + {24L, 24, 2, "UJPXMRCSNHGTLWIKFVBEDQOA" "NRUFVLWIPXMOJEDQHGTCSABK"}, + {24L, 24, 2, "MIBTUAQRFGHXCDEWNOPJKLVS" "OKXVFWSCGUTNDRQJBPMALIHE"}, + {144L, 24, 3, + "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO" + "BAFGHCDEMNOPIJKLTUVQRSXW"}, + {336L, 24, 3, + "QTKJWONXESRIHVUMLDCPGFAB" "JNEQRHTUKLWCOPXFSAIVBMDG" + "HENOPJKLTUVBQRSAXFGWCDMI"}, + /* order 25 */ + {20L, 25, 1, "EHILMNPQRSFTUVBJWXDOYGAKC"}, + {480L, 25, 2, "EHILMNPQRSCTUVBFWXDJYGOKA" "BDEGHIKLMNAPQRSCTUVFWXJYO"}, + /* order 26 */ + {156L, 26, 2, + "EXGZIBKDMFOHQJSLUNWPYRATCV" "BADCFEHGJILKNMPORQTSVUXWZY"}, + {12L, 26, 1, "FEHGJILKNMPORQTSVUXWZYBADC"}, +}; + +static const struct groups groups[] = { + {0, NULL}, /* trivial case: 0 */ + {0, NULL}, /* trivial case: 1 */ + {1, groupdata + 0}, /* 2 */ + {1, groupdata + 1}, /* 3 */ + {2, groupdata + 2}, /* 4 */ + {1, groupdata + 4}, /* 5 */ + {2, groupdata + 5}, /* 6 */ + {1, groupdata + 7}, /* 7 */ + {5, groupdata + 8}, /* 8 */ + {2, groupdata + 13}, /* 9 */ + {2, groupdata + 15}, /* 10 */ + {1, groupdata + 17}, /* 11 */ + {5, groupdata + 18}, /* 12 */ + {1, groupdata + 23}, /* 13 */ + {2, groupdata + 24}, /* 14 */ + {1, groupdata + 26}, /* 15 */ + {14, groupdata + 27}, /* 16 */ + {1, groupdata + 41}, /* 17 */ + {5, groupdata + 42}, /* 18 */ + {1, groupdata + 47}, /* 19 */ + {5, groupdata + 48}, /* 20 */ + {2, groupdata + 53}, /* 21 */ + {2, groupdata + 55}, /* 22 */ + {1, groupdata + 57}, /* 23 */ + {15, groupdata + 58}, /* 24 */ + {2, groupdata + 73}, /* 25 */ + {2, groupdata + 75}, /* 26 */ +}; + +/* ----- data generated by group.gap ends ----- */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, a = w*w; + digit *grid, *soln, *soln2; + int *indices; + int i, j, k, qh, qt; + int diff = params->diff; + const struct group *group; + char *desc, *p; + + /* + * Difficulty exceptions: some combinations of size and + * difficulty cannot be satisfied, because all puzzles of at + * most that difficulty are actually even easier. + * + * Remember to re-test this whenever a change is made to the + * solver logic! + * + * I tested it using the following shell command: + +for d in t n h x u; do + for id in '' i; do + for i in {3..9}; do + echo -n "./group --generate 1 ${i}d${d}${id}: " + perl -e 'alarm 30; exec @ARGV' \ + ./group --generate 1 ${i}d${d}${id} >/dev/null && echo ok + done + done +done + + * Of course, it's better to do that after taking the exceptions + * _out_, so as to detect exceptions that should be removed as + * well as those which should be added. + */ + if (w < 5 && diff == DIFF_UNREASONABLE) + diff--; + if ((w < 5 || ((w == 6 || w == 8) && params->id)) && diff == DIFF_EXTREME) + diff--; + if ((w < 6 || (w == 6 && params->id)) && diff == DIFF_HARD) + diff--; + if ((w < 4 || (w == 4 && params->id)) && diff == DIFF_NORMAL) + diff--; + + grid = snewn(a, digit); + soln = snewn(a, digit); + soln2 = snewn(a, digit); + indices = snewn(a, int); + + while (1) { + /* + * Construct a valid group table, by picking a group from + * the above data table, decompressing it into a full + * representation by BFS, and then randomly permuting its + * non-identity elements. + * + * We build the canonical table in 'soln' (and use 'grid' as + * our BFS queue), then transfer the table into 'grid' + * having shuffled the rows. + */ + assert(w >= 2); + assert(w < lenof(groups)); + group = groups[w].groups + random_upto(rs, groups[w].ngroups); + assert(group->order == w); + memset(soln, 0, a); + for (i = 0; i < w; i++) + soln[i] = i+1; + qh = qt = 0; + grid[qt++] = 1; + while (qh < qt) { + digit *row, *newrow; + + i = grid[qh++]; + row = soln + (i-1)*w; + + for (j = 0; j < group->ngens; j++) { + int nri; + const char *gen = group->gens + j*w; + + /* + * Apply each group generator to row, constructing a + * new row. + */ + nri = gen[row[0]-1] - 'A' + 1; /* which row is it? */ + newrow = soln + (nri-1)*w; + if (!newrow[0]) { /* not done yet */ + for (k = 0; k < w; k++) + newrow[k] = gen[row[k]-1] - 'A' + 1; + grid[qt++] = nri; + } + } + } + /* That's got the canonical table. Now shuffle it. */ + for (i = 0; i < w; i++) + soln2[i] = i; + if (params->id) /* do we shuffle in the identity? */ + shuffle(soln2+1, w-1, sizeof(*soln2), rs); + else + shuffle(soln2, w, sizeof(*soln2), rs); + for (i = 0; i < w; i++) + for (j = 0; j < w; j++) + grid[(soln2[i])*w+(soln2[j])] = soln2[soln[i*w+j]-1]+1; + + /* + * Remove entries one by one while the puzzle is still + * soluble at the appropriate difficulty level. + */ + memcpy(soln, grid, a); + if (!params->id) { + /* + * Start by blanking the entire identity row and column, + * and also another row and column so that the player + * can't trivially determine which element is the + * identity. + */ + + j = 1 + random_upto(rs, w-1); /* pick a second row/col to blank */ + for (i = 0; i < w; i++) { + grid[(soln2[0])*w+i] = grid[i*w+(soln2[0])] = 0; + grid[(soln2[j])*w+i] = grid[i*w+(soln2[j])] = 0; + } + + memcpy(soln2, grid, a); + if (solver(params, soln2, diff) > diff) + continue; /* go round again if that didn't work */ + } + + k = 0; + for (i = (params->id ? 1 : 0); i < w; i++) + for (j = (params->id ? 1 : 0); j < w; j++) + if (grid[i*w+j]) + indices[k++] = i*w+j; + shuffle(indices, k, sizeof(*indices), rs); + + for (i = 0; i < k; i++) { + memcpy(soln2, grid, a); + soln2[indices[i]] = 0; + if (solver(params, soln2, diff) <= diff) + grid[indices[i]] = 0; + } + + /* + * Make sure the puzzle isn't too easy. + */ + if (diff > 0) { + memcpy(soln2, grid, a); + if (solver(params, soln2, diff-1) < diff) + continue; /* go round and try again */ + } + + /* + * Done. + */ + break; + } + + /* + * Encode the puzzle description. + */ + desc = snewn(a*20, char); + p = encode_grid(desc, grid, a); + *p++ = '\0'; + desc = sresize(desc, p - desc, char); + + /* + * Encode the solution. + */ + *aux = snewn(a+2, char); + (*aux)[0] = 'S'; + for (i = 0; i < a; i++) + (*aux)[i+1] = TOCHAR(soln[i], params->id); + (*aux)[a+1] = '\0'; + + sfree(grid); + sfree(soln); + sfree(soln2); + sfree(indices); + + return desc; +} + +/* ---------------------------------------------------------------------- + * Gameplay. + */ + +static char *validate_grid_desc(const char **pdesc, int range, int area) +{ + const char *desc = *pdesc; + int squares = 0; + while (*desc && *desc != ',') { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + squares += n - 'a' + 1; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + int val = atoi(desc-1); + if (val < 1 || val > range) + return "Out-of-range number in game description"; + squares++; + while (*desc >= '0' && *desc <= '9') + desc++; + } else + return "Invalid character in game description"; + } + + if (squares < area) + return "Not enough data to fill grid"; + + if (squares > area) + return "Too much data to fit in grid"; + *pdesc = desc; + return NULL; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, a = w*w; + const char *p = desc; + + return validate_grid_desc(&p, w, a); +} + +static const char *spec_to_grid(const char *desc, digit *grid, int area) +{ + int i = 0; + while (*desc && *desc != ',') { + int n = *desc++; + if (n >= 'a' && n <= 'z') { + int run = n - 'a' + 1; + assert(i + run <= area); + while (run-- > 0) + grid[i++] = 0; + } else if (n == '_') { + /* do nothing */; + } else if (n > '0' && n <= '9') { + assert(i < area); + grid[i++] = atoi(desc-1); + while (*desc >= '0' && *desc <= '9') + desc++; + } else { + assert(!"We can't get here"); + } + } + assert(i == area); + return desc; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, a = w*w; + game_state *state = snew(game_state); + int i; + + state->par = *params; /* structure copy */ + state->grid = snewn(a, digit); + state->immutable = snewn(a, unsigned char); + state->pencil = snewn(a, int); + for (i = 0; i < a; i++) { + state->grid[i] = 0; + state->immutable[i] = 0; + state->pencil[i] = 0; + } + state->sequence = snewn(w, digit); + state->dividers = snewn(w, int); + for (i = 0; i < w; i++) { + state->sequence[i] = i; + state->dividers[i] = -1; + } + + desc = spec_to_grid(desc, state->grid, a); + for (i = 0; i < a; i++) + if (state->grid[i] != 0) + state->immutable[i] = TRUE; + + state->completed = state->cheated = FALSE; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->par.w, a = w*w; + game_state *ret = snew(game_state); + + ret->par = state->par; /* structure copy */ + + ret->grid = snewn(a, digit); + ret->immutable = snewn(a, unsigned char); + ret->pencil = snewn(a, int); + ret->sequence = snewn(w, digit); + ret->dividers = snewn(w, int); + memcpy(ret->grid, state->grid, a*sizeof(digit)); + memcpy(ret->immutable, state->immutable, a*sizeof(unsigned char)); + memcpy(ret->pencil, state->pencil, a*sizeof(int)); + memcpy(ret->sequence, state->sequence, w*sizeof(digit)); + memcpy(ret->dividers, state->dividers, w*sizeof(int)); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->immutable); + sfree(state->pencil); + sfree(state->sequence); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int w = state->par.w, a = w*w; + int i, ret; + digit *soln; + char *out; + + if (aux) + return dupstr(aux); + + soln = snewn(a, digit); + memcpy(soln, state->grid, a*sizeof(digit)); + + ret = solver(&state->par, soln, DIFFCOUNT-1); + + if (ret == diff_impossible) { + *error = "No solution exists for this puzzle"; + out = NULL; + } else if (ret == diff_ambiguous) { + *error = "Multiple solutions exist for this puzzle"; + out = NULL; + } else { + out = snewn(a+2, char); + out[0] = 'S'; + for (i = 0; i < a; i++) + out[i+1] = TOCHAR(soln[i], state->par.id); + out[a+1] = '\0'; + } + + sfree(soln); + return out; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w = state->par.w; + int x, y; + char *ret, *p, ch; + + ret = snewn(2*w*w+1, char); /* leave room for terminating NUL */ + + p = ret; + for (y = 0; y < w; y++) { + for (x = 0; x < w; x++) { + digit d = state->grid[y*w+x]; + + if (d == 0) { + ch = '.'; + } else { + ch = TOCHAR(d, state->par.id); + } + + *p++ = ch; + if (x == w-1) { + *p++ = '\n'; + } else { + *p++ = ' '; + } + } + } + + assert(p - ret == 2*w*w); + *p = '\0'; + return ret; +} + +struct game_ui { + /* + * These are the coordinates of the primary highlighted square on + * the grid, if hshow = 1. + */ + int hx, hy; + /* + * These are the coordinates hx,hy _before_ they go through + * state->sequence. + */ + int ohx, ohy; + /* + * These variables give the length and displacement of a diagonal + * sequence of highlighted squares starting at ohx,ohy (still if + * hshow = 1). To find the squares' real coordinates, for 0<=isequence. + */ + int odx, ody, odn; + /* + * This indicates whether the current highlight is a + * pencil-mark one or a real one. + */ + int hpencil; + /* + * This indicates whether or not we're showing the highlight + * (used to be hx = hy = -1); important so that when we're + * using the cursor keys it doesn't keep coming back at a + * fixed position. When hshow = 1, pressing a valid number + * or letter key or Space will enter that number or letter in the grid. + */ + int hshow; + /* + * This indicates whether we're using the highlight as a cursor; + * it means that it doesn't vanish on a keypress, and that it is + * allowed on immutable squares. + */ + int hcursor; + /* + * This indicates whether we're dragging a table header to + * reposition an entire row or column. + */ + int drag; /* 0=none 1=row 2=col */ + int dragnum; /* element being dragged */ + int dragpos; /* its current position */ + int edgepos; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + + ui->hx = ui->hy = 0; + ui->hpencil = ui->hshow = ui->hcursor = 0; + ui->drag = 0; + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + int w = newstate->par.w; + /* + * We prevent pencil-mode highlighting of a filled square, unless + * we're using the cursor keys. So if the user has just filled in + * a square which we had a pencil-mode highlight in (by Undo, or + * by Redo, or by Solve), then we cancel the highlight. + */ + if (ui->hshow && ui->hpencil && !ui->hcursor && + newstate->grid[ui->hy * w + ui->hx] != 0) { + ui->hshow = 0; + } + if (ui->hshow && ui->odn > 1) { + /* + * Reordering of rows or columns within the range of a + * multifill selection cancels the multifill and deselects + * everything. + */ + int i; + for (i = 0; i < ui->odn; i++) { + if (oldstate->sequence[ui->ohx + i*ui->odx] != + newstate->sequence[ui->ohx + i*ui->odx]) { + ui->hshow = 0; + break; + } + if (oldstate->sequence[ui->ohy + i*ui->ody] != + newstate->sequence[ui->ohy + i*ui->ody]) { + ui->hshow = 0; + break; + } + } + } else if (ui->hshow && + (newstate->sequence[ui->ohx] != ui->hx || + newstate->sequence[ui->ohy] != ui->hy)) { + /* + * Otherwise, reordering of the row or column containing the + * selection causes the selection to move with it. + */ + int i; + for (i = 0; i < w; i++) { + if (newstate->sequence[i] == ui->hx) + ui->ohx = i; + if (newstate->sequence[i] == ui->hy) + ui->ohy = i; + } + } +} + +#define PREFERRED_TILESIZE 48 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE / 2) +#define LEGEND (TILESIZE) +#define GRIDEXTRA max((TILESIZE / 32),1) +#define COORD(x) ((x)*TILESIZE + BORDER + LEGEND) +#define FROMCOORD(x) (((x)+(TILESIZE-BORDER-LEGEND)) / TILESIZE - 1) + +#define FLASH_TIME 0.4F + +#define DF_DIVIDER_TOP 0x1000 +#define DF_DIVIDER_BOT 0x2000 +#define DF_DIVIDER_LEFT 0x4000 +#define DF_DIVIDER_RIGHT 0x8000 +#define DF_HIGHLIGHT 0x0400 +#define DF_HIGHLIGHT_PENCIL 0x0200 +#define DF_IMMUTABLE 0x0100 +#define DF_LEGEND 0x0080 +#define DF_DIGIT_MASK 0x001F + +#define EF_DIGIT_SHIFT 5 +#define EF_DIGIT_MASK ((1 << EF_DIGIT_SHIFT) - 1) +#define EF_LEFT_SHIFT 0 +#define EF_RIGHT_SHIFT (3*EF_DIGIT_SHIFT) +#define EF_LEFT_MASK ((1UL << (3*EF_DIGIT_SHIFT)) - 1UL) +#define EF_RIGHT_MASK (EF_LEFT_MASK << EF_RIGHT_SHIFT) +#define EF_LATIN (1UL << (6*EF_DIGIT_SHIFT)) + +struct game_drawstate { + game_params par; + int w, tilesize; + int started; + long *tiles, *legend, *pencil, *errors; + long *errtmp; + digit *sequence; +}; + +static int check_errors(const game_state *state, long *errors) +{ + int w = state->par.w, a = w*w; + digit *grid = state->grid; + int i, j, k, x, y, errs = FALSE; + + /* + * To verify that we have a valid group table, it suffices to + * test latin-square-hood and associativity only. All the other + * group axioms follow from those two. + * + * Proof: + * + * Associativity is given; closure is obvious from latin- + * square-hood. We need to show that an identity exists and that + * every element has an inverse. + * + * Identity: take any element a. There will be some element e + * such that ea=a (in a latin square, every element occurs in + * every row and column, so a must occur somewhere in the a + * column, say on row e). For any other element b, there must + * exist x such that ax=b (same argument from latin-square-hood + * again), and then associativity gives us eb = e(ax) = (ea)x = + * ax = b. Hence eb=b for all b, i.e. e is a left-identity. A + * similar argument tells us that there must be some f which is + * a right-identity, and then we show they are the same element + * by observing that ef must simultaneously equal e and equal f. + * + * Inverses: given any a, by the latin-square argument again, + * there must exist p and q such that pa=e and aq=e (i.e. left- + * and right-inverses). We can show these are equal by + * associativity: p = pe = p(aq) = (pa)q = eq = q. [] + */ + + if (errors) + for (i = 0; i < a; i++) + errors[i] = 0; + + for (y = 0; y < w; y++) { + unsigned long mask = 0, errmask = 0; + for (x = 0; x < w; x++) { + unsigned long bit = 1UL << grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1 << (w+1)) - (1 << 1)) { + errs = TRUE; + errmask &= ~1UL; + if (errors) { + for (x = 0; x < w; x++) + if (errmask & (1UL << grid[y*w+x])) + errors[y*w+x] |= EF_LATIN; + } + } + } + + for (x = 0; x < w; x++) { + unsigned long mask = 0, errmask = 0; + for (y = 0; y < w; y++) { + unsigned long bit = 1UL << grid[y*w+x]; + errmask |= (mask & bit); + mask |= bit; + } + + if (mask != (1 << (w+1)) - (1 << 1)) { + errs = TRUE; + errmask &= ~1UL; + if (errors) { + for (y = 0; y < w; y++) + if (errmask & (1UL << grid[y*w+x])) + errors[y*w+x] |= EF_LATIN; + } + } + } + + for (i = 1; i < w; i++) + for (j = 1; j < w; j++) + for (k = 1; k < w; k++) + if (grid[i*w+j] && grid[j*w+k] && + grid[(grid[i*w+j]-1)*w+k] && + grid[i*w+(grid[j*w+k]-1)] && + grid[(grid[i*w+j]-1)*w+k] != grid[i*w+(grid[j*w+k]-1)]) { + if (errors) { + int a = i+1, b = j+1, c = k+1; + int ab = grid[i*w+j], bc = grid[j*w+k]; + int left = (ab-1)*w+(c-1), right = (a-1)*w+(bc-1); + /* + * If the appropriate error slot is already + * used for one of the squares, we don't + * fill either of them. + */ + if (!(errors[left] & EF_LEFT_MASK) && + !(errors[right] & EF_RIGHT_MASK)) { + long err; + err = a; + err = (err << EF_DIGIT_SHIFT) | b; + err = (err << EF_DIGIT_SHIFT) | c; + errors[left] |= err << EF_LEFT_SHIFT; + errors[right] |= err << EF_RIGHT_SHIFT; + } + } + errs = TRUE; + } + + return errs; +} + +static int find_in_sequence(digit *seq, int len, digit n) +{ + int i; + + for (i = 0; i < len; i++) + if (seq[i] == n) + return i; + + assert(!"Should never get here"); + return -1; +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->par.w; + int tx, ty; + char buf[80]; + + button &= ~MOD_MASK; + + tx = FROMCOORD(x); + ty = FROMCOORD(y); + + if (ui->drag) { + if (IS_MOUSE_DRAG(button)) { + int tcoord = ((ui->drag &~ 4) == 1 ? ty : tx); + ui->drag |= 4; /* some movement has happened */ + if (tcoord >= 0 && tcoord < w) { + ui->dragpos = tcoord; + return ""; + } + } else if (IS_MOUSE_RELEASE(button)) { + if (ui->drag & 4) { + ui->drag = 0; /* end drag */ + if (state->sequence[ui->dragpos] == ui->dragnum) + return ""; /* drag was a no-op overall */ + sprintf(buf, "D%d,%d", ui->dragnum, ui->dragpos); + return dupstr(buf); + } else { + ui->drag = 0; /* end 'drag' */ + if (ui->edgepos > 0 && ui->edgepos < w) { + sprintf(buf, "V%d,%d", + state->sequence[ui->edgepos-1], + state->sequence[ui->edgepos]); + return dupstr(buf); + } else + return ""; /* no-op */ + } + } + } else if (IS_MOUSE_DOWN(button)) { + if (tx >= 0 && tx < w && ty >= 0 && ty < w) { + int otx = tx, oty = ty; + tx = state->sequence[tx]; + ty = state->sequence[ty]; + if (button == LEFT_BUTTON) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil == 0) { + ui->hshow = 0; + } else { + ui->hx = tx; + ui->hy = ty; + ui->ohx = otx; + ui->ohy = oty; + ui->odx = ui->ody = 0; + ui->odn = 1; + ui->hshow = !state->immutable[ty*w+tx]; + ui->hpencil = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + if (button == RIGHT_BUTTON) { + /* + * Pencil-mode highlighting for non filled squares. + */ + if (state->grid[ty*w+tx] == 0) { + if (tx == ui->hx && ty == ui->hy && + ui->hshow && ui->hpencil) { + ui->hshow = 0; + } else { + ui->hpencil = 1; + ui->hx = tx; + ui->hy = ty; + ui->ohx = otx; + ui->ohy = oty; + ui->odx = ui->ody = 0; + ui->odn = 1; + ui->hshow = 1; + } + } else { + ui->hshow = 0; + } + ui->hcursor = 0; + return ""; /* UI activity occurred */ + } + } else if (tx >= 0 && tx < w && ty == -1) { + ui->drag = 2; + ui->dragnum = state->sequence[tx]; + ui->dragpos = tx; + ui->edgepos = FROMCOORD(x + TILESIZE/2); + return ""; + } else if (ty >= 0 && ty < w && tx == -1) { + ui->drag = 1; + ui->dragnum = state->sequence[ty]; + ui->dragpos = ty; + ui->edgepos = FROMCOORD(y + TILESIZE/2); + return ""; + } + } else if (IS_MOUSE_DRAG(button)) { + if (!ui->hpencil && + tx >= 0 && tx < w && ty >= 0 && ty < w && + abs(tx - ui->ohx) == abs(ty - ui->ohy)) { + ui->odn = abs(tx - ui->ohx) + 1; + ui->odx = (tx < ui->ohx ? -1 : +1); + ui->ody = (ty < ui->ohy ? -1 : +1); + } else { + ui->odx = ui->ody = 0; + ui->odn = 1; + } + return ""; + } + + if (IS_CURSOR_MOVE(button)) { + int cx = find_in_sequence(state->sequence, w, ui->hx); + int cy = find_in_sequence(state->sequence, w, ui->hy); + move_cursor(button, &cx, &cy, w, w, 0); + ui->hx = state->sequence[cx]; + ui->hy = state->sequence[cy]; + ui->hshow = ui->hcursor = 1; + return ""; + } + if (ui->hshow && + (button == CURSOR_SELECT)) { + ui->hpencil = 1 - ui->hpencil; + ui->hcursor = 1; + return ""; + } + + if (ui->hshow && + ((ISCHAR(button) && FROMCHAR(button, state->par.id) <= w) || + button == CURSOR_SELECT2 || button == '\b')) { + int n = FROMCHAR(button, state->par.id); + int i, buflen; + char *movebuf; + + if (button == CURSOR_SELECT2 || button == '\b') + n = 0; + + for (i = 0; i < ui->odn; i++) { + int x = state->sequence[ui->ohx + i*ui->odx]; + int y = state->sequence[ui->ohy + i*ui->ody]; + int index = y*w+x; + + /* + * Can't make pencil marks in a filled square. This can only + * become highlighted if we're using cursor keys. + */ + if (ui->hpencil && state->grid[index]) + return NULL; + + /* + * Can't do anything to an immutable square. Exception: + * trying to set it to what it already was is OK (so that + * multifilling can set a whole diagonal to a without + * having to detour round the one immutable square in the + * middle that already said a). + */ + if (!ui->hpencil && state->grid[index] == n) + /* OK even if it is immutable */; + else if (state->immutable[index]) + return NULL; + } + + movebuf = snewn(80 * ui->odn, char); + buflen = sprintf(movebuf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), + ui->hx, ui->hy, n); + for (i = 1; i < ui->odn; i++) { + assert(buflen < i*80); + buflen += sprintf(movebuf + buflen, "+%d,%d", + state->sequence[ui->ohx + i*ui->odx], + state->sequence[ui->ohy + i*ui->ody]); + } + movebuf = sresize(movebuf, buflen+1, char); + + if (!ui->hcursor) ui->hshow = 0; + + return movebuf; + } + + if (button == 'M' || button == 'm') + return dupstr("M"); + + return NULL; +} + +static game_state *execute_move(const game_state *from, const char *move) +{ + int w = from->par.w, a = w*w; + game_state *ret; + int x, y, i, j, n, pos; + + if (move[0] == 'S') { + ret = dup_game(from); + ret->completed = ret->cheated = TRUE; + + for (i = 0; i < a; i++) { + if (!ISCHAR(move[i+1]) || FROMCHAR(move[i+1], from->par.id) > w) { + free_game(ret); + return NULL; + } + ret->grid[i] = FROMCHAR(move[i+1], from->par.id); + ret->pencil[i] = 0; + } + + if (move[a+1] != '\0') { + free_game(ret); + return NULL; + } + + return ret; + } else if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d%n", &x, &y, &n, &pos) == 3 && + n >= 0 && n <= w) { + const char *mp = move + 1 + pos; + int pencil = (move[0] == 'P'); + ret = dup_game(from); + + while (1) { + if (x < 0 || x >= w || y < 0 || y >= w) { + free_game(ret); + return NULL; + } + if (from->immutable[y*w+x] && !(!pencil && from->grid[y*w+x] == n)) + return NULL; + + if (move[0] == 'P' && n > 0) { + ret->pencil[y*w+x] ^= 1 << n; + } else { + ret->grid[y*w+x] = n; + ret->pencil[y*w+x] = 0; + } + + if (!*mp) + break; + + if (*mp != '+') + return NULL; + if (sscanf(mp, "+%d,%d%n", &x, &y, &pos) < 2) + return NULL; + mp += pos; + } + + if (!ret->completed && !check_errors(ret, NULL)) + ret->completed = TRUE; + + return ret; + } else if (move[0] == 'M') { + /* + * Fill in absolutely all pencil marks everywhere. (I + * wouldn't use this for actual play, but it's a handy + * starting point when following through a set of + * diagnostics output by the standalone solver.) + */ + ret = dup_game(from); + for (i = 0; i < a; i++) { + if (!ret->grid[i]) + ret->pencil[i] = (1 << (w+1)) - (1 << 1); + } + return ret; + } else if (move[0] == 'D' && + sscanf(move+1, "%d,%d", &x, &y) == 2) { + /* + * Reorder the rows and columns so that digit x is in position + * y. + */ + ret = dup_game(from); + for (i = j = 0; i < w; i++) { + if (i == y) { + ret->sequence[i] = x; + } else { + if (from->sequence[j] == x) + j++; + ret->sequence[i] = from->sequence[j++]; + } + } + /* + * Eliminate any obsoleted dividers. + */ + for (x = 0; x < w; x++) { + int i = ret->sequence[x]; + int j = (x+1 < w ? ret->sequence[x+1] : -1); + if (ret->dividers[i] != j) + ret->dividers[i] = -1; + } + return ret; + } else if (move[0] == 'V' && + sscanf(move+1, "%d,%d", &i, &j) == 2) { + ret = dup_game(from); + if (ret->dividers[i] == j) + ret->dividers[i] = -1; + else + ret->dividers[i] = j; + return ret; + } else + return NULL; /* couldn't parse move string */ +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +#define SIZE(w) ((w) * TILESIZE + 2*BORDER + LEGEND) + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = *y = SIZE(params->w); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + ret[COL_GRID * 3 + 0] = 0.0F; + ret[COL_GRID * 3 + 1] = 0.0F; + ret[COL_GRID * 3 + 2] = 0.0F; + + ret[COL_USER * 3 + 0] = 0.0F; + ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_USER * 3 + 2] = 0.0F; + + ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + + ret[COL_DIAGONAL * 3 + 0] = 0.95F * ret[COL_BACKGROUND * 3 + 0]; + ret[COL_DIAGONAL * 3 + 1] = 0.95F * ret[COL_BACKGROUND * 3 + 1]; + ret[COL_DIAGONAL * 3 + 2] = 0.95F * ret[COL_BACKGROUND * 3 + 2]; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->par.w, a = w*w; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->w = w; + ds->par = state->par; /* structure copy */ + ds->tilesize = 0; + ds->started = FALSE; + ds->tiles = snewn(a, long); + ds->legend = snewn(w, long); + ds->pencil = snewn(a, long); + ds->errors = snewn(a, long); + ds->sequence = snewn(a, digit); + for (i = 0; i < a; i++) + ds->tiles[i] = ds->pencil[i] = -1; + for (i = 0; i < w; i++) + ds->legend[i] = -1; + ds->errtmp = snewn(a, long); + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->tiles); + sfree(ds->pencil); + sfree(ds->errors); + sfree(ds->errtmp); + sfree(ds->sequence); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, long tile, + long pencil, long error) +{ + int w = ds->w /* , a = w*w */; + int tx, ty, tw, th; + int cx, cy, cw, ch; + char str[64]; + + tx = BORDER + LEGEND + x * TILESIZE + 1; + ty = BORDER + LEGEND + y * TILESIZE + 1; + + cx = tx; + cy = ty; + cw = tw = TILESIZE-1; + ch = th = TILESIZE-1; + + if (tile & DF_LEGEND) { + cx += TILESIZE/10; + cy += TILESIZE/10; + cw -= TILESIZE/5; + ch -= TILESIZE/5; + tile |= DF_IMMUTABLE; + } + + clip(dr, cx, cy, cw, ch); + + /* background needs erasing */ + draw_rect(dr, cx, cy, cw, ch, + (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : + (x == y) ? COL_DIAGONAL : COL_BACKGROUND); + + /* dividers */ + if (tile & DF_DIVIDER_TOP) + draw_rect(dr, cx, cy, cw, 1, COL_GRID); + if (tile & DF_DIVIDER_BOT) + draw_rect(dr, cx, cy+ch-1, cw, 1, COL_GRID); + if (tile & DF_DIVIDER_LEFT) + draw_rect(dr, cx, cy, 1, ch, COL_GRID); + if (tile & DF_DIVIDER_RIGHT) + draw_rect(dr, cx+cw-1, cy, 1, ch, COL_GRID); + + /* pencil-mode highlight */ + if (tile & DF_HIGHLIGHT_PENCIL) { + int coords[6]; + coords[0] = cx; + coords[1] = cy; + coords[2] = cx+cw/2; + coords[3] = cy; + coords[4] = cx; + coords[5] = cy+ch/2; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + } + + /* new number needs drawing? */ + if (tile & DF_DIGIT_MASK) { + str[1] = '\0'; + str[0] = TOCHAR(tile & DF_DIGIT_MASK, ds->par.id); + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, + (error & EF_LATIN) ? COL_ERROR : + (tile & DF_IMMUTABLE) ? COL_GRID : COL_USER, str); + + if (error & EF_LEFT_MASK) { + int a = (error >> (EF_LEFT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; + int b = (error >> (EF_LEFT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; + int c = (error >> (EF_LEFT_SHIFT ))&EF_DIGIT_MASK; + char buf[10]; + sprintf(buf, "(%c%c)%c", TOCHAR(a, ds->par.id), + TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id)); + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/6, + FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE, + COL_ERROR, buf); + } + if (error & EF_RIGHT_MASK) { + int a = (error >> (EF_RIGHT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; + int b = (error >> (EF_RIGHT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK; + int c = (error >> (EF_RIGHT_SHIFT ))&EF_DIGIT_MASK; + char buf[10]; + sprintf(buf, "%c(%c%c)", TOCHAR(a, ds->par.id), + TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id)); + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE - TILESIZE/6, + FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE, + COL_ERROR, buf); + } + } else { + int i, j, npencil; + int pl, pr, pt, pb; + float bestsize; + int pw, ph, minph, pbest, fontsize; + + /* Count the pencil marks required. */ + for (i = 1, npencil = 0; i <= w; i++) + if (pencil & (1 << i)) + npencil++; + if (npencil) { + + minph = 2; + + /* + * Determine the bounding rectangle within which we're going + * to put the pencil marks. + */ + /* Start with the whole square */ + pl = tx + GRIDEXTRA; + pr = pl + TILESIZE - GRIDEXTRA; + pt = ty + GRIDEXTRA; + pb = pt + TILESIZE - GRIDEXTRA; + + /* + * We arrange our pencil marks in a grid layout, with + * the number of rows and columns adjusted to allow the + * maximum font size. + * + * So now we work out what the grid size ought to be. + */ + bestsize = 0.0; + pbest = 0; + /* Minimum */ + for (pw = 3; pw < max(npencil,4); pw++) { + float fw, fh, fs; + + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + fw = (pr - pl) / (float)pw; + fh = (pb - pt) / (float)ph; + fs = min(fw, fh); + if (fs > bestsize) { + bestsize = fs; + pbest = pw; + } + } + assert(pbest > 0); + pw = pbest; + ph = (npencil + pw - 1) / pw; + ph = max(ph, minph); + + /* + * Now we've got our grid dimensions, work out the pixel + * size of a grid element, and round it to the nearest + * pixel. (We don't want rounding errors to make the + * grid look uneven at low pixel sizes.) + */ + fontsize = min((pr - pl) / pw, (pb - pt) / ph); + + /* + * Centre the resulting figure in the square. + */ + pl = tx + (TILESIZE - fontsize * pw) / 2; + pt = ty + (TILESIZE - fontsize * ph) / 2; + + /* + * Now actually draw the pencil marks. + */ + for (i = 1, j = 0; i <= w; i++) + if (pencil & (1 << i)) { + int dx = j % pw, dy = j / pw; + + str[1] = '\0'; + str[0] = TOCHAR(i, ds->par.id); + draw_text(dr, pl + fontsize * (2*dx+1) / 2, + pt + fontsize * (2*dy+1) / 2, + FONT_VARIABLE, fontsize, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); + j++; + } + } + } + + unclip(dr); + + draw_update(dr, cx, cy, cw, ch); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->par.w /*, a = w*w */; + int x, y, i, j; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all + * games should start by drawing a big background-colour + * rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND); + + /* + * Big containing rectangle. + */ + draw_rect(dr, COORD(0) - GRIDEXTRA, COORD(0) - GRIDEXTRA, + w*TILESIZE+1+GRIDEXTRA*2, w*TILESIZE+1+GRIDEXTRA*2, + COL_GRID); + + draw_update(dr, 0, 0, SIZE(w), SIZE(w)); + + ds->started = TRUE; + } + + check_errors(state, ds->errtmp); + + /* + * Construct a modified version of state->sequence which takes + * into account an unfinished drag operation. + */ + if (ui->drag) { + x = ui->dragnum; + y = ui->dragpos; + } else { + x = y = -1; + } + for (i = j = 0; i < w; i++) { + if (i == y) { + ds->sequence[i] = x; + } else { + if (state->sequence[j] == x) + j++; + ds->sequence[i] = state->sequence[j++]; + } + } + + /* + * Draw the table legend. + */ + for (x = 0; x < w; x++) { + int sx = ds->sequence[x]; + long tile = (sx+1) | DF_LEGEND; + if (ds->legend[x] != tile) { + ds->legend[x] = tile; + draw_tile(dr, ds, -1, x, tile, 0, 0); + draw_tile(dr, ds, x, -1, tile, 0, 0); + } + } + + for (y = 0; y < w; y++) { + int sy = ds->sequence[y]; + for (x = 0; x < w; x++) { + long tile = 0L, pencil = 0L, error; + int sx = ds->sequence[x]; + + if (state->grid[sy*w+sx]) + tile = state->grid[sy*w+sx]; + else + pencil = (long)state->pencil[sy*w+sx]; + + if (state->immutable[sy*w+sx]) + tile |= DF_IMMUTABLE; + + if ((ui->drag == 5 && ui->dragnum == sy) || + (ui->drag == 6 && ui->dragnum == sx)) { + tile |= DF_HIGHLIGHT; + } else if (ui->hshow) { + int i = abs(x - ui->ohx); + int highlight = 0; + if (ui->odn > 1) { + /* + * When a diagonal multifill selection is shown, + * we show it in its original grid position + * regardless of in-progress row/col drags. Moving + * every square about would be horrible. + */ + if (i >= 0 && i < ui->odn && + x == ui->ohx + i*ui->odx && + y == ui->ohy + i*ui->ody) + highlight = 1; + } else { + /* + * For a single square, we move its highlight + * around with the drag. + */ + highlight = (ui->hx == sx && ui->hy == sy); + } + if (highlight) + tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT); + } + + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) + tile |= DF_HIGHLIGHT; /* completion flash */ + + if (y <= 0 || state->dividers[ds->sequence[y-1]] == sy) + tile |= DF_DIVIDER_TOP; + if (y+1 >= w || state->dividers[sy] == ds->sequence[y+1]) + tile |= DF_DIVIDER_BOT; + if (x <= 0 || state->dividers[ds->sequence[x-1]] == sx) + tile |= DF_DIVIDER_LEFT; + if (x+1 >= w || state->dividers[sx] == ds->sequence[x+1]) + tile |= DF_DIVIDER_RIGHT; + + error = ds->errtmp[sy*w+sx]; + + if (ds->tiles[y*w+x] != tile || + ds->pencil[y*w+x] != pencil || + ds->errors[y*w+x] != error) { + ds->tiles[y*w+x] = tile; + ds->pencil[y*w+x] = pencil; + ds->errors[y*w+x] = error; + draw_tile(dr, ds, x, y, tile, pencil, error); + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + if (state->completed) + return FALSE; + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* + * We use 9mm squares by default, like Solo. + */ + game_compute_size(params, 900, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w = state->par.w; + int ink = print_mono_colour(dr, 0); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, 3 * TILESIZE / 40); + draw_rect_outline(dr, BORDER + LEGEND, BORDER + LEGEND, + w*TILESIZE, w*TILESIZE, ink); + + /* + * Legend on table. + */ + for (x = 0; x < w; x++) { + char str[2]; + str[1] = '\0'; + str[0] = TOCHAR(x+1, state->par.id); + draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2, + BORDER + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + draw_text(dr, BORDER + TILESIZE/2, + BORDER+LEGEND + x*TILESIZE + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } + + /* + * Main grid. + */ + for (x = 1; x < w; x++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND, + BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND+w*TILESIZE, ink); + } + for (y = 1; y < w; y++) { + print_line_width(dr, TILESIZE / 40); + draw_line(dr, BORDER+LEGEND, BORDER+LEGEND+y*TILESIZE, + BORDER+LEGEND+w*TILESIZE, BORDER+LEGEND+y*TILESIZE, ink); + } + + /* + * Numbers. + */ + for (y = 0; y < w; y++) + for (x = 0; x < w; x++) + if (state->grid[y*w+x]) { + char str[2]; + str[1] = '\0'; + str[0] = TOCHAR(state->grid[y*w+x], state->par.id); + draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2, + BORDER+LEGEND + y*TILESIZE + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); + } +} + +#ifdef COMBINED +#define thegame group +#endif + +const struct game thegame = { + "Group", NULL, NULL, + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + digit *grid; + int grade = FALSE; + int ret, diff, really_show_working = FALSE; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "-v")) { + really_show_working = TRUE; + } else if (!strcmp(p, "-g")) { + grade = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-g | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + grid = snewn(p->w * p->w, digit); + + /* + * When solving a Normal puzzle, we don't want to bother the + * user with Hard-level deductions. For this reason, we grade + * the puzzle internally before doing anything else. + */ + ret = -1; /* placate optimiser */ + solver_show_working = FALSE; + for (diff = 0; diff < DIFFCOUNT; diff++) { + memcpy(grid, s->grid, p->w * p->w); + ret = solver(&s->par, grid, diff); + if (ret <= diff) + break; + } + + if (diff == DIFFCOUNT) { + if (grade) + printf("Difficulty rating: ambiguous\n"); + else + printf("Unable to find a unique solution\n"); + } else { + if (grade) { + if (ret == diff_impossible) + printf("Difficulty rating: impossible (no solution exists)\n"); + else + printf("Difficulty rating: %s\n", group_diffnames[ret]); + } else { + solver_show_working = really_show_working; + memcpy(grid, s->grid, p->w * p->w); + ret = solver(&s->par, grid, diff); + if (ret != diff) + printf("Puzzle is inconsistent\n"); + else { + memcpy(s->grid, grid, p->w * p->w); + fputs(game_text_format(s), stdout); + } + } + } + + return 0; +} + +#endif + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/unfinished/group.gap b/apps/plugins/puzzles/unfinished/group.gap new file mode 100644 index 0000000000..280adf4664 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/group.gap @@ -0,0 +1,97 @@ +# run this file with +# gap -b -q < /dev/null group.gap | perl -pe 's/\\\n//s' | indent -kr + +Print("/* ----- data generated by group.gap begins ----- */\n\n"); +Print("struct group {\n unsigned long autosize;\n"); +Print(" int order, ngens;\n const char *gens;\n};\n"); +Print("struct groups {\n int ngroups;\n"); +Print(" const struct group *groups;\n};\n\n"); +Print("static const struct group groupdata[] = {\n"); +offsets := [0]; +offset := 0; +for n in [2..26] do + Print(" /* order ", n, " */\n"); + for G in AllSmallGroups(n) do + + # Construct a representation of the group G as a subgroup + # of a permutation group, and find its generators in that + # group. + + # GAP has the 'IsomorphismPermGroup' function, but I don't want + # to use it because it doesn't guarantee that the permutation + # representation of the group forms a Cayley table. For example, + # C_4 could be represented as a subgroup of S_4 in many ways, + # and not all of them work: the group generated by (12) and (34) + # is clearly isomorphic to C_4 but its four elements do not form + # a Cayley table. The group generated by (12)(34) and (13)(24) + # is OK, though. + # + # Hence I construct the permutation representation _as_ the + # Cayley table, and then pick generators of that. This + # guarantees that when we rebuild the full group by BFS in + # group.c, we will end up with the right thing. + + ge := Elements(G); + gi := []; + for g in ge do + gr := []; + for h in ge do + k := g*h; + for i in [1..n] do + if k = ge[i] then + Add(gr, i); + fi; + od; + od; + Add(gi, PermList(gr)); + od; + + # GAP has the 'GeneratorsOfGroup' function, but we don't want to + # use it because it's bad at picking generators - it thinks the + # generators of C_4 are [ (1,2)(3,4), (1,3,2,4) ] and that those + # of C_6 are [ (1,2,3)(4,5,6), (1,4)(2,5)(3,6) ] ! + + gl := ShallowCopy(Elements(gi)); + Sort(gl, function(v,w) return Order(v) > Order(w); end); + + gens := []; + for x in gl do + if gens = [] or not (x in gp) then + Add(gens, x); + gp := GroupWithGenerators(gens); + fi; + od; + + # Construct the C representation of the group generators. + s := []; + for x in gens do + if Size(s) > 0 then + Add(s, '"'); + Add(s, ' '); + Add(s, '"'); + fi; + sep := "\\0"; + for i in ListPerm(x) do + chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + Add(s, chars[i]); + od; + od; + s := JoinStringsWithSeparator([" {", String(Size(AutomorphismGroup(G))), + "L, ", String(Size(G)), + ", ", String(Size(gens)), + ", \"", s, "\"},\n"],""); + Print(s); + offset := offset + 1; + od; + Add(offsets, offset); +od; +Print("};\n\nstatic const struct groups groups[] = {\n"); +Print(" {0, NULL}, /* trivial case: 0 */\n"); +Print(" {0, NULL}, /* trivial case: 1 */\n"); +n := 2; +for i in [1..Size(offsets)-1] do + Print(" {", offsets[i+1] - offsets[i], ", groupdata+", + offsets[i], "}, /* ", i+1, " */\n"); +od; +Print("};\n\n/* ----- data generated by group.gap ends ----- */\n"); +quit; diff --git a/apps/plugins/puzzles/unfinished/numgame.c b/apps/plugins/puzzles/unfinished/numgame.c new file mode 100644 index 0000000000..aed5c17347 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/numgame.c @@ -0,0 +1,1290 @@ +/* + * This program implements a breadth-first search which + * exhaustively solves the Countdown numbers game, and related + * games with slightly different rule sets such as `Flippo'. + * + * Currently it is simply a standalone command-line utility to + * which you provide a set of numbers and it tells you everything + * it can make together with how many different ways it can be + * made. I would like ultimately to turn it into the generator for + * a Puzzles puzzle, but I haven't even started on writing a + * Puzzles user interface yet. + */ + +/* + * TODO: + * + * - start thinking about difficulty ratings + * + anything involving associative operations will be flagged + * as many-paths because of the associative options (e.g. + * 2*3*4 can be (2*3)*4 or 2*(3*4), or indeed (2*4)*3). This + * is probably a _good_ thing, since those are unusually + * easy. + * + tree-structured calculations ((a*b)/(c+d)) have multiple + * paths because the independent branches of the tree can be + * evaluated in either order, whereas straight-line + * calculations with no branches will be considered easier. + * Can we do anything about this? It's certainly not clear to + * me that tree-structure calculations are _easier_, although + * I'm also not convinced they're harder. + * + I think for a realistic difficulty assessment we must also + * consider the `obviousness' of the arithmetic operations in + * some heuristic sense, and also (in Countdown) how many + * numbers ended up being used. + * - actually try some generations + * - at this point we're probably ready to start on the Puzzles + * integration. + */ + +#include +#include +#include +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +/* + * To search for numbers we can make, we employ a breadth-first + * search across the space of sets of input numbers. That is, for + * example, we start with the set (3,6,25,50,75,100); we apply + * moves which involve combining two numbers (e.g. adding the 50 + * and the 75 takes us to the set (3,6,25,100,125); and then we see + * if we ever end up with a set containing (say) 952. + * + * If the rules are changed so that all the numbers must be used, + * this is easy to adjust to: we simply see if we end up with a set + * containing _only_ (say) 952. + * + * Obviously, we can vary the rules about permitted arithmetic + * operations simply by altering the set of valid moves in the bfs. + * However, there's one common rule in this sort of puzzle which + * takes a little more thought, and that's _concatenation_. For + * example, if you are given (say) four 4s and required to make 10, + * you are permitted to combine two of the 4s into a 44 to begin + * with, making (44-4)/4 = 10. However, you are generally not + * allowed to concatenate two numbers that _weren't_ both in the + * original input set (you couldn't multiply two 4s to get 16 and + * then concatenate a 4 on to it to make 164), so concatenation is + * not an operation which is valid in all situations. + * + * We could enforce this restriction by storing a flag alongside + * each number indicating whether or not it's an original number; + * the rules being that concatenation of two numbers is only valid + * if they both have the original flag, and that its output _also_ + * has the original flag (so that you can concatenate three 4s into + * a 444), but that applying any other arithmetic operation clears + * the original flag on the output. However, we can get marginally + * simpler than that by observing that since concatenation has to + * happen to a number before any other operation, we can simply + * place all the concatenations at the start of the search. In + * other words, we have a global flag on an entire number _set_ + * which indicates whether we are still permitted to perform + * concatenations; if so, we can concatenate any of the numbers in + * that set. Performing any other operation clears the flag. + */ + +#define SETFLAG_CONCAT 1 /* we can do concatenation */ + +struct sets; + +struct ancestor { + struct set *prev; /* index of ancestor set in set list */ + unsigned char pa, pb, po, pr; /* operation that got here from prev */ +}; + +struct set { + int *numbers; /* rationals stored as n,d pairs */ + short nnumbers; /* # of rationals, so half # of ints */ + short flags; /* SETFLAG_CONCAT only, at present */ + int npaths; /* number of ways to reach this set */ + struct ancestor a; /* primary ancestor */ + struct ancestor *as; /* further ancestors, if we care */ + int nas, assize; +}; + +struct output { + int number; + struct set *set; + int index; /* which number in the set is it? */ + int npaths; /* number of ways to reach this */ +}; + +#define SETLISTLEN 1024 +#define NUMBERLISTLEN 32768 +#define OUTPUTLISTLEN 1024 +struct operation; +struct sets { + struct set **setlists; + int nsets, nsetlists, setlistsize; + tree234 *settree; + int **numberlists; + int nnumbers, nnumberlists, numberlistsize; + struct output **outputlists; + int noutputs, noutputlists, outputlistsize; + tree234 *outputtree; + const struct operation *const *ops; +}; + +#define OPFLAG_NEEDS_CONCAT 1 +#define OPFLAG_KEEPS_CONCAT 2 +#define OPFLAG_UNARY 4 +#define OPFLAG_UNARYPREFIX 8 +#define OPFLAG_FN 16 + +struct operation { + /* + * Most operations should be shown in the output working, but + * concatenation should not; we just take the result of the + * concatenation and assume that it's obvious how it was + * derived. + */ + int display; + + /* + * Text display of the operator, in expressions and for + * debugging respectively. + */ + char *text, *dbgtext; + + /* + * Flags dictating when the operator can be applied. + */ + int flags; + + /* + * Priority of the operator (for avoiding unnecessary + * parentheses when formatting it into a string). + */ + int priority; + + /* + * Associativity of the operator. Bit 0 means we need parens + * when the left operand of one of these operators is another + * instance of it, e.g. (2^3)^4. Bit 1 means we need parens + * when the right operand is another instance of the same + * operator, e.g. 2-(3-4). Thus: + * + * - this field is 0 for a fully associative operator, since + * we never need parens. + * - it's 1 for a right-associative operator. + * - it's 2 for a left-associative operator. + * - it's 3 for a _non_-associative operator (which always + * uses parens just to be sure). + */ + int assoc; + + /* + * Whether the operator is commutative. Saves time in the + * search if we don't have to try it both ways round. + */ + int commutes; + + /* + * Function which implements the operator. Returns TRUE on + * success, FALSE on failure. Takes two rationals and writes + * out a third. + */ + int (*perform)(int *a, int *b, int *output); +}; + +struct rules { + const struct operation *const *ops; + int use_all; +}; + +#define MUL(r, a, b) do { \ + (r) = (a) * (b); \ + if ((b) && (a) && (r) / (b) != (a)) return FALSE; \ +} while (0) + +#define ADD(r, a, b) do { \ + (r) = (a) + (b); \ + if ((a) > 0 && (b) > 0 && (r) < 0) return FALSE; \ + if ((a) < 0 && (b) < 0 && (r) > 0) return FALSE; \ +} while (0) + +#define OUT(output, n, d) do { \ + int g = gcd((n),(d)); \ + if (g < 0) g = -g; \ + if ((d) < 0) g = -g; \ + if (g == -1 && (n) < -INT_MAX) return FALSE; \ + if (g == -1 && (d) < -INT_MAX) return FALSE; \ + (output)[0] = (n)/g; \ + (output)[1] = (d)/g; \ + assert((output)[1] > 0); \ +} while (0) + +static int gcd(int x, int y) +{ + while (x != 0 && y != 0) { + int t = x; + x = y; + y = t % y; + } + + return abs(x + y); /* i.e. whichever one isn't zero */ +} + +static int perform_add(int *a, int *b, int *output) +{ + int at, bt, tn, bn; + /* + * a0/a1 + b0/b1 = (a0*b1 + b0*a1) / (a1*b1) + */ + MUL(at, a[0], b[1]); + MUL(bt, b[0], a[1]); + ADD(tn, at, bt); + MUL(bn, a[1], b[1]); + OUT(output, tn, bn); + return TRUE; +} + +static int perform_sub(int *a, int *b, int *output) +{ + int at, bt, tn, bn; + /* + * a0/a1 - b0/b1 = (a0*b1 - b0*a1) / (a1*b1) + */ + MUL(at, a[0], b[1]); + MUL(bt, b[0], a[1]); + ADD(tn, at, -bt); + MUL(bn, a[1], b[1]); + OUT(output, tn, bn); + return TRUE; +} + +static int perform_mul(int *a, int *b, int *output) +{ + int tn, bn; + /* + * a0/a1 * b0/b1 = (a0*b0) / (a1*b1) + */ + MUL(tn, a[0], b[0]); + MUL(bn, a[1], b[1]); + OUT(output, tn, bn); + return TRUE; +} + +static int perform_div(int *a, int *b, int *output) +{ + int tn, bn; + + /* + * Division by zero is outlawed. + */ + if (b[0] == 0) + return FALSE; + + /* + * a0/a1 / b0/b1 = (a0*b1) / (a1*b0) + */ + MUL(tn, a[0], b[1]); + MUL(bn, a[1], b[0]); + OUT(output, tn, bn); + return TRUE; +} + +static int perform_exact_div(int *a, int *b, int *output) +{ + int tn, bn; + + /* + * Division by zero is outlawed. + */ + if (b[0] == 0) + return FALSE; + + /* + * a0/a1 / b0/b1 = (a0*b1) / (a1*b0) + */ + MUL(tn, a[0], b[1]); + MUL(bn, a[1], b[0]); + OUT(output, tn, bn); + + /* + * Exact division means we require the result to be an integer. + */ + return (output[1] == 1); +} + +static int max_p10(int n, int *p10_r) +{ + /* + * Find the smallest power of ten strictly greater than n. + * + * Special case: we must return at least 10, even if n is + * zero. (This is because this function is used for finding + * the power of ten by which to multiply a number being + * concatenated to the front of n, and concatenating 1 to 0 + * should yield 10 and not 1.) + */ + int p10 = 10; + while (p10 <= (INT_MAX/10) && p10 <= n) + p10 *= 10; + if (p10 > INT_MAX/10) + return FALSE; /* integer overflow */ + *p10_r = p10; + return TRUE; +} + +static int perform_concat(int *a, int *b, int *output) +{ + int t1, t2, p10; + + /* + * We can't concatenate anything which isn't a non-negative + * integer. + */ + if (a[1] != 1 || b[1] != 1 || a[0] < 0 || b[0] < 0) + return FALSE; + + /* + * For concatenation, we can safely assume leading zeroes + * aren't an issue. It isn't clear whether they `should' be + * allowed, but it turns out not to matter: concatenating a + * leading zero on to a number in order to harmlessly get rid + * of the zero is never necessary because unwanted zeroes can + * be disposed of by adding them to something instead. So we + * disallow them always. + * + * The only other possibility is that you might want to + * concatenate a leading zero on to something and then + * concatenate another non-zero digit on to _that_ (to make, + * for example, 106); but that's also unnecessary, because you + * can make 106 just as easily by concatenating the 0 on to the + * _end_ of the 1 first. + */ + if (a[0] == 0) + return FALSE; + + if (!max_p10(b[0], &p10)) return FALSE; + + MUL(t1, p10, a[0]); + ADD(t2, t1, b[0]); + OUT(output, t2, 1); + return TRUE; +} + +#define IPOW(ret, x, y) do { \ + int ipow_limit = (y); \ + if ((x) == 1 || (x) == 0) ipow_limit = 1; \ + else if ((x) == -1) ipow_limit &= 1; \ + (ret) = 1; \ + while (ipow_limit-- > 0) { \ + int tmp; \ + MUL(tmp, ret, x); \ + ret = tmp; \ + } \ +} while (0) + +static int perform_exp(int *a, int *b, int *output) +{ + int an, ad, xn, xd; + + /* + * Exponentiation is permitted if the result is rational. This + * means that: + * + * - first we see whether we can take the (denominator-of-b)th + * root of a and get a rational; if not, we give up. + * + * - then we do take that root of a + * + * - then we multiply by itself (numerator-of-b) times. + */ + if (b[1] > 1) { + an = (int)(0.5 + pow(a[0], 1.0/b[1])); + ad = (int)(0.5 + pow(a[1], 1.0/b[1])); + IPOW(xn, an, b[1]); + IPOW(xd, ad, b[1]); + if (xn != a[0] || xd != a[1]) + return FALSE; + } else { + an = a[0]; + ad = a[1]; + } + if (b[0] >= 0) { + IPOW(xn, an, b[0]); + IPOW(xd, ad, b[0]); + } else { + IPOW(xd, an, -b[0]); + IPOW(xn, ad, -b[0]); + } + if (xd == 0) + return FALSE; + + OUT(output, xn, xd); + return TRUE; +} + +static int perform_factorial(int *a, int *b, int *output) +{ + int ret, t, i; + + /* + * Factorials of non-negative integers are permitted. + */ + if (a[1] != 1 || a[0] < 0) + return FALSE; + + /* + * However, a special case: we don't take a factorial of + * anything which would thereby remain the same. + */ + if (a[0] == 1 || a[0] == 2) + return FALSE; + + ret = 1; + for (i = 1; i <= a[0]; i++) { + MUL(t, ret, i); + ret = t; + } + + OUT(output, ret, 1); + return TRUE; +} + +static int perform_decimal(int *a, int *b, int *output) +{ + int p10; + + /* + * Add a decimal digit to the front of a number; + * fail if it's not an integer. + * So, 1 --> 0.1, 15 --> 0.15, + * or, rather, 1 --> 1/10, 15 --> 15/100, + * x --> x / (smallest power of 10 > than x) + * + */ + if (a[1] != 1) return FALSE; + + if (!max_p10(a[0], &p10)) return FALSE; + + OUT(output, a[0], p10); + return TRUE; +} + +static int perform_recur(int *a, int *b, int *output) +{ + int p10, tn, bn; + + /* + * This converts a number like .4 to .44444..., or .45 to .45454... + * The input number must be -1 < a < 1. + * + * Calculate the smallest power of 10 that divides the denominator exactly, + * returning if no such power of 10 exists. Then multiply the numerator + * up accordingly, and the new denominator becomes that power of 10 - 1. + */ + if (abs(a[0]) >= abs(a[1])) return FALSE; /* -1 < a < 1 */ + + p10 = 10; + while (p10 <= (INT_MAX/10)) { + if ((a[1] <= p10) && (p10 % a[1]) == 0) goto found; + p10 *= 10; + } + return FALSE; +found: + tn = a[0] * (p10 / a[1]); + bn = p10 - 1; + + OUT(output, tn, bn); + return TRUE; +} + +static int perform_root(int *a, int *b, int *output) +{ + /* + * A root B is: 1 iff a == 0 + * B ^ (1/A) otherwise + */ + int ainv[2], res; + + if (a[0] == 0) { + OUT(output, 1, 1); + return TRUE; + } + + OUT(ainv, a[1], a[0]); + res = perform_exp(b, ainv, output); + return res; +} + +static int perform_perc(int *a, int *b, int *output) +{ + if (a[0] == 0) return FALSE; /* 0% = 0, uninteresting. */ + if (a[1] > (INT_MAX/100)) return FALSE; + + OUT(output, a[0], a[1]*100); + return TRUE; +} + +static int perform_gamma(int *a, int *b, int *output) +{ + int asub1[2]; + + /* + * gamma(a) = (a-1)! + * + * special case not caught by perform_fact: gamma(1) is 1 so + * don't bother. + */ + if (a[0] == 1 && a[1] == 1) return FALSE; + + OUT(asub1, a[0]-a[1], a[1]); + return perform_factorial(asub1, b, output); +} + +static int perform_sqrt(int *a, int *b, int *output) +{ + int half[2] = { 1, 2 }; + + /* + * sqrt(0) == 0, sqrt(1) == 1: don't perform unary noops. + */ + if (a[0] == 0 || (a[0] == 1 && a[1] == 1)) return FALSE; + + return perform_exp(a, half, output); +} + +const static struct operation op_add = { + TRUE, "+", "+", 0, 10, 0, TRUE, perform_add +}; +const static struct operation op_sub = { + TRUE, "-", "-", 0, 10, 2, FALSE, perform_sub +}; +const static struct operation op_mul = { + TRUE, "*", "*", 0, 20, 0, TRUE, perform_mul +}; +const static struct operation op_div = { + TRUE, "/", "/", 0, 20, 2, FALSE, perform_div +}; +const static struct operation op_xdiv = { + TRUE, "/", "/", 0, 20, 2, FALSE, perform_exact_div +}; +const static struct operation op_concat = { + FALSE, "", "concat", OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT, + 1000, 0, FALSE, perform_concat +}; +const static struct operation op_exp = { + TRUE, "^", "^", 0, 30, 1, FALSE, perform_exp +}; +const static struct operation op_factorial = { + TRUE, "!", "!", OPFLAG_UNARY, 40, 0, FALSE, perform_factorial +}; +const static struct operation op_decimal = { + TRUE, ".", ".", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT, 50, 0, FALSE, perform_decimal +}; +const static struct operation op_recur = { + TRUE, "...", "recur", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 2, FALSE, perform_recur +}; +const static struct operation op_root = { + TRUE, "v~", "root", 0, 30, 1, FALSE, perform_root +}; +const static struct operation op_perc = { + TRUE, "%", "%", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 1, FALSE, perform_perc +}; +const static struct operation op_gamma = { + TRUE, "gamma", "gamma", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_FN, 1, 3, FALSE, perform_gamma +}; +const static struct operation op_sqrt = { + TRUE, "v~", "sqrt", OPFLAG_UNARY | OPFLAG_UNARYPREFIX, 30, 1, FALSE, perform_sqrt +}; + +/* + * In Countdown, divisions resulting in fractions are disallowed. + * http://www.askoxford.com/wordgames/countdown/rules/ + */ +const static struct operation *const ops_countdown[] = { + &op_add, &op_mul, &op_sub, &op_xdiv, NULL +}; +const static struct rules rules_countdown = { + ops_countdown, FALSE +}; + +/* + * A slightly different rule set which handles the reasonably well + * known puzzle of making 24 using two 3s and two 8s. For this we + * need rational rather than integer division. + */ +const static struct operation *const ops_3388[] = { + &op_add, &op_mul, &op_sub, &op_div, NULL +}; +const static struct rules rules_3388 = { + ops_3388, TRUE +}; + +/* + * A still more permissive rule set usable for the four-4s problem + * and similar things. Permits concatenation. + */ +const static struct operation *const ops_four4s[] = { + &op_add, &op_mul, &op_sub, &op_div, &op_concat, NULL +}; +const static struct rules rules_four4s = { + ops_four4s, TRUE +}; + +/* + * The most permissive ruleset I can think of. Permits + * exponentiation, and also silly unary operators like factorials. + */ +const static struct operation *const ops_anythinggoes[] = { + &op_add, &op_mul, &op_sub, &op_div, &op_concat, &op_exp, &op_factorial, + &op_decimal, &op_recur, &op_root, &op_perc, &op_gamma, &op_sqrt, NULL +}; +const static struct rules rules_anythinggoes = { + ops_anythinggoes, TRUE +}; + +#define ratcmp(a,op,b) ( (long long)(a)[0] * (b)[1] op \ + (long long)(b)[0] * (a)[1] ) + +static int addtoset(struct set *set, int newnumber[2]) +{ + int i, j; + + /* Find where we want to insert the new number */ + for (i = 0; i < set->nnumbers && + ratcmp(set->numbers+2*i, <, newnumber); i++); + + /* Move everything else up */ + for (j = set->nnumbers; j > i; j--) { + set->numbers[2*j] = set->numbers[2*j-2]; + set->numbers[2*j+1] = set->numbers[2*j-1]; + } + + /* Insert the new number */ + set->numbers[2*i] = newnumber[0]; + set->numbers[2*i+1] = newnumber[1]; + + set->nnumbers++; + + return i; +} + +#define ensure(array, size, newlen, type) do { \ + if ((newlen) > (size)) { \ + (size) = (newlen) + 512; \ + (array) = sresize((array), (size), type); \ + } \ +} while (0) + +static int setcmp(void *av, void *bv) +{ + struct set *a = (struct set *)av; + struct set *b = (struct set *)bv; + int i; + + if (a->nnumbers < b->nnumbers) + return -1; + else if (a->nnumbers > b->nnumbers) + return +1; + + if (a->flags < b->flags) + return -1; + else if (a->flags > b->flags) + return +1; + + for (i = 0; i < a->nnumbers; i++) { + if (ratcmp(a->numbers+2*i, <, b->numbers+2*i)) + return -1; + else if (ratcmp(a->numbers+2*i, >, b->numbers+2*i)) + return +1; + } + + return 0; +} + +static int outputcmp(void *av, void *bv) +{ + struct output *a = (struct output *)av; + struct output *b = (struct output *)bv; + + if (a->number < b->number) + return -1; + else if (a->number > b->number) + return +1; + + return 0; +} + +static int outputfindcmp(void *av, void *bv) +{ + int *a = (int *)av; + struct output *b = (struct output *)bv; + + if (*a < b->number) + return -1; + else if (*a > b->number) + return +1; + + return 0; +} + +static void addset(struct sets *s, struct set *set, int multiple, + struct set *prev, int pa, int po, int pb, int pr) +{ + struct set *s2; + int npaths = (prev ? prev->npaths : 1); + + assert(set == s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN); + s2 = add234(s->settree, set); + if (s2 == set) { + /* + * New set added to the tree. + */ + set->a.prev = prev; + set->a.pa = pa; + set->a.po = po; + set->a.pb = pb; + set->a.pr = pr; + set->npaths = npaths; + s->nsets++; + s->nnumbers += 2 * set->nnumbers; + set->as = NULL; + set->nas = set->assize = 0; + } else { + /* + * Rediscovered an existing set. Update its npaths. + */ + s2->npaths += npaths; + /* + * And optionally enter it as an additional ancestor. + */ + if (multiple) { + if (s2->nas >= s2->assize) { + s2->assize = s2->nas * 3 / 2 + 4; + s2->as = sresize(s2->as, s2->assize, struct ancestor); + } + s2->as[s2->nas].prev = prev; + s2->as[s2->nas].pa = pa; + s2->as[s2->nas].po = po; + s2->as[s2->nas].pb = pb; + s2->as[s2->nas].pr = pr; + s2->nas++; + } + } +} + +static struct set *newset(struct sets *s, int nnumbers, int flags) +{ + struct set *sn; + + ensure(s->setlists, s->setlistsize, s->nsets/SETLISTLEN+1, struct set *); + while (s->nsetlists <= s->nsets / SETLISTLEN) + s->setlists[s->nsetlists++] = snewn(SETLISTLEN, struct set); + sn = s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN; + + if (s->nnumbers + nnumbers * 2 > s->nnumberlists * NUMBERLISTLEN) + s->nnumbers = s->nnumberlists * NUMBERLISTLEN; + ensure(s->numberlists, s->numberlistsize, + s->nnumbers/NUMBERLISTLEN+1, int *); + while (s->nnumberlists <= s->nnumbers / NUMBERLISTLEN) + s->numberlists[s->nnumberlists++] = snewn(NUMBERLISTLEN, int); + sn->numbers = s->numberlists[s->nnumbers / NUMBERLISTLEN] + + s->nnumbers % NUMBERLISTLEN; + + /* + * Start the set off empty. + */ + sn->nnumbers = 0; + + sn->flags = flags; + + return sn; +} + +static int addoutput(struct sets *s, struct set *ss, int index, int *n) +{ + struct output *o, *o2; + + /* + * Target numbers are always integers. + */ + if (ss->numbers[2*index+1] != 1) + return FALSE; + + ensure(s->outputlists, s->outputlistsize, s->noutputs/OUTPUTLISTLEN+1, + struct output *); + while (s->noutputlists <= s->noutputs / OUTPUTLISTLEN) + s->outputlists[s->noutputlists++] = snewn(OUTPUTLISTLEN, + struct output); + o = s->outputlists[s->noutputs / OUTPUTLISTLEN] + + s->noutputs % OUTPUTLISTLEN; + + o->number = ss->numbers[2*index]; + o->set = ss; + o->index = index; + o->npaths = ss->npaths; + o2 = add234(s->outputtree, o); + if (o2 != o) { + o2->npaths += o->npaths; + } else { + s->noutputs++; + } + *n = o->number; + return TRUE; +} + +static struct sets *do_search(int ninputs, int *inputs, + const struct rules *rules, int *target, + int debug, int multiple) +{ + struct sets *s; + struct set *sn; + int qpos, i; + const struct operation *const *ops = rules->ops; + + s = snew(struct sets); + s->setlists = NULL; + s->nsets = s->nsetlists = s->setlistsize = 0; + s->numberlists = NULL; + s->nnumbers = s->nnumberlists = s->numberlistsize = 0; + s->outputlists = NULL; + s->noutputs = s->noutputlists = s->outputlistsize = 0; + s->settree = newtree234(setcmp); + s->outputtree = newtree234(outputcmp); + s->ops = ops; + + /* + * Start with the input set. + */ + sn = newset(s, ninputs, SETFLAG_CONCAT); + for (i = 0; i < ninputs; i++) { + int newnumber[2]; + newnumber[0] = inputs[i]; + newnumber[1] = 1; + addtoset(sn, newnumber); + } + addset(s, sn, multiple, NULL, 0, 0, 0, 0); + + /* + * Now perform the breadth-first search: keep looping over sets + * until we run out of steam. + */ + qpos = 0; + while (qpos < s->nsets) { + struct set *ss = s->setlists[qpos / SETLISTLEN] + qpos % SETLISTLEN; + struct set *sn; + int i, j, k, m; + + if (debug) { + int i; + printf("processing set:"); + for (i = 0; i < ss->nnumbers; i++) { + printf(" %d", ss->numbers[2*i]); + if (ss->numbers[2*i+1] != 1) + printf("/%d", ss->numbers[2*i+1]); + } + printf("\n"); + } + + /* + * Record all the valid output numbers in this state. We + * can always do this if there's only one number in the + * state; otherwise, we can only do it if we aren't + * required to use all the numbers in coming to our answer. + */ + if (ss->nnumbers == 1 || !rules->use_all) { + for (i = 0; i < ss->nnumbers; i++) { + int n; + + if (addoutput(s, ss, i, &n) && target && n == *target) + return s; + } + } + + /* + * Try every possible operation from this state. + */ + for (k = 0; ops[k] && ops[k]->perform; k++) { + if ((ops[k]->flags & OPFLAG_NEEDS_CONCAT) && + !(ss->flags & SETFLAG_CONCAT)) + continue; /* can't use this operation here */ + for (i = 0; i < ss->nnumbers; i++) { + int jlimit = (ops[k]->flags & OPFLAG_UNARY ? 1 : ss->nnumbers); + for (j = 0; j < jlimit; j++) { + int n[2], newnn = ss->nnumbers; + int pa, po, pb, pr; + + if (!(ops[k]->flags & OPFLAG_UNARY)) { + if (i == j) + continue; /* can't combine a number with itself */ + if (i > j && ops[k]->commutes) + continue; /* no need to do this both ways round */ + newnn--; + } + if (!ops[k]->perform(ss->numbers+2*i, ss->numbers+2*j, n)) + continue; /* operation failed */ + + sn = newset(s, newnn, ss->flags); + + if (!(ops[k]->flags & OPFLAG_KEEPS_CONCAT)) + sn->flags &= ~SETFLAG_CONCAT; + + for (m = 0; m < ss->nnumbers; m++) { + if (m == i || (!(ops[k]->flags & OPFLAG_UNARY) && + m == j)) + continue; + sn->numbers[2*sn->nnumbers] = ss->numbers[2*m]; + sn->numbers[2*sn->nnumbers + 1] = ss->numbers[2*m + 1]; + sn->nnumbers++; + } + pa = i; + if (ops[k]->flags & OPFLAG_UNARY) + pb = sn->nnumbers+10; + else + pb = j; + po = k; + pr = addtoset(sn, n); + addset(s, sn, multiple, ss, pa, po, pb, pr); + if (debug) { + int i; + if (ops[k]->flags & OPFLAG_UNARYPREFIX) + printf(" %s %d ->", ops[po]->dbgtext, pa); + else if (ops[k]->flags & OPFLAG_UNARY) + printf(" %d %s ->", pa, ops[po]->dbgtext); + else + printf(" %d %s %d ->", pa, ops[po]->dbgtext, pb); + for (i = 0; i < sn->nnumbers; i++) { + printf(" %d", sn->numbers[2*i]); + if (sn->numbers[2*i+1] != 1) + printf("/%d", sn->numbers[2*i+1]); + } + printf("\n"); + } + } + } + } + + qpos++; + } + + return s; +} + +static void free_sets(struct sets *s) +{ + int i; + + freetree234(s->settree); + freetree234(s->outputtree); + for (i = 0; i < s->nsetlists; i++) + sfree(s->setlists[i]); + sfree(s->setlists); + for (i = 0; i < s->nnumberlists; i++) + sfree(s->numberlists[i]); + sfree(s->numberlists); + for (i = 0; i < s->noutputlists; i++) + sfree(s->outputlists[i]); + sfree(s->outputlists); + sfree(s); +} + +/* + * Print a text formula for producing a given output. + */ +void print_recurse(struct sets *s, struct set *ss, int pathindex, int index, + int priority, int assoc, int child); +void print_recurse_inner(struct sets *s, struct set *ss, + struct ancestor *a, int pathindex, int index, + int priority, int assoc, int child) +{ + if (a->prev && index != a->pr) { + int pi; + + /* + * This number was passed straight down from this set's + * predecessor. Find its index in the previous set and + * recurse to there. + */ + pi = index; + assert(pi != a->pr); + if (pi > a->pr) + pi--; + if (pi >= min(a->pa, a->pb)) { + pi++; + if (pi >= max(a->pa, a->pb)) + pi++; + } + print_recurse(s, a->prev, pathindex, pi, priority, assoc, child); + } else if (a->prev && index == a->pr && + s->ops[a->po]->display) { + /* + * This number was created by a displayed operator in the + * transition from this set to its predecessor. Hence we + * write an open paren, then recurse into the first + * operand, then write the operator, then the second + * operand, and finally close the paren. + */ + char *op; + int parens, thispri, thisassoc; + + /* + * Determine whether we need parentheses. + */ + thispri = s->ops[a->po]->priority; + thisassoc = s->ops[a->po]->assoc; + parens = (thispri < priority || + (thispri == priority && (assoc & child))); + + if (parens) + putchar('('); + + if (s->ops[a->po]->flags & OPFLAG_UNARYPREFIX) + for (op = s->ops[a->po]->text; *op; op++) + putchar(*op); + + if (s->ops[a->po]->flags & OPFLAG_FN) + putchar('('); + + print_recurse(s, a->prev, pathindex, a->pa, thispri, thisassoc, 1); + + if (s->ops[a->po]->flags & OPFLAG_FN) + putchar(')'); + + if (!(s->ops[a->po]->flags & OPFLAG_UNARYPREFIX)) + for (op = s->ops[a->po]->text; *op; op++) + putchar(*op); + + if (!(s->ops[a->po]->flags & OPFLAG_UNARY)) + print_recurse(s, a->prev, pathindex, a->pb, thispri, thisassoc, 2); + + if (parens) + putchar(')'); + } else { + /* + * This number is either an original, or something formed + * by a non-displayed operator (concatenation). Either way, + * we display it as is. + */ + printf("%d", ss->numbers[2*index]); + if (ss->numbers[2*index+1] != 1) + printf("/%d", ss->numbers[2*index+1]); + } +} +void print_recurse(struct sets *s, struct set *ss, int pathindex, int index, + int priority, int assoc, int child) +{ + if (!ss->a.prev || pathindex < ss->a.prev->npaths) { + print_recurse_inner(s, ss, &ss->a, pathindex, + index, priority, assoc, child); + } else { + int i; + pathindex -= ss->a.prev->npaths; + for (i = 0; i < ss->nas; i++) { + if (pathindex < ss->as[i].prev->npaths) { + print_recurse_inner(s, ss, &ss->as[i], pathindex, + index, priority, assoc, child); + break; + } + pathindex -= ss->as[i].prev->npaths; + } + } +} +void print(int pathindex, struct sets *s, struct output *o) +{ + print_recurse(s, o->set, pathindex, o->index, 0, 0, 0); +} + +/* + * gcc -g -O0 -o numgame numgame.c -I.. ../{malloc,tree234,nullfe}.c -lm + */ +int main(int argc, char **argv) +{ + int doing_opts = TRUE; + const struct rules *rules = NULL; + char *pname = argv[0]; + int got_target = FALSE, target = 0; + int numbers[10], nnumbers = 0; + int verbose = FALSE; + int pathcounts = FALSE; + int multiple = FALSE; + int debug_bfs = FALSE; + int got_range = FALSE, rangemin = 0, rangemax = 0; + + struct output *o; + struct sets *s; + int i, start, limit; + + while (--argc) { + char *p = *++argv; + int c; + + if (doing_opts && *p == '-') { + p++; + + if (!strcmp(p, "-")) { + doing_opts = FALSE; + continue; + } else if (*p == '-') { + p++; + if (!strcmp(p, "debug-bfs")) { + debug_bfs = TRUE; + } else { + fprintf(stderr, "%s: option '--%s' not recognised\n", + pname, p); + } + } else while (p && *p) switch (c = *p++) { + case 'C': + rules = &rules_countdown; + break; + case 'B': + rules = &rules_3388; + break; + case 'D': + rules = &rules_four4s; + break; + case 'A': + rules = &rules_anythinggoes; + break; + case 'v': + verbose = TRUE; + break; + case 'p': + pathcounts = TRUE; + break; + case 'm': + multiple = TRUE; + break; + case 't': + case 'r': + { + char *v; + if (*p) { + v = p; + p = NULL; + } else if (--argc) { + v = *++argv; + } else { + fprintf(stderr, "%s: option '-%c' expects an" + " argument\n", pname, c); + return 1; + } + switch (c) { + case 't': + got_target = TRUE; + target = atoi(v); + break; + case 'r': + { + char *sep = strchr(v, '-'); + got_range = TRUE; + if (sep) { + rangemin = atoi(v); + rangemax = atoi(sep+1); + } else { + rangemin = 0; + rangemax = atoi(v); + } + } + break; + } + } + break; + default: + fprintf(stderr, "%s: option '-%c' not" + " recognised\n", pname, c); + return 1; + } + } else { + if (nnumbers >= lenof(numbers)) { + fprintf(stderr, "%s: internal limit of %d numbers exceeded\n", + pname, (int)lenof(numbers)); + return 1; + } else { + numbers[nnumbers++] = atoi(p); + } + } + } + + if (!rules) { + fprintf(stderr, "%s: no rule set specified; use -C,-B,-D,-A\n", pname); + return 1; + } + + if (!nnumbers) { + fprintf(stderr, "%s: no input numbers specified\n", pname); + return 1; + } + + if (got_range) { + if (got_target) { + fprintf(stderr, "%s: only one of -t and -r may be specified\n", pname); + return 1; + } + if (rangemin >= rangemax) { + fprintf(stderr, "%s: range not sensible (%d - %d)\n", pname, rangemin, rangemax); + return 1; + } + } + + s = do_search(nnumbers, numbers, rules, (got_target ? &target : NULL), + debug_bfs, multiple); + + if (got_target) { + o = findrelpos234(s->outputtree, &target, outputfindcmp, + REL234_LE, &start); + if (!o) + start = -1; + o = findrelpos234(s->outputtree, &target, outputfindcmp, + REL234_GE, &limit); + if (!o) + limit = -1; + assert(start != -1 || limit != -1); + if (start == -1) + start = limit; + else if (limit == -1) + limit = start; + limit++; + } else if (got_range) { + if (!findrelpos234(s->outputtree, &rangemin, outputfindcmp, + REL234_GE, &start) || + !findrelpos234(s->outputtree, &rangemax, outputfindcmp, + REL234_LE, &limit)) { + printf("No solutions available in specified range %d-%d\n", rangemin, rangemax); + return 1; + } + limit++; + } else { + start = 0; + limit = count234(s->outputtree); + } + + for (i = start; i < limit; i++) { + char buf[256]; + + o = index234(s->outputtree, i); + + sprintf(buf, "%d", o->number); + + if (pathcounts) + sprintf(buf + strlen(buf), " [%d]", o->npaths); + + if (got_target || verbose) { + int j, npaths; + + if (multiple) + npaths = o->npaths; + else + npaths = 1; + + for (j = 0; j < npaths; j++) { + printf("%s = ", buf); + print(j, s, o); + putchar('\n'); + } + } else { + printf("%s\n", buf); + } + } + + free_sets(s); + + return 0; +} + +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/unfinished/path.c b/apps/plugins/puzzles/unfinished/path.c new file mode 100644 index 0000000000..61d6c61c6a --- /dev/null +++ b/apps/plugins/puzzles/unfinished/path.c @@ -0,0 +1,786 @@ +/* + * Experimental grid generator for Nikoli's `Number Link' puzzle. + */ + +#include +#include +#include +#include "puzzles.h" + +/* + * 2005-07-08: This is currently a Path grid generator which will + * construct valid grids at a plausible speed. However, the grids + * are not of suitable quality to be used directly as puzzles. + * + * The basic strategy is to start with an empty grid, and + * repeatedly either (a) add a new path to it, or (b) extend one + * end of a path by one square in some direction and push other + * paths into new shapes in the process. The effect of this is that + * we are able to construct a set of paths which between them fill + * the entire grid. + * + * Quality issues: if we set the main loop to do (a) where possible + * and (b) only where necessary, we end up with a grid containing a + * few too many small paths, which therefore doesn't make for an + * interesting puzzle. If we reverse the priority so that we do (b) + * where possible and (a) only where necessary, we end up with some + * staggeringly interwoven grids with very very few separate paths, + * but the result of this is that there's invariably a solution + * other than the intended one which leaves many grid squares + * unfilled. There's also a separate problem which is that many + * grids have really boring and obvious paths in them, such as the + * entire bottom row of the grid being taken up by a single path. + * + * It's not impossible that a few tweaks might eliminate or reduce + * the incidence of boring paths, and might also find a happy + * medium between too many and too few. There remains the question + * of unique solutions, however. I fear there is no alternative but + * to write - somehow! - a solver. + * + * While I'm here, some notes on UI strategy for the parts of the + * puzzle implementation that _aren't_ the generator: + * + * - data model is to track connections between adjacent squares, + * so that you aren't limited to extending a path out from each + * number but can also mark sections of path which you know + * _will_ come in handy later. + * + * - user interface is to click in one square and drag to an + * adjacent one, thus creating a link between them. We can + * probably tolerate rapid mouse motion causing a drag directly + * to a square which is a rook move away, but any other rapid + * motion is ambiguous and probably the best option is to wait + * until the mouse returns to a square we know how to reach. + * + * - a drag causing the current path to backtrack has the effect + * of removing bits of it. + * + * - the UI should enforce at all times the constraint that at + * most two links can come into any square. + * + * - my Cunning Plan for actually implementing this: the game_ui + * contains a grid-sized array, which is copied from the current + * game_state on starting a drag. While a drag is active, the + * contents of the game_ui is adjusted with every mouse motion, + * and is displayed _in place_ of the game_state itself. On + * termination of a drag, the game_ui array is copied back into + * the new game_state (or rather, a string move is encoded which + * has precisely the set of link changes to cause that effect). + */ + +/* + * Standard notation for directions. + */ +#define L 0 +#define U 1 +#define R 2 +#define D 3 +#define DX(dir) ( (dir)==L ? -1 : (dir)==R ? +1 : 0) +#define DY(dir) ( (dir)==U ? -1 : (dir)==D ? +1 : 0) + +/* + * Perform a breadth-first search over a grid of squares with the + * colour of square (X,Y) given by grid[Y*w+X]. The search begins + * at (x,y), and finds all squares which are the same colour as + * (x,y) and reachable from it by orthogonal moves. On return: + * - dist[Y*w+X] gives the distance of (X,Y) from (x,y), or -1 if + * unreachable or a different colour + * - the returned value is the number of reachable squares, + * including (x,y) itself + * - list[0] up to list[returned value - 1] list those squares, in + * increasing order of distance from (x,y) (and in arbitrary + * order within that). + */ +static int bfs(int w, int h, int *grid, int x, int y, int *dist, int *list) +{ + int i, j, c, listsize, listdone; + + /* + * Start by clearing the output arrays. + */ + for (i = 0; i < w*h; i++) + dist[i] = list[i] = -1; + + /* + * Set up the initial list. + */ + listsize = 1; + listdone = 0; + list[0] = y*w+x; + dist[y*w+x] = 0; + c = grid[y*w+x]; + + /* + * Repeatedly process a square and add any extra squares to the + * end of list. + */ + while (listdone < listsize) { + i = list[listdone++]; + y = i / w; + x = i % w; + for (j = 0; j < 4; j++) { + int xx, yy, ii; + + xx = x + DX(j); + yy = y + DY(j); + ii = yy*w+xx; + + if (xx >= 0 && xx < w && yy >= 0 && yy < h && + grid[ii] == c && dist[ii] == -1) { + dist[ii] = dist[i] + 1; + assert(listsize < w*h); + list[listsize++] = ii; + } + } + } + + return listsize; +} + +struct genctx { + int w, h; + int *grid, *sparegrid, *sparegrid2, *sparegrid3; + int *dist, *list; + + int npaths, pathsize; + int *pathends, *sparepathends; /* 2*npaths entries */ + int *pathspare; /* npaths entries */ + int *extends; /* 8*npaths entries */ +}; + +static struct genctx *new_genctx(int w, int h) +{ + struct genctx *ctx = snew(struct genctx); + ctx->w = w; + ctx->h = h; + ctx->grid = snewn(w * h, int); + ctx->sparegrid = snewn(w * h, int); + ctx->sparegrid2 = snewn(w * h, int); + ctx->sparegrid3 = snewn(w * h, int); + ctx->dist = snewn(w * h, int); + ctx->list = snewn(w * h, int); + ctx->npaths = ctx->pathsize = 0; + ctx->pathends = ctx->sparepathends = ctx->pathspare = ctx->extends = NULL; + return ctx; +} + +static void free_genctx(struct genctx *ctx) +{ + sfree(ctx->grid); + sfree(ctx->sparegrid); + sfree(ctx->sparegrid2); + sfree(ctx->sparegrid3); + sfree(ctx->dist); + sfree(ctx->list); + sfree(ctx->pathends); + sfree(ctx->sparepathends); + sfree(ctx->pathspare); + sfree(ctx->extends); +} + +static int newpath(struct genctx *ctx) +{ + int n; + + n = ctx->npaths++; + if (ctx->npaths > ctx->pathsize) { + ctx->pathsize += 16; + ctx->pathends = sresize(ctx->pathends, ctx->pathsize*2, int); + ctx->sparepathends = sresize(ctx->sparepathends, ctx->pathsize*2, int); + ctx->pathspare = sresize(ctx->pathspare, ctx->pathsize, int); + ctx->extends = sresize(ctx->extends, ctx->pathsize*8, int); + } + return n; +} + +static int is_endpoint(struct genctx *ctx, int x, int y) +{ + int w = ctx->w, h = ctx->h, c; + + assert(x >= 0 && x < w && y >= 0 && y < h); + + c = ctx->grid[y*w+x]; + if (c < 0) + return FALSE; /* empty square is not an endpoint! */ + assert(c >= 0 && c < ctx->npaths); + if (ctx->pathends[c*2] == y*w+x || ctx->pathends[c*2+1] == y*w+x) + return TRUE; + return FALSE; +} + +/* + * Tries to extend a path by one square in the given direction, + * pushing other paths around if necessary. Returns TRUE on success + * or FALSE on failure. + */ +static int extend_path(struct genctx *ctx, int path, int end, int direction) +{ + int w = ctx->w, h = ctx->h; + int x, y, xe, ye, cut; + int i, j, jp, n, first, last; + + assert(path >= 0 && path < ctx->npaths); + assert(end == 0 || end == 1); + + /* + * Find the endpoint of the path and the point we plan to + * extend it into. + */ + y = ctx->pathends[path * 2 + end] / w; + x = ctx->pathends[path * 2 + end] % w; + assert(x >= 0 && x < w && y >= 0 && y < h); + + xe = x + DX(direction); + ye = y + DY(direction); + if (xe < 0 || xe >= w || ye < 0 || ye >= h) + return FALSE; /* could not extend in this direction */ + + /* + * We don't extend paths _directly_ into endpoints of other + * paths, although we don't mind too much if a knock-on effect + * of an extension is to push part of another path into a third + * path's endpoint. + */ + if (is_endpoint(ctx, xe, ye)) + return FALSE; + + /* + * We can't extend a path back the way it came. + */ + if (ctx->grid[ye*w+xe] == path) + return FALSE; + + /* + * Paths may not double back on themselves. Check if the new + * point is adjacent to any point of this path other than (x,y). + */ + for (j = 0; j < 4; j++) { + int xf, yf; + + xf = xe + DX(j); + yf = ye + DY(j); + + if (xf >= 0 && xf < w && yf >= 0 && yf < h && + (xf != x || yf != y) && ctx->grid[yf*w+xf] == path) + return FALSE; + } + + /* + * Now we're convinced it's valid to _attempt_ the extension. + * It may still fail if we run out of space to push other paths + * into. + * + * So now we can set up our temporary data structures. We will + * need: + * + * - a spare copy of the grid on which to gradually move paths + * around (sparegrid) + * + * - a second spare copy with which to remember how paths + * looked just before being cut (sparegrid2). FIXME: is + * sparegrid2 necessary? right now it's never different from + * grid itself + * + * - a third spare copy with which to do the internal + * calculations involved in reconstituting a cut path + * (sparegrid3) + * + * - something to track which paths currently need + * reconstituting after being cut, and which have already + * been cut (pathspare) + * + * - a spare copy of pathends to store the altered states in + * (sparepathends) + */ + memcpy(ctx->sparegrid, ctx->grid, w*h*sizeof(int)); + memcpy(ctx->sparegrid2, ctx->grid, w*h*sizeof(int)); + memcpy(ctx->sparepathends, ctx->pathends, ctx->npaths*2*sizeof(int)); + for (i = 0; i < ctx->npaths; i++) + ctx->pathspare[i] = 0; /* 0=untouched, 1=broken, 2=fixed */ + + /* + * Working in sparegrid, actually extend the path. If it cuts + * another, begin a loop in which we restore any cut path by + * moving it out of the way. + */ + cut = ctx->sparegrid[ye*w+xe]; + ctx->sparegrid[ye*w+xe] = path; + ctx->sparepathends[path*2+end] = ye*w+xe; + ctx->pathspare[path] = 2; /* this one is sacrosanct */ + if (cut >= 0) { + assert(cut >= 0 && cut < ctx->npaths); + ctx->pathspare[cut] = 1; /* broken */ + + while (1) { + for (i = 0; i < ctx->npaths; i++) + if (ctx->pathspare[i] == 1) + break; + if (i == ctx->npaths) + break; /* we're done */ + + /* + * Path i needs restoring. So walk along its original + * track (as given in sparegrid2) and see where it's + * been cut. Where it has, surround the cut points in + * the same colour, without overwriting already-fixed + * paths. + */ + memcpy(ctx->sparegrid3, ctx->sparegrid, w*h*sizeof(int)); + n = bfs(w, h, ctx->sparegrid2, + ctx->pathends[i*2] % w, ctx->pathends[i*2] / w, + ctx->dist, ctx->list); + first = last = -1; +if (ctx->sparegrid3[ctx->pathends[i*2]] != i || + ctx->sparegrid3[ctx->pathends[i*2+1]] != i) return FALSE;/* FIXME */ + for (j = 0; j < n; j++) { + jp = ctx->list[j]; + assert(ctx->dist[jp] == j); + assert(ctx->sparegrid2[jp] == i); + + /* + * Wipe out the original path in sparegrid. + */ + if (ctx->sparegrid[jp] == i) + ctx->sparegrid[jp] = -1; + + /* + * Be prepared to shorten the path at either end if + * the endpoints have been stomped on. + */ + if (ctx->sparegrid3[jp] == i) { + if (first < 0) + first = jp; + last = jp; + } + + if (ctx->sparegrid3[jp] != i) { + int jx = jp % w, jy = jp / w; + int dx, dy; + for (dy = -1; dy <= +1; dy++) + for (dx = -1; dx <= +1; dx++) { + int newp, newv; + if (!dy && !dx) + continue; /* central square */ + if (jx+dx < 0 || jx+dx >= w || + jy+dy < 0 || jy+dy >= h) + continue; /* out of range */ + newp = (jy+dy)*w+(jx+dx); + newv = ctx->sparegrid3[newp]; + if (newv >= 0 && (newv == i || + ctx->pathspare[newv] == 2)) + continue; /* can't use this square */ + ctx->sparegrid3[newp] = i; + } + } + } + + if (first < 0 || last < 0) + return FALSE; /* path is completely wiped out! */ + + /* + * Now we've covered sparegrid3 in possible squares for + * the new layout of path i. Find the actual layout + * we're going to use by bfs: we want the shortest path + * from one endpoint to the other. + */ + n = bfs(w, h, ctx->sparegrid3, first % w, first / w, + ctx->dist, ctx->list); + if (ctx->dist[last] < 2) { + /* + * Either there is no way to get between the path's + * endpoints, or the remaining endpoints simply + * aren't far enough apart to make the path viable + * any more. This means the entire push operation + * has failed. + */ + return FALSE; + } + + /* + * Write the new path into sparegrid. Also save the new + * endpoint locations, in case they've changed. + */ + jp = last; + j = ctx->dist[jp]; + while (1) { + int d; + + if (ctx->sparegrid[jp] >= 0) { + if (ctx->pathspare[ctx->sparegrid[jp]] == 2) + return FALSE; /* somehow we've hit a fixed path */ + ctx->pathspare[ctx->sparegrid[jp]] = 1; /* broken */ + } + ctx->sparegrid[jp] = i; + + if (j == 0) + break; + + /* + * Now look at the neighbours of jp to find one + * which has dist[] one less. + */ + for (d = 0; d < 4; d++) { + int jx = (jp % w) + DX(d), jy = (jp / w) + DY(d); + if (jx >= 0 && jx < w && jy >= 0 && jy < w && + ctx->dist[jy*w+jx] == j-1) { + jp = jy*w+jx; + j--; + break; + } + } + assert(d < 4); + } + + ctx->sparepathends[i*2] = first; + ctx->sparepathends[i*2+1] = last; +//printf("new ends of path %d: %d,%d\n", i, first, last); + ctx->pathspare[i] = 2; /* fixed */ + } + } + + /* + * If we got here, the extension was successful! + */ + memcpy(ctx->grid, ctx->sparegrid, w*h*sizeof(int)); + memcpy(ctx->pathends, ctx->sparepathends, ctx->npaths*2*sizeof(int)); + return TRUE; +} + +/* + * Tries to add a new path to the grid. + */ +static int add_path(struct genctx *ctx, random_state *rs) +{ + int w = ctx->w, h = ctx->h; + int i, ii, n; + + /* + * Our strategy is: + * - randomly choose an empty square in the grid + * - do a BFS from that point to find a long path starting + * from it + * - if we run out of viable empty squares, return failure. + */ + + /* + * Use `sparegrid' to collect a list of empty squares. + */ + n = 0; + for (i = 0; i < w*h; i++) + if (ctx->grid[i] == -1) + ctx->sparegrid[n++] = i; + + /* + * Shuffle the grid. + */ + for (i = n; i-- > 1 ;) { + int k = random_upto(rs, i+1); + if (k != i) { + int t = ctx->sparegrid[i]; + ctx->sparegrid[i] = ctx->sparegrid[k]; + ctx->sparegrid[k] = t; + } + } + + /* + * Loop over it trying to add paths. This looks like a + * horrifying N^4 algorithm (that is, (w*h)^2), but I predict + * that in fact the worst case will very rarely arise because + * when there's lots of grid space an attempt will succeed very + * quickly. + */ + for (ii = 0; ii < n; ii++) { + int i = ctx->sparegrid[ii]; + int y = i / w, x = i % w, nsq; + int r, c, j; + + /* + * BFS from here to find long paths. + */ + nsq = bfs(w, h, ctx->grid, x, y, ctx->dist, ctx->list); + + /* + * If there aren't any long enough, give up immediately. + */ + assert(nsq > 0); /* must be the start square at least! */ + if (ctx->dist[ctx->list[nsq-1]] < 3) + continue; + + /* + * Find the first viable endpoint in ctx->list (i.e. the + * first point with distance at least three). I could + * binary-search for this, but that would be O(log N) + * whereas in fact I can get a constant time bound by just + * searching up from the start - after all, there can be at + * most 13 points at _less_ than distance 3 from the + * starting one! + */ + for (j = 0; j < nsq; j++) + if (ctx->dist[ctx->list[j]] >= 3) + break; + assert(j < nsq); /* we tested above that there was one */ + + /* + * Now we know that any element of `list' between j and nsq + * would be valid in principle. However, we want a few long + * paths rather than many small ones, so select only those + * elements which are either the maximum length or one + * below it. + */ + while (ctx->dist[ctx->list[j]] + 1 < ctx->dist[ctx->list[nsq-1]]) + j++; + r = j + random_upto(rs, nsq - j); + j = ctx->list[r]; + + /* + * And that's our endpoint. Mark the new path on the grid. + */ + c = newpath(ctx); + ctx->pathends[c*2] = i; + ctx->pathends[c*2+1] = j; + ctx->grid[j] = c; + while (j != i) { + int d, np, index, pts[4]; + np = 0; + for (d = 0; d < 4; d++) { + int xn = (j % w) + DX(d), yn = (j / w) + DY(d); + if (xn >= 0 && xn < w && yn >= 0 && yn < w && + ctx->dist[yn*w+xn] == ctx->dist[j] - 1) + pts[np++] = yn*w+xn; + } + if (np > 1) + index = random_upto(rs, np); + else + index = 0; + j = pts[index]; + ctx->grid[j] = c; + } + + return TRUE; + } + + return FALSE; +} + +/* + * The main grid generation loop. + */ +static void gridgen_mainloop(struct genctx *ctx, random_state *rs) +{ + int w = ctx->w, h = ctx->h; + int i, n; + + /* + * The generation algorithm doesn't always converge. Loop round + * until it does. + */ + while (1) { + for (i = 0; i < w*h; i++) + ctx->grid[i] = -1; + ctx->npaths = 0; + + while (1) { + /* + * See if the grid is full. + */ + for (i = 0; i < w*h; i++) + if (ctx->grid[i] < 0) + break; + if (i == w*h) + return; + +#ifdef GENERATION_DIAGNOSTICS + { + int x, y; + for (y = 0; y < h; y++) { + printf("|"); + for (x = 0; x < w; x++) { + if (ctx->grid[y*w+x] >= 0) + printf("%2d", ctx->grid[y*w+x]); + else + printf(" ."); + } + printf(" |\n"); + } + } +#endif + /* + * Try adding a path. + */ + if (add_path(ctx, rs)) { +#ifdef GENERATION_DIAGNOSTICS + printf("added path\n"); +#endif + continue; + } + + /* + * Try extending a path. First list all the possible + * extensions. + */ + for (i = 0; i < ctx->npaths * 8; i++) + ctx->extends[i] = i; + n = i; + + /* + * Then shuffle the list. + */ + for (i = n; i-- > 1 ;) { + int k = random_upto(rs, i+1); + if (k != i) { + int t = ctx->extends[i]; + ctx->extends[i] = ctx->extends[k]; + ctx->extends[k] = t; + } + } + + /* + * Now try each one in turn until one works. + */ + for (i = 0; i < n; i++) { + int p, d, e; + p = ctx->extends[i]; + d = p % 4; + p /= 4; + e = p % 2; + p /= 2; + +#ifdef GENERATION_DIAGNOSTICS + printf("trying to extend path %d end %d (%d,%d) in dir %d\n", p, e, + ctx->pathends[p*2+e] % w, + ctx->pathends[p*2+e] / w, d); +#endif + if (extend_path(ctx, p, e, d)) { +#ifdef GENERATION_DIAGNOSTICS + printf("extended path %d end %d (%d,%d) in dir %d\n", p, e, + ctx->pathends[p*2+e] % w, + ctx->pathends[p*2+e] / w, d); +#endif + break; + } + } + + if (i < n) + continue; + + break; + } + } +} + +/* + * Wrapper function which deals with the boring bits such as + * removing the solution from the generated grid, shuffling the + * numeric labels and creating/disposing of the context structure. + */ +static int *gridgen(int w, int h, random_state *rs) +{ + struct genctx *ctx; + int *ret; + int i; + + ctx = new_genctx(w, h); + + gridgen_mainloop(ctx, rs); + + /* + * There is likely to be an ordering bias in the numbers + * (longer paths on lower numbers due to there having been more + * grid space when laying them down). So we must shuffle the + * numbers. We use ctx->pathspare for this. + * + * This is also as good a time as any to shift to numbering + * from 1, for display to the user. + */ + for (i = 0; i < ctx->npaths; i++) + ctx->pathspare[i] = i+1; + for (i = ctx->npaths; i-- > 1 ;) { + int k = random_upto(rs, i+1); + if (k != i) { + int t = ctx->pathspare[i]; + ctx->pathspare[i] = ctx->pathspare[k]; + ctx->pathspare[k] = t; + } + } + + /* FIXME: remove this at some point! */ + { + int y, x; + for (y = 0; y < h; y++) { + printf("|"); + for (x = 0; x < w; x++) { + assert(ctx->grid[y*w+x] >= 0); + printf("%2d", ctx->pathspare[ctx->grid[y*w+x]]); + } + printf(" |\n"); + } + printf("\n"); + } + + /* + * Clear the grid, and write in just the endpoints. + */ + for (i = 0; i < w*h; i++) + ctx->grid[i] = 0; + for (i = 0; i < ctx->npaths; i++) { + ctx->grid[ctx->pathends[i*2]] = + ctx->grid[ctx->pathends[i*2+1]] = ctx->pathspare[i]; + } + + ret = ctx->grid; + ctx->grid = NULL; + + free_genctx(ctx); + + return ret; +} + +#ifdef TEST_GEN + +#define TEST_GENERAL + +int main(void) +{ + int w = 10, h = 8; + random_state *rs = random_init("12345", 5); + int x, y, i, *grid; + + for (i = 0; i < 10; i++) { + grid = gridgen(w, h, rs); + + for (y = 0; y < h; y++) { + printf("|"); + for (x = 0; x < w; x++) { + if (grid[y*w+x] > 0) + printf("%2d", grid[y*w+x]); + else + printf(" ."); + } + printf(" |\n"); + } + printf("\n"); + + sfree(grid); + } + + return 0; +} +#endif + +#ifdef TEST_GENERAL +#include + +void fatal(char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "fatal error: "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); + exit(1); +} +#endif diff --git a/apps/plugins/puzzles/unfinished/separate.R b/apps/plugins/puzzles/unfinished/separate.R new file mode 100644 index 0000000000..f861c8f4fe --- /dev/null +++ b/apps/plugins/puzzles/unfinished/separate.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +SEPARATE_EXTRA = divvy dsf + +separate : [X] GTK COMMON separate SEPARATE_EXTRA separate-icon|no-icon + +separate : [G] WINDOWS COMMON separate SEPARATE_EXTRA separate.res|noicon.res + +ALL += separate[COMBINED] SEPARATE_EXTRA + +!begin am gtk +GAMES += separate +!end + +!begin >list.c + A(separate) \ +!end + +!begin >gamedesc.txt +separate:separate.exe:Separate:Rectangle-dividing puzzle:Partition the grid into regions containing one of each letter. +!end diff --git a/apps/plugins/puzzles/unfinished/separate.c b/apps/plugins/puzzles/unfinished/separate.c new file mode 100644 index 0000000000..898304a0e6 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/separate.c @@ -0,0 +1,859 @@ +/* + * separate.c: Implementation of `Block Puzzle', a Japanese-only + * Nikoli puzzle seen at + * http://www.nikoli.co.jp/ja/puzzles/block_puzzle/ + * + * It's difficult to be absolutely sure of the rules since online + * Japanese translators are so bad, but looking at the sample + * puzzle it seems fairly clear that the rules of this one are + * very simple. You have an mxn grid in which every square + * contains a letter, there are k distinct letters with k dividing + * mn, and every letter occurs the same number of times; your aim + * is to find a partition of the grid into disjoint k-ominoes such + * that each k-omino contains exactly one of each letter. + * + * (It may be that Nikoli always have m,n,k equal to one another. + * However, I don't see that that's critical to the puzzle; k|mn + * is the only really important constraint, and even that could + * probably be dispensed with if some squares were marked as + * unused.) + */ + +/* + * Current status: only the solver/generator is yet written, and + * although working in principle it's _very_ slow. It generates + * 5x5n5 or 6x6n4 readily enough, 6x6n6 with a bit of effort, and + * 7x7n7 only with a serious strain. I haven't dared try it higher + * than that yet. + * + * One idea to speed it up is to implement more of the solver. + * Ideas I've so far had include: + * + * - Generalise the deduction currently expressed as `an + * undersized chain with only one direction to extend must take + * it'. More generally, the deduction should say `if all the + * possible k-ominoes containing a given chain also contain + * square x, then mark square x as part of that k-omino'. + * + For example, consider this case: + * + * a ? b This represents the top left of a board; the letters + * ? ? ? a,b,c do not represent the letters used in the puzzle, + * c ? ? but indicate that those three squares are known to be + * of different ominoes. Now if k >= 4, we can immediately + * deduce that the square midway between b and c belongs to the + * same omino as a, because there is no way we can make a 4-or- + * more-omino containing a which does not also contain that square. + * (Most easily seen by imagining cutting that square out of the + * grid; then, clearly, the omino containing a has only two + * squares to expand into, and needs at least three.) + * + * The key difficulty with this mode of reasoning is + * identifying such squares. I can't immediately think of a + * simple algorithm for finding them on a wholesale basis. + * + * - Bfs out from a chain looking for the letters it lacks. For + * example, in this situation (top three rows of a 7x7n7 grid): + * + * +-----------+-+ + * |E-A-F-B-C D|D| + * +------- || + * |E-C-G-D G|G E| + * +-+--- | + * |E|E G A B F A| + * + * In this situation we can be sure that the top left chain + * E-A-F-B-C does extend rightwards to the D, because there is + * no other D within reach of that chain. Note also that the + * bfs can skip squares which are known to belong to other + * ominoes than this one. + * + * (This deduction, I fear, should only be used in an + * emergency, because it relies on _all_ squares within range + * of the bfs having particular values and so using it during + * incremental generation rather nails down a lot of the grid.) + * + * It's conceivable that another thing we could do would be to + * increase the flexibility in the grid generator: instead of + * nailing down the _value_ of any square depended on, merely nail + * down its equivalence to other squares. Unfortunately this turns + * the letter-selection phase of generation into a general graph + * colouring problem (we must draw a graph with equivalence + * classes of squares as the vertices, and an edge between any two + * vertices representing equivalence classes which contain squares + * that share an omino, and then k-colour the result) and hence + * requires recursion, which bodes ill for something we're doing + * that many times per generation. + * + * I suppose a simple thing I could try would be tuning the retry + * count, just in case it's set too high or too low for efficient + * generation. + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" + +enum { + COL_BACKGROUND, + NCOLOURS +}; + +struct game_params { + int w, h, k; +}; + +struct game_state { + int FIXME; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = ret->k = 5; /* FIXME: a bit bigger? */ + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + return FALSE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = params->k = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'n') { + string++; + params->k = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[256]; + sprintf(buf, "%dx%dn%d", params->w, params->h, params->k); + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + return NULL; +} + +static game_params *custom_params(const config_item *cfg) +{ + return NULL; +} + +static char *validate_params(const game_params *params, int full) +{ + return NULL; +} + +/* ---------------------------------------------------------------------- + * Solver and generator. + */ + +struct solver_scratch { + int w, h, k; + + /* + * Tracks connectedness between squares. + */ + int *dsf; + + /* + * size[dsf_canonify(dsf, yx)] tracks the size of the + * connected component containing yx. + */ + int *size; + + /* + * contents[dsf_canonify(dsf, yx)*k+i] tracks whether or not + * the connected component containing yx includes letter i. If + * the value is -1, it doesn't; otherwise its value is the + * index in the main grid of the square which contributes that + * letter to the component. + */ + int *contents; + + /* + * disconnect[dsf_canonify(dsf, yx1)*w*h + dsf_canonify(dsf, yx2)] + * tracks whether or not the connected components containing + * yx1 and yx2 are known to be distinct. + */ + unsigned char *disconnect; + + /* + * Temporary space used only inside particular solver loops. + */ + int *tmp; +}; + +struct solver_scratch *solver_scratch_new(int w, int h, int k) +{ + int wh = w*h; + struct solver_scratch *sc = snew(struct solver_scratch); + + sc->w = w; + sc->h = h; + sc->k = k; + + sc->dsf = snew_dsf(wh); + sc->size = snewn(wh, int); + sc->contents = snewn(wh * k, int); + sc->disconnect = snewn(wh*wh, unsigned char); + sc->tmp = snewn(wh, int); + + return sc; +} + +void solver_scratch_free(struct solver_scratch *sc) +{ + sfree(sc->dsf); + sfree(sc->size); + sfree(sc->contents); + sfree(sc->disconnect); + sfree(sc->tmp); + sfree(sc); +} + +void solver_connect(struct solver_scratch *sc, int yx1, int yx2) +{ + int w = sc->w, h = sc->h, k = sc->k; + int wh = w*h; + int i, yxnew; + + yx1 = dsf_canonify(sc->dsf, yx1); + yx2 = dsf_canonify(sc->dsf, yx2); + assert(yx1 != yx2); + + /* + * To connect two components together into a bigger one, we + * start by merging them in the dsf itself. + */ + dsf_merge(sc->dsf, yx1, yx2); + yxnew = dsf_canonify(sc->dsf, yx2); + + /* + * The size of the new component is the sum of the sizes of the + * old ones. + */ + sc->size[yxnew] = sc->size[yx1] + sc->size[yx2]; + + /* + * The contents bitmap of the new component is the union of the + * contents of the old ones. + * + * Given two numbers at most one of which is not -1, we can + * find the other one by adding the two and adding 1; this + * will yield -1 if both were -1 to begin with, otherwise the + * other. + * + * (A neater approach would be to take their bitwise AND, but + * this is unfortunately not well-defined standard C when done + * to signed integers.) + */ + for (i = 0; i < k; i++) { + assert(sc->contents[yx1*k+i] < 0 || sc->contents[yx2*k+i] < 0); + sc->contents[yxnew*k+i] = (sc->contents[yx1*k+i] + + sc->contents[yx2*k+i] + 1); + } + + /* + * We must combine the rows _and_ the columns in the disconnect + * matrix. + */ + for (i = 0; i < wh; i++) + sc->disconnect[yxnew*wh+i] = (sc->disconnect[yx1*wh+i] || + sc->disconnect[yx2*wh+i]); + for (i = 0; i < wh; i++) + sc->disconnect[i*wh+yxnew] = (sc->disconnect[i*wh+yx1] || + sc->disconnect[i*wh+yx2]); +} + +void solver_disconnect(struct solver_scratch *sc, int yx1, int yx2) +{ + int w = sc->w, h = sc->h; + int wh = w*h; + + yx1 = dsf_canonify(sc->dsf, yx1); + yx2 = dsf_canonify(sc->dsf, yx2); + assert(yx1 != yx2); + assert(!sc->disconnect[yx1*wh+yx2]); + assert(!sc->disconnect[yx2*wh+yx1]); + + /* + * Mark the components as disconnected from each other in the + * disconnect matrix. + */ + sc->disconnect[yx1*wh+yx2] = sc->disconnect[yx2*wh+yx1] = 1; +} + +void solver_init(struct solver_scratch *sc) +{ + int w = sc->w, h = sc->h; + int wh = w*h; + int i; + + /* + * Set up most of the scratch space. We don't set up the + * contents array, however, because this will change if we + * adjust the letter arrangement and re-run the solver. + */ + dsf_init(sc->dsf, wh); + for (i = 0; i < wh; i++) sc->size[i] = 1; + memset(sc->disconnect, 0, wh*wh); +} + +int solver_attempt(struct solver_scratch *sc, const unsigned char *grid, + unsigned char *gen_lock) +{ + int w = sc->w, h = sc->h, k = sc->k; + int wh = w*h; + int i, x, y; + int done_something_overall = FALSE; + + /* + * Set up the contents array from the grid. + */ + for (i = 0; i < wh*k; i++) + sc->contents[i] = -1; + for (i = 0; i < wh; i++) + sc->contents[dsf_canonify(sc->dsf, i)*k+grid[i]] = i; + + while (1) { + int done_something = FALSE; + + /* + * Go over the grid looking for reasons to add to the + * disconnect matrix. We're after pairs of squares which: + * + * - are adjacent in the grid + * - belong to distinct dsf components + * - their components are not already marked as + * disconnected + * - their components share a letter in common. + */ + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int dir; + for (dir = 0; dir < 2; dir++) { + int x2 = x + dir, y2 = y + 1 - dir; + int yx = y*w+x, yx2 = y2*w+x2; + + if (x2 >= w || y2 >= h) + continue; /* one square is outside the grid */ + + yx = dsf_canonify(sc->dsf, yx); + yx2 = dsf_canonify(sc->dsf, yx2); + if (yx == yx2) + continue; /* same dsf component */ + + if (sc->disconnect[yx*wh+yx2]) + continue; /* already known disconnected */ + + for (i = 0; i < k; i++) + if (sc->contents[yx*k+i] >= 0 && + sc->contents[yx2*k+i] >= 0) + break; + if (i == k) + continue; /* no letter in common */ + + /* + * We've found one. Mark yx and yx2 as + * disconnected from each other. + */ +#ifdef SOLVER_DIAGNOSTICS + printf("Disconnecting %d and %d (%c)\n", yx, yx2, 'A'+i); +#endif + solver_disconnect(sc, yx, yx2); + done_something = done_something_overall = TRUE; + + /* + * We have just made a deduction which hinges + * on two particular grid squares being the + * same. If we are feeding back to a generator + * loop, we must therefore mark those squares + * as fixed in the generator, so that future + * rearrangement of the grid will not break + * the information on which we have already + * based deductions. + */ + if (gen_lock) { + gen_lock[sc->contents[yx*k+i]] = 1; + gen_lock[sc->contents[yx2*k+i]] = 1; + } + } + } + } + + /* + * Now go over the grid looking for dsf components which + * are below maximum size and only have one way to extend, + * and extending them. + */ + for (i = 0; i < wh; i++) + sc->tmp[i] = -1; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int yx = dsf_canonify(sc->dsf, y*w+x); + int dir; + + if (sc->size[yx] == k) + continue; + + for (dir = 0; dir < 4; dir++) { + int x2 = x + (dir==0 ? -1 : dir==2 ? 1 : 0); + int y2 = y + (dir==1 ? -1 : dir==3 ? 1 : 0); + int yx2, yx2c; + + if (y2 < 0 || y2 >= h || x2 < 0 || x2 >= w) + continue; + yx2 = y2*w+x2; + yx2c = dsf_canonify(sc->dsf, yx2); + + if (yx2c != yx && !sc->disconnect[yx2c*wh+yx]) { + /* + * Component yx can be extended into square + * yx2. + */ + if (sc->tmp[yx] == -1) + sc->tmp[yx] = yx2; + else if (sc->tmp[yx] != yx2) + sc->tmp[yx] = -2; /* multiple choices found */ + } + } + } + } + for (i = 0; i < wh; i++) { + if (sc->tmp[i] >= 0) { + /* + * Make sure we haven't connected the two already + * during this loop (which could happen if for + * _both_ components this was the only way to + * extend them). + */ + if (dsf_canonify(sc->dsf, i) == + dsf_canonify(sc->dsf, sc->tmp[i])) + continue; + +#ifdef SOLVER_DIAGNOSTICS + printf("Connecting %d and %d\n", i, sc->tmp[i]); +#endif + solver_connect(sc, i, sc->tmp[i]); + done_something = done_something_overall = TRUE; + break; + } + } + + if (!done_something) + break; + } + + /* + * Return 0 if we haven't made any progress; 1 if we've done + * something but not solved it completely; 2 if we've solved + * it completely. + */ + for (i = 0; i < wh; i++) + if (sc->size[dsf_canonify(sc->dsf, i)] != k) + break; + if (i == wh) + return 2; + if (done_something_overall) + return 1; + return 0; +} + +unsigned char *generate(int w, int h, int k, random_state *rs) +{ + int wh = w*h; + int n = wh/k; + struct solver_scratch *sc; + unsigned char *grid; + unsigned char *shuffled; + int i, j, m, retries; + int *permutation; + unsigned char *gen_lock; + extern int *divvy_rectangle(int w, int h, int k, random_state *rs); + + sc = solver_scratch_new(w, h, k); + grid = snewn(wh, unsigned char); + shuffled = snewn(k, unsigned char); + permutation = snewn(wh, int); + gen_lock = snewn(wh, unsigned char); + + do { + int *dsf = divvy_rectangle(w, h, k, rs); + + /* + * Go through the dsf and find the indices of all the + * squares involved in each omino, in a manner conducive + * to per-omino indexing. We set permutation[i*k+j] to be + * the index of the jth square (ordered arbitrarily) in + * omino i. + */ + for (i = j = 0; i < wh; i++) + if (dsf_canonify(dsf, i) == i) { + sc->tmp[i] = j; + /* + * During this loop and the following one, we use + * the last element of each row of permutation[] + * as a counter of the number of indices so far + * placed in it. When we place the final index of + * an omino, that counter is overwritten, but that + * doesn't matter because we'll never use it + * again. Of course this depends critically on + * divvy_rectangle() having returned correct + * results, or else chaos would ensue. + */ + permutation[j*k+k-1] = 0; + j++; + } + for (i = 0; i < wh; i++) { + j = sc->tmp[dsf_canonify(dsf, i)]; + m = permutation[j*k+k-1]++; + permutation[j*k+m] = i; + } + + /* + * Track which squares' letters we have already depended + * on for deductions. This is gradually updated by + * solver_attempt(). + */ + memset(gen_lock, 0, wh); + + /* + * Now repeatedly fill the grid with letters, and attempt + * to solve it. If the solver makes progress but does not + * fail completely, then gen_lock will have been updated + * and we try again. On a complete failure, though, we + * have no option but to give up and abandon this set of + * ominoes. + */ + solver_init(sc); + retries = k*k; + while (1) { + /* + * Fill the grid with letters. We can safely use + * sc->tmp to hold the set of letters required at each + * stage, since it's at least size k and is currently + * unused. + */ + for (i = 0; i < n; i++) { + /* + * First, determine the set of letters already + * placed in this omino by gen_lock. + */ + for (j = 0; j < k; j++) + sc->tmp[j] = j; + for (j = 0; j < k; j++) { + int index = permutation[i*k+j]; + int letter = grid[index]; + if (gen_lock[index]) + sc->tmp[letter] = -1; + } + /* + * Now collect together all the remaining letters + * and randomly shuffle them. + */ + for (j = m = 0; j < k; j++) + if (sc->tmp[j] >= 0) + sc->tmp[m++] = sc->tmp[j]; + shuffle(sc->tmp, m, sizeof(*sc->tmp), rs); + /* + * Finally, write the shuffled letters into the + * grid. + */ + for (j = 0; j < k; j++) { + int index = permutation[i*k+j]; + if (!gen_lock[index]) + grid[index] = sc->tmp[--m]; + } + assert(m == 0); + } + + /* + * Now we have a candidate grid. Attempt to progress + * the solution. + */ + m = solver_attempt(sc, grid, gen_lock); + if (m == 2 || /* success */ + (m == 0 && retries-- <= 0)) /* failure */ + break; + if (m == 1) + retries = k*k; /* reset this counter, and continue */ + } + + sfree(dsf); + } while (m == 0); + + sfree(gen_lock); + sfree(permutation); + sfree(shuffled); + solver_scratch_free(sc); + + return grid; +} + +/* ---------------------------------------------------------------------- + * End of solver/generator code. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h, wh = w*h, k = params->k; + unsigned char *grid; + char *desc; + int i; + + grid = generate(w, h, k, rs); + + desc = snewn(wh+1, char); + for (i = 0; i < wh; i++) + desc[i] = 'A' + grid[i]; + desc[wh] = '\0'; + + sfree(grid); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + game_state *state = snew(game_state); + + state->FIXME = 0; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + game_state *ret = snew(game_state); + + ret->FIXME = state->FIXME; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return NULL; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +static game_ui *new_ui(const game_state *state) +{ + return NULL; +} + +static void free_ui(game_ui *ui) +{ +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int tilesize; + int FIXME; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = *y = 10 * tilesize; /* FIXME */ +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->tilesize = 0; + ds->FIXME = 0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + /* + * The initial contents of the window are not guaranteed and + * can vary with front ends. To be on the safe side, all games + * should start by drawing a big background-colour rectangle + * covering the whole window. + */ + draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame separate +#endif + +const struct game thegame = { + "Separate", NULL, NULL, + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + FALSE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + FALSE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + 20 /* FIXME */, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/unfinished/slide.R b/apps/plugins/puzzles/unfinished/slide.R new file mode 100644 index 0000000000..189ed652d1 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/slide.R @@ -0,0 +1,24 @@ +# -*- makefile -*- + +SLIDE_EXTRA = dsf tree234 + +slide : [X] GTK COMMON slide SLIDE_EXTRA slide-icon|no-icon + +slide : [G] WINDOWS COMMON slide SLIDE_EXTRA slide.res|noicon.res + +slidesolver : [U] slide[STANDALONE_SOLVER] SLIDE_EXTRA STANDALONE +slidesolver : [C] slide[STANDALONE_SOLVER] SLIDE_EXTRA STANDALONE + +ALL += slide[COMBINED] SLIDE_EXTRA + +!begin am gtk +GAMES += slide +!end + +!begin >list.c + A(slide) \ +!end + +!begin >gamedesc.txt +slide:slide.exe:Slide:Sliding block puzzle:Slide the blocks to let the key block out. +!end diff --git a/apps/plugins/puzzles/unfinished/slide.c b/apps/plugins/puzzles/unfinished/slide.c new file mode 100644 index 0000000000..b1aa04bc90 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/slide.c @@ -0,0 +1,2445 @@ +/* + * slide.c: Implementation of the block-sliding puzzle `Klotski'. + */ + +/* + * TODO: + * + * - Improve the generator. + * * actually, we seem to be mostly sensible already now. I + * want more choice over the type of main block and location + * of the exit/target, and I think I probably ought to give + * up on compactness and just bite the bullet and have the + * target area right outside the main wall, but mostly I + * think it's OK. + * * the move limit tends to make the game _slower_ to + * generate, which is odd. Perhaps investigate why. + * + * - Improve the graphics. + * * All the colours are a bit wishy-washy. _Some_ dark + * colours would surely not be excessive? Probably darken + * the tiles, the walls and the main block, and leave the + * target marker pale. + * * The cattle grid effect is still disgusting. Think of + * something completely different. + * * The highlight for next-piece-to-move in the solver is + * excessive, and the shadow blends in too well with the + * piece lowlights. Adjust both. + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +/* + * The implementation of this game revolves around the insight + * which makes an exhaustive-search solver feasible: although + * there are many blocks which can be rearranged in many ways, any + * two blocks of the same shape are _indistinguishable_ and hence + * the number of _distinct_ board layouts is generally much + * smaller. So we adopt a representation for board layouts which + * is inherently canonical, i.e. there are no two distinct + * representations which encode indistinguishable layouts. + * + * The way we do this is to encode each square of the board, in + * the normal left-to-right top-to-bottom order, as being one of + * the following things: + * - the first square (in the given order) of a block (`anchor') + * - special case of the above: the anchor for the _main_ block + * (i.e. the one which the aim of the game is to get to the + * target position) + * - a subsequent square of a block whose previous square was N + * squares ago + * - an impassable wall + * + * (We also separately store data about which board positions are + * forcefields only passable by the main block. We can't encode + * that in the main board data, because then the main block would + * destroy forcefields as it went over them.) + * + * Hence, for example, a 2x2 square block would be encoded as + * ANCHOR, followed by DIST(1), and w-2 squares later on there + * would be DIST(w-1) followed by DIST(1). So if you start at the + * last of those squares, the DIST numbers give you a linked list + * pointing back through all the other squares in the same block. + * + * So the solver simply does a bfs over all reachable positions, + * encoding them in this format and storing them in a tree234 to + * ensure it doesn't ever revisit an already-analysed position. + */ + +enum { + /* + * The colours are arranged here so that every base colour is + * directly followed by its highlight colour and then its + * lowlight colour. Do not break this, or draw_tile() will get + * confused. + */ + COL_BACKGROUND, + COL_HIGHLIGHT, + COL_LOWLIGHT, + COL_DRAGGING, + COL_DRAGGING_HIGHLIGHT, + COL_DRAGGING_LOWLIGHT, + COL_MAIN, + COL_MAIN_HIGHLIGHT, + COL_MAIN_LOWLIGHT, + COL_MAIN_DRAGGING, + COL_MAIN_DRAGGING_HIGHLIGHT, + COL_MAIN_DRAGGING_LOWLIGHT, + COL_TARGET, + COL_TARGET_HIGHLIGHT, + COL_TARGET_LOWLIGHT, + NCOLOURS +}; + +/* + * Board layout is a simple array of bytes. Each byte holds: + */ +#define ANCHOR 255 /* top-left-most square of some piece */ +#define MAINANCHOR 254 /* anchor of _main_ piece */ +#define EMPTY 253 /* empty square */ +#define WALL 252 /* immovable wall */ +#define MAXDIST 251 +/* all other values indicate distance back to previous square of same block */ +#define ISDIST(x) ( (unsigned char)((x)-1) <= MAXDIST-1 ) +#define DIST(x) (x) +#define ISANCHOR(x) ( (x)==ANCHOR || (x)==MAINANCHOR ) +#define ISBLOCK(x) ( ISANCHOR(x) || ISDIST(x) ) + +/* + * MAXDIST is the largest DIST value we can encode. This must + * therefore also be the maximum puzzle width in theory (although + * solver running time will dictate a much smaller limit in + * practice). + */ +#define MAXWID MAXDIST + +struct game_params { + int w, h; + int maxmoves; +}; + +struct game_immutable_state { + int refcount; + unsigned char *forcefield; +}; + +struct game_solution { + int nmoves; + int *moves; /* just like from solve_board() */ + int refcount; +}; + +struct game_state { + int w, h; + unsigned char *board; + int tx, ty; /* target coords for MAINANCHOR */ + int minmoves; /* for display only */ + int lastmoved, lastmoved_pos; /* for move counting */ + int movecount; + int completed; + int cheated; + struct game_immutable_state *imm; + struct game_solution *soln; + int soln_index; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = 7; + ret->h = 6; + ret->maxmoves = 40; + + return ret; +} + +static const struct game_params slide_presets[] = { + {7, 6, 25}, + {7, 6, -1}, + {8, 6, -1}, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + + if (i < 0 || i >= lenof(slide_presets)) + return FALSE; + + ret = snew(game_params); + *ret = slide_presets[i]; + + sprintf(str, "%dx%d", ret->w, ret->h); + if (ret->maxmoves >= 0) + sprintf(str + strlen(str), ", max %d moves", ret->maxmoves); + else + sprintf(str + strlen(str), ", no move limit"); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } + if (*string == 'm') { + string++; + params->maxmoves = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + } else if (*string == 'u') { + string++; + params->maxmoves = -1; + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + if (params->maxmoves >= 0) + sprintf(data + strlen(data), "m%d", params->maxmoves); + else + sprintf(data + strlen(data), "u"); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(4, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Solution length limit"; + ret[2].type = C_STRING; + sprintf(buf, "%d", params->maxmoves); + ret[2].sval = dupstr(buf); + ret[2].ival = 0; + + ret[3].name = NULL; + ret[3].type = C_END; + ret[3].sval = NULL; + ret[3].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + ret->maxmoves = atoi(cfg[2].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w > MAXWID) + return "Width must be at most " STR(MAXWID); + + if (params->w < 5) + return "Width must be at least 5"; + if (params->h < 4) + return "Height must be at least 4"; + + return NULL; +} + +static char *board_text_format(int w, int h, unsigned char *data, + unsigned char *forcefield) +{ + int wh = w*h; + int *dsf = snew_dsf(wh); + int i, x, y; + int retpos, retlen = (w*2+2)*(h*2+1)+1; + char *ret = snewn(retlen, char); + + for (i = 0; i < wh; i++) + if (ISDIST(data[i])) + dsf_merge(dsf, i - data[i], i); + retpos = 0; + for (y = 0; y < 2*h+1; y++) { + for (x = 0; x < 2*w+1; x++) { + int v; + int i = (y/2)*w+(x/2); + +#define dtype(i) (ISBLOCK(data[i]) ? \ + dsf_canonify(dsf, i) : data[i]) +#define dchar(t) ((t)==EMPTY ? ' ' : (t)==WALL ? '#' : \ + data[t] == MAINANCHOR ? '*' : '%') + + if (y % 2 && x % 2) { + int j = dtype(i); + v = dchar(j); + } else if (y % 2 && !(x % 2)) { + int j1 = (x > 0 ? dtype(i-1) : -1); + int j2 = (x < 2*w ? dtype(i) : -1); + if (j1 != j2) + v = '|'; + else + v = dchar(j1); + } else if (!(y % 2) && (x % 2)) { + int j1 = (y > 0 ? dtype(i-w) : -1); + int j2 = (y < 2*h ? dtype(i) : -1); + if (j1 != j2) + v = '-'; + else + v = dchar(j1); + } else { + int j1 = (x > 0 && y > 0 ? dtype(i-w-1) : -1); + int j2 = (x > 0 && y < 2*h ? dtype(i-1) : -1); + int j3 = (x < 2*w && y > 0 ? dtype(i-w) : -1); + int j4 = (x < 2*w && y < 2*h ? dtype(i) : -1); + if (j1 == j2 && j2 == j3 && j3 == j4) + v = dchar(j1); + else if (j1 == j2 && j3 == j4) + v = '|'; + else if (j1 == j3 && j2 == j4) + v = '-'; + else + v = '+'; + } + + assert(retpos < retlen); + ret[retpos++] = v; + } + assert(retpos < retlen); + ret[retpos++] = '\n'; + } + assert(retpos < retlen); + ret[retpos++] = '\0'; + assert(retpos == retlen); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Solver. + */ + +/* + * During solver execution, the set of visited board positions is + * stored as a tree234 of the following structures. `w', `h' and + * `data' are obvious in meaning; `dist' represents the minimum + * distance to reach this position from the starting point. + * + * `prev' links each board to the board position from which it was + * most efficiently derived. + */ +struct board { + int w, h; + int dist; + struct board *prev; + unsigned char *data; +}; + +static int boardcmp(void *av, void *bv) +{ + struct board *a = (struct board *)av; + struct board *b = (struct board *)bv; + return memcmp(a->data, b->data, a->w * a->h); +} + +static struct board *newboard(int w, int h, unsigned char *data) +{ + struct board *b = malloc(sizeof(struct board) + w*h); + b->data = (unsigned char *)b + sizeof(struct board); + memcpy(b->data, data, w*h); + b->w = w; + b->h = h; + b->dist = -1; + b->prev = NULL; + return b; +} + +/* + * The actual solver. Given a board, attempt to find the minimum + * length of move sequence which moves MAINANCHOR to (tx,ty), or + * -1 if no solution exists. Returns that minimum length. + * + * Also, if `moveout' is provided, writes out the moves in the + * form of a sequence of pairs of integers indicating the source + * and destination points of the anchor of the moved piece in each + * move. Exactly twice as many integers are written as the number + * returned from solve_board(), and `moveout' receives an int * + * which is a pointer to a dynamically allocated array. + */ +static int solve_board(int w, int h, unsigned char *board, + unsigned char *forcefield, int tx, int ty, + int movelimit, int **moveout) +{ + int wh = w*h; + struct board *b, *b2, *b3; + int *next, *anchors, *which; + int *movereached, *movequeue, mqhead, mqtail; + tree234 *sorted, *queue; + int i, j, dir; + int qlen, lastdist; + int ret; + +#ifdef SOLVER_DIAGNOSTICS + { + char *t = board_text_format(w, h, board); + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + int c = board[i*w+j]; + if (ISDIST(c)) + printf("D%-3d", c); + else if (c == MAINANCHOR) + printf("M "); + else if (c == ANCHOR) + printf("A "); + else if (c == WALL) + printf("W "); + else if (c == EMPTY) + printf("E "); + } + printf("\n"); + } + + printf("Starting solver for:\n%s\n", t); + sfree(t); + } +#endif + + sorted = newtree234(boardcmp); + queue = newtree234(NULL); + + b = newboard(w, h, board); + b->dist = 0; + add234(sorted, b); + addpos234(queue, b, 0); + qlen = 1; + + next = snewn(wh, int); + anchors = snewn(wh, int); + which = snewn(wh, int); + movereached = snewn(wh, int); + movequeue = snewn(wh, int); + lastdist = -1; + + while ((b = delpos234(queue, 0)) != NULL) { + qlen--; + if (movelimit >= 0 && b->dist >= movelimit) { + /* + * The problem is not soluble in under `movelimit' + * moves, so we can quit right now. + */ + b2 = NULL; + goto done; + } + if (b->dist != lastdist) { +#ifdef SOLVER_DIAGNOSTICS + printf("dist %d (%d)\n", b->dist, count234(sorted)); +#endif + lastdist = b->dist; + } + /* + * Find all the anchors and form a linked list of the + * squares within each block. + */ + for (i = 0; i < wh; i++) { + next[i] = -1; + anchors[i] = FALSE; + which[i] = -1; + if (ISANCHOR(b->data[i])) { + anchors[i] = TRUE; + which[i] = i; + } else if (ISDIST(b->data[i])) { + j = i - b->data[i]; + next[j] = i; + which[i] = which[j]; + } + } + + /* + * For each anchor, do an array-based BFS to find all the + * places we can slide it to. + */ + for (i = 0; i < wh; i++) { + if (!anchors[i]) + continue; + + mqhead = mqtail = 0; + for (j = 0; j < wh; j++) + movereached[j] = FALSE; + movequeue[mqtail++] = i; + while (mqhead < mqtail) { + int pos = movequeue[mqhead++]; + + /* + * Try to move in each direction from here. + */ + 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 offset = dy*w + dx; + int newpos = pos + offset; + int d = newpos - i; + + /* + * For each square involved in this block, + * check to see if the square d spaces away + * from it is either empty or part of the same + * block. + */ + for (j = i; j >= 0; j = next[j]) { + int jy = (pos+j-i) / w + dy, jx = (pos+j-i) % w + dx; + if (jy >= 0 && jy < h && jx >= 0 && jx < w && + ((b->data[j+d] == EMPTY || which[j+d] == i) && + (b->data[i] == MAINANCHOR || !forcefield[j+d]))) + /* ok */; + else + break; + } + if (j >= 0) + continue; /* this direction wasn't feasible */ + + /* + * If we've already tried moving this piece + * here, leave it. + */ + if (movereached[newpos]) + continue; + movereached[newpos] = TRUE; + movequeue[mqtail++] = newpos; + + /* + * We have a viable move. Make it. + */ + b2 = newboard(w, h, b->data); + for (j = i; j >= 0; j = next[j]) + b2->data[j] = EMPTY; + for (j = i; j >= 0; j = next[j]) + b2->data[j+d] = b->data[j]; + + b3 = add234(sorted, b2); + if (b3 != b2) { + sfree(b2); /* we already got one */ + } else { + b2->dist = b->dist + 1; + b2->prev = b; + addpos234(queue, b2, qlen++); + if (b2->data[ty*w+tx] == MAINANCHOR) + goto done; /* search completed! */ + } + } + } + } + } + b2 = NULL; + + done: + + if (b2) { + ret = b2->dist; + if (moveout) { + /* + * Now b2 represents the solved position. Backtrack to + * output the solution. + */ + *moveout = snewn(ret * 2, int); + j = ret * 2; + + while (b2->prev) { + int from = -1, to = -1; + + b = b2->prev; + + /* + * Scan b and b2 to find out which piece has + * moved. + */ + for (i = 0; i < wh; i++) { + if (ISANCHOR(b->data[i]) && !ISANCHOR(b2->data[i])) { + assert(from == -1); + from = i; + } else if (!ISANCHOR(b->data[i]) && ISANCHOR(b2->data[i])){ + assert(to == -1); + to = i; + } + } + + assert(from >= 0 && to >= 0); + assert(j >= 2); + (*moveout)[--j] = to; + (*moveout)[--j] = from; + + b2 = b; + } + assert(j == 0); + } + } else { + ret = -1; /* no solution */ + if (moveout) + *moveout = NULL; + } + + freetree234(queue); + + while ((b = delpos234(sorted, 0)) != NULL) + sfree(b); + freetree234(sorted); + + sfree(next); + sfree(anchors); + sfree(movereached); + sfree(movequeue); + sfree(which); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Random board generation. + */ + +static void generate_board(int w, int h, int *rtx, int *rty, int *minmoves, + random_state *rs, unsigned char **rboard, + unsigned char **rforcefield, int movelimit) +{ + int wh = w*h; + unsigned char *board, *board2, *forcefield; + unsigned char *tried_merge; + int *dsf; + int *list, nlist, pos; + int tx, ty; + int i, j; + int moves = 0; /* placate optimiser */ + + /* + * Set up a board and fill it with singletons, except for a + * border of walls. + */ + board = snewn(wh, unsigned char); + forcefield = snewn(wh, unsigned char); + board2 = snewn(wh, unsigned char); + memset(board, ANCHOR, wh); + memset(forcefield, FALSE, wh); + for (i = 0; i < w; i++) + board[i] = board[i+w*(h-1)] = WALL; + for (i = 0; i < h; i++) + board[i*w] = board[i*w+(w-1)] = WALL; + + tried_merge = snewn(wh * wh, unsigned char); + memset(tried_merge, 0, wh*wh); + dsf = snew_dsf(wh); + + /* + * Invent a main piece at one extreme. (FIXME: vary the + * extreme, and the piece.) + */ + board[w+1] = MAINANCHOR; + board[w+2] = DIST(1); + board[w*2+1] = DIST(w-1); + board[w*2+2] = DIST(1); + + /* + * Invent a target position. (FIXME: vary this too.) + */ + tx = w-2; + ty = h-3; + forcefield[ty*w+tx+1] = forcefield[(ty+1)*w+tx+1] = TRUE; + board[ty*w+tx+1] = board[(ty+1)*w+tx+1] = EMPTY; + + /* + * Gradually remove singletons until the game becomes soluble. + */ + for (j = w; j-- > 0 ;) + for (i = h; i-- > 0 ;) + if (board[i*w+j] == ANCHOR) { + /* + * See if the board is already soluble. + */ + if ((moves = solve_board(w, h, board, forcefield, + tx, ty, movelimit, NULL)) >= 0) + goto soluble; + + /* + * Otherwise, remove this piece. + */ + board[i*w+j] = EMPTY; + } + assert(!"We shouldn't get here"); + soluble: + + /* + * Make a list of all the inter-block edges on the board. + */ + list = snewn(wh*2, int); + nlist = 0; + for (i = 0; i+1 < w; i++) + for (j = 0; j < h; j++) + list[nlist++] = (j*w+i) * 2 + 0; /* edge to the right of j*w+i */ + for (j = 0; j+1 < h; j++) + for (i = 0; i < w; i++) + list[nlist++] = (j*w+i) * 2 + 1; /* edge below j*w+i */ + + /* + * Now go through that list in random order, trying to merge + * the blocks on each side of each edge. + */ + shuffle(list, nlist, sizeof(*list), rs); + while (nlist > 0) { + int x1, y1, p1, c1; + int x2, y2, p2, c2; + + pos = list[--nlist]; + y1 = y2 = pos / (w*2); + x1 = x2 = (pos / 2) % w; + if (pos % 2) + y2++; + else + x2++; + p1 = y1*w+x1; + p2 = y2*w+x2; + + /* + * Immediately abandon the attempt if we've already tried + * to merge the same pair of blocks along a different + * edge. + */ + c1 = dsf_canonify(dsf, p1); + c2 = dsf_canonify(dsf, p2); + if (tried_merge[c1 * wh + c2]) + continue; + + /* + * In order to be mergeable, these two squares must each + * either be, or belong to, a non-main anchor, and their + * anchors must also be distinct. + */ + if (!ISBLOCK(board[p1]) || !ISBLOCK(board[p2])) + continue; + while (ISDIST(board[p1])) + p1 -= board[p1]; + while (ISDIST(board[p2])) + p2 -= board[p2]; + if (board[p1] == MAINANCHOR || board[p2] == MAINANCHOR || p1 == p2) + continue; + + /* + * We can merge these blocks. Try it, and see if the + * puzzle remains soluble. + */ + memcpy(board2, board, wh); + j = -1; + while (p1 < wh || p2 < wh) { + /* + * p1 and p2 are the squares at the head of each block + * list. Pick the smaller one and put it on the output + * block list. + */ + i = min(p1, p2); + if (j < 0) { + board[i] = ANCHOR; + } else { + assert(i - j <= MAXDIST); + board[i] = DIST(i - j); + } + j = i; + + /* + * Now advance whichever list that came from. + */ + if (i == p1) { + do { + p1++; + } while (p1 < wh && board[p1] != DIST(p1-i)); + } else { + do { + p2++; + } while (p2 < wh && board[p2] != DIST(p2-i)); + } + } + j = solve_board(w, h, board, forcefield, tx, ty, movelimit, NULL); + if (j < 0) { + /* + * Didn't work. Revert the merge. + */ + memcpy(board, board2, wh); + tried_merge[c1 * wh + c2] = tried_merge[c2 * wh + c1] = TRUE; + } else { + int c; + + moves = j; + + dsf_merge(dsf, c1, c2); + c = dsf_canonify(dsf, c1); + for (i = 0; i < wh; i++) + tried_merge[c*wh+i] = (tried_merge[c1*wh+i] | + tried_merge[c2*wh+i]); + for (i = 0; i < wh; i++) + tried_merge[i*wh+c] = (tried_merge[i*wh+c1] | + tried_merge[i*wh+c2]); + } + } + + sfree(dsf); + sfree(list); + sfree(tried_merge); + sfree(board2); + + *rtx = tx; + *rty = ty; + *rboard = board; + *rforcefield = forcefield; + *minmoves = moves; +} + +/* ---------------------------------------------------------------------- + * End of solver/generator code. + */ + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h, wh = w*h; + int tx, ty, minmoves; + unsigned char *board, *forcefield; + char *ret, *p; + int i; + + generate_board(params->w, params->h, &tx, &ty, &minmoves, rs, + &board, &forcefield, params->maxmoves); +#ifdef GENERATOR_DIAGNOSTICS + { + char *t = board_text_format(params->w, params->h, board); + printf("%s\n", t); + sfree(t); + } +#endif + + /* + * Encode as a game ID. + */ + ret = snewn(wh * 6 + 40, char); + p = ret; + i = 0; + while (i < wh) { + if (ISDIST(board[i])) { + p += sprintf(p, "d%d", board[i]); + i++; + } else { + int count = 1; + int b = board[i], f = forcefield[i]; + int c = (b == ANCHOR ? 'a' : + b == MAINANCHOR ? 'm' : + b == EMPTY ? 'e' : + /* b == WALL ? */ 'w'); + if (f) *p++ = 'f'; + *p++ = c; + i++; + while (i < wh && board[i] == b && forcefield[i] == f) + i++, count++; + if (count > 1) + p += sprintf(p, "%d", count); + } + } + p += sprintf(p, ",%d,%d,%d", tx, ty, minmoves); + ret = sresize(ret, p+1 - ret, char); + + sfree(board); + sfree(forcefield); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h, wh = w*h; + int *active, *link; + int mains = 0; + int i, tx, ty, minmoves; + char *ret; + + active = snewn(wh, int); + link = snewn(wh, int); + i = 0; + + while (*desc && *desc != ',') { + if (i >= wh) { + ret = "Too much data in game description"; + goto done; + } + link[i] = -1; + active[i] = FALSE; + if (*desc == 'f' || *desc == 'F') { + desc++; + if (!*desc) { + ret = "Expected another character after 'f' in game " + "description"; + goto done; + } + } + + if (*desc == 'd' || *desc == 'D') { + int dist; + + desc++; + if (!isdigit((unsigned char)*desc)) { + ret = "Expected a number after 'd' in game description"; + goto done; + } + dist = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + + if (dist <= 0 || dist > i) { + ret = "Out-of-range number after 'd' in game description"; + goto done; + } + + if (!active[i - dist]) { + ret = "Invalid back-reference in game description"; + goto done; + } + + link[i] = i - dist; + + active[i] = TRUE; + active[link[i]] = FALSE; + i++; + } else { + int c = *desc++; + int count = 1; + + if (!strchr("aAmMeEwW", c)) { + ret = "Invalid character in game description"; + goto done; + } + if (isdigit((unsigned char)*desc)) { + count = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + if (i + count > wh) { + ret = "Too much data in game description"; + goto done; + } + while (count-- > 0) { + active[i] = (strchr("aAmM", c) != NULL); + link[i] = -1; + if (strchr("mM", c) != NULL) { + mains++; + } + i++; + } + } + } + if (mains != 1) { + ret = (mains == 0 ? "No main piece specified in game description" : + "More than one main piece specified in game description"); + goto done; + } + if (i < wh) { + ret = "Not enough data in game description"; + goto done; + } + + /* + * Now read the target coordinates. + */ + i = sscanf(desc, ",%d,%d,%d", &tx, &ty, &minmoves); + if (i < 2) { + ret = "No target coordinates specified"; + goto done; + /* + * (but minmoves is optional) + */ + } + + ret = NULL; + + done: + sfree(active); + sfree(link); + return ret; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h, wh = w*h; + game_state *state; + int i; + + state = snew(game_state); + state->w = w; + state->h = h; + state->board = snewn(wh, unsigned char); + state->lastmoved = state->lastmoved_pos = -1; + state->movecount = 0; + state->imm = snew(struct game_immutable_state); + state->imm->refcount = 1; + state->imm->forcefield = snewn(wh, unsigned char); + + i = 0; + + while (*desc && *desc != ',') { + int f = FALSE; + + assert(i < wh); + + if (*desc == 'f') { + f = TRUE; + desc++; + assert(*desc); + } + + if (*desc == 'd' || *desc == 'D') { + int dist; + + desc++; + dist = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + + state->board[i] = DIST(dist); + state->imm->forcefield[i] = f; + + i++; + } else { + int c = *desc++; + int count = 1; + + if (isdigit((unsigned char)*desc)) { + count = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + assert(i + count <= wh); + + c = (c == 'a' || c == 'A' ? ANCHOR : + c == 'm' || c == 'M' ? MAINANCHOR : + c == 'e' || c == 'E' ? EMPTY : + /* c == 'w' || c == 'W' ? */ WALL); + + while (count-- > 0) { + state->board[i] = c; + state->imm->forcefield[i] = f; + i++; + } + } + } + + /* + * Now read the target coordinates. + */ + state->tx = state->ty = 0; + state->minmoves = -1; + i = sscanf(desc, ",%d,%d,%d", &state->tx, &state->ty, &state->minmoves); + + if (state->board[state->ty*w+state->tx] == MAINANCHOR) + state->completed = 0; /* already complete! */ + else + state->completed = -1; + + state->cheated = FALSE; + state->soln = NULL; + state->soln_index = -1; + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->w, h = state->h, wh = w*h; + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + ret->board = snewn(wh, unsigned char); + memcpy(ret->board, state->board, wh); + ret->tx = state->tx; + ret->ty = state->ty; + ret->minmoves = state->minmoves; + ret->lastmoved = state->lastmoved; + ret->lastmoved_pos = state->lastmoved_pos; + ret->movecount = state->movecount; + ret->completed = state->completed; + ret->cheated = state->cheated; + ret->imm = state->imm; + ret->imm->refcount++; + ret->soln = state->soln; + ret->soln_index = state->soln_index; + if (ret->soln) + ret->soln->refcount++; + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->imm->refcount <= 0) { + sfree(state->imm->forcefield); + sfree(state->imm); + } + if (state->soln && --state->soln->refcount <= 0) { + sfree(state->soln->moves); + sfree(state->soln); + } + sfree(state->board); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int *moves; + int nmoves; + int i; + char *ret, *p, sep; + + /* + * Run the solver and attempt to find the shortest solution + * from the current position. + */ + nmoves = solve_board(state->w, state->h, state->board, + state->imm->forcefield, state->tx, state->ty, + -1, &moves); + + if (nmoves < 0) { + *error = "Unable to find a solution to this puzzle"; + return NULL; + } + if (nmoves == 0) { + *error = "Puzzle is already solved"; + return NULL; + } + + /* + * Encode the resulting solution as a move string. + */ + ret = snewn(nmoves * 40, char); + p = ret; + sep = 'S'; + + for (i = 0; i < nmoves; i++) { + p += sprintf(p, "%c%d-%d", sep, moves[i*2], moves[i*2+1]); + sep = ','; + } + + sfree(moves); + assert(p - ret < nmoves * 40); + ret = sresize(ret, p+1 - ret, char); + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return board_text_format(state->w, state->h, state->board, + state->imm->forcefield); +} + +struct game_ui { + int dragging; + int drag_anchor; + int drag_offset_x, drag_offset_y; + int drag_currpos; + unsigned char *reachable; + int *bfs_queue; /* used as scratch in interpret_move */ +}; + +static game_ui *new_ui(const game_state *state) +{ + int w = state->w, h = state->h, wh = w*h; + game_ui *ui = snew(game_ui); + + ui->dragging = FALSE; + ui->drag_anchor = ui->drag_currpos = -1; + ui->drag_offset_x = ui->drag_offset_y = -1; + ui->reachable = snewn(wh, unsigned char); + memset(ui->reachable, 0, wh); + ui->bfs_queue = snewn(wh, int); + + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui->bfs_queue); + sfree(ui->reachable); + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE/2) +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) +#define BORDER_WIDTH (1 + TILESIZE/20) +#define HIGHLIGHT_WIDTH (1 + TILESIZE/16) + +#define FLASH_INTERVAL 0.10F +#define FLASH_TIME 3*FLASH_INTERVAL + +struct game_drawstate { + int tilesize; + int w, h; + unsigned long *grid; /* what's currently displayed */ + int started; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int w = state->w, h = state->h, wh = w*h; + int tx, ty, i, j; + int qhead, qtail; + + if (button == LEFT_BUTTON) { + tx = FROMCOORD(x); + ty = FROMCOORD(y); + + if (tx < 0 || tx >= w || ty < 0 || ty >= h || + !ISBLOCK(state->board[ty*w+tx])) + return NULL; /* this click has no effect */ + + /* + * User has clicked on a block. Find the block's anchor + * and register that we've started dragging it. + */ + i = ty*w+tx; + while (ISDIST(state->board[i])) + i -= state->board[i]; + assert(i >= 0 && i < wh); + + ui->dragging = TRUE; + ui->drag_anchor = i; + ui->drag_offset_x = tx - (i % w); + ui->drag_offset_y = ty - (i / w); + ui->drag_currpos = i; + + /* + * Now we immediately bfs out from the current location of + * the anchor, to find all the places to which this block + * can be dragged. + */ + memset(ui->reachable, FALSE, wh); + qhead = qtail = 0; + ui->reachable[i] = TRUE; + ui->bfs_queue[qtail++] = i; + for (j = i; j < wh; j++) + if (state->board[j] == DIST(j - i)) + i = j; + while (qhead < qtail) { + int pos = ui->bfs_queue[qhead++]; + int x = pos % w, y = pos / w; + int dir; + + 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 newpos; + + if (x + dx < 0 || x + dx >= w || + y + dy < 0 || y + dy >= h) + continue; + + newpos = pos + dy*w + dx; + if (ui->reachable[newpos]) + continue; /* already done this one */ + + /* + * Now search the grid to see if the block we're + * dragging could fit into this space. + */ + for (j = i; j >= 0; j = (ISDIST(state->board[j]) ? + j - state->board[j] : -1)) { + int jx = (j+pos-ui->drag_anchor) % w; + int jy = (j+pos-ui->drag_anchor) / w; + int j2; + + if (jx + dx < 0 || jx + dx >= w || + jy + dy < 0 || jy + dy >= h) + break; /* this position isn't valid at all */ + + j2 = (j+pos-ui->drag_anchor) + dy*w + dx; + + if (state->board[j2] == EMPTY && + (!state->imm->forcefield[j2] || + state->board[ui->drag_anchor] == MAINANCHOR)) + continue; + while (ISDIST(state->board[j2])) + j2 -= state->board[j2]; + assert(j2 >= 0 && j2 < wh); + if (j2 == ui->drag_anchor) + continue; + else + break; + } + + if (j < 0) { + /* + * If we got to the end of that loop without + * disqualifying this position, mark it as + * reachable for this drag. + */ + ui->reachable[newpos] = TRUE; + ui->bfs_queue[qtail++] = newpos; + } + } + } + + /* + * And that's it. Update the display to reflect the start + * of a drag. + */ + return ""; + } else if (button == LEFT_DRAG && ui->dragging) { + int dist, distlimit, dx, dy, s, px, py; + + tx = FROMCOORD(x); + ty = FROMCOORD(y); + + tx -= ui->drag_offset_x; + ty -= ui->drag_offset_y; + + /* + * Now search outwards from (tx,ty), in order of Manhattan + * distance, until we find a reachable square. + */ + distlimit = w+tx; + distlimit = max(distlimit, h+ty); + distlimit = max(distlimit, tx); + distlimit = max(distlimit, ty); + for (dist = 0; dist <= distlimit; dist++) { + for (dx = -dist; dx <= dist; dx++) + for (s = -1; s <= +1; s += 2) { + dy = s * (dist - abs(dx)); + px = tx + dx; + py = ty + dy; + if (px >= 0 && px < w && py >= 0 && py < h && + ui->reachable[py*w+px]) { + ui->drag_currpos = py*w+px; + return ""; + } + } + } + return NULL; /* give up - this drag has no effect */ + } else if (button == LEFT_RELEASE && ui->dragging) { + char data[256], *str; + + /* + * Terminate the drag, and if the piece has actually moved + * then return a move string quoting the old and new + * locations of the piece's anchor. + */ + if (ui->drag_anchor != ui->drag_currpos) { + sprintf(data, "M%d-%d", ui->drag_anchor, ui->drag_currpos); + str = dupstr(data); + } else + str = ""; /* null move; just update the UI */ + + ui->dragging = FALSE; + ui->drag_anchor = ui->drag_currpos = -1; + ui->drag_offset_x = ui->drag_offset_y = -1; + memset(ui->reachable, 0, wh); + + return str; + } else if (button == ' ' && state->soln) { + /* + * Make the next move in the stored solution. + */ + char data[256]; + int a1, a2; + + a1 = state->soln->moves[state->soln_index*2]; + a2 = state->soln->moves[state->soln_index*2+1]; + if (a1 == state->lastmoved_pos) + a1 = state->lastmoved; + + sprintf(data, "M%d-%d", a1, a2); + return dupstr(data); + } + + return NULL; +} + +static int move_piece(int w, int h, const unsigned char *src, + unsigned char *dst, unsigned char *ff, int from, int to) +{ + int wh = w*h; + int i, j; + + if (!ISANCHOR(dst[from])) + return FALSE; + + /* + * Scan to the far end of the piece's linked list. + */ + for (i = j = from; j < wh; j++) + if (src[j] == DIST(j - i)) + i = j; + + /* + * Remove the piece from its old location in the new + * game state. + */ + for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1)) + dst[j] = EMPTY; + + /* + * And put it back in at the new location. + */ + for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1)) { + int jn = j + to - from; + if (jn < 0 || jn >= wh) + return FALSE; + if (dst[jn] == EMPTY && (!ff[jn] || src[from] == MAINANCHOR)) { + dst[jn] = src[j]; + } else { + return FALSE; + } + } + + return TRUE; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->w, h = state->h /* , wh = w*h */; + char c; + int a1, a2, n, movesize; + game_state *ret = dup_game(state); + + while (*move) { + c = *move; + if (c == 'S') { + /* + * This is a solve move, so we just set up a stored + * solution path. + */ + if (ret->soln && --ret->soln->refcount <= 0) { + sfree(ret->soln->moves); + sfree(ret->soln); + } + ret->soln = snew(struct game_solution); + ret->soln->nmoves = 0; + ret->soln->moves = NULL; + ret->soln->refcount = 1; + ret->soln_index = 0; + ret->cheated = TRUE; + + movesize = 0; + move++; + while (1) { + if (sscanf(move, "%d-%d%n", &a1, &a2, &n) != 2) { + free_game(ret); + return NULL; + } + + /* + * Special case: if the first move in the solution + * involves the piece for which we already have a + * partial stored move, adjust the source point to + * the original starting point of that piece. + */ + if (ret->soln->nmoves == 0 && a1 == ret->lastmoved) + a1 = ret->lastmoved_pos; + + if (ret->soln->nmoves >= movesize) { + movesize = (ret->soln->nmoves + 48) * 4 / 3; + ret->soln->moves = sresize(ret->soln->moves, + 2*movesize, int); + } + + ret->soln->moves[2*ret->soln->nmoves] = a1; + ret->soln->moves[2*ret->soln->nmoves+1] = a2; + ret->soln->nmoves++; + move += n; + if (*move != ',') + break; + move++; /* eat comma */ + } + } else if (c == 'M') { + move++; + if (sscanf(move, "%d-%d%n", &a1, &a2, &n) != 2 || + !move_piece(w, h, state->board, ret->board, + state->imm->forcefield, a1, a2)) { + free_game(ret); + return NULL; + } + if (a1 == ret->lastmoved) { + /* + * If the player has moved the same piece as they + * moved last time, don't increment the move + * count. In fact, if they've put the piece back + * where it started from, _decrement_ the move + * count. + */ + if (a2 == ret->lastmoved_pos) { + ret->movecount--; /* reverted last move */ + ret->lastmoved = ret->lastmoved_pos = -1; + } else { + ret->lastmoved = a2; + /* don't change lastmoved_pos */ + } + } else { + ret->lastmoved = a2; + ret->lastmoved_pos = a1; + ret->movecount++; + } + + /* + * If we have a stored solution path, see if we've + * strayed from it or successfully made the next move + * along it. + */ + if (ret->soln && ret->lastmoved_pos >= 0) { + if (ret->lastmoved_pos != + ret->soln->moves[ret->soln_index*2]) { + /* strayed from the path */ + ret->soln->refcount--; + assert(ret->soln->refcount > 0); + /* `state' at least still exists */ + ret->soln = NULL; + ret->soln_index = -1; + } else if (ret->lastmoved == + ret->soln->moves[ret->soln_index*2+1]) { + /* advanced along the path */ + ret->soln_index++; + if (ret->soln_index >= ret->soln->nmoves) { + /* finished the path! */ + ret->soln->refcount--; + assert(ret->soln->refcount > 0); + /* `state' at least still exists */ + ret->soln = NULL; + ret->soln_index = -1; + } + } + } + + if (ret->board[a2] == MAINANCHOR && + a2 == ret->ty * w + ret->tx && ret->completed < 0) + ret->completed = ret->movecount; + move += n; + } else { + free_game(ret); + return NULL; + } + if (*move == ';') + move++; + else if (*move) { + free_game(ret); + return NULL; + } + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* fool the macros */ + struct dummy { int tilesize; } dummy, *ds = &dummy; + dummy.tilesize = tilesize; + + *x = params->w * TILESIZE + 2*BORDER; + *y = params->h * TILESIZE + 2*BORDER; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static void raise_colour(float *target, float *src, float *limit) +{ + int i; + for (i = 0; i < 3; i++) + target[i] = (2*src[i] + limit[i]) / 3; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + /* + * When dragging a tile, we light it up a bit. + */ + raise_colour(ret+3*COL_DRAGGING, + ret+3*COL_BACKGROUND, ret+3*COL_HIGHLIGHT); + raise_colour(ret+3*COL_DRAGGING_HIGHLIGHT, + ret+3*COL_HIGHLIGHT, ret+3*COL_HIGHLIGHT); + raise_colour(ret+3*COL_DRAGGING_LOWLIGHT, + ret+3*COL_LOWLIGHT, ret+3*COL_HIGHLIGHT); + + /* + * The main tile is tinted blue. + */ + ret[COL_MAIN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_MAIN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1]; + ret[COL_MAIN * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 2]; + game_mkhighlight_specific(fe, ret, COL_MAIN, + COL_MAIN_HIGHLIGHT, COL_MAIN_LOWLIGHT); + + /* + * And we light that up a bit too when dragging. + */ + raise_colour(ret+3*COL_MAIN_DRAGGING, + ret+3*COL_MAIN, ret+3*COL_MAIN_HIGHLIGHT); + raise_colour(ret+3*COL_MAIN_DRAGGING_HIGHLIGHT, + ret+3*COL_MAIN_HIGHLIGHT, ret+3*COL_MAIN_HIGHLIGHT); + raise_colour(ret+3*COL_MAIN_DRAGGING_LOWLIGHT, + ret+3*COL_MAIN_LOWLIGHT, ret+3*COL_MAIN_HIGHLIGHT); + + /* + * The target area on the floor is tinted green. + */ + ret[COL_TARGET * 3 + 0] = ret[COL_BACKGROUND * 3 + 0]; + ret[COL_TARGET * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 1]; + ret[COL_TARGET * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + game_mkhighlight_specific(fe, ret, COL_TARGET, + COL_TARGET_HIGHLIGHT, COL_TARGET_LOWLIGHT); + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->w, h = state->h, wh = w*h; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->w = w; + ds->h = h; + ds->started = FALSE; + ds->grid = snewn(wh, unsigned long); + for (i = 0; i < wh; i++) + ds->grid[i] = ~(unsigned long)0; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +#define BG_NORMAL 0x00000001UL +#define BG_TARGET 0x00000002UL +#define BG_FORCEFIELD 0x00000004UL +#define FLASH_LOW 0x00000008UL +#define FLASH_HIGH 0x00000010UL +#define FG_WALL 0x00000020UL +#define FG_MAIN 0x00000040UL +#define FG_NORMAL 0x00000080UL +#define FG_DRAGGING 0x00000100UL +#define FG_SHADOW 0x00000200UL +#define FG_SOLVEPIECE 0x00000400UL +#define FG_MAINPIECESH 11 +#define FG_SHADOWSH 19 + +#define PIECE_LBORDER 0x00000001UL +#define PIECE_TBORDER 0x00000002UL +#define PIECE_RBORDER 0x00000004UL +#define PIECE_BBORDER 0x00000008UL +#define PIECE_TLCORNER 0x00000010UL +#define PIECE_TRCORNER 0x00000020UL +#define PIECE_BLCORNER 0x00000040UL +#define PIECE_BRCORNER 0x00000080UL +#define PIECE_MASK 0x000000FFUL + +/* + * Utility function. + */ +#define TYPE_MASK 0xF000 +#define COL_MASK 0x0FFF +#define TYPE_RECT 0x0000 +#define TYPE_TLCIRC 0x4000 +#define TYPE_TRCIRC 0x5000 +#define TYPE_BLCIRC 0x6000 +#define TYPE_BRCIRC 0x7000 +static void maybe_rect(drawing *dr, int x, int y, int w, int h, + int coltype, int col2) +{ + int colour = coltype & COL_MASK, type = coltype & TYPE_MASK; + + if (colour > NCOLOURS) + return; + if (type == TYPE_RECT) { + draw_rect(dr, x, y, w, h, colour); + } else { + int cx, cy, r; + + clip(dr, x, y, w, h); + + cx = x; + cy = y; + r = w-1; + if (type & 0x1000) + cx += r; + if (type & 0x2000) + cy += r; + + if (col2 == -1 || col2 == coltype) { + assert(w == h); + draw_circle(dr, cx, cy, r, colour, colour); + } else { + /* + * We aim to draw a quadrant of a circle in two + * different colours. We do this using Bresenham's + * algorithm directly, because the Puzzles drawing API + * doesn't have a draw-sector primitive. + */ + int bx, by, bd, bd2; + int xm = (type & 0x1000 ? -1 : +1); + int ym = (type & 0x2000 ? -1 : +1); + + by = r; + bx = 0; + bd = 0; + while (by >= bx) { + /* + * Plot the point. + */ + { + int x1 = cx+xm*bx, y1 = cy+ym*bx; + int x2, y2; + + x2 = cx+xm*by; y2 = y1; + draw_rect(dr, min(x1,x2), min(y1,y2), + abs(x1-x2)+1, abs(y1-y2)+1, colour); + x2 = x1; y2 = cy+ym*by; + draw_rect(dr, min(x1,x2), min(y1,y2), + abs(x1-x2)+1, abs(y1-y2)+1, col2); + } + + bd += 2*bx + 1; + bd2 = bd - (2*by - 1); + if (abs(bd2) < abs(bd)) { + bd = bd2; + by--; + } + bx++; + } + } + + unclip(dr); + } +} + +static void draw_wallpart(drawing *dr, game_drawstate *ds, + int tx, int ty, unsigned long val, + int cl, int cc, int ch) +{ + int coords[6]; + + draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc); + if (val & PIECE_LBORDER) + draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, TILESIZE, + ch); + if (val & PIECE_RBORDER) + draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty, + HIGHLIGHT_WIDTH, TILESIZE, cl); + if (val & PIECE_TBORDER) + draw_rect(dr, tx, ty, TILESIZE, HIGHLIGHT_WIDTH, ch); + if (val & PIECE_BBORDER) + draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH, + TILESIZE, HIGHLIGHT_WIDTH, cl); + if (!((PIECE_BBORDER | PIECE_LBORDER) &~ val)) { + draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl); + clip(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH); + coords[0] = tx - 1; + coords[1] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[2] = tx + HIGHLIGHT_WIDTH; + coords[3] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[4] = tx - 1; + coords[5] = ty + TILESIZE; + draw_polygon(dr, coords, 3, ch, ch); + unclip(dr); + } else if (val & PIECE_BLCORNER) { + draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch); + clip(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH); + coords[0] = tx - 1; + coords[1] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[2] = tx + HIGHLIGHT_WIDTH; + coords[3] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[4] = tx - 1; + coords[5] = ty + TILESIZE; + draw_polygon(dr, coords, 3, cl, cl); + unclip(dr); + } + if (!((PIECE_TBORDER | PIECE_RBORDER) &~ val)) { + draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl); + clip(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH); + coords[0] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[1] = ty - 1; + coords[2] = tx + TILESIZE; + coords[3] = ty - 1; + coords[4] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[5] = ty + HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 3, ch, ch); + unclip(dr); + } else if (val & PIECE_TRCORNER) { + draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch); + clip(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH); + coords[0] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[1] = ty - 1; + coords[2] = tx + TILESIZE; + coords[3] = ty - 1; + coords[4] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1; + coords[5] = ty + HIGHLIGHT_WIDTH; + draw_polygon(dr, coords, 3, cl, cl); + unclip(dr); + } + if (val & PIECE_TLCORNER) + draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch); + if (val & PIECE_BRCORNER) + draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, + ty+TILESIZE-HIGHLIGHT_WIDTH, + HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl); +} + +static void draw_piecepart(drawing *dr, game_drawstate *ds, + int tx, int ty, unsigned long val, + int cl, int cc, int ch) +{ + int x[6], y[6]; + + /* + * Drawing the blocks is hellishly fiddly. The blocks don't + * stretch to the full size of the tile; there's a border + * around them of size BORDER_WIDTH. Then they have bevelled + * borders of size HIGHLIGHT_WIDTH, and also rounded corners. + * + * I tried for some time to find a clean and clever way to + * figure out what needed drawing from the corner and border + * flags, but in the end the cleanest way I could find was the + * following. We divide the grid square into 25 parts by + * ruling four horizontal and four vertical lines across it; + * those lines are at BORDER_WIDTH and BORDER_WIDTH + + * HIGHLIGHT_WIDTH from the top, from the bottom, from the + * left and from the right. Then we carefully consider each of + * the resulting 25 sections of square, and decide separately + * what needs to go in it based on the flags. In complicated + * cases there can be up to five possibilities affecting any + * given section (no corner or border flags, just the corner + * flag, one border flag, the other border flag, both border + * flags). So there's a lot of very fiddly logic here and all + * I could really think to do was give it my best shot and + * then test it and correct all the typos. Not fun to write, + * and I'm sure it isn't fun to read either, but it seems to + * work. + */ + + x[0] = tx; + x[1] = x[0] + BORDER_WIDTH; + x[2] = x[1] + HIGHLIGHT_WIDTH; + x[5] = tx + TILESIZE; + x[4] = x[5] - BORDER_WIDTH; + x[3] = x[4] - HIGHLIGHT_WIDTH; + + y[0] = ty; + y[1] = y[0] + BORDER_WIDTH; + y[2] = y[1] + HIGHLIGHT_WIDTH; + y[5] = ty + TILESIZE; + y[4] = y[5] - BORDER_WIDTH; + y[3] = y[4] - HIGHLIGHT_WIDTH; + +#define RECT(p,q) x[p], y[q], x[(p)+1]-x[p], y[(q)+1]-y[q] + + maybe_rect(dr, RECT(0,0), + (val & (PIECE_TLCORNER | PIECE_TBORDER | + PIECE_LBORDER)) ? -1 : cc, -1); + maybe_rect(dr, RECT(1,0), + (val & PIECE_TLCORNER) ? ch : (val & PIECE_TBORDER) ? -1 : + (val & PIECE_LBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(2,0), + (val & PIECE_TBORDER) ? -1 : cc, -1); + maybe_rect(dr, RECT(3,0), + (val & PIECE_TRCORNER) ? cl : (val & PIECE_TBORDER) ? -1 : + (val & PIECE_RBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(4,0), + (val & (PIECE_TRCORNER | PIECE_TBORDER | + PIECE_RBORDER)) ? -1 : cc, -1); + maybe_rect(dr, RECT(0,1), + (val & PIECE_TLCORNER) ? ch : (val & PIECE_LBORDER) ? -1 : + (val & PIECE_TBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(1,1), + (val & PIECE_TLCORNER) ? cc : -1, -1); + maybe_rect(dr, RECT(1,1), + (val & PIECE_TLCORNER) ? ch | TYPE_TLCIRC : + !((PIECE_TBORDER | PIECE_LBORDER) &~ val) ? ch | TYPE_BRCIRC : + (val & (PIECE_TBORDER | PIECE_LBORDER)) ? ch : cc, -1); + maybe_rect(dr, RECT(2,1), + (val & PIECE_TBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(3,1), + (val & PIECE_TRCORNER) ? cc : -1, -1); + maybe_rect(dr, RECT(3,1), + (val & (PIECE_TBORDER | PIECE_RBORDER)) == PIECE_TBORDER ? ch : + (val & (PIECE_TBORDER | PIECE_RBORDER)) == PIECE_RBORDER ? cl : + !((PIECE_TBORDER|PIECE_RBORDER) &~ val) ? cl | TYPE_BLCIRC : + (val & PIECE_TRCORNER) ? cl | TYPE_TRCIRC : + cc, ch); + maybe_rect(dr, RECT(4,1), + (val & PIECE_TRCORNER) ? ch : (val & PIECE_RBORDER) ? -1 : + (val & PIECE_TBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(0,2), + (val & PIECE_LBORDER) ? -1 : cc, -1); + maybe_rect(dr, RECT(1,2), + (val & PIECE_LBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(2,2), + cc, -1); + maybe_rect(dr, RECT(3,2), + (val & PIECE_RBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(4,2), + (val & PIECE_RBORDER) ? -1 : cc, -1); + maybe_rect(dr, RECT(0,3), + (val & PIECE_BLCORNER) ? cl : (val & PIECE_LBORDER) ? -1 : + (val & PIECE_BBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(1,3), + (val & PIECE_BLCORNER) ? cc : -1, -1); + maybe_rect(dr, RECT(1,3), + (val & (PIECE_BBORDER | PIECE_LBORDER)) == PIECE_BBORDER ? cl : + (val & (PIECE_BBORDER | PIECE_LBORDER)) == PIECE_LBORDER ? ch : + !((PIECE_BBORDER|PIECE_LBORDER) &~ val) ? ch | TYPE_TRCIRC : + (val & PIECE_BLCORNER) ? ch | TYPE_BLCIRC : + cc, cl); + maybe_rect(dr, RECT(2,3), + (val & PIECE_BBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(3,3), + (val & PIECE_BRCORNER) ? cc : -1, -1); + maybe_rect(dr, RECT(3,3), + (val & PIECE_BRCORNER) ? cl | TYPE_BRCIRC : + !((PIECE_BBORDER | PIECE_RBORDER) &~ val) ? cl | TYPE_TLCIRC : + (val & (PIECE_BBORDER | PIECE_RBORDER)) ? cl : cc, -1); + maybe_rect(dr, RECT(4,3), + (val & PIECE_BRCORNER) ? cl : (val & PIECE_RBORDER) ? -1 : + (val & PIECE_BBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(0,4), + (val & (PIECE_BLCORNER | PIECE_BBORDER | + PIECE_LBORDER)) ? -1 : cc, -1); + maybe_rect(dr, RECT(1,4), + (val & PIECE_BLCORNER) ? ch : (val & PIECE_BBORDER) ? -1 : + (val & PIECE_LBORDER) ? ch : cc, -1); + maybe_rect(dr, RECT(2,4), + (val & PIECE_BBORDER) ? -1 : cc, -1); + maybe_rect(dr, RECT(3,4), + (val & PIECE_BRCORNER) ? cl : (val & PIECE_BBORDER) ? -1 : + (val & PIECE_RBORDER) ? cl : cc, -1); + maybe_rect(dr, RECT(4,4), + (val & (PIECE_BRCORNER | PIECE_BBORDER | + PIECE_RBORDER)) ? -1 : cc, -1); + +#undef RECT +} + +static void draw_tile(drawing *dr, game_drawstate *ds, + int x, int y, unsigned long val) +{ + int tx = COORD(x), ty = COORD(y); + int cc, ch, cl; + + /* + * Draw the tile background. + */ + if (val & BG_TARGET) + cc = COL_TARGET; + else + cc = COL_BACKGROUND; + ch = cc+1; + cl = cc+2; + if (val & FLASH_LOW) + cc = cl; + else if (val & FLASH_HIGH) + cc = ch; + + draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc); + if (val & BG_FORCEFIELD) { + /* + * Cattle-grid effect to indicate that nothing but the + * main block can slide over this square. + */ + int n = 3 * (TILESIZE / (3*HIGHLIGHT_WIDTH)); + int i; + + for (i = 1; i < n; i += 3) { + draw_rect(dr, tx,ty+(TILESIZE*i/n), TILESIZE,HIGHLIGHT_WIDTH, cl); + draw_rect(dr, tx+(TILESIZE*i/n),ty, HIGHLIGHT_WIDTH,TILESIZE, cl); + } + } + + /* + * Draw the tile midground: a shadow of a block, for + * displaying partial solutions. + */ + if (val & FG_SHADOW) { + draw_piecepart(dr, ds, tx, ty, (val >> FG_SHADOWSH) & PIECE_MASK, + cl, cl, cl); + } + + /* + * Draw the tile foreground, i.e. some section of a block or + * wall. + */ + if (val & FG_WALL) { + cc = COL_BACKGROUND; + ch = cc+1; + cl = cc+2; + if (val & FLASH_LOW) + cc = cl; + else if (val & FLASH_HIGH) + cc = ch; + + draw_wallpart(dr, ds, tx, ty, (val >> FG_MAINPIECESH) & PIECE_MASK, + cl, cc, ch); + } else if (val & (FG_MAIN | FG_NORMAL)) { + if (val & FG_DRAGGING) + cc = (val & FG_MAIN ? COL_MAIN_DRAGGING : COL_DRAGGING); + else + cc = (val & FG_MAIN ? COL_MAIN : COL_BACKGROUND); + ch = cc+1; + cl = cc+2; + + if (val & FLASH_LOW) + cc = cl; + else if (val & (FLASH_HIGH | FG_SOLVEPIECE)) + cc = ch; + + draw_piecepart(dr, ds, tx, ty, (val >> FG_MAINPIECESH) & PIECE_MASK, + cl, cc, ch); + } + + draw_update(dr, tx, ty, TILESIZE, TILESIZE); +} + +static unsigned long find_piecepart(int w, int h, int *dsf, int x, int y) +{ + int i = y*w+x; + int canon = dsf_canonify(dsf, i); + unsigned long val = 0; + + if (x == 0 || canon != dsf_canonify(dsf, i-1)) + val |= PIECE_LBORDER; + if (y== 0 || canon != dsf_canonify(dsf, i-w)) + val |= PIECE_TBORDER; + if (x == w-1 || canon != dsf_canonify(dsf, i+1)) + val |= PIECE_RBORDER; + if (y == h-1 || canon != dsf_canonify(dsf, i+w)) + val |= PIECE_BBORDER; + if (!(val & (PIECE_TBORDER | PIECE_LBORDER)) && + canon != dsf_canonify(dsf, i-1-w)) + val |= PIECE_TLCORNER; + if (!(val & (PIECE_TBORDER | PIECE_RBORDER)) && + canon != dsf_canonify(dsf, i+1-w)) + val |= PIECE_TRCORNER; + if (!(val & (PIECE_BBORDER | PIECE_LBORDER)) && + canon != dsf_canonify(dsf, i-1+w)) + val |= PIECE_BLCORNER; + if (!(val & (PIECE_BBORDER | PIECE_RBORDER)) && + canon != dsf_canonify(dsf, i+1+w)) + val |= PIECE_BRCORNER; + return val; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->w, h = state->h, wh = w*h; + unsigned char *board; + int *dsf; + int x, y, mainanchor, mainpos, dragpos, solvepos, solvesrc, solvedst; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed + * and can vary with front ends. To be on the safe side, + * all games should start by drawing a big + * background-colour rectangle covering the whole window. + */ + draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND); + ds->started = TRUE; + } + + /* + * Construct the board we'll be displaying (which may be + * different from the one in state if ui describes a drag in + * progress). + */ + board = snewn(wh, unsigned char); + memcpy(board, state->board, wh); + if (ui->dragging) { + int mpret = move_piece(w, h, state->board, board, + state->imm->forcefield, + ui->drag_anchor, ui->drag_currpos); + assert(mpret); + } + + if (state->soln) { + solvesrc = state->soln->moves[state->soln_index*2]; + solvedst = state->soln->moves[state->soln_index*2+1]; + if (solvesrc == state->lastmoved_pos) + solvesrc = state->lastmoved; + if (solvesrc == ui->drag_anchor) + solvesrc = ui->drag_currpos; + } else + solvesrc = solvedst = -1; + + /* + * Build a dsf out of that board, so we can conveniently tell + * which edges are connected and which aren't. + */ + dsf = snew_dsf(wh); + mainanchor = -1; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int i = y*w+x; + + if (ISDIST(board[i])) + dsf_merge(dsf, i, i - board[i]); + if (board[i] == MAINANCHOR) + mainanchor = i; + if (board[i] == WALL) { + if (x > 0 && board[i-1] == WALL) + dsf_merge(dsf, i, i-1); + if (y > 0 && board[i-w] == WALL) + dsf_merge(dsf, i, i-w); + } + } + assert(mainanchor >= 0); + mainpos = dsf_canonify(dsf, mainanchor); + dragpos = ui->drag_currpos > 0 ? dsf_canonify(dsf, ui->drag_currpos) : -1; + solvepos = solvesrc >= 0 ? dsf_canonify(dsf, solvesrc) : -1; + + /* + * Now we can construct the data about what we want to draw. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int i = y*w+x; + int j; + unsigned long val; + int canon; + + /* + * See if this square is part of the target area. + */ + j = i + mainanchor - (state->ty * w + state->tx); + while (j >= 0 && j < wh && ISDIST(board[j])) + j -= board[j]; + if (j == mainanchor) + val = BG_TARGET; + else + val = BG_NORMAL; + + if (state->imm->forcefield[i]) + val |= BG_FORCEFIELD; + + if (flashtime > 0) { + int flashtype = (int)(flashtime / FLASH_INTERVAL) & 1; + val |= (flashtype ? FLASH_LOW : FLASH_HIGH); + } + + if (board[i] != EMPTY) { + canon = dsf_canonify(dsf, i); + + if (board[i] == WALL) + val |= FG_WALL; + else if (canon == mainpos) + val |= FG_MAIN; + else + val |= FG_NORMAL; + if (canon == dragpos) + val |= FG_DRAGGING; + if (canon == solvepos) + val |= FG_SOLVEPIECE; + + /* + * Now look around to see if other squares + * belonging to the same block are adjacent to us. + */ + val |= find_piecepart(w, h, dsf, x, y) << FG_MAINPIECESH; + } + + /* + * If we're in the middle of showing a solution, + * display a shadow piece for the target of the + * current move. + */ + if (solvepos >= 0) { + int si = i - solvedst + solvesrc; + if (si >= 0 && si < wh && dsf_canonify(dsf, si) == solvepos) { + val |= find_piecepart(w, h, dsf, + si % w, si / w) << FG_SHADOWSH; + val |= FG_SHADOW; + } + } + + if (val != ds->grid[i]) { + draw_tile(dr, ds, x, y, val); + ds->grid[i] = val; + } + } + + /* + * Update the status bar. + */ + { + char statusbuf[256]; + + sprintf(statusbuf, "%sMoves: %d", + (state->completed >= 0 ? + (state->cheated ? "Auto-solved. " : "COMPLETED! ") : + (state->cheated ? "Auto-solver used. " : "")), + (state->completed >= 0 ? state->completed : state->movecount)); + if (state->minmoves >= 0) + sprintf(statusbuf+strlen(statusbuf), " (min %d)", + state->minmoves); + + status_bar(dr, statusbuf); + } + + sfree(dsf); + sfree(board); +} + +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 < 0 && newstate->completed >= 0) + return FLASH_TIME; + + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame slide +#endif + +const struct game thegame = { + "Slide", NULL, NULL, + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + TRUE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +#ifdef STANDALONE_SOLVER + +#include + +int main(int argc, char **argv) +{ + game_params *p; + game_state *s; + char *id = NULL, *desc, *err; + int count = FALSE; + int ret; + int *moves; + + while (--argc > 0) { + char *p = *++argv; + /* + if (!strcmp(p, "-v")) { + verbose = TRUE; + } else + */ + if (!strcmp(p, "-c")) { + count = TRUE; + } else if (*p == '-') { + fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); + return 1; + } else { + id = p; + } + } + + if (!id) { + fprintf(stderr, "usage: %s [-c | -v] \n", argv[0]); + return 1; + } + + desc = strchr(id, ':'); + if (!desc) { + fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); + return 1; + } + *desc++ = '\0'; + + p = default_params(); + decode_params(p, id); + err = validate_desc(p, desc); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err); + return 1; + } + s = new_game(NULL, p, desc); + + ret = solve_board(s->w, s->h, s->board, s->imm->forcefield, + s->tx, s->ty, -1, &moves); + if (ret < 0) { + printf("No solution found\n"); + } else { + int index = 0; + if (count) { + printf("%d moves required\n", ret); + return 0; + } + while (1) { + int moveret; + char *text = board_text_format(s->w, s->h, s->board, + s->imm->forcefield); + game_state *s2; + + printf("position %d:\n%s", index, text); + + if (index >= ret) + break; + + s2 = dup_game(s); + moveret = move_piece(s->w, s->h, s->board, + s2->board, s->imm->forcefield, + moves[index*2], moves[index*2+1]); + assert(moveret); + + free_game(s); + s = s2; + index++; + } + } + + return 0; +} + +#endif diff --git a/apps/plugins/puzzles/unfinished/sokoban.R b/apps/plugins/puzzles/unfinished/sokoban.R new file mode 100644 index 0000000000..3b6dab56ba --- /dev/null +++ b/apps/plugins/puzzles/unfinished/sokoban.R @@ -0,0 +1,19 @@ +# -*- makefile -*- + +sokoban : [X] GTK COMMON sokoban sokoban-icon|no-icon + +sokoban : [G] WINDOWS COMMON sokoban sokoban.res? + +ALL += sokoban[COMBINED] + +!begin am gtk +GAMES += sokoban +!end + +!begin >list.c + A(sokoban) \ +!end + +!begin >gamedesc.txt +sokoban:sokoban.exe:Sokoban:Barrel-pushing puzzle:Push all the barrels into the target squares. +!end diff --git a/apps/plugins/puzzles/unfinished/sokoban.c b/apps/plugins/puzzles/unfinished/sokoban.c new file mode 100644 index 0000000000..b5533c9034 --- /dev/null +++ b/apps/plugins/puzzles/unfinished/sokoban.c @@ -0,0 +1,1479 @@ +/* + * sokoban.c: An implementation of the well-known Sokoban barrel- + * pushing game. Random generation is too simplistic to be + * credible, but the rest of the gameplay works well enough to use + * it with hand-written level descriptions. + */ + +/* + * TODO: + * + * - I think it would be better to ditch the `prev' array, and + * instead make the `dist' array strictly monotonic (by having + * each distance be something like I*A+S, where A is the grid + * area, I the number of INITIAL squares trampled on, and S the + * number of harmless spaces moved through). This would permit + * the path-tracing when a pull is actually made to choose + * randomly from all the possible shortest routes, which would + * be superior in terms of eliminating directional bias. + * + So when tracing the path back to the current px,py, we + * look at all four adjacent squares, find the minimum + * distance, check that it's _strictly smaller_ than that of + * the current square, and restrict our choice to precisely + * those squares with that minimum distance. + * + The other place `prev' is currently used is in the check + * for consistency of a pull. We would have to replace the + * check for whether prev[ny*w+nx]==oy*w+ox with a check that + * made sure there was at least one adjacent square with a + * smaller distance which _wasn't_ oy*w+ox. Then when we did + * the path-tracing we'd also have to take this special case + * into account. + * + * - More discriminating choice of pull. (Snigger.) + * + favour putting targets in clumps + * + try to shoot for a reasonably consistent number of barrels + * (adjust willingness to generate a new barrel depending on + * how many are already present) + * + adjust willingness to break new ground depending on how + * much is already broken + * + * - generation time parameters: + * + enable NetHack mode (and find a better place for the hole) + * + decide how many of the remaining Is should be walls + * + * - at the end of generation, randomly position the starting + * player coordinates, probably by (somehow) reusing the same + * bfs currently inside the loop. + * + * - possible backtracking? + * + * - IWBNI we could spot completely unreachable bits of level at + * the outside, and not bother drawing grid lines for them. The + * NH levels currently look a bit weird with grid lines on the + * outside of the boundary. + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" + +/* + * Various subsets of these constants are used during game + * generation, game play, game IDs and the game_drawstate. + */ +#define INITIAL 'i' /* used only in game generation */ +#define SPACE 's' +#define WALL 'w' +#define PIT 'p' +#define DEEP_PIT 'd' +#define TARGET 't' +#define BARREL 'b' +#define BARRELTARGET 'f' /* target is 'f'illed */ +#define PLAYER 'u' /* yo'u'; used in game IDs */ +#define PLAYERTARGET 'v' /* bad letter: v is to u as t is to s */ +#define INVALID '!' /* used in drawstate to force redraw */ +/* + * We also support the use of any capital letter as a barrel, which + * will be displayed with that letter as a label. (This facilitates + * people distributing annotated game IDs for particular Sokoban + * levels, so they can accompany them with verbal instructions + * about pushing particular barrels in particular ways.) Therefore, + * to find out whether something is a barrel, we need a test + * function which does a bit more than just comparing to BARREL. + * + * When resting on target squares, capital-letter barrels are + * replaced with their control-character value (A -> ^A). + */ +#define IS_PLAYER(c) ( (c)==PLAYER || (c)==PLAYERTARGET ) +#define IS_BARREL(c) ( (c)==BARREL || (c)==BARRELTARGET || \ + ((c)>='A' && (c)<='Z') || ((c)>=1 && (c)<=26) ) +#define IS_ON_TARGET(c) ( (c)==TARGET || (c)==BARRELTARGET || \ + (c)==PLAYERTARGET || ((c)>=1 && (c)<=26) ) +#define TARGETISE(b) ( (b)==BARREL ? BARRELTARGET : (b)-('A'-1) ) +#define DETARGETISE(b) ( (b)==BARRELTARGET ? BARREL : (b)+('A'-1) ) +#define BARREL_LABEL(b) ( (b)>='A'&&(b)<='Z' ? (b) : \ + (b)>=1 && (b)<=26 ? (b)+('A'-1) : 0 ) + +#define DX(d) (d == 0 ? -1 : d == 2 ? +1 : 0) +#define DY(d) (d == 1 ? -1 : d == 3 ? +1 : 0) + +#define FLASH_LENGTH 0.3F + +enum { + COL_BACKGROUND, + COL_TARGET, + COL_PIT, + COL_DEEP_PIT, + COL_BARREL, + COL_PLAYER, + COL_TEXT, + COL_GRID, + COL_OUTLINE, + COL_HIGHLIGHT, + COL_LOWLIGHT, + COL_WALL, + NCOLOURS +}; + +struct game_params { + int w, h; + /* + * FIXME: a parameter involving degree of filling in? + */ +}; + +struct game_state { + game_params p; + unsigned char *grid; + int px, py; + int completed; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = 12; + ret->h = 10; + + return ret; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static const struct game_params sokoban_presets[] = { + { 12, 10 }, + { 16, 12 }, + { 20, 16 }, +}; + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params p, *ret; + char *retname; + char namebuf[80]; + + if (i < 0 || i >= lenof(sokoban_presets)) + return FALSE; + + p = sokoban_presets[i]; + ret = dup_params(&p); + sprintf(namebuf, "%dx%d", ret->w, ret->h); + retname = dupstr(namebuf); + + *params = ret; + *name = retname; + return TRUE; +} + +static void decode_params(game_params *params, char const *string) +{ + params->w = params->h = atoi(string); + while (*string && isdigit((unsigned char)*string)) string++; + if (*string == 'x') { + string++; + params->h = atoi(string); + } +} + +static char *encode_params(const game_params *params, int full) +{ + char data[256]; + + sprintf(data, "%dx%d", params->w, params->h); + + return dupstr(data); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w = atoi(cfg[0].sval); + ret->h = atoi(cfg[1].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->w < 4 || params->h < 4) + return "Width and height must both be at least 4"; + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Game generation mechanism. + * + * To generate a Sokoban level, we begin with a completely blank + * grid and make valid inverse moves. Grid squares can be in a + * number of states. The states are: + * + * - INITIAL: this square has not as yet been touched by any + * inverse move, which essentially means we haven't decided what + * it is yet. + * + * - SPACE: this square is a space. + * + * - TARGET: this square is a space which is also the target for a + * barrel. + * + * - BARREL: this square contains a barrel. + * + * - BARRELTARGET: this square contains a barrel _on_ a target. + * + * - WALL: this square is a wall. + * + * - PLAYER: this square contains the player. + * + * - PLAYERTARGET: this square contains the player on a target. + * + * We begin with every square of the in state INITIAL, apart from a + * solid ring of WALLs around the edge. We randomly position the + * PLAYER somewhere. Thereafter our valid moves are: + * + * - to move the PLAYER in one direction _pulling_ a barrel after + * us. For this to work, we must have SPACE or INITIAL in the + * direction we're moving, and BARREL or BARRELTARGET in the + * direction we're moving away from. We leave SPACE or TARGET + * respectively in the vacated square. + * + * - to create a new barrel by transforming an INITIAL square into + * BARRELTARGET. + * + * - to move the PLAYER freely through SPACE and TARGET squares, + * leaving SPACE or TARGET where it started. + * + * - to move the player through INITIAL squares, carving a tunnel + * of SPACEs as it goes. + * + * We try to avoid destroying INITIAL squares wherever possible (if + * there's a path to where we want to be using only SPACE, then we + * should always use that). At the end of generation, every square + * still in state INITIAL is one which was not required at any + * point during generation, which means we can randomly choose + * whether to make it SPACE or WALL. + * + * It's unclear as yet what the right strategy for wall placement + * should be. Too few WALLs will yield many alternative solutions + * to the puzzle, whereas too many might rule out so many + * possibilities that the intended solution becomes obvious. + */ + +static void sokoban_generate(int w, int h, unsigned char *grid, int moves, + int nethack, random_state *rs) +{ + struct pull { + int ox, oy, nx, ny, score; + }; + + struct pull *pulls; + int *dist, *prev, *heap; + int x, y, px, py, i, j, d, heapsize, npulls; + + pulls = snewn(w * h * 4, struct pull); + dist = snewn(w * h, int); + prev = snewn(w * h, int); + heap = snewn(w * h, int); + + /* + * Configure the initial grid. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + grid[y*w+x] = (x == 0 || y == 0 || x == w-1 || y == h-1 ? + WALL : INITIAL); + if (nethack) + grid[1] = DEEP_PIT; + + /* + * Place the player. + */ + i = random_upto(rs, (w-2) * (h-2)); + x = 1 + i % (w-2); + y = 1 + i / (w-2); + grid[y*w+x] = SPACE; + px = x; + py = y; + + /* + * Now loop around making random inverse Sokoban moves. In this + * loop we aim to make one actual barrel-pull per iteration, + * plus as many free moves as are necessary to get into + * position for that pull. + */ + while (moves-- >= 0) { + /* + * First enumerate all the viable barrel-pulls we can + * possibly make, counting two pulls of the same barrel in + * different directions as different. We also include pulls + * we can perform by creating a new barrel. Each pull is + * marked with the amount of violence it would have to do + * to the grid. + */ + npulls = 0; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (d = 0; d < 4; d++) { + int dx = DX(d); + int dy = DY(d); + int nx = x + dx, ny = y + dy; + int npx = nx + dx, npy = ny + dy; + int score = 0; + + /* + * The candidate move is to put the player at + * (nx,ny), and move him to (npx,npy), pulling + * a barrel at (x,y) to (nx,ny). So first we + * must check that all those squares are within + * the boundaries of the grid. For this it is + * sufficient to check npx,npy. + */ + if (npx < 0 || npx >= w || npy < 0 || npy >= h) + continue; + + /* + * (x,y) must either be a barrel, or a square + * which we can convert into a barrel. + */ + switch (grid[y*w+x]) { + case BARREL: case BARRELTARGET: + break; + case INITIAL: + if (nethack) + continue; + score += 10 /* new_barrel_score */; + break; + case DEEP_PIT: + if (!nethack) + continue; + break; + default: + continue; + } + + /* + * (nx,ny) must either be a space, or a square + * which we can convert into a space. + */ + switch (grid[ny*w+nx]) { + case SPACE: case TARGET: + break; + case INITIAL: + score += 3 /* new_space_score */; + break; + default: + continue; + } + + /* + * (npx,npy) must also either be a space, or a + * square which we can convert into a space. + */ + switch (grid[npy*w+npx]) { + case SPACE: case TARGET: + break; + case INITIAL: + score += 3 /* new_space_score */; + break; + default: + continue; + } + + /* + * That's sufficient to tag this as a possible + * pull right now. We still don't know if we + * can reach the required player position, but + * that's a job for the subsequent BFS phase to + * tell us. + */ + pulls[npulls].ox = x; + pulls[npulls].oy = y; + pulls[npulls].nx = nx; + pulls[npulls].ny = ny; + pulls[npulls].score = score; +#ifdef GENERATION_DIAGNOSTICS + printf("found potential pull: (%d,%d)-(%d,%d) cost %d\n", + pulls[npulls].ox, pulls[npulls].oy, + pulls[npulls].nx, pulls[npulls].ny, + pulls[npulls].score); +#endif + npulls++; + } +#ifdef GENERATION_DIAGNOSTICS + printf("found %d potential pulls\n", npulls); +#endif + + /* + * If there are no pulls available at all, we give up. + * + * (FIXME: or perhaps backtrack?) + */ + if (npulls == 0) + break; + + /* + * Now we do a BFS from our current position, to find all + * the squares we can get the player into. + * + * This BFS is unusually tricky. We want to give a positive + * distance only to squares which we have to carve through + * INITIALs to get to, which means we can't just stick + * every square we reach on the end of our to-do list. + * Instead, we must maintain our list as a proper priority + * queue. + */ + for (i = 0; i < w*h; i++) + dist[i] = prev[i] = -1; + + heap[0] = py*w+px; + heapsize = 1; + dist[py*w+px] = 0; + +#define PARENT(n) ( ((n)-1)/2 ) +#define LCHILD(n) ( 2*(n)+1 ) +#define RCHILD(n) ( 2*(n)+2 ) +#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0) + + while (heapsize > 0) { + /* + * Pull the smallest element off the heap: it's at + * position 0. Move the arbitrary element from the very + * end of the heap into position 0. + */ + y = heap[0] / w; + x = heap[0] % w; + + heapsize--; + heap[0] = heap[heapsize]; + + /* + * Now repeatedly move that arbitrary element down the + * heap by swapping it with the more suitable of its + * children. + */ + i = 0; + while (1) { + int lc, rc; + + lc = LCHILD(i); + rc = RCHILD(i); + + if (lc >= heapsize) + break; /* we've hit bottom */ + + if (rc >= heapsize) { + /* + * Special case: there is only one child to + * check. + */ + if (dist[heap[i]] > dist[heap[lc]]) + SWAP(heap[i], heap[lc]); + + /* _Now_ we've hit bottom. */ + break; + } else { + /* + * The common case: there are two children and + * we must check them both. + */ + if (dist[heap[i]] > dist[heap[lc]] || + dist[heap[i]] > dist[heap[rc]]) { + /* + * Pick the more appropriate child to swap with + * (i.e. the one which would want to be the + * parent if one were above the other - as one + * is about to be). + */ + if (dist[heap[lc]] > dist[heap[rc]]) { + SWAP(heap[i], heap[rc]); + i = rc; + } else { + SWAP(heap[i], heap[lc]); + i = lc; + } + } else { + /* This element is in the right place; we're done. */ + break; + } + } + } + + /* + * OK, that's given us (x,y) for this phase of the + * search. Now try all directions from here. + */ + + for (d = 0; d < 4; d++) { + int dx = DX(d); + int dy = DY(d); + int nx = x + dx, ny = y + dy; + if (nx < 0 || nx >= w || ny < 0 || ny >= h) + continue; + if (grid[ny*w+nx] != SPACE && grid[ny*w+nx] != TARGET && + grid[ny*w+nx] != INITIAL) + continue; + if (dist[ny*w+nx] == -1) { + dist[ny*w+nx] = dist[y*w+x] + (grid[ny*w+nx] == INITIAL); + prev[ny*w+nx] = y*w+x; + + /* + * Now insert ny*w+nx at the end of the heap, + * and move it down to its appropriate resting + * place. + */ + i = heapsize; + heap[heapsize++] = ny*w+nx; + + /* + * Swap element n with its parent repeatedly to + * preserve the heap property. + */ + + while (i > 0) { + int p = PARENT(i); + + if (dist[heap[p]] > dist[heap[i]]) { + SWAP(heap[p], heap[i]); + i = p; + } else + break; + } + } + } + } + +#undef PARENT +#undef LCHILD +#undef RCHILD +#undef SWAP + +#ifdef GENERATION_DIAGNOSTICS + printf("distance map:\n"); + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + int d = dist[i*w+j]; + int c; + if (d < 0) + c = '#'; + else if (d >= 36) + c = '!'; + else if (d >= 10) + c = 'A' - 10 + d; + else + c = '0' + d; + putchar(c); + } + putchar('\n'); + } +#endif + + /* + * Now we can go back through the `pulls' array, adjusting + * the score for each pull depending on how hard it is to + * reach its starting point, and also throwing out any + * whose starting points are genuinely unreachable even + * with the possibility of carving through INITIAL squares. + */ + for (i = j = 0; i < npulls; i++) { +#ifdef GENERATION_DIAGNOSTICS + printf("potential pull (%d,%d)-(%d,%d)", + pulls[i].ox, pulls[i].oy, + pulls[i].nx, pulls[i].ny); +#endif + x = pulls[i].nx; + y = pulls[i].ny; + if (dist[y*w+x] < 0) { +#ifdef GENERATION_DIAGNOSTICS + printf(" unreachable\n"); +#endif + continue; /* this pull isn't feasible at all */ + } else { + /* + * Another nasty special case we have to check is + * whether the initial barrel location (ox,oy) is + * on the path used to reach the square. This can + * occur if that square is in state INITIAL: the + * pull is initially considered valid on the basis + * that the INITIAL can become BARRELTARGET, and + * it's also considered reachable on the basis that + * INITIAL can be turned into SPACE, but it can't + * be both at once. + * + * Fortunately, if (ox,oy) is on the path at all, + * it must be only one space from the end, so this + * is easy to spot and rule out. + */ + if (prev[y*w+x] == pulls[i].oy*w+pulls[i].ox) { +#ifdef GENERATION_DIAGNOSTICS + printf(" goes through itself\n"); +#endif + continue; /* this pull isn't feasible at all */ + } + pulls[j] = pulls[i]; /* structure copy */ + pulls[j].score += dist[y*w+x] * 3 /* new_space_score */; +#ifdef GENERATION_DIAGNOSTICS + printf(" reachable at distance %d (cost now %d)\n", + dist[y*w+x], pulls[j].score); +#endif + j++; + } + } + npulls = j; + + /* + * Again, if there are no pulls available at all, we give + * up. + * + * (FIXME: or perhaps backtrack?) + */ + if (npulls == 0) + break; + + /* + * Now choose which pull to make. On the one hand we should + * prefer pulls which do less damage to the INITIAL squares + * (thus, ones for which we can already get into position + * via existing SPACEs, and for which the barrel already + * exists and doesn't have to be invented); on the other, + * we want to avoid _always_ preferring such pulls, on the + * grounds that that will lead to levels without very much + * stuff in. + * + * When creating new barrels, we prefer creations which are + * next to existing TARGET squares. + * + * FIXME: for the moment I'll make this very simple indeed. + */ + i = random_upto(rs, npulls); + + /* + * Actually make the pull, including carving a path to get + * to the site if necessary. + */ + x = pulls[i].nx; + y = pulls[i].ny; + while (prev[y*w+x] >= 0) { + int p; + + if (grid[y*w+x] == INITIAL) + grid[y*w+x] = SPACE; + + p = prev[y*w+x]; + y = p / w; + x = p % w; + } + px = 2*pulls[i].nx - pulls[i].ox; + py = 2*pulls[i].ny - pulls[i].oy; + if (grid[py*w+px] == INITIAL) + grid[py*w+px] = SPACE; + if (grid[pulls[i].ny*w+pulls[i].nx] == TARGET) + grid[pulls[i].ny*w+pulls[i].nx] = BARRELTARGET; + else + grid[pulls[i].ny*w+pulls[i].nx] = BARREL; + if (grid[pulls[i].oy*w+pulls[i].ox] == BARREL) + grid[pulls[i].oy*w+pulls[i].ox] = SPACE; + else if (grid[pulls[i].oy*w+pulls[i].ox] != DEEP_PIT) + grid[pulls[i].oy*w+pulls[i].ox] = TARGET; + } + + sfree(heap); + sfree(prev); + sfree(dist); + sfree(pulls); + + if (grid[py*w+px] == TARGET) + grid[py*w+px] = PLAYERTARGET; + else + grid[py*w+px] = PLAYER; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int w = params->w, h = params->h; + char *desc; + int desclen, descpos, descsize, prev, count; + unsigned char *grid; + int i, j; + + /* + * FIXME: perhaps some more interesting means of choosing how + * many moves to try? + */ + grid = snewn(w*h, unsigned char); + sokoban_generate(w, h, grid, w*h, FALSE, rs); + + desclen = descpos = descsize = 0; + desc = NULL; + prev = -1; + count = 0; + for (i = 0; i < w*h; i++) { + if (descsize < desclen + 40) { + descsize = desclen + 100; + desc = sresize(desc, descsize, char); + desc[desclen] = '\0'; + } + switch (grid[i]) { + case INITIAL: + j = 'w'; /* FIXME: make some of these 's'? */ + break; + case SPACE: + j = 's'; + break; + case WALL: + j = 'w'; + break; + case TARGET: + j = 't'; + break; + case BARREL: + j = 'b'; + break; + case BARRELTARGET: + j = 'f'; + break; + case DEEP_PIT: + j = 'd'; + break; + case PLAYER: + j = 'u'; + break; + case PLAYERTARGET: + j = 'v'; + break; + default: + j = '?'; + break; + } + assert(j != '?'); + if (j != prev) { + desc[desclen++] = j; + descpos = desclen; + prev = j; + count = 1; + } else { + count++; + desclen = descpos + sprintf(desc+descpos, "%d", count); + } + } + + sfree(grid); + + return desc; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w = params->w, h = params->h; + int area = 0; + int nplayers = 0; + + while (*desc) { + int c = *desc++; + int n = 1; + if (*desc && isdigit((unsigned char)*desc)) { + n = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + + area += n; + + if (c == PLAYER || c == PLAYERTARGET) + nplayers += n; + else if (c == INITIAL || c == SPACE || c == WALL || c == TARGET || + c == PIT || c == DEEP_PIT || IS_BARREL(c)) + /* ok */; + else + return "Invalid character in game description"; + } + + if (area > w*h) + return "Too much data in game description"; + if (area < w*h) + return "Too little data in game description"; + if (nplayers < 1) + return "No starting player position specified"; + if (nplayers > 1) + return "More than one starting player position specified"; + + return NULL; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w = params->w, h = params->h; + game_state *state = snew(game_state); + int i; + + state->p = *params; /* structure copy */ + state->grid = snewn(w*h, unsigned char); + state->px = state->py = -1; + state->completed = FALSE; + + i = 0; + + while (*desc) { + int c = *desc++; + int n = 1; + if (*desc && isdigit((unsigned char)*desc)) { + n = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) desc++; + } + + if (c == PLAYER || c == PLAYERTARGET) { + state->py = i / w; + state->px = i % w; + c = IS_ON_TARGET(c) ? TARGET : SPACE; + } + + while (n-- > 0) + state->grid[i++] = c; + } + + assert(i == w*h); + assert(state->px != -1 && state->py != -1); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w = state->p.w, h = state->p.h; + game_state *ret = snew(game_state); + + ret->p = state->p; /* structure copy */ + ret->grid = snewn(w*h, unsigned char); + memcpy(ret->grid, state->grid, w*h); + ret->px = state->px; + ret->py = state->py; + ret->completed = state->completed; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + return NULL; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + 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 { + game_params p; + int tilesize; + int started; + unsigned short *grid; +}; + +#define PREFERRED_TILESIZE 32 +#define TILESIZE (ds->tilesize) +#define BORDER (TILESIZE) +#define HIGHLIGHT_WIDTH (TILESIZE / 10) +#define COORD(x) ( (x) * TILESIZE + BORDER ) +#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 ) + +/* + * I'm going to need to do most of the move-type analysis in both + * interpret_move and execute_move, so I'll abstract it out into a + * subfunction. move_type() returns -1 for an illegal move, 0 for a + * movement, and 1 for a push. + */ +int move_type(game_state *state, int dx, int dy) +{ + int w = state->p.w, h = state->p.h; + int px = state->px, py = state->py; + int nx, ny, nbx, nby; + + assert(dx >= -1 && dx <= +1); + assert(dy >= -1 && dy <= +1); + assert(dx || dy); + + nx = px + dx; + ny = py + dy; + + /* + * Disallow any move that goes off the grid. + */ + if (nx < 0 || nx >= w || ny < 0 || ny >= h) + return -1; + + /* + * Examine the target square of the move to see whether it's a + * space, a barrel, or a wall. + */ + + if (state->grid[ny*w+nx] == WALL || + state->grid[ny*w+nx] == PIT || + state->grid[ny*w+nx] == DEEP_PIT) + return -1; /* this one's easy; just disallow it */ + + if (IS_BARREL(state->grid[ny*w+nx])) { + /* + * This is a push move. For a start, that means it must not + * be diagonal. + */ + if (dy && dx) + return -1; + + /* + * Now find the location of the third square involved in + * the push, and stop if it's off the edge. + */ + nbx = nx + dx; + nby = ny + dy; + if (nbx < 0 || nbx >= w || nby < 0 || nby >= h) + return -1; + + /* + * That third square must be able to accept a barrel. + */ + if (state->grid[nby*w+nbx] == SPACE || + state->grid[nby*w+nbx] == TARGET || + state->grid[nby*w+nbx] == PIT || + state->grid[nby*w+nbx] == DEEP_PIT) { + /* + * The push is valid. + */ + return 1; + } else { + return -1; + } + } else { + /* + * This is just an ordinary move. We've already checked the + * target square, so the only thing left to check is that a + * diagonal move has a space on one side to have notionally + * gone through. + */ + if (dx && dy && + state->grid[(py+dy)*w+px] != SPACE && + state->grid[(py+dy)*w+px] != TARGET && + state->grid[py*w+(px+dx)] != SPACE && + state->grid[py*w+(px+dx)] != TARGET) + return -1; + + /* + * Otherwise, the move is valid. + */ + return 0; + } +} + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int dx=0, dy=0; + char *move; + + /* + * Diagonal movement is supported as it is in NetHack: it's + * for movement only (never pushing), and one of the two + * squares adjacent to both the source and destination + * squares must be free to move through. In other words, it + * is only a shorthand for two orthogonal moves and cannot + * change the nature of the actual puzzle game. + */ + if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8')) + dx = 0, dy = -1; + else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2')) + dx = 0, dy = +1; + else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4')) + dx = -1, dy = 0; + else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6')) + dx = +1, dy = 0; + else if (button == (MOD_NUM_KEYPAD | '7')) + dx = -1, dy = -1; + else if (button == (MOD_NUM_KEYPAD | '9')) + dx = +1, dy = -1; + else if (button == (MOD_NUM_KEYPAD | '1')) + dx = -1, dy = +1; + else if (button == (MOD_NUM_KEYPAD | '3')) + dx = +1, dy = +1; + else if (button == LEFT_BUTTON) + { + if(x < COORD(state->px)) + dx = -1; + else if (x > COORD(state->px + 1)) + dx = 1; + if(y < COORD(state->py)) + dy = -1; + else if (y > COORD(state->py + 1)) + dy = 1; + } + else + return NULL; + + if((dx == 0) && (dy == 0)) + return(NULL); + + if (move_type(state, dx, dy) < 0) + return NULL; + + move = snewn(2, char); + move[1] = '\0'; + move[0] = '5' - 3*dy + dx; + return move; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w = state->p.w, h = state->p.h; + int px = state->px, py = state->py; + int dx, dy, nx, ny, nbx, nby, type, m, i, freebarrels, freetargets; + game_state *ret; + + if (*move < '1' || *move == '5' || *move > '9' || move[1]) + return NULL; /* invalid move string */ + + m = *move - '0'; + dx = (m + 2) % 3 - 1; + dy = 2 - (m + 2) / 3; + type = move_type(state, dx, dy); + if (type < 0) + return NULL; + + ret = dup_game(state); + + nx = px + dx; + ny = py + dy; + nbx = nx + dx; + nby = ny + dy; + + if (type) { + int b; + + /* + * Push. + */ + b = ret->grid[ny*w+nx]; + if (IS_ON_TARGET(b)) { + ret->grid[ny*w+nx] = TARGET; + b = DETARGETISE(b); + } else + ret->grid[ny*w+nx] = SPACE; + + if (ret->grid[nby*w+nbx] == PIT) + ret->grid[nby*w+nbx] = SPACE; + else if (ret->grid[nby*w+nbx] == DEEP_PIT) + /* do nothing - the pit eats the barrel and remains there */; + else if (ret->grid[nby*w+nbx] == TARGET) + ret->grid[nby*w+nbx] = TARGETISE(b); + else + ret->grid[nby*w+nbx] = b; + } + + ret->px = nx; + ret->py = ny; + + /* + * Check for completion. This is surprisingly complicated, + * given the presence of pits and deep pits, and also the fact + * that some Sokoban levels with pits have fewer pits than + * barrels (due to providing spares, e.g. NetHack's). I think + * the completion condition in fact must be that the game + * cannot become any _more_ complete. That is, _either_ there + * are no remaining barrels not on targets, _or_ there is a + * good reason why any such barrels cannot be placed. The only + * available good reason is that there are no remaining pits, + * no free target squares, and no deep pits at all. + */ + if (!ret->completed) { + freebarrels = FALSE; + freetargets = FALSE; + for (i = 0; i < w*h; i++) { + int v = ret->grid[i]; + + if (IS_BARREL(v) && !IS_ON_TARGET(v)) + freebarrels = TRUE; + if (v == DEEP_PIT || v == PIT || + (!IS_BARREL(v) && IS_ON_TARGET(v))) + freetargets = TRUE; + } + + if (!freebarrels || !freetargets) + ret->completed = TRUE; + } + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + struct { int tilesize; } ads, *ds = &ads; + ads.tilesize = tilesize; + + *x = 2 * BORDER + 1 + params->w * TILESIZE; + *y = 2 * BORDER + 1 + params->h * TILESIZE; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT); + + ret[COL_OUTLINE * 3 + 0] = 0.0F; + ret[COL_OUTLINE * 3 + 1] = 0.0F; + ret[COL_OUTLINE * 3 + 2] = 0.0F; + + ret[COL_PLAYER * 3 + 0] = 0.0F; + ret[COL_PLAYER * 3 + 1] = 1.0F; + ret[COL_PLAYER * 3 + 2] = 0.0F; + + ret[COL_BARREL * 3 + 0] = 0.6F; + ret[COL_BARREL * 3 + 1] = 0.3F; + ret[COL_BARREL * 3 + 2] = 0.0F; + + ret[COL_TARGET * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0]; + ret[COL_TARGET * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1]; + ret[COL_TARGET * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2]; + + ret[COL_PIT * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0] / 2; + ret[COL_PIT * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1] / 2; + ret[COL_PIT * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2] / 2; + + ret[COL_DEEP_PIT * 3 + 0] = 0.0F; + ret[COL_DEEP_PIT * 3 + 1] = 0.0F; + ret[COL_DEEP_PIT * 3 + 2] = 0.0F; + + ret[COL_TEXT * 3 + 0] = 1.0F; + ret[COL_TEXT * 3 + 1] = 1.0F; + ret[COL_TEXT * 3 + 2] = 1.0F; + + ret[COL_GRID * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0]; + ret[COL_GRID * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1]; + ret[COL_GRID * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2]; + + ret[COL_OUTLINE * 3 + 0] = 0.0F; + ret[COL_OUTLINE * 3 + 1] = 0.0F; + ret[COL_OUTLINE * 3 + 2] = 0.0F; + + for (i = 0; i < 3; i++) { + ret[COL_WALL * 3 + i] = (3 * ret[COL_BACKGROUND * 3 + i] + + 1 * ret[COL_HIGHLIGHT * 3 + i]) / 4; + } + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + int w = state->p.w, h = state->p.h; + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->p = state->p; /* structure copy */ + ds->grid = snewn(w*h, unsigned short); + for (i = 0; i < w*h; i++) + ds->grid[i] = INVALID; + ds->started = FALSE; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->grid); + sfree(ds); +} + +static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v) +{ + int tx = COORD(x), ty = COORD(y); + int bg = (v & 0x100 ? COL_HIGHLIGHT : COL_BACKGROUND); + + v &= 0xFF; + + clip(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1); + draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, bg); + + if (v == WALL) { + int coords[6]; + + coords[0] = tx + TILESIZE; + coords[1] = ty + TILESIZE; + coords[2] = tx + TILESIZE; + coords[3] = ty + 1; + coords[4] = tx + 1; + coords[5] = ty + TILESIZE; + draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT); + + coords[0] = tx + 1; + coords[1] = ty + 1; + draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); + + draw_rect(dr, tx + 1 + HIGHLIGHT_WIDTH, ty + 1 + HIGHLIGHT_WIDTH, + TILESIZE - 2*HIGHLIGHT_WIDTH, + TILESIZE - 2*HIGHLIGHT_WIDTH, COL_WALL); + } else if (v == PIT) { + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE*3/7, COL_PIT, COL_OUTLINE); + } else if (v == DEEP_PIT) { + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE*3/7, COL_DEEP_PIT, COL_OUTLINE); + } else { + if (IS_ON_TARGET(v)) { + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE*3/7, COL_TARGET, COL_OUTLINE); + } + if (IS_PLAYER(v)) { + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE/3, COL_PLAYER, COL_OUTLINE); + } else if (IS_BARREL(v)) { + char str[2]; + + draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, + TILESIZE/3, COL_BARREL, COL_OUTLINE); + str[1] = '\0'; + str[0] = BARREL_LABEL(v); + if (str[0]) { + draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, + FONT_VARIABLE, TILESIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, COL_TEXT, str); + } + } + } + + unclip(dr); + draw_update(dr, tx, ty, TILESIZE, TILESIZE); +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w = state->p.w, h = state->p.h /*, wh = w*h */; + int x, y; + int flashtype; + + if (flashtime && + !((int)(flashtime * 3 / FLASH_LENGTH) % 2)) + flashtype = 0x100; + else + flashtype = 0; + + /* + * Initialise a fresh drawstate. + */ + if (!ds->started) { + int wid, ht; + + /* + * Blank out the window initially. + */ + game_compute_size(&ds->p, TILESIZE, &wid, &ht); + draw_rect(dr, 0, 0, wid, ht, COL_BACKGROUND); + draw_update(dr, 0, 0, wid, ht); + + /* + * Draw the grid lines. + */ + for (y = 0; y <= h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), + COL_LOWLIGHT); + for (x = 0; x <= w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), + COL_LOWLIGHT); + + ds->started = TRUE; + } + + /* + * Draw the grid contents. + */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int v = state->grid[y*w+x]; + if (y == state->py && x == state->px) { + if (v == TARGET) + v = PLAYERTARGET; + else { + assert(v == SPACE); + v = PLAYER; + } + } + + v |= flashtype; + + if (ds->grid[y*w+x] != v) { + draw_tile(dr, ds, x, y, v); + ds->grid[y*w+x] = v; + } + } + +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed) + return FLASH_LENGTH; + else + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame sokoban +#endif + +const struct game thegame = { + "Sokoban", NULL, NULL, + 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_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; diff --git a/apps/plugins/puzzles/unruly.R b/apps/plugins/puzzles/unruly.R new file mode 100644 index 0000000000..064ccc35ba --- /dev/null +++ b/apps/plugins/puzzles/unruly.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +unruly : [X] GTK COMMON unruly unruly-icon|no-icon +unruly : [G] WINDOWS COMMON unruly unruly.res|noicon.res + +unrulysolver : [U] unruly[STANDALONE_SOLVER] STANDALONE +unrulysolver : [C] unruly[STANDALONE_SOLVER] STANDALONE + +ALL += unruly[COMBINED] + +!begin am gtk +GAMES += unruly +!end + +!begin >list.c + A(unruly) \ +!end + +!begin >gamedesc.txt +unruly:unruly.exe:Unruly:Black and white grid puzzle:Fill in the black and white grid to avoid runs of three. +!end diff --git a/apps/plugins/puzzles/unruly.c b/apps/plugins/puzzles/unruly.c new file mode 100644 index 0000000000..aea4cdf789 --- /dev/null +++ b/apps/plugins/puzzles/unruly.c @@ -0,0 +1,2071 @@ +/* + * unruly.c: Implementation for Binary Puzzles. + * (C) 2012 Lennard Sprong + * Created for Simon Tatham's Portable Puzzle Collection + * See LICENCE for licence details + * + * Objective of the game: Fill the grid with zeros and ones, with the + * following rules: + * - There can't be a run of three or more equal numbers. + * - Each row and column contains an equal amount of zeros and ones. + * + * This puzzle type is known under several names, including + * Tohu-Wa-Vohu, One and Two and Binairo. + * + * Some variants include an extra constraint, stating that no two rows or two + * columns may contain the same exact sequence of zeros and ones. + * This rule is rarely used, so it is not enabled in the default presets + * (but it can be selected via the Custom configurer). + * + * More information: + * http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm + */ + +/* + * Possible future improvements: + * + * More solver cleverness + * + * - a counting-based deduction in which you find groups of squares + * which must each contain at least one of a given colour, plus + * other squares which are already known to be that colour, and see + * if you have any squares left over when you've worked out where + * they all have to be. This is a generalisation of the current + * check_near_complete: where that only covers rows with three + * unfilled squares, this would handle more, such as + * 0 . . 1 0 1 . . 0 . + * in which each of the two-square gaps must contain a 0, and there + * are three 0s placed, and that means the rightmost square can't + * be a 0. + * + * - an 'Unreasonable' difficulty level, supporting recursion and + * backtracking. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" + +#ifdef STANDALONE_SOLVER +int solver_verbose = FALSE; +#endif + +enum { + COL_BACKGROUND, + COL_GRID, + COL_EMPTY, + /* + * When editing this enum, maintain the invariants + * COL_n_HIGHLIGHT = COL_n + 1 + * COL_n_LOWLIGHT = COL_n + 2 + */ + COL_0, + COL_0_HIGHLIGHT, + COL_0_LOWLIGHT, + COL_1, + COL_1_HIGHLIGHT, + COL_1_LOWLIGHT, + COL_CURSOR, + COL_ERROR, + NCOLOURS +}; + +struct game_params { + int w2, h2; /* full grid width and height respectively */ + int unique; /* should row and column patterns be unique? */ + int diff; +}; +#define DIFFLIST(A) \ + A(EASY,Easy, e) \ + A(NORMAL,Normal, n) \ + +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const unruly_diffnames[] = { DIFFLIST(TITLE) }; + +static char const unruly_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +const static struct game_params unruly_presets[] = { + { 8, 8, FALSE, DIFF_EASY}, + { 8, 8, FALSE, DIFF_NORMAL}, + {10, 10, FALSE, DIFF_EASY}, + {10, 10, FALSE, DIFF_NORMAL}, + {14, 14, FALSE, DIFF_EASY}, + {14, 14, FALSE, DIFF_NORMAL} +}; + +#define DEFAULT_PRESET 0 + +enum { + EMPTY, + N_ONE, + N_ZERO, + BOGUS +}; + +#define FE_HOR_ROW_LEFT 0x0001 +#define FE_HOR_ROW_MID 0x0003 +#define FE_HOR_ROW_RIGHT 0x0002 + +#define FE_VER_ROW_TOP 0x0004 +#define FE_VER_ROW_MID 0x000C +#define FE_VER_ROW_BOTTOM 0x0008 + +#define FE_COUNT 0x0010 + +#define FE_ROW_MATCH 0x0020 +#define FE_COL_MATCH 0x0040 + +#define FF_ONE 0x0080 +#define FF_ZERO 0x0100 +#define FF_CURSOR 0x0200 + +#define FF_FLASH1 0x0400 +#define FF_FLASH2 0x0800 +#define FF_IMMUTABLE 0x1000 + +struct game_state { + int w2, h2; + int unique; + char *grid; + unsigned char *immutable; + + int completed, cheated; +}; + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + *ret = unruly_presets[DEFAULT_PRESET]; /* structure copy */ + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char buf[80]; + + if (i < 0 || i >= lenof(unruly_presets)) + return FALSE; + + ret = snew(game_params); + *ret = unruly_presets[i]; /* structure copy */ + + sprintf(buf, "%dx%d %s", ret->w2, ret->h2, unruly_diffnames[ret->diff]); + + *name = dupstr(buf); + *params = ret; + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + char const *p = string; + + params->unique = FALSE; + + params->w2 = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == 'x') { + p++; + params->h2 = atoi(p); + while (*p && isdigit((unsigned char)*p)) p++; + } else { + params->h2 = params->w2; + } + + if (*p == 'u') { + p++; + params->unique = TRUE; + } + + if (*p == 'd') { + int i; + p++; + params->diff = DIFFCOUNT + 1; /* ...which is invalid */ + if (*p) { + for (i = 0; i < DIFFCOUNT; i++) { + if (*p == unruly_diffchars[i]) + params->diff = i; + } + p++; + } + } +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[80]; + + sprintf(buf, "%dx%d", params->w2, params->h2); + if (params->unique) + strcat(buf, "u"); + if (full) + sprintf(buf + strlen(buf), "d%c", unruly_diffchars[params->diff]); + + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(5, config_item); + + ret[0].name = "Width"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->w2); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = "Height"; + ret[1].type = C_STRING; + sprintf(buf, "%d", params->h2); + ret[1].sval = dupstr(buf); + ret[1].ival = 0; + + ret[2].name = "Unique rows and columns"; + ret[2].type = C_BOOLEAN; + ret[2].ival = params->unique; + + ret[3].name = "Difficulty"; + ret[3].type = C_CHOICES; + ret[3].sval = DIFFCONFIG; + ret[3].ival = params->diff; + + ret[4].name = NULL; + ret[4].type = C_END; + ret[4].sval = NULL; + ret[4].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->w2 = atoi(cfg[0].sval); + ret->h2 = atoi(cfg[1].sval); + ret->unique = cfg[2].ival; + ret->diff = cfg[3].ival; + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if ((params->w2 & 1) || (params->h2 & 1)) + return "Width and height must both be even"; + if (params->w2 < 6 || params->h2 < 6) + return "Width and height must be at least 6"; + if (params->unique) { + static const long A177790[] = { + /* + * The nth element of this array gives the number of + * distinct possible Unruly rows of length 2n (that is, + * containing exactly n 1s and n 0s and not containing + * three consecutive elements the same) for as long as + * those numbers fit in a 32-bit signed int. + * + * So in unique-rows mode, if the puzzle width is 2n, then + * the height must be at most (this array)[n], and vice + * versa. + * + * This is sequence A177790 in the Online Encyclopedia of + * Integer Sequences: http://oeis.org/A177790 + */ + 1L, 2L, 6L, 14L, 34L, 84L, 208L, 518L, 1296L, 3254L, + 8196L, 20700L, 52404L, 132942L, 337878L, 860142L, + 2192902L, 5598144L, 14308378L, 36610970L, 93770358L, + 240390602L, 616787116L, 1583765724L + }; + if (params->w2 < 2*lenof(A177790) && + params->h2 > A177790[params->w2/2]) { + return "Puzzle is too tall for unique-rows mode"; + } + if (params->h2 < 2*lenof(A177790) && + params->w2 > A177790[params->h2/2]) { + return "Puzzle is too long for unique-rows mode"; + } + } + if (params->diff >= DIFFCOUNT) + return "Unknown difficulty rating"; + + return NULL; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int w2 = params->w2, h2 = params->h2; + int s = w2 * h2; + + const char *p = desc; + int pos = 0; + + while (*p) { + if (*p >= 'a' && *p < 'z') + pos += 1 + (*p - 'a'); + else if (*p >= 'A' && *p < 'Z') + pos += 1 + (*p - 'A'); + else if (*p == 'Z' || *p == 'z') + pos += 25; + else + return "Description contains invalid characters"; + + ++p; + } + + if (pos < s+1) + return "Description too short"; + if (pos > s+1) + return "Description too long"; + + return NULL; +} + +static game_state *blank_state(int w2, int h2, int unique) +{ + game_state *state = snew(game_state); + int s = w2 * h2; + + state->w2 = w2; + state->h2 = h2; + state->unique = unique; + state->grid = snewn(s, char); + state->immutable = snewn(s, unsigned char); + + memset(state->grid, EMPTY, s); + memset(state->immutable, FALSE, s); + + state->completed = state->cheated = FALSE; + + return state; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int w2 = params->w2, h2 = params->h2; + int s = w2 * h2; + + game_state *state = blank_state(w2, h2, params->unique); + + const char *p = desc; + int pos = 0; + + while (*p) { + if (*p >= 'a' && *p < 'z') { + pos += (*p - 'a'); + if (pos < s) { + state->grid[pos] = N_ZERO; + state->immutable[pos] = TRUE; + } + pos++; + } else if (*p >= 'A' && *p < 'Z') { + pos += (*p - 'A'); + if (pos < s) { + state->grid[pos] = N_ONE; + state->immutable[pos] = TRUE; + } + pos++; + } else if (*p == 'Z' || *p == 'z') { + pos += 25; + } else + assert(!"Description contains invalid characters"); + + ++p; + } + assert(pos == s+1); + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int w2 = state->w2, h2 = state->h2; + int s = w2 * h2; + + game_state *ret = blank_state(w2, h2, state->unique); + + memcpy(ret->grid, state->grid, s); + memcpy(ret->immutable, state->immutable, s); + + ret->completed = state->completed; + ret->cheated = state->cheated; + + return ret; +} + +static void free_game(game_state *state) +{ + sfree(state->grid); + sfree(state->immutable); + + sfree(state); +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + int w2 = state->w2, h2 = state->h2; + int lr = w2*2 + 1; + + char *ret = snewn(lr * h2 + 1, char); + char *p = ret; + + int x, y; + for (y = 0; y < h2; y++) { + for (x = 0; x < w2; x++) { + /* Place number */ + char c = state->grid[y * w2 + x]; + *p++ = (c == N_ONE ? '1' : c == N_ZERO ? '0' : '.'); + *p++ = ' '; + } + /* End line */ + *p++ = '\n'; + } + /* End with NUL */ + *p++ = '\0'; + + return ret; +} + +/* ****** * + * Solver * + * ****** */ + +struct unruly_scratch { + int *ones_rows; + int *ones_cols; + int *zeros_rows; + int *zeros_cols; +}; + +static void unruly_solver_update_remaining(const game_state *state, + struct unruly_scratch *scratch) +{ + int w2 = state->w2, h2 = state->h2; + int x, y; + + /* Reset all scratch data */ + memset(scratch->ones_rows, 0, h2 * sizeof(int)); + memset(scratch->ones_cols, 0, w2 * sizeof(int)); + memset(scratch->zeros_rows, 0, h2 * sizeof(int)); + memset(scratch->zeros_cols, 0, w2 * sizeof(int)); + + for (x = 0; x < w2; x++) + for (y = 0; y < h2; y++) { + if (state->grid[y * w2 + x] == N_ONE) { + scratch->ones_rows[y]++; + scratch->ones_cols[x]++; + } else if (state->grid[y * w2 + x] == N_ZERO) { + scratch->zeros_rows[y]++; + scratch->zeros_cols[x]++; + } + } +} + +static struct unruly_scratch *unruly_new_scratch(const game_state *state) +{ + int w2 = state->w2, h2 = state->h2; + + struct unruly_scratch *ret = snew(struct unruly_scratch); + + ret->ones_rows = snewn(h2, int); + ret->ones_cols = snewn(w2, int); + ret->zeros_rows = snewn(h2, int); + ret->zeros_cols = snewn(w2, int); + + unruly_solver_update_remaining(state, ret); + + return ret; +} + +static void unruly_free_scratch(struct unruly_scratch *scratch) +{ + sfree(scratch->ones_rows); + sfree(scratch->ones_cols); + sfree(scratch->zeros_rows); + sfree(scratch->zeros_cols); + + sfree(scratch); +} + +static int unruly_solver_check_threes(game_state *state, int *rowcount, + int *colcount, int horizontal, + char check, char block) +{ + int w2 = state->w2, h2 = state->h2; + + int dx = horizontal ? 1 : 0, dy = 1 - dx; + int sx = dx, sy = dy; + int ex = w2 - dx, ey = h2 - dy; + + int x, y; + int ret = 0; + + /* Check for any three squares which almost form three in a row */ + for (y = sy; y < ey; y++) { + for (x = sx; x < ex; x++) { + int i1 = (y-dy) * w2 + (x-dx); + int i2 = y * w2 + x; + int i3 = (y+dy) * w2 + (x+dx); + + if (state->grid[i1] == check && state->grid[i2] == check + && state->grid[i3] == EMPTY) { + ret++; +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n", + i1 % w2, i1 / w2, i2 % w2, i2 / w2, + (block == N_ONE ? '1' : '0'), i3 % w2, + i3 / w2); + } +#endif + state->grid[i3] = block; + rowcount[i3 / w2]++; + colcount[i3 % w2]++; + } + if (state->grid[i1] == check && state->grid[i2] == EMPTY + && state->grid[i3] == check) { + ret++; +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n", + i1 % w2, i1 / w2, i3 % w2, i3 / w2, + (block == N_ONE ? '1' : '0'), i2 % w2, + i2 / w2); + } +#endif + state->grid[i2] = block; + rowcount[i2 / w2]++; + colcount[i2 % w2]++; + } + if (state->grid[i1] == EMPTY && state->grid[i2] == check + && state->grid[i3] == check) { + ret++; +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n", + i2 % w2, i2 / w2, i3 % w2, i3 / w2, + (block == N_ONE ? '1' : '0'), i1 % w2, + i1 / w2); + } +#endif + state->grid[i1] = block; + rowcount[i1 / w2]++; + colcount[i1 % w2]++; + } + } + } + + return ret; +} + +static int unruly_solver_check_all_threes(game_state *state, + struct unruly_scratch *scratch) +{ + int ret = 0; + + ret += + unruly_solver_check_threes(state, scratch->zeros_rows, + scratch->zeros_cols, TRUE, N_ONE, N_ZERO); + ret += + unruly_solver_check_threes(state, scratch->ones_rows, + scratch->ones_cols, TRUE, N_ZERO, N_ONE); + ret += + unruly_solver_check_threes(state, scratch->zeros_rows, + scratch->zeros_cols, FALSE, N_ONE, + N_ZERO); + ret += + unruly_solver_check_threes(state, scratch->ones_rows, + scratch->ones_cols, FALSE, N_ZERO, N_ONE); + + return ret; +} + +static int unruly_solver_check_uniques(game_state *state, int *rowcount, + int horizontal, char check, char block, + struct unruly_scratch *scratch) +{ + int w2 = state->w2, h2 = state->h2; + + int rmult = (horizontal ? w2 : 1); + int cmult = (horizontal ? 1 : w2); + int nr = (horizontal ? h2 : w2); + int nc = (horizontal ? w2 : h2); + int max = nc / 2; + + int r, r2, c; + int ret = 0; + + /* + * Find each row that has max entries of type 'check', and see if + * all those entries match those in any row with max-1 entries. If + * so, set the last non-matching entry of the latter row to ensure + * that it's different. + */ + for (r = 0; r < nr; r++) { + if (rowcount[r] != max) + continue; + for (r2 = 0; r2 < nr; r2++) { + int nmatch = 0, nonmatch = -1; + if (rowcount[r2] != max-1) + continue; + for (c = 0; c < nc; c++) { + if (state->grid[r*rmult + c*cmult] == check) { + if (state->grid[r2*rmult + c*cmult] == check) + nmatch++; + else + nonmatch = c; + } + } + if (nmatch == max-1) { + int i1 = r2 * rmult + nonmatch * cmult; + assert(nonmatch != -1); + if (state->grid[i1] == block) + continue; + assert(state->grid[i1] == EMPTY); +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: matching %s %i, %i gives %c at %i,%i\n", + horizontal ? "rows" : "cols", + r, r2, (block == N_ONE ? '1' : '0'), i1 % w2, + i1 / w2); + } +#endif + state->grid[i1] = block; + if (block == N_ONE) { + scratch->ones_rows[i1 / w2]++; + scratch->ones_cols[i1 % w2]++; + } else { + scratch->zeros_rows[i1 / w2]++; + scratch->zeros_cols[i1 % w2]++; + } + ret++; + } + } + } + return ret; +} + +static int unruly_solver_check_all_uniques(game_state *state, + struct unruly_scratch *scratch) +{ + int ret = 0; + + ret += unruly_solver_check_uniques(state, scratch->ones_rows, + TRUE, N_ONE, N_ZERO, scratch); + ret += unruly_solver_check_uniques(state, scratch->zeros_rows, + TRUE, N_ZERO, N_ONE, scratch); + ret += unruly_solver_check_uniques(state, scratch->ones_cols, + FALSE, N_ONE, N_ZERO, scratch); + ret += unruly_solver_check_uniques(state, scratch->zeros_cols, + FALSE, N_ZERO, N_ONE, scratch); + + return ret; +} + +static int unruly_solver_fill_row(game_state *state, int i, int horizontal, + int *rowcount, int *colcount, char fill) +{ + int ret = 0; + int w2 = state->w2, h2 = state->h2; + int j; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Filling %s %i with %c:", + (horizontal ? "Row" : "Col"), i, + (fill == N_ZERO ? '0' : '1')); + } +#endif + /* Place a number in every empty square in a row/column */ + for (j = 0; j < (horizontal ? w2 : h2); j++) { + int p = (horizontal ? i * w2 + j : j * w2 + i); + + if (state->grid[p] == EMPTY) { +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf(" (%i,%i)", (horizontal ? j : i), + (horizontal ? i : j)); + } +#endif + ret++; + state->grid[p] = fill; + rowcount[(horizontal ? i : j)]++; + colcount[(horizontal ? j : i)]++; + } + } + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("\n"); + } +#endif + + return ret; +} + +static int unruly_solver_check_complete_nums(game_state *state, + int *complete, int horizontal, + int *rowcount, int *colcount, + char fill) +{ + int w2 = state->w2, h2 = state->h2; + int count = (horizontal ? h2 : w2); /* number of rows to check */ + int target = (horizontal ? w2 : h2) / 2; /* target number of 0s/1s */ + int *other = (horizontal ? rowcount : colcount); + + int ret = 0; + + int i; + /* Check for completed rows/cols for one number, then fill in the rest */ + for (i = 0; i < count; i++) { + if (complete[i] == target && other[i] < target) { +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Row %i satisfied for %c\n", i, + (fill != N_ZERO ? '0' : '1')); + } +#endif + ret += unruly_solver_fill_row(state, i, horizontal, rowcount, + colcount, fill); + } + } + + return ret; +} + +static int unruly_solver_check_all_complete_nums(game_state *state, + struct unruly_scratch *scratch) +{ + int ret = 0; + + ret += + unruly_solver_check_complete_nums(state, scratch->ones_rows, TRUE, + scratch->zeros_rows, + scratch->zeros_cols, N_ZERO); + ret += + unruly_solver_check_complete_nums(state, scratch->ones_cols, FALSE, + scratch->zeros_rows, + scratch->zeros_cols, N_ZERO); + ret += + unruly_solver_check_complete_nums(state, scratch->zeros_rows, TRUE, + scratch->ones_rows, + scratch->ones_cols, N_ONE); + ret += + unruly_solver_check_complete_nums(state, scratch->zeros_cols, FALSE, + scratch->ones_rows, + scratch->ones_cols, N_ONE); + + return ret; +} + +static int unruly_solver_check_near_complete(game_state *state, + int *complete, int horizontal, + int *rowcount, int *colcount, + char fill) +{ + int w2 = state->w2, h2 = state->h2; + int w = w2/2, h = h2/2; + + int dx = horizontal ? 1 : 0, dy = 1 - dx; + + int sx = dx, sy = dy; + int ex = w2 - dx, ey = h2 - dy; + + int x, y; + int ret = 0; + + /* + * This function checks for a row with one Y remaining, then looks + * for positions that could cause the remaining squares in the row + * to make 3 X's in a row. Example: + * + * Consider the following row: + * 1 1 0 . . . + * If the last 1 was placed in the last square, the remaining + * squares would be 0: + * 1 1 0 0 0 1 + * This violates the 3 in a row rule. We now know that the last 1 + * shouldn't be in the last cell. + * 1 1 0 . . 0 + */ + + /* Check for any two blank and one filled square */ + for (y = sy; y < ey; y++) { + /* One type must have 1 remaining, the other at least 2 */ + if (horizontal && (complete[y] < w - 1 || rowcount[y] > w - 2)) + continue; + + for (x = sx; x < ex; x++) { + int i, i1, i2, i3; + if (!horizontal + && (complete[x] < h - 1 || colcount[x] > h - 2)) + continue; + + i = (horizontal ? y : x); + i1 = (y-dy) * w2 + (x-dx); + i2 = y * w2 + x; + i3 = (y+dy) * w2 + (x+dx); + + if (state->grid[i1] == fill && state->grid[i2] == EMPTY + && state->grid[i3] == EMPTY) { + /* + * Temporarily fill the empty spaces with something else. + * This avoids raising the counts for the row and column + */ + state->grid[i2] = BOGUS; + state->grid[i3] = BOGUS; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Row %i nearly satisfied for %c\n", i, + (fill != N_ZERO ? '0' : '1')); + } +#endif + ret += + unruly_solver_fill_row(state, i, horizontal, rowcount, + colcount, fill); + + state->grid[i2] = EMPTY; + state->grid[i3] = EMPTY; + } + + else if (state->grid[i1] == EMPTY && state->grid[i2] == fill + && state->grid[i3] == EMPTY) { + state->grid[i1] = BOGUS; + state->grid[i3] = BOGUS; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Row %i nearly satisfied for %c\n", i, + (fill != N_ZERO ? '0' : '1')); + } +#endif + ret += + unruly_solver_fill_row(state, i, horizontal, rowcount, + colcount, fill); + + state->grid[i1] = EMPTY; + state->grid[i3] = EMPTY; + } + + else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY + && state->grid[i3] == fill) { + state->grid[i1] = BOGUS; + state->grid[i2] = BOGUS; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Row %i nearly satisfied for %c\n", i, + (fill != N_ZERO ? '0' : '1')); + } +#endif + ret += + unruly_solver_fill_row(state, i, horizontal, rowcount, + colcount, fill); + + state->grid[i1] = EMPTY; + state->grid[i2] = EMPTY; + } + + else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY + && state->grid[i3] == EMPTY) { + state->grid[i1] = BOGUS; + state->grid[i2] = BOGUS; + state->grid[i3] = BOGUS; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Solver: Row %i nearly satisfied for %c\n", i, + (fill != N_ZERO ? '0' : '1')); + } +#endif + ret += + unruly_solver_fill_row(state, i, horizontal, rowcount, + colcount, fill); + + state->grid[i1] = EMPTY; + state->grid[i2] = EMPTY; + state->grid[i3] = EMPTY; + } + } + } + + return ret; +} + +static int unruly_solver_check_all_near_complete(game_state *state, + struct unruly_scratch *scratch) +{ + int ret = 0; + + ret += + unruly_solver_check_near_complete(state, scratch->ones_rows, TRUE, + scratch->zeros_rows, + scratch->zeros_cols, N_ZERO); + ret += + unruly_solver_check_near_complete(state, scratch->ones_cols, FALSE, + scratch->zeros_rows, + scratch->zeros_cols, N_ZERO); + ret += + unruly_solver_check_near_complete(state, scratch->zeros_rows, TRUE, + scratch->ones_rows, + scratch->ones_cols, N_ONE); + ret += + unruly_solver_check_near_complete(state, scratch->zeros_cols, FALSE, + scratch->ones_rows, + scratch->ones_cols, N_ONE); + + return ret; +} + +static int unruly_validate_rows(const game_state *state, int horizontal, + char check, int *errors) +{ + int w2 = state->w2, h2 = state->h2; + + int dx = horizontal ? 1 : 0, dy = 1 - dx; + + int sx = dx, sy = dy; + int ex = w2 - dx, ey = h2 - dy; + + int x, y; + int ret = 0; + + int err1 = (horizontal ? FE_HOR_ROW_LEFT : FE_VER_ROW_TOP); + int err2 = (horizontal ? FE_HOR_ROW_MID : FE_VER_ROW_MID); + int err3 = (horizontal ? FE_HOR_ROW_RIGHT : FE_VER_ROW_BOTTOM); + + /* Check for any three in a row, and mark errors accordingly (if + * required) */ + for (y = sy; y < ey; y++) { + for (x = sx; x < ex; x++) { + int i1 = (y-dy) * w2 + (x-dx); + int i2 = y * w2 + x; + int i3 = (y+dy) * w2 + (x+dx); + + if (state->grid[i1] == check && state->grid[i2] == check + && state->grid[i3] == check) { + ret++; + if (errors) { + errors[i1] |= err1; + errors[i2] |= err2; + errors[i3] |= err3; + } + } + } + } + + return ret; +} + +static int unruly_validate_unique(const game_state *state, int horizontal, + int *errors) +{ + int w2 = state->w2, h2 = state->h2; + + int rmult = (horizontal ? w2 : 1); + int cmult = (horizontal ? 1 : w2); + int nr = (horizontal ? h2 : w2); + int nc = (horizontal ? w2 : h2); + int err = (horizontal ? FE_ROW_MATCH : FE_COL_MATCH); + + int r, r2, c; + int ret = 0; + + /* Check for any two full rows matching exactly, and mark errors + * accordingly (if required) */ + for (r = 0; r < nr; r++) { + int nfull = 0; + for (c = 0; c < nc; c++) + if (state->grid[r*rmult + c*cmult] != EMPTY) + nfull++; + if (nfull != nc) + continue; + for (r2 = r+1; r2 < nr; r2++) { + int match = TRUE; + for (c = 0; c < nc; c++) + if (state->grid[r*rmult + c*cmult] != + state->grid[r2*rmult + c*cmult]) + match = FALSE; + if (match) { + if (errors) { + for (c = 0; c < nc; c++) { + errors[r*rmult + c*cmult] |= err; + errors[r2*rmult + c*cmult] |= err; + } + } + ret++; + } + } + } + + return ret; +} + +static int unruly_validate_all_rows(const game_state *state, int *errors) +{ + int errcount = 0; + + errcount += unruly_validate_rows(state, TRUE, N_ONE, errors); + errcount += unruly_validate_rows(state, FALSE, N_ONE, errors); + errcount += unruly_validate_rows(state, TRUE, N_ZERO, errors); + errcount += unruly_validate_rows(state, FALSE, N_ZERO, errors); + + if (state->unique) { + errcount += unruly_validate_unique(state, TRUE, errors); + errcount += unruly_validate_unique(state, FALSE, errors); + } + + if (errcount) + return -1; + return 0; +} + +static int unruly_validate_counts(const game_state *state, + struct unruly_scratch *scratch, int *errors) +{ + int w2 = state->w2, h2 = state->h2; + int w = w2/2, h = h2/2; + char below = FALSE; + char above = FALSE; + int i; + + /* See if all rows/columns are satisfied. If one is exceeded, + * mark it as an error (if required) + */ + + char hasscratch = TRUE; + if (!scratch) { + scratch = unruly_new_scratch(state); + hasscratch = FALSE; + } + + for (i = 0; i < w2; i++) { + if (scratch->ones_cols[i] < h) + below = TRUE; + if (scratch->zeros_cols[i] < h) + below = TRUE; + + if (scratch->ones_cols[i] > h) { + above = TRUE; + if (errors) + errors[2*h2 + i] = TRUE; + } else if (errors) + errors[2*h2 + i] = FALSE; + + if (scratch->zeros_cols[i] > h) { + above = TRUE; + if (errors) + errors[2*h2 + w2 + i] = TRUE; + } else if (errors) + errors[2*h2 + w2 + i] = FALSE; + } + for (i = 0; i < h2; i++) { + if (scratch->ones_rows[i] < w) + below = TRUE; + if (scratch->zeros_rows[i] < w) + below = TRUE; + + if (scratch->ones_rows[i] > w) { + above = TRUE; + if (errors) + errors[i] = TRUE; + } else if (errors) + errors[i] = FALSE; + + if (scratch->zeros_rows[i] > w) { + above = TRUE; + if (errors) + errors[h2 + i] = TRUE; + } else if (errors) + errors[h2 + i] = FALSE; + } + + if (!hasscratch) + unruly_free_scratch(scratch); + + return (above ? -1 : below ? 1 : 0); +} + +static int unruly_solve_game(game_state *state, + struct unruly_scratch *scratch, int diff) +{ + int done, maxdiff = -1; + + while (TRUE) { + done = 0; + + /* Check for impending 3's */ + done += unruly_solver_check_all_threes(state, scratch); + + /* Keep using the simpler techniques while they produce results */ + if (done) { + if (maxdiff < DIFF_EASY) + maxdiff = DIFF_EASY; + continue; + } + + /* Check for completed rows */ + done += unruly_solver_check_all_complete_nums(state, scratch); + + if (done) { + if (maxdiff < DIFF_EASY) + maxdiff = DIFF_EASY; + continue; + } + + /* Check for impending failures of row/column uniqueness, if + * it's enabled in this game mode */ + if (state->unique) { + done += unruly_solver_check_all_uniques(state, scratch); + + if (done) { + if (maxdiff < DIFF_EASY) + maxdiff = DIFF_EASY; + continue; + } + } + + /* Normal techniques */ + if (diff < DIFF_NORMAL) + break; + + /* Check for nearly completed rows */ + done += unruly_solver_check_all_near_complete(state, scratch); + + if (done) { + if (maxdiff < DIFF_NORMAL) + maxdiff = DIFF_NORMAL; + continue; + } + + break; + } + return maxdiff; +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + game_state *solved = dup_game(state); + struct unruly_scratch *scratch = unruly_new_scratch(solved); + char *ret = NULL; + int result; + + unruly_solve_game(solved, scratch, DIFFCOUNT); + + result = unruly_validate_counts(solved, scratch, NULL); + if (unruly_validate_all_rows(solved, NULL) == -1) + result = -1; + + if (result == 0) { + int w2 = solved->w2, h2 = solved->h2; + int s = w2 * h2; + char *p; + int i; + + ret = snewn(s + 2, char); + p = ret; + *p++ = 'S'; + + for (i = 0; i < s; i++) + *p++ = (solved->grid[i] == N_ONE ? '1' : '0'); + + *p++ = '\0'; + } else if (result == 1) + *error = "No solution found."; + else if (result == -1) + *error = "Puzzle is invalid."; + + free_game(solved); + unruly_free_scratch(scratch); + return ret; +} + +/* ********* * + * Generator * + * ********* */ + +static int unruly_fill_game(game_state *state, struct unruly_scratch *scratch, + random_state *rs) +{ + + int w2 = state->w2, h2 = state->h2; + int s = w2 * h2; + int i, j; + int *spaces; + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Generator: Attempt to fill grid\n"); + } +#endif + + /* Generate random array of spaces */ + spaces = snewn(s, int); + for (i = 0; i < s; i++) + spaces[i] = i; + shuffle(spaces, s, sizeof(*spaces), rs); + + /* + * Construct a valid filled grid by repeatedly picking an unfilled + * space and fill it, then calling the solver to fill in any + * spaces forced by the change. + */ + for (j = 0; j < s; j++) { + i = spaces[j]; + + if (state->grid[i] != EMPTY) + continue; + + if (random_upto(rs, 2)) { + state->grid[i] = N_ONE; + scratch->ones_rows[i / w2]++; + scratch->ones_cols[i % w2]++; + } else { + state->grid[i] = N_ZERO; + scratch->zeros_rows[i / w2]++; + scratch->zeros_cols[i % w2]++; + } + + unruly_solve_game(state, scratch, DIFFCOUNT); + } + sfree(spaces); + + if (unruly_validate_all_rows(state, NULL) != 0 + || unruly_validate_counts(state, scratch, NULL) != 0) + return FALSE; + + return TRUE; +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ +#ifdef STANDALONE_SOLVER + char *debug; + int temp_verbose = FALSE; +#endif + + int w2 = params->w2, h2 = params->h2; + int s = w2 * h2; + int *spaces; + int i, j, run; + char *ret, *p; + + game_state *state; + struct unruly_scratch *scratch; + + int attempts = 0; + + while (1) { + + while (TRUE) { + attempts++; + state = blank_state(w2, h2, params->unique); + scratch = unruly_new_scratch(state); + if (unruly_fill_game(state, scratch, rs)) + break; + free_game(state); + unruly_free_scratch(scratch); + } + +#ifdef STANDALONE_SOLVER + if (solver_verbose) { + printf("Puzzle generated in %i attempts\n", attempts); + debug = game_text_format(state); + fputs(debug, stdout); + sfree(debug); + + temp_verbose = solver_verbose; + solver_verbose = FALSE; + } +#endif + + unruly_free_scratch(scratch); + + /* Generate random array of spaces */ + spaces = snewn(s, int); + for (i = 0; i < s; i++) + spaces[i] = i; + shuffle(spaces, s, sizeof(*spaces), rs); + + /* + * Winnow the clues by starting from our filled grid, repeatedly + * picking a filled space and emptying it, as long as the solver + * reports that the puzzle can still be solved after doing so. + */ + for (j = 0; j < s; j++) { + char c; + game_state *solver; + + i = spaces[j]; + + c = state->grid[i]; + state->grid[i] = EMPTY; + + solver = dup_game(state); + scratch = unruly_new_scratch(state); + + unruly_solve_game(solver, scratch, params->diff); + + if (unruly_validate_counts(solver, scratch, NULL) != 0) + state->grid[i] = c; + + free_game(solver); + unruly_free_scratch(scratch); + } + sfree(spaces); + +#ifdef STANDALONE_SOLVER + if (temp_verbose) { + solver_verbose = TRUE; + + printf("Final puzzle:\n"); + debug = game_text_format(state); + fputs(debug, stdout); + sfree(debug); + } +#endif + + /* + * See if the game has accidentally come out too easy. + */ + if (params->diff > 0) { + int ok; + game_state *solver; + + solver = dup_game(state); + scratch = unruly_new_scratch(state); + + unruly_solve_game(solver, scratch, params->diff - 1); + + ok = unruly_validate_counts(solver, scratch, NULL); + + free_game(solver); + unruly_free_scratch(scratch); + + if (ok) + break; + } else { + /* + * Puzzles of the easiest difficulty can't be too easy. + */ + break; + } + } + + /* Encode description */ + ret = snewn(s + 1, char); + p = ret; + run = 0; + for (i = 0; i < s+1; i++) { + if (i == s || state->grid[i] == N_ZERO) { + while (run > 24) { + *p++ = 'z'; + run -= 25; + } + *p++ = 'a' + run; + run = 0; + } else if (state->grid[i] == N_ONE) { + while (run > 24) { + *p++ = 'Z'; + run -= 25; + } + *p++ = 'A' + run; + run = 0; + } else { + run++; + } + } + *p = '\0'; + + free_game(state); + + return ret; +} + +/* ************** * + * User Interface * + * ************** */ + +struct game_ui { + int cx, cy; + char cursor; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ret = snew(game_ui); + + ret->cx = ret->cy = 0; + ret->cursor = FALSE; + + return ret; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ +} + +struct game_drawstate { + int tilesize; + int w2, h2; + int started; + + int *gridfs; + int *rowfs; + + int *grid; +}; + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + int w2 = state->w2, h2 = state->h2; + int s = w2 * h2; + int i; + + ds->tilesize = 0; + ds->w2 = w2; + ds->h2 = h2; + ds->started = FALSE; + + ds->gridfs = snewn(s, int); + ds->rowfs = snewn(2 * (w2 + h2), int); + + ds->grid = snewn(s, int); + for (i = 0; i < s; i++) + ds->grid[i] = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->gridfs); + sfree(ds->rowfs); + sfree(ds->grid); + sfree(ds); +} + +#define COORD(x) ( (x) * ds->tilesize + ds->tilesize/2 ) +#define FROMCOORD(x) ( ((x)-(ds->tilesize/2)) / ds->tilesize ) + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int ox, int oy, int button) +{ + int hx = ui->cx; + int hy = ui->cy; + + int gx = FROMCOORD(ox); + int gy = FROMCOORD(oy); + + int w2 = state->w2, h2 = state->h2; + + button &= ~MOD_MASK; + + /* Mouse click */ + if (button == LEFT_BUTTON || button == RIGHT_BUTTON || + button == MIDDLE_BUTTON) { + if (ox >= (ds->tilesize / 2) && gx < w2 + && oy >= (ds->tilesize / 2) && gy < h2) { + hx = gx; + hy = gy; + ui->cursor = FALSE; + } else + return NULL; + } + + /* Keyboard move */ + if (IS_CURSOR_MOVE(button)) { + move_cursor(button, &ui->cx, &ui->cy, w2, h2, 0); + ui->cursor = TRUE; + return ""; + } + + /* Place one */ + if ((ui->cursor && (button == CURSOR_SELECT || button == CURSOR_SELECT2 + || button == '\b' || button == '0' || button == '1' + || button == '2')) || + button == LEFT_BUTTON || button == RIGHT_BUTTON || + button == MIDDLE_BUTTON) { + char buf[80]; + char c, i; + + if (state->immutable[hy * w2 + hx]) + return NULL; + + c = '-'; + i = state->grid[hy * w2 + hx]; + + if (button == '0' || button == '2') + c = '0'; + else if (button == '1') + c = '1'; + else if (button == MIDDLE_BUTTON) + c = '-'; + + /* Cycle through options */ + else if (button == CURSOR_SELECT2 || button == RIGHT_BUTTON) + c = (i == EMPTY ? '0' : i == N_ZERO ? '1' : '-'); + else if (button == CURSOR_SELECT || button == LEFT_BUTTON) + c = (i == EMPTY ? '1' : i == N_ONE ? '0' : '-'); + + if (state->grid[hy * w2 + hx] == + (c == '0' ? N_ZERO : c == '1' ? N_ONE : EMPTY)) + return NULL; /* don't put no-ops on the undo chain */ + + sprintf(buf, "P%c,%d,%d", c, hx, hy); + + return dupstr(buf); + } + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int w2 = state->w2, h2 = state->h2; + int s = w2 * h2; + int x, y, i; + char c; + + game_state *ret; + + if (move[0] == 'S') { + const char *p; + + ret = dup_game(state); + p = move + 1; + + for (i = 0; i < s; i++) { + + if (!*p || !(*p == '1' || *p == '0')) { + free_game(ret); + return NULL; + } + + ret->grid[i] = (*p == '1' ? N_ONE : N_ZERO); + p++; + } + + ret->completed = ret->cheated = TRUE; + return ret; + } else if (move[0] == 'P' + && sscanf(move + 1, "%c,%d,%d", &c, &x, &y) == 3 && x >= 0 + && x < w2 && y >= 0 && y < h2 && (c == '-' || c == '0' + || c == '1')) { + ret = dup_game(state); + i = y * w2 + x; + + if (state->immutable[i]) { + free_game(ret); + return NULL; + } + + ret->grid[i] = (c == '1' ? N_ONE : c == '0' ? N_ZERO : EMPTY); + + if (!ret->completed && unruly_validate_counts(ret, NULL, NULL) == 0 + && (unruly_validate_all_rows(ret, NULL) == 0)) + ret->completed = TRUE; + + return ret; + } + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = tilesize * (params->w2 + 1); + *y = tilesize * (params->h2 + 1); +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + int i; + + frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); + + for (i = 0; i < 3; i++) { + ret[COL_1 * 3 + i] = 0.2F; + ret[COL_1_HIGHLIGHT * 3 + i] = 0.4F; + ret[COL_1_LOWLIGHT * 3 + i] = 0.0F; + ret[COL_0 * 3 + i] = 0.95F; + ret[COL_0_HIGHLIGHT * 3 + i] = 1.0F; + ret[COL_0_LOWLIGHT * 3 + i] = 0.9F; + ret[COL_EMPTY * 3 + i] = 0.5F; + ret[COL_GRID * 3 + i] = 0.3F; + } + game_mkhighlight_specific(fe, ret, COL_0, COL_0_HIGHLIGHT, COL_0_LOWLIGHT); + game_mkhighlight_specific(fe, ret, COL_1, COL_1_HIGHLIGHT, COL_1_LOWLIGHT); + + ret[COL_ERROR * 3 + 0] = 1.0F; + ret[COL_ERROR * 3 + 1] = 0.0F; + ret[COL_ERROR * 3 + 2] = 0.0F; + + ret[COL_CURSOR * 3 + 0] = 0.0F; + ret[COL_CURSOR * 3 + 1] = 0.7F; + ret[COL_CURSOR * 3 + 2] = 0.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static void unruly_draw_err_rectangle(drawing *dr, int x, int y, int w, int h, + int tilesize) +{ + double thick = tilesize / 10; + double margin = tilesize / 20; + + draw_rect(dr, x+margin, y+margin, w-2*margin, thick, COL_ERROR); + draw_rect(dr, x+margin, y+margin, thick, h-2*margin, COL_ERROR); + draw_rect(dr, x+margin, y+h-margin-thick, w-2*margin, thick, COL_ERROR); + draw_rect(dr, x+w-margin-thick, y+margin, thick, h-2*margin, COL_ERROR); +} + +static void unruly_draw_tile(drawing *dr, int x, int y, int tilesize, int tile) +{ + clip(dr, x, y, tilesize, tilesize); + + /* Draw the grid edge first, so the tile can overwrite it */ + draw_rect(dr, x, y, tilesize, tilesize, COL_GRID); + + /* Background of the tile */ + { + int val = (tile & FF_ZERO ? 0 : tile & FF_ONE ? 2 : 1); + val = (val == 0 ? COL_0 : val == 2 ? COL_1 : COL_EMPTY); + + if ((tile & (FF_FLASH1 | FF_FLASH2)) && + (val == COL_0 || val == COL_1)) { + val += (tile & FF_FLASH1 ? 1 : 2); + } + + draw_rect(dr, x, y, tilesize-1, tilesize-1, val); + + if ((val == COL_0 || val == COL_1) && (tile & FF_IMMUTABLE)) { + draw_rect(dr, x + tilesize/6, y + tilesize/6, + tilesize - 2*(tilesize/6) - 2, 1, val + 2); + draw_rect(dr, x + tilesize/6, y + tilesize/6, + 1, tilesize - 2*(tilesize/6) - 2, val + 2); + draw_rect(dr, x + tilesize/6 + 1, y + tilesize - tilesize/6 - 2, + tilesize - 2*(tilesize/6) - 2, 1, val + 1); + draw_rect(dr, x + tilesize - tilesize/6 - 2, y + tilesize/6 + 1, + 1, tilesize - 2*(tilesize/6) - 2, val + 1); + } + } + + /* 3-in-a-row errors */ + if (tile & (FE_HOR_ROW_LEFT | FE_HOR_ROW_RIGHT)) { + int left = x, right = x + tilesize - 1; + if ((tile & FE_HOR_ROW_LEFT)) + right += tilesize/2; + if ((tile & FE_HOR_ROW_RIGHT)) + left -= tilesize/2; + unruly_draw_err_rectangle(dr, left, y, right-left, tilesize-1, tilesize); + } + if (tile & (FE_VER_ROW_TOP | FE_VER_ROW_BOTTOM)) { + int top = y, bottom = y + tilesize - 1; + if ((tile & FE_VER_ROW_TOP)) + bottom += tilesize/2; + if ((tile & FE_VER_ROW_BOTTOM)) + top -= tilesize/2; + unruly_draw_err_rectangle(dr, x, top, tilesize-1, bottom-top, tilesize); + } + + /* Count errors */ + if (tile & FE_COUNT) { + draw_text(dr, x + tilesize/2, y + tilesize/2, FONT_VARIABLE, + tilesize/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_ERROR, "!"); + } + + /* Row-match errors */ + if (tile & FE_ROW_MATCH) { + draw_rect(dr, x, y+tilesize/2-tilesize/12, + tilesize, 2*(tilesize/12), COL_ERROR); + } + if (tile & FE_COL_MATCH) { + draw_rect(dr, x+tilesize/2-tilesize/12, y, + 2*(tilesize/12), tilesize, COL_ERROR); + } + + /* Cursor rectangle */ + if (tile & FF_CURSOR) { + draw_rect(dr, x, y, tilesize/12, tilesize-1, COL_CURSOR); + draw_rect(dr, x, y, tilesize-1, tilesize/12, COL_CURSOR); + draw_rect(dr, x+tilesize-1-tilesize/12, y, tilesize/12, tilesize-1, + COL_CURSOR); + draw_rect(dr, x, y+tilesize-1-tilesize/12, tilesize-1, tilesize/12, + COL_CURSOR); + } + + unclip(dr); + draw_update(dr, x, y, tilesize, tilesize); +} + +#define TILE_SIZE (ds->tilesize) +#define DEFAULT_TILE_SIZE 32 +#define FLASH_FRAME 0.12F +#define FLASH_TIME (FLASH_FRAME * 3) + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w2 = state->w2, h2 = state->h2; + int s = w2 * h2; + int flash; + int x, y, i; + + if (!ds->started) { + /* Main window background */ + draw_rect(dr, 0, 0, TILE_SIZE * (w2+1), TILE_SIZE * (h2+1), + COL_BACKGROUND); + /* Outer edge of grid */ + draw_rect(dr, COORD(0)-TILE_SIZE/10, COORD(0)-TILE_SIZE/10, + TILE_SIZE*w2 + 2*(TILE_SIZE/10) - 1, + TILE_SIZE*h2 + 2*(TILE_SIZE/10) - 1, COL_GRID); + + draw_update(dr, 0, 0, TILE_SIZE * (w2+1), TILE_SIZE * (h2+1)); + ds->started = TRUE; + } + + flash = 0; + if (flashtime > 0) + flash = (int)(flashtime / FLASH_FRAME) == 1 ? FF_FLASH2 : FF_FLASH1; + + for (i = 0; i < s; i++) + ds->gridfs[i] = 0; + unruly_validate_all_rows(state, ds->gridfs); + for (i = 0; i < 2 * (h2 + w2); i++) + ds->rowfs[i] = 0; + unruly_validate_counts(state, NULL, ds->rowfs); + + for (y = 0; y < h2; y++) { + for (x = 0; x < w2; x++) { + int tile; + + i = y * w2 + x; + + tile = ds->gridfs[i]; + + if (state->grid[i] == N_ONE) { + tile |= FF_ONE; + if (ds->rowfs[y] || ds->rowfs[2*h2 + x]) + tile |= FE_COUNT; + } else if (state->grid[i] == N_ZERO) { + tile |= FF_ZERO; + if (ds->rowfs[h2 + y] || ds->rowfs[2*h2 + w2 + x]) + tile |= FE_COUNT; + } + + tile |= flash; + + if (state->immutable[i]) + tile |= FF_IMMUTABLE; + + if (ui->cursor && ui->cx == x && ui->cy == y) + tile |= FF_CURSOR; + + if (ds->grid[i] != tile) { + ds->grid[i] = tile; + unruly_draw_tile(dr, COORD(x), COORD(y), TILE_SIZE, tile); + } + } + } +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + return 0.0F; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ + int pw, ph; + + /* Using 7mm squares */ + game_compute_size(params, 700, &pw, &ph); + *x = pw / 100.0F; + *y = ph / 100.0F; +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ + int w2 = state->w2, h2 = state->h2; + int x, y; + + int ink = print_mono_colour(dr, 0); + + for (y = 0; y < h2; y++) + for (x = 0; x < w2; x++) { + int tx = x * tilesize + (tilesize / 2); + int ty = y * tilesize + (tilesize / 2); + + /* Draw the border */ + int coords[8]; + coords[0] = tx; + coords[1] = ty - 1; + coords[2] = tx + tilesize; + coords[3] = ty - 1; + coords[4] = tx + tilesize; + coords[5] = ty + tilesize - 1; + coords[6] = tx; + coords[7] = ty + tilesize - 1; + draw_polygon(dr, coords, 4, -1, ink); + + if (state->grid[y * w2 + x] == N_ONE) + draw_rect(dr, tx, ty, tilesize, tilesize, ink); + else if (state->grid[y * w2 + x] == N_ZERO) + draw_circle(dr, tx + tilesize/2, ty + tilesize/2, + tilesize/12, ink, ink); + } +} + +#ifdef COMBINED +#define thegame unruly +#endif + +const struct game thegame = { + "Unruly", "games.unruly", "unruly", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + TRUE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + DEFAULT_TILE_SIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + 0, /* flags */ +}; + +/* ***************** * + * Standalone solver * + * ***************** */ + +#ifdef STANDALONE_SOLVER +#include +#include + +/* Most of the standalone solver code was copied from unequal.c and singles.c */ + +const char *quis; + +static void usage_exit(const char *msg) +{ + if (msg) + fprintf(stderr, "%s: %s\n", quis, msg); + fprintf(stderr, + "Usage: %s [-v] [--seed SEED] | [game_id [game_id ...]]\n", + quis); + exit(1); +} + +int main(int argc, char *argv[]) +{ + random_state *rs; + time_t seed = time(NULL); + + game_params *params = NULL; + + char *id = NULL, *desc = NULL, *err; + + quis = argv[0]; + + while (--argc > 0) { + char *p = *++argv; + if (!strcmp(p, "--seed")) { + if (argc == 0) + usage_exit("--seed needs an argument"); + seed = (time_t) atoi(*++argv); + argc--; + } else if (!strcmp(p, "-v")) + solver_verbose = TRUE; + else if (*p == '-') + usage_exit("unrecognised option"); + else + id = p; + } + + if (id) { + desc = strchr(id, ':'); + if (desc) + *desc++ = '\0'; + + params = default_params(); + decode_params(params, id); + err = validate_params(params, TRUE); + if (err) { + fprintf(stderr, "Parameters are invalid\n"); + fprintf(stderr, "%s: %s", argv[0], err); + exit(1); + } + } + + if (!desc) { + char *desc_gen, *aux; + rs = random_new((void *) &seed, sizeof(time_t)); + if (!params) + params = default_params(); + printf("Generating puzzle with parameters %s\n", + encode_params(params, TRUE)); + desc_gen = new_game_desc(params, rs, &aux, FALSE); + + if (!solver_verbose) { + char *fmt = game_text_format(new_game(NULL, params, desc_gen)); + fputs(fmt, stdout); + sfree(fmt); + } + + printf("Game ID: %s\n", desc_gen); + } else { + game_state *input; + struct unruly_scratch *scratch; + int maxdiff, errcode; + + err = validate_desc(params, desc); + if (err) { + fprintf(stderr, "Description is invalid\n"); + fprintf(stderr, "%s", err); + exit(1); + } + + input = new_game(NULL, params, desc); + scratch = unruly_new_scratch(input); + + maxdiff = unruly_solve_game(input, scratch, DIFFCOUNT); + + errcode = unruly_validate_counts(input, scratch, NULL); + if (unruly_validate_all_rows(input, NULL) == -1) + errcode = -1; + + if (errcode != -1) { + char *fmt = game_text_format(input); + fputs(fmt, stdout); + sfree(fmt); + if (maxdiff < 0) + printf("Difficulty: already solved!\n"); + else + printf("Difficulty: %s\n", unruly_diffnames[maxdiff]); + } + + if (errcode == 1) + printf("No solution found.\n"); + else if (errcode == -1) + printf("Puzzle is invalid.\n"); + + free_game(input); + unruly_free_scratch(scratch); + } + + return 0; +} +#endif diff --git a/apps/plugins/puzzles/untangle.R b/apps/plugins/puzzles/untangle.R new file mode 100644 index 0000000000..a57f1e56fd --- /dev/null +++ b/apps/plugins/puzzles/untangle.R @@ -0,0 +1,21 @@ +# -*- makefile -*- + +UNTANGLE_EXTRA = tree234 + +untangle : [X] GTK COMMON untangle UNTANGLE_EXTRA untangle-icon|no-icon + +untangle : [G] WINDOWS COMMON untangle UNTANGLE_EXTRA untangle.res|noicon.res + +ALL += untangle[COMBINED] UNTANGLE_EXTRA + +!begin am gtk +GAMES += untangle +!end + +!begin >list.c + A(untangle) \ +!end + +!begin >gamedesc.txt +untangle:untangle.exe:Untangle:Planar graph layout puzzle:Reposition the points so that the lines do not cross. +!end diff --git a/apps/plugins/puzzles/untangle.c b/apps/plugins/puzzles/untangle.c new file mode 100644 index 0000000000..67da03d8c0 --- /dev/null +++ b/apps/plugins/puzzles/untangle.c @@ -0,0 +1,1491 @@ +/* + * untangle.c: Game about planar graphs. You are given a graph + * represented by points and straight lines, with some lines + * crossing; your task is to drag the points into a configuration + * where none of the lines cross. + * + * Cloned from a Flash game called `Planarity', by John Tantalo. + * at the time of writing + * this. The Flash game had a fixed set of levels; my added value, + * as usual, is automatic generation of random games to order. + */ + +/* + * TODO: + * + * - This puzzle, perhaps uniquely among the collection, could use + * support for non-aspect-ratio-preserving resizes. This would + * require some sort of fairly large redesign, unfortunately (since + * it would invalidate the basic assumption that puzzles' size + * requirements are adequately expressed by a single scalar tile + * size), and probably complicate the rest of the puzzles' API as a + * result. So I'm not sure I really want to do it. + * + * - It would be nice if we could somehow auto-detect a real `long + * long' type on the host platform and use it in place of my + * hand-hacked int64s. It'd be faster and more reliable. + */ + +#include +#include +#include +#include "rbassert.h" +#include +#include + +#include "puzzles.h" +#include "tree234.h" + +#define CIRCLE_RADIUS 6 +#define DRAG_THRESHOLD (CIRCLE_RADIUS * 2) +#define PREFERRED_TILESIZE 64 + +#define FLASH_TIME 0.30F +#define ANIM_TIME 0.13F +#define SOLVEANIM_TIME 0.50F + +enum { + COL_SYSBACKGROUND, + COL_BACKGROUND, + COL_LINE, +#ifdef SHOW_CROSSINGS + COL_CROSSEDLINE, +#endif + COL_OUTLINE, + COL_POINT, + COL_DRAGPOINT, + COL_NEIGHBOUR, + COL_FLASH1, + COL_FLASH2, + NCOLOURS +}; + +typedef struct point { + /* + * Points are stored using rational coordinates, with the same + * denominator for both coordinates. + */ + long x, y, d; +} point; + +typedef struct edge { + /* + * This structure is implicitly associated with a particular + * point set, so all it has to do is to store two point + * indices. It is required to store them in the order (lower, + * higher), i.e. a < b always. + */ + int a, b; +} edge; + +struct game_params { + int n; /* number of points */ +}; + +struct graph { + int refcount; /* for deallocation */ + tree234 *edges; /* stores `edge' structures */ +}; + +struct game_state { + game_params params; + int w, h; /* extent of coordinate system only */ + point *pts; +#ifdef SHOW_CROSSINGS + int *crosses; /* mark edges which are crossed */ +#endif + struct graph *graph; + int completed, cheated, just_solved; +}; + +static int edgecmpC(const void *av, const void *bv) +{ + const edge *a = (const edge *)av; + const edge *b = (const edge *)bv; + + if (a->a < b->a) + return -1; + else if (a->a > b->a) + return +1; + else if (a->b < b->b) + return -1; + else if (a->b > b->b) + return +1; + return 0; +} + +static int edgecmp(void *av, void *bv) { return edgecmpC(av, bv); } + +static game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->n = 10; + + return ret; +} + +static int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + int n; + char buf[80]; + + switch (i) { + case 0: n = 6; break; + case 1: n = 10; break; + case 2: n = 15; break; + case 3: n = 20; break; + case 4: n = 25; break; + default: return FALSE; + } + + sprintf(buf, "%d points", n); + *name = dupstr(buf); + + *params = ret = snew(game_params); + ret->n = n; + + return TRUE; +} + +static void free_params(game_params *params) +{ + sfree(params); +} + +static game_params *dup_params(const game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +static void decode_params(game_params *params, char const *string) +{ + params->n = atoi(string); +} + +static char *encode_params(const game_params *params, int full) +{ + char buf[80]; + + sprintf(buf, "%d", params->n); + + return dupstr(buf); +} + +static config_item *game_configure(const game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, config_item); + + ret[0].name = "Number of points"; + ret[0].type = C_STRING; + sprintf(buf, "%d", params->n); + ret[0].sval = dupstr(buf); + ret[0].ival = 0; + + ret[1].name = NULL; + ret[1].type = C_END; + ret[1].sval = NULL; + ret[1].ival = 0; + + return ret; +} + +static game_params *custom_params(const config_item *cfg) +{ + game_params *ret = snew(game_params); + + ret->n = atoi(cfg[0].sval); + + return ret; +} + +static char *validate_params(const game_params *params, int full) +{ + if (params->n < 4) + return "Number of points must be at least four"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Small number of 64-bit integer arithmetic operations, to prevent + * integer overflow at the very core of cross(). + */ + +typedef struct { + long hi; + unsigned long lo; +} int64; + +#define greater64(i,j) ( (i).hi>(j).hi || ((i).hi==(j).hi && (i).lo>(j).lo)) +#define sign64(i) ((i).hi < 0 ? -1 : (i).hi==0 && (i).lo==0 ? 0 : +1) + +static int64 mulu32to64(unsigned long x, unsigned long y) +{ + unsigned long a, b, c, d, t; + int64 ret; + + a = (x & 0xFFFF) * (y & 0xFFFF); + b = (x & 0xFFFF) * (y >> 16); + c = (x >> 16) * (y & 0xFFFF); + d = (x >> 16) * (y >> 16); + + ret.lo = a; + ret.hi = d + (b >> 16) + (c >> 16); + t = (b & 0xFFFF) << 16; + ret.lo += t; + if (ret.lo < t) + ret.hi++; + t = (c & 0xFFFF) << 16; + ret.lo += t; + if (ret.lo < t) + ret.hi++; + +#ifdef DIAGNOSTIC_VIA_LONGLONG + assert(((unsigned long long)ret.hi << 32) + ret.lo == + (unsigned long long)x * y); +#endif + + return ret; +} + +static int64 mul32to64(long x, long y) +{ + int sign = +1; + int64 ret; +#ifdef DIAGNOSTIC_VIA_LONGLONG + long long realret = (long long)x * y; +#endif + + if (x < 0) + x = -x, sign = -sign; + if (y < 0) + y = -y, sign = -sign; + + ret = mulu32to64(x, y); + + if (sign < 0) { + ret.hi = -ret.hi; + ret.lo = -ret.lo; + if (ret.lo) + ret.hi--; + } + +#ifdef DIAGNOSTIC_VIA_LONGLONG + assert(((unsigned long long)ret.hi << 32) + ret.lo == realret); +#endif + + return ret; +} + +static int64 dotprod64(long a, long b, long p, long q) +{ + int64 ab, pq; + + ab = mul32to64(a, b); + pq = mul32to64(p, q); + ab.hi += pq.hi; + ab.lo += pq.lo; + if (ab.lo < pq.lo) + ab.hi++; + return ab; +} + +/* + * Determine whether the line segments between a1 and a2, and + * between b1 and b2, intersect. We count it as an intersection if + * any of the endpoints lies _on_ the other line. + */ +static int cross(point a1, point a2, point b1, point b2) +{ + long b1x, b1y, b2x, b2y, px, py; + int64 d1, d2, d3; + + /* + * The condition for crossing is that b1 and b2 are on opposite + * sides of the line a1-a2, and vice versa. We determine this + * by taking the dot product of b1-a1 with a vector + * perpendicular to a2-a1, and similarly with b2-a1, and seeing + * if they have different signs. + */ + + /* + * Construct the vector b1-a1. We don't have to worry too much + * about the denominator, because we're only going to check the + * sign of this vector; we just need to get the numerator + * right. + */ + b1x = b1.x * a1.d - a1.x * b1.d; + b1y = b1.y * a1.d - a1.y * b1.d; + /* Now construct b2-a1, and a vector perpendicular to a2-a1, + * in the same way. */ + b2x = b2.x * a1.d - a1.x * b2.d; + b2y = b2.y * a1.d - a1.y * b2.d; + px = a1.y * a2.d - a2.y * a1.d; + py = a2.x * a1.d - a1.x * a2.d; + /* Take the dot products. Here we resort to 64-bit arithmetic. */ + d1 = dotprod64(b1x, px, b1y, py); + d2 = dotprod64(b2x, px, b2y, py); + /* If they have the same non-zero sign, the lines do not cross. */ + if ((sign64(d1) > 0 && sign64(d2) > 0) || + (sign64(d1) < 0 && sign64(d2) < 0)) + return FALSE; + + /* + * If the dot products are both exactly zero, then the two line + * segments are collinear. At this point the intersection + * condition becomes whether or not they overlap within their + * line. + */ + if (sign64(d1) == 0 && sign64(d2) == 0) { + /* Construct the vector a2-a1. */ + px = a2.x * a1.d - a1.x * a2.d; + py = a2.y * a1.d - a1.y * a2.d; + /* Determine the dot products of b1-a1 and b2-a1 with this. */ + d1 = dotprod64(b1x, px, b1y, py); + d2 = dotprod64(b2x, px, b2y, py); + /* If they're both strictly negative, the lines do not cross. */ + if (sign64(d1) < 0 && sign64(d2) < 0) + return FALSE; + /* Otherwise, take the dot product of a2-a1 with itself. If + * the other two dot products both exceed this, the lines do + * not cross. */ + d3 = dotprod64(px, px, py, py); + if (greater64(d1, d3) && greater64(d2, d3)) + return FALSE; + } + + /* + * We've eliminated the only important special case, and we + * have determined that b1 and b2 are on opposite sides of the + * line a1-a2. Now do the same thing the other way round and + * we're done. + */ + b1x = a1.x * b1.d - b1.x * a1.d; + b1y = a1.y * b1.d - b1.y * a1.d; + b2x = a2.x * b1.d - b1.x * a2.d; + b2y = a2.y * b1.d - b1.y * a2.d; + px = b1.y * b2.d - b2.y * b1.d; + py = b2.x * b1.d - b1.x * b2.d; + d1 = dotprod64(b1x, px, b1y, py); + d2 = dotprod64(b2x, px, b2y, py); + if ((sign64(d1) > 0 && sign64(d2) > 0) || + (sign64(d1) < 0 && sign64(d2) < 0)) + return FALSE; + + /* + * The lines must cross. + */ + return TRUE; +} + +static unsigned long squarert(unsigned long n) { + unsigned long d, a, b, di; + + d = n; + a = 0; + b = 1L << 30; /* largest available power of 4 */ + do { + a >>= 1; + di = 2*a + b; + if (di <= d) { + d -= di; + a += b; + } + b >>= 2; + } while (b); + + return a; +} + +/* + * Our solutions are arranged on a square grid big enough that n + * points occupy about 1/POINTDENSITY of the grid. + */ +#define POINTDENSITY 3 +#define MAXDEGREE 4 +#define COORDLIMIT(n) squarert((n) * POINTDENSITY) + +static void addedge(tree234 *edges, int a, int b) +{ + edge *e = snew(edge); + + assert(a != b); + + e->a = min(a, b); + e->b = max(a, b); + + add234(edges, e); +} + +static int isedge(tree234 *edges, int a, int b) +{ + edge e; + + assert(a != b); + + e.a = min(a, b); + e.b = max(a, b); + + return find234(edges, &e, NULL) != NULL; +} + +typedef struct vertex { + int param; + int vindex; +} vertex; + +static int vertcmpC(const void *av, const void *bv) +{ + const vertex *a = (vertex *)av; + const vertex *b = (vertex *)bv; + + if (a->param < b->param) + return -1; + else if (a->param > b->param) + return +1; + else if (a->vindex < b->vindex) + return -1; + else if (a->vindex > b->vindex) + return +1; + return 0; +} +static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); } + +/* + * Construct point coordinates for n points arranged in a circle, + * within the bounding box (0,0) to (w,w). + */ +static void make_circle(point *pts, int n, int w) +{ + long d, r, c, i; + + /* + * First, decide on a denominator. Although in principle it + * would be nice to set this really high so as to finely + * distinguish all the points on the circle, I'm going to set + * it at a fixed size to prevent integer overflow problems. + */ + d = PREFERRED_TILESIZE; + + /* + * Leave a little space outside the circle. + */ + c = d * w / 2; + r = d * w * 3 / 7; + + /* + * Place the points. + */ + for (i = 0; i < n; i++) { + double angle = i * 2 * PI / n; + double x = r * sin(angle), y = - r * cos(angle); + pts[i].x = (long)(c + x + 0.5); + pts[i].y = (long)(c + y + 0.5); + pts[i].d = d; + } +} + +static char *new_game_desc(const game_params *params, random_state *rs, + char **aux, int interactive) +{ + int n = params->n, i; + long w, h, j, k, m; + point *pts, *pts2; + long *tmp; + tree234 *edges, *vertices; + edge *e, *e2; + vertex *v, *vs, *vlist; + char *ret; + + w = h = COORDLIMIT(n); + + /* + * Choose n points from this grid. + */ + pts = snewn(n, point); + tmp = snewn(w*h, long); + for (i = 0; i < w*h; i++) + tmp[i] = i; + shuffle(tmp, w*h, sizeof(*tmp), rs); + for (i = 0; i < n; i++) { + pts[i].x = tmp[i] % w; + pts[i].y = tmp[i] / w; + pts[i].d = 1; + } + sfree(tmp); + + /* + * Now start adding edges between the points. + * + * At all times, we attempt to add an edge to the lowest-degree + * vertex we currently have, and we try the other vertices as + * candidate second endpoints in order of distance from this + * one. We stop as soon as we find an edge which + * + * (a) does not increase any vertex's degree beyond MAXDEGREE + * (b) does not cross any existing edges + * (c) does not intersect any actual point. + */ + vs = snewn(n, vertex); + vertices = newtree234(vertcmp); + for (i = 0; i < n; i++) { + v = vs + i; + v->param = 0; /* in this tree, param is the degree */ + v->vindex = i; + add234(vertices, v); + } + edges = newtree234(edgecmp); + vlist = snewn(n, vertex); + while (1) { + int added = FALSE; + + for (i = 0; i < n; i++) { + v = index234(vertices, i); + j = v->vindex; + + if (v->param >= MAXDEGREE) + break; /* nothing left to add! */ + + /* + * Sort the other vertices into order of their distance + * from this one. Don't bother looking below i, because + * we've already tried those edges the other way round. + * Also here we rule out target vertices with too high + * a degree, and (of course) ones to which we already + * have an edge. + */ + m = 0; + for (k = i+1; k < n; k++) { + vertex *kv = index234(vertices, k); + int ki = kv->vindex; + int dx, dy; + + if (kv->param >= MAXDEGREE || isedge(edges, ki, j)) + continue; + + vlist[m].vindex = ki; + dx = pts[ki].x - pts[j].x; + dy = pts[ki].y - pts[j].y; + vlist[m].param = dx*dx + dy*dy; + m++; + } + + qsort(vlist, m, sizeof(*vlist), vertcmpC); + + for (k = 0; k < m; k++) { + int p; + int ki = vlist[k].vindex; + + /* + * Check to see whether this edge intersects any + * existing edge or point. + */ + for (p = 0; p < n; p++) + if (p != ki && p != j && cross(pts[ki], pts[j], + pts[p], pts[p])) + break; + if (p < n) + continue; + for (p = 0; (e = index234(edges, p)) != NULL; p++) + if (e->a != ki && e->a != j && + e->b != ki && e->b != j && + cross(pts[ki], pts[j], pts[e->a], pts[e->b])) + break; + if (e) + continue; + + /* + * We're done! Add this edge, modify the degrees of + * the two vertices involved, and break. + */ + addedge(edges, j, ki); + added = TRUE; + del234(vertices, vs+j); + vs[j].param++; + add234(vertices, vs+j); + del234(vertices, vs+ki); + vs[ki].param++; + add234(vertices, vs+ki); + break; + } + + if (k < m) + break; + } + + if (!added) + break; /* we're done. */ + } + + /* + * That's our graph. Now shuffle the points, making sure that + * they come out with at least one crossed line when arranged + * in a circle (so that the puzzle isn't immediately solved!). + */ + tmp = snewn(n, long); + for (i = 0; i < n; i++) + tmp[i] = i; + pts2 = snewn(n, point); + make_circle(pts2, n, w); + while (1) { + shuffle(tmp, n, sizeof(*tmp), rs); + for (i = 0; (e = index234(edges, i)) != NULL; i++) { + for (j = i+1; (e2 = index234(edges, j)) != NULL; j++) { + if (e2->a == e->a || e2->a == e->b || + e2->b == e->a || e2->b == e->b) + continue; + if (cross(pts2[tmp[e2->a]], pts2[tmp[e2->b]], + pts2[tmp[e->a]], pts2[tmp[e->b]])) + break; + } + if (e2) + break; + } + if (e) + break; /* we've found a crossing */ + } + + /* + * We're done. Now encode the graph in a string format. Let's + * use a comma-separated list of dash-separated vertex number + * pairs, numbered from zero. We'll sort the list to prevent + * side channels. + */ + ret = NULL; + { + char *sep; + char buf[80]; + int retlen; + edge *ea; + + retlen = 0; + m = count234(edges); + ea = snewn(m, edge); + for (i = 0; (e = index234(edges, i)) != NULL; i++) { + assert(i < m); + ea[i].a = min(tmp[e->a], tmp[e->b]); + ea[i].b = max(tmp[e->a], tmp[e->b]); + retlen += 1 + sprintf(buf, "%d-%d", ea[i].a, ea[i].b); + } + assert(i == m); + qsort(ea, m, sizeof(*ea), edgecmpC); + + ret = snewn(retlen, char); + sep = ""; + k = 0; + + for (i = 0; i < m; i++) { + k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b); + sep = ","; + } + assert(k < retlen); + + sfree(ea); + } + + /* + * Encode the solution we started with as an aux_info string. + */ + { + char buf[80]; + char *auxstr; + int auxlen; + + auxlen = 2; /* leading 'S' and trailing '\0' */ + for (i = 0; i < n; i++) { + j = tmp[i]; + pts2[j] = pts[i]; + if (pts2[j].d & 1) { + pts2[j].x *= 2; + pts2[j].y *= 2; + pts2[j].d *= 2; + } + pts2[j].x += pts2[j].d / 2; + pts2[j].y += pts2[j].d / 2; + auxlen += sprintf(buf, ";P%d:%ld,%ld/%ld", i, + pts2[j].x, pts2[j].y, pts2[j].d); + } + k = 0; + auxstr = snewn(auxlen, char); + auxstr[k++] = 'S'; + for (i = 0; i < n; i++) + k += sprintf(auxstr+k, ";P%d:%ld,%ld/%ld", i, + pts2[i].x, pts2[i].y, pts2[i].d); + assert(k < auxlen); + *aux = auxstr; + } + sfree(pts2); + + sfree(tmp); + sfree(vlist); + freetree234(vertices); + sfree(vs); + while ((e = delpos234(edges, 0)) != NULL) + sfree(e); + freetree234(edges); + sfree(pts); + + return ret; +} + +static char *validate_desc(const game_params *params, const char *desc) +{ + int a, b; + + while (*desc) { + a = atoi(desc); + if (a < 0 || a >= params->n) + return "Number out of range in game description"; + while (*desc && isdigit((unsigned char)*desc)) desc++; + if (*desc != '-') + return "Expected '-' after number in game description"; + desc++; /* eat dash */ + b = atoi(desc); + if (b < 0 || b >= params->n) + return "Number out of range in game description"; + while (*desc && isdigit((unsigned char)*desc)) desc++; + if (*desc) { + if (*desc != ',') + return "Expected ',' after number in game description"; + desc++; /* eat comma */ + } + } + + return NULL; +} + +static void mark_crossings(game_state *state) +{ + int ok = TRUE; + int i, j; + edge *e, *e2; + +#ifdef SHOW_CROSSINGS + for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) + state->crosses[i] = FALSE; +#endif + + /* + * Check correctness: for every pair of edges, see whether they + * cross. + */ + for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) { + for (j = i+1; (e2 = index234(state->graph->edges, j)) != NULL; j++) { + if (e2->a == e->a || e2->a == e->b || + e2->b == e->a || e2->b == e->b) + continue; + if (cross(state->pts[e2->a], state->pts[e2->b], + state->pts[e->a], state->pts[e->b])) { + ok = FALSE; +#ifdef SHOW_CROSSINGS + state->crosses[i] = state->crosses[j] = TRUE; +#else + goto done; /* multi-level break - sorry */ +#endif + } + } + } + + /* + * e == NULL if we've gone through all the edge pairs + * without finding a crossing. + */ +#ifndef SHOW_CROSSINGS + done: +#endif + if (ok) + state->completed = TRUE; +} + +static game_state *new_game(midend *me, const game_params *params, + const char *desc) +{ + int n = params->n; + game_state *state = snew(game_state); + int a, b; + + state->params = *params; + state->w = state->h = COORDLIMIT(n); + state->pts = snewn(n, point); + make_circle(state->pts, n, state->w); + state->graph = snew(struct graph); + state->graph->refcount = 1; + state->graph->edges = newtree234(edgecmp); + state->completed = state->cheated = state->just_solved = FALSE; + + while (*desc) { + a = atoi(desc); + assert(a >= 0 && a < params->n); + while (*desc && isdigit((unsigned char)*desc)) desc++; + assert(*desc == '-'); + desc++; /* eat dash */ + b = atoi(desc); + assert(b >= 0 && b < params->n); + while (*desc && isdigit((unsigned char)*desc)) desc++; + if (*desc) { + assert(*desc == ','); + desc++; /* eat comma */ + } + addedge(state->graph->edges, a, b); + } + +#ifdef SHOW_CROSSINGS + state->crosses = snewn(count234(state->graph->edges), int); + mark_crossings(state); /* sets up `crosses' and `completed' */ +#endif + + return state; +} + +static game_state *dup_game(const game_state *state) +{ + int n = state->params.n; + game_state *ret = snew(game_state); + + ret->params = state->params; + ret->w = state->w; + ret->h = state->h; + ret->pts = snewn(n, point); + memcpy(ret->pts, state->pts, n * sizeof(point)); + ret->graph = state->graph; + ret->graph->refcount++; + ret->completed = state->completed; + ret->cheated = state->cheated; + ret->just_solved = state->just_solved; +#ifdef SHOW_CROSSINGS + ret->crosses = snewn(count234(ret->graph->edges), int); + memcpy(ret->crosses, state->crosses, + count234(ret->graph->edges) * sizeof(int)); +#endif + + return ret; +} + +static void free_game(game_state *state) +{ + if (--state->graph->refcount <= 0) { + edge *e; + while ((e = delpos234(state->graph->edges, 0)) != NULL) + sfree(e); + freetree234(state->graph->edges); + sfree(state->graph); + } + sfree(state->pts); + sfree(state); +} + +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) +{ + int n = state->params.n; + int matrix[4]; + point *pts; + int i, j, besti; + float bestd; + char buf[80], *ret; + int retlen, retsize; + + if (!aux) { + *error = "Solution not known for this puzzle"; + return NULL; + } + + /* + * Decode the aux_info to get the original point positions. + */ + pts = snewn(n, point); + aux++; /* eat 'S' */ + for (i = 0; i < n; i++) { + int p, k; + long x, y, d; + int ret = sscanf(aux, ";P%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k); + if (ret != 4 || p != i) { + *error = "Internal error: aux_info badly formatted"; + sfree(pts); + return NULL; + } + pts[i].x = x; + pts[i].y = y; + pts[i].d = d; + aux += k; + } + + /* + * Now go through eight possible symmetries of the point set. + * For each one, work out the sum of the Euclidean distances + * between the points' current positions and their new ones. + * + * We're squaring distances here, which means we're at risk of + * integer overflow. Fortunately, there's no real need to be + * massively careful about rounding errors, since this is a + * non-essential bit of the code; so I'll just work in floats + * internally. + */ + besti = -1; + bestd = 0.0F; + + for (i = 0; i < 8; i++) { + float d; + + matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0; + matrix[i & 1] = (i & 2) ? +1 : -1; + matrix[3-(i&1)] = (i & 4) ? +1 : -1; + + d = 0.0F; + for (j = 0; j < n; j++) { + float px = (float)pts[j].x / pts[j].d; + float py = (float)pts[j].y / pts[j].d; + float sx = (float)currstate->pts[j].x / currstate->pts[j].d; + float sy = (float)currstate->pts[j].y / currstate->pts[j].d; + float cx = (float)currstate->w / 2; + float cy = (float)currstate->h / 2; + float ox, oy, dx, dy; + + px -= cx; + py -= cy; + + ox = matrix[0] * px + matrix[1] * py; + oy = matrix[2] * px + matrix[3] * py; + + ox += cx; + oy += cy; + + dx = ox - sx; + dy = oy - sy; + + d += dx*dx + dy*dy; + } + + if (besti < 0 || bestd > d) { + besti = i; + bestd = d; + } + } + + assert(besti >= 0); + + /* + * Now we know which symmetry is closest to the points' current + * positions. Use it. + */ + matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0; + matrix[besti & 1] = (besti & 2) ? +1 : -1; + matrix[3-(besti&1)] = (besti & 4) ? +1 : -1; + + retsize = 256; + ret = snewn(retsize, char); + retlen = 0; + ret[retlen++] = 'S'; + ret[retlen] = '\0'; + + for (i = 0; i < n; i++) { + float px = (float)pts[i].x / pts[i].d; + float py = (float)pts[i].y / pts[i].d; + float cx = (float)currstate->w / 2; + float cy = (float)currstate->h / 2; + float ox, oy; + int extra; + + px -= cx; + py -= cy; + + ox = matrix[0] * px + matrix[1] * py; + oy = matrix[2] * px + matrix[3] * py; + + ox += cx; + oy += cy; + + /* + * Use a fixed denominator of 2, because we know the + * original points were on an integer grid offset by 1/2. + */ + pts[i].d = 2; + ox *= pts[i].d; + oy *= pts[i].d; + pts[i].x = (long)(ox + 0.5F); + pts[i].y = (long)(oy + 0.5F); + + extra = sprintf(buf, ";P%d:%ld,%ld/%ld", i, + pts[i].x, pts[i].y, pts[i].d); + if (retlen + extra >= retsize) { + retsize = retlen + extra + 256; + ret = sresize(ret, retsize, char); + } + strcpy(ret + retlen, buf); + retlen += extra; + } + + sfree(pts); + + return ret; +} + +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) +{ + return NULL; +} + +struct game_ui { + int dragpoint; /* point being dragged; -1 if none */ + point newpoint; /* where it's been dragged to so far */ + int just_dragged; /* reset in game_changed_state */ + int just_moved; /* _set_ in game_changed_state */ + float anim_length; +}; + +static game_ui *new_ui(const game_state *state) +{ + game_ui *ui = snew(game_ui); + ui->dragpoint = -1; + ui->just_moved = ui->just_dragged = FALSE; + return ui; +} + +static void free_ui(game_ui *ui) +{ + sfree(ui); +} + +static char *encode_ui(const game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, const char *encoding) +{ +} + +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) +{ + ui->dragpoint = -1; + ui->just_moved = ui->just_dragged; + ui->just_dragged = FALSE; +} + +struct game_drawstate { + long tilesize; + int bg, dragpoint; + long *x, *y; +}; + +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) +{ + int n = state->params.n; + + if (IS_MOUSE_DOWN(button)) { + int i, best; + long bestd; + + /* + * Begin drag. We drag the vertex _nearest_ to the pointer, + * just in case one is nearly on top of another and we want + * to drag the latter. However, we drag nothing at all if + * the nearest vertex is outside DRAG_THRESHOLD. + */ + best = -1; + bestd = 0; + + for (i = 0; i < n; i++) { + long px = state->pts[i].x * ds->tilesize / state->pts[i].d; + long py = state->pts[i].y * ds->tilesize / state->pts[i].d; + long dx = px - x; + long dy = py - y; + long d = dx*dx + dy*dy; + + if (best == -1 || bestd > d) { + best = i; + bestd = d; + } + } + + if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) { + ui->dragpoint = best; + ui->newpoint.x = x; + ui->newpoint.y = y; + ui->newpoint.d = ds->tilesize; + return ""; + } + + } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) { + ui->newpoint.x = x; + ui->newpoint.y = y; + ui->newpoint.d = ds->tilesize; + return ""; + } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) { + int p = ui->dragpoint; + char buf[80]; + + ui->dragpoint = -1; /* terminate drag, no matter what */ + + /* + * First, see if we're within range. The user can cancel a + * drag by dragging the point right off the window. + */ + if (ui->newpoint.x < 0 || + ui->newpoint.x >= (long)state->w*ui->newpoint.d || + ui->newpoint.y < 0 || + ui->newpoint.y >= (long)state->h*ui->newpoint.d) + return ""; + + /* + * We aren't cancelling the drag. Construct a move string + * indicating where this point is going to. + */ + sprintf(buf, "P%d:%ld,%ld/%ld", p, + ui->newpoint.x, ui->newpoint.y, ui->newpoint.d); + ui->just_dragged = TRUE; + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(const game_state *state, const char *move) +{ + int n = state->params.n; + int p, k; + long x, y, d; + game_state *ret = dup_game(state); + + ret->just_solved = FALSE; + + while (*move) { + if (*move == 'S') { + move++; + if (*move == ';') move++; + ret->cheated = ret->just_solved = TRUE; + } + if (*move == 'P' && + sscanf(move+1, "%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k) == 4 && + p >= 0 && p < n && d > 0) { + ret->pts[p].x = x; + ret->pts[p].y = y; + ret->pts[p].d = d; + + move += k+1; + if (*move == ';') move++; + } else { + free_game(ret); + return NULL; + } + } + + mark_crossings(ret); + + return ret; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) +{ + *x = *y = COORDLIMIT(params->n) * tilesize; +} + +static void game_set_size(drawing *dr, game_drawstate *ds, + const game_params *params, int tilesize) +{ + ds->tilesize = tilesize; +} + +static float *game_colours(frontend *fe, int *ncolours) +{ + float *ret = snewn(3 * NCOLOURS, float); + + /* + * COL_BACKGROUND is what we use as the normal background colour. + * Unusually, though, it isn't colour #0: COL_SYSBACKGROUND, a bit + * darker, takes that place. This means that if the user resizes + * an Untangle window so as to change its aspect ratio, the + * still-square playable area will be distinguished from the dead + * space around it. + */ + game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_SYSBACKGROUND); + + ret[COL_LINE * 3 + 0] = 0.0F; + ret[COL_LINE * 3 + 1] = 0.0F; + ret[COL_LINE * 3 + 2] = 0.0F; + +#ifdef SHOW_CROSSINGS + ret[COL_CROSSEDLINE * 3 + 0] = 1.0F; + ret[COL_CROSSEDLINE * 3 + 1] = 0.0F; + ret[COL_CROSSEDLINE * 3 + 2] = 0.0F; +#endif + + ret[COL_OUTLINE * 3 + 0] = 0.0F; + ret[COL_OUTLINE * 3 + 1] = 0.0F; + ret[COL_OUTLINE * 3 + 2] = 0.0F; + + ret[COL_POINT * 3 + 0] = 0.0F; + ret[COL_POINT * 3 + 1] = 0.0F; + ret[COL_POINT * 3 + 2] = 1.0F; + + ret[COL_DRAGPOINT * 3 + 0] = 1.0F; + ret[COL_DRAGPOINT * 3 + 1] = 1.0F; + ret[COL_DRAGPOINT * 3 + 2] = 1.0F; + + ret[COL_NEIGHBOUR * 3 + 0] = 1.0F; + ret[COL_NEIGHBOUR * 3 + 1] = 0.0F; + ret[COL_NEIGHBOUR * 3 + 2] = 0.0F; + + ret[COL_FLASH1 * 3 + 0] = 0.5F; + ret[COL_FLASH1 * 3 + 1] = 0.5F; + ret[COL_FLASH1 * 3 + 2] = 0.5F; + + ret[COL_FLASH2 * 3 + 0] = 1.0F; + ret[COL_FLASH2 * 3 + 1] = 1.0F; + ret[COL_FLASH2 * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + int i; + + ds->tilesize = 0; + ds->x = snewn(state->params.n, long); + ds->y = snewn(state->params.n, long); + for (i = 0; i < state->params.n; i++) + ds->x[i] = ds->y[i] = -1; + ds->bg = -1; + ds->dragpoint = -1; + + return ds; +} + +static void game_free_drawstate(drawing *dr, game_drawstate *ds) +{ + sfree(ds->y); + sfree(ds->x); + sfree(ds); +} + +static point mix(point a, point b, float distance) +{ + point ret; + + ret.d = a.d * b.d; + ret.x = (long)(a.x * b.d + distance * (b.x * a.d - a.x * b.d)); + ret.y = (long)(a.y * b.d + distance * (b.y * a.d - a.y * b.d)); + + return ret; +} + +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float animtime, float flashtime) +{ + int w, h; + edge *e; + int i, j; + int bg, points_moved; + + /* + * There's no terribly sensible way to do partial redraws of + * this game, so I'm going to have to resort to redrawing the + * whole thing every time. + */ + + if (flashtime == 0) + bg = COL_BACKGROUND; + else if ((int)(flashtime * 4 / FLASH_TIME) % 2 == 0) + bg = COL_FLASH1; + else + bg = COL_FLASH2; + + /* + * To prevent excessive spinning on redraw during a completion + * flash, we first check to see if _either_ the flash + * background colour has changed _or_ at least one point has + * moved _or_ a drag has begun or ended, and abandon the redraw + * if neither is the case. + * + * Also in this loop we work out the coordinates of all the + * points for this redraw. + */ + points_moved = FALSE; + for (i = 0; i < state->params.n; i++) { + point p = state->pts[i]; + long x, y; + + if (ui->dragpoint == i) + p = ui->newpoint; + + if (oldstate) + p = mix(oldstate->pts[i], p, animtime / ui->anim_length); + + x = p.x * ds->tilesize / p.d; + y = p.y * ds->tilesize / p.d; + + if (ds->x[i] != x || ds->y[i] != y) + points_moved = TRUE; + + ds->x[i] = x; + ds->y[i] = y; + } + + if (ds->bg == bg && ds->dragpoint == ui->dragpoint && !points_moved) + return; /* nothing to do */ + + ds->dragpoint = ui->dragpoint; + ds->bg = bg; + + game_compute_size(&state->params, ds->tilesize, &w, &h); + draw_rect(dr, 0, 0, w, h, bg); + + /* + * Draw the edges. + */ + + for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) { + draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b], +#ifdef SHOW_CROSSINGS + (oldstate?oldstate:state)->crosses[i] ? + COL_CROSSEDLINE : +#endif + COL_LINE); + } + + /* + * Draw the points. + * + * When dragging, we should not only vary the colours, but + * leave the point being dragged until last. + */ + for (j = 0; j < 3; j++) { + int thisc = (j == 0 ? COL_POINT : + j == 1 ? COL_NEIGHBOUR : COL_DRAGPOINT); + for (i = 0; i < state->params.n; i++) { + int c; + + if (ui->dragpoint == i) { + c = COL_DRAGPOINT; + } else if (ui->dragpoint >= 0 && + isedge(state->graph->edges, ui->dragpoint, i)) { + c = COL_NEIGHBOUR; + } else { + c = COL_POINT; + } + + if (c == thisc) { +#ifdef VERTEX_NUMBERS + draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg); + { + char buf[80]; + sprintf(buf, "%d", i); + draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE, + DRAG_THRESHOLD*3/2, + ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf); + } +#else + draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS, + c, COL_OUTLINE); +#endif + } + } + } + + draw_update(dr, 0, 0, w, h); +} + +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (ui->just_moved) + return 0.0F; + if ((dir < 0 ? oldstate : newstate)->just_solved) + ui->anim_length = SOLVEANIM_TIME; + else + ui->anim_length = ANIM_TIME; + return ui->anim_length; +} + +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) +{ + if (!oldstate->completed && newstate->completed && + !oldstate->cheated && !newstate->cheated) + return FLASH_TIME; + return 0.0F; +} + +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) +{ + return TRUE; +} + +static void game_print_size(const game_params *params, float *x, float *y) +{ +} + +static void game_print(drawing *dr, const game_state *state, int tilesize) +{ +} + +#ifdef COMBINED +#define thegame untangle +#endif + +const struct game thegame = { + "Untangle", "games.untangle", "untangle", + default_params, + game_fetch_preset, + decode_params, + encode_params, + free_params, + dup_params, + TRUE, game_configure, custom_params, + validate_params, + new_game_desc, + validate_desc, + new_game, + dup_game, + free_game, + TRUE, solve_game, + FALSE, game_can_format_as_text_now, game_text_format, + new_ui, + free_ui, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, + PREFERRED_TILESIZE, game_compute_size, game_set_size, + game_colours, + game_new_drawstate, + game_free_drawstate, + game_redraw, + game_anim_length, + game_flash_length, + game_status, + FALSE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ + FALSE, game_timing_state, + SOLVE_ANIMATES, /* flags */ +}; diff --git a/apps/plugins/puzzles/version.c b/apps/plugins/puzzles/version.c new file mode 100644 index 0000000000..1cef29feb7 --- /dev/null +++ b/apps/plugins/puzzles/version.c @@ -0,0 +1,7 @@ +/* + * Puzzles version numbering. + */ + +#include "version.h" + +char ver[] = VER; diff --git a/apps/plugins/puzzles/version.h b/apps/plugins/puzzles/version.h new file mode 100644 index 0000000000..997e00592b --- /dev/null +++ b/apps/plugins/puzzles/version.h @@ -0,0 +1,11 @@ +/* + * This header file provides the version #define for a particular + * build of Puzzles. + * + * When my automated build system does a full build, Buildscr + * completely overwrites this file with information appropriate to + * that build. The information _here_ is default stuff used for local + * development runs of 'make'. + */ + +#define VER "Unidentified build" diff --git a/apps/plugins/puzzles/wceinf.pl b/apps/plugins/puzzles/wceinf.pl new file mode 100644 index 0000000000..4756f3c2b8 --- /dev/null +++ b/apps/plugins/puzzles/wceinf.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl + +# Perl script to generate a .INF file for building a Pocket PC .CAB +# archive of Puzzles. This has to be scripted so that it can read +# gamedesc.txt and automatically adjust to the current available +# set of puzzles. + +# Usage: +# +# $ ./wceinf.pl gamedesc.txt > puzzles.inf + +$desc = shift @ARGV; +open DESC, "<", $desc; +while () { + chomp; + @_ = split /:/; + push @exes, $_[1]; + $names{$_[1]} = $_[2]; +} +close DESC; + +print '[Version]'."\n"; +print 'Signature = "$Windows NT$" ; required as-is'."\n"; +print 'Provider = "Simon Tatham" ; full app name will be " "'."\n"; +print 'CESignature = "$Windows CE$" ; required as-is'."\n"; +print ''."\n"; +print '[CEStrings]'."\n"; +print 'AppName = "Puzzle Collection" ; full app name will be " "'."\n"; +print 'InstallDir = %CE8%\%AppName% ; "\Program Files\Games\Puzzle Collection" (default install directory)'."\n"; +print ''."\n"; +print '[CEDevice.x86]'."\n"; +print 'ProcessorType = 686'."\n"; +print ''."\n"; +print '[CEDevice.ARM]'."\n"; +print 'ProcessorType = 2577'."\n"; +print ''."\n"; +print '[SourceDisksNames.x86] ; CPU-dependent files'."\n"; +print '2 = ,"x86 Files",,.'."\n"; +print ''."\n"; +print '[SourceDisksNames.ARMV4] ; CPU-dependent files'."\n"; +print '2 = ,"ARM Files",,.'."\n"; +print ''."\n"; +print '[SourceDisksFiles]'."\n"; +for $exe (@exes) { + print $exe.' = 2'."\n"; +} +print ''."\n"; +print '[DefaultInstall]'."\n"; +print 'CopyFiles = PuzzleFiles'."\n"; +print 'CEShortcuts = Links'."\n"; +print ''."\n"; +print '[DestinationDirs]'."\n"; +print 'PuzzleFiles = 0, %InstallDir%'."\n"; +print 'Links = 0, %CE14%\Puzzles'."\n"; +print ''."\n"; +print ';File copy list.'."\n"; +print '[PuzzleFiles]'."\n"; +for $exe (@exes) { + print $exe."\n"; +} +print ''."\n"; +print '[Links]'."\n"; +for $exe (@exes) { + print '"'.$names{$exe}.'",0,'.$exe."\n"; +} diff --git a/apps/plugins/puzzles/webpage.pl b/apps/plugins/puzzles/webpage.pl new file mode 100755 index 0000000000..3a0779ef0a --- /dev/null +++ b/apps/plugins/puzzles/webpage.pl @@ -0,0 +1,69 @@ +#!/usr/bin/perl + +# Construct the two pieces of my main puzzle collection web page that +# need to vary with the set of puzzles: the big list of s with +# puzzle pictures and links etc, and the list of Windows executable +# files down in the downloads section. + +use strict; +use warnings; +use HTML::Entities; + +open my $desc, "<", "gamedesc.txt" + or die "gamedesc.txt: open: $!\n"; + +open my $spans, ">", "wwwspans.html" + or die "wwwspans.html: open: $!\n"; + +open my $links, ">", "wwwlinks.html" + or die "wwwspans.html: open: $!\n"; + +my $n = 0; +while (<$desc>) { + chomp; + my ($id, $win, $displayname, $description, $summary) = split /:/, $_; + + printf $spans + ''. + ''. + ''. + ''. + '
%s
'. + ''. + '
['. + ' java '. + '|'. + ' js '. + '|'. + ' manual '. + ']
['. + ' %s '. + ']
%s
'. + "\n", + encode_entities($displayname), + encode_entities($description), + encode_entities($id), + encode_entities($id), + encode_entities($id), + encode_entities($id), + encode_entities($id), + encode_entities($win), + encode_entities($win), + encode_entities($summary); + + if ($n > 0) { + if ($n % 5 == 0) { + print $links "
"; + } else { + print $links " | "; + } + } + printf $links '%s', + encode_entities($win), encode_entities($win); + + $n++; +} + +close $desc; +close $spans; +close $links; diff --git a/apps/plugins/puzzles/website.url b/apps/plugins/puzzles/website.url new file mode 100644 index 0000000000..2ab37f6faf --- /dev/null +++ b/apps/plugins/puzzles/website.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://www.chiark.greenend.org.uk/~sgtatham/puzzles/ diff --git a/apps/plugins/puzzles/windows.c b/apps/plugins/puzzles/windows.c new file mode 100644 index 0000000000..492e37ceac --- /dev/null +++ b/apps/plugins/puzzles/windows.c @@ -0,0 +1,3760 @@ +/* + * windows.c: Windows front end for my puzzle collection. + */ + +#include +#include +#ifndef NO_HTMLHELP +#include +#endif /* NO_HTMLHELP */ + +#ifdef _WIN32_WCE +#include +#include +#endif + +#include +#include "rbassert.h" +#include +#include +#include +#include +#include + +#include "puzzles.h" + +#ifdef _WIN32_WCE +#include "resource.h" +#endif + +#define IDM_NEW 0x0010 +#define IDM_RESTART 0x0020 +#define IDM_UNDO 0x0030 +#define IDM_REDO 0x0040 +#define IDM_COPY 0x0050 +#define IDM_SOLVE 0x0060 +#define IDM_QUIT 0x0070 +#define IDM_CONFIG 0x0080 +#define IDM_DESC 0x0090 +#define IDM_SEED 0x00A0 +#define IDM_HELPC 0x00B0 +#define IDM_GAMEHELP 0x00C0 +#define IDM_ABOUT 0x00D0 +#define IDM_SAVE 0x00E0 +#define IDM_LOAD 0x00F0 +#define IDM_PRINT 0x0100 +#define IDM_PRESETS 0x0110 +#define IDM_GAMES 0x0300 + +#define IDM_KEYEMUL 0x0400 + +#define HELP_FILE_NAME "puzzles.hlp" +#define HELP_CNT_NAME "puzzles.cnt" +#ifndef NO_HTMLHELP +#define CHM_FILE_NAME "puzzles.chm" +#endif /* NO_HTMLHELP */ + +#ifndef NO_HTMLHELP +typedef HWND (CALLBACK *htmlhelp_t)(HWND, LPCSTR, UINT, DWORD); +static htmlhelp_t htmlhelp; +static HINSTANCE hh_dll; +#endif /* NO_HTMLHELP */ +enum { NONE, HLP, CHM } help_type; +char *help_path; +int help_has_contents; + +#ifndef FILENAME_MAX +#define FILENAME_MAX (260) +#endif + +#ifndef HGDI_ERROR +#define HGDI_ERROR ((HANDLE)GDI_ERROR) +#endif + +#ifdef COMBINED +#define CLASSNAME "Puzzles" +#else +#define CLASSNAME thegame.name +#endif + +#ifdef _WIN32_WCE + +/* + * Wrapper implementations of functions not supplied by the + * PocketPC API. + */ + +#define SHGetSubMenu(hWndMB,ID_MENU) (HMENU)SendMessage((hWndMB), SHCMBM_GETSUBMENU, (WPARAM)0, (LPARAM)ID_MENU) + +#undef MessageBox + +int MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) +{ + TCHAR wText[2048]; + TCHAR wCaption[2048]; + + MultiByteToWideChar (CP_ACP, 0, lpText, -1, wText, 2048); + MultiByteToWideChar (CP_ACP, 0, lpCaption, -1, wCaption, 2048); + + return MessageBoxW (hWnd, wText, wCaption, uType); +} + +BOOL SetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPCSTR lpString) +{ + TCHAR wText[256]; + + MultiByteToWideChar (CP_ACP, 0, lpString, -1, wText, 256); + return SetDlgItemTextW(hDlg, nIDDlgItem, wText); +} + +LPCSTR getenv(LPCSTR buf) +{ + return NULL; +} + +BOOL GetKeyboardState(PBYTE pb) +{ + return FALSE; +} + +static TCHAR wClassName[256], wGameName[256]; + +#endif + +#ifdef DEBUGGING +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(char *buf) +{ + /*DWORD dw; + + if (!debug_got_console) { + if (AllocConsole()) { + debug_got_console = 1; + debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); + } + } + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (debug_hdl != INVALID_HANDLE_VALUE) { + WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); + } + if (debug_fp) { + fputs(buf, debug_fp); + fflush(debug_fp); + }*/ + OutputDebugString(buf); +} + +void debug_printf(char *fmt, ...) +{ + char buf[4096]; + va_list ap; + static int debugging = -1; + + if (debugging == -1) + debugging = getenv("DEBUG_PUZZLES") ? 1 : 0; + + if (debugging) { + va_start(ap, fmt); + _vsnprintf(buf, 4095, fmt, ap); + dputs(buf); + va_end(ap); + } +} +#endif + +#ifndef _WIN32_WCE +#define WINFLAGS (WS_OVERLAPPEDWINDOW &~ \ + (WS_MAXIMIZEBOX | WS_OVERLAPPED)) +#else +#define WINFLAGS (WS_CAPTION | WS_SYSMENU) +#endif + +static void new_game_size(frontend *fe, float scale); + +struct font { + HFONT font; + int type; + int size; +}; + +struct cfg_aux { + int ctlid; +}; + +struct blitter { + HBITMAP bitmap; + frontend *fe; + int x, y, w, h; +}; + +enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC }; + +struct frontend { + const game *game; + midend *me; + HWND hwnd, statusbar, cfgbox; +#ifdef _WIN32_WCE + HWND numpad; /* window handle for the numeric pad */ +#endif + HINSTANCE inst; + HBITMAP bitmap, prevbm; + RECT bitmapPosition; /* game bitmap position within game window */ + HDC hdc; + COLORREF *colours; + HBRUSH *brushes; + HPEN *pens; + HRGN clip; + HMENU gamemenu, typemenu; + UINT timer; + DWORD timer_last_tickcount; + int npresets; + game_params **presets; + struct font *fonts; + int nfonts, fontsize; + config_item *cfg; + struct cfg_aux *cfgaux; + int cfg_which, dlg_done; + HFONT cfgfont; + HBRUSH oldbr; + HPEN oldpen; + int help_running; + enum { DRAWING, PRINTING, NOTHING } drawstatus; + DOCINFO di; + int printcount, printw, printh, printsolns, printcurr, printcolour; + float printscale; + int printoffsetx, printoffsety; + float printpixelscale; + int fontstart; + int linewidth, linedotted; + drawing *dr; + int xmin, ymin; + float puzz_scale; +}; + +void frontend_free(frontend *fe) +{ + midend_free(fe->me); + + sfree(fe->colours); + sfree(fe->brushes); + sfree(fe->pens); + sfree(fe->presets); + sfree(fe->fonts); + + sfree(fe); +} + +static void update_type_menu_tick(frontend *fe); +static void update_copy_menu_greying(frontend *fe); + +void fatal(char *fmt, ...) +{ + char buf[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK); + + exit(1); +} + +char *geterrstr(void) +{ + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + char *ret; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + ret = dupstr(lpMsgBuf); + + LocalFree(lpMsgBuf); + + return ret; +} + +void get_random_seed(void **randseed, int *randseedsize) +{ + SYSTEMTIME *st = snew(SYSTEMTIME); + + GetLocalTime(st); + + *randseed = (void *)st; + *randseedsize = sizeof(SYSTEMTIME); +} + +static void win_status_bar(void *handle, char *text) +{ +#ifdef _WIN32_WCE + TCHAR wText[255]; +#endif + frontend *fe = (frontend *)handle; + +#ifdef _WIN32_WCE + MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 255); + SendMessage(fe->statusbar, SB_SETTEXT, + (WPARAM) 255 | SBT_NOBORDERS, + (LPARAM) wText); +#else + SetWindowText(fe->statusbar, text); +#endif +} + +static blitter *win_blitter_new(void *handle, int w, int h) +{ + blitter *bl = snew(blitter); + + memset(bl, 0, sizeof(blitter)); + bl->w = w; + bl->h = h; + bl->bitmap = 0; + + return bl; +} + +static void win_blitter_free(void *handle, blitter *bl) +{ + if (bl->bitmap) DeleteObject(bl->bitmap); + sfree(bl); +} + +static void blitter_mkbitmap(frontend *fe, blitter *bl) +{ + HDC hdc = GetDC(fe->hwnd); + bl->bitmap = CreateCompatibleBitmap(hdc, bl->w, bl->h); + ReleaseDC(fe->hwnd, hdc); +} + +/* BitBlt(dstDC, dstX, dstY, dstW, dstH, srcDC, srcX, srcY, dType) */ + +static void win_blitter_save(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + HDC hdc_win, hdc_blit; + HBITMAP prev_blit; + + assert(fe->drawstatus == DRAWING); + + if (!bl->bitmap) blitter_mkbitmap(fe, bl); + + bl->x = x; bl->y = y; + + hdc_win = GetDC(fe->hwnd); + hdc_blit = CreateCompatibleDC(hdc_win); + if (!hdc_blit) fatal("hdc_blit failed: 0x%x", GetLastError()); + + prev_blit = SelectObject(hdc_blit, bl->bitmap); + if (prev_blit == NULL || prev_blit == HGDI_ERROR) + fatal("SelectObject for hdc_main failed: 0x%x", GetLastError()); + + if (!BitBlt(hdc_blit, 0, 0, bl->w, bl->h, + fe->hdc, x, y, SRCCOPY)) + fatal("BitBlt failed: 0x%x", GetLastError()); + + SelectObject(hdc_blit, prev_blit); + DeleteDC(hdc_blit); + ReleaseDC(fe->hwnd, hdc_win); +} + +static void win_blitter_load(void *handle, blitter *bl, int x, int y) +{ + frontend *fe = (frontend *)handle; + HDC hdc_win, hdc_blit; + HBITMAP prev_blit; + + assert(fe->drawstatus == DRAWING); + + assert(bl->bitmap); /* we should always have saved before loading */ + + if (x == BLITTER_FROMSAVED) x = bl->x; + if (y == BLITTER_FROMSAVED) y = bl->y; + + hdc_win = GetDC(fe->hwnd); + hdc_blit = CreateCompatibleDC(hdc_win); + + prev_blit = SelectObject(hdc_blit, bl->bitmap); + + BitBlt(fe->hdc, x, y, bl->w, bl->h, + hdc_blit, 0, 0, SRCCOPY); + + SelectObject(hdc_blit, prev_blit); + DeleteDC(hdc_blit); + ReleaseDC(fe->hwnd, hdc_win); +} + +void frontend_default_colour(frontend *fe, float *output) +{ + DWORD c = GetSysColor(COLOR_MENU); /* ick */ + + output[0] = (float)(GetRValue(c) / 255.0); + output[1] = (float)(GetGValue(c) / 255.0); + output[2] = (float)(GetBValue(c) / 255.0); +} + +static POINT win_transform_point(frontend *fe, int x, int y) +{ + POINT ret; + + assert(fe->drawstatus != NOTHING); + + if (fe->drawstatus == PRINTING) { + ret.x = (int)(fe->printoffsetx + fe->printpixelscale * x); + ret.y = (int)(fe->printoffsety + fe->printpixelscale * y); + } else { + ret.x = x; + ret.y = y; + } + + return ret; +} + +static void win_text_colour(frontend *fe, int colour) +{ + assert(fe->drawstatus != NOTHING); + + if (fe->drawstatus == PRINTING) { + int hatch; + float r, g, b; + print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b); + + /* + * Displaying text in hatched colours is not permitted. + */ + assert(hatch < 0); + + SetTextColor(fe->hdc, RGB(r * 255, g * 255, b * 255)); + } else { + SetTextColor(fe->hdc, fe->colours[colour]); + } +} + +static void win_set_brush(frontend *fe, int colour) +{ + HBRUSH br; + assert(fe->drawstatus != NOTHING); + + if (fe->drawstatus == PRINTING) { + int hatch; + float r, g, b; + print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b); + + if (hatch < 0) { + br = CreateSolidBrush(RGB(r * 255, g * 255, b * 255)); + } else { +#ifdef _WIN32_WCE + /* + * This is only ever required during printing, and the + * PocketPC port doesn't support printing. + */ + fatal("CreateHatchBrush not supported"); +#else + br = CreateHatchBrush(hatch == HATCH_BACKSLASH ? HS_FDIAGONAL : + hatch == HATCH_SLASH ? HS_BDIAGONAL : + hatch == HATCH_HORIZ ? HS_HORIZONTAL : + hatch == HATCH_VERT ? HS_VERTICAL : + hatch == HATCH_PLUS ? HS_CROSS : + /* hatch == HATCH_X ? */ HS_DIAGCROSS, + RGB(0,0,0)); +#endif + } + } else { + br = fe->brushes[colour]; + } + fe->oldbr = SelectObject(fe->hdc, br); +} + +static void win_reset_brush(frontend *fe) +{ + HBRUSH br; + + assert(fe->drawstatus != NOTHING); + + br = SelectObject(fe->hdc, fe->oldbr); + if (fe->drawstatus == PRINTING) + DeleteObject(br); +} + +static void win_set_pen(frontend *fe, int colour, int thin) +{ + HPEN pen; + assert(fe->drawstatus != NOTHING); + + if (fe->drawstatus == PRINTING) { + int hatch; + float r, g, b; + int width = thin ? 0 : fe->linewidth; + + if (fe->linedotted) + width = 0; + + print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b); + /* + * Stroking in hatched colours is not permitted. + */ + assert(hatch < 0); + pen = CreatePen(fe->linedotted ? PS_DOT : PS_SOLID, + width, RGB(r * 255, g * 255, b * 255)); + } else { + pen = fe->pens[colour]; + } + fe->oldpen = SelectObject(fe->hdc, pen); +} + +static void win_reset_pen(frontend *fe) +{ + HPEN pen; + + assert(fe->drawstatus != NOTHING); + + pen = SelectObject(fe->hdc, fe->oldpen); + if (fe->drawstatus == PRINTING) + DeleteObject(pen); +} + +static void win_clip(void *handle, int x, int y, int w, int h) +{ + frontend *fe = (frontend *)handle; + POINT p, q; + + if (fe->drawstatus == NOTHING) + return; + + p = win_transform_point(fe, x, y); + q = win_transform_point(fe, x+w, y+h); + IntersectClipRect(fe->hdc, p.x, p.y, q.x, q.y); +} + +static void win_unclip(void *handle) +{ + frontend *fe = (frontend *)handle; + + if (fe->drawstatus == NOTHING) + return; + + SelectClipRgn(fe->hdc, NULL); +} + +static void win_draw_text(void *handle, int x, int y, int fonttype, + int fontsize, int align, int colour, char *text) +{ + frontend *fe = (frontend *)handle; + POINT xy; + int i; + LOGFONT lf; + + if (fe->drawstatus == NOTHING) + return; + + if (fe->drawstatus == PRINTING) + fontsize = (int)(fontsize * fe->printpixelscale); + + xy = win_transform_point(fe, x, y); + + /* + * Find or create the font. + */ + for (i = fe->fontstart; i < fe->nfonts; i++) + if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize) + break; + + if (i == fe->nfonts) { + if (fe->fontsize <= fe->nfonts) { + fe->fontsize = fe->nfonts + 10; + fe->fonts = sresize(fe->fonts, fe->fontsize, struct font); + } + + fe->nfonts++; + + fe->fonts[i].type = fonttype; + fe->fonts[i].size = fontsize; + + memset (&lf, 0, sizeof(LOGFONT)); + lf.lfHeight = -fontsize; + lf.lfWeight = (fe->drawstatus == PRINTING ? 0 : FW_BOLD); + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfOutPrecision = OUT_DEFAULT_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfPitchAndFamily = (fonttype == FONT_FIXED ? + FIXED_PITCH | FF_DONTCARE : + VARIABLE_PITCH | FF_SWISS); +#ifdef _WIN32_WCE + wcscpy(lf.lfFaceName, TEXT("Tahoma")); +#endif + + fe->fonts[i].font = CreateFontIndirect(&lf); + } + + /* + * Position and draw the text. + */ + { + HFONT oldfont; + TEXTMETRIC tm; + SIZE size; + WCHAR wText[256]; + MultiByteToWideChar (CP_UTF8, 0, text, -1, wText, 256); + + oldfont = SelectObject(fe->hdc, fe->fonts[i].font); + if (GetTextMetrics(fe->hdc, &tm)) { + if (align & ALIGN_VCENTRE) + xy.y -= (tm.tmAscent+tm.tmDescent)/2; + else + xy.y -= tm.tmAscent; + } + if (GetTextExtentPoint32W(fe->hdc, wText, wcslen(wText), &size)) + { + if (align & ALIGN_HCENTRE) + xy.x -= size.cx / 2; + else if (align & ALIGN_HRIGHT) + xy.x -= size.cx; + } + SetBkMode(fe->hdc, TRANSPARENT); + win_text_colour(fe, colour); + ExtTextOutW(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL); + SelectObject(fe->hdc, oldfont); + } +} + +static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour) +{ + frontend *fe = (frontend *)handle; + POINT p, q; + + if (fe->drawstatus == NOTHING) + return; + + if (fe->drawstatus == DRAWING && w == 1 && h == 1) { + /* + * Rectangle() appears to get uppity if asked to draw a 1x1 + * rectangle, presumably on the grounds that that's beneath + * its dignity and you ought to be using SetPixel instead. + * So I will. + */ + SetPixel(fe->hdc, x, y, fe->colours[colour]); + } else { + win_set_brush(fe, colour); + win_set_pen(fe, colour, TRUE); + p = win_transform_point(fe, x, y); + q = win_transform_point(fe, x+w, y+h); + Rectangle(fe->hdc, p.x, p.y, q.x, q.y); + win_reset_brush(fe); + win_reset_pen(fe); + } +} + +static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) +{ + frontend *fe = (frontend *)handle; + POINT pp[2]; + + if (fe->drawstatus == NOTHING) + return; + + win_set_pen(fe, colour, FALSE); + pp[0] = win_transform_point(fe, x1, y1); + pp[1] = win_transform_point(fe, x2, y2); + Polyline(fe->hdc, pp, 2); + if (fe->drawstatus == DRAWING) + SetPixel(fe->hdc, pp[1].x, pp[1].y, fe->colours[colour]); + win_reset_pen(fe); +} + +static void win_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + POINT p, q; + + assert(outlinecolour >= 0); + + if (fe->drawstatus == NOTHING) + return; + + if (fillcolour >= 0) + win_set_brush(fe, fillcolour); + else + fe->oldbr = SelectObject(fe->hdc, GetStockObject(NULL_BRUSH)); + + win_set_pen(fe, outlinecolour, FALSE); + p = win_transform_point(fe, cx - radius, cy - radius); + q = win_transform_point(fe, cx + radius, cy + radius); + Ellipse(fe->hdc, p.x, p.y, q.x+1, q.y+1); + win_reset_brush(fe); + win_reset_pen(fe); +} + +static void win_draw_polygon(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) +{ + frontend *fe = (frontend *)handle; + POINT *pts; + int i; + + if (fe->drawstatus == NOTHING) + return; + + pts = snewn(npoints+1, POINT); + + for (i = 0; i <= npoints; i++) { + int j = (i < npoints ? i : 0); + pts[i] = win_transform_point(fe, coords[j*2], coords[j*2+1]); + } + + assert(outlinecolour >= 0); + + if (fillcolour >= 0) { + win_set_brush(fe, fillcolour); + win_set_pen(fe, outlinecolour, FALSE); + Polygon(fe->hdc, pts, npoints); + win_reset_brush(fe); + win_reset_pen(fe); + } else { + win_set_pen(fe, outlinecolour, FALSE); + Polyline(fe->hdc, pts, npoints+1); + win_reset_pen(fe); + } + + sfree(pts); +} + +static void win_start_draw(void *handle) +{ + frontend *fe = (frontend *)handle; + HDC hdc_win; + + assert(fe->drawstatus == NOTHING); + + hdc_win = GetDC(fe->hwnd); + fe->hdc = CreateCompatibleDC(hdc_win); + fe->prevbm = SelectObject(fe->hdc, fe->bitmap); + ReleaseDC(fe->hwnd, hdc_win); + fe->clip = NULL; +#ifndef _WIN32_WCE + SetMapMode(fe->hdc, MM_TEXT); +#endif + fe->drawstatus = DRAWING; +} + +static void win_draw_update(void *handle, int x, int y, int w, int h) +{ + frontend *fe = (frontend *)handle; + RECT r; + + if (fe->drawstatus != DRAWING) + return; + + r.left = x; + r.top = y; + r.right = x + w; + r.bottom = y + h; + + OffsetRect(&r, fe->bitmapPosition.left, fe->bitmapPosition.top); + InvalidateRect(fe->hwnd, &r, FALSE); +} + +static void win_end_draw(void *handle) +{ + frontend *fe = (frontend *)handle; + assert(fe->drawstatus == DRAWING); + SelectObject(fe->hdc, fe->prevbm); + DeleteDC(fe->hdc); + if (fe->clip) { + DeleteObject(fe->clip); + fe->clip = NULL; + } + fe->drawstatus = NOTHING; +} + +static void win_line_width(void *handle, float width) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + if (fe->drawstatus == NOTHING) + return; + + fe->linewidth = (int)(width * fe->printpixelscale); +} + +static void win_line_dotted(void *handle, int dotted) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + if (fe->drawstatus == NOTHING) + return; + + fe->linedotted = dotted; +} + +static void win_begin_doc(void *handle, int pages) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + if (fe->drawstatus == NOTHING) + return; + + if (StartDoc(fe->hdc, &fe->di) <= 0) { + char *e = geterrstr(); + MessageBox(fe->hwnd, e, "Error starting to print", + MB_ICONERROR | MB_OK); + sfree(e); + fe->drawstatus = NOTHING; + } + + /* + * Push a marker on the font stack so that we won't use the + * same fonts for printing and drawing. (This is because + * drawing seems to look generally better in bold, but printing + * is better not in bold.) + */ + fe->fontstart = fe->nfonts; +} + +static void win_begin_page(void *handle, int number) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + if (fe->drawstatus == NOTHING) + return; + + if (StartPage(fe->hdc) <= 0) { + char *e = geterrstr(); + MessageBox(fe->hwnd, e, "Error starting a page", + MB_ICONERROR | MB_OK); + sfree(e); + fe->drawstatus = NOTHING; + } +} + +static void win_begin_puzzle(void *handle, float xm, float xc, + float ym, float yc, int pw, int ph, float wmm) +{ + frontend *fe = (frontend *)handle; + int ppw, pph, pox, poy; + float mmpw, mmph, mmox, mmoy; + float scale; + + assert(fe->drawstatus != DRAWING); + if (fe->drawstatus == NOTHING) + return; + + ppw = GetDeviceCaps(fe->hdc, HORZRES); + pph = GetDeviceCaps(fe->hdc, VERTRES); + mmpw = (float)GetDeviceCaps(fe->hdc, HORZSIZE); + mmph = (float)GetDeviceCaps(fe->hdc, VERTSIZE); + + /* + * Compute the puzzle's position on the logical page. + */ + mmox = xm * mmpw + xc; + mmoy = ym * mmph + yc; + + /* + * Work out what that comes to in pixels. + */ + pox = (int)(mmox * (float)ppw / mmpw); + poy = (int)(mmoy * (float)pph / mmph); + + /* + * And determine the scale. + * + * I need a scale such that the maximum puzzle-coordinate + * extent of the rectangle (pw * scale) is equal to the pixel + * equivalent of the puzzle's millimetre width (wmm * ppw / + * mmpw). + */ + scale = (wmm * ppw) / (mmpw * pw); + + /* + * Now store pox, poy and scale for use in the main drawing + * functions. + */ + fe->printoffsetx = pox; + fe->printoffsety = poy; + fe->printpixelscale = scale; + + fe->linewidth = 1; + fe->linedotted = FALSE; +} + +static void win_end_puzzle(void *handle) +{ + /* Nothing needs to be done here. */ +} + +static void win_end_page(void *handle, int number) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + + if (fe->drawstatus == NOTHING) + return; + + if (EndPage(fe->hdc) <= 0) { + char *e = geterrstr(); + MessageBox(fe->hwnd, e, "Error finishing a page", + MB_ICONERROR | MB_OK); + sfree(e); + fe->drawstatus = NOTHING; + } +} + +static void win_end_doc(void *handle) +{ + frontend *fe = (frontend *)handle; + + assert(fe->drawstatus != DRAWING); + + /* + * Free all the fonts created since we began printing. + */ + while (fe->nfonts > fe->fontstart) { + fe->nfonts--; + DeleteObject(fe->fonts[fe->nfonts].font); + } + fe->fontstart = 0; + + /* + * The MSDN web site sample code doesn't bother to call EndDoc + * if an error occurs half way through printing. I expect doing + * so would cause the erroneous document to actually be + * printed, or something equally undesirable. + */ + if (fe->drawstatus == NOTHING) + return; + + if (EndDoc(fe->hdc) <= 0) { + char *e = geterrstr(); + MessageBox(fe->hwnd, e, "Error finishing printing", + MB_ICONERROR | MB_OK); + sfree(e); + fe->drawstatus = NOTHING; + } +} + +char *win_text_fallback(void *handle, const char *const *strings, int nstrings) +{ + /* + * We assume Windows can cope with any UTF-8 likely to be + * emitted by a puzzle. + */ + return dupstr(strings[0]); +} + +const struct drawing_api win_drawing = { + win_draw_text, + win_draw_rect, + win_draw_line, + win_draw_polygon, + win_draw_circle, + win_draw_update, + win_clip, + win_unclip, + win_start_draw, + win_end_draw, + win_status_bar, + win_blitter_new, + win_blitter_free, + win_blitter_save, + win_blitter_load, + win_begin_doc, + win_begin_page, + win_begin_puzzle, + win_end_puzzle, + win_end_page, + win_end_doc, + win_line_width, + win_line_dotted, + win_text_fallback, +}; + +void print(frontend *fe) +{ +#ifndef _WIN32_WCE + PRINTDLG pd; + char doctitle[256]; + document *doc; + midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */ + int i; + char *err = NULL; + + /* + * Create our document structure and fill it up with puzzles. + */ + doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F); + for (i = 0; i < fe->printcount; i++) { + if (i == 0 && fe->printcurr) { + err = midend_print_puzzle(fe->me, doc, fe->printsolns); + } else { + if (!nme) { + game_params *params; + + nme = midend_new(NULL, fe->game, NULL, NULL); + + /* + * Set the non-interactive mid-end to have the same + * parameters as the standard one. + */ + params = midend_get_params(fe->me); + midend_set_params(nme, params); + fe->game->free_params(params); + } + + midend_new_game(nme); + err = midend_print_puzzle(nme, doc, fe->printsolns); + } + if (err) + break; + } + if (nme) + midend_free(nme); + + if (err) { + MessageBox(fe->hwnd, err, "Error preparing puzzles for printing", + MB_ICONERROR | MB_OK); + document_free(doc); + return; + } + + memset(&pd, 0, sizeof(pd)); + pd.lStructSize = sizeof(pd); + pd.hwndOwner = fe->hwnd; + pd.hDevMode = NULL; + pd.hDevNames = NULL; + pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC | + PD_NOPAGENUMS | PD_NOSELECTION; + pd.nCopies = 1; + pd.nFromPage = pd.nToPage = 0xFFFF; + pd.nMinPage = pd.nMaxPage = 1; + + if (!PrintDlg(&pd)) { + document_free(doc); + return; + } + + /* + * Now pd.hDC is a device context for the printer. + */ + + /* + * FIXME: IWBNI we put up an Abort box here. + */ + + memset(&fe->di, 0, sizeof(fe->di)); + fe->di.cbSize = sizeof(fe->di); + sprintf(doctitle, "Printed puzzles from %s (from Simon Tatham's" + " Portable Puzzle Collection)", fe->game->name); + fe->di.lpszDocName = doctitle; + fe->di.lpszOutput = NULL; + fe->di.lpszDatatype = NULL; + fe->di.fwType = 0; + + fe->drawstatus = PRINTING; + fe->hdc = pd.hDC; + + fe->dr = drawing_new(&win_drawing, NULL, fe); + document_print(doc, fe->dr); + drawing_free(fe->dr); + fe->dr = NULL; + + fe->drawstatus = NOTHING; + + DeleteDC(pd.hDC); + document_free(doc); +#endif +} + +void deactivate_timer(frontend *fe) +{ + if (!fe) + return; /* for non-interactive midend */ + if (fe->hwnd) KillTimer(fe->hwnd, fe->timer); + fe->timer = 0; +} + +void activate_timer(frontend *fe) +{ + if (!fe) + return; /* for non-interactive midend */ + if (!fe->timer) { + fe->timer = SetTimer(fe->hwnd, 1, 20, NULL); + fe->timer_last_tickcount = GetTickCount(); + } +} + +void write_clip(HWND hwnd, char *data) +{ + HGLOBAL clipdata; + int len, i, j; + char *data2; + void *lock; + + /* + * Windows expects CRLF in the clipboard, so we must convert + * any \n that has come out of the puzzle backend. + */ + len = 0; + for (i = 0; data[i]; i++) { + if (data[i] == '\n') + len++; + len++; + } + data2 = snewn(len+1, char); + j = 0; + for (i = 0; data[i]; i++) { + if (data[i] == '\n') + data2[j++] = '\r'; + data2[j++] = data[i]; + } + assert(j == len); + data2[j] = '\0'; + + clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1); + if (!clipdata) { + sfree(data2); + return; + } + lock = GlobalLock(clipdata); + if (!lock) { + GlobalFree(clipdata); + sfree(data2); + return; + } + memcpy(lock, data2, len); + ((unsigned char *) lock)[len] = 0; + GlobalUnlock(clipdata); + + if (OpenClipboard(hwnd)) { + EmptyClipboard(); + SetClipboardData(CF_TEXT, clipdata); + CloseClipboard(); + } else + GlobalFree(clipdata); + + sfree(data2); +} + +/* + * Set up Help and see if we can find a help file. + */ +static void init_help(void) +{ +#ifndef _WIN32_WCE + char b[2048], *p, *q, *r; + FILE *fp; + + /* + * Find the executable file path, so we can look alongside + * it for help files. Trim the filename off the end. + */ + GetModuleFileName(NULL, b, sizeof(b) - 1); + r = b; + p = strrchr(b, '\\'); + if (p && p >= r) r = p+1; + q = strrchr(b, ':'); + if (q && q >= r) r = q+1; + +#ifndef NO_HTMLHELP + /* + * Try HTML Help first. + */ + strcpy(r, CHM_FILE_NAME); + if ( (fp = fopen(b, "r")) != NULL) { + fclose(fp); + + /* + * We have a .CHM. See if we can use it. + */ + hh_dll = LoadLibrary("hhctrl.ocx"); + if (hh_dll) { + htmlhelp = (htmlhelp_t)GetProcAddress(hh_dll, "HtmlHelpA"); + if (!htmlhelp) + FreeLibrary(hh_dll); + } + if (htmlhelp) { + help_path = dupstr(b); + help_type = CHM; + return; + } + } +#endif /* NO_HTMLHELP */ + + /* + * Now try old-style .HLP. + */ + strcpy(r, HELP_FILE_NAME); + if ( (fp = fopen(b, "r")) != NULL) { + fclose(fp); + + help_path = dupstr(b); + help_type = HLP; + + /* + * See if there's a .CNT file alongside it. + */ + strcpy(r, HELP_CNT_NAME); + if ( (fp = fopen(b, "r")) != NULL) { + fclose(fp); + help_has_contents = TRUE; + } else + help_has_contents = FALSE; + + return; + } + + help_type = NONE; /* didn't find any */ +#endif +} + +#ifndef _WIN32_WCE + +/* + * Start Help. + */ +static void start_help(frontend *fe, const char *topic) +{ + char *str = NULL; + int cmd; + + switch (help_type) { + case HLP: + assert(help_path); + if (topic) { + str = snewn(10+strlen(topic), char); + sprintf(str, "JI(`',`%s')", topic); + cmd = HELP_COMMAND; + } else if (help_has_contents) { + cmd = HELP_FINDER; + } else { + cmd = HELP_CONTENTS; + } + WinHelp(fe->hwnd, help_path, cmd, (DWORD)str); + fe->help_running = TRUE; + break; + case CHM: +#ifndef NO_HTMLHELP + assert(help_path); + assert(htmlhelp); + if (topic) { + str = snewn(20 + strlen(topic) + strlen(help_path), char); + sprintf(str, "%s::/%s.html>main", help_path, topic); + } else { + str = dupstr(help_path); + } + htmlhelp(fe->hwnd, str, HH_DISPLAY_TOPIC, 0); + fe->help_running = TRUE; + break; +#endif /* NO_HTMLHELP */ + case NONE: + assert(!"This shouldn't happen"); + break; + } + + sfree(str); +} + +/* + * Stop Help on window cleanup. + */ +static void stop_help(frontend *fe) +{ + if (fe->help_running) { + switch (help_type) { + case HLP: + WinHelp(fe->hwnd, help_path, HELP_QUIT, 0); + break; + case CHM: +#ifndef NO_HTMLHELP + assert(htmlhelp); + htmlhelp(NULL, NULL, HH_CLOSE_ALL, 0); + break; +#endif /* NO_HTMLHELP */ + case NONE: + assert(!"This shouldn't happen"); + break; + } + fe->help_running = FALSE; + } +} + +#endif + +/* + * Terminate Help on process exit. + */ +static void cleanup_help(void) +{ + /* Nothing to do currently. + * (If we were running HTML Help single-threaded, this is where we'd + * call HH_UNINITIALIZE.) */ +} + +static int get_statusbar_height(frontend *fe) +{ + int sy; + if (fe->statusbar) { + RECT sr; + GetWindowRect(fe->statusbar, &sr); + sy = sr.bottom - sr.top; + } else { + sy = 0; + } + return sy; +} + +static void adjust_statusbar(frontend *fe, RECT *r) +{ + int sy; + + if (!fe->statusbar) return; + + sy = get_statusbar_height(fe); +#ifndef _WIN32_WCE + SetWindowPos(fe->statusbar, NULL, 0, r->bottom-r->top-sy, r->right-r->left, + sy, SWP_NOZORDER); +#endif +} + +static void get_menu_size(HWND wh, RECT *r) +{ + HMENU bar = GetMenu(wh); + RECT rect; + int i; + + SetRect(r, 0, 0, 0, 0); + for (i = 0; i < GetMenuItemCount(bar); i++) { + GetMenuItemRect(wh, bar, i, &rect); + UnionRect(r, r, &rect); + } +} + +/* + * Given a proposed new puzzle size (cx,cy), work out the actual + * puzzle size that would be (px,py) and the window size including + * furniture (wx,wy). + */ + +static int check_window_resize(frontend *fe, int cx, int cy, + int *px, int *py, + int *wx, int *wy) +{ + RECT r; + int x, y, sy = get_statusbar_height(fe), changed = 0; + + /* disallow making window thinner than menu bar */ + x = max(cx, fe->xmin); + y = max(cy - sy, fe->ymin); + + /* + * See if we actually got the window size we wanted, and adjust + * the puzzle size if not. + */ + midend_size(fe->me, &x, &y, TRUE); + if (x != cx || y != cy) { + /* + * Resize the window, now we know what size we _really_ + * want it to be. + */ + r.left = r.top = 0; + r.right = x; + r.bottom = y + sy; + AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0); + *wx = r.right - r.left; + *wy = r.bottom - r.top; + changed = 1; + } + + *px = x; + *py = y; + + fe->puzz_scale = + (float)midend_tilesize(fe->me) / (float)fe->game->preferred_tilesize; + + return changed; +} + +/* + * Given the current window size, make sure it's sane for the + * current puzzle and resize if necessary. + */ + +static void check_window_size(frontend *fe, int *px, int *py) +{ + RECT r; + int wx, wy, cx, cy; + + GetClientRect(fe->hwnd, &r); + cx = r.right - r.left; + cy = r.bottom - r.top; + + if (check_window_resize(fe, cx, cy, px, py, &wx, &wy)) { +#ifdef _WIN32_WCE + SetWindowPos(fe->hwnd, NULL, 0, 0, wx, wy, + SWP_NOMOVE | SWP_NOZORDER); +#endif + ; + } + + GetClientRect(fe->hwnd, &r); + adjust_statusbar(fe, &r); +} + +static void get_max_puzzle_size(frontend *fe, int *x, int *y) +{ + RECT r, sr; + + if (SystemParametersInfo(SPI_GETWORKAREA, 0, &sr, FALSE)) { + *x = sr.right - sr.left; + *y = sr.bottom - sr.top; + r.left = 100; + r.right = 200; + r.top = 100; + r.bottom = 200; + AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0); + *x -= r.right - r.left - 100; + *y -= r.bottom - r.top - 100; + } else { + *x = *y = INT_MAX; + } + + if (fe->statusbar != NULL) { + GetWindowRect(fe->statusbar, &sr); + *y -= sr.bottom - sr.top; + } +} + +#ifdef _WIN32_WCE +/* Toolbar buttons on the numeric pad */ +static TBBUTTON tbNumpadButtons[] = +{ + {0, IDM_KEYEMUL + '1', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {1, IDM_KEYEMUL + '2', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {2, IDM_KEYEMUL + '3', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {3, IDM_KEYEMUL + '4', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {4, IDM_KEYEMUL + '5', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {5, IDM_KEYEMUL + '6', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {6, IDM_KEYEMUL + '7', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {7, IDM_KEYEMUL + '8', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {8, IDM_KEYEMUL + '9', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1}, + {9, IDM_KEYEMUL + ' ', TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, -1} +}; +#endif + +/* + * Allocate a new frontend structure and create its main window. + */ +static frontend *frontend_new(HINSTANCE inst) +{ + frontend *fe; + const char *nogame = "Puzzles (no game selected)"; + + fe = snew(frontend); + + fe->inst = inst; + + fe->game = NULL; + fe->me = NULL; + + fe->timer = 0; + fe->hwnd = NULL; + + fe->help_running = FALSE; + + fe->drawstatus = NOTHING; + fe->dr = NULL; + fe->fontstart = 0; + + fe->fonts = NULL; + fe->nfonts = fe->fontsize = 0; + + fe->colours = NULL; + fe->brushes = NULL; + fe->pens = NULL; + + fe->puzz_scale = 1.0; + + #ifdef _WIN32_WCE + MultiByteToWideChar (CP_ACP, 0, nogame, -1, wGameName, 256); + fe->hwnd = CreateWindowEx(0, wClassName, wGameName, + WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, inst, NULL); + + { + SHMENUBARINFO mbi; + RECT rc, rcBar, rcTB, rcClient; + + memset (&mbi, 0, sizeof(SHMENUBARINFO)); + mbi.cbSize = sizeof(SHMENUBARINFO); + mbi.hwndParent = fe->hwnd; + mbi.nToolBarId = IDR_MENUBAR1; + mbi.hInstRes = inst; + + SHCreateMenuBar(&mbi); + + GetWindowRect(fe->hwnd, &rc); + GetWindowRect(mbi.hwndMB, &rcBar); + rc.bottom -= rcBar.bottom - rcBar.top; + MoveWindow(fe->hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE); + + fe->numpad = NULL; + } +#else + fe->hwnd = CreateWindowEx(0, CLASSNAME, nogame, + WS_OVERLAPPEDWINDOW &~ + (WS_MAXIMIZEBOX), + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, inst, NULL); + if (!fe->hwnd) { + DWORD lerr = GetLastError(); + printf("no window: 0x%x\n", lerr); + } +#endif + + fe->gamemenu = NULL; + fe->presets = NULL; + + fe->statusbar = NULL; + fe->bitmap = NULL; + + SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe); + + return fe; +} + +static void savefile_write(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + fwrite(buf, 1, len, fp); +} + +static int savefile_read(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + int ret; + + ret = fread(buf, 1, len, fp); + return (ret == len); +} + +/* + * Create an appropriate midend structure to go in a puzzle window, + * given a game type and/or a command-line argument. + * + * 'arg' can be either a game ID string (descriptive, random, or a + * plain set of parameters) or the filename of a save file. The two + * boolean flag arguments indicate which possibilities are + * permissible. + */ +static midend *midend_for_new_game(frontend *fe, const game *cgame, + char *arg, int maybe_game_id, + int maybe_save_file, char **error) +{ + midend *me = NULL; + + if (!arg) { + if (me) midend_free(me); + me = midend_new(fe, cgame, &win_drawing, fe); + midend_new_game(me); + } else { + FILE *fp; + char *err_param, *err_load; + + /* + * See if arg is a valid filename of a save game file. + */ + err_load = NULL; + if (maybe_save_file && (fp = fopen(arg, "r")) != NULL) { + const game *loadgame; + +#ifdef COMBINED + /* + * Find out what kind of game is stored in the save + * file; if we're going to end up loading that, it + * will have to override our caller's judgment as to + * what game to initialise our midend with. + */ + char *id_name; + err_load = identify_game(&id_name, savefile_read, fp); + if (!err_load) { + int i; + for (i = 0; i < gamecount; i++) + if (!strcmp(id_name, gamelist[i]->name)) + break; + if (i == gamecount) { + err_load = "Save file is for a game not supported by" + " this program"; + } else { + loadgame = gamelist[i]; + rewind(fp); /* go back to the start for actual load */ + } + } +#else + loadgame = cgame; +#endif + if (!err_load) { + if (me) midend_free(me); + me = midend_new(fe, loadgame, &win_drawing, fe); + err_load = midend_deserialise(me, savefile_read, fp); + } + } else { + err_load = "Unable to open file"; + } + + if (maybe_game_id && (!maybe_save_file || err_load)) { + /* + * See if arg is a game description. + */ + if (me) midend_free(me); + me = midend_new(fe, cgame, &win_drawing, fe); + err_param = midend_game_id(me, arg); + if (!err_param) { + midend_new_game(me); + } else { + if (maybe_save_file) { + *error = snewn(256 + strlen(arg) + strlen(err_param) + + strlen(err_load), char); + sprintf(*error, "Supplied argument \"%s\" is neither a" + " game ID (%s) nor a save file (%s)", + arg, err_param, err_load); + } else { + *error = dupstr(err_param); + } + midend_free(me); + sfree(fe); + return NULL; + } + } else if (err_load) { + *error = dupstr(err_load); + midend_free(me); + sfree(fe); + return NULL; + } + } + + return me; +} + +/* + * Populate a frontend structure with a new midend structure, and + * create any window furniture that it needs. + * + * Previously-allocated memory and window furniture will be freed by + * this function. + * + */ +static int fe_set_midend(frontend *fe, midend *me) +{ + int x, y; + RECT r; + + if (fe->me) midend_free(fe->me); + fe->me = me; + fe->game = midend_which_game(fe->me); + + { + int i, ncolours; + float *colours; + + colours = midend_colours(fe->me, &ncolours); + + if (fe->colours) sfree(fe->colours); + if (fe->brushes) sfree(fe->brushes); + if (fe->pens) sfree(fe->pens); + + fe->colours = snewn(ncolours, COLORREF); + fe->brushes = snewn(ncolours, HBRUSH); + fe->pens = snewn(ncolours, HPEN); + + for (i = 0; i < ncolours; i++) { + fe->colours[i] = RGB(255 * colours[i*3+0], + 255 * colours[i*3+1], + 255 * colours[i*3+2]); + fe->brushes[i] = CreateSolidBrush(fe->colours[i]); + fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]); + } + sfree(colours); + } + + if (fe->statusbar) + DestroyWindow(fe->statusbar); + if (midend_wants_statusbar(fe->me)) { + fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, + TEXT(DEFAULT_STATUSBAR_TEXT), + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, /* status bar does these */ + NULL, NULL, fe->inst, NULL); + } else + fe->statusbar = NULL; + + get_max_puzzle_size(fe, &x, &y); + midend_size(fe->me, &x, &y, FALSE); + + r.left = r.top = 0; + r.right = x; + r.bottom = y; + AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0); + +#ifdef _WIN32_WCE + if (fe->numpad) + DestroyWindow(fe->numpad); + if (fe->game->flags & REQUIRE_NUMPAD) + { + fe->numpad = CreateToolbarEx (fe->hwnd, + WS_VISIBLE | WS_CHILD | CCS_NOPARENTALIGN | TBSTYLE_FLAT, + 0, 10, fe->inst, IDR_PADTOOLBAR, + tbNumpadButtons, sizeof (tbNumpadButtons) / sizeof (TBBUTTON), + 0, 0, 14, 15, sizeof (TBBUTTON)); + GetWindowRect(fe->numpad, &rcTB); + GetClientRect(fe->hwnd, &rcClient); + MoveWindow(fe->numpad, + 0, + rcClient.bottom - (rcTB.bottom - rcTB.top) - 1, + rcClient.right, + rcTB.bottom - rcTB.top, + FALSE); + SendMessage(fe->numpad, TB_SETINDENT, (rcClient.right - (10 * 21)) / 2, 0); + } + else { + fe->numpad = NULL; + } + MultiByteToWideChar (CP_ACP, 0, fe->game->name, -1, wGameName, 256); + SetWindowText(fe->hwnd, wGameName); +#else + SetWindowText(fe->hwnd, fe->game->name); +#endif + + if (fe->statusbar) + DestroyWindow(fe->statusbar); + if (midend_wants_statusbar(fe->me)) { + RECT sr; + fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"), + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, /* status bar does these */ + fe->hwnd, NULL, fe->inst, NULL); +#ifdef _WIN32_WCE + /* Flat status bar looks better on the Pocket PC */ + SendMessage(fe->statusbar, SB_SIMPLE, (WPARAM) TRUE, 0); + SendMessage(fe->statusbar, SB_SETTEXT, + (WPARAM) 255 | SBT_NOBORDERS, + (LPARAM) L""); +#endif + + /* + * Now resize the window to take account of the status bar. + */ + GetWindowRect(fe->statusbar, &sr); + GetWindowRect(fe->hwnd, &r); +#ifndef _WIN32_WCE + SetWindowPos(fe->hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + sr.bottom - sr.top, + SWP_NOMOVE | SWP_NOZORDER); +#endif + } else { + fe->statusbar = NULL; + } + + { + HMENU oldmenu = GetMenu(fe->hwnd); + +#ifndef _WIN32_WCE + HMENU bar = CreateMenu(); + HMENU menu = CreateMenu(); + RECT menusize; + + AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "&Game"); +#else + HMENU menu = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_GAME); + DeleteMenu(menu, 0, MF_BYPOSITION); +#endif + fe->gamemenu = menu; + AppendMenu(menu, MF_ENABLED, IDM_NEW, TEXT("&New")); + AppendMenu(menu, MF_ENABLED, IDM_RESTART, TEXT("&Restart")); +#ifndef _WIN32_WCE + /* ...here I run out of sensible accelerator characters. */ + AppendMenu(menu, MF_ENABLED, IDM_DESC, TEXT("Speci&fic...")); + AppendMenu(menu, MF_ENABLED, IDM_SEED, TEXT("Rando&m Seed...")); +#endif + + if (fe->presets) + sfree(fe->presets); + if ((fe->npresets = midend_num_presets(fe->me)) > 0 || + fe->game->can_configure) { + int i; +#ifndef _WIN32_WCE + HMENU sub = CreateMenu(); + + AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)sub, "&Type"); +#else + HMENU sub = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_TYPE); + DeleteMenu(sub, 0, MF_BYPOSITION); +#endif + fe->presets = snewn(fe->npresets, game_params *); + + for (i = 0; i < fe->npresets; i++) { + char *name; +#ifdef _WIN32_WCE + TCHAR wName[255]; +#endif + + midend_fetch_preset(fe->me, i, &name, &fe->presets[i]); + + /* + * FIXME: we ought to go through and do something + * with ampersands here. + */ + +#ifndef _WIN32_WCE + AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name); +#else + MultiByteToWideChar (CP_ACP, 0, name, -1, wName, 255); + AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, wName); +#endif + } + if (fe->game->can_configure) { + AppendMenu(sub, MF_ENABLED, IDM_CONFIG, TEXT("&Custom...")); + } + + fe->typemenu = sub; + } else { + fe->typemenu = INVALID_HANDLE_VALUE; + fe->presets = NULL; + } + +#ifdef COMBINED +#ifdef _WIN32_WCE +#error Windows CE does not support COMBINED build. +#endif + { + HMENU games = CreateMenu(); + int i; + + AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)games, "&Other"); + for (i = 0; i < gamecount; i++) { + if (strcmp(gamelist[i]->name, fe->game->name) != 0) { + /* only include those games that aren't the same as the + * game we're currently playing. */ + AppendMenu(games, MF_ENABLED, IDM_GAMES + i, gamelist[i]->name); + } + } + } +#endif + + AppendMenu(menu, MF_SEPARATOR, 0, 0); +#ifndef _WIN32_WCE + AppendMenu(menu, MF_ENABLED, IDM_LOAD, TEXT("&Load...")); + AppendMenu(menu, MF_ENABLED, IDM_SAVE, TEXT("&Save...")); + AppendMenu(menu, MF_SEPARATOR, 0, 0); + if (fe->game->can_print) { + AppendMenu(menu, MF_ENABLED, IDM_PRINT, TEXT("&Print...")); + AppendMenu(menu, MF_SEPARATOR, 0, 0); + } +#endif + AppendMenu(menu, MF_ENABLED, IDM_UNDO, TEXT("Undo")); + AppendMenu(menu, MF_ENABLED, IDM_REDO, TEXT("Redo")); +#ifndef _WIN32_WCE + if (fe->game->can_format_as_text_ever) { + AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED, IDM_COPY, TEXT("&Copy")); + } +#endif + if (fe->game->can_solve) { + AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve")); + } + AppendMenu(menu, MF_SEPARATOR, 0, 0); +#ifndef _WIN32_WCE + AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit")); + menu = CreateMenu(); + AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, TEXT("&Help")); +#endif + AppendMenu(menu, MF_ENABLED, IDM_ABOUT, TEXT("&About")); +#ifndef _WIN32_WCE + if (help_type != NONE) { + char *item; + AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED, IDM_HELPC, TEXT("&Contents")); + assert(fe->game->name); + item = snewn(10+strlen(fe->game->name), char); /*ick*/ + sprintf(item, "&Help on %s", fe->game->name); + AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item); + sfree(item); + } + DestroyMenu(oldmenu); + SetMenu(fe->hwnd, bar); + get_menu_size(fe->hwnd, &menusize); + fe->xmin = (menusize.right - menusize.left) + 25; +#endif + } + + if (fe->bitmap) DeleteObject(fe->bitmap); + fe->bitmap = NULL; + new_game_size(fe, fe->puzz_scale); /* initialises fe->bitmap */ + + return 0; +} + +static void show_window(frontend *fe) +{ + ShowWindow(fe->hwnd, SW_SHOWNORMAL); + SetForegroundWindow(fe->hwnd); + + update_type_menu_tick(fe); + update_copy_menu_greying(fe); + + midend_redraw(fe->me); +} + +#ifdef _WIN32_WCE +static HFONT dialog_title_font() +{ + static HFONT hf = NULL; + LOGFONT lf; + + if (hf) + return hf; + + memset (&lf, 0, sizeof(LOGFONT)); + lf.lfHeight = -11; /* - ((8 * GetDeviceCaps(hdc, LOGPIXELSY)) / 72) */ + lf.lfWeight = FW_BOLD; + wcscpy(lf.lfFaceName, TEXT("Tahoma")); + + return hf = CreateFontIndirect(&lf); +} + +static void make_dialog_full_screen(HWND hwnd) +{ + SHINITDLGINFO shidi; + + /* Make dialog full screen */ + shidi.dwMask = SHIDIM_FLAGS; + shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN | + SHIDIF_EMPTYMENU; + shidi.hDlg = hwnd; + SHInitDialog(&shidi); +} +#endif + +static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + + switch (msg) { + case WM_INITDIALOG: +#ifdef _WIN32_WCE + { + char title[256]; + + make_dialog_full_screen(hwnd); + + sprintf(title, "About %.250s", fe->game->name); + SetDlgItemTextA(hwnd, IDC_ABOUT_CAPTION, title); + + SendDlgItemMessage(hwnd, IDC_ABOUT_CAPTION, WM_SETFONT, + (WPARAM) dialog_title_font(), 0); + + SetDlgItemTextA(hwnd, IDC_ABOUT_GAME, fe->game->name); + SetDlgItemTextA(hwnd, IDC_ABOUT_VERSION, ver); + } +#endif + return TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK) +#ifdef _WIN32_WCE + EndDialog(hwnd, 1); +#else + fe->dlg_done = 1; +#endif + return 0; + + case WM_CLOSE: +#ifdef _WIN32_WCE + EndDialog(hwnd, 1); +#else + fe->dlg_done = 1; +#endif + return 0; + } + + return 0; +} + +/* + * Wrappers on midend_{get,set}_config, which extend the CFG_* + * enumeration to add CFG_PRINT. + */ +static config_item *frontend_get_config(frontend *fe, int which, + char **wintitle) +{ + if (which < CFG_FRONTEND_SPECIFIC) { + return midend_get_config(fe->me, which, wintitle); + } else if (which == CFG_PRINT) { + config_item *ret; + int i; + + *wintitle = snewn(40 + strlen(fe->game->name), char); + sprintf(*wintitle, "%s print setup", fe->game->name); + + ret = snewn(8, config_item); + + i = 0; + + ret[i].name = "Number of puzzles to print"; + ret[i].type = C_STRING; + ret[i].sval = dupstr("1"); + ret[i].ival = 0; + i++; + + ret[i].name = "Number of puzzles across the page"; + ret[i].type = C_STRING; + ret[i].sval = dupstr("1"); + ret[i].ival = 0; + i++; + + ret[i].name = "Number of puzzles down the page"; + ret[i].type = C_STRING; + ret[i].sval = dupstr("1"); + ret[i].ival = 0; + i++; + + ret[i].name = "Percentage of standard size"; + ret[i].type = C_STRING; + ret[i].sval = dupstr("100.0"); + ret[i].ival = 0; + i++; + + ret[i].name = "Include currently shown puzzle"; + ret[i].type = C_BOOLEAN; + ret[i].sval = NULL; + ret[i].ival = TRUE; + i++; + + ret[i].name = "Print solutions"; + ret[i].type = C_BOOLEAN; + ret[i].sval = NULL; + ret[i].ival = FALSE; + i++; + + if (fe->game->can_print_in_colour) { + ret[i].name = "Print in colour"; + ret[i].type = C_BOOLEAN; + ret[i].sval = NULL; + ret[i].ival = FALSE; + i++; + } + + ret[i].name = NULL; + ret[i].type = C_END; + ret[i].sval = NULL; + ret[i].ival = 0; + i++; + + return ret; + } else { + assert(!"We should never get here"); + return NULL; + } +} + +static char *frontend_set_config(frontend *fe, int which, config_item *cfg) +{ + if (which < CFG_FRONTEND_SPECIFIC) { + return midend_set_config(fe->me, which, cfg); + } else if (which == CFG_PRINT) { + if ((fe->printcount = atoi(cfg[0].sval)) <= 0) + return "Number of puzzles to print should be at least one"; + if ((fe->printw = atoi(cfg[1].sval)) <= 0) + return "Number of puzzles across the page should be at least one"; + if ((fe->printh = atoi(cfg[2].sval)) <= 0) + return "Number of puzzles down the page should be at least one"; + if ((fe->printscale = (float)atof(cfg[3].sval)) <= 0) + return "Print size should be positive"; + fe->printcurr = cfg[4].ival; + fe->printsolns = cfg[5].ival; + fe->printcolour = fe->game->can_print_in_colour && cfg[6].ival; + return NULL; + } else { + assert(!"We should never get here"); + return "Internal error"; + } +} + +#ifdef _WIN32_WCE +/* Separate version of mkctrl function for the Pocket PC. */ +/* Control coordinates should be specified in dialog units. */ +HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2, + LPCTSTR wclass, int wstyle, + int exstyle, const char *wtext, int wid) +{ + RECT rc; + TCHAR wwtext[256]; + + /* Convert dialog units into pixels */ + rc.left = x1; rc.right = x2; + rc.top = y1; rc.bottom = y2; + MapDialogRect(fe->cfgbox, &rc); + + MultiByteToWideChar (CP_ACP, 0, wtext, -1, wwtext, 256); + + return CreateWindowEx(exstyle, wclass, wwtext, + wstyle | WS_CHILD | WS_VISIBLE, + rc.left, rc.top, + rc.right - rc.left, rc.bottom - rc.top, + fe->cfgbox, (HMENU) wid, fe->inst, NULL); +} + +static void create_config_controls(frontend * fe) +{ + int id, nctrls; + int col1l, col1r, col2l, col2r, y; + config_item *i; + struct cfg_aux *j; + HWND ctl; + + /* Control placement done in dialog units */ + col1l = 4; col1r = 96; /* Label column */ + col2l = 100; col2r = 154; /* Input column (edit boxes and combo boxes) */ + + /* + * Count the controls so we can allocate cfgaux. + */ + for (nctrls = 0, i = fe->cfg; i->type != C_END; i++) + nctrls++; + fe->cfgaux = snewn(nctrls, struct cfg_aux); + + id = 1000; + y = 22; /* Leave some room for the dialog title */ + for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) { + switch (i->type) { + case C_STRING: + /* + * Edit box with a label beside it. + */ + mkctrl(fe, col1l, col1r, y + 1, y + 11, + TEXT("Static"), SS_LEFTNOWORDWRAP, 0, i->name, id++); + mkctrl(fe, col2l, col2r, y, y + 12, + TEXT("EDIT"), WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, + 0, "", (j->ctlid = id++)); + SetDlgItemTextA(fe->cfgbox, j->ctlid, i->sval); + break; + + case C_BOOLEAN: + /* + * Simple checkbox. + */ + mkctrl(fe, col1l, col2r, y + 1, y + 11, TEXT("BUTTON"), + BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP, + 0, i->name, (j->ctlid = id++)); + CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0)); + break; + + case C_CHOICES: + /* + * Drop-down list with a label beside it. + */ + mkctrl(fe, col1l, col1r, y + 1, y + 11, + TEXT("STATIC"), SS_LEFTNOWORDWRAP, 0, i->name, id++); + ctl = mkctrl(fe, col2l, col2r, y, y + 48, + TEXT("COMBOBOX"), WS_BORDER | WS_TABSTOP | + CBS_DROPDOWNLIST | CBS_HASSTRINGS, + 0, "", (j->ctlid = id++)); + { + char c, *p, *q, *str; + + p = i->sval; + c = *p++; + while (*p) { + q = p; + while (*q && *q != c) q++; + str = snewn(q-p+1, char); + strncpy(str, p, q-p); + str[q-p] = '\0'; + { + TCHAR ws[50]; + MultiByteToWideChar (CP_ACP, 0, str, -1, ws, 50); + SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)ws); + } + + sfree(str); + if (*q) q++; + p = q; + } + } + SendMessage(ctl, CB_SETCURSEL, i->ival, 0); + break; + } + + y += 15; + } + +} +#endif + +static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + config_item *i; + struct cfg_aux *j; + + switch (msg) { + case WM_INITDIALOG: +#ifdef _WIN32_WCE + { + char *title; + + fe = (frontend *) lParam; + SetWindowLong(hwnd, GWL_USERDATA, lParam); + fe->cfgbox = hwnd; + + fe->cfg = frontend_get_config(fe, fe->cfg_which, &title); + + make_dialog_full_screen(hwnd); + + SetDlgItemTextA(hwnd, IDC_CONFIG_CAPTION, title); + SendDlgItemMessage(hwnd, IDC_CONFIG_CAPTION, WM_SETFONT, + (WPARAM) dialog_title_font(), 0); + + create_config_controls(fe); + } +#endif + return TRUE; + + case WM_COMMAND: + /* + * OK and Cancel are special cases. + */ + if ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) { + if (LOWORD(wParam) == IDOK) { + char *err = frontend_set_config(fe, fe->cfg_which, fe->cfg); + + if (err) { + MessageBox(hwnd, err, "Validation error", + MB_ICONERROR | MB_OK); + } else { +#ifdef _WIN32_WCE + EndDialog(hwnd, 2); +#else + fe->dlg_done = 2; +#endif + } + } else { +#ifdef _WIN32_WCE + EndDialog(hwnd, 1); +#else + fe->dlg_done = 1; +#endif + } + return 0; + } + + /* + * First find the control whose id this is. + */ + for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) { + if (j->ctlid == LOWORD(wParam)) + break; + } + if (i->type == C_END) + return 0; /* not our problem */ + + if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) { + char buffer[4096]; +#ifdef _WIN32_WCE + TCHAR wBuffer[4096]; + GetDlgItemText(fe->cfgbox, j->ctlid, wBuffer, 4096); + WideCharToMultiByte(CP_ACP, 0, wBuffer, -1, buffer, 4096, NULL, NULL); +#else + GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer)); +#endif + buffer[lenof(buffer)-1] = '\0'; + sfree(i->sval); + i->sval = dupstr(buffer); + } else if (i->type == C_BOOLEAN && + (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DBLCLK)) { + i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid); + } else if (i->type == C_CHOICES && + HIWORD(wParam) == CBN_SELCHANGE) { + i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid, + CB_GETCURSEL, 0, 0); + } + + return 0; + + case WM_CLOSE: + fe->dlg_done = 1; + return 0; + } + + return 0; +} + +#ifndef _WIN32_WCE +HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2, + char *wclass, int wstyle, + int exstyle, const char *wtext, int wid) +{ + HWND ret; + ret = CreateWindowEx(exstyle, wclass, wtext, + wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1, + fe->cfgbox, (HMENU) wid, fe->inst, NULL); + SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(TRUE, 0)); + return ret; +} +#endif + +static void about(frontend *fe) +{ +#ifdef _WIN32_WCE + DialogBox(fe->inst, MAKEINTRESOURCE(IDD_ABOUT), fe->hwnd, AboutDlgProc); +#else + int i; + WNDCLASS wc; + MSG msg; + TEXTMETRIC tm; + HDC hdc; + HFONT oldfont; + SIZE size; + int gm, id; + int winwidth, winheight, y; + int height, width, maxwid; + const char *strings[16]; + int lengths[16]; + int nstrings = 0; + char titlebuf[512]; + + sprintf(titlebuf, "About %.250s", fe->game->name); + + strings[nstrings++] = fe->game->name; + strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection"; + strings[nstrings++] = ver; + + wc.style = CS_DBLCLKS | CS_SAVEBITS; + wc.lpfnWndProc = DefDlgProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA + 8; + wc.hInstance = fe->inst; + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); + wc.lpszMenuName = NULL; + wc.lpszClassName = "GameAboutBox"; + RegisterClass(&wc); + + hdc = GetDC(fe->hwnd); + SetMapMode(hdc, MM_TEXT); + + fe->dlg_done = FALSE; + + fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72), + 0, 0, 0, 0, + FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + FF_SWISS, + "MS Shell Dlg"); + + oldfont = SelectObject(hdc, fe->cfgfont); + if (GetTextMetrics(hdc, &tm)) { + height = tm.tmAscent + tm.tmDescent; + width = tm.tmAveCharWidth; + } else { + height = width = 30; + } + + /* + * Figure out the layout of the About box by measuring the + * length of each piece of text. + */ + maxwid = 0; + winheight = height/2; + + for (i = 0; i < nstrings; i++) { + if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size)) + lengths[i] = size.cx; + else + lengths[i] = 0; /* *shrug* */ + if (maxwid < lengths[i]) + maxwid = lengths[i]; + winheight += height * 3 / 2 + (height / 2); + } + + winheight += height + height * 7 / 4; /* OK button */ + winwidth = maxwid + 4*width; + + SelectObject(hdc, oldfont); + ReleaseDC(fe->hwnd, hdc); + + /* + * Create the dialog, now that we know its size. + */ + { + RECT r, r2; + + r.left = r.top = 0; + r.right = winwidth; + r.bottom = winheight; + + AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*| + DS_MODALFRAME | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU*/) &~ + (WS_MAXIMIZEBOX | WS_OVERLAPPED), + FALSE, 0); + + /* + * Centre the dialog on its parent window. + */ + r.right -= r.left; + r.bottom -= r.top; + GetWindowRect(fe->hwnd, &r2); + r.left = (r2.left + r2.right - r.right) / 2; + r.top = (r2.top + r2.bottom - r.bottom) / 2; + r.right += r.left; + r.bottom += r.top; + + fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf, + DS_MODALFRAME | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU, + r.left, r.top, + r.right-r.left, r.bottom-r.top, + fe->hwnd, NULL, fe->inst, NULL); + } + + SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); + + SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); + SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc); + + id = 1000; + y = height/2; + for (i = 0; i < nstrings; i++) { + int border = width*2 + (maxwid - lengths[i]) / 2; + mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8, + "Static", 0, 0, strings[i], id++); + y += height*3/2; + + assert(y < winheight); + y += height/2; + } + + y += height/2; /* extra space before OK */ + mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON", + BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0, + "OK", IDOK); + + SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0); + + EnableWindow(fe->hwnd, FALSE); + ShowWindow(fe->cfgbox, SW_SHOWNORMAL); + while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) { + if (!IsDialogMessage(fe->cfgbox, &msg)) + DispatchMessage(&msg); + if (fe->dlg_done) + break; + } + EnableWindow(fe->hwnd, TRUE); + SetForegroundWindow(fe->hwnd); + DestroyWindow(fe->cfgbox); + DeleteObject(fe->cfgfont); +#endif +} + +static int get_config(frontend *fe, int which) +{ +#ifdef _WIN32_WCE + fe->cfg_which = which; + + return DialogBoxParam(fe->inst, + MAKEINTRESOURCE(IDD_CONFIG), + fe->hwnd, ConfigDlgProc, + (LPARAM) fe) == 2; +#else + config_item *i; + struct cfg_aux *j; + char *title; + WNDCLASS wc; + MSG msg; + TEXTMETRIC tm; + HDC hdc; + HFONT oldfont; + SIZE size; + HWND ctl; + int gm, id, nctrls; + int winwidth, winheight, col1l, col1r, col2l, col2r, y; + int height, width, maxlabel, maxcheckbox; + + wc.style = CS_DBLCLKS | CS_SAVEBITS; + wc.lpfnWndProc = DefDlgProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA + 8; + wc.hInstance = fe->inst; + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); + wc.lpszMenuName = NULL; + wc.lpszClassName = "GameConfigBox"; + RegisterClass(&wc); + + hdc = GetDC(fe->hwnd); + SetMapMode(hdc, MM_TEXT); + + fe->dlg_done = FALSE; + + fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72), + 0, 0, 0, 0, + FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + FF_SWISS, + "MS Shell Dlg"); + + oldfont = SelectObject(hdc, fe->cfgfont); + if (GetTextMetrics(hdc, &tm)) { + height = tm.tmAscent + tm.tmDescent; + width = tm.tmAveCharWidth; + } else { + height = width = 30; + } + + fe->cfg = frontend_get_config(fe, which, &title); + fe->cfg_which = which; + + /* + * Figure out the layout of the config box by measuring the + * length of each piece of text. + */ + maxlabel = maxcheckbox = 0; + winheight = height/2; + + for (i = fe->cfg; i->type != C_END; i++) { + switch (i->type) { + case C_STRING: + case C_CHOICES: + /* + * Both these control types have a label filling only + * the left-hand column of the box. + */ + if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) && + maxlabel < size.cx) + maxlabel = size.cx; + winheight += height * 3 / 2 + (height / 2); + break; + + case C_BOOLEAN: + /* + * Checkboxes take up the whole of the box width. + */ + if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) && + maxcheckbox < size.cx) + maxcheckbox = size.cx; + winheight += height + (height / 2); + break; + } + } + + winheight += height + height * 7 / 4; /* OK / Cancel buttons */ + + col1l = 2*width; + col1r = col1l + maxlabel; + col2l = col1r + 2*width; + col2r = col2l + 30*width; + if (col2r < col1l+2*height+maxcheckbox) + col2r = col1l+2*height+maxcheckbox; + winwidth = col2r + 2*width; + + SelectObject(hdc, oldfont); + ReleaseDC(fe->hwnd, hdc); + + /* + * Create the dialog, now that we know its size. + */ + { + RECT r, r2; + + r.left = r.top = 0; + r.right = winwidth; + r.bottom = winheight; + + AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*| + DS_MODALFRAME | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU*/) &~ + (WS_MAXIMIZEBOX | WS_OVERLAPPED), + FALSE, 0); + + /* + * Centre the dialog on its parent window. + */ + r.right -= r.left; + r.bottom -= r.top; + GetWindowRect(fe->hwnd, &r2); + r.left = (r2.left + r2.right - r.right) / 2; + r.top = (r2.top + r2.bottom - r.bottom) / 2; + r.right += r.left; + r.bottom += r.top; + + fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title, + DS_MODALFRAME | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU, + r.left, r.top, + r.right-r.left, r.bottom-r.top, + fe->hwnd, NULL, fe->inst, NULL); + sfree(title); + } + + SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE); + + SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe); + SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc); + + /* + * Count the controls so we can allocate cfgaux. + */ + for (nctrls = 0, i = fe->cfg; i->type != C_END; i++) + nctrls++; + fe->cfgaux = snewn(nctrls, struct cfg_aux); + + id = 1000; + y = height/2; + for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) { + switch (i->type) { + case C_STRING: + /* + * Edit box with a label beside it. + */ + mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8, + "Static", 0, 0, i->name, id++); + ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2, + "EDIT", WS_TABSTOP | ES_AUTOHSCROLL, + WS_EX_CLIENTEDGE, "", (j->ctlid = id++)); + SetWindowText(ctl, i->sval); + y += height*3/2; + break; + + case C_BOOLEAN: + /* + * Simple checkbox. + */ + mkctrl(fe, col1l, col2r, y, y+height, "BUTTON", + BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP, + 0, i->name, (j->ctlid = id++)); + CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0)); + y += height; + break; + + case C_CHOICES: + /* + * Drop-down list with a label beside it. + */ + mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8, + "STATIC", 0, 0, i->name, id++); + ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2, + "COMBOBOX", WS_TABSTOP | + CBS_DROPDOWNLIST | CBS_HASSTRINGS, + WS_EX_CLIENTEDGE, "", (j->ctlid = id++)); + { + char c, *p, *q, *str; + + SendMessage(ctl, CB_RESETCONTENT, 0, 0); + p = i->sval; + c = *p++; + while (*p) { + q = p; + while (*q && *q != c) q++; + str = snewn(q-p+1, char); + strncpy(str, p, q-p); + str[q-p] = '\0'; + SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str); + sfree(str); + if (*q) q++; + p = q; + } + } + + SendMessage(ctl, CB_SETCURSEL, i->ival, 0); + + y += height*3/2; + break; + } + + assert(y < winheight); + y += height/2; + } + + y += height/2; /* extra space before OK and Cancel */ + mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON", + BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0, + "OK", IDOK); + mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON", + BS_PUSHBUTTON | WS_TABSTOP, 0, "Cancel", IDCANCEL); + + SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0); + + EnableWindow(fe->hwnd, FALSE); + ShowWindow(fe->cfgbox, SW_SHOWNORMAL); + while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) { + if (!IsDialogMessage(fe->cfgbox, &msg)) + DispatchMessage(&msg); + if (fe->dlg_done) + break; + } + EnableWindow(fe->hwnd, TRUE); + SetForegroundWindow(fe->hwnd); + DestroyWindow(fe->cfgbox); + DeleteObject(fe->cfgfont); + + free_cfg(fe->cfg); + sfree(fe->cfgaux); + + return (fe->dlg_done == 2); +#endif +} + +#ifdef _WIN32_WCE +static void calculate_bitmap_position(frontend *fe, int x, int y) +{ + /* Pocket PC - center the game in the full screen window */ + int yMargin; + RECT rcClient; + + GetClientRect(fe->hwnd, &rcClient); + fe->bitmapPosition.left = (rcClient.right - x) / 2; + yMargin = rcClient.bottom - y; + + if (fe->numpad != NULL) { + RECT rcPad; + GetWindowRect(fe->numpad, &rcPad); + yMargin -= rcPad.bottom - rcPad.top; + } + + if (fe->statusbar != NULL) { + RECT rcStatus; + GetWindowRect(fe->statusbar, &rcStatus); + yMargin -= rcStatus.bottom - rcStatus.top; + } + + fe->bitmapPosition.top = yMargin / 2; + + fe->bitmapPosition.right = fe->bitmapPosition.left + x; + fe->bitmapPosition.bottom = fe->bitmapPosition.top + y; +} +#else +static void calculate_bitmap_position(frontend *fe, int x, int y) +{ + /* Plain Windows - position the game in the upper-left corner */ + fe->bitmapPosition.left = 0; + fe->bitmapPosition.top = 0; + fe->bitmapPosition.right = fe->bitmapPosition.left + x; + fe->bitmapPosition.bottom = fe->bitmapPosition.top + y; +} +#endif + +static void new_bitmap(frontend *fe, int x, int y) +{ + HDC hdc; + + if (fe->bitmap) DeleteObject(fe->bitmap); + + hdc = GetDC(fe->hwnd); + fe->bitmap = CreateCompatibleBitmap(hdc, x, y); + calculate_bitmap_position(fe, x, y); + ReleaseDC(fe->hwnd, hdc); +} + +static void new_game_size(frontend *fe, float scale) +{ + RECT r, sr; + int x, y; + + get_max_puzzle_size(fe, &x, &y); + midend_size(fe->me, &x, &y, FALSE); + + if (scale != 1.0) { + x = (int)((float)x * fe->puzz_scale); + y = (int)((float)y * fe->puzz_scale); + midend_size(fe->me, &x, &y, TRUE); + } + fe->ymin = (fe->xmin * y) / x; + + r.left = r.top = 0; + r.right = x; + r.bottom = y; + AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0); + + if (fe->statusbar != NULL) { + GetWindowRect(fe->statusbar, &sr); + } else { + sr.left = sr.right = sr.top = sr.bottom = 0; + } +#ifndef _WIN32_WCE + SetWindowPos(fe->hwnd, NULL, 0, 0, + r.right - r.left, + r.bottom - r.top + sr.bottom - sr.top, + SWP_NOMOVE | SWP_NOZORDER); +#endif + + check_window_size(fe, &x, &y); + +#ifndef _WIN32_WCE + if (fe->statusbar != NULL) + SetWindowPos(fe->statusbar, NULL, 0, y, x, + sr.bottom - sr.top, SWP_NOZORDER); +#endif + + new_bitmap(fe, x, y); + +#ifdef _WIN32_WCE + InvalidateRect(fe->hwnd, NULL, TRUE); +#endif + midend_redraw(fe->me); +} + +/* + * Given a proposed new window rect, work out the resulting + * difference in client size (from current), and use to try + * and resize the puzzle, returning (wx,wy) as the actual + * new window size. + */ + +static void adjust_game_size(frontend *fe, RECT *proposed, int isedge, + int *wx_r, int *wy_r) +{ + RECT cr, wr; + int nx, ny, xdiff, ydiff, wx, wy; + + /* Work out the current window sizing, and thus the + * difference in size we're asking for. */ + GetClientRect(fe->hwnd, &cr); + wr = cr; + AdjustWindowRectEx(&wr, WINFLAGS, TRUE, 0); + + xdiff = (proposed->right - proposed->left) - (wr.right - wr.left); + ydiff = (proposed->bottom - proposed->top) - (wr.bottom - wr.top); + + if (isedge) { + /* These next four lines work around the fact that midend_size + * is happy to shrink _but not grow_ if you change one dimension + * but not the other. */ + if (xdiff > 0 && ydiff == 0) + ydiff = (xdiff * (wr.right - wr.left)) / (wr.bottom - wr.top); + if (xdiff == 0 && ydiff > 0) + xdiff = (ydiff * (wr.bottom - wr.top)) / (wr.right - wr.left); + } + + if (check_window_resize(fe, + (cr.right - cr.left) + xdiff, + (cr.bottom - cr.top) + ydiff, + &nx, &ny, &wx, &wy)) { + new_bitmap(fe, nx, ny); + midend_force_redraw(fe->me); + } else { + /* reset size to current window size */ + wx = wr.right - wr.left; + wy = wr.bottom - wr.top; + } + /* Re-fetch rectangle; size limits mean we might not have + * taken it quite to the mouse drag positions. */ + GetClientRect(fe->hwnd, &cr); + adjust_statusbar(fe, &cr); + + *wx_r = wx; *wy_r = wy; +} + +static void update_type_menu_tick(frontend *fe) +{ + int total, n, i; + + if (fe->typemenu == INVALID_HANDLE_VALUE) + return; + + total = GetMenuItemCount(fe->typemenu); + n = midend_which_preset(fe->me); + if (n < 0) + n = total - 1; /* "Custom" item */ + + for (i = 0; i < total; i++) { + int flag = (i == n ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(fe->typemenu, i, MF_BYPOSITION | flag); + } + + DrawMenuBar(fe->hwnd); +} + +static void update_copy_menu_greying(frontend *fe) +{ + UINT enable = (midend_can_format_as_text_now(fe->me) ? + MF_ENABLED : MF_GRAYED); + EnableMenuItem(fe->gamemenu, IDM_COPY, MF_BYCOMMAND | enable); +} + +static void new_game_type(frontend *fe) +{ + midend_new_game(fe->me); + new_game_size(fe, 1.0); + update_type_menu_tick(fe); + update_copy_menu_greying(fe); +} + +static int is_alt_pressed(void) +{ + BYTE keystate[256]; + int r = GetKeyboardState(keystate); + if (!r) + return FALSE; + if (keystate[VK_MENU] & 0x80) + return TRUE; + if (keystate[VK_RMENU] & 0x80) + return TRUE; + return FALSE; +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + int cmd; + + switch (message) { + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; + case WM_COMMAND: +#ifdef _WIN32_WCE + /* Numeric pad sends WM_COMMAND messages */ + if ((wParam >= IDM_KEYEMUL) && (wParam < IDM_KEYEMUL + 256)) + { + midend_process_key(fe->me, 0, 0, wParam - IDM_KEYEMUL); + } +#endif + cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */ + switch (cmd) { + case IDM_NEW: + if (!midend_process_key(fe->me, 0, 0, 'n')) + PostQuitMessage(0); + break; + case IDM_RESTART: + midend_restart_game(fe->me); + break; + case IDM_UNDO: + if (!midend_process_key(fe->me, 0, 0, 'u')) + PostQuitMessage(0); + break; + case IDM_REDO: + if (!midend_process_key(fe->me, 0, 0, '\x12')) + PostQuitMessage(0); + break; + case IDM_COPY: + { + char *text = midend_text_format(fe->me); + if (text) + write_clip(hwnd, text); + else + MessageBeep(MB_ICONWARNING); + sfree(text); + } + break; + case IDM_SOLVE: + { + char *msg = midend_solve(fe->me); + if (msg) + MessageBox(hwnd, msg, "Unable to solve", + MB_ICONERROR | MB_OK); + } + break; + case IDM_QUIT: + if (!midend_process_key(fe->me, 0, 0, 'q')) + PostQuitMessage(0); + break; + case IDM_CONFIG: + if (get_config(fe, CFG_SETTINGS)) + new_game_type(fe); + break; + case IDM_SEED: + if (get_config(fe, CFG_SEED)) + new_game_type(fe); + break; + case IDM_DESC: + if (get_config(fe, CFG_DESC)) + new_game_type(fe); + break; + case IDM_PRINT: + if (get_config(fe, CFG_PRINT)) + print(fe); + break; + case IDM_ABOUT: + about(fe); + break; + case IDM_LOAD: + case IDM_SAVE: + { + OPENFILENAME of; + char filename[FILENAME_MAX]; + int ret; + + memset(&of, 0, sizeof(of)); + of.hwndOwner = hwnd; + of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; + of.lpstrCustomFilter = NULL; + of.nFilterIndex = 1; + of.lpstrFile = filename; + filename[0] = '\0'; + of.nMaxFile = lenof(filename); + of.lpstrFileTitle = NULL; + of.lpstrTitle = (cmd == IDM_SAVE ? + "Enter name of game file to save" : + "Enter name of saved game file to load"); + of.Flags = 0; +#ifdef OPENFILENAME_SIZE_VERSION_400 + of.lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + of.lStructSize = sizeof(of); +#endif + of.lpstrInitialDir = NULL; + + if (cmd == IDM_SAVE) + ret = GetSaveFileName(&of); + else + ret = GetOpenFileName(&of); + + if (ret) { + if (cmd == IDM_SAVE) { + FILE *fp; + + if ((fp = fopen(filename, "r")) != NULL) { + char buf[256 + FILENAME_MAX]; + fclose(fp); + /* file exists */ + + sprintf(buf, "Are you sure you want to overwrite" + " the file \"%.*s\"?", + FILENAME_MAX, filename); + if (MessageBox(hwnd, buf, "Question", + MB_YESNO | MB_ICONQUESTION) + != IDYES) + break; + } + + fp = fopen(filename, "w"); + + if (!fp) { + MessageBox(hwnd, "Unable to open save file", + "Error", MB_ICONERROR | MB_OK); + break; + } + + midend_serialise(fe->me, savefile_write, fp); + + fclose(fp); + } else { + FILE *fp = fopen(filename, "r"); + char *err = NULL; + midend *me = fe->me; +#ifdef COMBINED + char *id_name; +#endif + + if (!fp) { + MessageBox(hwnd, "Unable to open saved game file", + "Error", MB_ICONERROR | MB_OK); + break; + } + +#ifdef COMBINED + /* + * This save file might be from a different + * game. + */ + err = identify_game(&id_name, savefile_read, fp); + if (!err) { + int i; + for (i = 0; i < gamecount; i++) + if (!strcmp(id_name, gamelist[i]->name)) + break; + if (i == gamecount) { + err = "Save file is for a game not " + "supported by this program"; + } else { + me = midend_for_new_game(fe, gamelist[i], NULL, + FALSE, FALSE, &err); + rewind(fp); /* for the actual load */ + } + sfree(id_name); + } +#endif + if (!err) + err = midend_deserialise(me, savefile_read, fp); + + fclose(fp); + + if (err) { + MessageBox(hwnd, err, "Error", MB_ICONERROR|MB_OK); + break; + } + + if (fe->me != me) + fe_set_midend(fe, me); + new_game_size(fe, 1.0); + } + } + } + + break; +#ifndef _WIN32_WCE + case IDM_HELPC: + start_help(fe, NULL); + break; + case IDM_GAMEHELP: + assert(help_type != NONE); + start_help(fe, help_type == CHM ? + fe->game->htmlhelp_topic : fe->game->winhelp_topic); + break; +#endif + default: +#ifdef COMBINED + if (wParam >= IDM_GAMES && wParam < (IDM_GAMES + (WPARAM)gamecount)) { + int p = wParam - IDM_GAMES; + char *error = NULL; + fe_set_midend(fe, midend_for_new_game(fe, gamelist[p], NULL, + FALSE, FALSE, &error)); + sfree(error); + } else +#endif + { + int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10; + + if (p >= 0 && p < fe->npresets) { + midend_set_params(fe->me, fe->presets[p]); + new_game_type(fe); + } + } + break; + } + break; + case WM_DESTROY: +#ifndef _WIN32_WCE + stop_help(fe); +#endif + frontend_free(fe); + PostQuitMessage(0); + return 0; + case WM_PAINT: + { + PAINTSTRUCT p; + HDC hdc, hdc2; + HBITMAP prevbm; + RECT rcDest; + + hdc = BeginPaint(hwnd, &p); + hdc2 = CreateCompatibleDC(hdc); + prevbm = SelectObject(hdc2, fe->bitmap); +#ifdef _WIN32_WCE + FillRect(hdc, &(p.rcPaint), (HBRUSH) GetStockObject(WHITE_BRUSH)); +#endif + IntersectRect(&rcDest, &(fe->bitmapPosition), &(p.rcPaint)); + BitBlt(hdc, + rcDest.left, rcDest.top, + rcDest.right - rcDest.left, + rcDest.bottom - rcDest.top, + hdc2, + rcDest.left - fe->bitmapPosition.left, + rcDest.top - fe->bitmapPosition.top, + SRCCOPY); + SelectObject(hdc2, prevbm); + DeleteDC(hdc2); + EndPaint(hwnd, &p); + } + return 0; + case WM_KEYDOWN: + { + int key = -1; + BYTE keystate[256]; + int r = GetKeyboardState(keystate); + int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0; + int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0; + + switch (wParam) { + case VK_LEFT: + if (!(lParam & 0x01000000)) + key = MOD_NUM_KEYPAD | '4'; + else + key = shift | ctrl | CURSOR_LEFT; + break; + case VK_RIGHT: + if (!(lParam & 0x01000000)) + key = MOD_NUM_KEYPAD | '6'; + else + key = shift | ctrl | CURSOR_RIGHT; + break; + case VK_UP: + if (!(lParam & 0x01000000)) + key = MOD_NUM_KEYPAD | '8'; + else + key = shift | ctrl | CURSOR_UP; + break; + case VK_DOWN: + if (!(lParam & 0x01000000)) + key = MOD_NUM_KEYPAD | '2'; + else + key = shift | ctrl | CURSOR_DOWN; + break; + /* + * Diagonal keys on the numeric keypad. + */ + case VK_PRIOR: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9'; + break; + case VK_NEXT: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3'; + break; + case VK_HOME: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7'; + break; + case VK_END: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1'; + break; + case VK_INSERT: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0'; + break; + case VK_CLEAR: + if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5'; + break; + /* + * Numeric keypad keys with Num Lock on. + */ + case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break; + case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break; + case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break; + case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break; + case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break; + case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break; + case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break; + case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break; + case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break; + case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break; + } + + if (key != -1) { + if (!midend_process_key(fe->me, 0, 0, key)) + PostQuitMessage(0); + } else { + MSG m; + m.hwnd = hwnd; + m.message = WM_KEYDOWN; + m.wParam = wParam; + m.lParam = lParam & 0xdfff; + TranslateMessage(&m); + } + } + break; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + { + int button; + + /* + * Shift-clicks count as middle-clicks, since otherwise + * two-button Windows users won't have any kind of + * middle click to use. + */ + if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT)) + button = MIDDLE_BUTTON; + else if (message == WM_RBUTTONDOWN || is_alt_pressed()) + button = RIGHT_BUTTON; + else +#ifndef _WIN32_WCE + button = LEFT_BUTTON; +#else + if ((fe->game->flags & REQUIRE_RBUTTON) == 0) + button = LEFT_BUTTON; + else + { + SHRGINFO shrgi; + + shrgi.cbSize = sizeof(SHRGINFO); + shrgi.hwndClient = hwnd; + shrgi.ptDown.x = (signed short)LOWORD(lParam); + shrgi.ptDown.y = (signed short)HIWORD(lParam); + shrgi.dwFlags = SHRG_RETURNCMD; + + if (GN_CONTEXTMENU == SHRecognizeGesture(&shrgi)) + button = RIGHT_BUTTON; + else + button = LEFT_BUTTON; + } +#endif + + if (!midend_process_key(fe->me, + (signed short)LOWORD(lParam) - fe->bitmapPosition.left, + (signed short)HIWORD(lParam) - fe->bitmapPosition.top, + button)) + PostQuitMessage(0); + + SetCapture(hwnd); + } + break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + { + int button; + + /* + * Shift-clicks count as middle-clicks, since otherwise + * two-button Windows users won't have any kind of + * middle click to use. + */ + if (message == WM_MBUTTONUP || (wParam & MK_SHIFT)) + button = MIDDLE_RELEASE; + else if (message == WM_RBUTTONUP || is_alt_pressed()) + button = RIGHT_RELEASE; + else + button = LEFT_RELEASE; + + if (!midend_process_key(fe->me, + (signed short)LOWORD(lParam) - fe->bitmapPosition.left, + (signed short)HIWORD(lParam) - fe->bitmapPosition.top, + button)) + PostQuitMessage(0); + + ReleaseCapture(); + } + break; + case WM_MOUSEMOVE: + { + int button; + + if (wParam & (MK_MBUTTON | MK_SHIFT)) + button = MIDDLE_DRAG; + else if (wParam & MK_RBUTTON || is_alt_pressed()) + button = RIGHT_DRAG; + else + button = LEFT_DRAG; + + if (!midend_process_key(fe->me, + (signed short)LOWORD(lParam) - fe->bitmapPosition.left, + (signed short)HIWORD(lParam) - fe->bitmapPosition.top, + button)) + PostQuitMessage(0); + } + break; + case WM_CHAR: + if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam)) + PostQuitMessage(0); + return 0; + case WM_TIMER: + if (fe->timer) { + DWORD now = GetTickCount(); + float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F; + midend_timer(fe->me, elapsed); + fe->timer_last_tickcount = now; + } + return 0; +#ifndef _WIN32_WCE + case WM_SIZING: + { + RECT *sr = (RECT *)lParam; + int wx, wy, isedge = 0; + + if (wParam == WMSZ_TOP || + wParam == WMSZ_RIGHT || + wParam == WMSZ_BOTTOM || + wParam == WMSZ_LEFT) isedge = 1; + adjust_game_size(fe, sr, isedge, &wx, &wy); + + /* Given the window size the puzzles constrain + * us to, work out which edge we should be moving. */ + if (wParam == WMSZ_TOP || + wParam == WMSZ_TOPLEFT || + wParam == WMSZ_TOPRIGHT) { + sr->top = sr->bottom - wy; + } else { + sr->bottom = sr->top + wy; + } + if (wParam == WMSZ_LEFT || + wParam == WMSZ_TOPLEFT || + wParam == WMSZ_BOTTOMLEFT) { + sr->left = sr->right - wx; + } else { + sr->right = sr->left + wx; + } + return TRUE; + } + break; +#endif + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +#ifdef _WIN32_WCE +static int FindPreviousInstance() +{ + /* Check if application is running. If it's running then focus on the window */ + HWND hOtherWnd = NULL; + + hOtherWnd = FindWindow (wGameName, wGameName); + if (hOtherWnd) + { + SetForegroundWindow (hOtherWnd); + return TRUE; + } + + return FALSE; +} +#endif + +/* + * Split a complete command line into argc/argv, attempting to do it + * exactly the same way the Visual Studio C library would do it (so + * that our console utilities, which receive argc and argv already + * broken apart by the C library, will have their command lines + * processed in the same way as the GUI utilities which get a whole + * command line and must call this function). + * + * Does not modify the input command line. + * + * The final parameter (argstart) is used to return a second array + * of char * pointers, the same length as argv, each one pointing + * at the start of the corresponding element of argv in the + * original command line. So if you get half way through processing + * your command line in argc/argv form and then decide you want to + * treat the rest as a raw string, you can. If you don't want to, + * `argstart' can be safely left NULL. + */ +void split_into_argv(char *cmdline, int *argc, char ***argv, + char ***argstart) +{ + char *p; + char *outputline, *q; + char **outputargv, **outputargstart; + int outputargc; + + /* + * These argument-breaking rules apply to Visual Studio 7, which + * is currently the compiler expected to be used for the Windows + * port of my puzzles. Visual Studio 10 has different rules, + * lacking the curious mod 3 behaviour of consecutive quotes + * described below; I presume they fixed a bug. As and when we + * migrate to a newer compiler, we'll have to adjust this to + * match; however, for the moment we faithfully imitate in our GUI + * utilities what our CLI utilities can't be prevented from doing. + * + * When I investigated this, at first glance the rules appeared to + * be: + * + * - Single quotes are not special characters. + * + * - Double quotes are removed, but within them spaces cease + * to be special. + * + * - Backslashes are _only_ special when a sequence of them + * appear just before a double quote. In this situation, + * they are treated like C backslashes: so \" just gives a + * literal quote, \\" gives a literal backslash and then + * opens or closes a double-quoted segment, \\\" gives a + * literal backslash and then a literal quote, \\\\" gives + * two literal backslashes and then opens/closes a + * double-quoted segment, and so forth. Note that this + * behaviour is identical inside and outside double quotes. + * + * - Two successive double quotes become one literal double + * quote, but only _inside_ a double-quoted segment. + * Outside, they just form an empty double-quoted segment + * (which may cause an empty argument word). + * + * - That only leaves the interesting question of what happens + * when one or more backslashes precedes two or more double + * quotes, starting inside a double-quoted string. And the + * answer to that appears somewhat bizarre. Here I tabulate + * number of backslashes (across the top) against number of + * quotes (down the left), and indicate how many backslashes + * are output, how many quotes are output, and whether a + * quoted segment is open at the end of the sequence: + * + * backslashes + * + * 0 1 2 3 4 + * + * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y + * --------+----------------------------- + * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n + * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n + * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y + * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n + * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n + * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y + * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n + * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n + * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y + * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n + * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n + * + * + * [Test fragment was of the form "a\\\"""b c" d.] + * + * There is very weird mod-3 behaviour going on here in the + * number of quotes, and it even applies when there aren't any + * backslashes! How ghastly. + * + * With a bit of thought, this extremely odd diagram suddenly + * coalesced itself into a coherent, if still ghastly, model of + * how things work: + * + * - As before, backslashes are only special when one or more + * of them appear contiguously before at least one double + * quote. In this situation the backslashes do exactly what + * you'd expect: each one quotes the next thing in front of + * it, so you end up with n/2 literal backslashes (if n is + * even) or (n-1)/2 literal backslashes and a literal quote + * (if n is odd). In the latter case the double quote + * character right after the backslashes is used up. + * + * - After that, any remaining double quotes are processed. A + * string of contiguous unescaped double quotes has a mod-3 + * behaviour: + * + * * inside a quoted segment, a quote ends the segment. + * * _immediately_ after ending a quoted segment, a quote + * simply produces a literal quote. + * * otherwise, outside a quoted segment, a quote begins a + * quoted segment. + * + * So, for example, if we started inside a quoted segment + * then two contiguous quotes would close the segment and + * produce a literal quote; three would close the segment, + * produce a literal quote, and open a new segment. If we + * started outside a quoted segment, then two contiguous + * quotes would open and then close a segment, producing no + * output (but potentially creating a zero-length argument); + * but three quotes would open and close a segment and then + * produce a literal quote. + */ + + /* + * First deal with the simplest of all special cases: if there + * aren't any arguments, return 0,NULL,NULL. + */ + while (*cmdline && isspace(*cmdline)) cmdline++; + if (!*cmdline) { + if (argc) *argc = 0; + if (argv) *argv = NULL; + if (argstart) *argstart = NULL; + return; + } + + /* + * This will guaranteeably be big enough; we can realloc it + * down later. + */ + outputline = snewn(1+strlen(cmdline), char); + outputargv = snewn(strlen(cmdline)+1 / 2, char *); + outputargstart = snewn(strlen(cmdline)+1 / 2, char *); + + p = cmdline; q = outputline; outputargc = 0; + + while (*p) { + int quote; + + /* Skip whitespace searching for start of argument. */ + while (*p && isspace(*p)) p++; + if (!*p) break; + + /* We have an argument; start it. */ + outputargv[outputargc] = q; + outputargstart[outputargc] = p; + outputargc++; + quote = 0; + + /* Copy data into the argument until it's finished. */ + while (*p) { + if (!quote && isspace(*p)) + break; /* argument is finished */ + + if (*p == '"' || *p == '\\') { + /* + * We have a sequence of zero or more backslashes + * followed by a sequence of zero or more quotes. + * Count up how many of each, and then deal with + * them as appropriate. + */ + int i, slashes = 0, quotes = 0; + while (*p == '\\') slashes++, p++; + while (*p == '"') quotes++, p++; + + if (!quotes) { + /* + * Special case: if there are no quotes, + * slashes are not special at all, so just copy + * n slashes to the output string. + */ + while (slashes--) *q++ = '\\'; + } else { + /* Slashes annihilate in pairs. */ + while (slashes >= 2) slashes -= 2, *q++ = '\\'; + + /* One remaining slash takes out the first quote. */ + if (slashes) quotes--, *q++ = '"'; + + if (quotes > 0) { + /* Outside a quote segment, a quote starts one. */ + if (!quote) quotes--, quote = 1; + + /* Now we produce (n+1)/3 literal quotes... */ + for (i = 3; i <= quotes+1; i += 3) *q++ = '"'; + + /* ... and end in a quote segment iff 3 divides n. */ + quote = (quotes % 3 == 0); + } + } + } else { + *q++ = *p++; + } + } + + /* At the end of an argument, just append a trailing NUL. */ + *q++ = '\0'; + } + + outputargv = sresize(outputargv, outputargc, char *); + outputargstart = sresize(outputargstart, outputargc, char *); + + if (argc) *argc = outputargc; + if (argv) *argv = outputargv; else sfree(outputargv); + if (argstart) *argstart = outputargstart; else sfree(outputargstart); +} + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + MSG msg; + char *error; + const game *gg; + frontend *fe; + midend *me; + int argc; + char **argv; + + split_into_argv(cmdline, &argc, &argv, NULL); + +#ifdef _WIN32_WCE + MultiByteToWideChar (CP_ACP, 0, CLASSNAME, -1, wClassName, 256); + if (FindPreviousInstance ()) + return 0; +#endif + + InitCommonControls(); + + if (!prev) { + WNDCLASS wndclass; + + wndclass.style = 0; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(200)); +#ifndef _WIN32_WCE + if (!wndclass.hIcon) /* in case resource file is absent */ + wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION); +#endif + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; +#ifdef _WIN32_WCE + wndclass.lpszClassName = wClassName; +#else + wndclass.lpszClassName = CLASSNAME; +#endif + + RegisterClass(&wndclass); + } + + while (*cmdline && isspace((unsigned char)*cmdline)) + cmdline++; + + init_help(); + +#ifdef COMBINED + gg = gamelist[0]; + if (argc > 0) { + int i; + for (i = 0; i < gamecount; i++) { + const char *p = gamelist[i]->name; + char *q = argv[0]; + while (*p && *q) { + if (isspace((unsigned char)*p)) { + while (*q && isspace((unsigned char)*q)) + q++; + } else { + if (tolower((unsigned char)*p) != + tolower((unsigned char)*q)) + break; + q++; + } + p++; + } + if (!*p) { + gg = gamelist[i]; + --argc; + ++argv; + break; + } + } + } +#else + gg = &thegame; +#endif + + fe = frontend_new(inst); + me = midend_for_new_game(fe, gg, argc > 0 ? argv[0] : NULL, + TRUE, TRUE, &error); + if (!me) { + char buf[128]; +#ifdef COMBINED + sprintf(buf, "Puzzles Error"); +#else + sprintf(buf, "%.100s Error", gg->name); +#endif + MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR); + sfree(error); + return 1; + } + fe_set_midend(fe, me); + show_window(fe); + + while (GetMessage(&msg, NULL, 0, 0)) { + DispatchMessage(&msg); + } + + DestroyWindow(fe->hwnd); + cleanup_help(); + + return msg.wParam; +} +/* vim: set shiftwidth=4 tabstop=8: */ diff --git a/apps/plugins/puzzles/winiss.pl b/apps/plugins/puzzles/winiss.pl new file mode 100755 index 0000000000..eca02d3b15 --- /dev/null +++ b/apps/plugins/puzzles/winiss.pl @@ -0,0 +1,79 @@ +#!/usr/bin/perl + +# Perl script to generate an Inno Setup installer script for +# Puzzles. This has to be scripted so that it can read gamedesc.txt +# and automatically adjust to the current available set of puzzles. + +# Usage: +# +# $ ./winiss.pl 20140922.sdfsdf gamedesc.txt > puzzles.iss +# +# where the first argument is the version number which will be encoded +# in the installer's version indicators. The first component of that +# version number will be expected to be a YYYYMMDD-format date. + +use warnings; +use Time::Local; + +$ver = shift @ARGV; + +# Parse the date out of $ver, and convert it into an integer number of +# days since an arbitrary epoch. This number is used for the Windows +# version resource (which wants a monotonic 16-bit integer). The epoch +# is chosen so that the first build using this date-based mechanism +# has a higher number than the last build in which that number was +# derived from a Subversion revision. +die "bad date format" if $ver !~ /^(\d{4})(\d{2})(\d{2})/; +$date = timegm(0,0,0,$3,$2-1,$1); +$integer_date = int($date / 86400) - 6000; + +$desc = shift @ARGV; +open DESC, "<", $desc; +while () { + chomp; + @_ = split /:/; + push @exes, $_[1]; + $names{$_[1]} = $_[2]; +} +close DESC; + +print '; -*- no -*-'."\n"; +print ';'."\n"; +print '; -- Inno Setup installer script for Puzzles.'."\n"; +print ''."\n"; +print '[Setup]'."\n"; +print 'AppName=Simon Tatham\'s Portable Puzzle Collection'."\n"; +print 'AppVerName=Puzzles version '.$ver."\n"; +print 'VersionInfoTextVersion=Version '.$ver."\n"; +print 'AppVersion=r'.$ver."\n"; +print 'VersionInfoVersion=0.0.'.$integer_date.'.0'."\n"; +print 'AppPublisher=Simon Tatham'."\n"; +print 'AppPublisherURL=http://www.chiark.greenend.org.uk/~sgtatham/puzzles/'."\n"; +print 'DefaultDirName={pf}\Simon Tatham\'s Portable Puzzle Collection'."\n"; +print 'DefaultGroupName=Simon Tatham\'s Puzzles'."\n"; +# print 'SetupIconFile=fixmethinkoneup.ico'."\n"; +# print 'UninstallDisplayIcon={app}\fixmethinkoneup.exe'."\n"; +print 'ChangesAssociations=no'."\n"; +print 'Compression=zip/9'."\n"; +print 'AllowNoIcons=yes'."\n"; +print 'OutputBaseFilename=installer'."\n"; +print ''."\n"; +print '[Files]'."\n"; +for $exe (@exes) { + print 'Source: "'.$exe.'"; DestDir: "{app}"; Flags: promptifolder replacesameversion uninsrestartdelete'."\n"; +} +print 'Source: "website.url"; DestDir: "{app}"; Flags: uninsrestartdelete'."\n"; +print 'Source: "puzzles.chm"; DestDir: "{app}"; Flags: uninsrestartdelete'."\n"; +print 'Source: "puzzles.hlp"; DestDir: "{app}"; Flags: uninsrestartdelete'."\n"; +print 'Source: "puzzles.cnt"; DestDir: "{app}"; Flags: uninsrestartdelete'."\n"; +print 'Source: "LICENCE"; DestDir: "{app}"; Flags: uninsrestartdelete'."\n"; +print ''."\n"; +print '[Icons]'."\n"; +for $exe (@exes) { + print 'Name: "{group}\\'.$names{$exe}.'"; Filename: "{app}\\'.$exe.'"'."\n"; +} +print '; We have to fall back from the .chm to the older .hlp file on some Windows'."\n"; +print '; versions.'."\n"; +print 'Name: "{group}\Puzzles Manual"; Filename: "{app}\puzzles.chm"; MinVersion: 4.1,5.0'."\n"; +print 'Name: "{group}\Puzzles Manual"; Filename: "{app}\puzzles.hlp"; OnlyBelowVersion: 4.1,5.0'."\n"; +print 'Name: "{group}\Puzzles Web Site"; Filename: "{app}\website.url"'."\n"; diff --git a/apps/plugins/puzzles/winwix.mc b/apps/plugins/puzzles/winwix.mc new file mode 100644 index 0000000000..1a1e620b82 --- /dev/null +++ b/apps/plugins/puzzles/winwix.mc @@ -0,0 +1,334 @@ +% # Source code for the Puzzles installer. +% # +% # The installer is built using WiX, but this file itself is not valid +% # XML input to WiX's candle.exe; instead, this is a template intended +% # to be processed through the standalone 'mason.pl' script provided +% # with Perl's Mason module. + +<%class> +has 'version' => (required => 1); +has 'descfile' => (required => 1); + + + + + + +% # Product tag. The Id component is set to "*", which causes WiX to +% # make up a new GUID every time it's run, whereas UpgradeCode is set +% # to a fixed GUID. This combination allows Windows to +% # recognise each new installer as different (because of Id) +% # versions of the same underlying thing (because of the common +% # UpgradeCode). + + +% # We force the install scope to perMachine, largely because I +% # don't really understand how to make it usefully switchable +% # between the two. If anyone is a WiX expert and does want to +% # install Puzzles locally in a user account, I hope they'll send a +% # well explained patch! + + +% # Permit installing an arbitrary one of these installers +% # over the top of an existing one, whether it's an upgrade or a +% # downgrade. +% # +% # Setting the REINSTALLMODE property to "amus" (from its default +% # of "omus") forces every component replaced by a different +% # version of the installer to be _actually_ reinstalled; the 'o' +% # flag in the default setting breaks the downgrade case by +% # causing Windows to disallow installation of an older version +% # over the top of a newer one - and to do so _silently_, so the +% # installer claims to have worked fine but the files that would have +% # been downgraded aren't there. + + + +% # Boilerplate + + +% # The actual directory structure and list of 'components' +% # (individual files or shortcuts or additions to PATH) that are +% # installed. + + + + +% # The following components all install things in the main +% # install directory (implicitly, by being nested where +% # they are in the XML structure). Most of them also put a +% # shortcut in a subdir of the Start menu, though some of +% # the more obscure things like LICENCE are just there for +% # the sake of being _somewhere_ and don't rate a shortcut. + +<%method file_component ($filename, $shortcutname)> +% my $filename_id = file_component_name($filename); + + +% if (defined $shortcutname) { + +% } + + + + +% for my $exe (@exes) { +<% $.file_component($exe, $names{$exe}) %> +% } + +<% $.file_component("puzzles.chm", "Puzzles Manual") %> +<% $.file_component("website.url", "Puzzles Web Site") %> +<% $.file_component("LICENCE") %> + + + + +% # This component doesn't actually install anything, but it +% # arranges for the Start Menu _directory_ to be removed again +% # on uninstall. All the actual shortcuts inside the directory +% # are placed by code above here. + + + + + + + + + + +% # Detect an installation of Puzzles made by the old Inno Setup +% # installer, and refuse to run if we find one. I don't know what +% # would happen if you tried anyway, but since they install files +% # at the same pathnames, it surely wouldn't end well. +% # +% # It could be argued that a better approach would be to actually +% # _launch_ the Inno Setup uninstaller automatically at this +% # point (prompting the user first, of course), but I'm not +% # nearly skilled enough with WiX to know how, or even if it's +% # feasible. + + + + + + + + + + +% # Separate the installation into 'features', which are parts of +% # the install that can be chosen separately. Trivial: there is only +% # one feature here. + +% for my $exe (@exes) { + +% } + " /> + " /> + " /> + + + +% # Installer user interface. +% # +% # Basically like WixUI_InstallDir, only I've ripped out the EULA. + + + + + + + + + + + + + + + + + + + + + + 1 + "1"]]> + + 1 + + NOT Installed + Installed + + 1 + 1 + NOT WIXUI_DONTVALIDATEPATH + "1"]]> + WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" + 1 + 1 + + NOT Installed + Installed AND NOT PATCH + Installed AND PATCH + + 1 + + 1 + 1 + 1 + +% # This ARPNOMODIFY flag prohibits changing the set of +% # installed features, because we don't have any. + + + +% # Glue: tell the install dir part of the UI what id my actual +% # install dir is known by. Otherwise the former won't know how +% # to alter the setting of the latter. + + +% # Include my custom installer artwork, created in Buildscr. FIXME: +% # create some! +% # +% # + + + + +<%init> + +use Digest::SHA qw(sha512_hex); +use Time::Local; + +die "bad date format" if $.version !~ /^(\d{4})(\d{2})(\d{2})/; +my $date = timegm(0,0,0,$3,$2-1,$1); +my $integer_date = int($date / 86400) - 6000; +my $winver = sprintf "0.0.%d.0", $integer_date; + +my @exes; +my %names; +{ + open my $descfh, "<", $.descfile or die "$.descfile: open: $!\n"; + while (<$descfh>) { + chomp; + my @fields = split /:/; + push @exes, $fields[1]; + $names{$fields[1]} = $fields[2]; + } + close $descfh; +} + +sub file_component_name($) { + my ($id) = @_; + $id =~ s!.*\\!!; + $id =~ y!A-Za-z0-9!_!cs; + return $id; +} + +sub invent_guid($) { + my ($name) = @_; + + # Invent a GUID. We'll need a lot of these in this installer - one + # per puzzle game and a few standard ones - and we'd like to + # arrange for them to be stable between versions of the installer, + # while not having to _manually_ invent one every time. + # + # So we generate our GUIDs by hashing a pile of fixed (but + # originally randomly generated) data with an identifying string + # that acts as source for the particular GUID. For example, + # hashing (fixed_random_data || "upgradecode") produces the GUID + # used as the upgrade code, and (fixed_random_data || + # "installfile:" || filename) gives the GUID for an MSI component + # that installs a specific file. + # + # Hashing _just_ the id strings would clearly be cheating (it's + # quite conceivable that someone might hash the same string for + # another reason and so generate a colliding GUID), but hashing a + # whole SHA-512 data block of random gibberish as well should make + # these GUIDs pseudo-random enough to not collide with anyone + # else's. + + my $randdata = pack "N*", + 0xCCAA8D31,0x42931BD9,0xA9D9878A,0x72E4FB9C,0xEA9B11DE,0x4FF17AEC, + 0x1AFA2DEC,0xB662640A,0x780143F5,0xBFFFF0FC,0x01CB46CF,0x832AE842, + 0xBCCDDA12,0x4DB24889,0xEC7A9BCD,0xBCCF70ED,0x85800659,0x8ABA9524, + 0xE484F8D6,0x5CBE55B3,0x95AD9B3D,0x0555F432,0x46737F89,0xE981471C, + 0x4B3419AD,0xD4E49DF4,0xB3EF69DE,0x2A7E391E,0xF3C3D370,0x3EA5E9FC, + 0xB35A57ED,0x52B21099,0x9BD99092,0x7B5097AE,0x4DBE59BD,0x2FCC709B, + 0xC555A779,0x4AE2E5AB,0xB7C74314,0x7F9194CF,0x8FFBCA88,0x46263306, + 0x4C714DF7,0x07FE8CEE,0x28974885,0x0869865D,0xBB5B0EA4,0x4064A928, + 0x28C41910,0x07030758,0x19E66319,0x050C9D4E,0xD79A37FB,0xF232D98B, + 0x0C3E4C25,0xC94F865B,0xB6D86BED,0x87DB718D,0xC65D4C43,0x7C8BBF6A, + 0x9DFDD26A,0x8C478976,0x53E47640,0x263E04AA,0xDD7C5456,0x766BDF50, + 0x86946E34,0xE80D8DE3,0xFB92949E,0x691FDAD0,0x96AB210D,0xB278D60B, + 0x71C7DC6B,0xD49846FC,0x38953F66,0x39777D72,0x4A0F80E5,0xFE1DD1A4, + 0xDA9D191A,0xA32844AD,0x171BFBCC,0xA7D556F6,0xF9F6D944,0xF9687060, + 0xDDDB81D0,0xE9AF4F2F,0xEF84685A,0x8A172492,0x50615EFC,0x20817881, + 0xC3E507E5,0x33189771,0xB9E2EBBD,0x2AAE34A3,0x8D3E7486,0x0A448F13, + 0x94F92A81,0x5150A34F,0x5ED50563,0xAD801A42,0xD0617AFA,0xB78F85F7, + 0x0019D384,0xF0F1C06F,0x6EF8D5B3,0x38092D09,0xC87CD4B3,0xE9D8C84F, + 0xE036648C,0xF2500BD9,0xCF069B5C,0x835326BA,0xCD107B6A,0x64F152B3, + 0xA9663E24,0x33ED5E08,0xC3B24F7E,0xA83205C8,0xA0560F30,0xDFF1226E, + 0xF1A404B7,0x9C2B4EF2,0x62802F88,0xE393A05F,0xC7F72F7B,0x1CD989BD, + 0x725AB516,0xA84F7E39,0xACC3572A,0x820ACB2D,0xAFF5BF06,0x476A2405, + 0x90953482,0x8E8035E1,0x1FB95F6E,0x01FD6766,0x1E63D78E,0xD7D42220, + 0x188D23E4,0x1941BCC5,0xEE1E6487,0x6E197F82,0x32772265,0x9B79D0C8, + 0xB4B617A1,0xCD2475B4,0xDE0F410B,0xE9CF69E4,0x831AC9A4,0xD549A00E, + 0x12ECC561,0x3D636A43,0x1FFFC99A,0xF79401C5,0xAA1D8251,0x84D29609, + 0x5464CB71,0xB28AAE5A,0x4AD934FC,0x347E8A5D,0xC87BCBA0,0x67172E33, + 0xEC70E245,0x4289A9EF,0xA8AF6BA5,0x1528FE0C,0xA87CBFF8,0x79AE1554, + 0xBD59DB9E,0xF1879F94,0x14D7E9F6,0x85196447,0xC4363A67,0x7E02A325, + 0x54051E05,0xABAFE646,0x65D5DF96,0xD3F8173B,0x09D475E7,0x9BF7BD0C, + 0x2DAF371A,0x793D063C,0xA68FD47B,0xBE2500A7,0x0D5071C4,0x08384AC8, + 0xF6CFE74E,0x124A5086,0x03475917,0x47267765,0x56F7DF31,0xE5696381, + 0xEB2B4668,0x78345B5B,0x6E2AFC0F,0x3AD0D43B,0x5C3C2BC9,0x833AB4A0, + 0x1DE2CDBF,0x4DDDCF58,0xEA25D69B,0x36E9B3B0,0xC8B11465,0x066A997E, + 0x9D51C7CD,0x8C6AE686,0xAFB06CE6,0xCC3F3017,0x6E4E4CC0,0x85A34875, + 0x498FE759,0xC24B6332,0xEBCD2257,0xE70FC659,0x439EC788,0xB47F2A06, + 0x696EE8A7,0xF70A31B8,0xECD840F7,0x80AE5E7A,0xC6EDF8AE,0x8165EAFD, + 0x5DAE5ADE,0x9FFD39CE,0xFC6B4C23,0x02BCA024,0xC1497A68,0xD18153EF, + 0xD787EA51,0x91386720,0xBF6E2200,0x98638391,0x144B9148,0x9A554DE1, + 0xA940DC7F,0x37C08265,0x7B782C60,0xC081FDD7,0x62B47201,0x43427563, + 0x1B66E983,0x3DAC0305,0x21E9DEA8,0xA9490EE0,0xE2EFD37D,0x3501F306, + 0x16361BD5,0x668B219D,0x17177844,0x3861A9A4,0x222403B2,0xB29F6714, + 0x7A2A938A,0xBC797418,0x3B241624,0x9163C3F2; + my $digest = sha512_hex($name . "\0" . $randdata); + return sprintf("%s-%s-%04x-%04x-%s", + substr($digest,0,8), + substr($digest,8,4), + 0x4000 | (0xFFF & hex(substr($digest,12,4))), + 0x8000 | (0x3FFF & hex(substr($digest,16,4))), + substr($digest,20,12)); +} + diff --git a/apps/plugins/sgt-puzzles.c b/apps/plugins/sgt-puzzles.c new file mode 100644 index 0000000000..082e5b4cd1 --- /dev/null +++ b/apps/plugins/sgt-puzzles.c @@ -0,0 +1,33 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Franklin Wei + * + * Overlay loader stub plugin for puzzles + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" + +#include "lib/overlay.h" + + + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + return run_overlay(parameter, PLUGIN_GAMES_DIR "/puzzles.ovl", "Puzzles"); +} diff --git a/docs/CREDITS b/docs/CREDITS index fa0fc5b582..85487d3611 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -684,3 +684,4 @@ Michael McTernan (The ARM unwinder author) Albert Song The New RAW team (Piotr Padkowski and others) The Fabother World team (Fabien Sanglard and others) +The sgt-puzzles Team (Simon Tatham and others) diff --git a/manual/plugins/images/ss-puzzles-cube-128x128x16.png b/manual/plugins/images/ss-puzzles-cube-128x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..c5cc5fc0ea7ab9d643447bc9579b1a386c11510f GIT binary patch literal 920 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwroCO|{#XuSa7#UhB#ep1VFHaZ8 zkcv5PXEEm8b`W6x{J;N~q;61*V6>3zsojsPP6)VOT*B(UXXBIuFOT>3JWKw?R97uv zAj87L&Uo1IKmj`QxX=g3GEeRmNp_!?|5?3!$&RH@9rmp&3Md~>bp*G-+jDo_yC3PhY4W0T^%lQNhzU)2qS9s<2 zogq^n?rm;3e7?kVe_ZOPPnkE$LO$6|-@fj6Owp&W(Y3DeS`QXI6JPBk$8^g(Z0bp8 zhw787j4C-Y^dm#n_!-}-u0Hidalzh{(3v(YTc)qkn%v&-ZBl6JCxHubn^qC6X}b7@ zNr#^WhcD7w57HJFap3Ky$xa7mcJF?pet7TPrCO8Cl^&EGtWXz4eao&}QmcIK zEog{-y;AYPRqop5&C-Gna+AaYj+*SPJ8hnHc<&9<^w87_jtQZXa!e_qZS4$^Qy(fi zgi&2ctv?53?ViiBAJn#AluFfGpCqurZRY9K5-ckMpMBC|W6YYeb5ECJ!$3&gJblu6 z5z~oMEC~-IIoh$lxgV2Tv->N<&A)x``p*d!)XPktcaE(@@G1LKX~x;%uOEK2{l48Y zl_lrXSB{|l>+8SYP3N<>d7${f@Hxwy|5^WocRlwvV96=^v&mNV!H((H2To@)zB+Hy x6nEN~(fp(C2>S5MX3zsT2otm=}7w zIEGZrc{}@Jo^T)!>*xRWug)_xYF&Jt6Lb5`OU)9u6^oAgTr1zi$^S$!;pxMD4e$9F z&j=jg!A_Jg+brnSI&dPoLatxl{O_*d($i&z_8LqA4GdiQ4;2p>>VFcGW0Lu-L

x zd0%@44*cTTyNivf=AHME%c%knF2^rEudUed*!R=jqXGv+9()gHn!$8r_jZ#thxU>wN6A|JTTvU_q>0l>*wwKZZq9pvEfSm+3$N4O{omdSA`C;am9UT7AZA|FZVIS3PixY)$tV%!^*e z$6Q_QJr`*2F$P6DWu}t#iGRzpMHz2RU;T&QX?u3`=i2w-90IcrD;?;$eZHF^xyQY1 zeXa;Y)C#7aJz4&DDueHp#kYOon-Sl0L73%+V}p(Ur&YF&4YDjJJ}a%iMH``yyvz>5 zp$!wm-*)bP&v5;$3j@=i)-Hx*jtSdeFSZ*MZ@H5o`aB)+Q;?AzFy>ZY;f!}Lq+zxL^LOKD71&q$DHm3qNhW6gYkhryWP z41)oLu|_1}hr->mj4OT(HzFp_e-M9G>!Eoj1M?FJhBSs5j0tQFk}$?s{(#5%1RZDq2bS`>GKXgSAFHSW1%|hiW!$?ZvDmZj=g31j+mwA-FDyP zz3_uQX)=Sl`wmew+o8^2W(x6eK9%2pRC{iDefD0!sKd}Z`@TP{lamw9Fswj?dj_0vd$@?2>`;)$-V#p literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-132x80x16.png b/manual/plugins/images/ss-puzzles-cube-132x80x16.png new file mode 100644 index 0000000000000000000000000000000000000000..4da785cd6fa2c7c537c4da28903421665ae060be GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0vp^EkGQ=!2~43JH1WFSZ*OwuH5-U91iq^elvfU1qG8eURCu{ z4B0vi9eNmzSHBuIot`Rpb8%F4@WQQ|51;#cc5YPX#{XZ>?~_=%{7yf}h9n+= zv%TvD3_q{*;!s}`J->HuzCVMN^ z`F~H$3f4IGOy)6vur_F8$B-l{0znVn#!@6T;yI_PeI#l{SE4%hT1* JWt~$(697r7!8HH? literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-160x128x16.png b/manual/plugins/images/ss-puzzles-cube-160x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..52fa88ddf7729e5c1008de96d417fc417f347f4b GIT binary patch literal 843 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Rg9%77Gk)~}Qk(@Ik;On71Q;1wD#d{uroWyp zjv*Cu-rnNQyY0Z^8u-3`@#Y>D&QF_7H>png>|ywQ5yz~M1upZwG7|5zZGAVbmT`+& z!)*3~&kT8T2hK9@sAMqbfAEa)jty9lkMVxS<*XSi`k1QyKVO{vJN%5mb3H}<4xNVH z*FV}bm~QOotM5>w5=V<$=JNBEjCGX(Y#ToJ+*iLV#k|M%#ij%G`5bJ zlUa;1W;@du+I`na9!Q*=%~o)OWTD8|2NRY}_K!Vz@9EF>3@t93X`T!=)*8$eYYsA0 zlm!TVhR`bbXM^}Rq?03FE Wp%?K>?r&KH$`YQgelF{r5}E*w`i8Rr literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-176x132x16.png b/manual/plugins/images/ss-puzzles-cube-176x132x16.png new file mode 100644 index 0000000000000000000000000000000000000000..b3cc3730b8f11614fa48dcf38802f722484cf0c6 GIT binary patch literal 925 zcmeAS@N?(olHy`uVBq!ia0vp^8-Tclg9%9Pw=>%Wq&N#aB8!1E2rx3VREh&R%>JG( zjv*Cu-rjEXd+Z_Na`Dgq{%`3G4jxPHY)f8TwJfft-)-^SnW0Og_q9ZBv|;=3>D^nnPf99Qht-rsGY^LA2;c1=T^w@FEuM^*Dj>HSN zysUF)3EXjC&{5=Fxr;#SZ&^W?ha?M4k!v}9?cd7JZ{}RyG+Du_Bk<1|j-+XFM^@D2 zuoz|w9MPyQVljLzcw|Lo1&}X%M5Ce($QLo*;-U4m9nY;>o*(55%Wj{FB=`D5Odum09{qoay{>m!7 znybd>DA1z7(Imj)DA1(9;RK{PfV86kiz1K)3X&>$5NiCM^S%j=0s<^R^KlR#MUVVB zy6cku{k`|YIqskI7dYR){7a^8+=>^WAJX-kB5F+5+nxJ6Pt1R=!lA!)r*8cE`TTzM zSL@aLV=8T%PL%0&{Oo`BKRe^UF`r{g^#2WZqDOw|y%(^Z%a`2>S5MX3zsT2ot*sMHV z978JRyqz1E7ws;WAy6X{>>2Pz2nPnr_iL( zVJGwLZ4}2LySbJ_j3M`HV!xxn`G*@Fd2D|CpDlh~t%G@vNypREoR1iu^FI3a7)USY zdvxqKkWS}+bc?@Hf$xKyvAGN1TFc(+zp}&U#N9LBXK&`0`&wN7XnCoh0O$D#-duS{iA59A*R*ZxSKtXe zVI1as+g+edRKPoJBtMT)ps3utkxp@>o{JZ;nF(tv1P~TWVPnJDlcil)7ct zWZ3l9n6}E-fYYkWgrCQ>?+!S!h6R0|R+*#nFW5yq0rH(HOC2jnfmr7MQ zI?}8-@+C4Z-VxukaD8Rf$#4))(DBN{4f+?9npPZ6j9=i&64-utpF=mtBEI(8MlnGb zS-#&)x=I~ZGT&IETm(uj-f(Z}PePF zX{~jHghB8P@h?D0K^NOmAv_^*w2|A%WU=Fc-A+UmbvQ~u+6db<{rD+ec5{F0_4QMZ z*G)I5(3`$Iqcs2k literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-220x176x16.png b/manual/plugins/images/ss-puzzles-cube-220x176x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2ea62176121975abaa2de00ced7260154e2e8508 GIT binary patch literal 1045 zcmeAS@N?(olHy`uVBq!ia0vp^cYt^U2NRHd%{To!km4-xh%5%uAi&7bQYjAPFdz4H zaSW-L^Y%_}mXfE0L*V`YuD=b-W~AN_HMM%VYSa3;#hj1)|Ln@)Q{{Y-Iyot}FRr0P zddex*rA3Y_V*<3q@i1$ppSrqlKjgT|RwI1PUp?WIvng5-70K)v_(OPzMEDqa+`|B$-gLK)suw%U7k zwG`C_yo-GJRa9d+`_p-%_hQMo$U1~=%448#nGB!CackXEZx!SN`pVO$+!l0MAoKo- z#`f*njYWGtFTQh+oAuPkI+jy!d&7Vd%NN|acN8QbaO$l{(t?^VEV6gG*jIjA5VNtb z{M`RLbL%z-+tnRvpI@T!n}qlvKPEvsZ#4%U-*(7R%lhe`xt|u7KXM{HL6Gl&>F<~S z*%M~rQTi`@ML)OK)25)E3qtfXrjo*ZZGP%E{|Vi{C+?j888YwNSN5l-6PG6C@ojmb z@#{t0f;~5{%1x@u`{gD#rRwJY{QL2-|9Nz75#p6-~7Az|9|{**Qa^?cWnb@#Qh9cr^KA? Tytmf{l({`!{an^LB{Ts5p`zIn literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-240x320x16.png b/manual/plugins/images/ss-puzzles-cube-240x320x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2e66d5ea062457a7246de4c89a9053e6d9c770 GIT binary patch literal 2582 zcmeAS@N?(olHy`uVBq!ia0vp^9~c-I9XOakB3)@LK#H@#BeEDsg8(B#OQkrF!+FBf z#WAE}&f8mwMNf_ix+LEFzo?c+bIYZmBjqRT z9IEu{Jn?1k4deDfBu~yuUzTqiw;RIyIgkBvxs?7L&7DRJxfhDgU;J|R*!;Z^IpgYm zGxpv85e%`vr0D$h|E+h_9rT$>POks6O~fmQqBJTJ|Ar~iG)tp0$a)QErbuU5z9 zemWIy@8or>`1Qxl5N|PkVfgY)JN_?+rUSo2{=QFF`zMSFA&0~_?P}HD{qIii3A`%j z@P6M{bDO6%&%fU(|M|!dn4y9l`){Ax@$${rxs$XXvp36qKT>%7>~c`#+Dm`gv9SL( zuU+->C#QaVTyXqu$DQo81wanNm-qc2{_@W?xmmn;?+ zzx_+z?Ki%gARX^sp`hz1c61J-Gz5`t(284HXFSFVMCU`%J) zQIV0UVb#&t)?>y46)^DvT2ZoIAWA?kO+dmG4GARaF5wSo`&%9Nm-pVz?&p2?eLnL( zD^1yw;Nj}$iosw!5;rooB5?wPargzl2+4q}d=e5|3O1&3k%(Rnn95QjQnKIfxsgCjtV`WW z$(M&9X;987{J%{fG(g~;HG-I){XbYd`CY%Q7K5j3o6TJ}C>l^2h&SKAUDoJ~Z{+3l zI*){HfE^{&h;T^nAPEboGnO~Xym;jh)7|}?pbSc?ej|ePSvd9ztq&GE61YP9ILd8> zK$&NDYz5!i9~NGFD?7{CB{bPf|97VIy4vNOCi}Z>0&0|H)1cczTb_E?Q}SAC_d?r; z^5|q$?~FokM?NCxwUURpcylFmeeO7IPZkV9Y)-!HiCj-MN-4n3_L?yOBqy~MYU{fUZRzI+>)>Oq7;6(2)fUmz& z3&iTtFxpLoF7vk)8RQ+UvJNUryt$$x0*Mc_|Dkw0whts+slD?8$s5Rru~!nzcK! z=!IRn;Ex@LRdd&}-~4V@HZ>~uVcqrYWDh@OKC>D*X{qfDa6>VhpT@O$Htexh_7>*K z$_0&X%XqpJt@F!xxD6-DdM(#W9N95T%;iO1h6sAS?+ry$CcW14fEZ%qM4RDIC%rCg zJA7vSNg#(v2Yd|DPZ0lk7W=Z`f)=^w_4Kr0FsO)GGBHa|n`I7H*TWiYo+-8p0{^tH zca#=nbH_xHAw&dDPR}TD@KUUiM+`=nxI-bcBz%7BUqtEhMd*6uc201&qTHd7u$b3! zo1$q!Eg~aM;}d8%0}@K(2871xMI-Ww%*;3q7RQb#U=~N9&OuBuRyx}~;t)bF;vzb@ zxvVkt8(-fPBZkg$?|I7p2{jbQXO#XspN{yCr?@!%isIgCRwkbA6Av@IqEd0mOFpIl z_cEaktG3V`F-n=DSh1xFO{O?vR8S_zjS>C0)8z*my)jKEXWWZ9t-uUl@#0iJivlpz5 zU(U{OUp-;RjE09vOl{qq5_)RnaU%QaF}pEgv@m1m{HI1W8e2J8)t__R#!O{GwsuVaQs3l_u5oh z#(2pOk6-`37Ij*QX`ktX46%fy`5Q1t*$1^9rXvt|C`nvjH>fH zu$HOrPu`Edt$!vo>}Iw3VOV`7@=|Ejf&Z+U^HnAjHve*S~_Sa2` zkQBFMrg^)cy>jo7m|1u+q|WKUW%Iiqrs&sNKAtug820;%Z`$%#z5m;z?e?jd>EE;3 z?Y6Ib&5d8_tF$)kuYWqXzxwIbo=2ZgC_RwBTlHCZewEeYPi8`#KbDK%|9|1U-FXe` zo){*-9ls|&ultbbb~bd0aKX8+{Kqx8V!IQyTs?2vz`UY{H+!jyg+%~saRbv?R==DVRne(R{W0~V~AaJ5& zFQ>)KhQ*+)S75~G?wV$z_8^6+uj@>P&w-OHGNLmt&S;p-X#vin>cRzJKl`aYSaG6f z)BrqTFip(BX6h%V$D;2KocvdHQLg^?-^J&8F54@f<+O;LTja+t@PUuXFKqdo%gii$ znj0ox)qMVif$_bzPe_t>9D|G()c}zB{qA>sc=*Ajf?;Q literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-cube-320x240x24.png b/manual/plugins/images/ss-puzzles-cube-320x240x24.png new file mode 100644 index 0000000000000000000000000000000000000000..37a7344e06703b2d17bf2070c758408ce8e72960 GIT binary patch literal 2162 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9EAz$3C4NP_?)LrbMNki((k z>EaktG3V`F-n=DSh1xFO{BKt+8}m_lS>C0)8z*my)jKEXWWZ9t-uUl@#0iJivlpz5 zU(U{OUp-;RjE09vOl{qq5_)RnaU%QaF}pEgv@m1m{HI1W8e2J8)t__R#!O{GwsuVaQs3l_u5oh z#(2pOk6-`37Ij*QX`ktX46%fy`5Q1t*$1^9rXvt|C`nvjH>fH zu$HOrPu`Edt$!vo>}Iw3VOV`7@=|Ejf&Z+U^HnAjHve*S~_Sa2` zkQBFMrg^)cy>jo7m|1u+q|WKUW%Iiqrs&sNKAtug820;%Z`$%#z5m;z?e?jd>EE;3 z?Y6Ib&5d8_tF$)kuYWqXzxwIbo=2ZgC_RwBTlHCZewEeYPi8`#KbDK%|9|1U-FXe` zo){*-9ls|&ultbbb~bd0aKX8+{Kqx8V!IQyTs?2vz`UY{H+!jyg+%~saRbv?R==DVRne(R{W0~V~AaJ5& zFQ>)KhQ*+)S75~G?wV$z_8^6+uj@>P&w-OHGNLmt&S;p-X#vin>cRzJKl`aYSaG6f z)BrqTFip(BX6h%V$D;2KocvdHQLg^?-^J&8F54@f<+O;LTja+t@PUuXFKqdo%gii$ znj0ox)qMVif$_bzPe_t>9D|G()c}zB{qA>scu|Tdqsgan5fWaiG}@P zd$tu7N7qhtsLoDtW3r7@p1x{!z{H6ST47d~ca=ZhXX#jFLAHP342m`zhUq5%{HTkZEi6~#`k|`=SMwSo%~~Y#I9P2jCz^7_M8`X rmAl&9yR)a_D3|g+hPD6XT7I&fR1h>?@T+GsDDpjB{an^LB{Ts5Zsg@; literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-128x128x16.png b/manual/plugins/images/ss-puzzles-map-128x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..60006ca951a7d0ff639227c39b2a91af1e658d3e GIT binary patch literal 1254 zcmb`H=~I&j5XSe-3vURCLIp8lkO|06d0)l3n9>2JV-ezG=O3~ zMr?x`1q9@10BeF$?AW4-h?*fBHc&AEORHE1L}FqQtcAwuO#g!J%5I9`0!qQmDxKR-a`PI|aGy4rp zQcq~+t-{jcm*TRx(|&~y18!gsgkMwyAIs(J1q!|Q$ z1b=Hd!L6g)sdVbO_BYpw;PNA3UEn{Nh444?Q) zE{)>9#qp9bSJ3ANmt8|>MOYl@-6An>E8cq2JLqWeAoUaKI-C#Kz_coDTiAz^NU{+U z!qeB6O?=!kd>*;q`;|Uc-wpzTt5b_D24bUzqpWo;jDuL~sy#uscfC$%ig z@+J#d-9zI~G`%@Ci}NwEhay%_?lhMII2WMa9SU)QKz>DW(W3Bj_XAF-x|JnT<`$kL z>t<_VAyHJ}sI%}iS3OpsklWFyNwyL`VKZ%dFGi#PO-p@|FcK|9l zD08oS|JikW1DhzM@`3^|^=tlEiB&m>L%+AyP=b(zoXi*SozUazr>osiurDwL&&2~( zB^^*FqJUgHbSf?4*@^A?LD|A4#HcpW<+)4_TMg%w#AeEw!r4 z%qK%%`5{L*X<$I?mksJj$irOEEYvJGss@jr|Jo;Cc>@;!_iHl~5o1c@S;eE6&y2T3 z7ne8EJ3NEMWN7NC%i4nquH0)`h0qivS!@Kh zzXT6mfoGgT-*(EI1ur#2k3u@9G>HRijj)e3-rO;{b21vpmJ^VYwO~IIob8aU-yn|1 zieWAJFlzJDzHAwfzvC|xYV?0PTe)*ArpuX~!-lQjCwtY6_-5V7IeC(lpnv+kR=g%BmQP zmD5pnzbe_~qkxKC85suM);39@oNmXytxBc92<1+tH7vuV*_Xm#FvEU$P(;!0a=!d? z3(Tz+ooli(zEh0Op^%<|0EkZbU*PdUxjf6izWH>@E2!)F*ONd^hSLn-IDrQdzWL9p z|KIrXB0Sd>yA3QxEGxq0?|vo6Gu@5_!LUv_P^+c=S*?$Ycr7NnZ?VI+J460st6|^d z)G7LoHE+!LuItwDdm}30^L_||BmPfK#}U((_R6F;6wB^^qciK2(Nrm+?ftjnM#n{6 Ii4>In1H>x&vH$=8 literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-128x160x16.png b/manual/plugins/images/ss-puzzles-map-128x160x16.png new file mode 100644 index 0000000000000000000000000000000000000000..9ba56d596587fde5351ce486af5367bab217985b GIT binary patch literal 1342 zcmeAS@N?(olHy`uVBq!ia0vp^4M4nrg9%7_fB3u#NO2Z;L>2>S5MX3zsT2otSUz~V zIEGZrc{}%Z;cWu}*U#qQ?SoEf+ICNb@4 zmynGKaJXGCLHvR3sYefHF-5IcoX+t<&f?9<>7o}}m}47WPe1b{;rB+PSErcXq=wA( zxtFY|U{n#YB-?;>2Rnav3GXt7?++3ut~;nMykLv<`|_^`XDY1@6VXu!zMzuAyKb_k z@x^)V``gcOoV}o%(AMzBOJ5@)`&$vCX?3|F~UHI@&WJ62ZkFS5DH}ttEpXDi=s>mKa zeR<1^h2L$r-ao|GR=w(v=+Ui5Pc^)|K6Q&S7l-wYV25&_gvIuPJ(l>e!ic=hBlq>0m@xL`&D#_RO`4!Wfxxw?xEi7yg`qQC!BHlm2kao z?V>}+^%RXl1QW_9==i;hQp*(;wO+nEkoD_{fY^qkj{A(bI;>Y{b~EYlsa;hm)=0SQ z80tJZRipfAg7|cy3F{ZcPYXWqxc=#~`x*zr#8?9wwx3Ao;91%bG4Y^)q}Q7(+zQst zj0qYHJgg0{yc)nD!NoAR1^=_2C}-BHtXD{S7<=GxU-(`};f)J8BlbpLdn_)vG;Z=! zAr*VQ<|EfGJBl(h_NyO!rE_!tHSbOL-W~Vf_RfrJ#bNb^=?jWX7=2Bid<$hh`S^y{ eyWcz-3}@`Wb1>a3b82x0m4cqGelF{r5}E)4{4MkV literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-128x96x16.png b/manual/plugins/images/ss-puzzles-map-128x96x16.png new file mode 100644 index 0000000000000000000000000000000000000000..931db51a8244b9296419a388e36b8d28934a5c77 GIT binary patch literal 1145 zcmV-<1cv*GP)Z$JHlhq|QNjU> z`vX8wx5Lp`)?x2}PvFruG#YMWiayDI=pRsfi+05DlOfGs3Z7XW&%ZTs`{#ymuc z?!iAkZjU1yo4qPJjPq5GJxm-;1AxZF(KG;POdL%EfX2kp6a%nyFg$jDIsm}hO}Myf zd}mI;004h~-yTN*D8vMSX#kRnK>*-7^CRCH!2NM9fF#@pmjaL=0}B9YJfFeU0Hitt zBLL|TCW8wCNEHTl0Mbz$2G<0TN)6csfOKH9CtnsoA`CD9(GQu%VE$NYb!7njGQ2jE zbZh|71UHKm1qT3eaI;8JZ~zboH;WVnF9ra#JL#)9*ixj|jFbgf3hE?UgJ?5Z5{(@G zY0|^gqGAC(0N7`GK>)Sv?f`t&?qUEyYF_|eb38kMW+UY{G>erek#vF%COu3QK9;w2u&4|g%$*)E!KV9~@((qG z=9+@PqKK9l1Qfpy+|CF<&0r*eVvJ&;PYOWCU@(9zOt)#y3t(p;t7ABTN2CInG!e4J zrp2bzgF!Pd2Egx}IBzppov7+5Er`#+2q1gfV+TmpBavoXF&P8^mM6}EO9=oDo;U}t zCIC2i;vCI_)EA?)p@IHJ0pMW%6hvAK0A?Uf#J|0;698(v006*XEdWK&H2~BYECirt zxD0?|LtX^{aO(vC^fwyJ1h6x(DAn)urvRE(U`C?g#_BRu0Koq|y9j{gA~k)@mUDyu zCi$!&O(8AGsAT}85`&_N4uHBIqgDctUKxO;lmf66W7J{*(pN*qXA%(rOC`F2FDBG9 zEBF2gDzE^c*QVT}TW^bO3hJIroz-Kjp169Dwt4HQTwt3ur%cIlt$f3JsLo5GUFLEn=ojV&7RC}v-1HY zKL-5C?2-75ss1dyNT&dhoER`C^Be|F&F=*uc`)EihIt|QjsTL!&jh;J7OA`=fMnYT zmUc(mEdB#Bzs+Ku4nQ(6=n6pXjCDo;Ny{Jr9HvG9NyQ)l9Hw>vN#2tKz+s936n`sM ztItuEL^xQqUYV78>|sJp0RTP&V{vrVEN+>VZci7j9O|K_7yyTXy|MtHXW&y-0N^m_ zR1@_H096KF4ULuop3-2EE(O3|LAfj;faibZhK~TiD=Pp50BMm*6KWa&bVVvnsA-5V ze*mGu3ILN8046H{OjZDxtN<`s0bsKJ19-lkb-A_;@%0H{+g`p14O0~W{W{q6&~XZS zemq`&{bn$5R~5kXyFLK)Q@IIeD;}qyeyUyt6?Z_$U4mN;Lp}5lQsjvc`wo}{00000 LNkvXXu0mjfg^SC} literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-132x80x16.png b/manual/plugins/images/ss-puzzles-map-132x80x16.png new file mode 100644 index 0000000000000000000000000000000000000000..d7f8f47e470988d900862e58110761abf6ddc8f5 GIT binary patch literal 985 zcmV;~119{5P)mpD6h-T?M9EQ7YATG%)d8fQN*IBZFaqfdjIy7uYIjgA3|-+;-haN)hs5W@ z=Ev{1<2c)&UzZ$)_*M3^l`sM+VFc0?7}{|evp>$q!q_fn^bN}Tq2C${~p{;@TTity0F5Sp<(c85_R9tIdfOPWW*_$cUyAv7lP zN5XiPWnoC>_y!x*x*=gO=U5E32?cADFsO4ZhJ;~tlrY>H2?HFvg|A~a+8E4nM$j-A zx>`$H@0yvIzsOWw0Hd$P55pGu?l8bAoG{E`-xdau1K2TITXxjAe@_^bZegsKnDdG= zp4l4qY-+4!Z=Wy(7=G!-P$H2e48PO~4da2$sS2*M%^3z}1{7_P`?8@!oh7RvJf9VAQWX`ciTj+8X015RCdid0;oY1ctUc zx$y!~Fdlz_0(uzW`U4FJzX`DK46?bQ@uRX|DQa8>d1zr6c))>9*jx2VFhCv#7(;mY zVR*938u2rh{%$hB&?XNc&A4(?8O)S)4GB@+4blS zFshXYeev|*lQ5W7?m!r9c1;-k0`$2b&wvi@y_$s^l>chRP2ed>naU zR1J@WL2CevD$XAXgO(Hq_?)UK>yg$cW~00KSA@axFkDsBpxWZ+vhp5G#1KJJ81!>o zOT`JJlL7{@`mZ`G{{m;00000NkvXX Hu0mjfps~LJ literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-160x128x16.png b/manual/plugins/images/ss-puzzles-map-160x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1610d132885cc00f37c97af105aece2b80227e GIT binary patch literal 1438 zcmaJ>eK^y56rb6c*OA+7no1tJWIQ9uZQk^RlMjV^^}g7N$n->i%>8y61V$`J8jU-}A>g&pDrbe_wAMO(RVh z45mXqwT71)KdhROixPxf#-3f!8LNKWI)+6ag< z#Jvm-vh4g3x({<+GK_AsqMHBSz@=V0jLC>@u(VxC|!vP!-l3i`)GZTC_9n z(4+}tNlyr%LP_w&i@`S}Im8;9()(35ZRl`?BG~1e=(UR=Sro8Mh3Sm764AFX@VaH^s4@9XbBtvPN?Y?%O8;tY)>O#Bqc>i(L0id*P(!UAn;& zdyuUMw8Y5{!A-q(Y!P1MXk=C-Qy`3;`Qqt;lgtoW;9)4wF%OwAp(q0fYcQ}* zzZ9$iwM+mT&6;l}GN+hYIg)xmoB_3Q2K$gHF_`T|%A}NsI0ga zEV|VHQMB*Qc-jv8*s-S3(uwg`FnSdZ?h?b}A;HD^iy#`uwcNH}>S(e^kX;R6UaKrR z5B5s9^!xecvvG2>#1HQMtr2qSaJq+YUy#2A5`U8n`S+ingk0fy$6E`eLnwSKnZRQ2 zVl;SKc}=Y-9KbELYlX~PQz3VOl5`5G?!elQQ-CX*b5ypwJ`<3g6m2%@a$(xU31fiq z%iuYI)72D6xTLQf1G?~6Rs3FO)Z)<6SelDvK@PWfz-+tP+NMSE8(eX!cQZI?oy6fM zIbmphVI!^ErNKq3Ghs;VM%yDQnHKvKrb6kqhqgdHVOByTA+gGtHoQyfzKr1m8N~}dmbna&d&e&@q;M?EA~Kwi>h_i* zzwD!*ZF+@s1azg_my9%aRjg3KMxq{YbIAzmN=pjtq>=!48IH~z!P>wE#1}#gcVezM z7*`YV05fSHuOlzbuacTeofT96V1~O@$mSm}e0Ipd8v=_S_`XqL!Lc+!Z&o`!FVhw( ziJk>^wbWDRO{8dhyED#E37w?yc)f$$t+fTv6`EoFob&?|s}ZX(hkN{ud~8q4rSRQ6 zb>ZD?UR1}`PYYz!ta^e_fo(V48ZIJK)uqlB5geTusyG`pR!VSyiqM3ufsfTMV1k5r z3gllvWg`=abmgKwirCN4nr-^_NB|SZV@Qvlo^@*v87LPmPNeTlzK-Ejk*Qc!u6LxX z&Kuvtp7a%S_qX2@BKSz)PizJeQF%(gfCGBb*KX(m)t*)Y)uvutsT-899IPh5cupR1 z{pMrVe*2g|+3eYw!|8l?8pZC4AF39Cew&+q{kb55b;8pNMN!^b8CcZx{%R$=te*nJ(tgrw8 literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-176x132x16.png b/manual/plugins/images/ss-puzzles-map-176x132x16.png new file mode 100644 index 0000000000000000000000000000000000000000..23acddebbcfcfec7dd0e3a5580e4b8e9a47e37ec GIT binary patch literal 1580 zcmZ8hdpJ}H6z6tv8INnnddD?WBduj>m=xu*mpsO7X*RSqtJ>M2n*L@l{N$rpv|=_sMT69j1P)R(Bfy))e&&5w1u7`81rLribmxu z!4s(U3jz9s&mn=p_7O?5zWK#YmOoOZcoet? z5OV4-NY9~lqbyQ9yu_I^mDwjQWDH~Dcf|0=}GeCa&ZkFuOwxX@^US1lGWqRp72{+#5LH=WU%cEVtpJ=)g!E3I`1}=#w zi}KQfq?thb1GV5)dh?wOpijSG^j8?~R*F8ELr)|LmqxrTLT+6XiYp@m%al3%;GI{# z!ET2(`AAb7xr>S0=J@S!fhw&_83!ercalykRKP>CIk`ze^>9x5|`z?-|5VIPov7tDf1QZDAvLnR;CE zlz*B~V8Ul@x{lcX5Q(efqHx%V4VGvze&!Vei6kYNs&*jk0ugOT8BvjmQ^gXNedtM_ zPIWzK!3wEN;#_W`?#LBg;Hr?@9sQ3kv%L0)#Jb1n&z%owzrrY4f7M?`NeC$#*xn^T zk);lhou?BVWNO%uL4&~TGJe$oJizO(L(V7cT?hvqHEBqu$(7g7-mbK|;W&XR8;Mr) z@^QSnFxgoPjowC_AE)w19N@tYgMYiTO`n&40}&>598d4^V0*%nxvgfWVpQSCv7|hm z_AVjQ0PJE>p?F`liK9MyiP*gHfcI5{Qx)c%Tz+Fcu|~z$NnQKg&Ox{R>h3*73!@u` z!==cy`|C*(-gj=Y8`yA-=CLl`4{kkJ%;J%Iv6>a;;1o7^(;liQK;IDbz!}%Un*`{) zu(Tul?6D@7wuII2WN)CwCtET^*gPxemu!)4eY$35!kOV=AWLb;%22Ib>kbtweTF=H zmdNWU|g z$ZJIxVw`V`vqpUC+njrI&znuD^M&hkK16;@y{E|Xtxh1r)Vs2=0IofBCmM%Y2nHn9 zH4845QHLQ=)1jD2+g{t`8E6O^2(}asN*6RIt7nd*ZD^$WtliwRwq0@@Xd@UnkGGL{ z#8IO3{Ca7sr9Mz7`I0>-^)9BQk*^u@lzHz9=kv&aeq{qZa`9JohELA^%C@AFo0tq_ zT6?*d&}_szP8!&ey=eZv>9P4cA0B6Rs1O3Fn(m_UenG8Cc-9_BOb#ncv!;mm9s&uB zPFAPhsb|>hQFGy{sEbkVkQ|$oe&O9ZkW419Bf=-EnU@#AqsL1UBEBq8ja7}GPTtGJ zpQAhMdu->Bi9Ym7^#8FRVNEj2y3Txr>c3Y-0XO(4nu-)?Ee}`fJ4N! z2(x!0B-u(ky28)Ls5#|lMtABuq*y^o-M9NLBiBZge4jHjF$Uh!J7 zCgCSizh=^R_Rv%WUN}*1CSWe6msmn68lWS@@`2_tn>C;`(yDQDt)a#?;*3tqlFynS M96-k|_(${q1)dAUx&QzG literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-176x220x16.png b/manual/plugins/images/ss-puzzles-map-176x220x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3a636f91c0db6cdb341ce8fa3da30690f4ed624b GIT binary patch literal 1833 zcmd^A{Zo@y7JlCtLPAS;*+`*4!!2mEE6RWZ4Jb|8(8`KM5SJxrX(JG9WF-Y8(NIXB z@ItjNQp1;gWk9rIL0$Qh55X`{5imtUfgmJ^5g3q|APEq%ak{_k->_%q-e=BpXU;iu z?wRN09{KVMYb!@92!gDG4l=^b*kC>iJjQ%S&$R6{10nHXWRe-*8VqIe?adBuB8Wi` z=UrV?OmY>UBZ10`!}S*)4DDe*z;Dg|r6~LNqJ8f6+w)U%$-2t>mg(xPhhlHp58wS` z2z(QEZl5INz?)~#2fJwh!s0JO^uK_E7;I2KASY*E6ZKW-cx}e@I|jGrnu;8gc5!y@ zBzK^Pjr$~TkiAx7jSR9e7$jpS8HXw$7*(fTs~>}u0t$G84doWjMr(~-p9!1S6WHps zykM8K*HyF){pM>OjPe1qkw9~==er`iN3cLVy5AlZvoT0Nr<}+*T$0N z3!F0(NFzXX^I{2DNrld4=!qSF{*-{+S1d#`()(;kRot z_xLCMBc&|im4##}7{jXBEKh|)&pE;}PRc_qJ-p&GS`q)O|NGJ`5+0qs8dXMM4UXNX zpjA=QzFQGJmD6I8o%J}0A^2NQz^&DQ*Ix%lY{~b+Acp=yXK_aWcwJaVbx-5co_mOFc+BAVNb&2rx)lk7lV@W+;#a@ADA5zLI1bo`h ze3(tUP_i@aJY>t1JNj64pOLJ!!;E2;E{f9p|_yp$OGxIC|F}~ z)g;>$cSe*v(N35QE_G8rwBt>lh~QDP-~h7NZ(npc^2v%LkHbyOf#|=dpV&ViGap%2 R{dVC61%Ao+_0z9K{{esxC4K+^ literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-220x176x16.png b/manual/plugins/images/ss-puzzles-map-220x176x16.png new file mode 100644 index 0000000000000000000000000000000000000000..962875e0bf2f2d8a5b53eac2bade0c6368b595a8 GIT binary patch literal 2086 zcmbtVeKeG58z+Xw_$cdpMJn}_c5IW9!ptBtLp|eYunaBlypByx#z#I{wL@czIPx&H z6_1&n5~4;8##g5nnep|CmBz=52196ucvZvuu&2M?bKZaUIp=O*fK!m!js@s15xdLhu%&{s(qJj&9j`?4C-6u)np4!M0x*#bZ_#{n9z|lYe4m;=YPl}OG&R&+fYtH;hAJ5EXV7c$YL*2Ov> zqMS1Lhb{2)YdH?X_fjPx&ZlxD9+1^{yJ;-kjtAISN^;!o7_=CEWzhGUlI-Az574HE zXmXB1jt4~OotU>vH(S+Ysm|a&{kCu3n)ITe7r?9kFZxd6&T`vDo^YJcngxdIQ%`^w z#oe3AirX8GBGpe#lZt(s9N?!p;ZgA*wFXQ_l6Ic5vKRE+5)6#q){`p0TiYVK+=J z=;hN&`}yvB8UF2}UertWT2DZscTjIH{d>|VC_BdY60ny=$G;V6(y0|}!9Xp7*k1+a z>sWD35LB~nX9|4l*6l1o$HsoPz}K0fjSxA4DB7@pSTtn)MQ5vy4iJy>GBvgYE$SA{ zRyXrMB-0>eBHUY}Df2lQsljit1^VRXx{w72?&Zhs1Ahu(`Q@(W5*{K=_AlKI4blxV zs85eG1Fb~5R;#6eao#_;{?6YNU@W-xby@9`hS8MLR`7j&60td`p;^{)ea)H`(rv>3 z3QH3IeRi$B&nZ`-cLpK(2cf_^*w^dQG&NCQk}umWOg4*)qP zJ=wGb;gYBMV~tQG6m0Zo&r*nfvBKTF>QdzxGXfM#qQYp|snPLn=E8&xrm&(!HR-4D z+F}6ZVwU$|9ku9PT%#R#48*TaA2Z>6iwRy>nYmCE2+jY3_n=)Yc-$bmRcTk=&uLWp zC>aA3PA-r}+sFM(@P8K?XvBX(|6V+Xwd1v`+T)jI&a4k7H6h4^ek!vE$OyR)@1GIu z7I%K?qb$|6y9K?b6CqV|m>T^7GNLFN)!oT|-eriI<;e zOh0&-=0{n`2bZF!K88%|Ok4R%BISm{cf&|w!O$Z%S*q6%@OmhE_nI;|9D7w@d?Bi) zxQp*8VJRQi48GBzOA?}<6-$r0=M$3xL(acX7w|<61KqqGs^-6l41uin?nrlG*PWE> zSC~!68Dlz`=~P>+Dk{2*=M79C(6rU1_ArK5pI0W;<#n>`9nt~-d6roZ}FWN+C3b>RriWCsd5{%G4UH!UA1yY3NFHV0o*#N#X6{;TD@i`*fFI0eq9X#dL zhIxrFqW2vikmn&`+f79JYH9|}AeW4HEZ7pe(Ns3I+kuQs$qnK_>OK8Oilh-jx%8bi%-8*3Q?> j8TTBMxobC)pwe;fXX7XJv-`}S`^UcjocH{G=e?cx zocBFBhXQ=p7+M$t0IwX4|qVOgyS(b0Fezv)v#i2cTw8K+7l@zhB%;?La zK6e4f_d3}PfAf&w9qzNK)$r%*Aio{-{G&XZYQX;pruZJ#$xlD_Ru{0i`C9dA5ROPv zt7M+u>3Tz59i!q*54Q!Ep|lVEErKU1YJ6f}M3Mu9nJNlx>VIt%OHZ?5+V9a|E#T75 z;M{I058oK`8%@-P1I3!E_3tk(AG3y|d-aP>UHoKGyVI%NS5xH1%cBCB5M z1R2pzOE!YYH_}QBOTJyciJna!)U6iXEj)*%feNymS9j(|&5um za?S#tEn;uw;5JFXnGA1wGt!S5BH4XiMGtHUQ_g|<$N=dkr1VS+eLYqHD7VtphOj*r zt@{n#yXDljw&2rc6Qg|Nst@|#Coh{qLboy=8JTR-5R|F`J&==U#3oAmf((!s=}hGY zuvujSr?xt@+9Ov3w?g63ZAMK$z71HH;*-!`@{SAb6C6#J+6+LVT0_|Lnujrzghey0 zH{xMe%<&J*c#QOHV&6zm#U>o=s2<&8 z8$!k|`(FxZP(EWAgFePl!u!sIYI?>B+B+dL#;_hLEOH&j^jyqKTId={hll;yQk<$z zGZ*8ar&aYMy5&TF3zs&`otm9Zs*C2%aG7*risN0l)0r7XIvvNoKCu_>@Jy=}!)3v& z!*k77SIK$PqMeAzNeg%}2=C4j;=$#oUV_?6jM6g$jugewHR|2Ai_G9xx^$=t%GO(9@{mabjI`ScYF4?BR>q)1=`lFeKz@Y@ zQsL=K!F-NP79EfWEGog^aIxJgbfo%q@8Q=`n#dcS;}IHBUVH!%E1k1Ww-&|ya-gI4 z$xQYF*X-QUAEJ_WL%m@Nmq_lo+dPysedHsusc_FYBamdX0UWe9EILERH>%%VjpiwE~OPZU|)!FXG@Hc zrApx?&chZ{#7|yJr&5(08re(jx-dyaaf|hc|I?yDKF3%_LOLy3c`n^(*H}S{@_9#H zW1`P=hlTEdUXbrWxffNt5Tg013%BN7KN;cOYnFR=wo#VdQlFN8 zn%oMlTA;CJrwixVf9!ymiK5hsS6L*VN(Vz)EITws&wYn3@a_xwq=`z*O6m(j<-DGW zmyqw>I9=|iz}ucVtvnfm@2{WK5eBfVhqBclN3*5NXr>Tm5o~C^ppV9=)&B9NH8H`= zM#q7S@q&@}cs|jQHM{Zl10!P&JVRN9M97}JhfOaZ^zPuWK zj0JN6sU5v})qm|i_&w}x(#NxreDyCsvEhIEqW@3)Th;PE<&s5$cIJX;_K}rs{3~Nv z_9{a|$SI~8X=gGWGRFoYb857+w?S*G6x`HOqoVKM1^)t#zuQ`$^Dw=IkZ z6QJr-L6Ar~Hn&K~iBDT@|BFzja&}@>Y==i@#fjg|+Z9&_Ud(l=y!xqdn{_a`CsR!9 S++Ma)D*E{Zc-QSc$^Qq#hu6jc literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-240x400x16.png b/manual/plugins/images/ss-puzzles-map-240x400x16.png new file mode 100644 index 0000000000000000000000000000000000000000..107a9285b3b8363f7b461cd58ecc4109cc91e6f5 GIT binary patch literal 2917 zcmeHJYgAL&6%GjmB|Jn7aX`tccBDcfLO`YzLZSjPywnkpN5afdMP!5lg1|-cAft|= z#7ajVLAb~=A`}ouCm0N&qnJg($<>&_2MI$&xuHNrF0V*JFHE~uyV})X{Wz!wQnΠHWp=o|Sq;E9^GU+zuJL+#3Af%&S4 zgnkf65qlU13u`AelR2B>TuzUi&07vEHOcOLuVb?o&+qWh@shyh9C{qe< z{N|>gp;$V$>r(KiA@7ABK4fTDg#VU_zduZ8$6}xpBM?z6zgDiWU7kZ zuD_(;Om|>Dee4JI2WA6K(C&g+UpwTlF*RCi1j=Vd;bfguu>>2;PV3452NO;@<^({y zD|dF6uR)xH12kCU{^r*ME!!MkBnJ_p^y2fDOp|u(f@o+xlx)dv=V`Zq#a4*3g~k(< ztbth;vQYZG4eW)LCeX7TAsH4Z<4qBvWMtx0yF6+3+2y`(=C8IkfGvhF)#`S-nZ3!= z8|U)V_7Un+KvKaQ+7JPGk?stCbLHOJ0IX~&!wN_oGxRX&hPSm?nK-hNOMpZ{4Ri`T ze#hVd)TcYa&*M@S-hdLogI|}Vi8I&&k}*fPC@%HgcVNKD^?XjkbX?Qrtb*p81i!YN z>ryh@5T2AT3%w=Kc6orySx6DD&I#h~9N9rvu7%&G-msFsM72QipWH}S>aQ`BK{zN6 zU_uoR7^If90a=ZDX<`|RRa!=&; z>(+wqB71yE?hm!fPKq5M)_(!*CNY7D7D3&SKsTf!gDU^T8n~oSz?k$s)x&Cb%6#AG z<=zTL><-fwkX!S+G0z^g@Wzn#Rv`^Gh`Y4>%)$3dvH+|pZ35;dkJCU-e!njKMhNW< zOYGV%v0_ihlplph{Syub#j6k>R976cwz%Z9+lyvT7xT5xP>Ib`oDrWMKj?j>tr0%% z0-FfdC2W5?#Qa)7Goor-W?_-XedzeS2PkwC^R;pkw!}~VU=3oHIm3;Qkj4=?TsWsw zpRJ4K*h>%RYI?nG;Rx{=)?+6~Is84HVmiq^poV@JHy)!uIU#c$s(wHg8Up{8F|l$G(j z0j7eeO894JCyaO zE(fw+#ip~qMRSJEofoKTp;P>r%>Kc?;1CM~LHYvO1xctS6ev@kX_!fMJXpmd7+KN2uzyVy9 zNYHjD#~pHy&b9*bRKFLriMUj#x9jADwXrgkvZXE~8B*UjSQ|$#u*wT-^q2d*+NDJD z%LDzyxRCF5-0dt}2d71<1X;*@hx$I*PCkRw9WJHKQG<11@+*k}WSD?*f2NeSld3U= zaW@_6z^;121+){sn)xuU|KzwcNI@yA!x*1EWGh@vC!eZCIOKU>Z*Lx*TwFDk->6(( z8X5Hil`$v^Z+zCnu5>M#EpDZf@vAT@gWM7=8HT0pk-b6-;{>XOK2WG$rbcS2xwR)B zH1fJv7D|KDG-LX>b`Ft?U);owRHtX@U(?BDQNtS$Q1%K4g%ma_Gz)|>$f{^u@by8f zk^%H4)UsRCnLK!*fT?STEc7LwaUgtDFil0>XBJY0U=#s6++rz1OD)!&QJOdo7S zH7F5GeGXWA(+h&sO(+RWdO(f#feo}d)*qD&TCvpPc|Mk{_~b%OO3IKU`;@-*Pyd`c zZ&lKVWbNTi6*lijY3~xyipT+o#XrD7SR~us%$6}oLr*?J&-n3r$lxr*1;dvf`forC za*pdX9#NL@&Hs|)|93g?WJGRF!X=?~;os^v_P>|)U)=xM>iojV|BVxFDJQ6|gJWi8 zw4fa&S782lvZ5fubxxTM3%?98FT9E-hn(kl8L+T~rCgHv$?N;fGv!LoTg_Ja0tUQu zGZQ2m-Y-~?m-p8^cOU+cYuu_hlkLv?@iY@>n))L>Q%+md|K!xkIneZ|3u7!?&dkeR pesjGbqGkLihtDUSR=d|(?wcPgy)r| literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-320x240x16.png b/manual/plugins/images/ss-puzzles-map-320x240x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7378709a16df45f4e5aa187223a8d04cc2223a77 GIT binary patch literal 3103 zcmcImX;c$u7k(#!AdnEjvO}n7p&~RW1}GrNVgn)|sE8t)6et*3L@<^>#)VB3tWv15 zSpkuyBp6nUFd8jXKCnR8w?xFM1c;(6215r<<$Qno{rOJk$INr@bDw+PGv}WB-qd}r zRDzs_8~}jecWR_F zO0z}jn}A2J4t>K9$u`+1CC5Fuha0+A<2GRk2IquRudYjeozJyvU_{tj?&OG5KQr2s zZR-&9>$k*R4id2UfH~NptbjNqVvXGe|7>zFP3t?EMwBymuyA(Awr(-aCV@G$wCC$9 z^Sha1$=pPndCr@#E#xw()d(ccBV8a&=BpT z;Qa%EwIzRJs2cJc!1ITG>>j}^db*%8Wj@K6(E*)F z>H^U_?NDbrXbd+3So#^@dru378chM&M-c(OLoqApGZcB<3#yWB76n#!45T4RR@nDHoXb5=1|l9+i&l#mC{X{a*G+U`OOe zq>vQ=G$&>0d(|qX={r`Ae*z-11SerCbw#5tid3&$N3S+PiFPPsJSh!By1yc4BI7p& z_h9NKG%8jO8E-YS%EVc{#beEjIe-+*t%AufUYH&V?fl3Ys002ZUpc|pv0%fX&VE3{ zBft~C@_YOr|6fLjbpKCpdyc8rG#V9W4jjuk?WT`0%=t(os~0#3-D26tJ;ovP278$} z9CBm-h(2W_Y-0I+*%Tz7{w|ky1-Q0F72Su*+h+|*zlL(J%~)w-vov1J7JVZfX*bOk z%6Ww-IeTYYI8+6oTyhXqe{m=Q9WIcVgk=om`UxRgvfUeqk#y6=fD!G z6gxP%DDbs+=lN0;sVODV&7rKX5ivmQ%qHv;2FS{(zmZC-d6i!^0q?oy7=` zy)GWRoy${kh0LSq6VX^Uxzy;LOs-|a+XkpVo2POE>#AcJ54&Mjh*P)Ts+eutDHrHp4xTupEf^32(PB3}%I&SCPu0C|Ni=3UFH z%m>h$yDz%zIGFpQCQ%O#YPqIO1N}KX6$RiqJ;Z7@+^KhZ&Zy|9ORRff6=y(|&^Y+* z1D&(vWv{{Idv`Px>fQ~_*OC1MX-7tb$*<2PS8k}62L-Lhu&PgYRAOdR6UB4lA*?!m{{-9HAV+p@6C%cx>9sQ(@&D`AktRCC&@{; zW&4^$mtXbUnnZ5lo-SN4hU3Q^v|8_jY#J1O@h(S>+^Mcg!KC|~n!fYoz?)sJQs%vp zy=T3Erk`>_U(7(=mYC1G?31wyJMes0?ZHCc*<6Ll%m7gL>LspN;rS)l9*?c3rjIyj zw>m*jHF1&W8Q#|{uFPWcQNinm`7dHVFTq&_sv*TW1DU8;n>}O!~$LdBG4%hOxsQ?7^6>KW+BmS_oa2AdNsacz``G*8*uki zzs;ZzYxM%JlXrAI`=KCBihmVO?qdhiD2T2;SqU!3kXi`h;4Dlea17tVO?g(?^i2R02~lv$EIWechy zW|}yTbWDCZlt-Tolpj79Uvigo-#~J@T1LYYXBcj7Z0SGV<4F{@S^vaS$znL~$qa}> z^tUl^Nb>EggAdDQw#aX*ATHpOhOpKaS9vNb3Hx6*3Y6d>&1pjawhW$1Z^EJZB7p%M zrx}j#H@noUQZX|tsk@-gO$279!^-S9r97ic1ULlWhZ*jPbVf`9Q)q%g1tgq10qS-P z6GhMQa^4HgE^vI(R?Kury*hsp>Z*$zvqluSyCE}^VcE#m*MCYgc#%Fx9=;CJs(Dum z4b8m!Qeh!SWM-vRCc{T!io=g%_=RScBLQD?tJ17Lx=OMi`x@2uV`b6V&;?WDx#Ebq zn8!#AxwiR=)9WhV8$8z@DC1d@*_co~aVrZaviGnw0`vo>jkCcvEakfvu+G)NPy$j| zAvj?s?y;N2f`yHxd0CKEn8s){$Hp({Bo5eYuIb(bQ*F7L;k7}j*SCSCD?HaUh}45; zH4RXk^Jly<$h^~3HHq*3MAVftB|dU9P#KKr*K}WnsoLDh@VWo=DW*%-ue!=$sv|QhUcl7aHG)VlTC1@ikj{+F-dys+ zqSpp36lT(wdm~O~t8CG66y7>(dD$hbRtpgmoV7nh&y_IWZ!R~nc6?&_;|(K%$d&7% zF9|@mEY@Bm4)3fzUyfeTe!s(qG_x!w-7v;*`~=oF#Ow7<8zO>U77#r#pme`vJ3uSR z@x8bnRGRFHU&{Omo42TeMdL%Ed^5wC`ni=nC()jC&J+H;f-j=oSDgMH-HO;`cZ3@8 zGw-^^O8!@vrqSJj&QHIy-vUpyXllaP{W6WGW-Wps$!tblI~la#{8Pcvm%YyI{~m}1 z#)>}PVQ=@@6|V2>{O9HMf3LRx#ZE^cEXgIEQX6q{O1@ZC#5q`F)0mc$&%q=}W)~`# z)1z-a3NNr@rp!v1)=T1@KKKpA-(r@!i3dLK0e+_SXuL(cDp)M I+WDXR3j^AS@c;k- literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-puzzles-map-320x240x24.png b/manual/plugins/images/ss-puzzles-map-320x240x24.png new file mode 100644 index 0000000000000000000000000000000000000000..209752d0818e1906ed6ad78694c953ad9713ccf1 GIT binary patch literal 3100 zcmcImX;>528a|T&B9IU*5|%&^g(^Y>%i0i#wc!FTP&Nso5KIMy0J*q@vJJMzDy>LW z+`v>81wj^(MT=o-R31dYDr!)Wpi*R$5h@7+;SM~-fA`P5Pv^&+_dD--zq8CY-}#Pw z~Ww`mEFqJF|qkDAIK^)={H2o?Z8|I1!)awH^adTypJ79K*38xB&3YM0|`24 zCKcb=0S(2zSjY%%fw4z@fTXB_L<2{G7WTZn8b|p!x=kS4z-?O(G~$!R5=3{5sl?ox zzoPINpAxY}UzAZ?V?X4HAwijeVo7AZhN29rHP))EibtBR_pU~&qvU)e^qvWUzDKMJ z(NoE=oa=}3Zwnb{LCKs)U6F`<8fGy~a7`O>xJBrR7UazldLmPoZA-DYJ8}6(m`)@= z(`1>ZM@4qD5o(>MiP_VRX{M}osk^Zu^PQwsstJbn`w&c3&V1|4ds;j` z(5l=qe>^t-m$82hdhcWf^|#`_IP0PZ)Z~U_<`Q1#aCCCB%5q2gwePo|FrlE$W=y#Q zc$z!tQKAjycK#6|VJcCJ~IT8vN@ySTp15L#8PDH0j$Z=o5)m$)ul#Tpjj=@77G+QE@4^gW5)LjZN#@ zQ{B=v>)cmZE7U=c$ZRAuB-JPGB3sebe5##{N(*ZrnhHP42jG+?^MnKM&{=~hk-@yd z;ALlc-cvb(sb8M2_d}VF2RDtG&NCLg68|D`(xQ0>lCUPM6HE={h``}b@$ zWu75c{`teX!0m9QT?xYxvUR&K%!ARF_*y_^B0GIHZeZ<=>I93vm}{9PnR%_!U5Y24 z>#7%}Og24IKl6MdtS>T8Z%CceiRc(}PWCrs?af2C0~JRMHM~7{%%*VqPSs5h#EA@x z?(XbnJAZ2jJhCIf;fSQf3nwY+p+PNft3ATEE5|IBqAR~3DbS(J6bqesSZuXdYNV&o zpNp`HknE4^O%T6tjnIj3hUJG$VGkuVWDhzZhEj_AYd9VmlBC&3AK^A9lA@s7?zH;% zbaiW~o2H_)_ekIvk>~ZyKtKJ}kKgr+UqQ4f(YP_`uq4cuH`V%l+td7O#IY;aCHp35 zThbPfB)G}E6lJUF-hW;upU0OyARu26Eo+u2M;%FuqH3q&R~c_+Z>OG*1a^5>zcQd$ znqzYOhu9b$`^{ZA8?b7Pw6I5=CPDAl1%a#&_%ZCQ)e2@Uwm|t+3lfel#@+o%0<&_A z7bHueET>NrfApCFG(GJMDoS5vyd}{85Zhhs2@)M?t1n(69M0e46@2L^F>yIsC9c-H zLy)Vks2a*!q?=3NY&z3a_-iCMF!+#Or1xeoQb2<1H+#9P+P5rP!*quc>Yh04JGjnQ zNA%Jjp}l!=t|KGY5G#xXA?>&9O5iV@6vi^Jru)IALcKS`NVgc;R@ZFiW3~vvlghU1 zlOx`L!>Z%A7GQbt&vVe6{31H+rZOaxAP-Tj!R6?9jBR55PmRTQrH@Z~9NWoOU;=TL|^|HsYIDbQQJ04 z@Jx#(5U=c91-7^ev%xkxrfS2cqpuDintoxH-~eES)AT0u{nzkX3#i)YW{}lR@|tlu z&q1&C+kS>j0eYi}-ZVo$6gfo+wFGqpmU>EjSl&~0hQOWtq1vn7fTDUWiInYz)y7IC z>kd`@xcL3kCThLrl<&^ygkwP@O+Jz42I9!;}D@{er#F>QnsCs?H@h9!Ns^Z?8h?{Xd>>4u=2-o%u_?u#W zy$K@MAPSl>w4wW{2I`ft|E4Ta%r{K^m^W>gd26eniujqRrnwNx`Y2ZR3=bX67z!7p zTb$_N-*PO;y8_eik`ttz$`(%sS#h{<|5kt%3q<{`Y2h~C?gMPgn#U%HSE%eo{6y)Y zKZyYQI>3zqi#i+|`lHfs-G4&@Y2GoHPl2q1VwWyK%V!RJpt2~*qr$8!#^I^eS!wP) zbsEU%iRZiB(Snn`MJ_LtHS3yI1F2ukWh*F)DSp;He70hR>5RpES1^>dTx{4qJQ?x5 z>38gTT_jp?u-CxFZE)Oh>O7Pp>F|w(>0>?E;uYOdq=`mD;6P%--f`wmNIgCiH?E6TLEPoQ@aBhr>VFQHflZue zcu9Kl;VRBvN1$(@hjI?V_Wr$KQ;_TbVouSpQub@F!FIJpe!p6CS`a)!8C{;=Uxr9# zS<<1#*SV{zUR-`CdDSp0q0FZF8Ok`;XTj_kEDhSsahrOxV#Fq&BJqUJta8^^gh7U*%NA(vHta-ib7MeyKDDTwitf`qIqirbP)BMn8;uZFp zWl8a;6hmLWVG0XpVB&Icuy%M^wTQ8>B>2~l%FE`#f|`GS)ZX59DPC@SPT#MM?Xm$s znFTxq8XTB7xyt8p|I4k{TcBDMVC^a4{y#%+!T$QZ+u!&aj1Iq8)Uau%e5e^I_+gE|A_`OJo0dk<8^eA-~TgxLXlze(#6*%2s3PrJjVD_ zD_$_b{HTIL`OWli3OC~2j%})SSt(h4gYlhD-;YaKMc*~uZ7=WZK3lr&UpQ;Yi`Fz( zhc$Jz|J_7gzKUE+J{ekBaQ0F|?x%vq5}K2z&&!HXyIhxIx#)OJ#S%t02a(0i43cx* zvbMT=CbMiYY??VGvxj+?^cxvn!_9mvtlE!DYJOC8*wBALI4xO9*ujm@;px90Tt;2J zo;sFu0~vmquqe!F*fmWxa_MpwmCA%%vAJv>Nhv*?^MiaCLs%=#7U(T5W!Ng{$8g1L zu`5Gqdo{zsY8H-+f5n6mM%m9CtWK#lPkrW3W3*V?H0wwZgbm?@pRn&DKn z+glB51vBLj90spexE-l)?kaRh*Kjg@^`@E-^Ufq-5=o-E?@b8MQ!#|fQskrGV zuq$UKU7kO;m+6&J5yPxQ88Q>rM?chd^}fs4k?VH%rEh(i8GFF?1&<{*emC{9Z`%7z zz(Khu!O`C7QKNUYXm4cX_j1 zc^5zV?qd<{|8{owi Plm|Us{an^LB{Ts5vh;{K literal 0 HcmV?d00001 diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index 2cd49035ff..3c25807424 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -72,6 +72,8 @@ text files% \opt{lcd_bitmap}{\input{plugins/pong.tex}} +\opt{lcd_bitmap}{\input{plugins/puzzles.tex}} + \opt{lcd_bitmap}{\input{plugins/reversi.tex}} \opt{lcd_bitmap}{\input{plugins/robotfindskitten.tex}} diff --git a/manual/plugins/puzzles.tex b/manual/plugins/puzzles.tex new file mode 100644 index 0000000000..5ce2c5ab63 --- /dev/null +++ b/manual/plugins/puzzles.tex @@ -0,0 +1,7 @@ +\subsection{Puzzles} +\screenshot{plugins/images/ss-puzzles-cube}{``Cube'' from Puzzles}{fig:Cube} +\screenshot{plugins/images/ss-puzzles-map}{``Map'' from Puzzles}{fig:Map} + +``Puzzles'' is a port of Simon Tatham's Portable Puzzle +Collection. For documentation on the games included, please visit +(\url{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}).