From b3098efbc489be685ff644cde8dd6844f0198479 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Feb 2020 06:05:00 +0000 Subject: [PATCH] Tracks: add standalone solver program. Having one of these makes it much easier to debug what's going on when the solver can't solve something. Also, now the solver can grade the difficulty of a puzzle, it's useful to expose that feature in a command-line tool. --- .gitignore | 1 + tracks.R | 3 ++ tracks.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 116 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index bc9635b..d4ed12e3 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ /tents /tentssolver /tracks +/trackssolver /towers /towerssolver /twiddle diff --git a/tracks.R b/tracks.R index f88dfb0..8b0ac97 100644 --- a/tracks.R +++ b/tracks.R @@ -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 diff --git a/tracks.c b/tracks.c index 7431180..480f434 100644 --- a/tracks.c +++ b/tracks.c @@ -533,6 +533,26 @@ static game_state *copy_and_strip(const game_state *state, game_state *ret, int return ret; } +#ifdef STANDALONE_SOLVER +#include +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; @@ -884,10 +904,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; @@ -901,11 +921,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); @@ -1058,7 +1078,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 @@ -1115,12 +1135,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)) @@ -1141,7 +1161,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)) @@ -1190,7 +1210,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 @@ -2683,4 +2703,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] \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: */