forked from len0rd/rockbox
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...
|
there's still bugs to fix, so stay tuned...
|
||||||
|
|
||||||
December 2018: Resync to 3ece3d6. Happy holidays!
|
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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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 */
|
/* help text is compressed using LZ4; see compress.c for details */
|
||||||
/* DO NOT EDIT! */
|
/* 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
|
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||||
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
|
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
|
||||||
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael
|
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
|
||||||
Quevillon.
|
Quevillon and Asher Gordon.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation files
|
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;
|
*tilesize = FLORET_TILESIZE;
|
||||||
*xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
|
*xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
|
||||||
*yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry;
|
*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)
|
static grid *grid_new_floret(int width, int height, const char *desc)
|
||||||
|
|
|
@ -44,6 +44,17 @@
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined USE_CAIRO && GTK_CHECK_VERSION(2,10,0)
|
||||||
|
/* We can only use printing if we are using Cairo for drawing and we
|
||||||
|
have a GTK version >= 2.10 (when GtkPrintOperation was added). */
|
||||||
|
# define USE_PRINTING
|
||||||
|
# if GTK_CHECK_VERSION(2,18,0)
|
||||||
|
/* We can embed the page setup. Before 2.18, we needed to have a
|
||||||
|
separate page setup. */
|
||||||
|
# define USE_EMBED_PAGE_SETUP
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if GTK_CHECK_VERSION(3,0,0)
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
/* The old names are still more concise! */
|
/* The old names are still more concise! */
|
||||||
#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
|
#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
|
||||||
|
@ -131,6 +142,18 @@ struct font {
|
||||||
int size;
|
int size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An internal API for functions which need to be different for
|
||||||
|
* printing and drawing.
|
||||||
|
*/
|
||||||
|
struct internal_drawing_api {
|
||||||
|
void (*set_colour)(frontend *fe, int colour);
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
void (*fill)(frontend *fe);
|
||||||
|
void (*fill_preserve)(frontend *fe);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This structure holds all the data relevant to a single window.
|
* This structure holds all the data relevant to a single window.
|
||||||
* In principle this would allow us to open multiple independent
|
* In principle this would allow us to open multiple independent
|
||||||
|
@ -180,7 +203,7 @@ struct frontend {
|
||||||
GtkWidget *cfgbox;
|
GtkWidget *cfgbox;
|
||||||
void *paste_data;
|
void *paste_data;
|
||||||
int paste_data_len;
|
int paste_data_len;
|
||||||
int pw, ph; /* pixmap size (w, h are area size) */
|
int pw, ph, ps; /* pixmap size (w, h are area size, s is GDK scale) */
|
||||||
int ox, oy; /* offset of pixmap in drawing area */
|
int ox, oy; /* offset of pixmap in drawing area */
|
||||||
#ifdef OLD_FILESEL
|
#ifdef OLD_FILESEL
|
||||||
char *filesel_name;
|
char *filesel_name;
|
||||||
|
@ -225,6 +248,23 @@ struct frontend {
|
||||||
*/
|
*/
|
||||||
bool awaiting_resize_ack;
|
bool awaiting_resize_ack;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
int printcount, printw, printh;
|
||||||
|
float printscale;
|
||||||
|
bool printsolns, printcolour;
|
||||||
|
int hatch;
|
||||||
|
float hatchthick, hatchspace;
|
||||||
|
drawing *print_dr;
|
||||||
|
document *doc;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
GtkPrintOperation *printop;
|
||||||
|
GtkPrintContext *printcontext;
|
||||||
|
GtkSpinButton *printcount_spin_button, *printw_spin_button,
|
||||||
|
*printh_spin_button, *printscale_spin_button;
|
||||||
|
GtkCheckButton *soln_check_button, *colour_check_button;
|
||||||
|
#endif
|
||||||
|
const struct internal_drawing_api *dr_api;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct blitter {
|
struct blitter {
|
||||||
|
@ -247,15 +287,19 @@ void get_random_seed(void **randseed, int *randseedsize)
|
||||||
void frontend_default_colour(frontend *fe, float *output)
|
void frontend_default_colour(frontend *fe, float *output)
|
||||||
{
|
{
|
||||||
#if !GTK_CHECK_VERSION(3,0,0)
|
#if !GTK_CHECK_VERSION(3,0,0)
|
||||||
|
if (!fe->headless) {
|
||||||
/*
|
/*
|
||||||
* Use the widget style's default background colour as the
|
* If we have a widget and it has a style that specifies a
|
||||||
* background for the puzzle drawing area.
|
* default background colour, use that as the background for
|
||||||
|
* the puzzle drawing area.
|
||||||
*/
|
*/
|
||||||
GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL];
|
GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL];
|
||||||
output[0] = col.red / 65535.0;
|
output[0] = col.red / 65535.0;
|
||||||
output[1] = col.green / 65535.0;
|
output[1] = col.green / 65535.0;
|
||||||
output[2] = col.blue / 65535.0;
|
output[2] = col.blue / 65535.0;
|
||||||
#else
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GTK 3 has decided that there's no such thing as a 'default
|
* GTK 3 has decided that there's no such thing as a 'default
|
||||||
* background colour' any more, because widget styles might set
|
* background colour' any more, because widget styles might set
|
||||||
|
@ -263,9 +307,11 @@ void frontend_default_colour(frontend *fe, float *output)
|
||||||
* image. We don't want to get into overlaying our entire puzzle
|
* image. We don't want to get into overlaying our entire puzzle
|
||||||
* on an arbitrary background image, so we'll just make up a
|
* on an arbitrary background image, so we'll just make up a
|
||||||
* reasonable shade of grey.
|
* reasonable shade of grey.
|
||||||
|
*
|
||||||
|
* This is also what we do on GTK 2 in headless mode, where we
|
||||||
|
* don't have a widget style to query.
|
||||||
*/
|
*/
|
||||||
output[0] = output[1] = output[2] = 0.9F;
|
output[0] = output[1] = output[2] = 0.9F;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_status_bar(void *handle, const char *text)
|
void gtk_status_bar(void *handle, const char *text)
|
||||||
|
@ -290,6 +336,7 @@ void gtk_status_bar(void *handle, const char *text)
|
||||||
static void setup_drawing(frontend *fe)
|
static void setup_drawing(frontend *fe)
|
||||||
{
|
{
|
||||||
fe->cr = cairo_create(fe->image);
|
fe->cr = cairo_create(fe->image);
|
||||||
|
cairo_scale(fe->cr, fe->ps, fe->ps);
|
||||||
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
|
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
|
||||||
cairo_set_line_width(fe->cr, 1.0);
|
cairo_set_line_width(fe->cr, 1.0);
|
||||||
cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
|
cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
|
||||||
|
@ -321,7 +368,7 @@ static void snaffle_colours(frontend *fe)
|
||||||
fe->colours = midend_colours(fe->me, &fe->ncolours);
|
fe->colours = midend_colours(fe->me, &fe->ncolours);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_colour(frontend *fe, int colour)
|
static void draw_set_colour(frontend *fe, int colour)
|
||||||
{
|
{
|
||||||
cairo_set_source_rgb(fe->cr,
|
cairo_set_source_rgb(fe->cr,
|
||||||
fe->colours[3*colour + 0],
|
fe->colours[3*colour + 0],
|
||||||
|
@ -329,6 +376,17 @@ static void set_colour(frontend *fe, int colour)
|
||||||
fe->colours[3*colour + 2]);
|
fe->colours[3*colour + 2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void print_set_colour(frontend *fe, int colour)
|
||||||
|
{
|
||||||
|
float r, g, b;
|
||||||
|
|
||||||
|
print_get_colour(fe->print_dr, colour, fe->printcolour,
|
||||||
|
&(fe->hatch), &r, &g, &b);
|
||||||
|
|
||||||
|
if (fe->hatch < 0)
|
||||||
|
cairo_set_source_rgb(fe->cr, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
static void set_window_background(frontend *fe, int colour)
|
static void set_window_background(frontend *fe, int colour)
|
||||||
{
|
{
|
||||||
#if GTK_CHECK_VERSION(3,20,0)
|
#if GTK_CHECK_VERSION(3,20,0)
|
||||||
|
@ -395,6 +453,82 @@ static void save_screenshot_png(frontend *fe, const char *screenshot_file)
|
||||||
cairo_surface_write_to_png(fe->image, screenshot_file);
|
cairo_surface_write_to_png(fe->image, screenshot_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void do_hatch(frontend *fe)
|
||||||
|
{
|
||||||
|
double i, x, y, width, height, maxdim;
|
||||||
|
|
||||||
|
/* Get the dimensions of the region to be hatched. */
|
||||||
|
cairo_path_extents(fe->cr, &x, &y, &width, &height);
|
||||||
|
|
||||||
|
maxdim = max(width, height);
|
||||||
|
|
||||||
|
cairo_save(fe->cr);
|
||||||
|
|
||||||
|
/* Set the line color and width. */
|
||||||
|
cairo_set_source_rgb(fe->cr, 0, 0, 0);
|
||||||
|
cairo_set_line_width(fe->cr, fe->hatchthick);
|
||||||
|
/* Clip to the region. */
|
||||||
|
cairo_clip(fe->cr);
|
||||||
|
/* Hatch the bounding area of the fill region. */
|
||||||
|
if (fe->hatch == HATCH_VERT || fe->hatch == HATCH_PLUS) {
|
||||||
|
for (i = 0.0; i <= width; i += fe->hatchspace) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, 0, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_HORIZ || fe->hatch == HATCH_PLUS) {
|
||||||
|
for (i = 0.0; i <= height; i += fe->hatchspace) {
|
||||||
|
cairo_move_to(fe->cr, 0, i);
|
||||||
|
cairo_rel_line_to(fe->cr, width, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_SLASH || fe->hatch == HATCH_X) {
|
||||||
|
for (i = -height; i <= width; i += fe->hatchspace * ROOT2) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, maxdim, maxdim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_BACKSLASH || fe->hatch == HATCH_X) {
|
||||||
|
for (i = 0.0; i <= width + height; i += fe->hatchspace * ROOT2) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, -maxdim, maxdim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cairo_stroke(fe->cr);
|
||||||
|
|
||||||
|
cairo_restore(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_draw_fill(frontend *fe)
|
||||||
|
{
|
||||||
|
cairo_fill(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_draw_fill_preserve(frontend *fe)
|
||||||
|
{
|
||||||
|
cairo_fill_preserve(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_print_fill(frontend *fe)
|
||||||
|
{
|
||||||
|
if (fe->hatch < 0)
|
||||||
|
cairo_fill(fe->cr);
|
||||||
|
else
|
||||||
|
do_hatch(fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_print_fill_preserve(frontend *fe)
|
||||||
|
{
|
||||||
|
if (fe->hatch < 0) {
|
||||||
|
cairo_fill_preserve(fe->cr);
|
||||||
|
} else {
|
||||||
|
cairo_path_t *oldpath;
|
||||||
|
oldpath = cairo_copy_path(fe->cr);
|
||||||
|
do_hatch(fe);
|
||||||
|
cairo_append_path(fe->cr, oldpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void do_clip(frontend *fe, int x, int y, int w, int h)
|
static void do_clip(frontend *fe, int x, int y, int w, int h)
|
||||||
{
|
{
|
||||||
cairo_new_path(fe->cr);
|
cairo_new_path(fe->cr);
|
||||||
|
@ -413,7 +547,7 @@ static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
|
||||||
cairo_new_path(fe->cr);
|
cairo_new_path(fe->cr);
|
||||||
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
|
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
|
||||||
cairo_rectangle(fe->cr, x, y, w, h);
|
cairo_rectangle(fe->cr, x, y, w, h);
|
||||||
cairo_fill(fe->cr);
|
fe->dr_api->fill(fe);
|
||||||
cairo_restore(fe->cr);
|
cairo_restore(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,11 +581,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
|
||||||
cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
|
cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
|
||||||
cairo_close_path(fe->cr);
|
cairo_close_path(fe->cr);
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
cairo_fill_preserve(fe->cr);
|
fe->dr_api->fill_preserve(fe);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
cairo_stroke(fe->cr);
|
cairo_stroke(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,11 +596,11 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
|
||||||
cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
|
cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
|
||||||
cairo_close_path(fe->cr); /* Just in case... */
|
cairo_close_path(fe->cr); /* Just in case... */
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
cairo_fill_preserve(fe->cr);
|
fe->dr_api->fill_preserve(fe);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
cairo_stroke(fe->cr);
|
cairo_stroke(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,23 +646,24 @@ static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr,
|
||||||
static void setup_backing_store(frontend *fe)
|
static void setup_backing_store(frontend *fe)
|
||||||
{
|
{
|
||||||
#ifndef USE_CAIRO_WITHOUT_PIXMAP
|
#ifndef USE_CAIRO_WITHOUT_PIXMAP
|
||||||
if (fe->headless) {
|
if (!fe->headless) {
|
||||||
fprintf(stderr, "headless mode does not work with GDK pixmaps\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
|
fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
|
||||||
fe->pw, fe->ph, -1);
|
fe->pw*fe->ps, fe->ph*fe->ps, -1);
|
||||||
|
} else {
|
||||||
|
fe->pixmap = NULL;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
|
fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
|
||||||
fe->pw, fe->ph);
|
fe->pw*fe->ps, fe->ph*fe->ps);
|
||||||
|
|
||||||
wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true);
|
wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true);
|
||||||
#ifndef USE_CAIRO_WITHOUT_PIXMAP
|
#ifndef USE_CAIRO_WITHOUT_PIXMAP
|
||||||
|
if (!fe->headless)
|
||||||
wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
|
wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
|
||||||
#endif
|
#endif
|
||||||
#if GTK_CHECK_VERSION(3,22,0)
|
|
||||||
if (!fe->headless) {
|
if (!fe->headless) {
|
||||||
|
#if GTK_CHECK_VERSION(3,22,0)
|
||||||
GdkWindow *gdkwin;
|
GdkWindow *gdkwin;
|
||||||
cairo_region_t *region;
|
cairo_region_t *region;
|
||||||
GdkDrawingContext *drawctx;
|
GdkDrawingContext *drawctx;
|
||||||
|
@ -541,11 +676,11 @@ static void setup_backing_store(frontend *fe)
|
||||||
wipe_and_maybe_destroy_cairo(fe, cr, false);
|
wipe_and_maybe_destroy_cairo(fe, cr, false);
|
||||||
gdk_window_end_draw_frame(gdkwin, drawctx);
|
gdk_window_end_draw_frame(gdkwin, drawctx);
|
||||||
cairo_region_destroy(region);
|
cairo_region_destroy(region);
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
wipe_and_maybe_destroy_cairo(
|
wipe_and_maybe_destroy_cairo(
|
||||||
fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true);
|
fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool backing_store_ok(frontend *fe)
|
static bool backing_store_ok(frontend *fe)
|
||||||
|
@ -616,7 +751,7 @@ static void set_window_background(frontend *fe, int colour)
|
||||||
gdk_window_set_background(fe->window->window, &fe->colours[colour]);
|
gdk_window_set_background(fe->window->window, &fe->colours[colour]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_colour(frontend *fe, int colour)
|
static void draw_set_colour(frontend *fe, int colour)
|
||||||
{
|
{
|
||||||
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
|
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
|
||||||
}
|
}
|
||||||
|
@ -709,11 +844,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
|
gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In principle we ought to be able to use gdk_draw_polygon for
|
* In principle we ought to be able to use gdk_draw_polygon for
|
||||||
|
@ -733,14 +868,14 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
|
||||||
int fillcolour, int outlinecolour)
|
int fillcolour, int outlinecolour)
|
||||||
{
|
{
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
gdk_draw_arc(fe->pixmap, fe->gc, true,
|
gdk_draw_arc(fe->pixmap, fe->gc, true,
|
||||||
cx - radius, cy - radius,
|
cx - radius, cy - radius,
|
||||||
2 * radius, 2 * radius, 0, 360 * 64);
|
2 * radius, 2 * radius, 0, 360 * 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
gdk_draw_arc(fe->pixmap, fe->gc, false,
|
gdk_draw_arc(fe->pixmap, fe->gc, false,
|
||||||
cx - radius, cy - radius,
|
cx - radius, cy - radius,
|
||||||
2 * radius, 2 * radius, 0, 360 * 64);
|
2 * radius, 2 * radius, 0, 360 * 64);
|
||||||
|
@ -1045,21 +1180,21 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
|
||||||
/*
|
/*
|
||||||
* Do the job.
|
* Do the job.
|
||||||
*/
|
*/
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
align_and_draw_text(fe, i, align, x, y, text);
|
align_and_draw_text(fe, i, align, x, y, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
|
void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)handle;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_rect(fe, x, y, w, h);
|
do_draw_rect(fe, x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
|
void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)handle;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_line(fe, x1, y1, x2, y2);
|
do_draw_line(fe, x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,7 +1202,7 @@ void gtk_draw_thick_line(void *handle, float thickness,
|
||||||
float x1, float y1, float x2, float y2, int colour)
|
float x1, float y1, float x2, float y2, int colour)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)handle;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
|
do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1161,6 +1296,105 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
void gtk_begin_doc(void *handle, int pages)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
gtk_print_operation_set_n_pages(fe->printop, pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_begin_page(void *handle, int number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_begin_puzzle(void *handle, float xm, float xc,
|
||||||
|
float ym, float yc, int pw, int ph, float wmm)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
double ppw, pph, pox, poy, dpmmx, dpmmy;
|
||||||
|
double scale;
|
||||||
|
|
||||||
|
ppw = gtk_print_context_get_width(fe->printcontext);
|
||||||
|
pph = gtk_print_context_get_height(fe->printcontext);
|
||||||
|
dpmmx = gtk_print_context_get_dpi_x(fe->printcontext) / 25.4;
|
||||||
|
dpmmy = gtk_print_context_get_dpi_y(fe->printcontext) / 25.4;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the puzzle's position in pixels on the logical page.
|
||||||
|
*/
|
||||||
|
pox = xm * ppw + xc * dpmmx;
|
||||||
|
poy = ym * pph + yc * dpmmy;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And determine the scale.
|
||||||
|
*
|
||||||
|
* I need a scale such that the maximum puzzle-coordinate
|
||||||
|
* extent of the rectangle (pw * scale) is equal to the pixel
|
||||||
|
* equivalent of the puzzle's millimetre width (wmm * dpmmx).
|
||||||
|
*/
|
||||||
|
scale = wmm * dpmmx / pw;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now instruct Cairo to transform points based on our calculated
|
||||||
|
* values (order here *is* important).
|
||||||
|
*/
|
||||||
|
cairo_save(fe->cr);
|
||||||
|
cairo_translate(fe->cr, pox, poy);
|
||||||
|
cairo_scale(fe->cr, scale, scale);
|
||||||
|
|
||||||
|
fe->hatchthick = 0.2 * pw / wmm;
|
||||||
|
fe->hatchspace = 1.0 * pw / wmm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_puzzle(void *handle)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
cairo_restore(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_page(void *handle, int number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_doc(void *handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_line_width(void *handle, float width)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
cairo_set_line_width(fe->cr, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_line_dotted(void *handle, bool dotted)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
|
||||||
|
if (dotted) {
|
||||||
|
const double dash = 35.0;
|
||||||
|
cairo_set_dash(fe->cr, &dash, 1, 0);
|
||||||
|
} else {
|
||||||
|
cairo_set_dash(fe->cr, NULL, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* USE_PRINTING */
|
||||||
|
|
||||||
|
const struct internal_drawing_api internal_drawing = {
|
||||||
|
draw_set_colour,
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
do_draw_fill,
|
||||||
|
do_draw_fill_preserve,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
const struct internal_drawing_api internal_printing = {
|
||||||
|
print_set_colour,
|
||||||
|
do_print_fill,
|
||||||
|
do_print_fill_preserve,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
const struct drawing_api gtk_drawing = {
|
const struct drawing_api gtk_drawing = {
|
||||||
gtk_draw_text,
|
gtk_draw_text,
|
||||||
gtk_draw_rect,
|
gtk_draw_rect,
|
||||||
|
@ -1177,8 +1411,19 @@ const struct drawing_api gtk_drawing = {
|
||||||
gtk_blitter_free,
|
gtk_blitter_free,
|
||||||
gtk_blitter_save,
|
gtk_blitter_save,
|
||||||
gtk_blitter_load,
|
gtk_blitter_load,
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
gtk_begin_doc,
|
||||||
|
gtk_begin_page,
|
||||||
|
gtk_begin_puzzle,
|
||||||
|
gtk_end_puzzle,
|
||||||
|
gtk_end_page,
|
||||||
|
gtk_end_doc,
|
||||||
|
gtk_line_width,
|
||||||
|
gtk_line_dotted,
|
||||||
|
#else
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
|
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
|
||||||
NULL, NULL, /* line_width, line_dotted */
|
NULL, NULL, /* line_width, line_dotted */
|
||||||
|
#endif
|
||||||
#ifdef USE_PANGO
|
#ifdef USE_PANGO
|
||||||
gtk_text_fallback,
|
gtk_text_fallback,
|
||||||
#else
|
#else
|
||||||
|
@ -1340,12 +1585,22 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
GdkRectangle dirtyrect;
|
GdkRectangle dirtyrect;
|
||||||
|
|
||||||
|
cairo_surface_t *target_surface = cairo_get_target(cr);
|
||||||
|
cairo_matrix_t m;
|
||||||
|
cairo_get_matrix(cr, &m);
|
||||||
|
double orig_sx, orig_sy;
|
||||||
|
cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
|
||||||
|
cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
|
||||||
|
cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
|
||||||
|
|
||||||
gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
|
gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
|
||||||
cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
|
cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
|
||||||
cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
|
cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
|
||||||
dirtyrect.width, dirtyrect.height);
|
dirtyrect.width, dirtyrect.height);
|
||||||
cairo_fill(cr);
|
cairo_fill(cr);
|
||||||
|
|
||||||
|
cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -1390,16 +1645,22 @@ static gint map_window(GtkWidget *widget, GdkEvent *event,
|
||||||
static void resize_puzzle_to_area(frontend *fe, int x, int y)
|
static void resize_puzzle_to_area(frontend *fe, int x, int y)
|
||||||
{
|
{
|
||||||
int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
|
int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
|
||||||
|
int oldps = fe->ps;
|
||||||
|
|
||||||
fe->w = x;
|
fe->w = x;
|
||||||
fe->h = y;
|
fe->h = y;
|
||||||
midend_size(fe->me, &x, &y, true);
|
midend_size(fe->me, &x, &y, true);
|
||||||
fe->pw = x;
|
fe->pw = x;
|
||||||
fe->ph = y;
|
fe->ph = y;
|
||||||
|
#if GTK_CHECK_VERSION(3,10,0)
|
||||||
|
fe->ps = gtk_widget_get_scale_factor(fe->area);
|
||||||
|
#else
|
||||||
|
fe->ps = 1;
|
||||||
|
#endif
|
||||||
fe->ox = (fe->w - fe->pw) / 2;
|
fe->ox = (fe->w - fe->pw) / 2;
|
||||||
fe->oy = (fe->h - fe->ph) / 2;
|
fe->oy = (fe->h - fe->ph) / 2;
|
||||||
|
|
||||||
if (oldw != fe->w || oldpw != fe->pw ||
|
if (oldw != fe->w || oldpw != fe->pw || oldps != fe->ps ||
|
||||||
oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
|
oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
|
||||||
if (backing_store_ok(fe))
|
if (backing_store_ok(fe))
|
||||||
teardown_backing_store(fe);
|
teardown_backing_store(fe);
|
||||||
|
@ -1413,6 +1674,7 @@ static gint configure_area(GtkWidget *widget,
|
||||||
GdkEventConfigure *event, gpointer data)
|
GdkEventConfigure *event, gpointer data)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
resize_puzzle_to_area(fe, event->width, event->height);
|
resize_puzzle_to_area(fe, event->width, event->height);
|
||||||
#if GTK_CHECK_VERSION(3,0,0)
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
fe->awaiting_resize_ack = false;
|
fe->awaiting_resize_ack = false;
|
||||||
|
@ -2245,6 +2507,317 @@ static char *file_selector(frontend *fe, const char *title, bool save)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
|
||||||
|
{
|
||||||
|
GtkLabel *count_label, *width_label, *height_label,
|
||||||
|
*scale_llabel, *scale_rlabel;
|
||||||
|
GtkBox *scale_hbox;
|
||||||
|
GtkWidget *grid;
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
fe->printcount_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 999, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printcount_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printcount_spin_button, true);
|
||||||
|
fe->printw_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printw_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printw_spin_button, true);
|
||||||
|
fe->printh_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printh_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printh_spin_button, true);
|
||||||
|
fe->printscale_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 1000, 1));
|
||||||
|
gtk_spin_button_set_digits(fe->printscale_spin_button, 1);
|
||||||
|
gtk_spin_button_set_numeric(fe->printscale_spin_button, true);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
fe->soln_check_button =
|
||||||
|
GTK_CHECK_BUTTON(
|
||||||
|
gtk_check_button_new_with_label("Print solutions"));
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
fe->colour_check_button =
|
||||||
|
GTK_CHECK_BUTTON(
|
||||||
|
gtk_check_button_new_with_label("Print in color"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set defaults to what was selected last time. */
|
||||||
|
gtk_spin_button_set_value(fe->printcount_spin_button,
|
||||||
|
(gdouble)fe->printcount);
|
||||||
|
gtk_spin_button_set_value(fe->printw_spin_button,
|
||||||
|
(gdouble)fe->printw);
|
||||||
|
gtk_spin_button_set_value(fe->printh_spin_button,
|
||||||
|
(gdouble)fe->printh);
|
||||||
|
gtk_spin_button_set_value(fe->printscale_spin_button,
|
||||||
|
(gdouble)fe->printscale);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_toggle_button_set_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->soln_check_button), fe->printsolns);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_toggle_button_set_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->colour_check_button), fe->printcolour);
|
||||||
|
}
|
||||||
|
|
||||||
|
count_label = GTK_LABEL(gtk_label_new("Puzzles to print:"));
|
||||||
|
width_label = GTK_LABEL(gtk_label_new("Puzzles across:"));
|
||||||
|
height_label = GTK_LABEL(gtk_label_new("Puzzles down:"));
|
||||||
|
scale_llabel = GTK_LABEL(gtk_label_new("Puzzle scale:"));
|
||||||
|
scale_rlabel = GTK_LABEL(gtk_label_new("%"));
|
||||||
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(width_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(height_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(scale_llabel), GTK_ALIGN_START);
|
||||||
|
#else
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(count_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(width_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(height_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(scale_llabel), 0, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
scale_hbox = GTK_BOX(gtk_hbox_new(false, 6));
|
||||||
|
gtk_box_pack_start(scale_hbox, GTK_WIDGET(fe->printscale_spin_button),
|
||||||
|
false, false, 0);
|
||||||
|
gtk_box_pack_start(scale_hbox, GTK_WIDGET(scale_rlabel),
|
||||||
|
false, false, 0);
|
||||||
|
|
||||||
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
|
grid = gtk_grid_new();
|
||||||
|
gtk_grid_set_column_spacing(GTK_GRID(grid), 18);
|
||||||
|
gtk_grid_set_row_spacing(GTK_GRID(grid), 18);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(count_label), 0, 0, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(width_label), 0, 1, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(height_label), 0, 2, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_llabel), 0, 3, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printcount_spin_button),
|
||||||
|
1, 0, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printw_spin_button),
|
||||||
|
1, 1, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printh_spin_button),
|
||||||
|
1, 2, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_hbox), 1, 3, 1, 1);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->soln_check_button),
|
||||||
|
0, 4, 1, 1);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->colour_check_button),
|
||||||
|
thegame.can_solve, 4, 1, 1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
grid = gtk_table_new((thegame.can_solve || thegame.can_print_in_colour) ?
|
||||||
|
5 : 4, 2, false);
|
||||||
|
gtk_table_set_col_spacings(GTK_TABLE(grid), 18);
|
||||||
|
gtk_table_set_row_spacings(GTK_TABLE(grid), 18);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(count_label), 0, 1, 0, 1,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(width_label), 0, 1, 1, 2,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(height_label), 0, 1, 2, 3,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_llabel), 0, 1, 3, 4,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printcount_spin_button),
|
||||||
|
1, 2, 0, 1,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printw_spin_button),
|
||||||
|
1, 2, 1, 2,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printh_spin_button),
|
||||||
|
1, 2, 2, 3,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_hbox), 1, 2, 3, 4,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->soln_check_button),
|
||||||
|
0, 1, 4, 5,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->colour_check_button),
|
||||||
|
thegame.can_solve, thegame.can_solve + 1, 4, 5,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
|
||||||
|
|
||||||
|
gtk_widget_show_all(grid);
|
||||||
|
|
||||||
|
return G_OBJECT(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_print_widget(GtkPrintOperation *print,
|
||||||
|
GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
/* We ignore `widget' because it is easier and faster to store the
|
||||||
|
widgets we need in `fe' then to get the children of `widget'. */
|
||||||
|
fe->printcount =
|
||||||
|
gtk_spin_button_get_value_as_int(fe->printcount_spin_button);
|
||||||
|
fe->printw = gtk_spin_button_get_value_as_int(fe->printw_spin_button);
|
||||||
|
fe->printh = gtk_spin_button_get_value_as_int(fe->printh_spin_button);
|
||||||
|
fe->printscale = gtk_spin_button_get_value(fe->printscale_spin_button);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
fe->printsolns =
|
||||||
|
gtk_toggle_button_get_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->soln_check_button));
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
fe->printcolour =
|
||||||
|
gtk_toggle_button_get_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->colour_check_button));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_begin(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
|
||||||
|
int i;
|
||||||
|
|
||||||
|
fe->printcontext = context;
|
||||||
|
fe->cr = gtk_print_context_get_cairo_context(context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create our document structure and fill it up with puzzles.
|
||||||
|
*/
|
||||||
|
fe->doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
|
||||||
|
|
||||||
|
for (i = 0; i < fe->printcount; i++) {
|
||||||
|
const char *err;
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
err = midend_print_puzzle(fe->me, fe->doc, fe->printsolns);
|
||||||
|
} else {
|
||||||
|
if (!nme) {
|
||||||
|
game_params *params;
|
||||||
|
|
||||||
|
nme = midend_new(NULL, &thegame, NULL, NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the non-interactive mid-end to have the same
|
||||||
|
* parameters as the standard one.
|
||||||
|
*/
|
||||||
|
params = midend_get_params(fe->me);
|
||||||
|
midend_set_params(nme, params);
|
||||||
|
thegame.free_params(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
midend_new_game(nme);
|
||||||
|
err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
error_box(fe->window, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nme)
|
||||||
|
midend_free(nme);
|
||||||
|
|
||||||
|
/* Begin the document. */
|
||||||
|
document_begin(fe->doc, fe->print_dr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_page(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context,
|
||||||
|
gint page_nr, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
document_print_page(fe->doc, fe->print_dr, page_nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_end(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
/* End and free the document. */
|
||||||
|
document_end(fe->doc, fe->print_dr);
|
||||||
|
document_free(fe->doc);
|
||||||
|
fe->doc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_dialog(frontend *fe)
|
||||||
|
{
|
||||||
|
GError *error;
|
||||||
|
static GtkPrintSettings *settings = NULL;
|
||||||
|
static GtkPageSetup *page_setup = NULL;
|
||||||
|
#ifndef USE_EMBED_PAGE_SETUP
|
||||||
|
GtkPageSetup *new_page_setup;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fe->printop = gtk_print_operation_new();
|
||||||
|
gtk_print_operation_set_use_full_page(fe->printop, true);
|
||||||
|
gtk_print_operation_set_custom_tab_label(fe->printop, "Puzzle Settings");
|
||||||
|
g_signal_connect(fe->printop, "create-custom-widget",
|
||||||
|
G_CALLBACK(create_print_widget), fe);
|
||||||
|
g_signal_connect(fe->printop, "custom-widget-apply",
|
||||||
|
G_CALLBACK(apply_print_widget), fe);
|
||||||
|
g_signal_connect(fe->printop, "begin-print", G_CALLBACK(print_begin), fe);
|
||||||
|
g_signal_connect(fe->printop, "draw-page", G_CALLBACK(draw_page), fe);
|
||||||
|
g_signal_connect(fe->printop, "end-print", G_CALLBACK(print_end), fe);
|
||||||
|
#ifdef USE_EMBED_PAGE_SETUP
|
||||||
|
gtk_print_operation_set_embed_page_setup(fe->printop, true);
|
||||||
|
#else
|
||||||
|
if (page_setup == NULL) {
|
||||||
|
page_setup =
|
||||||
|
g_object_ref(
|
||||||
|
gtk_print_operation_get_default_page_setup(fe->printop));
|
||||||
|
}
|
||||||
|
if (settings == NULL) {
|
||||||
|
settings =
|
||||||
|
g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
|
||||||
|
}
|
||||||
|
new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(fe->window),
|
||||||
|
page_setup, settings);
|
||||||
|
g_object_unref(page_setup);
|
||||||
|
page_setup = new_page_setup;
|
||||||
|
gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (settings != NULL)
|
||||||
|
gtk_print_operation_set_print_settings(fe->printop, settings);
|
||||||
|
if (page_setup != NULL)
|
||||||
|
gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
|
||||||
|
|
||||||
|
switch (gtk_print_operation_run(fe->printop,
|
||||||
|
GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
|
||||||
|
GTK_WINDOW(fe->window), &error)) {
|
||||||
|
case GTK_PRINT_OPERATION_RESULT_ERROR:
|
||||||
|
error_box(fe->window, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
break;
|
||||||
|
case GTK_PRINT_OPERATION_RESULT_APPLY:
|
||||||
|
if (settings != NULL)
|
||||||
|
g_object_unref(settings);
|
||||||
|
settings =
|
||||||
|
g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
|
||||||
|
#ifdef USE_EMBED_PAGE_SETUP
|
||||||
|
if (page_setup != NULL)
|
||||||
|
g_object_unref(page_setup);
|
||||||
|
page_setup =
|
||||||
|
g_object_ref(
|
||||||
|
gtk_print_operation_get_default_page_setup(fe->printop));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Don't error out on -Werror=switch. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(fe->printop);
|
||||||
|
fe->printop = NULL;
|
||||||
|
fe->printcontext = NULL;
|
||||||
|
}
|
||||||
|
#endif /* USE_PRINTING */
|
||||||
|
|
||||||
struct savefile_write_ctx {
|
struct savefile_write_ctx {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
int error;
|
int error;
|
||||||
|
@ -2346,6 +2919,15 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
print_dialog(fe);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
|
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
|
@ -2388,18 +2970,31 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
#if GTK_CHECK_VERSION(3,0,0)
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
|
# define ABOUT_PARAMS \
|
||||||
|
"program-name", thegame.name, \
|
||||||
|
"version", ver, \
|
||||||
|
"comments", "Part of Simon Tatham's Portable Puzzle Collection"
|
||||||
|
|
||||||
extern char *const *const xpm_icons[];
|
extern char *const *const xpm_icons[];
|
||||||
extern const int n_xpm_icons;
|
extern const int n_xpm_icons;
|
||||||
|
|
||||||
|
if (n_xpm_icons) {
|
||||||
GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
|
GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
|
||||||
((const gchar **)xpm_icons[n_xpm_icons-1]);
|
((const gchar **)xpm_icons[n_xpm_icons-1]);
|
||||||
|
|
||||||
gtk_show_about_dialog
|
gtk_show_about_dialog
|
||||||
(GTK_WINDOW(fe->window),
|
(GTK_WINDOW(fe->window),
|
||||||
"program-name", thegame.name,
|
ABOUT_PARAMS,
|
||||||
"version", ver,
|
|
||||||
"comments", "Part of Simon Tatham's Portable Puzzle Collection",
|
|
||||||
"logo", icon,
|
"logo", icon,
|
||||||
(const gchar *)NULL);
|
(const gchar *)NULL);
|
||||||
g_object_unref(G_OBJECT(icon));
|
g_object_unref(G_OBJECT(icon));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gtk_show_about_dialog
|
||||||
|
(GTK_WINDOW(fe->window),
|
||||||
|
ABOUT_PARAMS,
|
||||||
|
(const gchar *)NULL);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
char titlebuf[256];
|
char titlebuf[256];
|
||||||
char textbuf[1024];
|
char textbuf[1024];
|
||||||
|
@ -2489,6 +3084,9 @@ static frontend *new_window(
|
||||||
char *arg, int argtype, char **error, bool headless)
|
char *arg, int argtype, char **error, bool headless)
|
||||||
{
|
{
|
||||||
frontend *fe;
|
frontend *fe;
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
frontend *print_fe = NULL;
|
||||||
|
#endif
|
||||||
GtkBox *vbox, *hbox;
|
GtkBox *vbox, *hbox;
|
||||||
GtkWidget *menu, *menuitem;
|
GtkWidget *menu, *menuitem;
|
||||||
GList *iconlist;
|
GList *iconlist;
|
||||||
|
@ -2501,13 +3099,14 @@ static frontend *new_window(
|
||||||
fe = snew(frontend);
|
fe = snew(frontend);
|
||||||
memset(fe, 0, sizeof(frontend));
|
memset(fe, 0, sizeof(frontend));
|
||||||
|
|
||||||
#if !GTK_CHECK_VERSION(3,0,0)
|
#ifndef USE_CAIRO
|
||||||
if (headless) {
|
if (headless) {
|
||||||
fprintf(stderr, "headless mode not supported below GTK 3\n");
|
fprintf(stderr, "headless mode not supported for non-Cairo drawing\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
fe->headless = headless;
|
fe->headless = headless;
|
||||||
|
fe->ps = 1; /* in headless mode, configure_area won't have set this */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fe->timer_active = false;
|
fe->timer_active = false;
|
||||||
|
@ -2515,6 +3114,32 @@ static frontend *new_window(
|
||||||
|
|
||||||
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
||||||
|
|
||||||
|
fe->dr_api = &internal_drawing;
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
print_fe = snew(frontend);
|
||||||
|
memset(print_fe, 0, sizeof(frontend));
|
||||||
|
|
||||||
|
/* Defaults */
|
||||||
|
print_fe->printcount = print_fe->printw = print_fe->printh = 1;
|
||||||
|
print_fe->printscale = 100;
|
||||||
|
print_fe->printsolns = false;
|
||||||
|
print_fe->printcolour = thegame.can_print_in_colour;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to use the same midend as the main frontend because
|
||||||
|
* we need midend_print_puzzle() to be able to print the
|
||||||
|
* current puzzle.
|
||||||
|
*/
|
||||||
|
print_fe->me = fe->me;
|
||||||
|
|
||||||
|
print_fe->print_dr = drawing_new(>k_drawing, print_fe->me, print_fe);
|
||||||
|
|
||||||
|
print_fe->dr_api = &internal_printing;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (arg) {
|
if (arg) {
|
||||||
const char *err;
|
const char *err;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
@ -2568,6 +3193,12 @@ static frontend *new_window(
|
||||||
*error = dupstr(errbuf);
|
*error = dupstr(errbuf);
|
||||||
midend_free(fe->me);
|
midend_free(fe->me);
|
||||||
sfree(fe);
|
sfree(fe);
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
drawing_free(print_fe->print_dr);
|
||||||
|
sfree(print_fe);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2711,6 +3342,16 @@ static frontend *new_window(
|
||||||
g_signal_connect(G_OBJECT(menuitem), "activate",
|
g_signal_connect(G_OBJECT(menuitem), "activate",
|
||||||
G_CALLBACK(menu_save_event), fe);
|
G_CALLBACK(menu_save_event), fe);
|
||||||
gtk_widget_show(menuitem);
|
gtk_widget_show(menuitem);
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
|
menuitem = gtk_menu_item_new_with_label("Print...");
|
||||||
|
gtk_container_add(GTK_CONTAINER(menu), menuitem);
|
||||||
|
g_signal_connect(G_OBJECT(menuitem), "activate",
|
||||||
|
G_CALLBACK(menu_print_event), print_fe);
|
||||||
|
gtk_widget_show(menuitem);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifndef STYLUS_BASED
|
#ifndef STYLUS_BASED
|
||||||
add_menu_separator(GTK_CONTAINER(menu));
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
|
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
|
||||||
|
|
|
@ -591,6 +591,92 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
|
||||||
#define SOLVER(upper,title,func,lower) func,
|
#define SOLVER(upper,title,func,lower) func,
|
||||||
static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) };
|
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)
|
static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
|
||||||
{
|
{
|
||||||
int a = w*w;
|
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,
|
ret = latin_solver(soln, w, maxdiff,
|
||||||
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
||||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||||
keen_solvers, &ctx, NULL, NULL);
|
keen_solvers, keen_valid, &ctx, NULL, NULL);
|
||||||
|
|
||||||
sfree(ctx.dscratch);
|
sfree(ctx.dscratch);
|
||||||
sfree(ctx.iscratch);
|
sfree(ctx.iscratch);
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||||
|
|
||||||
#ifdef STANDALONE_SOLVER
|
#ifdef STANDALONE_SOLVER
|
||||||
int solver_show_working, solver_recurse_depth;
|
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
|
static int latin_solver_recurse
|
||||||
(struct latin_solver *solver, int diff_simple, int diff_set_0,
|
(struct latin_solver *solver, int diff_simple, int diff_set_0,
|
||||||
int diff_set_1, int diff_forcing, int diff_recursive,
|
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)
|
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||||
{
|
{
|
||||||
int best, bestcount;
|
int best, bestcount;
|
||||||
|
@ -817,7 +817,8 @@ static int latin_solver_recurse
|
||||||
ret = latin_solver_top(&subsolver, diff_recursive,
|
ret = latin_solver_top(&subsolver, diff_recursive,
|
||||||
diff_simple, diff_set_0, diff_set_1,
|
diff_simple, diff_set_0, diff_set_1,
|
||||||
diff_forcing, diff_recursive,
|
diff_forcing, diff_recursive,
|
||||||
usersolvers, newctx, ctxnew, ctxfree);
|
usersolvers, valid, newctx,
|
||||||
|
ctxnew, ctxfree);
|
||||||
latin_solver_free(&subsolver);
|
latin_solver_free(&subsolver);
|
||||||
if (ctxnew)
|
if (ctxnew)
|
||||||
ctxfree(newctx);
|
ctxfree(newctx);
|
||||||
|
@ -879,8 +880,8 @@ static int latin_solver_recurse
|
||||||
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||||
{
|
{
|
||||||
struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver);
|
struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver);
|
||||||
int ret, diff = diff_simple;
|
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,
|
int nsol = latin_solver_recurse(solver,
|
||||||
diff_simple, diff_set_0, diff_set_1,
|
diff_simple, diff_set_0, diff_set_1,
|
||||||
diff_forcing, diff_recursive,
|
diff_forcing, diff_recursive,
|
||||||
usersolvers, ctx, ctxnew, ctxfree);
|
usersolvers, valid, ctx,
|
||||||
|
ctxnew, ctxfree);
|
||||||
if (nsol < 0) diff = diff_impossible;
|
if (nsol < 0) diff = diff_impossible;
|
||||||
else if (nsol == 1) diff = diff_recursive;
|
else if (nsol == 1) diff = diff_recursive;
|
||||||
else if (nsol > 1) diff = diff_ambiguous;
|
else if (nsol > 1) diff = diff_ambiguous;
|
||||||
|
@ -990,6 +992,17 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
|
||||||
}
|
}
|
||||||
#endif
|
#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);
|
latin_solver_free_scratch(scratch);
|
||||||
|
|
||||||
return diff;
|
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 latin_solver_main(struct latin_solver *solver, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||||
{
|
{
|
||||||
int diff;
|
int diff;
|
||||||
#ifdef STANDALONE_SOLVER
|
#ifdef STANDALONE_SOLVER
|
||||||
|
@ -1027,7 +1040,7 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff,
|
||||||
diff = latin_solver_top(solver, maxdiff,
|
diff = latin_solver_top(solver, maxdiff,
|
||||||
diff_simple, diff_set_0, diff_set_1,
|
diff_simple, diff_set_0, diff_set_1,
|
||||||
diff_forcing, diff_recursive,
|
diff_forcing, diff_recursive,
|
||||||
usersolvers, ctx, ctxnew, ctxfree);
|
usersolvers, valid, ctx, ctxnew, ctxfree);
|
||||||
|
|
||||||
#ifdef STANDALONE_SOLVER
|
#ifdef STANDALONE_SOLVER
|
||||||
sfree(names);
|
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 latin_solver(digit *grid, int o, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree)
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
|
||||||
{
|
{
|
||||||
struct latin_solver solver;
|
struct latin_solver solver;
|
||||||
int diff;
|
int diff;
|
||||||
|
@ -1050,7 +1063,7 @@ int latin_solver(digit *grid, int o, int maxdiff,
|
||||||
diff = latin_solver_main(&solver, maxdiff,
|
diff = latin_solver_main(&solver, maxdiff,
|
||||||
diff_simple, diff_set_0, diff_set_1,
|
diff_simple, diff_set_0, diff_set_1,
|
||||||
diff_forcing, diff_recursive,
|
diff_forcing, diff_recursive,
|
||||||
usersolvers, ctx, ctxnew, ctxfree);
|
usersolvers, valid, ctx, ctxnew, ctxfree);
|
||||||
latin_solver_free(&solver);
|
latin_solver_free(&solver);
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ int latin_solver_diff_set(struct latin_solver *solver,
|
||||||
bool extreme);
|
bool extreme);
|
||||||
|
|
||||||
typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx);
|
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 *(*ctxnew_t)(void *ctx);
|
||||||
typedef void (*ctxfree_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 latin_solver(digit *grid, int o, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||||
|
|
||||||
/* Version you can call if you want to alloc and free latin_solver yourself */
|
/* 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 latin_solver_main(struct latin_solver *solver, int maxdiff,
|
||||||
int diff_simple, int diff_set_0, int diff_set_1,
|
int diff_simple, int diff_set_0, int diff_set_1,
|
||||||
int diff_forcing, int diff_recursive,
|
int diff_forcing, int diff_recursive,
|
||||||
usersolver_t const *usersolvers, void *ctx,
|
usersolver_t const *usersolvers, validator_t valid,
|
||||||
ctxnew_t ctxnew, ctxfree_t ctxfree);
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
||||||
|
|
||||||
void latin_solver_debug(unsigned char *cube, int o);
|
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))
|
if (full && params->unique && (params->w <= 2 || params->h <= 2))
|
||||||
return "Width and height must both be greater than two";
|
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)
|
if (params->n > params->w * params->h - 9)
|
||||||
return "Too many mines for grid size";
|
return "Too many mines for grid size";
|
||||||
if (params->n < 1)
|
if (params->n < 1)
|
||||||
|
|
|
@ -20,6 +20,7 @@ enum {
|
||||||
COL_GRID,
|
COL_GRID,
|
||||||
COL_CURSOR,
|
COL_CURSOR,
|
||||||
COL_ERROR,
|
COL_ERROR,
|
||||||
|
COL_CURSOR_GUIDE,
|
||||||
NCOLOURS
|
NCOLOURS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1665,6 +1666,7 @@ static float *game_colours(frontend *fe, int *ncolours)
|
||||||
ret[COL_TEXT * 3 + i] = 0.0F;
|
ret[COL_TEXT * 3 + i] = 0.0F;
|
||||||
ret[COL_FULL * 3 + i] = 0.0F;
|
ret[COL_FULL * 3 + i] = 0.0F;
|
||||||
ret[COL_EMPTY * 3 + i] = 1.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 + 0] = 1.0F;
|
||||||
ret[COL_CURSOR * 3 + 1] = 0.25F;
|
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++) {
|
for (i = 0; i < state->common->w + state->common->h; i++) {
|
||||||
int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT;
|
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) {
|
if (ds->numcolours[i] != colour) {
|
||||||
draw_numbers(dr, ds, state, i, true, colour);
|
draw_numbers(dr, ds, state, i, true, colour);
|
||||||
ds->numcolours[i] = colour;
|
ds->numcolours[i] = colour;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* setup and layout.
|
* setup and layout.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "puzzles.h"
|
#include "puzzles.h"
|
||||||
|
|
||||||
struct puzzle {
|
struct puzzle {
|
||||||
|
@ -88,7 +90,7 @@ void document_add_puzzle(document *doc, const game *game, game_params *par,
|
||||||
doc->got_solns = true;
|
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 *w, float *h, float *scale)
|
||||||
{
|
{
|
||||||
float ww, hh, ourscale;
|
float ww, hh, ourscale;
|
||||||
|
@ -115,32 +117,68 @@ 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 ppp; /* puzzles per page */
|
||||||
int pages, passes;
|
int pages, passes;
|
||||||
int page, pass;
|
|
||||||
int pageno;
|
|
||||||
|
|
||||||
ppp = doc->pw * doc->ph;
|
ppp = doc->pw * doc->ph;
|
||||||
pages = (doc->npuzzles + ppp - 1) / ppp;
|
pages = (doc->npuzzles + ppp - 1) / ppp;
|
||||||
passes = (doc->got_solns ? 2 : 1);
|
passes = (doc->got_solns ? 2 : 1);
|
||||||
|
|
||||||
print_begin_doc(dr, pages * passes);
|
return pages * passes;
|
||||||
|
}
|
||||||
|
|
||||||
pageno = 1;
|
/*
|
||||||
for (pass = 0; pass < passes; pass++) {
|
* Begin a document.
|
||||||
for (page = 0; page < pages; page++) {
|
*/
|
||||||
|
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;
|
int i, n, offset;
|
||||||
float colsum, rowsum;
|
float colsum, rowsum;
|
||||||
|
|
||||||
print_begin_page(dr, pageno);
|
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;
|
offset = page * ppp;
|
||||||
n = min(ppp, doc->npuzzles - offset);
|
n = min(ppp, doc->npuzzles - offset);
|
||||||
|
|
||||||
|
print_begin_page(dr, pageno);
|
||||||
|
|
||||||
for (i = 0; i < doc->pw; i++)
|
for (i = 0; i < doc->pw; i++)
|
||||||
doc->colwid[i] = 0;
|
doc->colwid[i] = 0;
|
||||||
for (i = 0; i < doc->ph; i++)
|
for (i = 0; i < doc->ph; i++)
|
||||||
|
@ -239,9 +277,17 @@ void document_print(document *doc, drawing *dr)
|
||||||
}
|
}
|
||||||
|
|
||||||
print_end_page(dr, pageno);
|
print_end_page(dr, pageno);
|
||||||
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);
|
print_end_doc(dr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
#include "puzzles.h"
|
#include "puzzles.h"
|
||||||
|
|
||||||
#define ROOT2 1.414213562
|
|
||||||
|
|
||||||
struct psdata {
|
struct psdata {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
bool colour;
|
bool colour;
|
||||||
|
|
|
@ -3360,7 +3360,8 @@ This software is \i{copyright} 2004-2014 Simon Tatham.
|
||||||
|
|
||||||
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||||
K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
|
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
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation files
|
obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define PI 3.141592653589793238462643383279502884197169399
|
#define PI 3.141592653589793238462643383279502884197169399
|
||||||
|
#define ROOT2 1.414213562373095048801688724209698078569672
|
||||||
|
|
||||||
#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
|
#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_free(document *doc);
|
||||||
void document_add_puzzle(document *doc, const game *game, game_params *par,
|
void document_add_puzzle(document *doc, const game *game, game_params *par,
|
||||||
game_state *st, game_state *st2);
|
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
|
* ps.c
|
||||||
|
|
|
@ -574,6 +574,38 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
|
||||||
#define SOLVER(upper,title,func,lower) func,
|
#define SOLVER(upper,title,func,lower) func,
|
||||||
static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) };
|
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)
|
static int solver(int w, int *clues, digit *soln, int maxdiff)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -589,7 +621,7 @@ static int solver(int w, int *clues, digit *soln, int maxdiff)
|
||||||
ret = latin_solver(soln, w, maxdiff,
|
ret = latin_solver(soln, w, maxdiff,
|
||||||
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
|
||||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||||
towers_solvers, &ctx, NULL, NULL);
|
towers_solvers, towers_valid, &ctx, NULL, NULL);
|
||||||
|
|
||||||
sfree(ctx.iscratch);
|
sfree(ctx.iscratch);
|
||||||
sfree(ctx.dscratch);
|
sfree(ctx.dscratch);
|
||||||
|
|
|
@ -8,6 +8,9 @@ tracks : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res
|
||||||
|
|
||||||
ALL += tracks[COMBINED] TRACKS_EXTRA
|
ALL += tracks[COMBINED] TRACKS_EXTRA
|
||||||
|
|
||||||
|
trackssolver : [U] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
|
||||||
|
trackssolver : [C] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
|
||||||
|
|
||||||
!begin am gtk
|
!begin am gtk
|
||||||
GAMES += tracks
|
GAMES += tracks
|
||||||
!end
|
!end
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
* #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
|
* #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
|
||||||
* #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1
|
* #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
|
* #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>
|
#include <stdio.h>
|
||||||
|
@ -31,7 +32,9 @@
|
||||||
*/
|
*/
|
||||||
#define DIFFLIST(A) \
|
#define DIFFLIST(A) \
|
||||||
A(EASY,Easy,e) \
|
A(EASY,Easy,e) \
|
||||||
A(TRICKY,Tricky,t)
|
A(TRICKY,Tricky,t) \
|
||||||
|
A(HARD,Hard,h) \
|
||||||
|
/* end of list */
|
||||||
|
|
||||||
#define ENUM(upper,title,lower) DIFF_ ## upper,
|
#define ENUM(upper,title,lower) DIFF_ ## upper,
|
||||||
#define TITLE(upper,title,lower) #title,
|
#define TITLE(upper,title,lower) #title,
|
||||||
|
@ -65,10 +68,12 @@ static const struct game_params tracks_presets[] = {
|
||||||
{10, 8, DIFF_TRICKY, 1 },
|
{10, 8, DIFF_TRICKY, 1 },
|
||||||
{10, 10, DIFF_EASY, 1},
|
{10, 10, DIFF_EASY, 1},
|
||||||
{10, 10, DIFF_TRICKY, 1},
|
{10, 10, DIFF_TRICKY, 1},
|
||||||
|
{10, 10, DIFF_HARD, 1},
|
||||||
{15, 10, DIFF_EASY, 1},
|
{15, 10, DIFF_EASY, 1},
|
||||||
{15, 10, DIFF_TRICKY, 1},
|
{15, 10, DIFF_TRICKY, 1},
|
||||||
{15, 15, DIFF_EASY, 1},
|
{15, 15, DIFF_EASY, 1},
|
||||||
{15, 15, DIFF_TRICKY, 1},
|
{15, 15, DIFF_TRICKY, 1},
|
||||||
|
{15, 15, DIFF_HARD, 1},
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool game_fetch_preset(int i, char **name, game_params **params)
|
static bool game_fetch_preset(int i, char **name, game_params **params)
|
||||||
|
@ -452,7 +457,7 @@ start:
|
||||||
state->numbers->col_s = px;
|
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);
|
static void debug_state(game_state *state, const char *what);
|
||||||
|
|
||||||
/* Clue-setting algorithm:
|
/* Clue-setting algorithm:
|
||||||
|
@ -533,6 +538,26 @@ static game_state *copy_and_strip(const game_state *state, game_state *ret, int
|
||||||
return ret;
|
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) {
|
static int solve_progress(const game_state *state) {
|
||||||
int i, w = state->p.w, h = state->p.h, progress = 0;
|
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 *positions = snewn(w*h, int), npositions = 0;
|
||||||
int *nedges_previous_solve = snewn(w*h, int);
|
int *nedges_previous_solve = snewn(w*h, int);
|
||||||
game_state *scratch = dup_game(state);
|
game_state *scratch = dup_game(state);
|
||||||
|
int diff_used;
|
||||||
|
|
||||||
debug_state(state, "gen: Initial board");
|
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 */
|
/* First, check whether the puzzle is already either too easy, or just right */
|
||||||
scratch = copy_and_strip(state, scratch, -1);
|
scratch = copy_and_strip(state, scratch, -1);
|
||||||
if (diff > 0) {
|
sr = tracks_solve(scratch, diff, &diff_used);
|
||||||
sr = tracks_solve(scratch, diff-1);
|
if (diff_used < diff) {
|
||||||
if (sr < 0)
|
|
||||||
assert(!"Generator should not have created impossible puzzle");
|
|
||||||
if (sr > 0) {
|
|
||||||
ret = -1; /* already too easy, even without adding clues. */
|
ret = -1; /* already too easy, even without adding clues. */
|
||||||
debug(("gen: ...already too easy, need new board."));
|
debug(("gen: ...already too easy, need new board."));
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
sr = tracks_solve(scratch, diff);
|
|
||||||
if (sr < 0)
|
if (sr < 0)
|
||||||
assert(!"Generator should not have created impossible puzzle");
|
assert(!"Generator should not have created impossible puzzle");
|
||||||
if (sr > 0) {
|
if (sr > 0) {
|
||||||
|
@ -629,12 +651,10 @@ static int add_clues(game_state *state, random_state *rs, int diff)
|
||||||
if (check_phantom_moves(scratch))
|
if (check_phantom_moves(scratch))
|
||||||
continue; /* adding a clue here would add phantom track */
|
continue; /* adding a clue here would add phantom track */
|
||||||
|
|
||||||
if (diff > 0) {
|
if (tracks_solve(scratch, diff, &diff_used) > 0) {
|
||||||
if (tracks_solve(scratch, diff-1) > 0) {
|
if (diff_used < diff) {
|
||||||
continue; /* adding a clue here makes it too easy */
|
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
|
/* we're now soluble (and we weren't before): add this clue, and then
|
||||||
start stripping clues */
|
start stripping clues */
|
||||||
debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w));
|
debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w));
|
||||||
|
@ -676,7 +696,7 @@ strip_clues:
|
||||||
if (check_phantom_moves(scratch))
|
if (check_phantom_moves(scratch))
|
||||||
continue; /* removing a clue here would add phantom track */
|
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));
|
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. */
|
state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */
|
||||||
}
|
}
|
||||||
|
@ -686,6 +706,7 @@ strip_clues:
|
||||||
|
|
||||||
done:
|
done:
|
||||||
sfree(positions);
|
sfree(positions);
|
||||||
|
sfree(nedges_previous_solve);
|
||||||
free_game(scratch);
|
free_game(scratch);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -780,7 +801,7 @@ newpath:
|
||||||
}
|
}
|
||||||
*p++ = '\0';
|
*p++ = '\0';
|
||||||
|
|
||||||
ret = tracks_solve(state, DIFFCOUNT);
|
ret = tracks_solve(state, DIFFCOUNT, NULL);
|
||||||
assert(ret >= 0);
|
assert(ret >= 0);
|
||||||
free_game(state);
|
free_game(state);
|
||||||
|
|
||||||
|
@ -882,6 +903,10 @@ static game_state *new_game(midend *me, const game_params *params, const char *d
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct solver_scratch {
|
||||||
|
int *dsf;
|
||||||
|
};
|
||||||
|
|
||||||
static int solve_set_sflag(game_state *state, int x, int y,
|
static int solve_set_sflag(game_state *state, int x, int y,
|
||||||
unsigned int f, const char *why)
|
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)
|
if (state->sflags[i] & f)
|
||||||
return 0;
|
return 0;
|
||||||
debug(("solve: square (%d,%d) -> %s: %s",
|
solverdebug(("square (%d,%d) -> %s: %s",
|
||||||
x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
||||||
if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
|
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->impossible = true;
|
||||||
}
|
}
|
||||||
state->sflags[i] |= f;
|
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)
|
if (sf & f)
|
||||||
return 0;
|
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',
|
(d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R',
|
||||||
(f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
(f == S_TRACK ? "TRACK" : "NOTRACK"), why));
|
||||||
if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
|
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;
|
state->impossible = true;
|
||||||
}
|
}
|
||||||
S_E_SET(state, x, y, d, f);
|
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 (ctrack != (target-1)) return 0;
|
||||||
if (nperp > 0 || n1edge != 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));
|
si%w, si/w, i1edge%w, i1edge/w));
|
||||||
|
|
||||||
/* We have a match: anything that's more than 1 away from this square
|
/* 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)) {
|
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));
|
what, si%w, si/w, nloose, target-e2count));
|
||||||
state->impossible = true;
|
state->impossible = true;
|
||||||
}
|
}
|
||||||
if (nloose > 0 && nloose == (target - e2count)) {
|
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));
|
what, si%w, si/w, nloose));
|
||||||
for (j = 0, i = si; j < n; j++, i += id) {
|
for (j = 0, i = si; j < n; j++, i += id) {
|
||||||
if (!(state->sflags[i] & S_MARK))
|
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) {
|
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));
|
what, si%w, si/w));
|
||||||
for (j = 0, i = si; j < n; j++, i += id) {
|
for (j = 0, i = si; j < n; j++, i += id) {
|
||||||
if (!(state->sflags[i] & S_MARK))
|
if (!(state->sflags[i] & S_MARK))
|
||||||
|
@ -1176,6 +1201,110 @@ static int solve_check_loose_ends(game_state *state)
|
||||||
return did;
|
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,
|
static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
|
||||||
int *dsf, int startc, int endc)
|
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");
|
return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop");
|
||||||
}
|
}
|
||||||
if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) {
|
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:
|
/* We mustn't join the start to the end if:
|
||||||
- there are other bits of track that aren't attached to either end
|
- there are other bits of track that aren't attached to either end
|
||||||
- the clues are not fully satisfied yet
|
- 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");
|
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;
|
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..."));
|
debug(("solve..."));
|
||||||
state->impossible = false;
|
state->impossible = false;
|
||||||
|
@ -1305,20 +1569,36 @@ static int tracks_solve(game_state *state, int diff)
|
||||||
solve_discount_edge(state, w-1, y, R);
|
solve_discount_edge(state, w-1, y, R);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (!state->impossible) {
|
||||||
didsth = false;
|
|
||||||
|
|
||||||
didsth |= solve_update_flags(state);
|
/* Can't use do ... while (0) because we need a 'continue' in this macro */
|
||||||
didsth |= solve_count_clues(state);
|
#define TRY(curr_diff, funcall) \
|
||||||
didsth |= solve_check_loop(state);
|
if (diff >= (curr_diff) && (funcall)) { \
|
||||||
|
if (max_diff < curr_diff) \
|
||||||
|
max_diff = curr_diff; \
|
||||||
|
continue; \
|
||||||
|
} else ((void)0)
|
||||||
|
|
||||||
if (diff >= DIFF_TRICKY) {
|
TRY(DIFF_EASY, solve_update_flags(state));
|
||||||
didsth |= solve_check_single(state);
|
TRY(DIFF_EASY, solve_count_clues(state));
|
||||||
didsth |= solve_check_loose_ends(state);
|
TRY(DIFF_EASY, solve_check_loop(state));
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didsth || state->impossible) break;
|
sfree(sc->dsf);
|
||||||
}
|
|
||||||
|
if (max_diff_out)
|
||||||
|
*max_diff_out = max_diff;
|
||||||
|
|
||||||
return state->impossible ? -1 : check_completion(state, false) ? 1 : 0;
|
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;
|
char *move;
|
||||||
|
|
||||||
solved = dup_game(currstate);
|
solved = dup_game(currstate);
|
||||||
ret = tracks_solve(solved, DIFFCOUNT);
|
ret = tracks_solve(solved, DIFFCOUNT, NULL);
|
||||||
if (ret < 1) {
|
if (ret < 1) {
|
||||||
free_game(solved);
|
free_game(solved);
|
||||||
solved = dup_game(state);
|
solved = dup_game(state);
|
||||||
ret = tracks_solve(solved, DIFFCOUNT);
|
ret = tracks_solve(solved, DIFFCOUNT, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 1) {
|
if (ret < 1) {
|
||||||
|
@ -2094,7 +2374,7 @@ static game_state *execute_move(const game_state *state, const char *move)
|
||||||
goto badmove;
|
goto badmove;
|
||||||
move += n;
|
move += n;
|
||||||
} else if (c == 'H') {
|
} else if (c == 'H') {
|
||||||
tracks_solve(ret, DIFFCOUNT);
|
tracks_solve(ret, DIFFCOUNT, NULL);
|
||||||
move++;
|
move++;
|
||||||
} else {
|
} else {
|
||||||
goto badmove;
|
goto badmove;
|
||||||
|
@ -2675,4 +2955,87 @@ const struct game thegame = {
|
||||||
0, /* flags */
|
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: */
|
/* 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;
|
int i, x, y, col, maxlen;
|
||||||
bool o = state->orientable;
|
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
|
* First work out how many characters we need to display each
|
||||||
* number. We're pretty flexible on grid contents here, so we
|
* 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;
|
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
|
* 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,
|
* 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,
|
#define SOLVER(upper,title,func,lower) func,
|
||||||
static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) };
|
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)
|
static int solver_state(game_state *state, int maxdiff)
|
||||||
{
|
{
|
||||||
struct solver_ctx *ctx = new_ctx(state);
|
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_solver_main(&solver, maxdiff,
|
||||||
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
||||||
DIFF_EXTREME, DIFF_RECURSIVE,
|
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);
|
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_solver_main(&solver, DIFF_RECURSIVE,
|
||||||
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
|
||||||
DIFF_EXTREME, DIFF_RECURSIVE,
|
DIFF_EXTREME, DIFF_RECURSIVE,
|
||||||
unequal_solvers, ctx, clone_ctx, free_ctx);
|
unequal_solvers, unequal_valid, ctx,
|
||||||
|
clone_ctx, free_ctx);
|
||||||
|
|
||||||
free_ctx(ctx);
|
free_ctx(ctx);
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
#define DIFFLIST(A) \
|
#define DIFFLIST(A) \
|
||||||
A(TRIVIAL,Trivial,NULL,t) \
|
A(TRIVIAL,Trivial,NULL,t) \
|
||||||
A(NORMAL,Normal,solver_normal,n) \
|
A(NORMAL,Normal,solver_normal,n) \
|
||||||
A(HARD,Hard,NULL,h) \
|
A(HARD,Hard,solver_hard,h) \
|
||||||
A(EXTREME,Extreme,NULL,x) \
|
A(EXTREME,Extreme,NULL,x) \
|
||||||
A(UNREASONABLE,Unreasonable,NULL,u)
|
A(UNREASONABLE,Unreasonable,NULL,u)
|
||||||
#define ENUM(upper,title,func,lower) DIFF_ ## upper,
|
#define ENUM(upper,title,func,lower) DIFF_ ## upper,
|
||||||
|
@ -280,6 +280,23 @@ static const char *validate_params(const game_params *params, bool full)
|
||||||
* Solver.
|
* 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)
|
static int solver_normal(struct latin_solver *solver, void *vctx)
|
||||||
{
|
{
|
||||||
int w = solver->o;
|
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
|
* So we pick any a,b,c we like; then if we know ab, bc, and
|
||||||
* (ab)c we can fill in a(bc).
|
* (ab)c we can fill in a(bc).
|
||||||
*/
|
*/
|
||||||
for (i = 1; i < w; i++)
|
for (i = 0; i < w; i++)
|
||||||
for (j = 1; j < w; j++)
|
for (j = 0; j < w; j++)
|
||||||
for (k = 1; k < w; k++) {
|
for (k = 0; k < w; k++) {
|
||||||
if (!grid[i*w+j] || !grid[j*w+k])
|
if (!grid[i*w+j] || !grid[j*w+k])
|
||||||
continue;
|
continue;
|
||||||
if (grid[(grid[i*w+j]-1)*w+k] &&
|
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;
|
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,
|
#define SOLVER(upper,title,func,lower) func,
|
||||||
static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) };
|
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)
|
static int solver(const game_params *params, digit *grid, int maxdiff)
|
||||||
{
|
{
|
||||||
int w = params->w;
|
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,
|
ret = latin_solver_main(&solver, maxdiff,
|
||||||
DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
|
DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
|
||||||
DIFF_EXTREME, DIFF_UNREASONABLE,
|
DIFF_EXTREME, DIFF_UNREASONABLE,
|
||||||
group_solvers, NULL, NULL, NULL);
|
group_solvers, group_valid, NULL, NULL, NULL);
|
||||||
|
|
||||||
latin_solver_free(&solver);
|
latin_solver_free(&solver);
|
||||||
|
|
||||||
|
@ -2183,6 +2394,11 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diff == DIFFCOUNT) {
|
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)
|
if (grade)
|
||||||
printf("Difficulty rating: ambiguous\n");
|
printf("Difficulty rating: ambiguous\n");
|
||||||
else
|
else
|
||||||
|
|
|
@ -68,6 +68,103 @@
|
||||||
* has precisely the set of link changes to cause that effect).
|
* 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.
|
* Standard notation for directions.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue