puzzles: resync with upstream
This brings the upstream version to 9aa7b7c (with some of my changes as well). Change-Id: I5bf8a3e0b8672d82cb1bf34afc07adbe12a3ac53
This commit is contained in:
parent
dd3a8e0898
commit
48b0ef1cf2
60 changed files with 1890 additions and 333 deletions
|
@ -59,3 +59,7 @@ April 2018: Finished up the rest of the games. All work now! Surely
|
|||
there's still bugs to fix, so stay tuned...
|
||||
|
||||
December 2018: Resync to 3ece3d6. Happy holidays!
|
||||
|
||||
May 2019: Resync to e2135d5.
|
||||
|
||||
June 2020: Resync to 9aa7b7c.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* auto-generated on Dec 21 2018 by genhelp.sh */
|
||||
/* auto-generated on Jun 25 2020 by genhelp.sh */
|
||||
/* help text is compressed using LZ4; see compress.c for details */
|
||||
/* DO NOT EDIT! */
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ This software is copyright (c) 2004-2014 Simon Tatham.
|
|||
|
||||
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
|
||||
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael
|
||||
Quevillon.
|
||||
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
|
||||
Quevillon and Asher Gordon.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
blackbox:blackbox.exe:Black Box:Ball-finding puzzle:Find the hidden balls in the box by bouncing laser beams off them.
|
||||
bridges:bridges.exe:Bridges:Bridge-placing puzzle:Connect all the islands with a network of bridges.
|
||||
cube:cube.exe:Cube:Rolling cube puzzle:Pick up all the blue squares by rolling the cube over them.
|
||||
dominosa:dominosa.exe:Dominosa:Domino tiling puzzle:Tile the rectangle with a full set of dominoes.
|
||||
fifteen:fifteen.exe:Fifteen:Sliding block puzzle:Slide the tiles around to arrange them into order.
|
||||
filling:filling.exe:Filling:Polyomino puzzle:Mark every square with the area of its containing region.
|
||||
flip:flip.exe:Flip:Tile inversion puzzle:Flip groups of squares to light them all up at once.
|
||||
flood:flood.exe:Flood:Flood-filling puzzle:Turn the grid the same colour in as few flood fills as possible.
|
||||
galaxies:galaxies.exe:Galaxies:Symmetric polyomino puzzle:Divide the grid into rotationally symmetric regions each centred on a dot.
|
||||
guess:guess.exe:Guess:Combination-guessing puzzle:Guess the hidden combination of colours.
|
||||
inertia:inertia.exe:Inertia:Gem-collecting puzzle:Collect all the gems without running into any of the mines.
|
||||
keen:keen.exe:Keen:Arithmetic Latin square puzzle:Complete the latin square in accordance with the arithmetic clues.
|
||||
lightup:lightup.exe:Light Up:Light-bulb placing puzzle:Place bulbs to light up all the squares.
|
||||
loopy:loopy.exe:Loopy:Loop-drawing puzzle:Draw a single closed loop, given clues about number of adjacent edges.
|
||||
magnets:magnets.exe:Magnets:Magnet-placing puzzle:Place magnets to satisfy the clues and avoid like poles touching.
|
||||
map:map.exe:Map:Map-colouring puzzle:Colour the map so that adjacent regions are never the same colour.
|
||||
mines:mines.exe:Mines:Mine-finding puzzle:Find all the mines without treading on any of them.
|
||||
net:netgame.exe:Net:Network jigsaw puzzle:Rotate each tile to reassemble the network.
|
||||
netslide:netslide.exe:Netslide:Toroidal sliding network puzzle:Slide a row at a time to reassemble the network.
|
||||
palisade:palisade.exe:Palisade:Grid-division puzzle:Divide the grid into equal-sized areas in accordance with the clues.
|
||||
pattern:pattern.exe:Pattern:Pattern puzzle:Fill in the pattern in the grid, given only the lengths of runs of black squares.
|
||||
pearl:pearl.exe:Pearl:Loop-drawing puzzle:Draw a single closed loop, given clues about corner and straight squares.
|
||||
pegs:pegs.exe:Pegs:Peg solitaire puzzle:Jump pegs over each other to remove all but one.
|
||||
range:range.exe:Range:Visible-distance puzzle:Place black squares to limit the visible distance from each numbered cell.
|
||||
rect:rect.exe:Rectangles:Rectangles puzzle:Divide the grid into rectangles with areas equal to the numbers.
|
||||
samegame:samegame.exe:Same Game:Block-clearing puzzle:Clear the grid by removing touching groups of the same colour squares.
|
||||
signpost:signpost.exe:Signpost:Square-connecting puzzle:Connect the squares into a path following the arrows.
|
||||
singles:singles.exe:Singles:Number-removing puzzle:Black out the right set of duplicate numbers.
|
||||
sixteen:sixteen.exe:Sixteen:Toroidal sliding block puzzle:Slide a row at a time to arrange the tiles into order.
|
||||
slant:slant.exe:Slant:Maze-drawing puzzle:Draw a maze of slanting lines that matches the clues.
|
||||
solo:solo.exe:Solo:Number placement puzzle:Fill in the grid so that each row, column and square block contains one of every digit.
|
||||
tents:tents.exe:Tents:Tent-placing puzzle:Place a tent next to each tree.
|
||||
towers:towers.exe:Towers:Tower-placing Latin square puzzle:Complete the latin square of towers in accordance with the clues.
|
||||
tracks:tracks.exe:Tracks:Path-finding railway track puzzle:Fill in the railway track according to the clues.
|
||||
twiddle:twiddle.exe:Twiddle:Rotational sliding block puzzle:Rotate the tiles around themselves to arrange them into order.
|
||||
undead:undead.exe:Undead:Monster-placing puzzle:Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors.
|
||||
unequal:unequal.exe:Unequal:Latin square puzzle:Complete the latin square in accordance with the > signs.
|
||||
unruly:unruly.exe:Unruly:Black and white grid puzzle:Fill in the black and white grid to avoid runs of three.
|
||||
untangle:untangle.exe:Untangle:Planar graph layout puzzle:Reposition the points so that the lines do not cross.
|
|
@ -2378,6 +2378,8 @@ static void grid_size_floret(int width, int height,
|
|||
*tilesize = FLORET_TILESIZE;
|
||||
*xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
|
||||
*yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry;
|
||||
if (height == 1)
|
||||
*yextent += (5*qy-4*py)/2;
|
||||
}
|
||||
|
||||
static grid *grid_new_floret(int width, int height, const char *desc)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -591,6 +591,92 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
|
|||
#define SOLVER(upper,title,func,lower) func,
|
||||
static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) };
|
||||
|
||||
static int transpose(int index, int w)
|
||||
{
|
||||
return (index % w) * w + (index / w);
|
||||
}
|
||||
|
||||
static bool keen_valid(struct latin_solver *solver, void *vctx)
|
||||
{
|
||||
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
|
||||
int w = ctx->w;
|
||||
int box, i;
|
||||
|
||||
/*
|
||||
* Iterate over each clue box and check it's satisfied.
|
||||
*/
|
||||
for (box = 0; box < ctx->nboxes; box++) {
|
||||
int *sq = ctx->boxlist + ctx->boxes[box];
|
||||
int n = ctx->boxes[box+1] - ctx->boxes[box];
|
||||
long value = ctx->clues[box] & ~CMASK;
|
||||
long op = ctx->clues[box] & CMASK;
|
||||
bool fail = false;
|
||||
|
||||
switch (op) {
|
||||
case C_ADD: {
|
||||
long sum = 0;
|
||||
for (i = 0; i < n; i++)
|
||||
sum += solver->grid[transpose(sq[i], w)];
|
||||
fail = (sum != value);
|
||||
break;
|
||||
}
|
||||
|
||||
case C_MUL: {
|
||||
long remaining = value;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (remaining % solver->grid[transpose(sq[i], w)]) {
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
remaining /= solver->grid[transpose(sq[i], w)];
|
||||
}
|
||||
if (remaining != 1)
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case C_SUB:
|
||||
assert(n == 2);
|
||||
if (value != labs(solver->grid[transpose(sq[0], w)] -
|
||||
solver->grid[transpose(sq[1], w)]))
|
||||
fail = true;
|
||||
break;
|
||||
|
||||
case C_DIV: {
|
||||
int num, den;
|
||||
assert(n == 2);
|
||||
num = max(solver->grid[transpose(sq[0], w)],
|
||||
solver->grid[transpose(sq[1], w)]);
|
||||
den = min(solver->grid[transpose(sq[0], w)],
|
||||
solver->grid[transpose(sq[1], w)]);
|
||||
if (den * value != num)
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*sclue at (%d,%d) is violated\n",
|
||||
solver_recurse_depth*4, "",
|
||||
sq[0]/w+1, sq[0]%w+1);
|
||||
printf("%*s (%s clue with target %ld containing [",
|
||||
solver_recurse_depth*4, "",
|
||||
(op == C_ADD ? "addition" : op == C_SUB ? "subtraction":
|
||||
op == C_MUL ? "multiplication" : "division"), value);
|
||||
for (i = 0; i < n; i++)
|
||||
printf(" %d", (int)solver->grid[transpose(sq[i], w)]);
|
||||
printf(" ]\n");
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
|
||||
{
|
||||
int a = w*w;
|
||||
|
@ -638,7 +724,7 @@ static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
|
|||
ret = latin_solver(soln, w, maxdiff,
|
||||
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||
keen_solvers, &ctx, NULL, NULL);
|
||||
keen_solvers, keen_valid, &ctx, NULL, NULL);
|
||||
|
||||
sfree(ctx.dscratch);
|
||||
sfree(ctx.iscratch);
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
|
||||
#ifdef STANDALONE_SOLVER
|
||||
int solver_show_working, solver_recurse_depth;
|
||||
|
@ -711,7 +711,7 @@ int latin_solver_diff_set(struct latin_solver *solver,
|
|||
static int latin_solver_recurse
|
||||
(struct latin_solver *solver, int diff_simple, int diff_set_0,
|
||||
int diff_set_1, int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
usersolver_t const *usersolvers, validator_t valid, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
{
|
||||
int best, bestcount;
|
||||
|
@ -817,7 +817,8 @@ static int latin_solver_recurse
|
|||
ret = latin_solver_top(&subsolver, diff_recursive,
|
||||
diff_simple, diff_set_0, diff_set_1,
|
||||
diff_forcing, diff_recursive,
|
||||
usersolvers, newctx, ctxnew, ctxfree);
|
||||
usersolvers, valid, newctx,
|
||||
ctxnew, ctxfree);
|
||||
latin_solver_free(&subsolver);
|
||||
if (ctxnew)
|
||||
ctxfree(newctx);
|
||||
|
@ -879,8 +880,8 @@ static int latin_solver_recurse
|
|||
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
{
|
||||
struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver);
|
||||
int ret, diff = diff_simple;
|
||||
|
@ -941,7 +942,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
|||
int nsol = latin_solver_recurse(solver,
|
||||
diff_simple, diff_set_0, diff_set_1,
|
||||
diff_forcing, diff_recursive,
|
||||
usersolvers, ctx, ctxnew, ctxfree);
|
||||
usersolvers, valid, ctx,
|
||||
ctxnew, ctxfree);
|
||||
if (nsol < 0) diff = diff_impossible;
|
||||
else if (nsol == 1) diff = diff_recursive;
|
||||
else if (nsol > 1) diff = diff_ambiguous;
|
||||
|
@ -990,6 +992,17 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
|||
}
|
||||
#endif
|
||||
|
||||
if (diff != diff_impossible && diff != diff_unfinished &&
|
||||
diff != diff_ambiguous && valid && !valid(solver, ctx)) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*ssolution failed final validation!\n",
|
||||
solver_recurse_depth*4, "");
|
||||
}
|
||||
#endif
|
||||
diff = diff_impossible;
|
||||
}
|
||||
|
||||
latin_solver_free_scratch(scratch);
|
||||
|
||||
return diff;
|
||||
|
@ -998,8 +1011,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
|||
int latin_solver_main(struct latin_solver *solver, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
{
|
||||
int diff;
|
||||
#ifdef STANDALONE_SOLVER
|
||||
|
@ -1027,7 +1040,7 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff,
|
|||
diff = latin_solver_top(solver, maxdiff,
|
||||
diff_simple, diff_set_0, diff_set_1,
|
||||
diff_forcing, diff_recursive,
|
||||
usersolvers, ctx, ctxnew, ctxfree);
|
||||
usersolvers, valid, ctx, ctxnew, ctxfree);
|
||||
|
||||
#ifdef STANDALONE_SOLVER
|
||||
sfree(names);
|
||||
|
@ -1040,8 +1053,8 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff,
|
|||
int latin_solver(digit *grid, int o, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||
{
|
||||
struct latin_solver solver;
|
||||
int diff;
|
||||
|
@ -1050,7 +1063,7 @@ int latin_solver(digit *grid, int o, int maxdiff,
|
|||
diff = latin_solver_main(&solver, maxdiff,
|
||||
diff_simple, diff_set_0, diff_set_1,
|
||||
diff_forcing, diff_recursive,
|
||||
usersolvers, ctx, ctxnew, ctxfree);
|
||||
usersolvers, valid, ctx, ctxnew, ctxfree);
|
||||
latin_solver_free(&solver);
|
||||
return diff;
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ int latin_solver_diff_set(struct latin_solver *solver,
|
|||
bool extreme);
|
||||
|
||||
typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx);
|
||||
typedef bool (*validator_t)(struct latin_solver *solver, void *ctx);
|
||||
typedef void *(*ctxnew_t)(void *ctx);
|
||||
typedef void (*ctxfree_t)(void *ctx);
|
||||
|
||||
|
@ -96,15 +97,15 @@ enum { diff_impossible = 10, diff_ambiguous, diff_unfinished };
|
|||
int latin_solver(digit *grid, int o, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
|
||||
/* Version you can call if you want to alloc and free latin_solver yourself */
|
||||
int latin_solver_main(struct latin_solver *solver, int maxdiff,
|
||||
int diff_simple, int diff_set_0, int diff_set_1,
|
||||
int diff_forcing, int diff_recursive,
|
||||
usersolver_t const *usersolvers, void *ctx,
|
||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
usersolver_t const *usersolvers, validator_t valid,
|
||||
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||
|
||||
void latin_solver_debug(unsigned char *cube, int o);
|
||||
|
||||
|
|
|
@ -258,6 +258,8 @@ static const char *validate_params(const game_params *params, bool full)
|
|||
*/
|
||||
if (full && params->unique && (params->w <= 2 || params->h <= 2))
|
||||
return "Width and height must both be greater than two";
|
||||
if (params->n < 0)
|
||||
return "Mine count may not be negative";
|
||||
if (params->n > params->w * params->h - 9)
|
||||
return "Too many mines for grid size";
|
||||
if (params->n < 1)
|
||||
|
|
|
@ -20,6 +20,7 @@ enum {
|
|||
COL_GRID,
|
||||
COL_CURSOR,
|
||||
COL_ERROR,
|
||||
COL_CURSOR_GUIDE,
|
||||
NCOLOURS
|
||||
};
|
||||
|
||||
|
@ -1660,11 +1661,12 @@ static float *game_colours(frontend *fe, int *ncolours)
|
|||
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
ret[COL_GRID * 3 + i] = 0.3F;
|
||||
ret[COL_UNKNOWN * 3 + i] = 0.5F;
|
||||
ret[COL_TEXT * 3 + i] = 0.0F;
|
||||
ret[COL_FULL * 3 + i] = 0.0F;
|
||||
ret[COL_EMPTY * 3 + i] = 1.0F;
|
||||
ret[COL_GRID * 3 + i] = 0.3F;
|
||||
ret[COL_UNKNOWN * 3 + i] = 0.5F;
|
||||
ret[COL_TEXT * 3 + i] = 0.0F;
|
||||
ret[COL_FULL * 3 + i] = 0.0F;
|
||||
ret[COL_EMPTY * 3 + i] = 1.0F;
|
||||
ret[COL_CURSOR_GUIDE * 3 + i] = 0.5F;
|
||||
}
|
||||
ret[COL_CURSOR * 3 + 0] = 1.0F;
|
||||
ret[COL_CURSOR * 3 + 1] = 0.25F;
|
||||
|
@ -1891,6 +1893,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
|
|||
*/
|
||||
for (i = 0; i < state->common->w + state->common->h; i++) {
|
||||
int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT;
|
||||
if (colour == COL_TEXT && ((cx >= 0 && i == cx) || (cy >= 0 && i == cy + ds->w))) {
|
||||
colour = COL_CURSOR_GUIDE;
|
||||
}
|
||||
if (ds->numcolours[i] != colour) {
|
||||
draw_numbers(dr, ds, state, i, true, colour);
|
||||
ds->numcolours[i] = colour;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* setup and layout.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "puzzles.h"
|
||||
|
||||
struct puzzle {
|
||||
|
@ -88,7 +90,7 @@ void document_add_puzzle(document *doc, const game *game, game_params *par,
|
|||
doc->got_solns = true;
|
||||
}
|
||||
|
||||
static void get_puzzle_size(document *doc, struct puzzle *pz,
|
||||
static void get_puzzle_size(const document *doc, struct puzzle *pz,
|
||||
float *w, float *h, float *scale)
|
||||
{
|
||||
float ww, hh, ourscale;
|
||||
|
@ -115,133 +117,177 @@ static void get_puzzle_size(document *doc, struct puzzle *pz,
|
|||
}
|
||||
|
||||
/*
|
||||
* Having accumulated a load of puzzles, actually do the printing.
|
||||
* Calculate the the number of pages for a document.
|
||||
*/
|
||||
void document_print(document *doc, drawing *dr)
|
||||
int document_npages(const document *doc)
|
||||
{
|
||||
int ppp; /* puzzles per page */
|
||||
int pages, passes;
|
||||
int page, pass;
|
||||
int pageno;
|
||||
|
||||
ppp = doc->pw * doc->ph;
|
||||
pages = (doc->npuzzles + ppp - 1) / ppp;
|
||||
passes = (doc->got_solns ? 2 : 1);
|
||||
|
||||
print_begin_doc(dr, pages * passes);
|
||||
return pages * passes;
|
||||
}
|
||||
|
||||
pageno = 1;
|
||||
for (pass = 0; pass < passes; pass++) {
|
||||
for (page = 0; page < pages; page++) {
|
||||
int i, n, offset;
|
||||
float colsum, rowsum;
|
||||
|
||||
print_begin_page(dr, pageno);
|
||||
|
||||
offset = page * ppp;
|
||||
n = min(ppp, doc->npuzzles - offset);
|
||||
|
||||
for (i = 0; i < doc->pw; i++)
|
||||
doc->colwid[i] = 0;
|
||||
for (i = 0; i < doc->ph; i++)
|
||||
doc->rowht[i] = 0;
|
||||
|
||||
/*
|
||||
* Lay the page out by computing all the puzzle sizes.
|
||||
*/
|
||||
for (i = 0; i < n; i++) {
|
||||
struct puzzle *pz = doc->puzzles + offset + i;
|
||||
int x = i % doc->pw, y = i / doc->pw;
|
||||
float w, h, scale;
|
||||
|
||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||
|
||||
/* Update the maximum width/height of this column. */
|
||||
doc->colwid[x] = max(doc->colwid[x], w);
|
||||
doc->rowht[y] = max(doc->rowht[y], h);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add up the maximum column/row widths to get the
|
||||
* total amount of space used up by puzzles on the
|
||||
* page. We will use this to compute gutter widths.
|
||||
*/
|
||||
colsum = 0.0;
|
||||
for (i = 0; i < doc->pw; i++)
|
||||
colsum += doc->colwid[i];
|
||||
rowsum = 0.0;
|
||||
for (i = 0; i < doc->ph; i++)
|
||||
rowsum += doc->rowht[i];
|
||||
|
||||
/*
|
||||
* Now do the printing.
|
||||
*/
|
||||
for (i = 0; i < n; i++) {
|
||||
struct puzzle *pz = doc->puzzles + offset + i;
|
||||
int x = i % doc->pw, y = i / doc->pw, j;
|
||||
float w, h, scale, xm, xc, ym, yc;
|
||||
int pixw, pixh, tilesize;
|
||||
|
||||
if (pass == 1 && !pz->st2)
|
||||
continue; /* nothing to do */
|
||||
|
||||
/*
|
||||
* The total amount of gutter space is the page
|
||||
* width minus colsum. This is divided into pw+1
|
||||
* gutters, so the amount of horizontal gutter
|
||||
* space appearing to the left of this puzzle
|
||||
* column is
|
||||
*
|
||||
* (width-colsum) * (x+1)/(pw+1)
|
||||
* = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
|
||||
*/
|
||||
xm = (float)(x+1) / (doc->pw + 1);
|
||||
xc = -xm * colsum;
|
||||
/* And similarly for y. */
|
||||
ym = (float)(y+1) / (doc->ph + 1);
|
||||
yc = -ym * rowsum;
|
||||
|
||||
/*
|
||||
* However, the amount of space to the left of this
|
||||
* puzzle isn't just gutter space: we must also
|
||||
* count the widths of all the previous columns.
|
||||
*/
|
||||
for (j = 0; j < x; j++)
|
||||
xc += doc->colwid[j];
|
||||
/* And similarly for rows. */
|
||||
for (j = 0; j < y; j++)
|
||||
yc += doc->rowht[j];
|
||||
|
||||
/*
|
||||
* Now we adjust for this _specific_ puzzle, which
|
||||
* means centring it within the cell we've just
|
||||
* computed.
|
||||
*/
|
||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||
xc += (doc->colwid[x] - w) / 2;
|
||||
yc += (doc->rowht[y] - h) / 2;
|
||||
|
||||
/*
|
||||
* And now we know where and how big we want to
|
||||
* print the puzzle, just go ahead and do so. For
|
||||
* the moment I'll pick a standard pixel tile size
|
||||
* of 512.
|
||||
*
|
||||
* (FIXME: would it be better to pick this value
|
||||
* with reference to the printer resolution? Or
|
||||
* permit each game to choose its own?)
|
||||
*/
|
||||
tilesize = 512;
|
||||
pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
|
||||
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
|
||||
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
|
||||
print_end_puzzle(dr);
|
||||
}
|
||||
|
||||
print_end_page(dr, pageno);
|
||||
pageno++;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Begin a document.
|
||||
*/
|
||||
void document_begin(const document *doc, drawing *dr)
|
||||
{
|
||||
print_begin_doc(dr, document_npages(doc));
|
||||
}
|
||||
|
||||
/*
|
||||
* End a document.
|
||||
*/
|
||||
void document_end(const document *doc, drawing *dr)
|
||||
{
|
||||
print_end_doc(dr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a single page of a document.
|
||||
*/
|
||||
void document_print_page(const document *doc, drawing *dr, int page_nr)
|
||||
{
|
||||
int ppp; /* puzzles per page */
|
||||
int pages;
|
||||
int page, pass;
|
||||
int pageno;
|
||||
int i, n, offset;
|
||||
float colsum, rowsum;
|
||||
|
||||
ppp = doc->pw * doc->ph;
|
||||
pages = (doc->npuzzles + ppp - 1) / ppp;
|
||||
|
||||
/* Get the current page, pass, and pageno based on page_nr. */
|
||||
if (page_nr < pages) {
|
||||
page = page_nr;
|
||||
pass = 0;
|
||||
}
|
||||
else {
|
||||
assert(doc->got_solns);
|
||||
page = page_nr - pages;
|
||||
pass = 1;
|
||||
}
|
||||
pageno = page_nr + 1;
|
||||
|
||||
offset = page * ppp;
|
||||
n = min(ppp, doc->npuzzles - offset);
|
||||
|
||||
print_begin_page(dr, pageno);
|
||||
|
||||
for (i = 0; i < doc->pw; i++)
|
||||
doc->colwid[i] = 0;
|
||||
for (i = 0; i < doc->ph; i++)
|
||||
doc->rowht[i] = 0;
|
||||
|
||||
/*
|
||||
* Lay the page out by computing all the puzzle sizes.
|
||||
*/
|
||||
for (i = 0; i < n; i++) {
|
||||
struct puzzle *pz = doc->puzzles + offset + i;
|
||||
int x = i % doc->pw, y = i / doc->pw;
|
||||
float w, h, scale;
|
||||
|
||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||
|
||||
/* Update the maximum width/height of this column. */
|
||||
doc->colwid[x] = max(doc->colwid[x], w);
|
||||
doc->rowht[y] = max(doc->rowht[y], h);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add up the maximum column/row widths to get the
|
||||
* total amount of space used up by puzzles on the
|
||||
* page. We will use this to compute gutter widths.
|
||||
*/
|
||||
colsum = 0.0;
|
||||
for (i = 0; i < doc->pw; i++)
|
||||
colsum += doc->colwid[i];
|
||||
rowsum = 0.0;
|
||||
for (i = 0; i < doc->ph; i++)
|
||||
rowsum += doc->rowht[i];
|
||||
|
||||
/*
|
||||
* Now do the printing.
|
||||
*/
|
||||
for (i = 0; i < n; i++) {
|
||||
struct puzzle *pz = doc->puzzles + offset + i;
|
||||
int x = i % doc->pw, y = i / doc->pw, j;
|
||||
float w, h, scale, xm, xc, ym, yc;
|
||||
int pixw, pixh, tilesize;
|
||||
|
||||
if (pass == 1 && !pz->st2)
|
||||
continue; /* nothing to do */
|
||||
|
||||
/*
|
||||
* The total amount of gutter space is the page
|
||||
* width minus colsum. This is divided into pw+1
|
||||
* gutters, so the amount of horizontal gutter
|
||||
* space appearing to the left of this puzzle
|
||||
* column is
|
||||
*
|
||||
* (width-colsum) * (x+1)/(pw+1)
|
||||
* = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
|
||||
*/
|
||||
xm = (float)(x+1) / (doc->pw + 1);
|
||||
xc = -xm * colsum;
|
||||
/* And similarly for y. */
|
||||
ym = (float)(y+1) / (doc->ph + 1);
|
||||
yc = -ym * rowsum;
|
||||
|
||||
/*
|
||||
* However, the amount of space to the left of this
|
||||
* puzzle isn't just gutter space: we must also
|
||||
* count the widths of all the previous columns.
|
||||
*/
|
||||
for (j = 0; j < x; j++)
|
||||
xc += doc->colwid[j];
|
||||
/* And similarly for rows. */
|
||||
for (j = 0; j < y; j++)
|
||||
yc += doc->rowht[j];
|
||||
|
||||
/*
|
||||
* Now we adjust for this _specific_ puzzle, which
|
||||
* means centring it within the cell we've just
|
||||
* computed.
|
||||
*/
|
||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||
xc += (doc->colwid[x] - w) / 2;
|
||||
yc += (doc->rowht[y] - h) / 2;
|
||||
|
||||
/*
|
||||
* And now we know where and how big we want to
|
||||
* print the puzzle, just go ahead and do so. For
|
||||
* the moment I'll pick a standard pixel tile size
|
||||
* of 512.
|
||||
*
|
||||
* (FIXME: would it be better to pick this value
|
||||
* with reference to the printer resolution? Or
|
||||
* permit each game to choose its own?)
|
||||
*/
|
||||
tilesize = 512;
|
||||
pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
|
||||
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
|
||||
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
|
||||
print_end_puzzle(dr);
|
||||
}
|
||||
|
||||
print_end_page(dr, pageno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Having accumulated a load of puzzles, actually do the printing.
|
||||
*/
|
||||
void document_print(const document *doc, drawing *dr)
|
||||
{
|
||||
int page, pages;
|
||||
pages = document_npages(doc);
|
||||
print_begin_doc(dr, pages);
|
||||
for (page = 0; page < pages; page++)
|
||||
document_print_page(doc, dr, page);
|
||||
print_end_doc(dr);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
|
||||
#include "puzzles.h"
|
||||
|
||||
#define ROOT2 1.414213562
|
||||
|
||||
struct psdata {
|
||||
FILE *fp;
|
||||
bool colour;
|
||||
|
|
|
@ -3360,7 +3360,8 @@ This software is \i{copyright} 2004-2014 Simon Tatham.
|
|||
|
||||
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||
K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
|
||||
Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
|
||||
Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
|
||||
Quevillon and Asher Gordon.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#define PI 3.141592653589793238462643383279502884197169399
|
||||
#define ROOT2 1.414213562373095048801688724209698078569672
|
||||
|
||||
#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
|
||||
|
||||
|
@ -530,7 +531,11 @@ document *document_new(int pw, int ph, float userscale);
|
|||
void document_free(document *doc);
|
||||
void document_add_puzzle(document *doc, const game *game, game_params *par,
|
||||
game_state *st, game_state *st2);
|
||||
void document_print(document *doc, drawing *dr);
|
||||
int document_npages(const document *doc);
|
||||
void document_begin(const document *doc, drawing *dr);
|
||||
void document_end(const document *doc, drawing *dr);
|
||||
void document_print_page(const document *doc, drawing *dr, int page_nr);
|
||||
void document_print(const document *doc, drawing *dr);
|
||||
|
||||
/*
|
||||
* ps.c
|
||||
|
|
|
@ -574,6 +574,38 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
|
|||
#define SOLVER(upper,title,func,lower) func,
|
||||
static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) };
|
||||
|
||||
static bool towers_valid(struct latin_solver *solver, void *vctx)
|
||||
{
|
||||
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
|
||||
int w = ctx->w;
|
||||
int c, i, n, best, clue, start, step;
|
||||
for (c = 0; c < 4*w; c++) {
|
||||
clue = ctx->clues[c];
|
||||
if (!clue)
|
||||
continue;
|
||||
|
||||
STARTSTEP(start, step, c, w);
|
||||
n = best = 0;
|
||||
for (i = 0; i < w; i++) {
|
||||
if (solver->grid[start+i*step] > best) {
|
||||
best = solver->grid[start+i*step];
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (n != clue) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
printf("%*sclue %s %d is violated\n",
|
||||
solver_recurse_depth*4, "",
|
||||
cluepos[c/w], c%w+1);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int solver(int w, int *clues, digit *soln, int maxdiff)
|
||||
{
|
||||
int ret;
|
||||
|
@ -589,7 +621,7 @@ static int solver(int w, int *clues, digit *soln, int maxdiff)
|
|||
ret = latin_solver(soln, w, maxdiff,
|
||||
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||
towers_solvers, &ctx, NULL, NULL);
|
||||
towers_solvers, towers_valid, &ctx, NULL, NULL);
|
||||
|
||||
sfree(ctx.iscratch);
|
||||
sfree(ctx.dscratch);
|
||||
|
|
|
@ -8,6 +8,9 @@ tracks : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res
|
|||
|
||||
ALL += tracks[COMBINED] TRACKS_EXTRA
|
||||
|
||||
trackssolver : [U] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
|
||||
trackssolver : [C] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
|
||||
|
||||
!begin am gtk
|
||||
GAMES += tracks
|
||||
!end
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
* #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
|
||||
* #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1
|
||||
* #115 8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1
|
||||
* #942 8x8:n5iCfAzAe,2,2,S5,5,3,5,4,5,4,5,2,S5,3,4,5,3
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
@ -29,9 +30,11 @@
|
|||
* Difficulty levels. I do some macro ickery here to ensure that my
|
||||
* enum and the various forms of my name list always match up.
|
||||
*/
|
||||
#define DIFFLIST(A) \
|
||||
A(EASY,Easy,e) \
|
||||
A(TRICKY,Tricky,t)
|
||||
#define DIFFLIST(A) \
|
||||
A(EASY,Easy,e) \
|
||||
A(TRICKY,Tricky,t) \
|
||||
A(HARD,Hard,h) \
|
||||
/* end of list */
|
||||
|
||||
#define ENUM(upper,title,lower) DIFF_ ## upper,
|
||||
#define TITLE(upper,title,lower) #title,
|
||||
|
@ -65,10 +68,12 @@ static const struct game_params tracks_presets[] = {
|
|||
{10, 8, DIFF_TRICKY, 1 },
|
||||
{10, 10, DIFF_EASY, 1},
|
||||
{10, 10, DIFF_TRICKY, 1},
|
||||
{10, 10, DIFF_HARD, 1},
|
||||
{15, 10, DIFF_EASY, 1},
|
||||
{15, 10, DIFF_TRICKY, 1},
|
||||
{15, 15, DIFF_EASY, 1},
|
||||
{15, 15, DIFF_TRICKY, 1},
|
||||
{15, 15, DIFF_HARD, 1},
|
||||
};
|
||||
|
||||
static bool game_fetch_preset(int i, char **name, game_params **params)
|
||||
|
@ -452,7 +457,7 @@ start:
|
|||
state->numbers->col_s = px;
|
||||
}
|
||||
|
||||
static int tracks_solve(game_state *state, int diff);
|
||||
static int tracks_solve(game_state *state, int diff, int *max_diff_out);
|
||||
static void debug_state(game_state *state, const char *what);
|
||||
|
||||
/* Clue-setting algorithm:
|
||||
|
@ -533,6 +538,26 @@ static game_state *copy_and_strip(const game_state *state, game_state *ret, int
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifdef STANDALONE_SOLVER
|
||||
#include <stdarg.h>
|
||||
static FILE *solver_diagnostics_fp = NULL;
|
||||
static void solver_diagnostic(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(solver_diagnostics_fp, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', solver_diagnostics_fp);
|
||||
}
|
||||
#define solverdebug(printf_params) do { \
|
||||
if (solver_diagnostics_fp) { \
|
||||
solver_diagnostic printf_params; \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define solverdebug(printf_params) ((void)0)
|
||||
#endif
|
||||
|
||||
static int solve_progress(const game_state *state) {
|
||||
int i, w = state->p.w, h = state->p.h, progress = 0;
|
||||
|
||||
|
@ -575,6 +600,7 @@ static int add_clues(game_state *state, random_state *rs, int diff)
|
|||
int *positions = snewn(w*h, int), npositions = 0;
|
||||
int *nedges_previous_solve = snewn(w*h, int);
|
||||
game_state *scratch = dup_game(state);
|
||||
int diff_used;
|
||||
|
||||
debug_state(state, "gen: Initial board");
|
||||
|
||||
|
@ -591,17 +617,13 @@ static int add_clues(game_state *state, random_state *rs, int diff)
|
|||
|
||||
/* First, check whether the puzzle is already either too easy, or just right */
|
||||
scratch = copy_and_strip(state, scratch, -1);
|
||||
if (diff > 0) {
|
||||
sr = tracks_solve(scratch, diff-1);
|
||||
if (sr < 0)
|
||||
assert(!"Generator should not have created impossible puzzle");
|
||||
if (sr > 0) {
|
||||
ret = -1; /* already too easy, even without adding clues. */
|
||||
debug(("gen: ...already too easy, need new board."));
|
||||
goto done;
|
||||
}
|
||||
sr = tracks_solve(scratch, diff, &diff_used);
|
||||
if (diff_used < diff) {
|
||||
ret = -1; /* already too easy, even without adding clues. */
|
||||
debug(("gen: ...already too easy, need new board."));
|
||||
goto done;
|
||||
}
|
||||
sr = tracks_solve(scratch, diff);
|
||||
|
||||
if (sr < 0)
|
||||
assert(!"Generator should not have created impossible puzzle");
|
||||
if (sr > 0) {
|
||||
|
@ -629,12 +651,10 @@ static int add_clues(game_state *state, random_state *rs, int diff)
|
|||
if (check_phantom_moves(scratch))
|
||||
continue; /* adding a clue here would add phantom track */
|
||||
|
||||
if (diff > 0) {
|
||||
if (tracks_solve(scratch, diff-1) > 0) {
|
||||
if (tracks_solve(scratch, diff, &diff_used) > 0) {
|
||||
if (diff_used < diff) {
|
||||
continue; /* adding a clue here makes it too easy */
|
||||
}
|
||||
}
|
||||
if (tracks_solve(scratch, diff) > 0) {
|
||||
/* we're now soluble (and we weren't before): add this clue, and then
|
||||
start stripping clues */
|
||||
debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w));
|
||||
|
@ -676,7 +696,7 @@ strip_clues:
|
|||
if (check_phantom_moves(scratch))
|
||||
continue; /* removing a clue here would add phantom track */
|
||||
|
||||
if (tracks_solve(scratch, diff) > 0) {
|
||||
if (tracks_solve(scratch, diff, NULL) > 0) {
|
||||
debug(("gen: ... removing clue at (%d,%d), still soluble without it", i%w, i/w));
|
||||
state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */
|
||||
}
|
||||
|
@ -686,6 +706,7 @@ strip_clues:
|
|||
|
||||
done:
|
||||
sfree(positions);
|
||||
sfree(nedges_previous_solve);
|
||||
free_game(scratch);
|
||||
return ret;
|
||||
}
|
||||
|
@ -780,7 +801,7 @@ newpath:
|
|||
}
|
||||
*p++ = '\0';
|
||||
|
||||
ret = tracks_solve(state, DIFFCOUNT);
|
||||
ret = tracks_solve(state, DIFFCOUNT, NULL);
|
||||
assert(ret >= 0);
|
||||
free_game(state);
|
||||
|
||||
|
@ -882,6 +903,10 @@ static game_state *new_game(midend *me, const game_params *params, const char *d
|
|||
return state;
|
||||
}
|
||||
|
||||
struct solver_scratch {
|
||||
int *dsf;
|
||||
};
|
||||
|
||||
static int solve_set_sflag(game_state *state, int x, int y,
|
||||
unsigned int f, const char *why)
|
||||
{
|
||||
|
@ -889,10 +914,10 @@ static int solve_set_sflag(game_state *state, int x, int y,
|
|||
|
||||
if (state->sflags[i] & f)
|
||||
return 0;
|
||||
debug(("solve: square (%d,%d) -> %s: %s",
|
||||
solverdebug(("square (%d,%d) -> %s: %s",
|
||||
x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
||||
if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
|
||||
debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
|
||||
solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
|
||||
state->impossible = true;
|
||||
}
|
||||
state->sflags[i] |= f;
|
||||
|
@ -906,11 +931,11 @@ static int solve_set_eflag(game_state *state, int x, int y, int d,
|
|||
|
||||
if (sf & f)
|
||||
return 0;
|
||||
debug(("solve: edge (%d,%d)/%c -> %s: %s", x, y,
|
||||
solverdebug(("edge (%d,%d)/%c -> %s: %s", x, y,
|
||||
(d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R',
|
||||
(f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
||||
if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
|
||||
debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
|
||||
solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
|
||||
state->impossible = true;
|
||||
}
|
||||
S_E_SET(state, x, y, d, f);
|
||||
|
@ -1063,7 +1088,7 @@ static int solve_check_single_sub(game_state *state, int si, int id, int n,
|
|||
if (ctrack != (target-1)) return 0;
|
||||
if (nperp > 0 || n1edge != 1) return 0;
|
||||
|
||||
debug(("check_single from (%d,%d): 1 match from (%d,%d)",
|
||||
solverdebug(("check_single from (%d,%d): 1 match from (%d,%d)",
|
||||
si%w, si/w, i1edge%w, i1edge/w));
|
||||
|
||||
/* We have a match: anything that's more than 1 away from this square
|
||||
|
@ -1120,12 +1145,12 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n,
|
|||
}
|
||||
|
||||
if (nloose > (target - e2count)) {
|
||||
debug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE",
|
||||
solverdebug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE",
|
||||
what, si%w, si/w, nloose, target-e2count));
|
||||
state->impossible = true;
|
||||
}
|
||||
if (nloose > 0 && nloose == (target - e2count)) {
|
||||
debug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.",
|
||||
solverdebug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.",
|
||||
what, si%w, si/w, nloose));
|
||||
for (j = 0, i = si; j < n; j++, i += id) {
|
||||
if (!(state->sflags[i] & S_MARK))
|
||||
|
@ -1146,7 +1171,7 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n,
|
|||
}
|
||||
}
|
||||
if (nloose == 1 && (target - e2count) == 2 && nperp == 0) {
|
||||
debug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel",
|
||||
solverdebug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel",
|
||||
what, si%w, si/w));
|
||||
for (j = 0, i = si; j < n; j++, i += id) {
|
||||
if (!(state->sflags[i] & S_MARK))
|
||||
|
@ -1176,6 +1201,110 @@ static int solve_check_loose_ends(game_state *state)
|
|||
return did;
|
||||
}
|
||||
|
||||
static void solve_check_neighbours_count(
|
||||
game_state *state, int start, int step, int n, int clueindex,
|
||||
bool *onefill, bool *oneempty)
|
||||
{
|
||||
int to_fill = state->numbers->numbers[clueindex];
|
||||
int to_empty = n - to_fill;
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
int p = start + i*step;
|
||||
if (state->sflags[p] & S_TRACK)
|
||||
to_fill--;
|
||||
if (state->sflags[p] & S_NOTRACK)
|
||||
to_empty--;
|
||||
}
|
||||
*onefill = (to_fill == 1);
|
||||
*oneempty = (to_empty == 1);
|
||||
}
|
||||
|
||||
static int solve_check_neighbours_try(game_state *state, int x, int y,
|
||||
int X, int Y, bool onefill,
|
||||
bool oneempty, unsigned dir,
|
||||
const char *what)
|
||||
{
|
||||
int w = state->p.w, p = y*w+x, P = Y*w+X;
|
||||
|
||||
/*
|
||||
* We're given a neighbouring pair of squares p,P, with 'dir'
|
||||
* being the direction from the former to the latter. We aim to
|
||||
* spot situations in which, if p is a track square, then P must
|
||||
* also be one (because p doesn't have enough free exits to avoid
|
||||
* using the one that goes towards P).
|
||||
*
|
||||
* Then, if the target number of track squares on their shared
|
||||
* row/column says that there's only one track square left to
|
||||
* place, it can't be p, because P would have to be one too,
|
||||
* violating the clue. So in that situation we can mark p as
|
||||
* unfilled. Conversely, if there's only one _non_-track square
|
||||
* left to place, it can't be P, so we can mark P as filled.
|
||||
*/
|
||||
|
||||
if ((state->sflags[p] | state->sflags[P]) & (S_TRACK | S_NOTRACK))
|
||||
return 0; /* no need: we already know something about these squares */
|
||||
|
||||
int possible_exits_except_dir = nbits[
|
||||
ALLDIR & ~dir & ~S_E_DIRS(state, x, y, E_NOTRACK)];
|
||||
if (possible_exits_except_dir >= 2)
|
||||
return 0; /* square p need not connect to P, even if it is filled */
|
||||
|
||||
/* OK, now we know that if p is filled, P must be filled too. */
|
||||
|
||||
int did = 0;
|
||||
if (onefill) {
|
||||
/* But at most one of them can be filled, so it can't be p. */
|
||||
state->sflags[p] |= S_NOTRACK;
|
||||
solverdebug(("square (%d,%d) -> NOTRACK: otherwise, that and (%d,%d) "
|
||||
"would make too many TRACK in %s", x, y, X, Y, what));
|
||||
did++;
|
||||
}
|
||||
if (oneempty) {
|
||||
/* Alternatively, at least one of them _must_ be filled, so P
|
||||
* must be. */
|
||||
state->sflags[P] |= S_TRACK;
|
||||
solverdebug(("square (%d,%d) -> TRACK: otherwise, that and (%d,%d) "
|
||||
"would make too many NOTRACK in %s", X, Y, x, y, what));
|
||||
did++;
|
||||
}
|
||||
return did;
|
||||
}
|
||||
|
||||
static int solve_check_neighbours(game_state *state, bool both_ways)
|
||||
{
|
||||
int w = state->p.w, h = state->p.h, x, y, did = 0;
|
||||
bool onefill, oneempty;
|
||||
|
||||
for (x = 0; x < w; x++) {
|
||||
solve_check_neighbours_count(state, x, w, h, x, &onefill, &oneempty);
|
||||
if (!both_ways)
|
||||
oneempty = false; /* disable the harder version of the deduction */
|
||||
if (!onefill && !oneempty)
|
||||
continue;
|
||||
for (y = 0; y+1 < h; y++) {
|
||||
did += solve_check_neighbours_try(state, x, y, x, y+1,
|
||||
onefill, oneempty, D, "column");
|
||||
did += solve_check_neighbours_try(state, x, y+1, x, y,
|
||||
onefill, oneempty, U, "column");
|
||||
}
|
||||
}
|
||||
for (y = 0; y < h; y++) {
|
||||
solve_check_neighbours_count(state, y*w, 1, w, w+y,
|
||||
&onefill, &oneempty);
|
||||
if (!both_ways)
|
||||
oneempty = false; /* disable the harder version of the deduction */
|
||||
if (!onefill && !oneempty)
|
||||
continue;
|
||||
for (x = 0; x+1 < w; x++) {
|
||||
did += solve_check_neighbours_try(state, x, y, x+1, y,
|
||||
onefill, oneempty, R, "row");
|
||||
did += solve_check_neighbours_try(state, x+1, y, x, y,
|
||||
onefill, oneempty, L, "row");
|
||||
}
|
||||
}
|
||||
return did;
|
||||
}
|
||||
|
||||
static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
|
||||
int *dsf, int startc, int endc)
|
||||
{
|
||||
|
@ -1195,7 +1324,7 @@ static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
|
|||
return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop");
|
||||
}
|
||||
if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) {
|
||||
debug(("Adding link at (%d,%d) would join start to end", x, y));
|
||||
solverdebug(("Adding link at (%d,%d) would join start to end", x, y));
|
||||
/* We mustn't join the start to the end if:
|
||||
- there are other bits of track that aren't attached to either end
|
||||
- the clues are not fully satisfied yet
|
||||
|
@ -1287,10 +1416,145 @@ static void solve_discount_edge(game_state *state, int x, int y, int d)
|
|||
solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge");
|
||||
}
|
||||
|
||||
static int tracks_solve(game_state *state, int diff)
|
||||
static int solve_bridge_sub(game_state *state, int x, int y, int d,
|
||||
struct solver_scratch *sc)
|
||||
{
|
||||
/*
|
||||
* Imagine a graph on the squares of the grid, with an edge
|
||||
* connecting neighbouring squares only if it's not yet known
|
||||
* whether there's a track between them.
|
||||
*
|
||||
* This function is called if the edge between x,y and X,Y is a
|
||||
* bridge in that graph: that is, it's not part of any loop in the
|
||||
* graph, or equivalently, removing it would increase the number
|
||||
* of connected components in the graph.
|
||||
*
|
||||
* In that situation, we can fill in the edge by a parity
|
||||
* argument. Construct a closed loop of edges in the grid, all of
|
||||
* whose states are known except this one. The track starts and
|
||||
* ends outside this loop, so it must cross the boundary of the
|
||||
* loop an even number of times. So if we count up how many times
|
||||
* the track is known to cross the edges of our loop, then we can
|
||||
* fill in the last edge in whichever way makes that number even.
|
||||
*
|
||||
* In fact, there's not even any need to go to the effort of
|
||||
* constructing a _single_ closed loop. The simplest thing is to
|
||||
* delete the bridge edge from the graph, find a connected
|
||||
* component of the reduced graph whose boundary includes that
|
||||
* edge, and take every edge separating that component from
|
||||
* another. This may not lead to _exactly one_ cycle - the
|
||||
* component could be non-simply connected and have a hole in the
|
||||
* middle - but that doesn't matter, because the same parity
|
||||
* constraint applies just as well with more than one disjoint
|
||||
* loop.
|
||||
*/
|
||||
int w = state->p.w, h = state->p.h, wh = w*h;
|
||||
int X = x + DX(d), Y = y + DY(d);
|
||||
int xi, yi, di;
|
||||
|
||||
assert(d == D || d == R);
|
||||
|
||||
if (!sc->dsf)
|
||||
sc->dsf = snew_dsf(wh);
|
||||
dsf_init(sc->dsf, wh);
|
||||
|
||||
for (xi = 0; xi < w; xi++) {
|
||||
for (yi = 0; yi < h; yi++) {
|
||||
/* We expect to have been called with X,Y either to the
|
||||
* right of x,y or below it, not the other way round. If
|
||||
* that were not true, the tests in this loop to exclude
|
||||
* the bridge edge would have to be twice as annoying. */
|
||||
|
||||
if (yi+1 < h && !S_E_FLAGS(state, xi, yi, D) &&
|
||||
!(xi == x && yi == y && xi == X && yi+1 == Y))
|
||||
dsf_merge(sc->dsf, yi*w+xi, (yi+1)*w+xi);
|
||||
|
||||
if (xi+1 < w && !S_E_FLAGS(state, xi, yi, R) &&
|
||||
!(xi == x && yi == y && xi+1 == X && yi == Y))
|
||||
dsf_merge(sc->dsf, yi*w+xi, yi*w+(xi+1));
|
||||
}
|
||||
}
|
||||
|
||||
int component = dsf_canonify(sc->dsf, y*w+x);
|
||||
int parity = 0;
|
||||
for (xi = 0; xi < w; xi++) {
|
||||
for (yi = 0; yi < h; yi++) {
|
||||
if (dsf_canonify(sc->dsf, yi*w+xi) != component)
|
||||
continue;
|
||||
for (di = 1; di < 16; di *= 2) {
|
||||
int Xi = xi + DX(di), Yi = yi + DY(di);
|
||||
if ((Xi < 0 || Xi >= w || Yi < 0 || Yi >= h ||
|
||||
dsf_canonify(sc->dsf, Yi*w+Xi) != component) &&
|
||||
(S_E_DIRS(state, xi, yi, E_TRACK) & di))
|
||||
parity ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
solve_set_eflag(state, x, y, d, parity ? E_TRACK : E_NOTRACK, "parity");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct solve_bridge_neighbour_ctx {
|
||||
game_state *state;
|
||||
int x, y, dirs;
|
||||
};
|
||||
static int solve_bridge_neighbour(int vertex, void *vctx)
|
||||
{
|
||||
struct solve_bridge_neighbour_ctx *ctx =
|
||||
(struct solve_bridge_neighbour_ctx *)vctx;
|
||||
int w = ctx->state->p.w;
|
||||
|
||||
if (vertex >= 0) {
|
||||
ctx->x = vertex % w;
|
||||
ctx->y = vertex / w;
|
||||
ctx->dirs = ALLDIR
|
||||
& ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_TRACK)
|
||||
& ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_NOTRACK);
|
||||
}
|
||||
unsigned dir = ctx->dirs & -ctx->dirs; /* isolate lowest set bit */
|
||||
if (!dir)
|
||||
return -1;
|
||||
ctx->dirs &= ~dir;
|
||||
int xr = ctx->x + DX(dir), yr = ctx->y + DY(dir);
|
||||
assert(0 <= xr && xr < w);
|
||||
assert(0 <= yr && yr < ctx->state->p.h);
|
||||
return yr * w + xr;
|
||||
}
|
||||
|
||||
static int solve_check_bridge_parity(game_state *state,
|
||||
struct solver_scratch *sc)
|
||||
{
|
||||
int w = state->p.w, h = state->p.h, wh = w*h;
|
||||
struct findloopstate *fls;
|
||||
struct solve_bridge_neighbour_ctx ctx[1];
|
||||
int x, y, did = 0;
|
||||
|
||||
ctx->state = state;
|
||||
fls = findloop_new_state(wh);
|
||||
findloop_run(fls, wh, solve_bridge_neighbour, ctx);
|
||||
|
||||
for (x = 0; x < w; x++) {
|
||||
for (y = 0; y < h; y++) {
|
||||
if (y+1 < h && !findloop_is_loop_edge(fls, y*w+x, (y+1)*w+x))
|
||||
did += solve_bridge_sub(state, x, y, D, sc);
|
||||
if (x+1 < w && !findloop_is_loop_edge(fls, y*w+x, y*w+(x+1)))
|
||||
did += solve_bridge_sub(state, x, y, R, sc);
|
||||
}
|
||||
}
|
||||
|
||||
findloop_free_state(fls);
|
||||
|
||||
return did;
|
||||
}
|
||||
|
||||
static int tracks_solve(game_state *state, int diff, int *max_diff_out)
|
||||
{
|
||||
int x, y, w = state->p.w, h = state->p.h;
|
||||
bool didsth;
|
||||
struct solver_scratch sc[1];
|
||||
int max_diff = DIFF_EASY;
|
||||
|
||||
sc->dsf = NULL;
|
||||
|
||||
debug(("solve..."));
|
||||
state->impossible = false;
|
||||
|
@ -1305,21 +1569,37 @@ static int tracks_solve(game_state *state, int diff)
|
|||
solve_discount_edge(state, w-1, y, R);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
didsth = false;
|
||||
while (!state->impossible) {
|
||||
|
||||
didsth |= solve_update_flags(state);
|
||||
didsth |= solve_count_clues(state);
|
||||
didsth |= solve_check_loop(state);
|
||||
/* Can't use do ... while (0) because we need a 'continue' in this macro */
|
||||
#define TRY(curr_diff, funcall) \
|
||||
if (diff >= (curr_diff) && (funcall)) { \
|
||||
if (max_diff < curr_diff) \
|
||||
max_diff = curr_diff; \
|
||||
continue; \
|
||||
} else ((void)0)
|
||||
|
||||
if (diff >= DIFF_TRICKY) {
|
||||
didsth |= solve_check_single(state);
|
||||
didsth |= solve_check_loose_ends(state);
|
||||
}
|
||||
TRY(DIFF_EASY, solve_update_flags(state));
|
||||
TRY(DIFF_EASY, solve_count_clues(state));
|
||||
TRY(DIFF_EASY, solve_check_loop(state));
|
||||
|
||||
if (!didsth || state->impossible) break;
|
||||
TRY(DIFF_TRICKY, solve_check_single(state));
|
||||
TRY(DIFF_TRICKY, solve_check_loose_ends(state));
|
||||
TRY(DIFF_TRICKY, solve_check_neighbours(state, false));
|
||||
|
||||
TRY(DIFF_HARD, solve_check_neighbours(state, true));
|
||||
TRY(DIFF_HARD, solve_check_bridge_parity(state, sc));
|
||||
|
||||
#undef TRY
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
sfree(sc->dsf);
|
||||
|
||||
if (max_diff_out)
|
||||
*max_diff_out = max_diff;
|
||||
|
||||
return state->impossible ? -1 : check_completion(state, false) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
@ -1379,11 +1659,11 @@ static char *solve_game(const game_state *state, const game_state *currstate,
|
|||
char *move;
|
||||
|
||||
solved = dup_game(currstate);
|
||||
ret = tracks_solve(solved, DIFFCOUNT);
|
||||
ret = tracks_solve(solved, DIFFCOUNT, NULL);
|
||||
if (ret < 1) {
|
||||
free_game(solved);
|
||||
solved = dup_game(state);
|
||||
ret = tracks_solve(solved, DIFFCOUNT);
|
||||
ret = tracks_solve(solved, DIFFCOUNT, NULL);
|
||||
}
|
||||
|
||||
if (ret < 1) {
|
||||
|
@ -2094,7 +2374,7 @@ static game_state *execute_move(const game_state *state, const char *move)
|
|||
goto badmove;
|
||||
move += n;
|
||||
} else if (c == 'H') {
|
||||
tracks_solve(ret, DIFFCOUNT);
|
||||
tracks_solve(ret, DIFFCOUNT, NULL);
|
||||
move++;
|
||||
} else {
|
||||
goto badmove;
|
||||
|
@ -2675,4 +2955,87 @@ const struct game thegame = {
|
|||
0, /* flags */
|
||||
};
|
||||
|
||||
#ifdef STANDALONE_SOLVER
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
game_params *p;
|
||||
game_state *s;
|
||||
char *id = NULL, *desc;
|
||||
int maxdiff = DIFFCOUNT, diff_used;
|
||||
const char *err;
|
||||
bool diagnostics = false, grade = false;
|
||||
int retd;
|
||||
|
||||
while (--argc > 0) {
|
||||
char *p = *++argv;
|
||||
if (!strcmp(p, "-v")) {
|
||||
diagnostics = true;
|
||||
} else if (!strcmp(p, "-g")) {
|
||||
grade = true;
|
||||
} else if (!strncmp(p, "-d", 2) && p[2] && !p[3]) {
|
||||
int i;
|
||||
bool bad = true;
|
||||
for (i = 0; i < lenof(tracks_diffchars); i++)
|
||||
if (tracks_diffchars[i] == p[2]) {
|
||||
bad = false;
|
||||
maxdiff = i;
|
||||
break;
|
||||
}
|
||||
if (bad) {
|
||||
fprintf(stderr, "%s: unrecognised difficulty `%c'\n",
|
||||
argv[0], p[2]);
|
||||
return 1;
|
||||
}
|
||||
} else if (*p == '-') {
|
||||
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
|
||||
return 1;
|
||||
} else {
|
||||
id = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
fprintf(stderr, "usage: %s [-v | -g] <game_id>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
desc = strchr(id, ':');
|
||||
if (!desc) {
|
||||
fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
*desc++ = '\0';
|
||||
|
||||
p = default_params();
|
||||
decode_params(p, id);
|
||||
err = validate_desc(p, desc);
|
||||
if (err) {
|
||||
fprintf(stderr, "%s: %s\n", argv[0], err);
|
||||
return 1;
|
||||
}
|
||||
s = new_game(NULL, p, desc);
|
||||
|
||||
solver_diagnostics_fp = (diagnostics ? stdout : NULL);
|
||||
retd = tracks_solve(s, maxdiff, &diff_used);
|
||||
if (retd < 0) {
|
||||
printf("Puzzle is inconsistent\n");
|
||||
} else if (grade) {
|
||||
printf("Difficulty rating: %s\n",
|
||||
(retd == 0 ? "Ambiguous" : tracks_diffnames[diff_used]));
|
||||
} else {
|
||||
char *text = game_text_format(s);
|
||||
fputs(text, stdout);
|
||||
sfree(text);
|
||||
if (retd == 0)
|
||||
printf("Could not deduce a unique solution\n");
|
||||
}
|
||||
free_game(s);
|
||||
free_params(p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* vim: set shiftwidth=4 tabstop=8: */
|
||||
|
|
|
@ -552,6 +552,12 @@ static char *game_text_format(const game_state *state)
|
|||
int i, x, y, col, maxlen;
|
||||
bool o = state->orientable;
|
||||
|
||||
/* Pedantic check: ensure buf is large enough to format an int in
|
||||
* decimal, using the bound log10(2) < 1/3. (Obviously in practice
|
||||
* int is not going to be larger than even 32 bits any time soon,
|
||||
* but.) */
|
||||
assert(sizeof(buf) >= 1 + sizeof(int) * CHAR_BIT/3);
|
||||
|
||||
/*
|
||||
* First work out how many characters we need to display each
|
||||
* number. We're pretty flexible on grid contents here, so we
|
||||
|
@ -563,6 +569,11 @@ static char *game_text_format(const game_state *state)
|
|||
if (col < x) col = x;
|
||||
}
|
||||
|
||||
/* Reassure sprintf-checking compilers like gcc that the field
|
||||
* width we've just computed is not now excessive */
|
||||
if (col >= sizeof(buf))
|
||||
col = sizeof(buf)-1;
|
||||
|
||||
/*
|
||||
* Now we know the exact total size of the grid we're going to
|
||||
* produce: it's got h rows, each containing w lots of col+o,
|
||||
|
|
|
@ -816,6 +816,74 @@ static int solver_set(struct latin_solver *solver, void *vctx)
|
|||
#define SOLVER(upper,title,func,lower) func,
|
||||
static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) };
|
||||
|
||||
static bool unequal_valid(struct latin_solver *solver, void *vctx)
|
||||
{
|
||||
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
|
||||
if (ctx->state->mode == MODE_ADJACENT) {
|
||||
int o = solver->o;
|
||||
int x, y, nx, ny, v, nv, i;
|
||||
|
||||
for (x = 0; x+1 < o; x++) {
|
||||
for (y = 0; y+1 < o; y++) {
|
||||
v = grid(x, y);
|
||||
for (i = 0; i < 4; i++) {
|
||||
bool is_adj, should_be_adj;
|
||||
|
||||
should_be_adj =
|
||||
(GRID(ctx->state, flags, x, y) & adjthan[i].f);
|
||||
|
||||
nx = x + adjthan[i].dx, ny = y + adjthan[i].dy;
|
||||
if (nx < 0 || ny < 0 || nx >= o || ny >= o)
|
||||
continue;
|
||||
|
||||
nv = grid(nx, ny);
|
||||
is_adj = (labs(v - nv) == 1);
|
||||
|
||||
if (is_adj && !should_be_adj) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
printf("%*s(%d,%d):%d and (%d,%d):%d have "
|
||||
"adjacent values, but should not\n",
|
||||
solver_recurse_depth*4, "",
|
||||
x+1, y+1, v, nx+1, ny+1, nv);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_adj && should_be_adj) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
printf("%*s(%d,%d):%d and (%d,%d):%d do not have "
|
||||
"adjacent values, but should\n",
|
||||
solver_recurse_depth*4, "",
|
||||
x+1, y+1, v, nx+1, ny+1, nv);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; i < ctx->nlinks; i++) {
|
||||
struct solver_link *link = &ctx->links[i];
|
||||
int gv = grid(link->gx, link->gy);
|
||||
int lv = grid(link->lx, link->ly);
|
||||
if (gv <= lv) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
printf("%*s(%d,%d):%d should be greater than (%d,%d):%d, "
|
||||
"but is not\n", solver_recurse_depth*4, "",
|
||||
link->gx+1, link->gy+1, gv,
|
||||
link->lx+1, link->ly+1, lv);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int solver_state(game_state *state, int maxdiff)
|
||||
{
|
||||
struct solver_ctx *ctx = new_ctx(state);
|
||||
|
@ -827,7 +895,8 @@ static int solver_state(game_state *state, int maxdiff)
|
|||
diff = latin_solver_main(&solver, maxdiff,
|
||||
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
||||
DIFF_EXTREME, DIFF_RECURSIVE,
|
||||
unequal_solvers, ctx, clone_ctx, free_ctx);
|
||||
unequal_solvers, unequal_valid, ctx,
|
||||
clone_ctx, free_ctx);
|
||||
|
||||
memcpy(state->hints, solver.cube, state->order*state->order*state->order);
|
||||
|
||||
|
@ -2155,7 +2224,8 @@ static int solve(game_params *p, char *desc, int debug)
|
|||
diff = latin_solver_main(&solver, DIFF_RECURSIVE,
|
||||
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
||||
DIFF_EXTREME, DIFF_RECURSIVE,
|
||||
unequal_solvers, ctx, clone_ctx, free_ctx);
|
||||
unequal_solvers, unequal_valid, ctx,
|
||||
clone_ctx, free_ctx);
|
||||
|
||||
free_ctx(ctx);
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
#define DIFFLIST(A) \
|
||||
A(TRIVIAL,Trivial,NULL,t) \
|
||||
A(NORMAL,Normal,solver_normal,n) \
|
||||
A(HARD,Hard,NULL,h) \
|
||||
A(HARD,Hard,solver_hard,h) \
|
||||
A(EXTREME,Extreme,NULL,x) \
|
||||
A(UNREASONABLE,Unreasonable,NULL,u)
|
||||
#define ENUM(upper,title,func,lower) DIFF_ ## upper,
|
||||
|
@ -280,6 +280,23 @@ static const char *validate_params(const game_params *params, bool full)
|
|||
* Solver.
|
||||
*/
|
||||
|
||||
static int find_identity(struct latin_solver *solver)
|
||||
{
|
||||
int w = solver->o;
|
||||
digit *grid = solver->grid;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
for (j = 0; j < w; j++) {
|
||||
if (grid[i*w+j] == i+1)
|
||||
return j+1;
|
||||
if (grid[i*w+j] == j+1)
|
||||
return i+1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int solver_normal(struct latin_solver *solver, void *vctx)
|
||||
{
|
||||
int w = solver->o;
|
||||
|
@ -295,9 +312,9 @@ static int solver_normal(struct latin_solver *solver, void *vctx)
|
|||
* So we pick any a,b,c we like; then if we know ab, bc, and
|
||||
* (ab)c we can fill in a(bc).
|
||||
*/
|
||||
for (i = 1; i < w; i++)
|
||||
for (j = 1; j < w; j++)
|
||||
for (k = 1; k < w; k++) {
|
||||
for (i = 0; i < w; i++)
|
||||
for (j = 0; j < w; j++)
|
||||
for (k = 0; k < w; k++) {
|
||||
if (!grid[i*w+j] || !grid[j*w+k])
|
||||
continue;
|
||||
if (grid[(grid[i*w+j]-1)*w+k] &&
|
||||
|
@ -358,12 +375,206 @@ static int solver_normal(struct latin_solver *solver, void *vctx)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in the row and column for the group identity, if it's not
|
||||
* already known and if we've just found out what it is.
|
||||
*/
|
||||
i = find_identity(solver);
|
||||
if (i) {
|
||||
bool done_something = false;
|
||||
for (j = 1; j <= w; j++) {
|
||||
if (!grid[(i-1)*w+(j-1)] || !grid[(j-1)*w+(i-1)]) {
|
||||
done_something = true;
|
||||
}
|
||||
}
|
||||
if (done_something) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*s%s is the group identity\n",
|
||||
solver_recurse_depth*4, "", names[i-1]);
|
||||
}
|
||||
#endif
|
||||
for (j = 1; j <= w; j++) {
|
||||
if (!grid[(j-1)*w+(i-1)]) {
|
||||
if (!cube(i-1, j-1, j)) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*s but %s cannot go at (%d,%d) - "
|
||||
"contradiction!\n",
|
||||
solver_recurse_depth*4, "",
|
||||
names[j-1], i, j);
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*s placing %s at (%d,%d)\n",
|
||||
solver_recurse_depth*4, "",
|
||||
names[j-1], i, j);
|
||||
}
|
||||
#endif
|
||||
latin_solver_place(solver, i-1, j-1, j);
|
||||
}
|
||||
if (!grid[(i-1)*w+(j-1)]) {
|
||||
if (!cube(j-1, i-1, j)) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*s but %s cannot go at (%d,%d) - "
|
||||
"contradiction!\n",
|
||||
solver_recurse_depth*4, "",
|
||||
names[j-1], j, i);
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*s placing %s at (%d,%d)\n",
|
||||
solver_recurse_depth*4, "",
|
||||
names[j-1], j, i);
|
||||
}
|
||||
#endif
|
||||
latin_solver_place(solver, j-1, i-1, j);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int solver_hard(struct latin_solver *solver, void *vctx)
|
||||
{
|
||||
bool done_something = false;
|
||||
int w = solver->o;
|
||||
#ifdef STANDALONE_SOLVER
|
||||
char **names = solver->names;
|
||||
#endif
|
||||
int i, j;
|
||||
|
||||
/*
|
||||
* In identity-hidden mode, systematically rule out possibilities
|
||||
* for the group identity.
|
||||
*
|
||||
* In solver_normal, we used the fact that any filled square in
|
||||
* the grid whose contents _does_ match one of the elements it's
|
||||
* the product of - that is, ab=a or ab=b - tells you immediately
|
||||
* that the other element is the identity.
|
||||
*
|
||||
* Here, we use the flip side of that: any filled square in the
|
||||
* grid whose contents does _not_ match either its row or column -
|
||||
* that is, if ab is neither a nor b - tells you immediately that
|
||||
* _neither_ of those elements is the identity. And if that's
|
||||
* true, then we can also immediately rule out the possibility
|
||||
* that it acts as the identity on any element at all.
|
||||
*/
|
||||
for (i = 0; i < w; i++) {
|
||||
bool i_can_be_id = true;
|
||||
#ifdef STANDALONE_SOLVER
|
||||
char title[80];
|
||||
#endif
|
||||
|
||||
for (j = 0; j < w; j++) {
|
||||
if (grid(i,j) && grid(i,j) != j+1) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
sprintf(title, "%s cannot be the identity: "
|
||||
"%s%s = %s =/= %s", names[i], names[i], names[j],
|
||||
names[grid(i,j)-1], names[j]);
|
||||
#endif
|
||||
i_can_be_id = false;
|
||||
break;
|
||||
}
|
||||
if (grid(j,i) && grid(j,i) != j+1) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working)
|
||||
sprintf(title, "%s cannot be the identity: "
|
||||
"%s%s = %s =/= %s", names[i], names[j], names[i],
|
||||
names[grid(j,i)-1], names[j]);
|
||||
#endif
|
||||
i_can_be_id = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!i_can_be_id) {
|
||||
/* Now rule out ij=j or ji=j for all j. */
|
||||
for (j = 0; j < w; j++) {
|
||||
if (cube(i, j, j+1)) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
if (title[0]) {
|
||||
printf("%*s%s\n", solver_recurse_depth*4, "",
|
||||
title);
|
||||
title[0] = '\0';
|
||||
}
|
||||
printf("%*s ruling out %s at (%d,%d)\n",
|
||||
solver_recurse_depth*4, "", names[j], i, j);
|
||||
}
|
||||
#endif
|
||||
cube(i, j, j+1) = false;
|
||||
}
|
||||
if (cube(j, i, j+1)) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
if (title[0]) {
|
||||
printf("%*s%s\n", solver_recurse_depth*4, "",
|
||||
title);
|
||||
title[0] = '\0';
|
||||
}
|
||||
printf("%*s ruling out %s at (%d,%d)\n",
|
||||
solver_recurse_depth*4, "", names[j], j, i);
|
||||
}
|
||||
#endif
|
||||
cube(j, i, j+1) = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return done_something;
|
||||
}
|
||||
|
||||
#define SOLVER(upper,title,func,lower) func,
|
||||
static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) };
|
||||
|
||||
static bool group_valid(struct latin_solver *solver, void *ctx)
|
||||
{
|
||||
int w = solver->o;
|
||||
#ifdef STANDALONE_SOLVER
|
||||
char **names = solver->names;
|
||||
#endif
|
||||
int i, j, k;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
for (j = 0; j < w; j++)
|
||||
for (k = 0; k < w; k++) {
|
||||
int ij = grid(i, j) - 1;
|
||||
int jk = grid(j, k) - 1;
|
||||
int ij_k = grid(ij, k) - 1;
|
||||
int i_jk = grid(i, jk) - 1;
|
||||
if (ij_k != i_jk) {
|
||||
#ifdef STANDALONE_SOLVER
|
||||
if (solver_show_working) {
|
||||
printf("%*sfailure of associativity: "
|
||||
"(%s%s)%s = %s%s = %s but "
|
||||
"%s(%s%s) = %s%s = %s\n",
|
||||
solver_recurse_depth*4, "",
|
||||
names[i], names[j], names[k],
|
||||
names[ij], names[k], names[ij_k],
|
||||
names[i], names[j], names[k],
|
||||
names[i], names[jk], names[i_jk]);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int solver(const game_params *params, digit *grid, int maxdiff)
|
||||
{
|
||||
int w = params->w;
|
||||
|
@ -387,7 +598,7 @@ static int solver(const game_params *params, digit *grid, int maxdiff)
|
|||
ret = latin_solver_main(&solver, maxdiff,
|
||||
DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
|
||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||
group_solvers, NULL, NULL, NULL);
|
||||
group_solvers, group_valid, NULL, NULL, NULL);
|
||||
|
||||
latin_solver_free(&solver);
|
||||
|
||||
|
@ -2183,6 +2394,11 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
if (diff == DIFFCOUNT) {
|
||||
if (really_show_working) {
|
||||
solver_show_working = true;
|
||||
memcpy(grid, s->grid, p->w * p->w);
|
||||
ret = solver(&s->par, grid, DIFFCOUNT - 1);
|
||||
}
|
||||
if (grade)
|
||||
printf("Difficulty rating: ambiguous\n");
|
||||
else
|
||||
|
|
|
@ -68,6 +68,103 @@
|
|||
* 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.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue