mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-14 02:27:39 -04:00
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:
parent
3dd69ce23e
commit
eca00638ae
58 changed files with 9698 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2,3 +2,4 @@ rockbox.c
|
||||||
rbwrappers.c
|
rbwrappers.c
|
||||||
rbmalloc.c
|
rbmalloc.c
|
||||||
lz4tiny.c
|
lz4tiny.c
|
||||||
|
dummy/nullhelp.c
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
8
apps/plugins/puzzles/dummy/nullhelp.c
Normal file
8
apps/plugins/puzzles/dummy/nullhelp.c
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
apps/plugins/puzzles/src/unfinished/CMakeLists.txt
Normal file
31
apps/plugins/puzzles/src/unfinished/CMakeLists.txt
Normal 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()
|
14
apps/plugins/puzzles/src/unfinished/README
Normal file
14
apps/plugins/puzzles/src/unfinished/README
Normal 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.
|
2497
apps/plugins/puzzles/src/unfinished/group.c
Normal file
2497
apps/plugins/puzzles/src/unfinished/group.c
Normal file
File diff suppressed because it is too large
Load diff
97
apps/plugins/puzzles/src/unfinished/group.gap
Normal file
97
apps/plugins/puzzles/src/unfinished/group.gap
Normal 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;
|
1294
apps/plugins/puzzles/src/unfinished/numgame.c
Normal file
1294
apps/plugins/puzzles/src/unfinished/numgame.c
Normal file
File diff suppressed because it is too large
Load diff
866
apps/plugins/puzzles/src/unfinished/path.c
Normal file
866
apps/plugins/puzzles/src/unfinished/path.c
Normal 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
|
861
apps/plugins/puzzles/src/unfinished/separate.c
Normal file
861
apps/plugins/puzzles/src/unfinished/separate.c
Normal 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 */
|
||||||
|
};
|
2444
apps/plugins/puzzles/src/unfinished/slide.c
Normal file
2444
apps/plugins/puzzles/src/unfinished/slide.c
Normal file
File diff suppressed because it is too large
Load diff
1476
apps/plugins/puzzles/src/unfinished/sokoban.c
Normal file
1476
apps/plugins/puzzles/src/unfinished/sokoban.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue