ref: b967a3ea86d6285457abc2a89e455c8f62d2c22a
dir: /tents.c/
/* * tents.c: Puzzle involving placing tents next to trees subject to * some confusing conditions. * * TODO: * * - it might be nice to make setter-provided tent/nontent clues * inviolable? * * on the other hand, this would introduce considerable extra * complexity and size into the game state; also inviolable * clues would have to be marked as such somehow, in an * intrusive and annoying manner. Since they're never * generated by _my_ generator, I'm currently more inclined * not to bother. * * - more difficult levels at the top end? * * for example, sometimes we can deduce that two BLANKs in * the same row are each adjacent to the same unattached tree * and to nothing else, implying that they can't both be * tents; this enables us to rule out some extra combinations * in the row-based deduction loop, and hence deduce more * from the number in that row than we could otherwise do. * * that by itself doesn't seem worth implementing a new * difficulty level for, but if I can find a few more things * like that then it might become worthwhile. * * I wonder if there's a sensible heuristic for where to * guess which would make a recursive solver viable? */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ctype.h> #include <math.h> #include "puzzles.h" #include "matching.h" /* * Design discussion * ----------------- * * The rules of this puzzle as available on the WWW are poorly * specified. The bits about tents having to be orthogonally * adjacent to trees, tents not being even diagonally adjacent to * one another, and the number of tents in each row and column * being given are simple enough; the difficult bit is the * tent-to-tree matching. * * Some sources use simplistic wordings such as `each tree is * exactly connected to only one tent', which is extremely unclear: * it's easy to read erroneously as `each tree is _orthogonally * adjacent_ to exactly one tent', which is definitely incorrect. * Even the most coherent sources I've found don't do a much better * job of stating the rule. * * A more precise statement of the rule is that it must be possible * to find a bijection f between tents and trees such that each * tree T is orthogonally adjacent to the tent f(T), but that a * tent is permitted to be adjacent to other trees in addition to * its own. This slightly non-obvious criterion is what gives this * puzzle most of its subtlety. * * However, there's a particularly subtle ambiguity left over. Is * the bijection between tents and trees required to be _unique_? * In other words, is that bijection conceptually something the * player should be able to exhibit as part of the solution (even * if they aren't actually required to do so)? Or is it sufficient * to have a unique _placement_ of the tents which gives rise to at * least one suitable bijection? * * The puzzle shown to the right of this .T. 2 *T* 2 * paragraph illustrates the problem. There T.T 0 -> T-T 0 * are two distinct bijections available. .T. 2 *T* 2 * The answer to the above question will * determine whether it's a valid puzzle. 202 202 * * This is an important question, because it affects both the * player and the generator. Eventually I found all the instances * of this puzzle I could Google up, solved them all by hand, and * verified that in all cases the tree/tent matching was uniquely * determined given the tree and tent positions. Therefore, the * puzzle as implemented in this source file takes the following * policy: * * - When checking a user-supplied solution for correctness, only * verify that there exists _at least_ one matching. * - When generating a puzzle, enforce that there must be * _exactly_ one. * * Algorithmic implications * ------------------------ * * Another way of phrasing the tree/tent matching criterion is to * say that the bipartite adjacency graph between trees and tents * has a perfect matching. That is, if you construct a graph which * has a vertex per tree and a vertex per tent, and an edge between * any tree and tent which are orthogonally adjacent, it is * possible to find a set of N edges of that graph (where N is the * number of trees and also the number of tents) which between them * connect every tree to every tent. * * The most efficient known algorithms for finding such a matching * given a graph, as far as I'm aware, are the Munkres assignment * algorithm (also known as the Hungarian algorithm) and the * Ford-Fulkerson algorithm (for finding optimal flows in * networks). Each of these takes O(N^3) running time; so we're * talking O(N^3) time to verify any candidate solution to this * puzzle. That's just about OK if you're doing it once per mouse * click (and in fact not even that, since the sensible thing to do * is check all the _other_ puzzle criteria and only wade into this * quagmire if none are violated); but if the solver had to keep * doing N^3 work internally, then it would probably end up with * more like N^5 or N^6 running time, and grid generation would * become very clunky. * * Fortunately, I've been able to prove a very useful property of * _unique_ perfect matchings, by adapting the proof of Hall's * Marriage Theorem. For those unaware of Hall's Theorem, I'll * recap it and its proof: it states that a bipartite graph * contains a perfect matching iff every set of vertices on the * left side of the graph have a neighbourhood _at least_ as big on * the right. * * This condition is obviously satisfied if a perfect matching does * exist; each left-side node has a distinct right-side node which * is the one assigned to it by the matching, and thus any set of n * left vertices must have a combined neighbourhood containing at * least the n corresponding right vertices, and possibly others * too. Alternatively, imagine if you had (say) three left-side * nodes all of which were connected to only two right-side nodes * between them: any perfect matching would have to assign one of * those two right nodes to each of the three left nodes, and still * give the three left nodes a different right node each. This is * of course impossible. * * To prove the converse (that if every subset of left vertices * satisfies the Hall condition then a perfect matching exists), * consider trying to find a proper subset of the left vertices * which _exactly_ satisfies the Hall condition: that is, its right * neighbourhood is precisely the same size as it. If we can find * such a subset, then we can split the bipartite graph into two * smaller ones: one consisting of the left subset and its right * neighbourhood, the other consisting of everything else. Edges * from the left side of the former graph to the right side of the * latter do not exist, by construction; edges from the right side * of the former to the left of the latter cannot be part of any * perfect matching because otherwise the left subset would not be * left with enough distinct right vertices to connect to (this is * exactly the same deduction used in Solo's set analysis). You can * then prove (left as an exercise) that both these smaller graphs * still satisfy the Hall condition, and therefore the proof will * follow by induction. * * There's one other possibility, which is the case where _no_ * proper subset of the left vertices has a right neighbourhood of * exactly the same size. That is, every left subset has a strictly * _larger_ right neighbourhood. In this situation, we can simply * remove an _arbitrary_ edge from the graph. This cannot reduce * the size of any left subset's right neighbourhood by more than * one, so if all neighbourhoods were strictly bigger than they * needed to be initially, they must now still be _at least as big_ * as they need to be. So we can keep throwing out arbitrary edges * until we find a set which exactly satisfies the Hall condition, * and then proceed as above. [] * * That's Hall's theorem. I now build on this by examining the * circumstances in which a bipartite graph can have a _unique_ * perfect matching. It is clear that in the second case, where no * left subset exactly satisfies the Hall condition and so we can * remove an arbitrary edge, there cannot be a unique perfect * matching: given one perfect matching, we choose our arbitrary * removed edge to be one of those contained in it, and then we can * still find a perfect matching in the remaining graph, which will * be a distinct perfect matching in the original. * * So it is a necessary condition for a unique perfect matching * that there must be at least one proper left subset which * _exactly_ satisfies the Hall condition. But now consider the * smaller graph constructed by taking that left subset and its * neighbourhood: if the graph as a whole had a unique perfect * matching, then so must this smaller one, which means we can find * a proper left subset _again_, and so on. Repeating this process * must eventually reduce us to a graph with only one left-side * vertex (so there are no proper subsets at all); this vertex must * be connected to only one right-side vertex, and hence must be so * in the original graph as well (by construction). So we can * discard this vertex pair from the graph, and any other edges * that involved it (which will by construction be from other left * vertices only), and the resulting smaller graph still has a * unique perfect matching which means we can do the same thing * again. * * In other words, given any bipartite graph with a unique perfect * matching, we can find that matching by the following extremely * simple algorithm: * * - Find a left-side vertex which is only connected to one * right-side vertex. * - Assign those vertices to one another, and therefore discard * any other edges connecting to that right vertex. * - Repeat until all vertices have been matched. * * This algorithm can be run in O(V+E) time (where V is the number * of vertices and E is the number of edges in the graph), and the * only way it can fail is if there is not a unique perfect * matching (either because there is no matching at all, or because * it isn't unique; but it can't distinguish those cases). * * Thus, the internal solver in this source file can be confident * that if the tree/tent matching is uniquely determined by the * tree and tent positions, it can find it using only this kind of * obvious and simple operation: assign a tree to a tent if it * cannot possibly belong to any other tent, and vice versa. If the * solver were _only_ trying to determine the matching, even that * `vice versa' wouldn't be required; but it can come in handy when * not all the tents have been placed yet. I can therefore be * reasonably confident that as long as my solver doesn't need to * cope with grids that have a non-unique matching, it will also * not need to do anything complicated like set analysis between * trees and tents. */ /* * In standalone solver mode, `verbose' is a variable which can be * set by command-line option; in debugging mode it's simply always * true. */ #if defined STANDALONE_SOLVER #define SOLVER_DIAGNOSTICS bool verbose = false; #elif defined SOLVER_DIAGNOSTICS #define verbose true #endif /* * 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(A) \ A(EASY,Easy,e) \ A(TRICKY,Tricky,t) #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 tents_diffnames[] = { DIFFLIST(TITLE) }; static char const tents_diffchars[] = DIFFLIST(ENCODE); #define DIFFCONFIG DIFFLIST(CONFIG) enum { COL_BACKGROUND, COL_GRID, COL_GRASS, COL_TREETRUNK, COL_TREELEAF, COL_TENT, COL_ERROR, COL_ERRTEXT, COL_ERRTRUNK, NCOLOURS }; enum { BLANK, TREE, TENT, NONTENT, MAGIC }; struct game_params { int w, h; int diff; }; struct numbers { int refcount; int *numbers; }; struct game_state { game_params p; char *grid; struct numbers *numbers; bool completed, used_solve; }; static game_params *default_params(void) { game_params *ret = snew(game_params); ret->w = ret->h = 8; ret->diff = DIFF_EASY; return ret; } static const struct game_params tents_presets[] = { {8, 8, DIFF_EASY}, {8, 8, DIFF_TRICKY}, {10, 10, DIFF_EASY}, {10, 10, DIFF_TRICKY}, {15, 15, DIFF_EASY}, {15, 15, DIFF_TRICKY}, }; static bool game_fetch_preset(int i, char **name, game_params **params) { game_params *ret; char str[80]; if (i < 0 || i >= lenof(tents_presets)) return false; ret = snew(game_params); *ret = tents_presets[i]; sprintf(str, "%dx%d %s", ret->w, ret->h, tents_diffnames[ret->diff]); *name = dupstr(str); *params = ret; return true; } static void free_params(game_params *params) { sfree(params); } static game_params *dup_params(const game_params *params) { game_params *ret = snew(game_params); *ret = *params; /* structure copy */ return ret; } static void decode_params(game_params *params, char const *string) { params->w = params->h = atoi(string); while (*string && isdigit((unsigned char)*string)) string++; if (*string == 'x') { string++; params->h = atoi(string); while (*string && isdigit((unsigned char)*string)) string++; } if (*string == 'd') { int i; string++; for (i = 0; i < DIFFCOUNT; i++) if (*string == tents_diffchars[i]) params->diff = i; if (*string) string++; } } static char *encode_params(const game_params *params, bool full) { char buf[120]; sprintf(buf, "%dx%d", params->w, params->h); if (full) sprintf(buf + strlen(buf), "d%c", tents_diffchars[params->diff]); return dupstr(buf); } static config_item *game_configure(const game_params *params) { config_item *ret; char buf[80]; ret = snewn(4, config_item); ret[0].name = "Width"; ret[0].type = C_STRING; sprintf(buf, "%d", params->w); ret[0].u.string.sval = dupstr(buf); ret[1].name = "Height"; ret[1].type = C_STRING; sprintf(buf, "%d", params->h); ret[1].u.string.sval = dupstr(buf); ret[2].name = "Difficulty"; ret[2].type = C_CHOICES; ret[2].u.choices.choicenames = DIFFCONFIG; ret[2].u.choices.selected = params->diff; ret[3].name = NULL; ret[3].type = C_END; return ret; } static game_params *custom_params(const config_item *cfg) { game_params *ret = snew(game_params); ret->w = atoi(cfg[0].u.string.sval); ret->h = atoi(cfg[1].u.string.sval); ret->diff = cfg[2].u.choices.selected; return ret; } static const char *validate_params(const game_params *params, bool full) { /* * Generating anything under 4x4 runs into trouble of one kind * or another. */ if (params->w < 4 || params->h < 4) return "Width and height must both be at least four"; return NULL; } /* * Scratch space for solver. */ enum { N, U, L, R, D, MAXDIR }; /* link directions */ #define dx(d) ( ((d)==R) - ((d)==L) ) #define dy(d) ( ((d)==D) - ((d)==U) ) #define F(d) ( U + D - (d) ) struct solver_scratch { char *links; /* mapping between trees and tents */ int *locs; char *place, *mrows, *trows; }; static struct solver_scratch *new_scratch(int w, int h) { struct solver_scratch *ret = snew(struct solver_scratch); ret->links = snewn(w*h, char); ret->locs = snewn(max(w, h), int); ret->place = snewn(max(w, h), char); ret->mrows = snewn(3 * max(w, h), char); ret->trows = snewn(3 * max(w, h), char); return ret; } static void free_scratch(struct solver_scratch *sc) { sfree(sc->trows); sfree(sc->mrows); sfree(sc->place); sfree(sc->locs); sfree(sc->links); sfree(sc); } /* * Solver. Returns 0 for impossibility, 1 for success, 2 for * ambiguity or failure to converge. */ static int tents_solve(int w, int h, const char *grid, int *numbers, char *soln, struct solver_scratch *sc, int diff) { int x, y, d, i, j; char *mrow, *trow, *trow1, *trow2; /* * Set up solver data. */ memset(sc->links, N, w*h); /* * Set up solution array. */ memcpy(soln, grid, w*h); /* * Main solver loop. */ while (1) { bool done_something = false; /* * Any tent which has only one unattached tree adjacent to * it can be tied to that tree. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (soln[y*w+x] == TENT && !sc->links[y*w+x]) { int linkd = 0; for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && soln[y2*w+x2] == TREE && !sc->links[y2*w+x2]) { if (linkd) break; /* found more than one */ else linkd = d; } } if (d == MAXDIR && linkd == 0) { #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("tent at %d,%d cannot link to anything\n", x, y); #endif return 0; /* no solution exists */ } else if (d == MAXDIR) { int x2 = x + dx(linkd), y2 = y + dy(linkd); #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("tent at %d,%d can only link to tree at" " %d,%d\n", x, y, x2, y2); #endif sc->links[y*w+x] = linkd; sc->links[y2*w+x2] = F(linkd); done_something = true; } } if (done_something) continue; if (diff < 0) break; /* don't do anything else! */ /* * Mark a blank square as NONTENT if it is not orthogonally * adjacent to any unmatched tree. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (soln[y*w+x] == BLANK) { bool can_be_tent = false; for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && soln[y2*w+x2] == TREE && !sc->links[y2*w+x2]) can_be_tent = true; } if (!can_be_tent) { #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("%d,%d cannot be a tent (no adjacent" " unmatched tree)\n", x, y); #endif soln[y*w+x] = NONTENT; done_something = true; } } if (done_something) continue; /* * Mark a blank square as NONTENT if it is (perhaps * diagonally) adjacent to any other tent. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (soln[y*w+x] == BLANK) { int dx, dy; bool imposs = false; for (dy = -1; dy <= +1; dy++) for (dx = -1; dx <= +1; dx++) if (dy || dx) { int x2 = x + dx, y2 = y + dy; if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && soln[y2*w+x2] == TENT) imposs = true; } if (imposs) { #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("%d,%d cannot be a tent (adjacent tent)\n", x, y); #endif soln[y*w+x] = NONTENT; done_something = true; } } if (done_something) continue; /* * Any tree which has exactly one {unattached tent, BLANK} * adjacent to it must have its tent in that square. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (soln[y*w+x] == TREE && !sc->links[y*w+x]) { int linkd = 0, linkd2 = 0, nd = 0; for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (!(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h)) continue; if (soln[y2*w+x2] == BLANK || (soln[y2*w+x2] == TENT && !sc->links[y2*w+x2])) { if (linkd) linkd2 = d; else linkd = d; nd++; } } if (nd == 0) { #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("tree at %d,%d cannot link to anything\n", x, y); #endif return 0; /* no solution exists */ } else if (nd == 1) { int x2 = x + dx(linkd), y2 = y + dy(linkd); #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("tree at %d,%d can only link to tent at" " %d,%d\n", x, y, x2, y2); #endif soln[y2*w+x2] = TENT; sc->links[y*w+x] = linkd; sc->links[y2*w+x2] = F(linkd); done_something = true; } else if (nd == 2 && (!dx(linkd) != !dx(linkd2)) && diff >= DIFF_TRICKY) { /* * If there are two possible places where * this tree's tent can go, and they are * diagonally separated rather than being * on opposite sides of the tree, then the * square (other than the tree square) * which is adjacent to both of them must * be a non-tent. */ int x2 = x + dx(linkd) + dx(linkd2); int y2 = y + dy(linkd) + dy(linkd2); assert(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h); if (soln[y2*w+x2] == BLANK) { #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("possible tent locations for tree at" " %d,%d rule out tent at %d,%d\n", x, y, x2, y2); #endif soln[y2*w+x2] = NONTENT; done_something = true; } } } if (done_something) continue; /* * If localised deductions about the trees and tents * themselves haven't helped us, it's time to resort to the * numbers round the grid edge. For each row and column, we * go through all possible combinations of locations for * the unplaced tents, rule out any which have adjacent * tents, and spot any square which is given the same state * by all remaining combinations. */ for (i = 0; i < w+h; i++) { int start, step, len, start1, start2, n, k; if (i < w) { /* * This is the number for a column. */ start = i; step = w; len = h; if (i > 0) start1 = start - 1; else start1 = -1; if (i+1 < w) start2 = start + 1; else start2 = -1; } else { /* * This is the number for a row. */ start = (i-w)*w; step = 1; len = w; if (i > w) start1 = start - w; else start1 = -1; if (i+1 < w+h) start2 = start + w; else start2 = -1; } if (diff < DIFF_TRICKY) { /* * In Easy mode, we don't look at the effect of one * row on the next (i.e. ruling out a square if all * possibilities for an adjacent row place a tent * next to it). */ start1 = start2 = -1; } k = numbers[i]; /* * Count and store the locations of the free squares, * and also count the number of tents already placed. */ n = 0; for (j = 0; j < len; j++) { if (soln[start+j*step] == TENT) k--; /* one fewer tent to place */ else if (soln[start+j*step] == BLANK) sc->locs[n++] = j; } if (n == 0) continue; /* nothing left to do here */ /* * Now we know we're placing k tents in n squares. Set * up the first possibility. */ for (j = 0; j < n; j++) sc->place[j] = (j < k ? TENT : NONTENT); /* * We're aiming to find squares in this row which are * invariant over all valid possibilities. Thus, we * maintain the current state of that invariance. We * start everything off at MAGIC to indicate that it * hasn't been set up yet. */ mrow = sc->mrows; trow = sc->trows; trow1 = sc->trows + len; trow2 = sc->trows + 2*len; memset(mrow, MAGIC, 3*len); /* * And iterate over all possibilities. */ while (1) { int p; bool valid; /* * See if this possibility is valid. The only way * it can fail to be valid is if it contains two * adjacent tents. (Other forms of invalidity, such * as containing a tent adjacent to one already * placed, will have been dealt with already by * other parts of the solver.) */ valid = true; for (j = 0; j+1 < n; j++) if (sc->place[j] == TENT && sc->place[j+1] == TENT && sc->locs[j+1] == sc->locs[j]+1) { valid = false; break; } if (valid) { /* * Merge this valid combination into mrow. */ memset(trow, MAGIC, len); memset(trow+len, BLANK, 2*len); for (j = 0; j < n; j++) { trow[sc->locs[j]] = sc->place[j]; if (sc->place[j] == TENT) { int jj; for (jj = sc->locs[j]-1; jj <= sc->locs[j]+1; jj++) if (jj >= 0 && jj < len) trow1[jj] = trow2[jj] = NONTENT; } } for (j = 0; j < 3*len; j++) { if (trow[j] == MAGIC) continue; if (mrow[j] == MAGIC || mrow[j] == trow[j]) { /* * Either this is the first valid * placement we've found at all, or * this square's contents are * consistent with every previous valid * combination. */ mrow[j] = trow[j]; } else { /* * This square's contents fail to match * what they were in a different * combination, so we cannot deduce * anything about this square. */ mrow[j] = BLANK; } } } /* * Find the next combination of k choices from n. * We do this by finding the rightmost tent which * can be moved one place right, doing so, and * shunting all tents to the right of that as far * left as they can go. */ p = 0; for (j = n-1; j > 0; j--) { if (sc->place[j] == TENT) p++; if (sc->place[j] == NONTENT && sc->place[j-1] == TENT) { sc->place[j-1] = NONTENT; sc->place[j] = TENT; while (p--) sc->place[++j] = TENT; while (++j < n) sc->place[j] = NONTENT; break; } } if (j <= 0) break; /* we've finished */ } /* * It's just possible that _no_ placement was valid, in * which case we have an internally inconsistent * puzzle. */ if (mrow[sc->locs[0]] == MAGIC) return 0; /* inconsistent */ /* * Now go through mrow and see if there's anything * we've deduced which wasn't already mentioned in soln. */ for (j = 0; j < len; j++) { int whichrow; for (whichrow = 0; whichrow < 3; whichrow++) { char *mthis = mrow + whichrow * len; int tstart = (whichrow == 0 ? start : whichrow == 1 ? start1 : start2); if (tstart >= 0 && mthis[j] != MAGIC && mthis[j] != BLANK && soln[tstart+j*step] == BLANK) { int pos = tstart+j*step; #ifdef SOLVER_DIAGNOSTICS if (verbose) printf("%s %d forces %s at %d,%d\n", step==1 ? "row" : "column", step==1 ? start/w : start, mthis[j] == TENT ? "tent" : "non-tent", pos % w, pos / w); #endif soln[pos] = mthis[j]; done_something = true; } } } } if (done_something) continue; if (!done_something) break; } /* * The solver has nothing further it can do. Return 1 if both * soln and sc->links are completely filled in, or 2 otherwise. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { if (soln[y*w+x] == BLANK) return 2; if (soln[y*w+x] != NONTENT && sc->links[y*w+x] == 0) return 2; } return 1; } static char *new_game_desc(const game_params *params_in, random_state *rs, char **aux, bool interactive) { game_params params_copy = *params_in; /* structure copy */ game_params *params = ¶ms_copy; int w = params->w, h = params->h; int ntrees = w * h / 5; char *grid = snewn(w*h, char); char *puzzle = snewn(w*h, char); int *numbers = snewn(w+h, int); char *soln = snewn(w*h, char); int *order = snewn(w*h, int); int *treemap = snewn(w*h, int); int maxedges = ntrees*4 + w*h; int *adjdata = snewn(maxedges, int); int **adjlists = snewn(ntrees, int *); int *adjsizes = snewn(ntrees, int); int *outr = snewn(4*ntrees, int); struct solver_scratch *sc = new_scratch(w, h); char *ret, *p; int i, j, nl, nr; int *adjptr; /* * Since this puzzle has many global deductions and doesn't * permit limited clue sets, generating grids for this puzzle * is hard enough that I see no better option than to simply * generate a solution and see if it's unique and has the * required difficulty. This turns out to be computationally * plausible as well. * * We chose our tree count (hence also tent count) by dividing * the total grid area by five above. Why five? Well, w*h/4 is * the maximum number of tents you can _possibly_ fit into the * grid without violating the separation criterion, and to * achieve that you are constrained to a very small set of * possible layouts (the obvious one with a tent at every * (even,even) coordinate, and trivial variations thereon). So * if we reduce the tent count a bit more, we enable more * random-looking placement; 5 turns out to be a plausible * figure which yields sensible puzzles. Increasing the tent * count would give puzzles whose solutions were too regimented * and could be solved by the use of that knowledge (and would * also take longer to find a viable placement); decreasing it * would make the grids emptier and more boring. * * Actually generating a grid is a matter of first placing the * tents, and then placing the trees by the use of matching.c * (finding a distinct square adjacent to every tent). We do it * this way round because otherwise satisfying the tent * separation condition would become onerous: most randomly * chosen tent layouts do not satisfy this condition, so we'd * have gone to a lot of work before finding that a candidate * layout was unusable. Instead, we place the tents first and * ensure they meet the separation criterion _before_ doing * lots of computation; this works much better. * * This generation strategy can fail at many points, including * as early as tent placement (if you get a bad random order in * which to greedily try the grid squares, you won't even * manage to find enough mutually non-adjacent squares to put * the tents in). Then it can fail if matching.c doesn't manage * to find a good enough matching (i.e. the tent placements don't * admit any adequate tree placements); and finally it can fail * if the solver finds that the problem has the wrong * difficulty (including being actually non-unique). All of * these, however, are insufficiently frequent to cause * trouble. */ if (params->diff > DIFF_EASY && params->w <= 4 && params->h <= 4) params->diff = DIFF_EASY; /* downgrade to prevent tight loop */ while (1) { /* * Make a list of grid squares which we'll permute as we pick * the tent locations. * * We'll also need to index all the potential tree squares, * i.e. the ones adjacent to the tents. */ for (i = 0; i < w*h; i++) { order[i] = i; treemap[i] = -1; } /* * Place tents at random without making any two adjacent. */ memset(grid, BLANK, w*h); j = ntrees; nr = 0; /* Loop end condition: either j==0 (we've placed all the * tents), or the number of grid squares we have yet to try * is too few to fit the remaining tents into. */ for (i = 0; j > 0 && i+j <= w*h; i++) { int which, x, y, d, tmp; int dy, dx; bool ok = true; which = i + random_upto(rs, w*h - i); tmp = order[which]; order[which] = order[i]; order[i] = tmp; x = order[i] % w; y = order[i] / w; for (dy = -1; dy <= +1; dy++) for (dx = -1; dx <= +1; dx++) if (x+dx >= 0 && x+dx < w && y+dy >= 0 && y+dy < h && grid[(y+dy)*w+(x+dx)] == TENT) ok = false; if (ok) { grid[order[i]] = TENT; for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && treemap[y2*w+x2] == -1) { treemap[y2*w+x2] = nr++; } } j--; } } if (j > 0) continue; /* couldn't place all the tents */ /* * Build up the graph for matching.c. */ adjptr = adjdata; nl = 0; for (i = 0; i < w*h; i++) { if (grid[i] == TENT) { int d, x = i % w, y = i / w; adjlists[nl] = adjptr; for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h) { assert(treemap[y2*w+x2] != -1); *adjptr++ = treemap[y2*w+x2]; } } adjsizes[nl] = adjptr - adjlists[nl]; nl++; } } /* * Call the matching algorithm to actually place the trees. */ j = matching(ntrees, nr, adjlists, adjsizes, rs, NULL, outr); if (j < ntrees) continue; /* couldn't place all the trees */ /* * Fill in the trees in the grid, by cross-referencing treemap * (which maps a grid square to its index as known to * matching()) against the output from matching(). * * Note that for these purposes we don't actually care _which_ * tent each potential tree square is assigned to - we only * care whether it was assigned to any tent at all, in order * to decide whether to put a tree in it. */ for (i = 0; i < w*h; i++) if (treemap[i] != -1 && outr[treemap[i]] != -1) grid[i] = TREE; /* * I think it looks ugly if there isn't at least one of * _something_ (tent or tree) in each row and each column * of the grid. This doesn't give any information away * since a completely empty row/column is instantly obvious * from the clues (it has no trees and a zero). */ for (i = 0; i < w; i++) { for (j = 0; j < h; j++) { if (grid[j*w+i] != BLANK) break; /* found something in this column */ } if (j == h) break; /* found empty column */ } if (i < w) continue; /* a column was empty */ for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (grid[j*w+i] != BLANK) break; /* found something in this row */ } if (i == w) break; /* found empty row */ } if (j < h) continue; /* a row was empty */ /* * Now set up the numbers round the edge. */ for (i = 0; i < w; i++) { int n = 0; for (j = 0; j < h; j++) if (grid[j*w+i] == TENT) n++; numbers[i] = n; } for (i = 0; i < h; i++) { int n = 0; for (j = 0; j < w; j++) if (grid[i*w+j] == TENT) n++; numbers[w+i] = n; } /* * And now actually solve the puzzle, to see whether it's * unique and has the required difficulty. */ for (i = 0; i < w*h; i++) puzzle[i] = grid[i] == TREE ? TREE : BLANK; i = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff-1); j = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff); /* * We expect solving with difficulty params->diff to have * succeeded (otherwise the problem is too hard), and * solving with diff-1 to have failed (otherwise it's too * easy). */ if (i == 2 && j == 1) break; } /* * That's it. Encode as a game ID. */ ret = snewn((w+h)*40 + ntrees + (w*h)/26 + 1, char); p = ret; j = 0; for (i = 0; i <= w*h; i++) { bool c = (i < w*h ? grid[i] == TREE : true); if (c) { *p++ = (j == 0 ? '_' : j-1 + 'a'); j = 0; } else { j++; while (j > 25) { *p++ = 'z'; j -= 25; } } } for (i = 0; i < w+h; i++) p += sprintf(p, ",%d", numbers[i]); *p++ = '\0'; ret = sresize(ret, p - ret, char); /* * And encode the solution as an aux_info. */ *aux = snewn(ntrees * 40, char); p = *aux; *p++ = 'S'; for (i = 0; i < w*h; i++) if (grid[i] == TENT) p += sprintf(p, ";T%d,%d", i%w, i/w); *p++ = '\0'; *aux = sresize(*aux, p - *aux, char); free_scratch(sc); sfree(outr); sfree(adjdata); sfree(adjlists); sfree(adjsizes); sfree(treemap); sfree(order); sfree(soln); sfree(numbers); sfree(puzzle); sfree(grid); return ret; } static const char *validate_desc(const game_params *params, const char *desc) { int w = params->w, h = params->h; int area, i; area = 0; while (*desc && *desc != ',') { if (*desc == '_') area++; else if (*desc >= 'a' && *desc < 'z') area += *desc - 'a' + 2; else if (*desc == 'z') area += 25; else if (*desc == '!' || *desc == '-') /* do nothing */; else return "Invalid character in grid specification"; desc++; } if (area < w * h + 1) return "Not enough data to fill grid"; else if (area > w * h + 1) return "Too much data to fill grid"; for (i = 0; i < w+h; i++) { if (!*desc) return "Not enough numbers given after grid specification"; else if (*desc != ',') return "Invalid character in number list"; desc++; while (*desc && isdigit((unsigned char)*desc)) desc++; } if (*desc) return "Unexpected additional data at end of game description"; return NULL; } static game_state *new_game(midend *me, const game_params *params, const char *desc) { int w = params->w, h = params->h; game_state *state = snew(game_state); int i; state->p = *params; /* structure copy */ state->grid = snewn(w*h, char); state->numbers = snew(struct numbers); state->numbers->refcount = 1; state->numbers->numbers = snewn(w+h, int); state->completed = state->used_solve = false; i = 0; memset(state->grid, BLANK, w*h); while (*desc) { int run, type; type = TREE; if (*desc == '_') run = 0; else if (*desc >= 'a' && *desc < 'z') run = *desc - ('a'-1); else if (*desc == 'z') { run = 25; type = BLANK; } else { assert(*desc == '!' || *desc == '-'); run = -1; type = (*desc == '!' ? TENT : NONTENT); } desc++; i += run; assert(i >= 0 && i <= w*h); if (i == w*h) { assert(type == TREE); break; } else { if (type != BLANK) state->grid[i++] = type; } } for (i = 0; i < w+h; i++) { assert(*desc == ','); desc++; state->numbers->numbers[i] = atoi(desc); while (*desc && isdigit((unsigned char)*desc)) desc++; } assert(!*desc); return state; } static game_state *dup_game(const game_state *state) { int w = state->p.w, h = state->p.h; game_state *ret = snew(game_state); ret->p = state->p; /* structure copy */ ret->grid = snewn(w*h, char); memcpy(ret->grid, state->grid, w*h); ret->numbers = state->numbers; state->numbers->refcount++; ret->completed = state->completed; ret->used_solve = state->used_solve; return ret; } static void free_game(game_state *state) { if (--state->numbers->refcount <= 0) { sfree(state->numbers->numbers); sfree(state->numbers); } sfree(state->grid); sfree(state); } static char *solve_game(const game_state *state, const game_state *currstate, const char *aux, const char **error) { int w = state->p.w, h = state->p.h; if (aux) { /* * If we already have the solution, save ourselves some * time. */ return dupstr(aux); } else { struct solver_scratch *sc = new_scratch(w, h); char *soln; int ret; char *move, *p; int i; soln = snewn(w*h, char); ret = tents_solve(w, h, state->grid, state->numbers->numbers, soln, sc, DIFFCOUNT-1); free_scratch(sc); if (ret != 1) { sfree(soln); if (ret == 0) *error = "This puzzle is not self-consistent"; else *error = "Unable to find a unique solution for this puzzle"; return NULL; } /* * Construct a move string which turns the current state * into the solved state. */ move = snewn(w*h * 40, char); p = move; *p++ = 'S'; for (i = 0; i < w*h; i++) if (soln[i] == TENT) p += sprintf(p, ";T%d,%d", i%w, i/w); *p++ = '\0'; move = sresize(move, p - move, char); sfree(soln); return move; } } static bool game_can_format_as_text_now(const game_params *params) { return params->w <= 1998 && params->h <= 1998; /* 999 tents */ } static char *game_text_format(const game_state *state) { int w = state->p.w, h = state->p.h, r, c; int cw = 4, ch = 2, gw = (w+1)*cw + 2, gh = (h+1)*ch + 1, len = gw * gh; char *board = snewn(len + 1, char); sprintf(board, "%*s\n", len - 2, ""); for (r = 0; r <= h; ++r) { for (c = 0; c <= w; ++c) { int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2; int i = r*w + c, n = 1000; if (r == h && c == w) /* NOP */; else if (c == w) n = state->numbers->numbers[w + r]; else if (r == h) n = state->numbers->numbers[c]; else switch (state->grid[i]) { case BLANK: board[center] = '.'; break; case TREE: board[center] = 'T'; break; case TENT: memcpy(board + center - 1, "//\\", 3); break; case NONTENT: break; default: memcpy(board + center - 1, "wtf", 3); } if (n < 100) { board[center] = '0' + n % 10; if (n >= 10) board[center - 1] = '0' + n / 10; } else if (n < 1000) { board[center + 1] = '0' + n % 10; board[center] = '0' + n / 10 % 10; board[center - 1] = '0' + n / 100; } board[cell] = '+'; memset(board + cell + 1, '-', cw - 1); for (i = 1; i < ch; ++i) board[cell + i*gw] = '|'; } for (c = 0; c < ch; ++c) { board[(r*ch+c)*gw + gw - 2] = c == 0 ? '+' : r < h ? '|' : ' '; board[(r*ch+c)*gw + gw - 1] = '\n'; } } memset(board + len - gw, '-', gw - 2 - cw); for (c = 0; c <= w; ++c) board[len - gw + cw*c] = '+'; return board; } struct game_ui { int dsx, dsy; /* coords of drag start */ int dex, dey; /* coords of drag end */ int drag_button; /* -1 for none, or a button code */ bool drag_ok; /* dragged off the window, to cancel */ int cx, cy; /* cursor position. */ bool cdisp; /* is cursor displayed? */ }; static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); ui->dsx = ui->dsy = -1; ui->dex = ui->dey = -1; ui->drag_button = -1; ui->drag_ok = false; ui->cx = ui->cy = 0; ui->cdisp = false; return ui; } static void free_ui(game_ui *ui) { sfree(ui); } static char *encode_ui(const game_ui *ui) { return NULL; } static void decode_ui(game_ui *ui, const char *encoding) { } static void game_changed_state(game_ui *ui, const game_state *oldstate, const game_state *newstate) { } struct game_drawstate { int tilesize; bool started; game_params p; int *drawn, *numbersdrawn; int cx, cy; /* last-drawn cursor pos, or (-1,-1) if absent. */ }; #define PREFERRED_TILESIZE 32 #define TILESIZE (ds->tilesize) #define TLBORDER (TILESIZE/2) #define BRBORDER (TILESIZE*3/2) #define COORD(x) ( (x) * TILESIZE + TLBORDER ) #define FROMCOORD(x) ( ((x) - TLBORDER + TILESIZE) / TILESIZE - 1 ) #define FLASH_TIME 0.30F static int drag_xform(const game_ui *ui, int x, int y, int v) { int xmin, ymin, xmax, ymax; xmin = min(ui->dsx, ui->dex); xmax = max(ui->dsx, ui->dex); ymin = min(ui->dsy, ui->dey); ymax = max(ui->dsy, ui->dey); #ifndef STYLUS_BASED /* * Left-dragging has no effect, so we treat a left-drag as a * single click on dsx,dsy. */ if (ui->drag_button == LEFT_BUTTON) { xmin = xmax = ui->dsx; ymin = ymax = ui->dsy; } #endif if (x < xmin || x > xmax || y < ymin || y > ymax) return v; /* no change outside drag area */ if (v == TREE) return v; /* trees are inviolate always */ if (xmin == xmax && ymin == ymax) { /* * Results of a simple click. Left button sets blanks to * tents; right button sets blanks to non-tents; either * button clears a non-blank square. * If stylus-based however, it loops instead. */ if (ui->drag_button == LEFT_BUTTON) #ifdef STYLUS_BASED v = (v == BLANK ? TENT : (v == TENT ? NONTENT : BLANK)); else v = (v == BLANK ? NONTENT : (v == NONTENT ? TENT : BLANK)); #else v = (v == BLANK ? TENT : BLANK); else v = (v == BLANK ? NONTENT : BLANK); #endif } else { /* * Results of a drag. Left-dragging has no effect. * Right-dragging sets all blank squares to non-tents and * has no effect on anything else. */ if (ui->drag_button == RIGHT_BUTTON) v = (v == BLANK ? NONTENT : v); else #ifdef STYLUS_BASED v = (v == BLANK ? NONTENT : v); #else /* do nothing */; #endif } return v; } static char *interpret_move(const game_state *state, game_ui *ui, const game_drawstate *ds, int x, int y, int button) { int w = state->p.w, h = state->p.h; char tmpbuf[80]; bool shift = button & MOD_SHFT, control = button & MOD_CTRL; button &= ~MOD_MASK; if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { x = FROMCOORD(x); y = FROMCOORD(y); if (x < 0 || y < 0 || x >= w || y >= h) return NULL; ui->drag_button = button; ui->dsx = ui->dex = x; ui->dsy = ui->dey = y; ui->drag_ok = true; ui->cdisp = false; return UI_UPDATE; } if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) && ui->drag_button > 0) { int xmin, ymin, xmax, ymax; char *buf; const char *sep; int buflen, bufsize, tmplen; x = FROMCOORD(x); y = FROMCOORD(y); if (x < 0 || y < 0 || x >= w || y >= h) { ui->drag_ok = false; } else { /* * Drags are limited to one row or column. Hence, we * work out which coordinate is closer to the drag * start, and move it _to_ the drag start. */ if (abs(x - ui->dsx) < abs(y - ui->dsy)) x = ui->dsx; else y = ui->dsy; ui->dex = x; ui->dey = y; ui->drag_ok = true; } if (IS_MOUSE_DRAG(button)) return UI_UPDATE; /* * The drag has been released. Enact it. */ if (!ui->drag_ok) { ui->drag_button = -1; return UI_UPDATE; /* drag was just cancelled */ } xmin = min(ui->dsx, ui->dex); xmax = max(ui->dsx, ui->dex); ymin = min(ui->dsy, ui->dey); ymax = max(ui->dsy, ui->dey); assert(0 <= xmin && xmin <= xmax && xmax < w); assert(0 <= ymin && ymin <= ymax && ymax < h); buflen = 0; bufsize = 256; buf = snewn(bufsize, char); sep = ""; for (y = ymin; y <= ymax; y++) for (x = xmin; x <= xmax; x++) { int v = drag_xform(ui, x, y, state->grid[y*w+x]); if (state->grid[y*w+x] != v) { tmplen = sprintf(tmpbuf, "%s%c%d,%d", sep, (int)(v == BLANK ? 'B' : v == TENT ? 'T' : 'N'), x, y); sep = ";"; if (buflen + tmplen >= bufsize) { bufsize = buflen + tmplen + 256; buf = sresize(buf, bufsize, char); } strcpy(buf+buflen, tmpbuf); buflen += tmplen; } } ui->drag_button = -1; /* drag is terminated */ if (buflen == 0) { sfree(buf); return UI_UPDATE; /* drag was terminated */ } else { buf[buflen] = '\0'; return buf; } } if (IS_CURSOR_MOVE(button)) { ui->cdisp = true; if (shift || control) { int len = 0, i, indices[2]; indices[0] = ui->cx + w * ui->cy; move_cursor(button, &ui->cx, &ui->cy, w, h, false); indices[1] = ui->cx + w * ui->cy; /* NONTENTify all unique traversed eligible squares */ for (i = 0; i <= (indices[0] != indices[1]); ++i) if (state->grid[indices[i]] == BLANK || (control && state->grid[indices[i]] == TENT)) { len += sprintf(tmpbuf + len, "%sN%d,%d", len ? ";" : "", indices[i] % w, indices[i] / w); assert(len < lenof(tmpbuf)); } tmpbuf[len] = '\0'; if (len) return dupstr(tmpbuf); } else move_cursor(button, &ui->cx, &ui->cy, w, h, false); return UI_UPDATE; } if (ui->cdisp) { char rep = 0; int v = state->grid[ui->cy*w+ui->cx]; if (v != TREE) { #ifdef SINGLE_CURSOR_SELECT if (button == CURSOR_SELECT) /* SELECT cycles T, N, B */ rep = v == BLANK ? 'T' : v == TENT ? 'N' : 'B'; #else if (button == CURSOR_SELECT) rep = v == BLANK ? 'T' : 'B'; else if (button == CURSOR_SELECT2) rep = v == BLANK ? 'N' : 'B'; else if (button == 'T' || button == 'N' || button == 'B') rep = (char)button; #endif } if (rep) { sprintf(tmpbuf, "%c%d,%d", (int)rep, ui->cx, ui->cy); return dupstr(tmpbuf); } } else if (IS_CURSOR_SELECT(button)) { ui->cdisp = true; return UI_UPDATE; } return NULL; } static game_state *execute_move(const game_state *state, const char *move) { int w = state->p.w, h = state->p.h; char c; int x, y, m, n, i, j; game_state *ret = dup_game(state); while (*move) { c = *move; if (c == 'S') { int i; ret->used_solve = true; /* * Set all non-tree squares to NONTENT. The rest of the * solve move will fill the tents in over the top. */ for (i = 0; i < w*h; i++) if (ret->grid[i] != TREE) ret->grid[i] = NONTENT; move++; } else if (c == 'B' || c == 'T' || c == 'N') { move++; if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || x < 0 || y < 0 || x >= w || y >= h) { free_game(ret); return NULL; } if (ret->grid[y*w+x] == TREE) { free_game(ret); return NULL; } ret->grid[y*w+x] = (c == 'B' ? BLANK : c == 'T' ? TENT : NONTENT); move += n; } else { free_game(ret); return NULL; } if (*move == ';') move++; else if (*move) { free_game(ret); return NULL; } } /* * Check for completion. */ for (i = n = m = 0; i < w*h; i++) { if (ret->grid[i] == TENT) n++; else if (ret->grid[i] == TREE) m++; } if (n == m) { int *gridids, *adjdata, **adjlists, *adjsizes, *adjptr; /* * We have the right number of tents, which is a * precondition for the game being complete. Now check that * the numbers add up. */ for (i = 0; i < w; i++) { n = 0; for (j = 0; j < h; j++) if (ret->grid[j*w+i] == TENT) n++; if (ret->numbers->numbers[i] != n) goto completion_check_done; } for (i = 0; i < h; i++) { n = 0; for (j = 0; j < w; j++) if (ret->grid[i*w+j] == TENT) n++; if (ret->numbers->numbers[w+i] != n) goto completion_check_done; } /* * Also, check that no two tents are adjacent. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { if (x+1 < w && ret->grid[y*w+x] == TENT && ret->grid[y*w+x+1] == TENT) goto completion_check_done; if (y+1 < h && ret->grid[y*w+x] == TENT && ret->grid[(y+1)*w+x] == TENT) goto completion_check_done; if (x+1 < w && y+1 < h) { if (ret->grid[y*w+x] == TENT && ret->grid[(y+1)*w+(x+1)] == TENT) goto completion_check_done; if (ret->grid[(y+1)*w+x] == TENT && ret->grid[y*w+(x+1)] == TENT) goto completion_check_done; } } /* * OK; we have the right number of tents, they match the * numeric clues, and they satisfy the non-adjacency * criterion. Finally, we need to verify that they can be * placed in a one-to-one matching with the trees such that * every tent is orthogonally adjacent to its tree. * * This bit is where the hard work comes in: we have to do * it by finding such a matching using matching.c. */ gridids = snewn(w*h, int); adjdata = snewn(m*4, int); adjlists = snewn(m, int *); adjsizes = snewn(m, int); /* Assign each tent and tree a consecutive vertex id for * matching(). */ for (i = n = 0; i < w*h; i++) { if (ret->grid[i] == TENT) gridids[i] = n++; } assert(n == m); for (i = n = 0; i < w*h; i++) { if (ret->grid[i] == TREE) gridids[i] = n++; } assert(n == m); /* Build the vertices' adjacency lists. */ adjptr = adjdata; for (y = 0; y < h; y++) for (x = 0; x < w; x++) if (ret->grid[y*w+x] == TREE) { int d, treeid = gridids[y*w+x]; adjlists[treeid] = adjptr; /* * Here we use the direction enum declared for * the solver. We make use of the fact that the * directions are declared in the order * U,L,R,D, meaning that we go through the four * neighbours of any square in numerically * increasing order. */ for (d = 1; d < MAXDIR; d++) { int x2 = x + dx(d), y2 = y + dy(d); if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h && ret->grid[y2*w+x2] == TENT) { *adjptr++ = gridids[y2*w+x2]; } } adjsizes[treeid] = adjptr - adjlists[treeid]; } n = matching(m, m, adjlists, adjsizes, NULL, NULL, NULL); sfree(gridids); sfree(adjdata); sfree(adjlists); sfree(adjsizes); if (n != m) goto completion_check_done; /* * We haven't managed to fault the grid on any count. Score! */ ret->completed = true; } completion_check_done: return ret; } /* ---------------------------------------------------------------------- * Drawing routines. */ static void game_compute_size(const game_params *params, int tilesize, int *x, int *y) { /* fool the macros */ struct dummy { int tilesize; } dummy, *ds = &dummy; dummy.tilesize = tilesize; *x = TLBORDER + BRBORDER + TILESIZE * params->w; *y = TLBORDER + BRBORDER + TILESIZE * params->h; } static void game_set_size(drawing *dr, game_drawstate *ds, const game_params *params, int tilesize) { ds->tilesize = tilesize; } static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); ret[COL_GRID * 3 + 0] = 0.0F; ret[COL_GRID * 3 + 1] = 0.0F; ret[COL_GRID * 3 + 2] = 0.0F; ret[COL_GRASS * 3 + 0] = 0.7F; ret[COL_GRASS * 3 + 1] = 1.0F; ret[COL_GRASS * 3 + 2] = 0.5F; ret[COL_TREETRUNK * 3 + 0] = 0.6F; ret[COL_TREETRUNK * 3 + 1] = 0.4F; ret[COL_TREETRUNK * 3 + 2] = 0.0F; ret[COL_TREELEAF * 3 + 0] = 0.0F; ret[COL_TREELEAF * 3 + 1] = 0.7F; ret[COL_TREELEAF * 3 + 2] = 0.0F; ret[COL_TENT * 3 + 0] = 0.8F; ret[COL_TENT * 3 + 1] = 0.7F; ret[COL_TENT * 3 + 2] = 0.0F; ret[COL_ERROR * 3 + 0] = 1.0F; ret[COL_ERROR * 3 + 1] = 0.0F; ret[COL_ERROR * 3 + 2] = 0.0F; ret[COL_ERRTEXT * 3 + 0] = 1.0F; ret[COL_ERRTEXT * 3 + 1] = 1.0F; ret[COL_ERRTEXT * 3 + 2] = 1.0F; ret[COL_ERRTRUNK * 3 + 0] = 0.6F; ret[COL_ERRTRUNK * 3 + 1] = 0.0F; ret[COL_ERRTRUNK * 3 + 2] = 0.0F; *ncolours = NCOLOURS; return ret; } static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) { int w = state->p.w, h = state->p.h; struct game_drawstate *ds = snew(struct game_drawstate); int i; ds->tilesize = 0; ds->started = false; ds->p = state->p; /* structure copy */ ds->drawn = snewn(w*h, int); for (i = 0; i < w*h; i++) ds->drawn[i] = MAGIC; ds->numbersdrawn = snewn(w+h, int); for (i = 0; i < w+h; i++) ds->numbersdrawn[i] = 2; ds->cx = ds->cy = -1; return ds; } static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->drawn); sfree(ds->numbersdrawn); sfree(ds); } enum { ERR_ADJ_TOPLEFT = 4, ERR_ADJ_TOP, ERR_ADJ_TOPRIGHT, ERR_ADJ_LEFT, ERR_ADJ_RIGHT, ERR_ADJ_BOTLEFT, ERR_ADJ_BOT, ERR_ADJ_BOTRIGHT, ERR_OVERCOMMITTED }; static int *find_errors(const game_state *state, char *grid) { int w = state->p.w, h = state->p.h; int *ret = snewn(w*h + w + h, int); int *tmp = snewn(w*h*2, int), *dsf = tmp + w*h; int x, y; /* * This function goes through a grid and works out where to * highlight play errors in red. The aim is that it should * produce at least one error highlight for any complete grid * (or complete piece of grid) violating a puzzle constraint, so * that a grid containing no BLANK squares is either a win or is * marked up in some way that indicates why not. * * So it's easy enough to highlight errors in the numeric clues * - just light up any row or column number which is not * fulfilled - and it's just as easy to highlight adjacent * tents. The difficult bit is highlighting failures in the * tent/tree matching criterion. * * A natural approach would seem to be to apply the matching.c * algorithm to find the tent/tree matching; if this fails, it * could be made to produce as a by-product some set of trees * which have too few tents between them (or vice versa). However, * it's bad for localising errors, because it's not easy to make * the algorithm narrow down to the _smallest_ such set of trees: * if trees A and B have only one tent between them, for instance, * it might perfectly well highlight not only A and B but also * trees C and D which are correctly matched on the far side of * the grid, on the grounds that those four trees between them * have only three tents. * * Also, that approach fares badly when you introduce the * additional requirement that incomplete grids should have * errors highlighted only when they can be proved to be errors * - so that trees should not be marked as having too few tents * if there are enough BLANK squares remaining around them that * could be turned into the missing tents (to do so would be * patronising, since the overwhelming likelihood is not that * the player has forgotten to put a tree there but that they * have merely not put one there _yet_). However, tents with too * few trees can be marked immediately, since those are * definitely player error. * * So I adopt an alternative approach, which is to consider the * bipartite adjacency graph between trees and tents * ('bipartite' in the sense that for these purposes I * deliberately ignore two adjacent trees or two adjacent * tents), divide that graph up into its connected components * using a dsf, and look for components which contain different * numbers of trees and tents. This allows me to highlight * groups of tents with too few trees between them immediately, * and then in order to find groups of trees with too few tents * I redo the same process but counting BLANKs as potential * tents (so that the only trees highlighted are those * surrounded by enough NONTENTs to make it impossible to give * them enough tents). * * However, this technique is incomplete: it is not a sufficient * condition for the existence of a perfect matching that every * connected component of the graph has the same number of tents * and trees. An example of a graph which satisfies the latter * condition but still has no perfect matching is * * A B C * | / ,/| * | / ,'/ | * | / ,' / | * |/,' / | * 1 2 3 * * which can be realised in Tents as * * B * A 1 C 2 * 3 * * The matching-error highlighter described above will not mark * this construction as erroneous. However, something else will: * the three tents in the above diagram (let us suppose A,B,C * are the tents, though it doesn't matter which) contain two * diagonally adjacent pairs. So there will be _an_ error * highlighted for the above layout, even though not all types * of error will be highlighted. * * And in fact we can prove that this will always be the case: * that the shortcomings of the matching-error highlighter will * always be made up for by the easy tent adjacency highlighter. * * Lemma: Let G be a bipartite graph between n trees and n * tents, which is connected, and in which no tree has degree * more than two (but a tent may). Then G has a perfect matching. * * (Note: in the statement and proof of the Lemma I will * consistently use 'tree' to indicate a type of graph vertex as * opposed to a tent, and not to indicate a tree in the graph- * theoretic sense.) * * Proof: * * If we can find a tent of degree 1 joined to a tree of degree * 2, then any perfect matching must pair that tent with that * tree. Hence, we can remove both, leaving a smaller graph G' * which still satisfies all the conditions of the Lemma, and * which has a perfect matching iff G does. * * So, wlog, we may assume G contains no tent of degree 1 joined * to a tree of degree 2; if it does, we can reduce it as above. * * If G has no tent of degree 1 at all, then every tent has * degree at least two, so there are at least 2n edges in the * graph. But every tree has degree at most two, so there are at * most 2n edges. Hence there must be exactly 2n edges, so every * tree and every tent must have degree exactly two, which means * that the whole graph consists of a single loop (by * connectedness), and therefore certainly has a perfect * matching. * * Alternatively, if G does have a tent of degree 1 but it is * not connected to a tree of degree 2, then the tree it is * connected to must have degree 1 - and, by connectedness, that * must mean that that tent and that tree between them form the * entire graph. This trivial graph has a trivial perfect * matching. [] * * That proves the lemma. Hence, in any case where the matching- * error highlighter fails to highlight an erroneous component * (because it has the same number of tents as trees, but they * cannot be matched up), the above lemma tells us that there * must be a tree with degree more than 2, i.e. a tree * orthogonally adjacent to at least three tents. But in that * case, there must be some pair of those three tents which are * diagonally adjacent to each other, so the tent-adjacency * highlighter will necessarily show an error. So any filled * layout in Tents which is not a correct solution to the puzzle * must have _some_ error highlighted by the subroutine below. * * (Of course it would be nicer if we could highlight all * errors: in the above example layout, we would like to * highlight tents A,B as having too few trees between them, and * trees 2,3 as having too few tents, in addition to marking the * adjacency problems. But I can't immediately think of any way * to find the smallest sets of such tents and trees without an * O(2^N) loop over all subsets of a given component.) */ /* * ret[0] through to ret[w*h-1] give error markers for the grid * squares. After that, ret[w*h] to ret[w*h+w-1] give error * markers for the column numbers, and ret[w*h+w] to * ret[w*h+w+h-1] for the row numbers. */ /* * Spot tent-adjacency violations. */ for (x = 0; x < w*h; x++) ret[x] = 0; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { if (y+1 < h && x+1 < w && ((grid[y*w+x] == TENT && grid[(y+1)*w+(x+1)] == TENT) || (grid[(y+1)*w+x] == TENT && grid[y*w+(x+1)] == TENT))) { ret[y*w+x] |= 1 << ERR_ADJ_BOTRIGHT; ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOPRIGHT; ret[y*w+(x+1)] |= 1 << ERR_ADJ_BOTLEFT; ret[(y+1)*w+(x+1)] |= 1 << ERR_ADJ_TOPLEFT; } if (y+1 < h && grid[y*w+x] == TENT && grid[(y+1)*w+x] == TENT) { ret[y*w+x] |= 1 << ERR_ADJ_BOT; ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOP; } if (x+1 < w && grid[y*w+x] == TENT && grid[y*w+(x+1)] == TENT) { ret[y*w+x] |= 1 << ERR_ADJ_RIGHT; ret[y*w+(x+1)] |= 1 << ERR_ADJ_LEFT; } } } /* * Spot numeric clue violations. */ for (x = 0; x < w; x++) { int tents = 0, maybetents = 0; for (y = 0; y < h; y++) { if (grid[y*w+x] == TENT) tents++; else if (grid[y*w+x] == BLANK) maybetents++; } ret[w*h+x] = (tents > state->numbers->numbers[x] || tents + maybetents < state->numbers->numbers[x]); } for (y = 0; y < h; y++) { int tents = 0, maybetents = 0; for (x = 0; x < w; x++) { if (grid[y*w+x] == TENT) tents++; else if (grid[y*w+x] == BLANK) maybetents++; } ret[w*h+w+y] = (tents > state->numbers->numbers[w+y] || tents + maybetents < state->numbers->numbers[w+y]); } /* * Identify groups of tents with too few trees between them, * which we do by constructing the connected components of the * bipartite adjacency graph between tents and trees * ('bipartite' in the sense that we deliberately ignore * adjacency between tents or between trees), and highlighting * all the tents in any component which has a smaller tree * count. */ dsf_init(dsf, w*h); /* Construct the equivalence classes. */ for (y = 0; y < h; y++) { for (x = 0; x < w-1; x++) { if ((grid[y*w+x] == TREE && grid[y*w+x+1] == TENT) || (grid[y*w+x] == TENT && grid[y*w+x+1] == TREE)) dsf_merge(dsf, y*w+x, y*w+x+1); } } for (y = 0; y < h-1; y++) { for (x = 0; x < w; x++) { if ((grid[y*w+x] == TREE && grid[(y+1)*w+x] == TENT) || (grid[y*w+x] == TENT && grid[(y+1)*w+x] == TREE)) dsf_merge(dsf, y*w+x, (y+1)*w+x); } } /* Count up the tent/tree difference in each one. */ for (x = 0; x < w*h; x++) tmp[x] = 0; for (x = 0; x < w*h; x++) { y = dsf_canonify(dsf, x); if (grid[x] == TREE) tmp[y]++; else if (grid[x] == TENT) tmp[y]--; } /* And highlight any tent belonging to an equivalence class with * a score less than zero. */ for (x = 0; x < w*h; x++) { y = dsf_canonify(dsf, x); if (grid[x] == TENT && tmp[y] < 0) ret[x] |= 1 << ERR_OVERCOMMITTED; } /* * Identify groups of trees with too few tents between them. * This is done similarly, except that we now count BLANK as * equivalent to TENT, i.e. we only highlight such trees when * the user hasn't even left _room_ to provide tents for them * all. (Otherwise, we'd highlight all trees red right at the * start of the game, before the user had done anything wrong!) */ #define TENT(x) ((x)==TENT || (x)==BLANK) dsf_init(dsf, w*h); /* Construct the equivalence classes. */ for (y = 0; y < h; y++) { for (x = 0; x < w-1; x++) { if ((grid[y*w+x] == TREE && TENT(grid[y*w+x+1])) || (TENT(grid[y*w+x]) && grid[y*w+x+1] == TREE)) dsf_merge(dsf, y*w+x, y*w+x+1); } } for (y = 0; y < h-1; y++) { for (x = 0; x < w; x++) { if ((grid[y*w+x] == TREE && TENT(grid[(y+1)*w+x])) || (TENT(grid[y*w+x]) && grid[(y+1)*w+x] == TREE)) dsf_merge(dsf, y*w+x, (y+1)*w+x); } } /* Count up the tent/tree difference in each one. */ for (x = 0; x < w*h; x++) tmp[x] = 0; for (x = 0; x < w*h; x++) { y = dsf_canonify(dsf, x); if (grid[x] == TREE) tmp[y]++; else if (TENT(grid[x])) tmp[y]--; } /* And highlight any tree belonging to an equivalence class with * a score more than zero. */ for (x = 0; x < w*h; x++) { y = dsf_canonify(dsf, x); if (grid[x] == TREE && tmp[y] > 0) ret[x] |= 1 << ERR_OVERCOMMITTED; } #undef TENT sfree(tmp); return ret; } static void draw_err_adj(drawing *dr, game_drawstate *ds, int x, int y) { int coords[8]; int yext, xext; /* * Draw a diamond. */ coords[0] = x - TILESIZE*2/5; coords[1] = y; coords[2] = x; coords[3] = y - TILESIZE*2/5; coords[4] = x + TILESIZE*2/5; coords[5] = y; coords[6] = x; coords[7] = y + TILESIZE*2/5; draw_polygon(dr, coords, 4, COL_ERROR, COL_GRID); /* * Draw an exclamation mark in the diamond. This turns out to * look unpleasantly off-centre if done via draw_text, so I do * it by hand on the basis that exclamation marks aren't that * difficult to draw... */ xext = TILESIZE/16; yext = TILESIZE*2/5 - (xext*2+2); draw_rect(dr, x-xext, y-yext, xext*2+1, yext*2+1 - (xext*3), COL_ERRTEXT); draw_rect(dr, x-xext, y+yext-xext*2+1, xext*2+1, xext*2, COL_ERRTEXT); } static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v, bool cur, bool printing) { int err; int tx = COORD(x), ty = COORD(y); int cx = tx + TILESIZE/2, cy = ty + TILESIZE/2; err = v & ~15; v &= 15; clip(dr, tx, ty, TILESIZE, TILESIZE); if (!printing) { draw_rect(dr, tx, ty, TILESIZE, TILESIZE, COL_GRID); draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, (v == BLANK ? COL_BACKGROUND : COL_GRASS)); } if (v == TREE) { int i; (printing ? draw_rect_outline : draw_rect) (dr, cx-TILESIZE/15, ty+TILESIZE*3/10, 2*(TILESIZE/15)+1, (TILESIZE*9/10 - TILESIZE*3/10), (err & (1<<ERR_OVERCOMMITTED) ? COL_ERRTRUNK : COL_TREETRUNK)); for (i = 0; i < (printing ? 2 : 1); i++) { int col = (i == 1 ? COL_BACKGROUND : (err & (1<<ERR_OVERCOMMITTED) ? COL_ERROR : COL_TREELEAF)); int sub = i * (TILESIZE/32); draw_circle(dr, cx, ty+TILESIZE*4/10, TILESIZE/4 - sub, col, col); draw_circle(dr, cx+TILESIZE/5, ty+TILESIZE/4, TILESIZE/8 - sub, col, col); draw_circle(dr, cx-TILESIZE/5, ty+TILESIZE/4, TILESIZE/8 - sub, col, col); draw_circle(dr, cx+TILESIZE/4, ty+TILESIZE*6/13, TILESIZE/8 - sub, col, col); draw_circle(dr, cx-TILESIZE/4, ty+TILESIZE*6/13, TILESIZE/8 - sub, col, col); } } else if (v == TENT) { int coords[6]; int col; coords[0] = cx - TILESIZE/3; coords[1] = cy + TILESIZE/3; coords[2] = cx + TILESIZE/3; coords[3] = cy + TILESIZE/3; coords[4] = cx; coords[5] = cy - TILESIZE/3; col = (err & (1<<ERR_OVERCOMMITTED) ? COL_ERROR : COL_TENT); draw_polygon(dr, coords, 3, (printing ? -1 : col), col); } if (err & (1 << ERR_ADJ_TOPLEFT)) draw_err_adj(dr, ds, tx, ty); if (err & (1 << ERR_ADJ_TOP)) draw_err_adj(dr, ds, tx+TILESIZE/2, ty); if (err & (1 << ERR_ADJ_TOPRIGHT)) draw_err_adj(dr, ds, tx+TILESIZE, ty); if (err & (1 << ERR_ADJ_LEFT)) draw_err_adj(dr, ds, tx, ty+TILESIZE/2); if (err & (1 << ERR_ADJ_RIGHT)) draw_err_adj(dr, ds, tx+TILESIZE, ty+TILESIZE/2); if (err & (1 << ERR_ADJ_BOTLEFT)) draw_err_adj(dr, ds, tx, ty+TILESIZE); if (err & (1 << ERR_ADJ_BOT)) draw_err_adj(dr, ds, tx+TILESIZE/2, ty+TILESIZE); if (err & (1 << ERR_ADJ_BOTRIGHT)) draw_err_adj(dr, ds, tx+TILESIZE, ty+TILESIZE); if (cur) { int coff = TILESIZE/8; draw_rect_outline(dr, tx + coff, ty + coff, TILESIZE - coff*2 + 1, TILESIZE - coff*2 + 1, COL_GRID); } unclip(dr); draw_update(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1); } /* * Internal redraw function, used for printing as well as drawing. */ static void int_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, float animtime, float flashtime, bool printing) { int w = state->p.w, h = state->p.h; int x, y; bool flashing; int cx = -1, cy = -1; bool cmoved = false; char *tmpgrid; int *errors; if (ui) { if (ui->cdisp) { cx = ui->cx; cy = ui->cy; } if (cx != ds->cx || cy != ds->cy) cmoved = true; } if (printing || !ds->started) { if (printing) print_line_width(dr, TILESIZE/64); /* * Draw the grid. */ for (y = 0; y <= h; y++) draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), COL_GRID); for (x = 0; x <= w; x++) draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), COL_GRID); } if (flashtime > 0) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1; else flashing = false; /* * Find errors. For this we use _part_ of the information from a * currently active drag: we transform dsx,dsy but not anything * else. (This seems to strike a good compromise between having * the error highlights respond instantly to single clicks, but * not giving constant feedback during a right-drag.) */ if (ui && ui->drag_button >= 0) { tmpgrid = snewn(w*h, char); memcpy(tmpgrid, state->grid, w*h); tmpgrid[ui->dsy * w + ui->dsx] = drag_xform(ui, ui->dsx, ui->dsy, tmpgrid[ui->dsy * w + ui->dsx]); errors = find_errors(state, tmpgrid); sfree(tmpgrid); } else { errors = find_errors(state, state->grid); } /* * Draw the grid. */ for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { int v = state->grid[y*w+x]; bool credraw = false; /* * We deliberately do not take drag_ok into account * here, because user feedback suggests that it's * marginally nicer not to have the drag effects * flickering on and off disconcertingly. */ if (ui && ui->drag_button >= 0) v = drag_xform(ui, x, y, v); if (flashing && (v == TREE || v == TENT)) v = NONTENT; if (cmoved) { if ((x == cx && y == cy) || (x == ds->cx && y == ds->cy)) credraw = true; } v |= errors[y*w+x]; if (printing || ds->drawn[y*w+x] != v || credraw) { draw_tile(dr, ds, x, y, v, (x == cx && y == cy), printing); if (!printing) ds->drawn[y*w+x] = v; } } } /* * Draw (or redraw, if their error-highlighted state has * changed) the numbers. */ for (x = 0; x < w; x++) { if (printing || ds->numbersdrawn[x] != errors[w*h+x]) { char buf[80]; draw_rect(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1, COL_BACKGROUND); sprintf(buf, "%d", state->numbers->numbers[x]); draw_text(dr, COORD(x) + TILESIZE/2, COORD(h+1), FONT_VARIABLE, TILESIZE/2, ALIGN_HCENTRE|ALIGN_VNORMAL, (errors[w*h+x] ? COL_ERROR : COL_GRID), buf); draw_update(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1); if (!printing) ds->numbersdrawn[x] = errors[w*h+x]; } } for (y = 0; y < h; y++) { if (printing || ds->numbersdrawn[w+y] != errors[w*h+w+y]) { char buf[80]; draw_rect(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE, COL_BACKGROUND); sprintf(buf, "%d", state->numbers->numbers[w+y]); draw_text(dr, COORD(w+1), COORD(y) + TILESIZE/2, FONT_VARIABLE, TILESIZE/2, ALIGN_HRIGHT|ALIGN_VCENTRE, (errors[w*h+w+y] ? COL_ERROR : COL_GRID), buf); draw_update(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE); if (!printing) ds->numbersdrawn[w+y] = errors[w*h+w+y]; } } if (cmoved) { ds->cx = cx; ds->cy = cy; } sfree(errors); } static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, float animtime, float flashtime) { int_redraw(dr, ds, oldstate, state, dir, ui, animtime, flashtime, false); } static float game_anim_length(const game_state *oldstate, const game_state *newstate, int dir, game_ui *ui) { return 0.0F; } static float game_flash_length(const game_state *oldstate, const game_state *newstate, int dir, game_ui *ui) { if (!oldstate->completed && newstate->completed && !oldstate->used_solve && !newstate->used_solve) return FLASH_TIME; return 0.0F; } static void game_get_cursor_location(const game_ui *ui, const game_drawstate *ds, const game_state *state, const game_params *params, int *x, int *y, int *w, int *h) { if(ui->cdisp) { *x = COORD(ui->cx); *y = COORD(ui->cy); *w = *h = TILESIZE; } } static int game_status(const game_state *state) { return state->completed ? +1 : 0; } static bool game_timing_state(const game_state *state, game_ui *ui) { return true; } static void game_print_size(const game_params *params, float *x, float *y) { int pw, ph; /* * I'll use 6mm squares by default. */ game_compute_size(params, 600, &pw, &ph); *x = pw / 100.0F; *y = ph / 100.0F; } static void game_print(drawing *dr, const game_state *state, int tilesize) { int c; /* Ick: fake up `ds->tilesize' for macro expansion purposes */ game_drawstate ads, *ds = &ads; game_set_size(dr, ds, NULL, tilesize); c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND); c = print_mono_colour(dr, 0); assert(c == COL_GRID); c = print_mono_colour(dr, 1); assert(c == COL_GRASS); c = print_mono_colour(dr, 0); assert(c == COL_TREETRUNK); c = print_mono_colour(dr, 0); assert(c == COL_TREELEAF); c = print_mono_colour(dr, 0); assert(c == COL_TENT); int_redraw(dr, ds, NULL, state, +1, NULL, 0.0F, 0.0F, true); } #ifdef COMBINED #define thegame tents #endif const struct game thegame = { "Tents", "games.tents", "tents", default_params, game_fetch_preset, NULL, decode_params, encode_params, free_params, dup_params, true, game_configure, custom_params, validate_params, new_game_desc, validate_desc, new_game, dup_game, free_game, true, solve_game, true, game_can_format_as_text_now, game_text_format, new_ui, free_ui, encode_ui, decode_ui, NULL, /* game_request_keys */ game_changed_state, interpret_move, execute_move, PREFERRED_TILESIZE, game_compute_size, game_set_size, game_colours, game_new_drawstate, game_free_drawstate, game_redraw, game_anim_length, game_flash_length, game_get_cursor_location, game_status, true, false, game_print_size, game_print, false, /* wants_statusbar */ false, game_timing_state, REQUIRE_RBUTTON, /* flags */ }; #ifdef STANDALONE_SOLVER #include <stdarg.h> int main(int argc, char **argv) { game_params *p; game_state *s, *s2; char *id = NULL, *desc; const char *err; bool grade = false; int ret, diff; bool really_verbose = false; struct solver_scratch *sc; while (--argc > 0) { char *p = *++argv; if (!strcmp(p, "-v")) { really_verbose = true; } else if (!strcmp(p, "-g")) { grade = true; } else if (*p == '-') { fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); return 1; } else { id = p; } } if (!id) { fprintf(stderr, "usage: %s [-g | -v] <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); s2 = new_game(NULL, p, desc); sc = new_scratch(p->w, p->h); /* * When solving an Easy puzzle, we don't want to bother the * user with Hard-level deductions. For this reason, we grade * the puzzle internally before doing anything else. */ ret = -1; /* placate optimiser */ for (diff = 0; diff < DIFFCOUNT; diff++) { ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers, s2->grid, sc, diff); if (ret < 2) break; } if (diff == DIFFCOUNT) { if (grade) printf("Difficulty rating: too hard to solve internally\n"); else printf("Unable to find a unique solution\n"); } else { if (grade) { if (ret == 0) printf("Difficulty rating: impossible (no solution exists)\n"); else if (ret == 1) printf("Difficulty rating: %s\n", tents_diffnames[diff]); } else { verbose = really_verbose; ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers, s2->grid, sc, diff); if (ret == 0) printf("Puzzle is inconsistent\n"); else fputs(game_text_format(s2), stdout); } } return 0; } #endif /* vim: set shiftwidth=4 tabstop=8: */