ref: 6235f7fb3d1fd85c53ac7da9f461a6299c64ad5b
parent: 82ee3d42a4fd4907b8a4c98579d934176654eb7d
author: Simon Tatham <anakin@pobox.com>
date: Tue Apr 2 17:01:36 EDT 2019
Dominosa: introduce a difficulty system. Currently, there are just two difficulty levels. 'Basic' is the same as the old solver; 'Trivial' is the subset that guarantees the puzzle can be solved using only the two simplest deductions of all: 'this domino can only go in one place' and 'only one domino orientation can fit in this square'. The solver has also acquired a -g option, to grade the difficulty of an input puzzle using this system.
--- a/dominosa.c
+++ b/dominosa.c
@@ -84,6 +84,24 @@
#define FLASH_TIME 0.13F
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(X) \
+ X(TRIVIAL,Trivial,t) \
+ X(BASIC,Basic,b) \
+ X(AMBIGUOUS,Ambiguous,a) \
+ /* end of list */
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const dominosa_diffnames[] = { DIFFLIST(TITLE) };
+static char const dominosa_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
enum {
COL_BACKGROUND,
COL_TEXT,
@@ -98,7 +116,7 @@
struct game_params {
int n;
- bool unique;
+ int diff;
};
struct game_numbers {
@@ -125,35 +143,39 @@
game_params *ret = snew(game_params);
ret->n = 6;
- ret->unique = true;
+ ret->diff = DIFF_BASIC;
return ret;
}
-static bool game_fetch_preset(int i, char **name, game_params **params)
+static const struct game_params dominosa_presets[] = {
+ { 3, DIFF_TRIVIAL },
+ { 4, DIFF_TRIVIAL },
+ { 5, DIFF_TRIVIAL },
+ { 6, DIFF_TRIVIAL },
+ { 4, DIFF_BASIC },
+ { 5, DIFF_BASIC },
+ { 6, DIFF_BASIC },
+ { 7, DIFF_BASIC },
+ { 8, DIFF_BASIC },
+ { 9, DIFF_BASIC },
+};
+
+static bool game_fetch_preset(int i, char **name, game_params **params_out)
{
- game_params *ret;
- int n;
+ game_params *params;
char buf[80];
- switch (i) {
- case 0: n = 3; break;
- case 1: n = 4; break;
- case 2: n = 5; break;
- case 3: n = 6; break;
- case 4: n = 7; break;
- case 5: n = 8; break;
- case 6: n = 9; break;
- default: return false;
- }
+ if (i < 0 || i >= lenof(dominosa_presets))
+ return false;
- sprintf(buf, "Up to double-%d", n);
- *name = dupstr(buf);
+ params = snew(game_params);
+ *params = dominosa_presets[i]; /* structure copy */
- *params = ret = snew(game_params);
- ret->n = n;
- ret->unique = true;
+ sprintf(buf, "Order %d, %s", params->n, dominosa_diffnames[params->diff]);
+ *name = dupstr(buf);
+ *params_out = params;
return true;
}
@@ -171,18 +193,36 @@
static void decode_params(game_params *params, char const *string)
{
- params->n = atoi(string);
- while (*string && isdigit((unsigned char)*string)) string++;
- if (*string == 'a')
- params->unique = false;
-}
+ const char *p = string;
+
+ params->n = atoi(p);
+ while (*p && isdigit((unsigned char)*p)) p++;
+
+ while (*p) {
+ char c = *p++;
+ if (c == 'a') {
+ /* Legacy encoding from before the difficulty system */
+ params->diff = DIFF_AMBIGUOUS;
+ } else if (c == 'd') {
+ int i;
+ params->diff = DIFFCOUNT+1; /* ...which is invalid */
+ if (*p) {
+ for (i = 0; i < DIFFCOUNT; i++) {
+ if (*p == dominosa_diffchars[i])
+ params->diff = i;
+ }
+ p++;
+ }
+ }
+ }
+}
static char *encode_params(const game_params *params, bool full)
{
char buf[80];
- sprintf(buf, "%d", params->n);
- if (full && !params->unique)
- strcat(buf, "a");
+ int len = sprintf(buf, "%d", params->n);
+ if (full)
+ len += sprintf(buf + len, "d%c", dominosa_diffchars[params->diff]);
return dupstr(buf);
}
@@ -198,9 +238,10 @@
sprintf(buf, "%d", params->n);
ret[0].u.string.sval = dupstr(buf);
- ret[1].name = "Ensure unique solution";
- ret[1].type = C_BOOLEAN;
- ret[1].u.boolean.bval = params->unique;
+ ret[1].name = "Difficulty";
+ ret[1].type = C_CHOICES;
+ ret[1].u.choices.choicenames = DIFFCONFIG;
+ ret[1].u.choices.selected = params->diff;
ret[2].name = NULL;
ret[2].type = C_END;
@@ -213,7 +254,7 @@
game_params *ret = snew(game_params);
ret->n = atoi(cfg[0].u.string.sval);
- ret->unique = cfg[1].u.boolean.bval;
+ ret->diff = cfg[1].u.choices.selected;
return ret;
}
@@ -222,6 +263,8 @@
{
if (params->n < 1)
return "Maximum face number must be at least one";
+ if (params->diff >= DIFFCOUNT)
+ return "Unknown difficulty rating";
return NULL;
}
@@ -305,6 +348,7 @@
struct solver_scratch {
int n, dc, pc, w, h, wh;
+ int max_diff_used;
struct solver_domino *dominoes;
struct solver_placement *placements;
struct solver_square *squares;
@@ -491,6 +535,8 @@
sq->placements[sq->nplacements++] = p;
}
}
+
+ sc->max_diff_used = DIFF_TRIVIAL;
}
static void rule_out_placement(
@@ -707,7 +753,7 @@
* 2 = multiple possibilities remain (puzzle is ambiguous or solver is not
* smart enough)
*/
-static int run_solver(struct solver_scratch *sc)
+static int run_solver(struct solver_scratch *sc, int max_diff_allowed)
{
int di, si;
bool done_something;
@@ -732,28 +778,37 @@
for (di = 0; di < sc->dc; di++)
if (deduce_domino_single_placement(sc, di))
done_something = true;
- if (done_something)
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
continue;
+ }
for (si = 0; si < sc->wh; si++)
if (deduce_square_single_placement(sc, si))
done_something = true;
- if (done_something)
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
continue;
+ }
- /* FIXME: Trivial difficulty ends here */
+ if (max_diff_allowed <= DIFF_TRIVIAL)
+ continue;
for (si = 0; si < sc->wh; si++)
if (deduce_square_single_domino(sc, si))
done_something = true;
- if (done_something)
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
continue;
+ }
for (di = 0; di < sc->dc; di++)
if (deduce_domino_must_overlap(sc, di))
done_something = true;
- if (done_something)
+ if (done_something) {
+ sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
continue;
+ }
} while (done_something);
@@ -851,7 +906,7 @@
/* Optionally flip the domino round. */
int flip = -1;
- if (params->unique) {
+ if (params->diff != DIFF_AMBIGUOUS) {
int t1, t2;
/*
* If we're after a unique solution, we can do
@@ -910,7 +965,9 @@
}
assert(j == k);
solver_setup_grid(sc, grid2);
- } while (params->unique && run_solver(sc) > 1);
+ } while (params->diff != DIFF_AMBIGUOUS &&
+ (run_solver(sc, params->diff) > 1 ||
+ sc->max_diff_used < params->diff));
solver_free_scratch(sc);
@@ -1200,7 +1257,7 @@
} else {
struct solver_scratch *sc = solver_make_scratch(n);
solver_setup_grid(sc, state->numbers->numbers);
- run_solver(sc);
+ run_solver(sc, DIFFCOUNT);
ret = solution_move_string(sc);
solver_free_scratch(sc);
}
@@ -2018,7 +2075,7 @@
game_state *s, *s2;
char *id = NULL, *desc;
const char *err;
- bool diagnostics = false;
+ bool grade = false, diagnostics = false;
struct solver_scratch *sc;
int retd;
@@ -2026,6 +2083,8 @@
char *p = *++argv;
if (!strcmp(p, "-v")) {
diagnostics = true;
+ } else if (!strcmp(p, "-g")) {
+ grade = true;
} else if (*p == '-') {
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
return 1;
@@ -2035,7 +2094,7 @@
}
if (!id) {
- fprintf(stderr, "usage: %s [-v] <game_id>\n", argv[0]);
+ fprintf(stderr, "usage: %s [-v | -g] <game_id>\n", argv[0]);
return 1;
}
@@ -2058,9 +2117,12 @@
solver_diagnostics = diagnostics;
sc = solver_make_scratch(p->n);
solver_setup_grid(sc, s->numbers->numbers);
- retd = run_solver(sc);
+ retd = run_solver(sc, DIFFCOUNT);
if (retd == 0) {
printf("Puzzle is inconsistent\n");
+ } else if (grade) {
+ printf("Difficulty rating: %s\n",
+ dominosa_diffnames[sc->max_diff_used]);
} else {
char *move, *text;
move = solution_move_string(sc);