mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
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.
This commit is contained in:
150
dominosa.c
150
dominosa.c
@ -84,6 +84,24 @@
|
|||||||
|
|
||||||
#define FLASH_TIME 0.13F
|
#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 {
|
enum {
|
||||||
COL_BACKGROUND,
|
COL_BACKGROUND,
|
||||||
COL_TEXT,
|
COL_TEXT,
|
||||||
@ -98,7 +116,7 @@ enum {
|
|||||||
|
|
||||||
struct game_params {
|
struct game_params {
|
||||||
int n;
|
int n;
|
||||||
bool unique;
|
int diff;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct game_numbers {
|
struct game_numbers {
|
||||||
@ -125,35 +143,39 @@ static game_params *default_params(void)
|
|||||||
game_params *ret = snew(game_params);
|
game_params *ret = snew(game_params);
|
||||||
|
|
||||||
ret->n = 6;
|
ret->n = 6;
|
||||||
ret->unique = true;
|
ret->diff = DIFF_BASIC;
|
||||||
|
|
||||||
return ret;
|
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;
|
game_params *params;
|
||||||
int n;
|
|
||||||
char buf[80];
|
char buf[80];
|
||||||
|
|
||||||
switch (i) {
|
if (i < 0 || i >= lenof(dominosa_presets))
|
||||||
case 0: n = 3; break;
|
return false;
|
||||||
case 1: n = 4; break;
|
|
||||||
case 2: n = 5; break;
|
params = snew(game_params);
|
||||||
case 3: n = 6; break;
|
*params = dominosa_presets[i]; /* structure copy */
|
||||||
case 4: n = 7; break;
|
|
||||||
case 5: n = 8; break;
|
sprintf(buf, "Order %d, %s", params->n, dominosa_diffnames[params->diff]);
|
||||||
case 6: n = 9; break;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(buf, "Up to double-%d", n);
|
|
||||||
*name = dupstr(buf);
|
*name = dupstr(buf);
|
||||||
|
*params_out = params;
|
||||||
*params = ret = snew(game_params);
|
|
||||||
ret->n = n;
|
|
||||||
ret->unique = true;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,18 +193,36 @@ static game_params *dup_params(const game_params *params)
|
|||||||
|
|
||||||
static void decode_params(game_params *params, char const *string)
|
static void decode_params(game_params *params, char const *string)
|
||||||
{
|
{
|
||||||
params->n = atoi(string);
|
const char *p = string;
|
||||||
while (*string && isdigit((unsigned char)*string)) string++;
|
|
||||||
if (*string == 'a')
|
params->n = atoi(p);
|
||||||
params->unique = false;
|
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)
|
static char *encode_params(const game_params *params, bool full)
|
||||||
{
|
{
|
||||||
char buf[80];
|
char buf[80];
|
||||||
sprintf(buf, "%d", params->n);
|
int len = sprintf(buf, "%d", params->n);
|
||||||
if (full && !params->unique)
|
if (full)
|
||||||
strcat(buf, "a");
|
len += sprintf(buf + len, "d%c", dominosa_diffchars[params->diff]);
|
||||||
return dupstr(buf);
|
return dupstr(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,9 +238,10 @@ static config_item *game_configure(const game_params *params)
|
|||||||
sprintf(buf, "%d", params->n);
|
sprintf(buf, "%d", params->n);
|
||||||
ret[0].u.string.sval = dupstr(buf);
|
ret[0].u.string.sval = dupstr(buf);
|
||||||
|
|
||||||
ret[1].name = "Ensure unique solution";
|
ret[1].name = "Difficulty";
|
||||||
ret[1].type = C_BOOLEAN;
|
ret[1].type = C_CHOICES;
|
||||||
ret[1].u.boolean.bval = params->unique;
|
ret[1].u.choices.choicenames = DIFFCONFIG;
|
||||||
|
ret[1].u.choices.selected = params->diff;
|
||||||
|
|
||||||
ret[2].name = NULL;
|
ret[2].name = NULL;
|
||||||
ret[2].type = C_END;
|
ret[2].type = C_END;
|
||||||
@ -213,7 +254,7 @@ static game_params *custom_params(const config_item *cfg)
|
|||||||
game_params *ret = snew(game_params);
|
game_params *ret = snew(game_params);
|
||||||
|
|
||||||
ret->n = atoi(cfg[0].u.string.sval);
|
ret->n = atoi(cfg[0].u.string.sval);
|
||||||
ret->unique = cfg[1].u.boolean.bval;
|
ret->diff = cfg[1].u.choices.selected;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -222,6 +263,8 @@ static const char *validate_params(const game_params *params, bool full)
|
|||||||
{
|
{
|
||||||
if (params->n < 1)
|
if (params->n < 1)
|
||||||
return "Maximum face number must be at least one";
|
return "Maximum face number must be at least one";
|
||||||
|
if (params->diff >= DIFFCOUNT)
|
||||||
|
return "Unknown difficulty rating";
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +348,7 @@ struct solver_square {
|
|||||||
|
|
||||||
struct solver_scratch {
|
struct solver_scratch {
|
||||||
int n, dc, pc, w, h, wh;
|
int n, dc, pc, w, h, wh;
|
||||||
|
int max_diff_used;
|
||||||
struct solver_domino *dominoes;
|
struct solver_domino *dominoes;
|
||||||
struct solver_placement *placements;
|
struct solver_placement *placements;
|
||||||
struct solver_square *squares;
|
struct solver_square *squares;
|
||||||
@ -491,6 +535,8 @@ static void solver_setup_grid(struct solver_scratch *sc, const int *numbers)
|
|||||||
sq->placements[sq->nplacements++] = p;
|
sq->placements[sq->nplacements++] = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sc->max_diff_used = DIFF_TRIVIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rule_out_placement(
|
static void rule_out_placement(
|
||||||
@ -707,7 +753,7 @@ static bool deduce_domino_must_overlap(struct solver_scratch *sc, int di)
|
|||||||
* 2 = multiple possibilities remain (puzzle is ambiguous or solver is not
|
* 2 = multiple possibilities remain (puzzle is ambiguous or solver is not
|
||||||
* smart enough)
|
* 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;
|
int di, si;
|
||||||
bool done_something;
|
bool done_something;
|
||||||
@ -732,28 +778,37 @@ static int run_solver(struct solver_scratch *sc)
|
|||||||
for (di = 0; di < sc->dc; di++)
|
for (di = 0; di < sc->dc; di++)
|
||||||
if (deduce_domino_single_placement(sc, di))
|
if (deduce_domino_single_placement(sc, di))
|
||||||
done_something = true;
|
done_something = true;
|
||||||
if (done_something)
|
if (done_something) {
|
||||||
|
sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (si = 0; si < sc->wh; si++)
|
for (si = 0; si < sc->wh; si++)
|
||||||
if (deduce_square_single_placement(sc, si))
|
if (deduce_square_single_placement(sc, si))
|
||||||
done_something = true;
|
done_something = true;
|
||||||
if (done_something)
|
if (done_something) {
|
||||||
|
sc->max_diff_used = max(sc->max_diff_used, DIFF_TRIVIAL);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* FIXME: Trivial difficulty ends here */
|
if (max_diff_allowed <= DIFF_TRIVIAL)
|
||||||
|
continue;
|
||||||
|
|
||||||
for (si = 0; si < sc->wh; si++)
|
for (si = 0; si < sc->wh; si++)
|
||||||
if (deduce_square_single_domino(sc, si))
|
if (deduce_square_single_domino(sc, si))
|
||||||
done_something = true;
|
done_something = true;
|
||||||
if (done_something)
|
if (done_something) {
|
||||||
|
sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (di = 0; di < sc->dc; di++)
|
for (di = 0; di < sc->dc; di++)
|
||||||
if (deduce_domino_must_overlap(sc, di))
|
if (deduce_domino_must_overlap(sc, di))
|
||||||
done_something = true;
|
done_something = true;
|
||||||
if (done_something)
|
if (done_something) {
|
||||||
|
sc->max_diff_used = max(sc->max_diff_used, DIFF_BASIC);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
} while (done_something);
|
} while (done_something);
|
||||||
|
|
||||||
@ -851,7 +906,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
|
|||||||
/* Optionally flip the domino round. */
|
/* Optionally flip the domino round. */
|
||||||
int flip = -1;
|
int flip = -1;
|
||||||
|
|
||||||
if (params->unique) {
|
if (params->diff != DIFF_AMBIGUOUS) {
|
||||||
int t1, t2;
|
int t1, t2;
|
||||||
/*
|
/*
|
||||||
* If we're after a unique solution, we can do
|
* If we're after a unique solution, we can do
|
||||||
@ -910,7 +965,9 @@ static char *new_game_desc(const game_params *params, random_state *rs,
|
|||||||
}
|
}
|
||||||
assert(j == k);
|
assert(j == k);
|
||||||
solver_setup_grid(sc, grid2);
|
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);
|
solver_free_scratch(sc);
|
||||||
|
|
||||||
@ -1200,7 +1257,7 @@ static char *solve_game(const game_state *state, const game_state *currstate,
|
|||||||
} else {
|
} else {
|
||||||
struct solver_scratch *sc = solver_make_scratch(n);
|
struct solver_scratch *sc = solver_make_scratch(n);
|
||||||
solver_setup_grid(sc, state->numbers->numbers);
|
solver_setup_grid(sc, state->numbers->numbers);
|
||||||
run_solver(sc);
|
run_solver(sc, DIFFCOUNT);
|
||||||
ret = solution_move_string(sc);
|
ret = solution_move_string(sc);
|
||||||
solver_free_scratch(sc);
|
solver_free_scratch(sc);
|
||||||
}
|
}
|
||||||
@ -2018,7 +2075,7 @@ int main(int argc, char **argv)
|
|||||||
game_state *s, *s2;
|
game_state *s, *s2;
|
||||||
char *id = NULL, *desc;
|
char *id = NULL, *desc;
|
||||||
const char *err;
|
const char *err;
|
||||||
bool diagnostics = false;
|
bool grade = false, diagnostics = false;
|
||||||
struct solver_scratch *sc;
|
struct solver_scratch *sc;
|
||||||
int retd;
|
int retd;
|
||||||
|
|
||||||
@ -2026,6 +2083,8 @@ int main(int argc, char **argv)
|
|||||||
char *p = *++argv;
|
char *p = *++argv;
|
||||||
if (!strcmp(p, "-v")) {
|
if (!strcmp(p, "-v")) {
|
||||||
diagnostics = true;
|
diagnostics = true;
|
||||||
|
} else if (!strcmp(p, "-g")) {
|
||||||
|
grade = true;
|
||||||
} else if (*p == '-') {
|
} else if (*p == '-') {
|
||||||
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
|
fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
|
||||||
return 1;
|
return 1;
|
||||||
@ -2035,7 +2094,7 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!id) {
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2058,9 +2117,12 @@ int main(int argc, char **argv)
|
|||||||
solver_diagnostics = diagnostics;
|
solver_diagnostics = diagnostics;
|
||||||
sc = solver_make_scratch(p->n);
|
sc = solver_make_scratch(p->n);
|
||||||
solver_setup_grid(sc, s->numbers->numbers);
|
solver_setup_grid(sc, s->numbers->numbers);
|
||||||
retd = run_solver(sc);
|
retd = run_solver(sc, DIFFCOUNT);
|
||||||
if (retd == 0) {
|
if (retd == 0) {
|
||||||
printf("Puzzle is inconsistent\n");
|
printf("Puzzle is inconsistent\n");
|
||||||
|
} else if (grade) {
|
||||||
|
printf("Difficulty rating: %s\n",
|
||||||
|
dominosa_diffnames[sc->max_diff_used]);
|
||||||
} else {
|
} else {
|
||||||
char *move, *text;
|
char *move, *text;
|
||||||
move = solution_move_string(sc);
|
move = solution_move_string(sc);
|
||||||
|
Reference in New Issue
Block a user