mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
Files

I've only just realised that there's a false-positive bug in the latin.c solver framework. It's designed to solve puzzles in which the solution is a latin square but with some additional constraints provided by the individual puzzle, and so during solving, it runs a mixture of its own standard deduction functions that apply to any latin-square puzzle and extra functions provided by the client puzzle to do deductions based on the extra clues or constraints. But what happens if the _last_ move in the solving process is performed by one of the latin.c built-in methods, and it causes a violation of the client puzzle's extra constraints? Nothing will ever notice, and so the solver will report that the puzzle has a solution when it actually has none. An example is the Group game id 12i:m12b9a1zd9i6d10c3y2l11q4r . This was reported by 'groupsolver -g' as being ambiguous. But if you look at the two 'solutions' reported in the verbose diagnostics, one of them is arrant nonsense: it has no identity element at all, and therefore, it fails associativity all over the place. Actually that puzzle _does_ have a unique solution. This bug has been around for ages, and nobody has reported a problem. For recursive solving, that's not much of a surprise, because it would cause a spurious accusation of ambiguity, so that at generation time some valid puzzles would be wrongly discarded, and you'd never see them. But at non-recursive levels, I can't see a reason why this bug _couldn't_ have led one of the games to present an actually impossible puzzle believing it to be soluble. Possibly this never came up because the other clients of latin.c are more forgiving of this error in some way. For example, they might all be very likely to use their extra clues early in the solving process, so that the requirements are already baked in by the time the final grid square is filled. I don't know! Anyway. The fix is to introduce last-minute client-side validation: whenever the centralised latin_solver thinks it's come up with a filled grid, it should present it to a puzzle-specific validator function and check that it's _really_ a legal solution. This commit does the plumbing for all of that: it introduces the new validator function as one of the many parameters to latin_solver, and arranges to call it in an appropriate way during the solving process. But all the per-puzzle validation functions are empty, for the moment.
124 lines
4.2 KiB
C
124 lines
4.2 KiB
C
#ifndef LATIN_H
|
|
#define LATIN_H
|
|
|
|
#include "puzzles.h"
|
|
|
|
typedef unsigned char digit;
|
|
|
|
/* --- Solver structures, definitions --- */
|
|
|
|
#ifdef STANDALONE_SOLVER
|
|
extern int solver_show_working, solver_recurse_depth;
|
|
#endif
|
|
|
|
struct latin_solver {
|
|
int o; /* order of latin square */
|
|
unsigned char *cube; /* o^3, indexed by x, y, and digit:
|
|
true in that position indicates a possibility */
|
|
digit *grid; /* o^2, indexed by x and y: for final deductions */
|
|
|
|
unsigned char *row; /* o^2: row[y*cr+n-1] true if n is in row y */
|
|
unsigned char *col; /* o^2: col[x*cr+n-1] true if n is in col x */
|
|
|
|
#ifdef STANDALONE_SOLVER
|
|
char **names; /* o: names[n-1] gives name of 'digit' n */
|
|
#endif
|
|
};
|
|
#define cubepos(x,y,n) (((x)*solver->o+(y))*solver->o+(n)-1)
|
|
#define cube(x,y,n) (solver->cube[cubepos(x,y,n)])
|
|
|
|
#define gridpos(x,y) ((y)*solver->o+(x))
|
|
#define grid(x,y) (solver->grid[gridpos(x,y)])
|
|
|
|
|
|
/* --- Solver individual strategies --- */
|
|
|
|
/* Place a value at a specific location. */
|
|
void latin_solver_place(struct latin_solver *solver, int x, int y, int n);
|
|
|
|
/* Positional elimination. */
|
|
int latin_solver_elim(struct latin_solver *solver, int start, int step
|
|
#ifdef STANDALONE_SOLVER
|
|
, const char *fmt, ...
|
|
#endif
|
|
);
|
|
|
|
struct latin_solver_scratch; /* private to latin.c */
|
|
/* Set elimination */
|
|
int latin_solver_set(struct latin_solver *solver,
|
|
struct latin_solver_scratch *scratch,
|
|
int start, int step1, int step2
|
|
#ifdef STANDALONE_SOLVER
|
|
, const char *fmt, ...
|
|
#endif
|
|
);
|
|
|
|
/* Forcing chains */
|
|
int latin_solver_forcing(struct latin_solver *solver,
|
|
struct latin_solver_scratch *scratch);
|
|
|
|
|
|
/* --- Solver allocation --- */
|
|
|
|
/* Fills in (and allocates members for) a latin_solver struct.
|
|
* Will allocate members of snew, but not snew itself
|
|
* (allowing 'struct latin_solver' to be the first element in a larger
|
|
* struct, for example). */
|
|
void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o);
|
|
void latin_solver_free(struct latin_solver *solver);
|
|
|
|
/* Allocates scratch space (for _set and _forcing) */
|
|
struct latin_solver_scratch *
|
|
latin_solver_new_scratch(struct latin_solver *solver);
|
|
void latin_solver_free_scratch(struct latin_solver_scratch *scratch);
|
|
|
|
|
|
/* --- Solver guts --- */
|
|
|
|
/* Looped positional elimination */
|
|
int latin_solver_diff_simple(struct latin_solver *solver);
|
|
|
|
/* Looped set elimination; extreme permits use of the more difficult
|
|
* single-number elimination. */
|
|
int latin_solver_diff_set(struct latin_solver *solver,
|
|
struct latin_solver_scratch *scratch,
|
|
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);
|
|
|
|
/* Individual puzzles should use their enumerations for their
|
|
* own difficulty levels, ensuring they don't clash with these. */
|
|
enum { diff_impossible = 10, diff_ambiguous, diff_unfinished };
|
|
|
|
/* Externally callable function that allocates and frees a latin_solver */
|
|
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, 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, validator_t valid,
|
|
void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree);
|
|
|
|
void latin_solver_debug(unsigned char *cube, int o);
|
|
|
|
/* --- Generation and checking --- */
|
|
|
|
digit *latin_generate(int o, random_state *rs);
|
|
|
|
/* The order of the latin rectangle is max(w,h). */
|
|
digit *latin_generate_rect(int w, int h, random_state *rs);
|
|
|
|
bool latin_check(digit *sq, int order); /* true => not a latin square */
|
|
|
|
void latin_debug(digit *sq, int order);
|
|
|
|
#endif
|