puzzles: add Slide and Sokoban.

This enables two of the "unfinished" puzzles. Slide requires a new "sticky
mouse mode" to enable dragging. The help system is disabled for these
puzzles, since they lack manual chapters.

Group is currently unplayable due to lack of request_keys() support, which
will need to be added upstream. Separate fails to draw anything.

Change-Id: I7bcff3679ac5b10b0f39c5eaa19a36b4b1fe8d53
This commit is contained in:
Franklin Wei 2024-08-18 21:14:07 -04:00
parent 3dd69ce23e
commit eca00638ae
58 changed files with 9698 additions and 26 deletions

View file

@ -3,6 +3,7 @@ rockbox.c
rbwrappers.c rbwrappers.c
rbmalloc.c rbmalloc.c
lz4tiny.c lz4tiny.c
dummy/nullhelp.c
/* puzzles core sources */ /* puzzles core sources */
src/combi.c src/combi.c

View file

@ -35,13 +35,8 @@ src/undead.c
src/unequal.c src/unequal.c
src/unruly.c src/unruly.c
src/untangle.c src/untangle.c
src/unfinished/slide.c
/* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */ src/unfinished/sokoban.c
/* The help system would also need to be patched to compile these. */
/*src/unfinished/group.c*/
/*src/unfinished/separate.c*/
/*src/unfinished/slide.c*/
/*src/unfinished/sokoban.c*/
/* no c200v2 */ /* no c200v2 */
#if PLUGIN_BUFFER_SIZE > 0x14000 #if PLUGIN_BUFFER_SIZE > 0x14000

View file

@ -2,3 +2,4 @@ rockbox.c
rbwrappers.c rbwrappers.c
rbmalloc.c rbmalloc.c
lz4tiny.c lz4tiny.c
dummy/nullhelp.c

View file

@ -158,6 +158,7 @@ int main()
printf("};\n\n"); printf("};\n\n");
printf("const unsigned short help_text_len = %d;\n", help_text_len); printf("const unsigned short help_text_len = %d;\n", help_text_len);
printf("const unsigned short help_text_words = %d;\n", word_idx); printf("const unsigned short help_text_words = %d;\n", word_idx);
printf("const bool help_valid = true;\n");
return 0; return 0;
} }

View file

@ -0,0 +1,8 @@
#include "help.h"
const char help_text[] __attribute__((weak)) = "";
const char quick_help_text[] __attribute__((weak)) = "";
const unsigned short help_text_len __attribute__((weak)) = 0, quick_help_text_len __attribute__((weak)) = 0, help_text_words __attribute__((weak)) = 0;
struct style_text help_text_style[] __attribute__((weak)) = {};
const bool help_valid __attribute__((weak)) = false;

View file

@ -1,3 +1,5 @@
#include <stdbool.h>
#ifdef ROCKBOX #ifdef ROCKBOX
#include "lib/display_text.h" #include "lib/display_text.h"
#endif #endif
@ -12,3 +14,5 @@ extern const unsigned short help_text_len, quick_help_text_len, help_text_words;
#if defined(ROCKBOX) #if defined(ROCKBOX)
extern struct style_text help_text_style[]; extern struct style_text help_text_style[];
#endif #endif
extern const bool help_valid;

View file

@ -340,4 +340,5 @@ const char help_text[] = {
const unsigned short help_text_len = 5480; const unsigned short help_text_len = 5480;
const unsigned short help_text_words = 1016; const unsigned short help_text_words = 1016;
const bool help_valid = true;
const char quick_help_text[] = "Find the hidden balls in the box by bouncing laser beams off them."; const char quick_help_text[] = "Find the hidden balls in the box by bouncing laser beams off them.";

View file

@ -358,4 +358,5 @@ const char help_text[] = {
const unsigned short help_text_len = 5613; const unsigned short help_text_len = 5613;
const unsigned short help_text_words = 1026; const unsigned short help_text_words = 1026;
const bool help_valid = true;
const char quick_help_text[] = "Connect all the islands with a network of bridges."; const char quick_help_text[] = "Connect all the islands with a network of bridges.";

View file

@ -166,4 +166,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2071; const unsigned short help_text_len = 2071;
const unsigned short help_text_words = 386; const unsigned short help_text_words = 386;
const bool help_valid = true;
const char quick_help_text[] = "Pick up all the blue squares by rolling the cube over them."; const char quick_help_text[] = "Pick up all the blue squares by rolling the cube over them.";

View file

@ -176,4 +176,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2299; const unsigned short help_text_len = 2299;
const unsigned short help_text_words = 401; const unsigned short help_text_words = 401;
const bool help_valid = true;
const char quick_help_text[] = "Tile the rectangle with a full set of dominoes."; const char quick_help_text[] = "Tile the rectangle with a full set of dominoes.";

View file

@ -152,4 +152,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1927; const unsigned short help_text_len = 1927;
const unsigned short help_text_words = 353; const unsigned short help_text_words = 353;
const bool help_valid = true;
const char quick_help_text[] = "Slide the tiles around to arrange them into order."; const char quick_help_text[] = "Slide the tiles around to arrange them into order.";

View file

@ -142,4 +142,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1821; const unsigned short help_text_len = 1821;
const unsigned short help_text_words = 328; const unsigned short help_text_words = 328;
const bool help_valid = true;
const char quick_help_text[] = "Mark every square with the area of its containing region."; const char quick_help_text[] = "Mark every square with the area of its containing region.";

View file

@ -131,4 +131,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1539; const unsigned short help_text_len = 1539;
const unsigned short help_text_words = 299; const unsigned short help_text_words = 299;
const bool help_valid = true;
const char quick_help_text[] = "Flip groups of squares to light them all up at once."; const char quick_help_text[] = "Flip groups of squares to light them all up at once.";

View file

@ -182,4 +182,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2395; const unsigned short help_text_len = 2395;
const unsigned short help_text_words = 452; const unsigned short help_text_words = 452;
const bool help_valid = true;
const char quick_help_text[] = "Turn the grid the same colour in as few flood fills as possible."; const char quick_help_text[] = "Turn the grid the same colour in as few flood fills as possible.";

View file

@ -208,4 +208,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2766; const unsigned short help_text_len = 2766;
const unsigned short help_text_words = 498; const unsigned short help_text_words = 498;
const bool help_valid = true;
const char quick_help_text[] = "Divide the grid into rotationally symmetric regions each centred on a dot."; const char quick_help_text[] = "Divide the grid into rotationally symmetric regions each centred on a dot.";

View file

@ -238,4 +238,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3506; const unsigned short help_text_len = 3506;
const unsigned short help_text_words = 650; const unsigned short help_text_words = 650;
const bool help_valid = true;
const char quick_help_text[] = "Guess the hidden combination of colours."; const char quick_help_text[] = "Guess the hidden combination of colours.";

View file

@ -179,4 +179,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2286; const unsigned short help_text_len = 2286;
const unsigned short help_text_words = 431; const unsigned short help_text_words = 431;
const bool help_valid = true;
const char quick_help_text[] = "Collect all the gems without running into any of the mines."; const char quick_help_text[] = "Collect all the gems without running into any of the mines.";

View file

@ -260,4 +260,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3969; const unsigned short help_text_len = 3969;
const unsigned short help_text_words = 762; const unsigned short help_text_words = 762;
const bool help_valid = true;
const char quick_help_text[] = "Complete the latin square in accordance with the arithmetic clues."; const char quick_help_text[] = "Complete the latin square in accordance with the arithmetic clues.";

View file

@ -191,4 +191,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2549; const unsigned short help_text_len = 2549;
const unsigned short help_text_words = 468; const unsigned short help_text_words = 468;
const bool help_valid = true;
const char quick_help_text[] = "Place bulbs to light up all the squares."; const char quick_help_text[] = "Place bulbs to light up all the squares.";

View file

@ -264,4 +264,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3584; const unsigned short help_text_len = 3584;
const unsigned short help_text_words = 660; const unsigned short help_text_words = 660;
const bool help_valid = true;
const char quick_help_text[] = "Draw a single closed loop, given clues about number of adjacent edges."; const char quick_help_text[] = "Draw a single closed loop, given clues about number of adjacent edges.";

View file

@ -190,4 +190,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2522; const unsigned short help_text_len = 2522;
const unsigned short help_text_words = 439; const unsigned short help_text_words = 439;
const bool help_valid = true;
const char quick_help_text[] = "Place magnets to satisfy the clues and avoid like poles touching."; const char quick_help_text[] = "Place magnets to satisfy the clues and avoid like poles touching.";

View file

@ -271,4 +271,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3752; const unsigned short help_text_len = 3752;
const unsigned short help_text_words = 686; const unsigned short help_text_words = 686;
const bool help_valid = true;
const char quick_help_text[] = "Colour the map so that adjacent regions are never the same colour."; const char quick_help_text[] = "Colour the map so that adjacent regions are never the same colour.";

View file

@ -260,4 +260,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3814; const unsigned short help_text_len = 3814;
const unsigned short help_text_words = 732; const unsigned short help_text_words = 732;
const bool help_valid = true;
const char quick_help_text[] = "Find all the mines without treading on any of them."; const char quick_help_text[] = "Find all the mines without treading on any of them.";

View file

@ -147,4 +147,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1673; const unsigned short help_text_len = 1673;
const unsigned short help_text_words = 285; const unsigned short help_text_words = 285;
const bool help_valid = true;
const char quick_help_text[] = "Fill in the grid given clues about number of nearby black squares."; const char quick_help_text[] = "Fill in the grid given clues about number of nearby black squares.";

View file

@ -297,4 +297,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3919; const unsigned short help_text_len = 3919;
const unsigned short help_text_words = 689; const unsigned short help_text_words = 689;
const bool help_valid = true;
const char quick_help_text[] = "Rotate each tile to reassemble the network."; const char quick_help_text[] = "Rotate each tile to reassemble the network.";

View file

@ -58,4 +58,5 @@ const char help_text[] = {
const unsigned short help_text_len = 546; const unsigned short help_text_len = 546;
const unsigned short help_text_words = 99; const unsigned short help_text_words = 99;
const bool help_valid = true;
const char quick_help_text[] = "Slide a row at a time to reassemble the network."; const char quick_help_text[] = "Slide a row at a time to reassemble the network.";

View file

@ -140,4 +140,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1672; const unsigned short help_text_len = 1672;
const unsigned short help_text_words = 285; const unsigned short help_text_words = 285;
const bool help_valid = true;
const char quick_help_text[] = "Divide the grid into equal-sized areas in accordance with the clues."; const char quick_help_text[] = "Divide the grid into equal-sized areas in accordance with the clues.";

View file

@ -168,4 +168,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2167; const unsigned short help_text_len = 2167;
const unsigned short help_text_words = 389; const unsigned short help_text_words = 389;
const bool help_valid = true;
const char quick_help_text[] = "Fill in the pattern in the grid, given only the lengths of runs of black squares."; const char quick_help_text[] = "Fill in the pattern in the grid, given only the lengths of runs of black squares.";

View file

@ -249,4 +249,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3598; const unsigned short help_text_len = 3598;
const unsigned short help_text_words = 659; const unsigned short help_text_words = 659;
const bool help_valid = true;
const char quick_help_text[] = "Draw a single closed loop, given clues about corner and straight squares."; const char quick_help_text[] = "Draw a single closed loop, given clues about corner and straight squares.";

View file

@ -148,4 +148,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1734; const unsigned short help_text_len = 1734;
const unsigned short help_text_words = 326; const unsigned short help_text_words = 326;
const bool help_valid = true;
const char quick_help_text[] = "Jump pegs over each other to remove all but one."; const char quick_help_text[] = "Jump pegs over each other to remove all but one.";

View file

@ -170,4 +170,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2223; const unsigned short help_text_len = 2223;
const unsigned short help_text_words = 395; const unsigned short help_text_words = 395;
const bool help_valid = true;
const char quick_help_text[] = "Place black squares to limit the visible distance from each numbered cell."; const char quick_help_text[] = "Place black squares to limit the visible distance from each numbered cell.";

View file

@ -258,4 +258,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3536; const unsigned short help_text_len = 3536;
const unsigned short help_text_words = 603; const unsigned short help_text_words = 603;
const bool help_valid = true;
const char quick_help_text[] = "Divide the grid into rectangles with areas equal to the numbers."; const char quick_help_text[] = "Divide the grid into rectangles with areas equal to the numbers.";

View file

@ -188,4 +188,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2492; const unsigned short help_text_len = 2492;
const unsigned short help_text_words = 445; const unsigned short help_text_words = 445;
const bool help_valid = true;
const char quick_help_text[] = "Clear the grid by removing touching groups of the same colour squares."; const char quick_help_text[] = "Clear the grid by removing touching groups of the same colour squares.";

View file

@ -222,4 +222,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3255; const unsigned short help_text_len = 3255;
const unsigned short help_text_words = 595; const unsigned short help_text_words = 595;
const bool help_valid = true;
const char quick_help_text[] = "Connect the squares into a path following the arrows."; const char quick_help_text[] = "Connect the squares into a path following the arrows.";

View file

@ -149,4 +149,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1780; const unsigned short help_text_len = 1780;
const unsigned short help_text_words = 309; const unsigned short help_text_words = 309;
const bool help_valid = true;
const char quick_help_text[] = "Black out the right set of duplicate numbers."; const char quick_help_text[] = "Black out the right set of duplicate numbers.";

View file

@ -197,4 +197,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2553; const unsigned short help_text_len = 2553;
const unsigned short help_text_words = 454; const unsigned short help_text_words = 454;
const bool help_valid = true;
const char quick_help_text[] = "Slide a row at a time to arrange the tiles into order."; const char quick_help_text[] = "Slide a row at a time to arrange the tiles into order.";

View file

@ -199,4 +199,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2582; const unsigned short help_text_len = 2582;
const unsigned short help_text_words = 474; const unsigned short help_text_words = 474;
const bool help_valid = true;
const char quick_help_text[] = "Draw a maze of slanting lines that matches the clues."; const char quick_help_text[] = "Draw a maze of slanting lines that matches the clues.";

View file

@ -383,4 +383,5 @@ const char help_text[] = {
const unsigned short help_text_len = 6259; const unsigned short help_text_len = 6259;
const unsigned short help_text_words = 1153; const unsigned short help_text_words = 1153;
const bool help_valid = true;
const char quick_help_text[] = "Fill in the grid so that each row, column and square block contains one of every digit."; const char quick_help_text[] = "Fill in the grid so that each row, column and square block contains one of every digit.";

View file

@ -163,4 +163,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2158; const unsigned short help_text_len = 2158;
const unsigned short help_text_words = 401; const unsigned short help_text_words = 401;
const bool help_valid = true;
const char quick_help_text[] = "Place a tent next to each tree."; const char quick_help_text[] = "Place a tent next to each tree.";

View file

@ -263,4 +263,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3906; const unsigned short help_text_len = 3906;
const unsigned short help_text_words = 732; const unsigned short help_text_words = 732;
const bool help_valid = true;
const char quick_help_text[] = "Complete the latin square of towers in accordance with the clues."; const char quick_help_text[] = "Complete the latin square of towers in accordance with the clues.";

View file

@ -150,4 +150,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1881; const unsigned short help_text_len = 1881;
const unsigned short help_text_words = 337; const unsigned short help_text_words = 337;
const bool help_valid = true;
const char quick_help_text[] = "Fill in the railway track according to the clues."; const char quick_help_text[] = "Fill in the railway track according to the clues.";

View file

@ -205,4 +205,5 @@ const char help_text[] = {
const unsigned short help_text_len = 2945; const unsigned short help_text_len = 2945;
const unsigned short help_text_words = 549; const unsigned short help_text_words = 549;
const bool help_valid = true;
const char quick_help_text[] = "Rotate the tiles around themselves to arrange them into order."; const char quick_help_text[] = "Rotate the tiles around themselves to arrange them into order.";

View file

@ -248,4 +248,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3574; const unsigned short help_text_len = 3574;
const unsigned short help_text_words = 660; const unsigned short help_text_words = 660;
const bool help_valid = true;
const char quick_help_text[] = "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors."; const char quick_help_text[] = "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors.";

View file

@ -257,4 +257,5 @@ const char help_text[] = {
const unsigned short help_text_len = 3954; const unsigned short help_text_len = 3954;
const unsigned short help_text_words = 731; const unsigned short help_text_words = 731;
const bool help_valid = true;
const char quick_help_text[] = "Complete the latin square in accordance with the > signs."; const char quick_help_text[] = "Complete the latin square in accordance with the > signs.";

View file

@ -145,4 +145,5 @@ const char help_text[] = {
const unsigned short help_text_len = 1707; const unsigned short help_text_len = 1707;
const unsigned short help_text_words = 306; const unsigned short help_text_words = 306;
const bool help_valid = true;
const char quick_help_text[] = "Fill in the black and white grid to avoid runs of three."; const char quick_help_text[] = "Fill in the black and white grid to avoid runs of three.";

View file

@ -97,4 +97,5 @@ const char help_text[] = {
const unsigned short help_text_len = 974; const unsigned short help_text_len = 974;
const unsigned short help_text_words = 174; const unsigned short help_text_words = 174;
const bool help_valid = true;
const char quick_help_text[] = "Reposition the points so that the lines do not cross."; const char quick_help_text[] = "Reposition the points so that the lines do not cross.";

View file

@ -25,6 +25,8 @@ PUZZLES_OBJ = $(call c2obj, $(PUZZLES_SRC))
PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock))) PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock)))
OTHER_SRC += $(PUZZLES_SRC) OTHER_SRC += $(PUZZLES_SRC)
OTHER_INC += -I$(PUZZLES_SRCDIR)/src -I $(PUZZLES_SRCDIR)
ROCKS += $(PUZZLES_ROCKS) ROCKS += $(PUZZLES_ROCKS)
PUZZLES_OPTIMIZE = -O2 PUZZLES_OPTIMIZE = -O2
@ -49,6 +51,13 @@ $(PUZZLES_OBJDIR)/sgt-%.rock: $(PUZZLES_OBJDIR)/src/%.o $(PUZZLES_OBJDIR)/help/%
-lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map -lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map
$(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@) $(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@)
$(PUZZLES_OBJDIR)/sgt-%.rock: $(PUZZLES_OBJDIR)/src/unfinished/%.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB)
$(call PRINTS,LD $(@F))$(CC) $(PLUGINFLAGS) -o $(PUZZLES_OBJDIR)/$*.elf \
$(filter %.o, $^) \
$(filter %.a, $+) \
-lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map
$(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@)
$(PUZZLES_SRCDIR)/rbcompat.h: $(APPSDIR)/plugin.h \ $(PUZZLES_SRCDIR)/rbcompat.h: $(APPSDIR)/plugin.h \
$(APPSDIR)/plugins/lib/pluginlib_exit.h \ $(APPSDIR)/plugins/lib/pluginlib_exit.h \
$(BUILDDIR)/sysfont.h \ $(BUILDDIR)/sysfont.h \

View file

@ -30,8 +30,8 @@ then
echo "[1/5] Removing current src/ directory" echo "[1/5] Removing current src/ directory"
rm -rf src rm -rf src
echo "[2/5] Copying new sources" echo "[2/5] Copying new sources"
mkdir src mkdir -p src/unfinished
cp -r "$1"/{*.h,puzzles.but,LICENCE,README,CMakeLists.txt} src cp -r "$1"/{*.h,puzzles.but,LICENCE,README,CMakeLists.txt,unfinished} src
# Parse out definitions of core, core_obj, and common from # Parse out definitions of core, core_obj, and common from
# CMakeLists. Extract the .c filenames, except malloc.c, and store # CMakeLists. Extract the .c filenames, except malloc.c, and store
@ -46,17 +46,12 @@ then
SRC="$(cat SOURCES.games SOURCES.core | sed 's/src\///' | tr '\n' ' ' | head -c-1) loopy.c pearl.c solo.c" SRC="$(cat SOURCES.games SOURCES.core | sed 's/src\///' | tr '\n' ' ' | head -c-1) loopy.c pearl.c solo.c"
echo "Detected sources:" $SRC echo "Detected sources:" $SRC
pushd "$1" > /dev/null pushd "$1" > /dev/null
cp $SRC "$ROOT"/src cp -r $SRC "$ROOT"/src
popd > /dev/null popd > /dev/null
cat <<EOF >> SOURCES.games cat src/unfinished/CMakeLists.txt | awk '/puzzle\(/{p=1} p{print} /\)/{p=0}' | grep -Eo "\(.*$" | tr -dc "a-z\n" | awk '{print "src/unfinished/"$0".c"}' | grep -v "group" | grep -v "separate" >> SOURCES.games
/* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */ cat <<EOF >> SOURCES.games
/* The help system would also need to be patched to compile these. */
/*src/unfinished/group.c*/
/*src/unfinished/separate.c*/
/*src/unfinished/slide.c*/
/*src/unfinished/sokoban.c*/
/* no c200v2 */ /* no c200v2 */
#if PLUGIN_BUFFER_SIZE > 0x14000 #if PLUGIN_BUFFER_SIZE > 0x14000

View file

@ -322,6 +322,7 @@ static struct viewport clip_rect;
static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false;
static int mouse_x, mouse_y; static int mouse_x, mouse_y;
static bool mouse_dragging = false; /* for sticky mode only */
extern bool audiobuf_available; /* defined in rbmalloc.c */ extern bool audiobuf_available; /* defined in rbmalloc.c */
@ -346,6 +347,7 @@ static struct {
bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */ bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */
bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */ bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */
bool numerical_chooser; /* repurpose select to activate a numerical chooser */ bool numerical_chooser; /* repurpose select to activate a numerical chooser */
bool sticky_mouse; /* if mouse left button should be persistent and toggled on/off */
} input_settings; } input_settings;
static bool accept_input = true; static bool accept_input = true;
@ -748,6 +750,7 @@ static void rb_color(int n)
fatal("bad color %d", n); fatal("bad color %d", n);
return; return;
} }
if(colors)
rb->lcd_set_foreground(colors[n]); rb->lcd_set_foreground(colors[n]);
} }
@ -1284,7 +1287,8 @@ static void draw_title(bool clear_first)
rb->lcd_setfont(cur_font = FONT_UI); rb->lcd_setfont(cur_font = FONT_UI);
rb->lcd_getstringsize(str, &w, &h); rb->lcd_getstringsize(str, &w, &h);
rb->lcd_set_foreground(BG_COLOR);
rb->lcd_set_foreground(colors ? colors[0] : BG_COLOR);
rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h); rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h);
rb->lcd_set_drawmode(DRMODE_FG); rb->lcd_set_drawmode(DRMODE_FG);
@ -1682,9 +1686,17 @@ static int process_input(int tmo, bool do_pausemenu)
LOGF("sending left click"); LOGF("sending left click");
send_click(LEFT_BUTTON, true); /* right-click is handled earlier */ send_click(LEFT_BUTTON, true); /* right-click is handled earlier */
} }
} else if(input_settings.sticky_mouse) {
if(pressed & BTN_FIRE) {
send_click(LEFT_BUTTON, false);
accept_input = false;
mouse_dragging = !mouse_dragging;
} else if(mouse_dragging) {
send_click(LEFT_DRAG, false);
} else {
send_click(LEFT_RELEASE, false);
} }
else } else {
{
if(pressed & BTN_FIRE) { if(pressed & BTN_FIRE) {
send_click(LEFT_BUTTON, false); send_click(LEFT_BUTTON, false);
accept_input = false; accept_input = false;
@ -2482,6 +2494,7 @@ static bool presets_menu(void)
static void quick_help(void) static void quick_help(void)
{ {
#ifndef NO_HELP_TEXT
#if defined(FOR_REAL) && defined(DEBUG_MENU) #if defined(FOR_REAL) && defined(DEBUG_MENU)
if(++help_times >= 5) if(++help_times >= 5)
{ {
@ -2492,11 +2505,12 @@ static void quick_help(void)
rb->splash(0, quick_help_text); rb->splash(0, quick_help_text);
rb->button_get(true); rb->button_get(true);
return; #endif
} }
static void full_help(const char *name) static void full_help(const char *name)
{ {
#ifndef NO_HELP_TEXT
unsigned old_bg = rb->lcd_get_background(); unsigned old_bg = rb->lcd_get_background();
bool orig_clipped = clipped; bool orig_clipped = clipped;
@ -2551,6 +2565,7 @@ static void full_help(const char *name)
if(orig_clipped) if(orig_clipped)
rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
#endif
} }
static void init_default_settings(void) static void init_default_settings(void)
@ -2701,6 +2716,11 @@ static int pausemenu_cb(int action,
if(!midend_which_game(me)->can_solve) if(!midend_which_game(me)->can_solve)
return ACTION_EXIT_MENUITEM; return ACTION_EXIT_MENUITEM;
break; break;
case 7:
case 8:
if(!help_valid)
return ACTION_EXIT_MENUITEM;
break;
case 9: case 9:
if(audiobuf_available) if(audiobuf_available)
break; break;
@ -2751,7 +2771,7 @@ static void reset_drawing(void)
rb->lcd_set_viewport(NULL); rb->lcd_set_viewport(NULL);
rb->lcd_set_backdrop(NULL); rb->lcd_set_backdrop(NULL);
rb->lcd_set_foreground(LCD_BLACK); rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(BG_COLOR); rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
} }
/* Make a new game, but tell the user through a splash so they don't /* Make a new game, but tell the user through a splash so they don't
@ -2876,7 +2896,7 @@ static int pause_menu(void)
break; break;
} }
} }
rb->lcd_set_background(BG_COLOR); rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
rb->lcd_clear_display(); rb->lcd_clear_display();
midend_force_redraw(me); midend_force_redraw(me);
rb->lcd_update(); rb->lcd_update();
@ -2923,6 +2943,7 @@ static void init_colors(void)
float *floatcolors = midend_colors(me, &ncolors); float *floatcolors = midend_colors(me, &ncolors);
/* convert them to packed RGB */ /* convert them to packed RGB */
sfree(colors);
colors = smalloc(ncolors * sizeof(unsigned)); colors = smalloc(ncolors * sizeof(unsigned));
unsigned *ptr = colors; unsigned *ptr = colors;
float *floatptr = floatcolors; float *floatptr = floatcolors;
@ -3007,6 +3028,7 @@ static void tune_input(const char *name)
static const char *no_rclick_on_hold[] = { static const char *no_rclick_on_hold[] = {
"Map", "Map",
"Signpost", "Signpost",
"Slide",
"Untangle", "Untangle",
NULL NULL
}; };
@ -3015,11 +3037,21 @@ static void tune_input(const char *name)
static const char *mouse_games[] = { static const char *mouse_games[] = {
"Loopy", "Loopy",
"Slide",
NULL NULL
}; };
mouse_mode = string_in_list(name, mouse_games); mouse_mode = string_in_list(name, mouse_games);
static const char *sticky_mouse_games[] = {
"Map",
"Signpost",
"Slide",
"Untangle",
};
input_settings.sticky_mouse = string_in_list(name, sticky_mouse_games);
static const char *number_chooser_games[] = { static const char *number_chooser_games[] = {
"Filling", "Filling",
"Keen", "Keen",
@ -3312,7 +3344,10 @@ static int mainmenu_cb(int action,
if(!load_success) if(!load_success)
return ACTION_EXIT_MENUITEM; return ACTION_EXIT_MENUITEM;
break; break;
case 2:
case 3: case 3:
if(!help_valid)
return ACTION_EXIT_MENUITEM;
break; break;
case 4: case 4:
if(audiobuf_available) if(audiobuf_available)
@ -3476,12 +3511,14 @@ static void puzzles_main(void)
/* quit without saving */ /* quit without saving */
midend_free(me); midend_free(me);
sfree(colors); sfree(colors);
colors = NULL;
return; return;
case -3: case -3:
/* save and quit */ /* save and quit */
save_game(); save_game();
midend_free(me); midend_free(me);
sfree(colors); sfree(colors);
colors = NULL;
return; return;
default: default:
break; break;
@ -3511,6 +3548,7 @@ static void puzzles_main(void)
rb->yield(); rb->yield();
} }
sfree(colors); sfree(colors);
colors = NULL;
} }
} }

View file

@ -0,0 +1,31 @@
puzzle(group
DISPLAYNAME "Group"
DESCRIPTION "Group theory puzzle"
OBJECTIVE "Complete the unfinished Cayley table of a group.")
solver(group ${CMAKE_SOURCE_DIR}/latin.c)
puzzle(separate
DISPLAYNAME "Separate"
DESCRIPTION "Rectangle-dividing puzzle"
OBJECTIVE "Partition the grid into regions containing one of each letter.")
puzzle(slide
DISPLAYNAME "Slide"
DESCRIPTION "Sliding block puzzle"
OBJECTIVE "Slide the blocks to let the key block out.")
solver(slide)
puzzle(sokoban
DISPLAYNAME "Sokoban"
DESCRIPTION "Barrel-pushing puzzle"
OBJECTIVE "Push all the barrels into the target squares.")
# These unfinished programs don't even have the structure of a puzzle
# game yet; they're just command-line programs containing test
# implementations of some of the needed functionality.
cliprogram(numgame numgame.c)
cliprogram(path path.c COMPILE_DEFINITIONS TEST_GEN)
export_variables_to_parent_scope()

View file

@ -0,0 +1,14 @@
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.
The CMake build system will _build_ all of the source in this
directory (to ensure it hasn't become unbuildable), but they won't be
included in all-in-one puzzle binaries or installed by 'make install'
targets. If you want to temporarily change that, you can reconfigure
your build by defining the CMake variable PUZZLES_ENABLE_UNFINISHED.
For example,
cmake . -DPUZZLES_ENABLE_UNFINISHED="group;slide"
will build as if both Group and Slide were fully official puzzles.

File diff suppressed because it is too large Load diff

View file

@ -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;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,866 @@
/*
* Experimental grid generator for Nikoli's `Number Link' puzzle.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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).
*/
/*
* 2020-05-11: some thoughts on a solver.
*
* Consider this example puzzle, from Wikipedia:
*
* ---4---
* -3--25-
* ---31--
* ---5---
* -------
* --1----
* 2---4--
*
* The kind of deduction that a human wants to make here is: which way
* does the path between the 4s go? In particular, does it go round
* the left of the W-shaped cluster of endpoints, or round the right
* of it? It's clear at a glance that it must go to the right, because
* _any_ path between the 4s that goes to the left of that cluster, no
* matter what detailed direction it takes, will disconnect the
* remaining grid squares into two components, with the two 2s not in
* the same component. So we immediately know that the path between
* the 4s _must_ go round the right-hand side of the grid.
*
* How do you model that global and topological reasoning in a
* computer?
*
* The most plausible idea I've seen so far is to use fundamental
* groups. The fundamental group of loops based at a given point in a
* space is a free group, under loop concatenation and up to homotopy,
* generated by the loops that go in each direction around each hole
* in the space. In this case, the 'holes' are clues, or connected
* groups of clues.
*
* So you might be able to enumerate all the homotopy classes of paths
* between (say) the two 4s as follows. Start with any old path
* between them (say, find the first one that breadth-first search
* will give you). Choose one of the 4s to regard as the base point
* (arbitrarily). Then breadth-first search among the space of _paths_
* by the following procedure. Given a candidate path, append to it
* each of the possible loops that starts from the base point,
* circumnavigates one clue cluster, and returns to the base point.
* The result will typically be a path that retraces its steps and
* self-intersects. Now adjust it homotopically so that it doesn't. If
* that can't be done, then we haven't generated a fresh candidate
* path; if it can, then we've got a new path that is not homotopic to
* any path we already had, so add it to our list and queue it up to
* become the starting point of this search later.
*
* The idea is that this should exhaustively enumerate, up to
* homotopy, the different ways in which the two 4s can connect to
* each other within the constraint that you have to actually fit the
* path non-self-intersectingly into this grid. Then you can keep a
* list of those homotopy classes in mind, and start ruling them out
* by techniques like the connectivity approach described above.
* Hopefully you end up narrowing down to few enough homotopy classes
* that you can deduce something concrete about actual squares of the
* grid - for example, here, that if the path between 4s has to go
* round the right, then we know some specific squares it must go
* through, so we can fill those in. And then, having filled in a
* piece of the middle of a path, you can now regard connecting the
* ultimate endpoints to that mid-section as two separate subproblems,
* so you've reduced to a simpler instance of the same puzzle.
*
* But I don't know whether all of this actually works. I more or less
* believe the process for enumerating elements of the free group; but
* I'm not as confident that when you find a group element that won't
* fit in the grid, you'll never have to consider its descendants in
* the BFS either. And I'm assuming that 'unwind the self-intersection
* homotopically' is a thing that can actually be turned into a
* sensible algorithm.
*
* --------
*
* Another thing that might be needed is to characterise _which_
* homotopy class a given path is in.
*
* For this I think it's sufficient to choose a collection of paths
* along the _edges_ of the square grid, each of which connects two of
* the holes in the grid (including the grid exterior, which counts as
* a huge hole), such that they form a spanning tree between the
* holes. Then assign each of those paths an orientation, so that
* crossing it in one direction counts as 'positive' and the other
* 'negative'. Now analyse a candidate path from one square to another
* by following it and noting down which of those paths it crosses in
* which direction, then simplifying the result like a free group word
* (i.e. adjacent + and - crossings of the same path cancel out).
*
* --------
*
* If we choose those paths to be of minimal length, then we can get
* an upper bound on the number of homotopy classes by observing that
* you can't traverse any of those barriers more times than will fit
* non-self-intersectingly in the grid. That might be an alternative
* method of bounding the search through the fundamental group to only
* finitely many possibilities.
*/
/*
* 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_new("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

View file

@ -0,0 +1,861 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#ifdef NO_TGMATH_H
# include <math.h>
#else
# include <tgmath.h>
#endif
#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 bool 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, bool 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 const char *validate_params(const game_params *params, bool full)
{
return NULL;
}
/* ----------------------------------------------------------------------
* Solver and generator.
*/
struct solver_scratch {
int w, h, k;
/*
* Tracks connectedness between squares.
*/
DSF *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.
*/
bool *disconnect;
/*
* Temporary space used only inside particular solver loops.
*/
int *tmp;
};
static 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 = dsf_new(wh);
sc->size = snewn(wh, int);
sc->contents = snewn(wh * k, int);
sc->disconnect = snewn(wh*wh, bool);
sc->tmp = snewn(wh, int);
return sc;
}
static void solver_scratch_free(struct solver_scratch *sc)
{
dsf_free(sc->dsf);
sfree(sc->size);
sfree(sc->contents);
sfree(sc->disconnect);
sfree(sc->tmp);
sfree(sc);
}
static 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]);
}
static 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] = true;
sc->disconnect[yx2*wh+yx1] = true;
}
static 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_reinit(sc->dsf);
for (i = 0; i < wh; i++) sc->size[i] = 1;
memset(sc->disconnect, 0, wh*wh * sizeof(bool));
}
static int solver_attempt(struct solver_scratch *sc, const unsigned char *grid,
bool *gen_lock)
{
int w = sc->w, h = sc->h, k = sc->k;
int wh = w*h;
int i, x, y;
bool 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) {
bool 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]] = true;
gen_lock[sc->contents[yx2*k+i]] = true;
}
}
}
}
/*
* 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;
}
static 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;
bool *gen_lock;
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, bool);
do {
DSF *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 * sizeof(bool));
/*
* 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 */
}
dsf_free(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, bool 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 const 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, const char **error)
{
return NULL;
}
static bool 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 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,
const game_ui *ui, 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)
{
}
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 void game_get_cursor_location(const game_ui *ui,
const game_drawstate *ds,
const game_state *state,
const game_params *params,
int *x, int *y, int *w, int *h)
{
}
static int game_status(const game_state *state)
{
return 0;
}
static bool game_timing_state(const game_state *state, game_ui *ui)
{
return true;
}
static void game_print_size(const game_params *params, const game_ui *ui,
float *x, float *y)
{
}
static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
int tilesize)
{
}
#ifdef COMBINED
#define thegame separate
#endif
const struct game thegame = {
"Separate", NULL, NULL,
default_params,
game_fetch_preset, NULL,
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,
NULL, NULL, /* get_prefs, set_prefs */
new_ui,
free_ui,
NULL, /* encode_ui */
NULL, /* decode_ui */
NULL, /* game_request_keys */
game_changed_state,
NULL, /* current_key_label */
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_get_cursor_location,
game_status,
false, false, game_print_size, game_print,
false, /* wants_statusbar */
false, game_timing_state,
0, /* flags */
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff