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:
Franklin Wei 2020-06-25 14:44:33 -04:00
parent dd3a8e0898
commit 48b0ef1cf2
60 changed files with 1890 additions and 333 deletions

View file

@ -59,3 +59,7 @@ April 2018: Finished up the rest of the games. All work now! Surely
there's still bugs to fix, so stay tuned...
December 2018: Resync to 3ece3d6. Happy holidays!
May 2019: Resync to e2135d5.
June 2020: Resync to 9aa7b7c.

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -1,4 +1,4 @@
/* auto-generated on Dec 21 2018 by genhelp.sh */
/* auto-generated on Jun 25 2020 by genhelp.sh */
/* help text is compressed using LZ4; see compress.c for details */
/* DO NOT EDIT! */

View file

@ -2,8 +2,8 @@ This software is copyright (c) 2004-2014 Simon Tatham.
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael
Quevillon.
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
Quevillon and Asher Gordon.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files

View file

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

View file

@ -2378,6 +2378,8 @@ static void grid_size_floret(int width, int height,
*tilesize = FLORET_TILESIZE;
*xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
*yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry;
if (height == 1)
*yextent += (5*qy-4*py)/2;
}
static grid *grid_new_floret(int width, int height, const char *desc)

View file

@ -44,6 +44,17 @@
# 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)
/* The old names are still more concise! */
#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
@ -131,6 +142,18 @@ struct font {
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.
* In principle this would allow us to open multiple independent
@ -180,7 +203,7 @@ struct frontend {
GtkWidget *cfgbox;
void *paste_data;
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 */
#ifdef OLD_FILESEL
char *filesel_name;
@ -225,6 +248,23 @@ struct frontend {
*/
bool awaiting_resize_ack;
#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 {
@ -247,15 +287,19 @@ void get_random_seed(void **randseed, int *randseedsize)
void frontend_default_colour(frontend *fe, float *output)
{
#if !GTK_CHECK_VERSION(3,0,0)
if (!fe->headless) {
/*
* Use the widget style's default background colour as the
* background for the puzzle drawing area.
* If we have a widget and it has a style that specifies a
* 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];
output[0] = col.red / 65535.0;
output[1] = col.green / 65535.0;
output[2] = col.blue / 65535.0;
#else
}
#endif
/*
* GTK 3 has decided that there's no such thing as a 'default
* 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
* on an arbitrary background image, so we'll just make up a
* 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;
#endif
}
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)
{
fe->cr = cairo_create(fe->image);
cairo_scale(fe->cr, fe->ps, fe->ps);
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
cairo_set_line_width(fe->cr, 1.0);
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);
}
static void set_colour(frontend *fe, int colour)
static void draw_set_colour(frontend *fe, int colour)
{
cairo_set_source_rgb(fe->cr,
fe->colours[3*colour + 0],
@ -329,6 +376,17 @@ static void set_colour(frontend *fe, int colour)
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)
{
#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);
}
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)
{
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_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
cairo_rectangle(fe->cr, x, y, w, h);
cairo_fill(fe->cr);
fe->dr_api->fill(fe);
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_close_path(fe->cr);
if (fillcolour >= 0) {
set_colour(fe, fillcolour);
cairo_fill_preserve(fe->cr);
fe->dr_api->set_colour(fe, fillcolour);
fe->dr_api->fill_preserve(fe);
}
assert(outlinecolour >= 0);
set_colour(fe, outlinecolour);
fe->dr_api->set_colour(fe, outlinecolour);
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_close_path(fe->cr); /* Just in case... */
if (fillcolour >= 0) {
set_colour(fe, fillcolour);
cairo_fill_preserve(fe->cr);
fe->dr_api->set_colour(fe, fillcolour);
fe->dr_api->fill_preserve(fe);
}
assert(outlinecolour >= 0);
set_colour(fe, outlinecolour);
fe->dr_api->set_colour(fe, outlinecolour);
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)
{
#ifndef USE_CAIRO_WITHOUT_PIXMAP
if (fe->headless) {
fprintf(stderr, "headless mode does not work with GDK pixmaps\n");
exit(1);
}
if (!fe->headless) {
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
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);
#ifndef USE_CAIRO_WITHOUT_PIXMAP
if (!fe->headless)
wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
#endif
#if GTK_CHECK_VERSION(3,22,0)
if (!fe->headless) {
#if GTK_CHECK_VERSION(3,22,0)
GdkWindow *gdkwin;
cairo_region_t *region;
GdkDrawingContext *drawctx;
@ -541,12 +676,12 @@ static void setup_backing_store(frontend *fe)
wipe_and_maybe_destroy_cairo(fe, cr, false);
gdk_window_end_draw_frame(gdkwin, drawctx);
cairo_region_destroy(region);
}
#else
wipe_and_maybe_destroy_cairo(
fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true);
#endif
}
}
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]);
}
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]);
}
@ -709,11 +844,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
}
if (fillcolour >= 0) {
set_colour(fe, fillcolour);
fe->dr_api->set_colour(fe, fillcolour);
gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
}
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
@ -733,14 +868,14 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
int fillcolour, int outlinecolour)
{
if (fillcolour >= 0) {
set_colour(fe, fillcolour);
fe->dr_api->set_colour(fe, fillcolour);
gdk_draw_arc(fe->pixmap, fe->gc, true,
cx - radius, cy - radius,
2 * radius, 2 * radius, 0, 360 * 64);
}
assert(outlinecolour >= 0);
set_colour(fe, outlinecolour);
fe->dr_api->set_colour(fe, outlinecolour);
gdk_draw_arc(fe->pixmap, fe->gc, false,
cx - radius, cy - radius,
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.
*/
set_colour(fe, colour);
fe->dr_api->set_colour(fe, colour);
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)
{
frontend *fe = (frontend *)handle;
set_colour(fe, colour);
fe->dr_api->set_colour(fe, colour);
do_draw_rect(fe, x, y, w, h);
}
void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
{
frontend *fe = (frontend *)handle;
set_colour(fe, colour);
fe->dr_api->set_colour(fe, colour);
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)
{
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);
}
@ -1161,6 +1296,105 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
}
#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 = {
gtk_draw_text,
gtk_draw_rect,
@ -1177,8 +1411,19 @@ const struct drawing_api gtk_drawing = {
gtk_blitter_free,
gtk_blitter_save,
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, /* line_width, line_dotted */
#endif
#ifdef USE_PANGO
gtk_text_fallback,
#else
@ -1340,12 +1585,22 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
frontend *fe = (frontend *)data;
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);
cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
dirtyrect.width, dirtyrect.height);
cairo_fill(cr);
cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
return true;
}
#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)
{
int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
int oldps = fe->ps;
fe->w = x;
fe->h = y;
midend_size(fe->me, &x, &y, true);
fe->pw = x;
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->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)) {
if (backing_store_ok(fe))
teardown_backing_store(fe);
@ -1413,6 +1674,7 @@ static gint configure_area(GtkWidget *widget,
GdkEventConfigure *event, gpointer data)
{
frontend *fe = (frontend *)data;
resize_puzzle_to_area(fe, event->width, event->height);
#if GTK_CHECK_VERSION(3,0,0)
fe->awaiting_resize_ack = false;
@ -2245,6 +2507,317 @@ static char *file_selector(frontend *fe, const char *title, bool save)
#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 {
FILE *fp;
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)
{
frontend *fe = (frontend *)data;
@ -2388,18 +2970,31 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
frontend *fe = (frontend *)data;
#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 const int n_xpm_icons;
if (n_xpm_icons) {
GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
((const gchar **)xpm_icons[n_xpm_icons-1]);
gtk_show_about_dialog
(GTK_WINDOW(fe->window),
"program-name", thegame.name,
"version", ver,
"comments", "Part of Simon Tatham's Portable Puzzle Collection",
ABOUT_PARAMS,
"logo", icon,
(const gchar *)NULL);
g_object_unref(G_OBJECT(icon));
}
else {
gtk_show_about_dialog
(GTK_WINDOW(fe->window),
ABOUT_PARAMS,
(const gchar *)NULL);
}
#else
char titlebuf[256];
char textbuf[1024];
@ -2489,6 +3084,9 @@ static frontend *new_window(
char *arg, int argtype, char **error, bool headless)
{
frontend *fe;
#ifdef USE_PRINTING
frontend *print_fe = NULL;
#endif
GtkBox *vbox, *hbox;
GtkWidget *menu, *menuitem;
GList *iconlist;
@ -2501,13 +3099,14 @@ static frontend *new_window(
fe = snew(frontend);
memset(fe, 0, sizeof(frontend));
#if !GTK_CHECK_VERSION(3,0,0)
#ifndef USE_CAIRO
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);
}
#else
fe->headless = headless;
fe->ps = 1; /* in headless mode, configure_area won't have set this */
#endif
fe->timer_active = false;
@ -2515,6 +3114,32 @@ static frontend *new_window(
fe->me = midend_new(fe, &thegame, &gtk_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(&gtk_drawing, print_fe->me, print_fe);
print_fe->dr_api = &internal_printing;
}
#endif
if (arg) {
const char *err;
FILE *fp;
@ -2568,6 +3193,12 @@ static frontend *new_window(
*error = dupstr(errbuf);
midend_free(fe->me);
sfree(fe);
#ifdef USE_PRINTING
if (thegame.can_print) {
drawing_free(print_fe->print_dr);
sfree(print_fe);
}
#endif
return NULL;
}
@ -2711,6 +3342,16 @@ static frontend *new_window(
g_signal_connect(G_OBJECT(menuitem), "activate",
G_CALLBACK(menu_save_event), fe);
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
add_menu_separator(GTK_CONTAINER(menu));
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);

View file

@ -591,6 +591,92 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
#define SOLVER(upper,title,func,lower) func,
static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) };
static int transpose(int index, int w)
{
return (index % w) * w + (index / w);
}
static bool keen_valid(struct latin_solver *solver, void *vctx)
{
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
int w = ctx->w;
int box, i;
/*
* Iterate over each clue box and check it's satisfied.
*/
for (box = 0; box < ctx->nboxes; box++) {
int *sq = ctx->boxlist + ctx->boxes[box];
int n = ctx->boxes[box+1] - ctx->boxes[box];
long value = ctx->clues[box] & ~CMASK;
long op = ctx->clues[box] & CMASK;
bool fail = false;
switch (op) {
case C_ADD: {
long sum = 0;
for (i = 0; i < n; i++)
sum += solver->grid[transpose(sq[i], w)];
fail = (sum != value);
break;
}
case C_MUL: {
long remaining = value;
for (i = 0; i < n; i++) {
if (remaining % solver->grid[transpose(sq[i], w)]) {
fail = true;
break;
}
remaining /= solver->grid[transpose(sq[i], w)];
}
if (remaining != 1)
fail = true;
break;
}
case C_SUB:
assert(n == 2);
if (value != labs(solver->grid[transpose(sq[0], w)] -
solver->grid[transpose(sq[1], w)]))
fail = true;
break;
case C_DIV: {
int num, den;
assert(n == 2);
num = max(solver->grid[transpose(sq[0], w)],
solver->grid[transpose(sq[1], w)]);
den = min(solver->grid[transpose(sq[0], w)],
solver->grid[transpose(sq[1], w)]);
if (den * value != num)
fail = true;
break;
}
}
if (fail) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*sclue at (%d,%d) is violated\n",
solver_recurse_depth*4, "",
sq[0]/w+1, sq[0]%w+1);
printf("%*s (%s clue with target %ld containing [",
solver_recurse_depth*4, "",
(op == C_ADD ? "addition" : op == C_SUB ? "subtraction":
op == C_MUL ? "multiplication" : "division"), value);
for (i = 0; i < n; i++)
printf(" %d", (int)solver->grid[transpose(sq[i], w)]);
printf(" ]\n");
}
#endif
return false;
}
}
return true;
}
static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
{
int a = w*w;
@ -638,7 +724,7 @@ static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
ret = latin_solver(soln, w, maxdiff,
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
DIFF_EXTREME, DIFF_UNREASONABLE,
keen_solvers, &ctx, NULL, NULL);
keen_solvers, keen_valid, &ctx, NULL, NULL);
sfree(ctx.dscratch);
sfree(ctx.iscratch);

View file

@ -19,8 +19,8 @@
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree);
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
#ifdef STANDALONE_SOLVER
int solver_show_working, solver_recurse_depth;
@ -711,7 +711,7 @@ int latin_solver_diff_set(struct latin_solver *solver,
static int latin_solver_recurse
(struct latin_solver *solver, int diff_simple, int diff_set_0,
int diff_set_1, int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
usersolver_t const *usersolvers, validator_t valid, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree)
{
int best, bestcount;
@ -817,7 +817,8 @@ static int latin_solver_recurse
ret = latin_solver_top(&subsolver, diff_recursive,
diff_simple, diff_set_0, diff_set_1,
diff_forcing, diff_recursive,
usersolvers, newctx, ctxnew, ctxfree);
usersolvers, valid, newctx,
ctxnew, ctxfree);
latin_solver_free(&subsolver);
if (ctxnew)
ctxfree(newctx);
@ -879,8 +880,8 @@ static int latin_solver_recurse
static int latin_solver_top(struct latin_solver *solver, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree)
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
{
struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver);
int ret, diff = diff_simple;
@ -941,7 +942,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
int nsol = latin_solver_recurse(solver,
diff_simple, diff_set_0, diff_set_1,
diff_forcing, diff_recursive,
usersolvers, ctx, ctxnew, ctxfree);
usersolvers, valid, ctx,
ctxnew, ctxfree);
if (nsol < 0) diff = diff_impossible;
else if (nsol == 1) diff = diff_recursive;
else if (nsol > 1) diff = diff_ambiguous;
@ -990,6 +992,17 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
}
#endif
if (diff != diff_impossible && diff != diff_unfinished &&
diff != diff_ambiguous && valid && !valid(solver, ctx)) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*ssolution failed final validation!\n",
solver_recurse_depth*4, "");
}
#endif
diff = diff_impossible;
}
latin_solver_free_scratch(scratch);
return diff;
@ -998,8 +1011,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff,
int latin_solver_main(struct latin_solver *solver, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree)
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
{
int diff;
#ifdef STANDALONE_SOLVER
@ -1027,7 +1040,7 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff,
diff = latin_solver_top(solver, maxdiff,
diff_simple, diff_set_0, diff_set_1,
diff_forcing, diff_recursive,
usersolvers, ctx, ctxnew, ctxfree);
usersolvers, valid, ctx, ctxnew, ctxfree);
#ifdef STANDALONE_SOLVER
sfree(names);
@ -1040,8 +1053,8 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff,
int latin_solver(digit *grid, int o, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree)
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree)
{
struct latin_solver solver;
int diff;
@ -1050,7 +1063,7 @@ int latin_solver(digit *grid, int o, int maxdiff,
diff = latin_solver_main(&solver, maxdiff,
diff_simple, diff_set_0, diff_set_1,
diff_forcing, diff_recursive,
usersolvers, ctx, ctxnew, ctxfree);
usersolvers, valid, ctx, ctxnew, ctxfree);
latin_solver_free(&solver);
return diff;
}

View file

@ -85,6 +85,7 @@ int latin_solver_diff_set(struct latin_solver *solver,
bool extreme);
typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx);
typedef bool (*validator_t)(struct latin_solver *solver, void *ctx);
typedef void *(*ctxnew_t)(void *ctx);
typedef void (*ctxfree_t)(void *ctx);
@ -96,15 +97,15 @@ enum { diff_impossible = 10, diff_ambiguous, diff_unfinished };
int latin_solver(digit *grid, int o, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree);
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
/* Version you can call if you want to alloc and free latin_solver yourself */
int latin_solver_main(struct latin_solver *solver, int maxdiff,
int diff_simple, int diff_set_0, int diff_set_1,
int diff_forcing, int diff_recursive,
usersolver_t const *usersolvers, void *ctx,
ctxnew_t ctxnew, ctxfree_t ctxfree);
usersolver_t const *usersolvers, validator_t valid,
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
void latin_solver_debug(unsigned char *cube, int o);

View file

@ -258,6 +258,8 @@ static const char *validate_params(const game_params *params, bool full)
*/
if (full && params->unique && (params->w <= 2 || params->h <= 2))
return "Width and height must both be greater than two";
if (params->n < 0)
return "Mine count may not be negative";
if (params->n > params->w * params->h - 9)
return "Too many mines for grid size";
if (params->n < 1)

View file

@ -20,6 +20,7 @@ enum {
COL_GRID,
COL_CURSOR,
COL_ERROR,
COL_CURSOR_GUIDE,
NCOLOURS
};
@ -1665,6 +1666,7 @@ static float *game_colours(frontend *fe, int *ncolours)
ret[COL_TEXT * 3 + i] = 0.0F;
ret[COL_FULL * 3 + i] = 0.0F;
ret[COL_EMPTY * 3 + i] = 1.0F;
ret[COL_CURSOR_GUIDE * 3 + i] = 0.5F;
}
ret[COL_CURSOR * 3 + 0] = 1.0F;
ret[COL_CURSOR * 3 + 1] = 0.25F;
@ -1891,6 +1893,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
*/
for (i = 0; i < state->common->w + state->common->h; i++) {
int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT;
if (colour == COL_TEXT && ((cx >= 0 && i == cx) || (cy >= 0 && i == cy + ds->w))) {
colour = COL_CURSOR_GUIDE;
}
if (ds->numcolours[i] != colour) {
draw_numbers(dr, ds, state, i, true, colour);
ds->numcolours[i] = colour;

View file

@ -3,6 +3,8 @@
* setup and layout.
*/
#include <assert.h>
#include "puzzles.h"
struct puzzle {
@ -88,7 +90,7 @@ void document_add_puzzle(document *doc, const game *game, game_params *par,
doc->got_solns = true;
}
static void get_puzzle_size(document *doc, struct puzzle *pz,
static void get_puzzle_size(const document *doc, struct puzzle *pz,
float *w, float *h, float *scale)
{
float ww, hh, ourscale;
@ -115,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 pages, passes;
int page, pass;
int pageno;
ppp = doc->pw * doc->ph;
pages = (doc->npuzzles + ppp - 1) / ppp;
passes = (doc->got_solns ? 2 : 1);
print_begin_doc(dr, pages * passes);
return pages * passes;
}
pageno = 1;
for (pass = 0; pass < passes; pass++) {
for (page = 0; page < pages; page++) {
/*
* Begin a document.
*/
void document_begin(const document *doc, drawing *dr)
{
print_begin_doc(dr, document_npages(doc));
}
/*
* End a document.
*/
void document_end(const document *doc, drawing *dr)
{
print_end_doc(dr);
}
/*
* Print a single page of a document.
*/
void document_print_page(const document *doc, drawing *dr, int page_nr)
{
int ppp; /* puzzles per page */
int pages;
int page, pass;
int pageno;
int i, n, offset;
float colsum, rowsum;
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;
n = min(ppp, doc->npuzzles - offset);
print_begin_page(dr, pageno);
for (i = 0; i < doc->pw; i++)
doc->colwid[i] = 0;
for (i = 0; i < doc->ph; i++)
@ -239,9 +277,17 @@ void document_print(document *doc, drawing *dr)
}
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);
}

View file

@ -9,8 +9,6 @@
#include "puzzles.h"
#define ROOT2 1.414213562
struct psdata {
FILE *fp;
bool colour;

View file

@ -3360,7 +3360,8 @@ This software is \i{copyright} 2004-2014 Simon Tatham.
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
Quevillon and Asher Gordon.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files

View file

@ -11,6 +11,7 @@
#include <stdbool.h>
#define PI 3.141592653589793238462643383279502884197169399
#define ROOT2 1.414213562373095048801688724209698078569672
#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
@ -530,7 +531,11 @@ document *document_new(int pw, int ph, float userscale);
void document_free(document *doc);
void document_add_puzzle(document *doc, const game *game, game_params *par,
game_state *st, game_state *st2);
void document_print(document *doc, drawing *dr);
int document_npages(const document *doc);
void document_begin(const document *doc, drawing *dr);
void document_end(const document *doc, drawing *dr);
void document_print_page(const document *doc, drawing *dr, int page_nr);
void document_print(const document *doc, drawing *dr);
/*
* ps.c

View file

@ -574,6 +574,38 @@ static int solver_hard(struct latin_solver *solver, void *vctx)
#define SOLVER(upper,title,func,lower) func,
static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) };
static bool towers_valid(struct latin_solver *solver, void *vctx)
{
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
int w = ctx->w;
int c, i, n, best, clue, start, step;
for (c = 0; c < 4*w; c++) {
clue = ctx->clues[c];
if (!clue)
continue;
STARTSTEP(start, step, c, w);
n = best = 0;
for (i = 0; i < w; i++) {
if (solver->grid[start+i*step] > best) {
best = solver->grid[start+i*step];
n++;
}
}
if (n != clue) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
printf("%*sclue %s %d is violated\n",
solver_recurse_depth*4, "",
cluepos[c/w], c%w+1);
#endif
return false;
}
}
return true;
}
static int solver(int w, int *clues, digit *soln, int maxdiff)
{
int ret;
@ -589,7 +621,7 @@ static int solver(int w, int *clues, digit *soln, int maxdiff)
ret = latin_solver(soln, w, maxdiff,
DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
DIFF_EXTREME, DIFF_UNREASONABLE,
towers_solvers, &ctx, NULL, NULL);
towers_solvers, towers_valid, &ctx, NULL, NULL);
sfree(ctx.iscratch);
sfree(ctx.dscratch);

View file

@ -8,6 +8,9 @@ tracks : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res
ALL += tracks[COMBINED] TRACKS_EXTRA
trackssolver : [U] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
trackssolver : [C] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE
!begin am gtk
GAMES += tracks
!end

View file

@ -12,6 +12,7 @@
* #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
* #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1
* #115 8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1
* #942 8x8:n5iCfAzAe,2,2,S5,5,3,5,4,5,4,5,2,S5,3,4,5,3
*/
#include <stdio.h>
@ -31,7 +32,9 @@
*/
#define DIFFLIST(A) \
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 TITLE(upper,title,lower) #title,
@ -65,10 +68,12 @@ static const struct game_params tracks_presets[] = {
{10, 8, DIFF_TRICKY, 1 },
{10, 10, DIFF_EASY, 1},
{10, 10, DIFF_TRICKY, 1},
{10, 10, DIFF_HARD, 1},
{15, 10, DIFF_EASY, 1},
{15, 10, DIFF_TRICKY, 1},
{15, 15, DIFF_EASY, 1},
{15, 15, DIFF_TRICKY, 1},
{15, 15, DIFF_HARD, 1},
};
static bool game_fetch_preset(int i, char **name, game_params **params)
@ -452,7 +457,7 @@ start:
state->numbers->col_s = px;
}
static int tracks_solve(game_state *state, int diff);
static int tracks_solve(game_state *state, int diff, int *max_diff_out);
static void debug_state(game_state *state, const char *what);
/* Clue-setting algorithm:
@ -533,6 +538,26 @@ static game_state *copy_and_strip(const game_state *state, game_state *ret, int
return ret;
}
#ifdef STANDALONE_SOLVER
#include <stdarg.h>
static FILE *solver_diagnostics_fp = NULL;
static void solver_diagnostic(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(solver_diagnostics_fp, fmt, ap);
va_end(ap);
fputc('\n', solver_diagnostics_fp);
}
#define solverdebug(printf_params) do { \
if (solver_diagnostics_fp) { \
solver_diagnostic printf_params; \
} \
} while (0)
#else
#define solverdebug(printf_params) ((void)0)
#endif
static int solve_progress(const game_state *state) {
int i, w = state->p.w, h = state->p.h, progress = 0;
@ -575,6 +600,7 @@ static int add_clues(game_state *state, random_state *rs, int diff)
int *positions = snewn(w*h, int), npositions = 0;
int *nedges_previous_solve = snewn(w*h, int);
game_state *scratch = dup_game(state);
int diff_used;
debug_state(state, "gen: Initial board");
@ -591,17 +617,13 @@ static int add_clues(game_state *state, random_state *rs, int diff)
/* First, check whether the puzzle is already either too easy, or just right */
scratch = copy_and_strip(state, scratch, -1);
if (diff > 0) {
sr = tracks_solve(scratch, diff-1);
if (sr < 0)
assert(!"Generator should not have created impossible puzzle");
if (sr > 0) {
sr = tracks_solve(scratch, diff, &diff_used);
if (diff_used < diff) {
ret = -1; /* already too easy, even without adding clues. */
debug(("gen: ...already too easy, need new board."));
goto done;
}
}
sr = tracks_solve(scratch, diff);
if (sr < 0)
assert(!"Generator should not have created impossible puzzle");
if (sr > 0) {
@ -629,12 +651,10 @@ static int add_clues(game_state *state, random_state *rs, int diff)
if (check_phantom_moves(scratch))
continue; /* adding a clue here would add phantom track */
if (diff > 0) {
if (tracks_solve(scratch, diff-1) > 0) {
if (tracks_solve(scratch, diff, &diff_used) > 0) {
if (diff_used < diff) {
continue; /* adding a clue here makes it too easy */
}
}
if (tracks_solve(scratch, diff) > 0) {
/* we're now soluble (and we weren't before): add this clue, and then
start stripping clues */
debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w));
@ -676,7 +696,7 @@ strip_clues:
if (check_phantom_moves(scratch))
continue; /* removing a clue here would add phantom track */
if (tracks_solve(scratch, diff) > 0) {
if (tracks_solve(scratch, diff, NULL) > 0) {
debug(("gen: ... removing clue at (%d,%d), still soluble without it", i%w, i/w));
state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */
}
@ -686,6 +706,7 @@ strip_clues:
done:
sfree(positions);
sfree(nedges_previous_solve);
free_game(scratch);
return ret;
}
@ -780,7 +801,7 @@ newpath:
}
*p++ = '\0';
ret = tracks_solve(state, DIFFCOUNT);
ret = tracks_solve(state, DIFFCOUNT, NULL);
assert(ret >= 0);
free_game(state);
@ -882,6 +903,10 @@ static game_state *new_game(midend *me, const game_params *params, const char *d
return state;
}
struct solver_scratch {
int *dsf;
};
static int solve_set_sflag(game_state *state, int x, int y,
unsigned int f, const char *why)
{
@ -889,10 +914,10 @@ static int solve_set_sflag(game_state *state, int x, int y,
if (state->sflags[i] & f)
return 0;
debug(("solve: square (%d,%d) -> %s: %s",
solverdebug(("square (%d,%d) -> %s: %s",
x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
state->impossible = true;
}
state->sflags[i] |= f;
@ -906,11 +931,11 @@ static int solve_set_eflag(game_state *state, int x, int y, int d,
if (sf & f)
return 0;
debug(("solve: edge (%d,%d)/%c -> %s: %s", x, y,
solverdebug(("edge (%d,%d)/%c -> %s: %s", x, y,
(d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R',
(f == S_TRACK ? "TRACK" : "NOTRACK"), why));
if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
state->impossible = true;
}
S_E_SET(state, x, y, d, f);
@ -1063,7 +1088,7 @@ static int solve_check_single_sub(game_state *state, int si, int id, int n,
if (ctrack != (target-1)) return 0;
if (nperp > 0 || n1edge != 1) return 0;
debug(("check_single from (%d,%d): 1 match from (%d,%d)",
solverdebug(("check_single from (%d,%d): 1 match from (%d,%d)",
si%w, si/w, i1edge%w, i1edge/w));
/* We have a match: anything that's more than 1 away from this square
@ -1120,12 +1145,12 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n,
}
if (nloose > (target - e2count)) {
debug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE",
solverdebug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE",
what, si%w, si/w, nloose, target-e2count));
state->impossible = true;
}
if (nloose > 0 && nloose == (target - e2count)) {
debug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.",
solverdebug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.",
what, si%w, si/w, nloose));
for (j = 0, i = si; j < n; j++, i += id) {
if (!(state->sflags[i] & S_MARK))
@ -1146,7 +1171,7 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n,
}
}
if (nloose == 1 && (target - e2count) == 2 && nperp == 0) {
debug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel",
solverdebug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel",
what, si%w, si/w));
for (j = 0, i = si; j < n; j++, i += id) {
if (!(state->sflags[i] & S_MARK))
@ -1176,6 +1201,110 @@ static int solve_check_loose_ends(game_state *state)
return did;
}
static void solve_check_neighbours_count(
game_state *state, int start, int step, int n, int clueindex,
bool *onefill, bool *oneempty)
{
int to_fill = state->numbers->numbers[clueindex];
int to_empty = n - to_fill;
int i;
for (i = 0; i < n; i++) {
int p = start + i*step;
if (state->sflags[p] & S_TRACK)
to_fill--;
if (state->sflags[p] & S_NOTRACK)
to_empty--;
}
*onefill = (to_fill == 1);
*oneempty = (to_empty == 1);
}
static int solve_check_neighbours_try(game_state *state, int x, int y,
int X, int Y, bool onefill,
bool oneempty, unsigned dir,
const char *what)
{
int w = state->p.w, p = y*w+x, P = Y*w+X;
/*
* We're given a neighbouring pair of squares p,P, with 'dir'
* being the direction from the former to the latter. We aim to
* spot situations in which, if p is a track square, then P must
* also be one (because p doesn't have enough free exits to avoid
* using the one that goes towards P).
*
* Then, if the target number of track squares on their shared
* row/column says that there's only one track square left to
* place, it can't be p, because P would have to be one too,
* violating the clue. So in that situation we can mark p as
* unfilled. Conversely, if there's only one _non_-track square
* left to place, it can't be P, so we can mark P as filled.
*/
if ((state->sflags[p] | state->sflags[P]) & (S_TRACK | S_NOTRACK))
return 0; /* no need: we already know something about these squares */
int possible_exits_except_dir = nbits[
ALLDIR & ~dir & ~S_E_DIRS(state, x, y, E_NOTRACK)];
if (possible_exits_except_dir >= 2)
return 0; /* square p need not connect to P, even if it is filled */
/* OK, now we know that if p is filled, P must be filled too. */
int did = 0;
if (onefill) {
/* But at most one of them can be filled, so it can't be p. */
state->sflags[p] |= S_NOTRACK;
solverdebug(("square (%d,%d) -> NOTRACK: otherwise, that and (%d,%d) "
"would make too many TRACK in %s", x, y, X, Y, what));
did++;
}
if (oneempty) {
/* Alternatively, at least one of them _must_ be filled, so P
* must be. */
state->sflags[P] |= S_TRACK;
solverdebug(("square (%d,%d) -> TRACK: otherwise, that and (%d,%d) "
"would make too many NOTRACK in %s", X, Y, x, y, what));
did++;
}
return did;
}
static int solve_check_neighbours(game_state *state, bool both_ways)
{
int w = state->p.w, h = state->p.h, x, y, did = 0;
bool onefill, oneempty;
for (x = 0; x < w; x++) {
solve_check_neighbours_count(state, x, w, h, x, &onefill, &oneempty);
if (!both_ways)
oneempty = false; /* disable the harder version of the deduction */
if (!onefill && !oneempty)
continue;
for (y = 0; y+1 < h; y++) {
did += solve_check_neighbours_try(state, x, y, x, y+1,
onefill, oneempty, D, "column");
did += solve_check_neighbours_try(state, x, y+1, x, y,
onefill, oneempty, U, "column");
}
}
for (y = 0; y < h; y++) {
solve_check_neighbours_count(state, y*w, 1, w, w+y,
&onefill, &oneempty);
if (!both_ways)
oneempty = false; /* disable the harder version of the deduction */
if (!onefill && !oneempty)
continue;
for (x = 0; x+1 < w; x++) {
did += solve_check_neighbours_try(state, x, y, x+1, y,
onefill, oneempty, R, "row");
did += solve_check_neighbours_try(state, x+1, y, x, y,
onefill, oneempty, L, "row");
}
}
return did;
}
static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
int *dsf, int startc, int endc)
{
@ -1195,7 +1324,7 @@ static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop");
}
if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) {
debug(("Adding link at (%d,%d) would join start to end", x, y));
solverdebug(("Adding link at (%d,%d) would join start to end", x, y));
/* We mustn't join the start to the end if:
- there are other bits of track that aren't attached to either end
- the clues are not fully satisfied yet
@ -1287,10 +1416,145 @@ static void solve_discount_edge(game_state *state, int x, int y, int d)
solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge");
}
static int tracks_solve(game_state *state, int diff)
static int solve_bridge_sub(game_state *state, int x, int y, int d,
struct solver_scratch *sc)
{
/*
* Imagine a graph on the squares of the grid, with an edge
* connecting neighbouring squares only if it's not yet known
* whether there's a track between them.
*
* This function is called if the edge between x,y and X,Y is a
* bridge in that graph: that is, it's not part of any loop in the
* graph, or equivalently, removing it would increase the number
* of connected components in the graph.
*
* In that situation, we can fill in the edge by a parity
* argument. Construct a closed loop of edges in the grid, all of
* whose states are known except this one. The track starts and
* ends outside this loop, so it must cross the boundary of the
* loop an even number of times. So if we count up how many times
* the track is known to cross the edges of our loop, then we can
* fill in the last edge in whichever way makes that number even.
*
* In fact, there's not even any need to go to the effort of
* constructing a _single_ closed loop. The simplest thing is to
* delete the bridge edge from the graph, find a connected
* component of the reduced graph whose boundary includes that
* edge, and take every edge separating that component from
* another. This may not lead to _exactly one_ cycle - the
* component could be non-simply connected and have a hole in the
* middle - but that doesn't matter, because the same parity
* constraint applies just as well with more than one disjoint
* loop.
*/
int w = state->p.w, h = state->p.h, wh = w*h;
int X = x + DX(d), Y = y + DY(d);
int xi, yi, di;
assert(d == D || d == R);
if (!sc->dsf)
sc->dsf = snew_dsf(wh);
dsf_init(sc->dsf, wh);
for (xi = 0; xi < w; xi++) {
for (yi = 0; yi < h; yi++) {
/* We expect to have been called with X,Y either to the
* right of x,y or below it, not the other way round. If
* that were not true, the tests in this loop to exclude
* the bridge edge would have to be twice as annoying. */
if (yi+1 < h && !S_E_FLAGS(state, xi, yi, D) &&
!(xi == x && yi == y && xi == X && yi+1 == Y))
dsf_merge(sc->dsf, yi*w+xi, (yi+1)*w+xi);
if (xi+1 < w && !S_E_FLAGS(state, xi, yi, R) &&
!(xi == x && yi == y && xi+1 == X && yi == Y))
dsf_merge(sc->dsf, yi*w+xi, yi*w+(xi+1));
}
}
int component = dsf_canonify(sc->dsf, y*w+x);
int parity = 0;
for (xi = 0; xi < w; xi++) {
for (yi = 0; yi < h; yi++) {
if (dsf_canonify(sc->dsf, yi*w+xi) != component)
continue;
for (di = 1; di < 16; di *= 2) {
int Xi = xi + DX(di), Yi = yi + DY(di);
if ((Xi < 0 || Xi >= w || Yi < 0 || Yi >= h ||
dsf_canonify(sc->dsf, Yi*w+Xi) != component) &&
(S_E_DIRS(state, xi, yi, E_TRACK) & di))
parity ^= 1;
}
}
}
solve_set_eflag(state, x, y, d, parity ? E_TRACK : E_NOTRACK, "parity");
return 1;
}
struct solve_bridge_neighbour_ctx {
game_state *state;
int x, y, dirs;
};
static int solve_bridge_neighbour(int vertex, void *vctx)
{
struct solve_bridge_neighbour_ctx *ctx =
(struct solve_bridge_neighbour_ctx *)vctx;
int w = ctx->state->p.w;
if (vertex >= 0) {
ctx->x = vertex % w;
ctx->y = vertex / w;
ctx->dirs = ALLDIR
& ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_TRACK)
& ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_NOTRACK);
}
unsigned dir = ctx->dirs & -ctx->dirs; /* isolate lowest set bit */
if (!dir)
return -1;
ctx->dirs &= ~dir;
int xr = ctx->x + DX(dir), yr = ctx->y + DY(dir);
assert(0 <= xr && xr < w);
assert(0 <= yr && yr < ctx->state->p.h);
return yr * w + xr;
}
static int solve_check_bridge_parity(game_state *state,
struct solver_scratch *sc)
{
int w = state->p.w, h = state->p.h, wh = w*h;
struct findloopstate *fls;
struct solve_bridge_neighbour_ctx ctx[1];
int x, y, did = 0;
ctx->state = state;
fls = findloop_new_state(wh);
findloop_run(fls, wh, solve_bridge_neighbour, ctx);
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
if (y+1 < h && !findloop_is_loop_edge(fls, y*w+x, (y+1)*w+x))
did += solve_bridge_sub(state, x, y, D, sc);
if (x+1 < w && !findloop_is_loop_edge(fls, y*w+x, y*w+(x+1)))
did += solve_bridge_sub(state, x, y, R, sc);
}
}
findloop_free_state(fls);
return did;
}
static int tracks_solve(game_state *state, int diff, int *max_diff_out)
{
int x, y, w = state->p.w, h = state->p.h;
bool didsth;
struct solver_scratch sc[1];
int max_diff = DIFF_EASY;
sc->dsf = NULL;
debug(("solve..."));
state->impossible = false;
@ -1305,20 +1569,36 @@ static int tracks_solve(game_state *state, int diff)
solve_discount_edge(state, w-1, y, R);
}
while (1) {
didsth = false;
while (!state->impossible) {
didsth |= solve_update_flags(state);
didsth |= solve_count_clues(state);
didsth |= solve_check_loop(state);
/* Can't use do ... while (0) because we need a 'continue' in this macro */
#define TRY(curr_diff, funcall) \
if (diff >= (curr_diff) && (funcall)) { \
if (max_diff < curr_diff) \
max_diff = curr_diff; \
continue; \
} else ((void)0)
if (diff >= DIFF_TRICKY) {
didsth |= solve_check_single(state);
didsth |= solve_check_loose_ends(state);
TRY(DIFF_EASY, solve_update_flags(state));
TRY(DIFF_EASY, solve_count_clues(state));
TRY(DIFF_EASY, solve_check_loop(state));
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;
}
@ -1379,11 +1659,11 @@ static char *solve_game(const game_state *state, const game_state *currstate,
char *move;
solved = dup_game(currstate);
ret = tracks_solve(solved, DIFFCOUNT);
ret = tracks_solve(solved, DIFFCOUNT, NULL);
if (ret < 1) {
free_game(solved);
solved = dup_game(state);
ret = tracks_solve(solved, DIFFCOUNT);
ret = tracks_solve(solved, DIFFCOUNT, NULL);
}
if (ret < 1) {
@ -2094,7 +2374,7 @@ static game_state *execute_move(const game_state *state, const char *move)
goto badmove;
move += n;
} else if (c == 'H') {
tracks_solve(ret, DIFFCOUNT);
tracks_solve(ret, DIFFCOUNT, NULL);
move++;
} else {
goto badmove;
@ -2675,4 +2955,87 @@ const struct game thegame = {
0, /* flags */
};
#ifdef STANDALONE_SOLVER
int main(int argc, char **argv)
{
game_params *p;
game_state *s;
char *id = NULL, *desc;
int maxdiff = DIFFCOUNT, diff_used;
const char *err;
bool diagnostics = false, grade = false;
int retd;
while (--argc > 0) {
char *p = *++argv;
if (!strcmp(p, "-v")) {
diagnostics = true;
} else if (!strcmp(p, "-g")) {
grade = true;
} else if (!strncmp(p, "-d", 2) && p[2] && !p[3]) {
int i;
bool bad = true;
for (i = 0; i < lenof(tracks_diffchars); i++)
if (tracks_diffchars[i] == p[2]) {
bad = false;
maxdiff = i;
break;
}
if (bad) {
fprintf(stderr, "%s: unrecognised difficulty `%c'\n",
argv[0], p[2]);
return 1;
}
} else if (*p == '-') {
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
return 1;
} else {
id = p;
}
}
if (!id) {
fprintf(stderr, "usage: %s [-v | -g] <game_id>\n", argv[0]);
return 1;
}
desc = strchr(id, ':');
if (!desc) {
fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
return 1;
}
*desc++ = '\0';
p = default_params();
decode_params(p, id);
err = validate_desc(p, desc);
if (err) {
fprintf(stderr, "%s: %s\n", argv[0], err);
return 1;
}
s = new_game(NULL, p, desc);
solver_diagnostics_fp = (diagnostics ? stdout : NULL);
retd = tracks_solve(s, maxdiff, &diff_used);
if (retd < 0) {
printf("Puzzle is inconsistent\n");
} else if (grade) {
printf("Difficulty rating: %s\n",
(retd == 0 ? "Ambiguous" : tracks_diffnames[diff_used]));
} else {
char *text = game_text_format(s);
fputs(text, stdout);
sfree(text);
if (retd == 0)
printf("Could not deduce a unique solution\n");
}
free_game(s);
free_params(p);
return 0;
}
#endif
/* vim: set shiftwidth=4 tabstop=8: */

View file

@ -552,6 +552,12 @@ static char *game_text_format(const game_state *state)
int i, x, y, col, maxlen;
bool o = state->orientable;
/* Pedantic check: ensure buf is large enough to format an int in
* decimal, using the bound log10(2) < 1/3. (Obviously in practice
* int is not going to be larger than even 32 bits any time soon,
* but.) */
assert(sizeof(buf) >= 1 + sizeof(int) * CHAR_BIT/3);
/*
* First work out how many characters we need to display each
* number. We're pretty flexible on grid contents here, so we
@ -563,6 +569,11 @@ static char *game_text_format(const game_state *state)
if (col < x) col = x;
}
/* Reassure sprintf-checking compilers like gcc that the field
* width we've just computed is not now excessive */
if (col >= sizeof(buf))
col = sizeof(buf)-1;
/*
* Now we know the exact total size of the grid we're going to
* produce: it's got h rows, each containing w lots of col+o,

View file

@ -816,6 +816,74 @@ static int solver_set(struct latin_solver *solver, void *vctx)
#define SOLVER(upper,title,func,lower) func,
static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) };
static bool unequal_valid(struct latin_solver *solver, void *vctx)
{
struct solver_ctx *ctx = (struct solver_ctx *)vctx;
if (ctx->state->mode == MODE_ADJACENT) {
int o = solver->o;
int x, y, nx, ny, v, nv, i;
for (x = 0; x+1 < o; x++) {
for (y = 0; y+1 < o; y++) {
v = grid(x, y);
for (i = 0; i < 4; i++) {
bool is_adj, should_be_adj;
should_be_adj =
(GRID(ctx->state, flags, x, y) & adjthan[i].f);
nx = x + adjthan[i].dx, ny = y + adjthan[i].dy;
if (nx < 0 || ny < 0 || nx >= o || ny >= o)
continue;
nv = grid(nx, ny);
is_adj = (labs(v - nv) == 1);
if (is_adj && !should_be_adj) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
printf("%*s(%d,%d):%d and (%d,%d):%d have "
"adjacent values, but should not\n",
solver_recurse_depth*4, "",
x+1, y+1, v, nx+1, ny+1, nv);
#endif
return false;
}
if (!is_adj && should_be_adj) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
printf("%*s(%d,%d):%d and (%d,%d):%d do not have "
"adjacent values, but should\n",
solver_recurse_depth*4, "",
x+1, y+1, v, nx+1, ny+1, nv);
#endif
return false;
}
}
}
}
} else {
int i;
for (i = 0; i < ctx->nlinks; i++) {
struct solver_link *link = &ctx->links[i];
int gv = grid(link->gx, link->gy);
int lv = grid(link->lx, link->ly);
if (gv <= lv) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
printf("%*s(%d,%d):%d should be greater than (%d,%d):%d, "
"but is not\n", solver_recurse_depth*4, "",
link->gx+1, link->gy+1, gv,
link->lx+1, link->ly+1, lv);
#endif
return false;
}
}
}
return true;
}
static int solver_state(game_state *state, int maxdiff)
{
struct solver_ctx *ctx = new_ctx(state);
@ -827,7 +895,8 @@ static int solver_state(game_state *state, int maxdiff)
diff = latin_solver_main(&solver, maxdiff,
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
DIFF_EXTREME, DIFF_RECURSIVE,
unequal_solvers, ctx, clone_ctx, free_ctx);
unequal_solvers, unequal_valid, ctx,
clone_ctx, free_ctx);
memcpy(state->hints, solver.cube, state->order*state->order*state->order);
@ -2155,7 +2224,8 @@ static int solve(game_params *p, char *desc, int debug)
diff = latin_solver_main(&solver, DIFF_RECURSIVE,
DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
DIFF_EXTREME, DIFF_RECURSIVE,
unequal_solvers, ctx, clone_ctx, free_ctx);
unequal_solvers, unequal_valid, ctx,
clone_ctx, free_ctx);
free_ctx(ctx);

View file

@ -43,7 +43,7 @@
#define DIFFLIST(A) \
A(TRIVIAL,Trivial,NULL,t) \
A(NORMAL,Normal,solver_normal,n) \
A(HARD,Hard,NULL,h) \
A(HARD,Hard,solver_hard,h) \
A(EXTREME,Extreme,NULL,x) \
A(UNREASONABLE,Unreasonable,NULL,u)
#define ENUM(upper,title,func,lower) DIFF_ ## upper,
@ -280,6 +280,23 @@ static const char *validate_params(const game_params *params, bool full)
* Solver.
*/
static int find_identity(struct latin_solver *solver)
{
int w = solver->o;
digit *grid = solver->grid;
int i, j;
for (i = 0; i < w; i++)
for (j = 0; j < w; j++) {
if (grid[i*w+j] == i+1)
return j+1;
if (grid[i*w+j] == j+1)
return i+1;
}
return 0;
}
static int solver_normal(struct latin_solver *solver, void *vctx)
{
int w = solver->o;
@ -295,9 +312,9 @@ static int solver_normal(struct latin_solver *solver, void *vctx)
* So we pick any a,b,c we like; then if we know ab, bc, and
* (ab)c we can fill in a(bc).
*/
for (i = 1; i < w; i++)
for (j = 1; j < w; j++)
for (k = 1; k < w; k++) {
for (i = 0; i < w; i++)
for (j = 0; j < w; j++)
for (k = 0; k < w; k++) {
if (!grid[i*w+j] || !grid[j*w+k])
continue;
if (grid[(grid[i*w+j]-1)*w+k] &&
@ -358,12 +375,206 @@ static int solver_normal(struct latin_solver *solver, void *vctx)
}
}
/*
* Fill in the row and column for the group identity, if it's not
* already known and if we've just found out what it is.
*/
i = find_identity(solver);
if (i) {
bool done_something = false;
for (j = 1; j <= w; j++) {
if (!grid[(i-1)*w+(j-1)] || !grid[(j-1)*w+(i-1)]) {
done_something = true;
}
}
if (done_something) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*s%s is the group identity\n",
solver_recurse_depth*4, "", names[i-1]);
}
#endif
for (j = 1; j <= w; j++) {
if (!grid[(j-1)*w+(i-1)]) {
if (!cube(i-1, j-1, j)) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*s but %s cannot go at (%d,%d) - "
"contradiction!\n",
solver_recurse_depth*4, "",
names[j-1], i, j);
}
#endif
return -1;
}
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*s placing %s at (%d,%d)\n",
solver_recurse_depth*4, "",
names[j-1], i, j);
}
#endif
latin_solver_place(solver, i-1, j-1, j);
}
if (!grid[(i-1)*w+(j-1)]) {
if (!cube(j-1, i-1, j)) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*s but %s cannot go at (%d,%d) - "
"contradiction!\n",
solver_recurse_depth*4, "",
names[j-1], j, i);
}
#endif
return -1;
}
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*s placing %s at (%d,%d)\n",
solver_recurse_depth*4, "",
names[j-1], j, i);
}
#endif
latin_solver_place(solver, j-1, i-1, j);
}
}
return 1;
}
}
return 0;
}
static int solver_hard(struct latin_solver *solver, void *vctx)
{
bool done_something = false;
int w = solver->o;
#ifdef STANDALONE_SOLVER
char **names = solver->names;
#endif
int i, j;
/*
* In identity-hidden mode, systematically rule out possibilities
* for the group identity.
*
* In solver_normal, we used the fact that any filled square in
* the grid whose contents _does_ match one of the elements it's
* the product of - that is, ab=a or ab=b - tells you immediately
* that the other element is the identity.
*
* Here, we use the flip side of that: any filled square in the
* grid whose contents does _not_ match either its row or column -
* that is, if ab is neither a nor b - tells you immediately that
* _neither_ of those elements is the identity. And if that's
* true, then we can also immediately rule out the possibility
* that it acts as the identity on any element at all.
*/
for (i = 0; i < w; i++) {
bool i_can_be_id = true;
#ifdef STANDALONE_SOLVER
char title[80];
#endif
for (j = 0; j < w; j++) {
if (grid(i,j) && grid(i,j) != j+1) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
sprintf(title, "%s cannot be the identity: "
"%s%s = %s =/= %s", names[i], names[i], names[j],
names[grid(i,j)-1], names[j]);
#endif
i_can_be_id = false;
break;
}
if (grid(j,i) && grid(j,i) != j+1) {
#ifdef STANDALONE_SOLVER
if (solver_show_working)
sprintf(title, "%s cannot be the identity: "
"%s%s = %s =/= %s", names[i], names[j], names[i],
names[grid(j,i)-1], names[j]);
#endif
i_can_be_id = false;
break;
}
}
if (!i_can_be_id) {
/* Now rule out ij=j or ji=j for all j. */
for (j = 0; j < w; j++) {
if (cube(i, j, j+1)) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
if (title[0]) {
printf("%*s%s\n", solver_recurse_depth*4, "",
title);
title[0] = '\0';
}
printf("%*s ruling out %s at (%d,%d)\n",
solver_recurse_depth*4, "", names[j], i, j);
}
#endif
cube(i, j, j+1) = false;
}
if (cube(j, i, j+1)) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
if (title[0]) {
printf("%*s%s\n", solver_recurse_depth*4, "",
title);
title[0] = '\0';
}
printf("%*s ruling out %s at (%d,%d)\n",
solver_recurse_depth*4, "", names[j], j, i);
}
#endif
cube(j, i, j+1) = false;
}
}
}
}
return done_something;
}
#define SOLVER(upper,title,func,lower) func,
static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) };
static bool group_valid(struct latin_solver *solver, void *ctx)
{
int w = solver->o;
#ifdef STANDALONE_SOLVER
char **names = solver->names;
#endif
int i, j, k;
for (i = 0; i < w; i++)
for (j = 0; j < w; j++)
for (k = 0; k < w; k++) {
int ij = grid(i, j) - 1;
int jk = grid(j, k) - 1;
int ij_k = grid(ij, k) - 1;
int i_jk = grid(i, jk) - 1;
if (ij_k != i_jk) {
#ifdef STANDALONE_SOLVER
if (solver_show_working) {
printf("%*sfailure of associativity: "
"(%s%s)%s = %s%s = %s but "
"%s(%s%s) = %s%s = %s\n",
solver_recurse_depth*4, "",
names[i], names[j], names[k],
names[ij], names[k], names[ij_k],
names[i], names[j], names[k],
names[i], names[jk], names[i_jk]);
}
#endif
return false;
}
}
return true;
}
static int solver(const game_params *params, digit *grid, int maxdiff)
{
int w = params->w;
@ -387,7 +598,7 @@ static int solver(const game_params *params, digit *grid, int maxdiff)
ret = latin_solver_main(&solver, maxdiff,
DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
DIFF_EXTREME, DIFF_UNREASONABLE,
group_solvers, NULL, NULL, NULL);
group_solvers, group_valid, NULL, NULL, NULL);
latin_solver_free(&solver);
@ -2183,6 +2394,11 @@ int main(int argc, char **argv)
}
if (diff == DIFFCOUNT) {
if (really_show_working) {
solver_show_working = true;
memcpy(grid, s->grid, p->w * p->w);
ret = solver(&s->par, grid, DIFFCOUNT - 1);
}
if (grade)
printf("Difficulty rating: ambiguous\n");
else

View file

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