ref: b3364419daf57fb508b907b3365d34d312fc7b07
parent: b7356cd209eb6a0b8bb153e34a9c1a5831884c7b
author: Simon Tatham <[email protected]>
date: Sun Oct 29 04:41:02 EST 2006
I'm sick and tired of having unfinished puzzle code lying around on several different systems in strange directories. So I'm creating an `unfinished' directory within source control, and centralising all my half-finished, half-baked or otherwise half-arsed puzzle implementations into it. Herewith Sokoban (playable but rubbish generation), Pearl (Masyu - rubbish generation and nothing else), Path (Number Link - rubbish generation and nothing else) and NumGame (the Countdown numbers game - currently just a solver and not even a generator yet). [originally from svn r6883]
--- /dev/null
+++ b/unfinished/README
@@ -1,0 +1,9 @@
+This subdirectory contains puzzle implementations which are
+half-written, fundamentally flawed, or in other ways unready to be
+shipped as part of the polished Puzzles collection.
+
+Those puzzles which have .R files can be built as part of the
+Puzzles collection by symlinking their source files into the parent
+directory and re-running mkfiles.pl. Anything without a .R file
+isn't even finished enough to do that, and you should read the
+source file itself to find out the status.
--- /dev/null
+++ b/unfinished/numgame.c
@@ -1,0 +1,914 @@
+/*
+ * This program implements a breadth-first search which
+ * exhaustively solves the Countdown numbers game, and related
+ * games with slightly different rule sets such as `Flippo'.
+ *
+ * Currently it is simply a standalone command-line utility to
+ * which you provide a set of numbers and it tells you everything
+ * it can make together with how many different ways it can be
+ * made. I would like ultimately to turn it into the generator for
+ * a Puzzles puzzle, but I haven't even started on writing a
+ * Puzzles user interface yet.
+ */
+
+/*
+ * TODO:
+ *
+ * - start thinking about difficulty ratings
+ * + anything involving associative operations will be flagged
+ * as many-paths because of the associative options (e.g.
+ * 2*3*4 can be (2*3)*4 or 2*(3*4), or indeed (2*4)*3). This
+ * is probably a _good_ thing, since those are unusually
+ * easy.
+ * + tree-structured calculations ((a*b)/(c+d)) have multiple
+ * paths because the independent branches of the tree can be
+ * evaluated in either order, whereas straight-line
+ * calculations with no branches will be considered easier.
+ * Can we do anything about this? It's certainly not clear to
+ * me that tree-structure calculations are _easier_, although
+ * I'm also not convinced they're harder.
+ * + I think for a realistic difficulty assessment we must also
+ * consider the `obviousness' of the arithmetic operations in
+ * some heuristic sense, and also (in Countdown) how many
+ * numbers ended up being used.
+ * - actually try some generations
+ * - at this point we're probably ready to start on the Puzzles
+ * integration.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+/*
+ * To search for numbers we can make, we employ a breadth-first
+ * search across the space of sets of input numbers. That is, for
+ * example, we start with the set (3,6,25,50,75,100); we apply
+ * moves which involve combining two numbers (e.g. adding the 50
+ * and the 75 takes us to the set (3,6,25,100,125); and then we see
+ * if we ever end up with a set containing (say) 952.
+ *
+ * If the rules are changed so that all the numbers must be used,
+ * this is easy to adjust to: we simply see if we end up with a set
+ * containing _only_ (say) 952.
+ *
+ * Obviously, we can vary the rules about permitted arithmetic
+ * operations simply by altering the set of valid moves in the bfs.
+ * However, there's one common rule in this sort of puzzle which
+ * takes a little more thought, and that's _concatenation_. For
+ * example, if you are given (say) four 4s and required to make 10,
+ * you are permitted to combine two of the 4s into a 44 to begin
+ * with, making (44-4)/4 = 10. However, you are generally not
+ * allowed to concatenate two numbers that _weren't_ both in the
+ * original input set (you couldn't multiply two 4s to get 16 and
+ * then concatenate a 4 on to it to make 164), so concatenation is
+ * not an operation which is valid in all situations.
+ *
+ * We could enforce this restriction by storing a flag alongside
+ * each number indicating whether or not it's an original number;
+ * the rules being that concatenation of two numbers is only valid
+ * if they both have the original flag, and that its output _also_
+ * has the original flag (so that you can concatenate three 4s into
+ * a 444), but that applying any other arithmetic operation clears
+ * the original flag on the output. However, we can get marginally
+ * simpler than that by observing that since concatenation has to
+ * happen to a number before any other operation, we can simply
+ * place all the concatenations at the start of the search. In
+ * other words, we have a global flag on an entire number _set_
+ * which indicates whether we are still permitted to perform
+ * concatenations; if so, we can concatenate any of the numbers in
+ * that set. Performing any other operation clears the flag.
+ */
+
+#define SETFLAG_CONCAT 1 /* we can do concatenation */
+
+struct sets;
+
+struct set {
+ int *numbers; /* rationals stored as n,d pairs */
+ short nnumbers; /* # of rationals, so half # of ints */
+ short flags; /* SETFLAG_CONCAT only, at present */
+ struct set *prev; /* index of ancestor set in set list */
+ unsigned char pa, pb, po, pr; /* operation that got here from prev */
+ int npaths; /* number of ways to reach this set */
+};
+
+struct output {
+ int number;
+ struct set *set;
+ int index; /* which number in the set is it? */
+ int npaths; /* number of ways to reach this */
+};
+
+#define SETLISTLEN 1024
+#define NUMBERLISTLEN 32768
+#define OUTPUTLISTLEN 1024
+struct operation;
+struct sets {
+ struct set **setlists;
+ int nsets, nsetlists, setlistsize;
+ tree234 *settree;
+ int **numberlists;
+ int nnumbers, nnumberlists, numberlistsize;
+ struct output **outputlists;
+ int noutputs, noutputlists, outputlistsize;
+ tree234 *outputtree;
+ const struct operation *const *ops;
+};
+
+#define OPFLAG_NEEDS_CONCAT 1
+#define OPFLAG_KEEPS_CONCAT 2
+
+struct operation {
+ /*
+ * Most operations should be shown in the output working, but
+ * concatenation should not; we just take the result of the
+ * concatenation and assume that it's obvious how it was
+ * derived.
+ */
+ int display;
+
+ /*
+ * Text display of the operator.
+ */
+ char *text;
+
+ /*
+ * Flags dictating when the operator can be applied.
+ */
+ int flags;
+
+ /*
+ * Priority of the operator (for avoiding unnecessary
+ * parentheses when formatting it into a string).
+ */
+ int priority;
+
+ /*
+ * Associativity of the operator. Bit 0 means we need parens
+ * when the left operand of one of these operators is another
+ * instance of it, e.g. (2^3)^4. Bit 1 means we need parens
+ * when the right operand is another instance of the same
+ * operator, e.g. 2-(3-4). Thus:
+ *
+ * - this field is 0 for a fully associative operator, since
+ * we never need parens.
+ * - it's 1 for a right-associative operator.
+ * - it's 2 for a left-associative operator.
+ * - it's 3 for a _non_-associative operator (which always
+ * uses parens just to be sure).
+ */
+ int assoc;
+
+ /*
+ * Whether the operator is commutative. Saves time in the
+ * search if we don't have to try it both ways round.
+ */
+ int commutes;
+
+ /*
+ * Function which implements the operator. Returns TRUE on
+ * success, FALSE on failure. Takes two rationals and writes
+ * out a third.
+ */
+ int (*perform)(int *a, int *b, int *output);
+};
+
+struct rules {
+ const struct operation *const *ops;
+ int use_all;
+};
+
+#define MUL(r, a, b) do { \
+ (r) = (a) * (b); \
+ if ((b) && (a) && (r) / (b) != (a)) return FALSE; \
+} while (0)
+
+#define ADD(r, a, b) do { \
+ (r) = (a) + (b); \
+ if ((a) > 0 && (b) > 0 && (r) < 0) return FALSE; \
+ if ((a) < 0 && (b) < 0 && (r) > 0) return FALSE; \
+} while (0)
+
+#define OUT(output, n, d) do { \
+ int g = gcd((n),(d)); \
+ if ((d) < 0) g = -g; \
+ (output)[0] = (n)/g; \
+ (output)[1] = (d)/g; \
+ assert((output)[1] > 0); \
+} while (0)
+
+static int gcd(int x, int y)
+{
+ while (x != 0 && y != 0) {
+ int t = x;
+ x = y;
+ y = t % y;
+ }
+
+ return abs(x + y); /* i.e. whichever one isn't zero */
+}
+
+static int perform_add(int *a, int *b, int *output)
+{
+ int at, bt, tn, bn;
+ /*
+ * a0/a1 + b0/b1 = (a0*b1 + b0*a1) / (a1*b1)
+ */
+ MUL(at, a[0], b[1]);
+ MUL(bt, b[0], a[1]);
+ ADD(tn, at, bt);
+ MUL(bn, a[1], b[1]);
+ OUT(output, tn, bn);
+ return TRUE;
+}
+
+static int perform_sub(int *a, int *b, int *output)
+{
+ int at, bt, tn, bn;
+ /*
+ * a0/a1 - b0/b1 = (a0*b1 - b0*a1) / (a1*b1)
+ */
+ MUL(at, a[0], b[1]);
+ MUL(bt, b[0], a[1]);
+ ADD(tn, at, -bt);
+ MUL(bn, a[1], b[1]);
+ OUT(output, tn, bn);
+ return TRUE;
+}
+
+static int perform_mul(int *a, int *b, int *output)
+{
+ int tn, bn;
+ /*
+ * a0/a1 * b0/b1 = (a0*b0) / (a1*b1)
+ */
+ MUL(tn, a[0], b[0]);
+ MUL(bn, a[1], b[1]);
+ OUT(output, tn, bn);
+ return TRUE;
+}
+
+static int perform_div(int *a, int *b, int *output)
+{
+ int tn, bn;
+
+ /*
+ * Division by zero is outlawed.
+ */
+ if (b[0] == 0)
+ return FALSE;
+
+ /*
+ * a0/a1 / b0/b1 = (a0*b1) / (a1*b0)
+ */
+ MUL(tn, a[0], b[1]);
+ MUL(bn, a[1], b[0]);
+ OUT(output, tn, bn);
+ return TRUE;
+}
+
+static int perform_exact_div(int *a, int *b, int *output)
+{
+ int tn, bn;
+
+ /*
+ * Division by zero is outlawed.
+ */
+ if (b[0] == 0)
+ return FALSE;
+
+ /*
+ * a0/a1 / b0/b1 = (a0*b1) / (a1*b0)
+ */
+ MUL(tn, a[0], b[1]);
+ MUL(bn, a[1], b[0]);
+ OUT(output, tn, bn);
+
+ /*
+ * Exact division means we require the result to be an integer.
+ */
+ return (output[1] == 1);
+}
+
+static int perform_concat(int *a, int *b, int *output)
+{
+ int t1, t2, p10;
+
+ /*
+ * We can't concatenate anything which isn't an integer.
+ */
+ if (a[1] != 1 || b[1] != 1)
+ return FALSE;
+
+ /*
+ * For concatenation, we can safely assume leading zeroes
+ * aren't an issue. It isn't clear whether they `should' be
+ * allowed, but it turns out not to matter: concatenating a
+ * leading zero on to a number in order to harmlessly get rid
+ * of the zero is never necessary because unwanted zeroes can
+ * be disposed of by adding them to something instead. So we
+ * disallow them always.
+ *
+ * The only other possibility is that you might want to
+ * concatenate a leading zero on to something and then
+ * concatenate another non-zero digit on to _that_ (to make,
+ * for example, 106); but that's also unnecessary, because you
+ * can make 106 just as easily by concatenating the 0 on to the
+ * _end_ of the 1 first.
+ */
+ if (a[0] == 0)
+ return FALSE;
+
+ /*
+ * Find the smallest power of ten strictly greater than b. This
+ * is the power of ten by which we'll multiply a.
+ *
+ * Special case: we must multiply a by at least 10, even if b
+ * is zero.
+ */
+ p10 = 10;
+ while (p10 <= (INT_MAX/10) && p10 <= b[0])
+ p10 *= 10;
+ if (p10 > INT_MAX/10)
+ return FALSE; /* integer overflow */
+ MUL(t1, p10, a[0]);
+ ADD(t2, t1, b[0]);
+ OUT(output, t2, 1);
+ return TRUE;
+}
+
+const static struct operation op_add = {
+ TRUE, "+", 0, 10, 0, TRUE, perform_add
+};
+const static struct operation op_sub = {
+ TRUE, "-", 0, 10, 2, FALSE, perform_sub
+};
+const static struct operation op_mul = {
+ TRUE, "*", 0, 20, 0, TRUE, perform_mul
+};
+const static struct operation op_div = {
+ TRUE, "/", 0, 20, 2, FALSE, perform_div
+};
+const static struct operation op_xdiv = {
+ TRUE, "/", 0, 20, 2, FALSE, perform_exact_div
+};
+const static struct operation op_concat = {
+ FALSE, "", OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT,
+ 1000, 0, FALSE, perform_concat
+};
+
+/*
+ * In Countdown, divisions resulting in fractions are disallowed.
+ * http://www.askoxford.com/wordgames/countdown/rules/
+ */
+const static struct operation *const ops_countdown[] = {
+ &op_add, &op_mul, &op_sub, &op_xdiv, NULL
+};
+const static struct rules rules_countdown = {
+ ops_countdown, FALSE
+};
+
+/*
+ * A slightly different rule set which handles the reasonably well
+ * known puzzle of making 24 using two 3s and two 8s. For this we
+ * need rational rather than integer division.
+ */
+const static struct operation *const ops_3388[] = {
+ &op_add, &op_mul, &op_sub, &op_div, NULL
+};
+const static struct rules rules_3388 = {
+ ops_3388, TRUE
+};
+
+/*
+ * A still more permissive rule set usable for the four-4s problem
+ * and similar things. Permits concatenation.
+ */
+const static struct operation *const ops_four4s[] = {
+ &op_add, &op_mul, &op_sub, &op_div, &op_concat, NULL
+};
+const static struct rules rules_four4s = {
+ ops_four4s, TRUE
+};
+
+#define ratcmp(a,op,b) ( (long long)(a)[0] * (b)[1] op \
+ (long long)(b)[0] * (a)[1] )
+
+static int addtoset(struct set *set, int newnumber[2])
+{
+ int i, j;
+
+ /* Find where we want to insert the new number */
+ for (i = 0; i < set->nnumbers &&
+ ratcmp(set->numbers+2*i, <, newnumber); i++);
+
+ /* Move everything else up */
+ for (j = set->nnumbers; j > i; j--) {
+ set->numbers[2*j] = set->numbers[2*j-2];
+ set->numbers[2*j+1] = set->numbers[2*j-1];
+ }
+
+ /* Insert the new number */
+ set->numbers[2*i] = newnumber[0];
+ set->numbers[2*i+1] = newnumber[1];
+
+ set->nnumbers++;
+
+ return i;
+}
+
+#define ensure(array, size, newlen, type) do { \
+ if ((newlen) > (size)) { \
+ (size) = (newlen) + 512; \
+ (array) = sresize((array), (size), type); \
+ } \
+} while (0)
+
+static int setcmp(void *av, void *bv)
+{
+ struct set *a = (struct set *)av;
+ struct set *b = (struct set *)bv;
+ int i;
+
+ if (a->nnumbers < b->nnumbers)
+ return -1;
+ else if (a->nnumbers > b->nnumbers)
+ return +1;
+
+ if (a->flags < b->flags)
+ return -1;
+ else if (a->flags > b->flags)
+ return +1;
+
+ for (i = 0; i < a->nnumbers; i++) {
+ if (ratcmp(a->numbers+2*i, <, b->numbers+2*i))
+ return -1;
+ else if (ratcmp(a->numbers+2*i, >, b->numbers+2*i))
+ return +1;
+ }
+
+ return 0;
+}
+
+static int outputcmp(void *av, void *bv)
+{
+ struct output *a = (struct output *)av;
+ struct output *b = (struct output *)bv;
+
+ if (a->number < b->number)
+ return -1;
+ else if (a->number > b->number)
+ return +1;
+
+ return 0;
+}
+
+static int outputfindcmp(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct output *b = (struct output *)bv;
+
+ if (*a < b->number)
+ return -1;
+ else if (*a > b->number)
+ return +1;
+
+ return 0;
+}
+
+static void addset(struct sets *s, struct set *set, struct set *prev)
+{
+ struct set *s2;
+ int npaths = (prev ? prev->npaths : 1);
+
+ assert(set == s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN);
+ s2 = add234(s->settree, set);
+ if (s2 == set) {
+ /*
+ * New set added to the tree.
+ */
+ set->prev = prev;
+ set->npaths = npaths;
+ s->nsets++;
+ s->nnumbers += 2 * set->nnumbers;
+ } else {
+ /*
+ * Rediscovered an existing set. Update its npaths only.
+ */
+ s2->npaths += npaths;
+ }
+}
+
+static struct set *newset(struct sets *s, int nnumbers, int flags)
+{
+ struct set *sn;
+
+ ensure(s->setlists, s->setlistsize, s->nsets/SETLISTLEN+1, struct set *);
+ while (s->nsetlists <= s->nsets / SETLISTLEN)
+ s->setlists[s->nsetlists++] = snewn(SETLISTLEN, struct set);
+ sn = s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN;
+
+ if (s->nnumbers + nnumbers * 2 > s->nnumberlists * NUMBERLISTLEN)
+ s->nnumbers = s->nnumberlists * NUMBERLISTLEN;
+ ensure(s->numberlists, s->numberlistsize,
+ s->nnumbers/NUMBERLISTLEN+1, int *);
+ while (s->nnumberlists <= s->nnumbers / NUMBERLISTLEN)
+ s->numberlists[s->nnumberlists++] = snewn(NUMBERLISTLEN, int);
+ sn->numbers = s->numberlists[s->nnumbers / NUMBERLISTLEN] +
+ s->nnumbers % NUMBERLISTLEN;
+
+ /*
+ * Start the set off empty.
+ */
+ sn->nnumbers = 0;
+
+ sn->flags = flags;
+
+ return sn;
+}
+
+static int addoutput(struct sets *s, struct set *ss, int index, int *n)
+{
+ struct output *o, *o2;
+
+ /*
+ * Target numbers are always integers.
+ */
+ if (ss->numbers[2*index+1] != 1)
+ return FALSE;
+
+ ensure(s->outputlists, s->outputlistsize, s->noutputs/OUTPUTLISTLEN+1,
+ struct output *);
+ while (s->noutputlists <= s->noutputs / OUTPUTLISTLEN)
+ s->outputlists[s->noutputlists++] = snewn(OUTPUTLISTLEN,
+ struct output);
+ o = s->outputlists[s->noutputs / OUTPUTLISTLEN] +
+ s->noutputs % OUTPUTLISTLEN;
+
+ o->number = ss->numbers[2*index];
+ o->set = ss;
+ o->index = index;
+ o->npaths = ss->npaths;
+ o2 = add234(s->outputtree, o);
+ if (o2 != o) {
+ o2->npaths += o->npaths;
+ } else {
+ s->noutputs++;
+ }
+ *n = o->number;
+ return TRUE;
+}
+
+static struct sets *do_search(int ninputs, int *inputs,
+ const struct rules *rules, int *target)
+{
+ struct sets *s;
+ struct set *sn;
+ int qpos, i;
+ const struct operation *const *ops = rules->ops;
+
+ s = snew(struct sets);
+ s->setlists = NULL;
+ s->nsets = s->nsetlists = s->setlistsize = 0;
+ s->numberlists = NULL;
+ s->nnumbers = s->nnumberlists = s->numberlistsize = 0;
+ s->outputlists = NULL;
+ s->noutputs = s->noutputlists = s->outputlistsize = 0;
+ s->settree = newtree234(setcmp);
+ s->outputtree = newtree234(outputcmp);
+ s->ops = ops;
+
+ /*
+ * Start with the input set.
+ */
+ sn = newset(s, ninputs, SETFLAG_CONCAT);
+ for (i = 0; i < ninputs; i++) {
+ int newnumber[2];
+ newnumber[0] = inputs[i];
+ newnumber[1] = 1;
+ addtoset(sn, newnumber);
+ }
+ addset(s, sn, NULL);
+
+ /*
+ * Now perform the breadth-first search: keep looping over sets
+ * until we run out of steam.
+ */
+ qpos = 0;
+ while (qpos < s->nsets) {
+ struct set *ss = s->setlists[qpos / SETLISTLEN] + qpos % SETLISTLEN;
+ struct set *sn;
+ int i, j, k, m;
+
+ /*
+ * Record all the valid output numbers in this state. We
+ * can always do this if there's only one number in the
+ * state; otherwise, we can only do it if we aren't
+ * required to use all the numbers in coming to our answer.
+ */
+ if (ss->nnumbers == 1 || !rules->use_all) {
+ for (i = 0; i < ss->nnumbers; i++) {
+ int n;
+
+ if (addoutput(s, ss, i, &n) && target && n == *target)
+ return s;
+ }
+ }
+
+ /*
+ * Try every possible operation from this state.
+ */
+ for (k = 0; ops[k] && ops[k]->perform; k++) {
+ if ((ops[k]->flags & OPFLAG_NEEDS_CONCAT) &&
+ !(ss->flags & SETFLAG_CONCAT))
+ continue; /* can't use this operation here */
+ for (i = 0; i < ss->nnumbers; i++) {
+ for (j = 0; j < ss->nnumbers; j++) {
+ int n[2];
+
+ if (i == j)
+ continue; /* can't combine a number with itself */
+ if (i > j && ops[k]->commutes)
+ continue; /* no need to do this both ways round */
+ if (!ops[k]->perform(ss->numbers+2*i, ss->numbers+2*j, n))
+ continue; /* operation failed */
+
+ sn = newset(s, ss->nnumbers-1, ss->flags);
+
+ if (!(ops[k]->flags & OPFLAG_KEEPS_CONCAT))
+ sn->flags &= ~SETFLAG_CONCAT;
+
+ for (m = 0; m < ss->nnumbers; m++) {
+ if (m == i || m == j)
+ continue;
+ sn->numbers[2*sn->nnumbers] = ss->numbers[2*m];
+ sn->numbers[2*sn->nnumbers + 1] = ss->numbers[2*m + 1];
+ sn->nnumbers++;
+ }
+ sn->pa = i;
+ sn->pb = j;
+ sn->po = k;
+ sn->pr = addtoset(sn, n);
+ addset(s, sn, ss);
+ }
+ }
+ }
+
+ qpos++;
+ }
+
+ return s;
+}
+
+static void free_sets(struct sets *s)
+{
+ int i;
+
+ freetree234(s->settree);
+ freetree234(s->outputtree);
+ for (i = 0; i < s->nsetlists; i++)
+ sfree(s->setlists[i]);
+ sfree(s->setlists);
+ for (i = 0; i < s->nnumberlists; i++)
+ sfree(s->numberlists[i]);
+ sfree(s->numberlists);
+ for (i = 0; i < s->noutputlists; i++)
+ sfree(s->outputlists[i]);
+ sfree(s->outputlists);
+ sfree(s);
+}
+
+/*
+ * Construct a text formula for producing a given output.
+ */
+void mkstring_recurse(char **str, int *len,
+ struct sets *s, struct set *ss, int index,
+ int priority, int assoc, int child)
+{
+ if (ss->prev && index != ss->pr) {
+ int pi;
+
+ /*
+ * This number was passed straight down from this set's
+ * predecessor. Find its index in the previous set and
+ * recurse to there.
+ */
+ pi = index;
+ assert(pi != ss->pr);
+ if (pi > ss->pr)
+ pi--;
+ if (pi >= min(ss->pa, ss->pb)) {
+ pi++;
+ if (pi >= max(ss->pa, ss->pb))
+ pi++;
+ }
+ mkstring_recurse(str, len, s, ss->prev, pi, priority, assoc, child);
+ } else if (ss->prev && index == ss->pr &&
+ s->ops[ss->po]->display) {
+ /*
+ * This number was created by a displayed operator in the
+ * transition from this set to its predecessor. Hence we
+ * write an open paren, then recurse into the first
+ * operand, then write the operator, then the second
+ * operand, and finally close the paren.
+ */
+ char *op;
+ int parens, thispri, thisassoc;
+
+ /*
+ * Determine whether we need parentheses.
+ */
+ thispri = s->ops[ss->po]->priority;
+ thisassoc = s->ops[ss->po]->assoc;
+ parens = (thispri < priority ||
+ (thispri == priority && (assoc & child)));
+
+ if (parens) {
+ if (str)
+ *(*str)++ = '(';
+ if (len)
+ (*len)++;
+ }
+ mkstring_recurse(str, len, s, ss->prev, ss->pa, thispri, thisassoc, 1);
+ for (op = s->ops[ss->po]->text; *op; op++) {
+ if (str)
+ *(*str)++ = *op;
+ if (len)
+ (*len)++;
+ }
+ mkstring_recurse(str, len, s, ss->prev, ss->pb, thispri, thisassoc, 2);
+ if (parens) {
+ if (str)
+ *(*str)++ = ')';
+ if (len)
+ (*len)++;
+ }
+ } else {
+ /*
+ * This number is either an original, or something formed
+ * by a non-displayed operator (concatenation). Either way,
+ * we display it as is.
+ */
+ char buf[80], *p;
+ int blen;
+ blen = sprintf(buf, "%d", ss->numbers[2*index]);
+ if (ss->numbers[2*index+1] != 1)
+ blen += sprintf(buf+blen, "/%d", ss->numbers[2*index+1]);
+ assert(blen < lenof(buf));
+ for (p = buf; *p; p++) {
+ if (str)
+ *(*str)++ = *p;
+ if (len)
+ (*len)++;
+ }
+ }
+}
+char *mkstring(struct sets *s, struct output *o)
+{
+ int len;
+ char *str, *p;
+
+ len = 0;
+ mkstring_recurse(NULL, &len, s, o->set, o->index, 0, 0, 0);
+ str = snewn(len+1, char);
+ p = str;
+ mkstring_recurse(&p, NULL, s, o->set, o->index, 0, 0, 0);
+ assert(p - str <= len);
+ *p = '\0';
+ return str;
+}
+
+int main(int argc, char **argv)
+{
+ int doing_opts = TRUE;
+ const struct rules *rules = NULL;
+ char *pname = argv[0];
+ int got_target = FALSE, target = 0;
+ int numbers[10], nnumbers = 0;
+ int verbose = FALSE;
+ int pathcounts = FALSE;
+
+ struct output *o;
+ struct sets *s;
+ int i, start, limit;
+
+ while (--argc) {
+ char *p = *++argv;
+ int c;
+
+ if (doing_opts && *p == '-') {
+ p++;
+
+ if (!strcmp(p, "-")) {
+ doing_opts = FALSE;
+ continue;
+ } else while (*p) switch (c = *p++) {
+ case 'C':
+ rules = &rules_countdown;
+ break;
+ case 'B':
+ rules = &rules_3388;
+ break;
+ case 'D':
+ rules = &rules_four4s;
+ break;
+ case 'v':
+ verbose = TRUE;
+ break;
+ case 'p':
+ pathcounts = TRUE;
+ break;
+ case 't':
+ {
+ char *v;
+ if (*p) {
+ v = p;
+ p = NULL;
+ } else if (--argc) {
+ v = *++argv;
+ } else {
+ fprintf(stderr, "%s: option '-%c' expects an"
+ " argument\n", pname, c);
+ return 1;
+ }
+ switch (c) {
+ case 't':
+ got_target = TRUE;
+ target = atoi(v);
+ break;
+ }
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: option '-%c' not"
+ " recognised\n", pname, c);
+ return 1;
+ }
+ } else {
+ if (nnumbers >= lenof(numbers)) {
+ fprintf(stderr, "%s: internal limit of %d numbers exceeded\n",
+ pname, lenof(numbers));
+ return 1;
+ } else {
+ numbers[nnumbers++] = atoi(p);
+ }
+ }
+ }
+
+ if (!rules) {
+ fprintf(stderr, "%s: no rule set specified; use -C,-B,-D\n", pname);
+ return 1;
+ }
+
+ if (!nnumbers) {
+ fprintf(stderr, "%s: no input numbers specified\n", pname);
+ return 1;
+ }
+
+ s = do_search(nnumbers, numbers, rules, (got_target ? &target : NULL));
+
+ if (got_target) {
+ o = findrelpos234(s->outputtree, &target, outputfindcmp,
+ REL234_LE, &start);
+ if (!o)
+ start = -1;
+ o = findrelpos234(s->outputtree, &target, outputfindcmp,
+ REL234_GE, &limit);
+ if (!o)
+ limit = -1;
+ assert(start != -1 || limit != -1);
+ if (start == -1)
+ start = limit;
+ else if (limit == -1)
+ limit = start;
+ limit++;
+ } else {
+ start = 0;
+ limit = count234(s->outputtree);
+ }
+
+ for (i = start; i < limit; i++) {
+ o = index234(s->outputtree, i);
+
+ printf("%d", o->number);
+
+ if (pathcounts)
+ printf(" [%d]", o->npaths);
+
+ if (got_target || verbose) {
+ char *p = mkstring(s, o);
+ printf(" = %s", p);
+ sfree(p);
+ }
+
+ printf("\n");
+ }
+
+ free_sets(s);
+
+ return 0;
+}
--- /dev/null
+++ b/unfinished/path.c
@@ -1,0 +1,786 @@
+/*
+ * Experimental grid generator for Nikoli's `Number Link' puzzle.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "puzzles.h"
+
+/*
+ * 2005-07-08: This is currently a Path grid generator which will
+ * construct valid grids at a plausible speed. However, the grids
+ * are not of suitable quality to be used directly as puzzles.
+ *
+ * The basic strategy is to start with an empty grid, and
+ * repeatedly either (a) add a new path to it, or (b) extend one
+ * end of a path by one square in some direction and push other
+ * paths into new shapes in the process. The effect of this is that
+ * we are able to construct a set of paths which between them fill
+ * the entire grid.
+ *
+ * Quality issues: if we set the main loop to do (a) where possible
+ * and (b) only where necessary, we end up with a grid containing a
+ * few too many small paths, which therefore doesn't make for an
+ * interesting puzzle. If we reverse the priority so that we do (b)
+ * where possible and (a) only where necessary, we end up with some
+ * staggeringly interwoven grids with very very few separate paths,
+ * but the result of this is that there's invariably a solution
+ * other than the intended one which leaves many grid squares
+ * unfilled. There's also a separate problem which is that many
+ * grids have really boring and obvious paths in them, such as the
+ * entire bottom row of the grid being taken up by a single path.
+ *
+ * It's not impossible that a few tweaks might eliminate or reduce
+ * the incidence of boring paths, and might also find a happy
+ * medium between too many and too few. There remains the question
+ * of unique solutions, however. I fear there is no alternative but
+ * to write - somehow! - a solver.
+ *
+ * While I'm here, some notes on UI strategy for the parts of the
+ * puzzle implementation that _aren't_ the generator:
+ *
+ * - data model is to track connections between adjacent squares,
+ * so that you aren't limited to extending a path out from each
+ * number but can also mark sections of path which you know
+ * _will_ come in handy later.
+ *
+ * - user interface is to click in one square and drag to an
+ * adjacent one, thus creating a link between them. We can
+ * probably tolerate rapid mouse motion causing a drag directly
+ * to a square which is a rook move away, but any other rapid
+ * motion is ambiguous and probably the best option is to wait
+ * until the mouse returns to a square we know how to reach.
+ *
+ * - a drag causing the current path to backtrack has the effect
+ * of removing bits of it.
+ *
+ * - the UI should enforce at all times the constraint that at
+ * most two links can come into any square.
+ *
+ * - my Cunning Plan for actually implementing this: the game_ui
+ * contains a grid-sized array, which is copied from the current
+ * game_state on starting a drag. While a drag is active, the
+ * contents of the game_ui is adjusted with every mouse motion,
+ * and is displayed _in place_ of the game_state itself. On
+ * termination of a drag, the game_ui array is copied back into
+ * the new game_state (or rather, a string move is encoded which
+ * has precisely the set of link changes to cause that effect).
+ */
+
+/*
+ * Standard notation for directions.
+ */
+#define L 0
+#define U 1
+#define R 2
+#define D 3
+#define DX(dir) ( (dir)==L ? -1 : (dir)==R ? +1 : 0)
+#define DY(dir) ( (dir)==U ? -1 : (dir)==D ? +1 : 0)
+
+/*
+ * Perform a breadth-first search over a grid of squares with the
+ * colour of square (X,Y) given by grid[Y*w+X]. The search begins
+ * at (x,y), and finds all squares which are the same colour as
+ * (x,y) and reachable from it by orthogonal moves. On return:
+ * - dist[Y*w+X] gives the distance of (X,Y) from (x,y), or -1 if
+ * unreachable or a different colour
+ * - the returned value is the number of reachable squares,
+ * including (x,y) itself
+ * - list[0] up to list[returned value - 1] list those squares, in
+ * increasing order of distance from (x,y) (and in arbitrary
+ * order within that).
+ */
+static int bfs(int w, int h, int *grid, int x, int y, int *dist, int *list)
+{
+ int i, j, c, listsize, listdone;
+
+ /*
+ * Start by clearing the output arrays.
+ */
+ for (i = 0; i < w*h; i++)
+ dist[i] = list[i] = -1;
+
+ /*
+ * Set up the initial list.
+ */
+ listsize = 1;
+ listdone = 0;
+ list[0] = y*w+x;
+ dist[y*w+x] = 0;
+ c = grid[y*w+x];
+
+ /*
+ * Repeatedly process a square and add any extra squares to the
+ * end of list.
+ */
+ while (listdone < listsize) {
+ i = list[listdone++];
+ y = i / w;
+ x = i % w;
+ for (j = 0; j < 4; j++) {
+ int xx, yy, ii;
+
+ xx = x + DX(j);
+ yy = y + DY(j);
+ ii = yy*w+xx;
+
+ if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
+ grid[ii] == c && dist[ii] == -1) {
+ dist[ii] = dist[i] + 1;
+ assert(listsize < w*h);
+ list[listsize++] = ii;
+ }
+ }
+ }
+
+ return listsize;
+}
+
+struct genctx {
+ int w, h;
+ int *grid, *sparegrid, *sparegrid2, *sparegrid3;
+ int *dist, *list;
+
+ int npaths, pathsize;
+ int *pathends, *sparepathends; /* 2*npaths entries */
+ int *pathspare; /* npaths entries */
+ int *extends; /* 8*npaths entries */
+};
+
+static struct genctx *new_genctx(int w, int h)
+{
+ struct genctx *ctx = snew(struct genctx);
+ ctx->w = w;
+ ctx->h = h;
+ ctx->grid = snewn(w * h, int);
+ ctx->sparegrid = snewn(w * h, int);
+ ctx->sparegrid2 = snewn(w * h, int);
+ ctx->sparegrid3 = snewn(w * h, int);
+ ctx->dist = snewn(w * h, int);
+ ctx->list = snewn(w * h, int);
+ ctx->npaths = ctx->pathsize = 0;
+ ctx->pathends = ctx->sparepathends = ctx->pathspare = ctx->extends = NULL;
+ return ctx;
+}
+
+static void free_genctx(struct genctx *ctx)
+{
+ sfree(ctx->grid);
+ sfree(ctx->sparegrid);
+ sfree(ctx->sparegrid2);
+ sfree(ctx->sparegrid3);
+ sfree(ctx->dist);
+ sfree(ctx->list);
+ sfree(ctx->pathends);
+ sfree(ctx->sparepathends);
+ sfree(ctx->pathspare);
+ sfree(ctx->extends);
+}
+
+static int newpath(struct genctx *ctx)
+{
+ int n;
+
+ n = ctx->npaths++;
+ if (ctx->npaths > ctx->pathsize) {
+ ctx->pathsize += 16;
+ ctx->pathends = sresize(ctx->pathends, ctx->pathsize*2, int);
+ ctx->sparepathends = sresize(ctx->sparepathends, ctx->pathsize*2, int);
+ ctx->pathspare = sresize(ctx->pathspare, ctx->pathsize, int);
+ ctx->extends = sresize(ctx->extends, ctx->pathsize*8, int);
+ }
+ return n;
+}
+
+static int is_endpoint(struct genctx *ctx, int x, int y)
+{
+ int w = ctx->w, h = ctx->h, c;
+
+ assert(x >= 0 && x < w && y >= 0 && y < h);
+
+ c = ctx->grid[y*w+x];
+ if (c < 0)
+ return FALSE; /* empty square is not an endpoint! */
+ assert(c >= 0 && c < ctx->npaths);
+ if (ctx->pathends[c*2] == y*w+x || ctx->pathends[c*2+1] == y*w+x)
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * Tries to extend a path by one square in the given direction,
+ * pushing other paths around if necessary. Returns TRUE on success
+ * or FALSE on failure.
+ */
+static int extend_path(struct genctx *ctx, int path, int end, int direction)
+{
+ int w = ctx->w, h = ctx->h;
+ int x, y, xe, ye, cut;
+ int i, j, jp, n, first, last;
+
+ assert(path >= 0 && path < ctx->npaths);
+ assert(end == 0 || end == 1);
+
+ /*
+ * Find the endpoint of the path and the point we plan to
+ * extend it into.
+ */
+ y = ctx->pathends[path * 2 + end] / w;
+ x = ctx->pathends[path * 2 + end] % w;
+ assert(x >= 0 && x < w && y >= 0 && y < h);
+
+ xe = x + DX(direction);
+ ye = y + DY(direction);
+ if (xe < 0 || xe >= w || ye < 0 || ye >= h)
+ return FALSE; /* could not extend in this direction */
+
+ /*
+ * We don't extend paths _directly_ into endpoints of other
+ * paths, although we don't mind too much if a knock-on effect
+ * of an extension is to push part of another path into a third
+ * path's endpoint.
+ */
+ if (is_endpoint(ctx, xe, ye))
+ return FALSE;
+
+ /*
+ * We can't extend a path back the way it came.
+ */
+ if (ctx->grid[ye*w+xe] == path)
+ return FALSE;
+
+ /*
+ * Paths may not double back on themselves. Check if the new
+ * point is adjacent to any point of this path other than (x,y).
+ */
+ for (j = 0; j < 4; j++) {
+ int xf, yf;
+
+ xf = xe + DX(j);
+ yf = ye + DY(j);
+
+ if (xf >= 0 && xf < w && yf >= 0 && yf < h &&
+ (xf != x || yf != y) && ctx->grid[yf*w+xf] == path)
+ return FALSE;
+ }
+
+ /*
+ * Now we're convinced it's valid to _attempt_ the extension.
+ * It may still fail if we run out of space to push other paths
+ * into.
+ *
+ * So now we can set up our temporary data structures. We will
+ * need:
+ *
+ * - a spare copy of the grid on which to gradually move paths
+ * around (sparegrid)
+ *
+ * - a second spare copy with which to remember how paths
+ * looked just before being cut (sparegrid2). FIXME: is
+ * sparegrid2 necessary? right now it's never different from
+ * grid itself
+ *
+ * - a third spare copy with which to do the internal
+ * calculations involved in reconstituting a cut path
+ * (sparegrid3)
+ *
+ * - something to track which paths currently need
+ * reconstituting after being cut, and which have already
+ * been cut (pathspare)
+ *
+ * - a spare copy of pathends to store the altered states in
+ * (sparepathends)
+ */
+ memcpy(ctx->sparegrid, ctx->grid, w*h*sizeof(int));
+ memcpy(ctx->sparegrid2, ctx->grid, w*h*sizeof(int));
+ memcpy(ctx->sparepathends, ctx->pathends, ctx->npaths*2*sizeof(int));
+ for (i = 0; i < ctx->npaths; i++)
+ ctx->pathspare[i] = 0; /* 0=untouched, 1=broken, 2=fixed */
+
+ /*
+ * Working in sparegrid, actually extend the path. If it cuts
+ * another, begin a loop in which we restore any cut path by
+ * moving it out of the way.
+ */
+ cut = ctx->sparegrid[ye*w+xe];
+ ctx->sparegrid[ye*w+xe] = path;
+ ctx->sparepathends[path*2+end] = ye*w+xe;
+ ctx->pathspare[path] = 2; /* this one is sacrosanct */
+ if (cut >= 0) {
+ assert(cut >= 0 && cut < ctx->npaths);
+ ctx->pathspare[cut] = 1; /* broken */
+
+ while (1) {
+ for (i = 0; i < ctx->npaths; i++)
+ if (ctx->pathspare[i] == 1)
+ break;
+ if (i == ctx->npaths)
+ break; /* we're done */
+
+ /*
+ * Path i needs restoring. So walk along its original
+ * track (as given in sparegrid2) and see where it's
+ * been cut. Where it has, surround the cut points in
+ * the same colour, without overwriting already-fixed
+ * paths.
+ */
+ memcpy(ctx->sparegrid3, ctx->sparegrid, w*h*sizeof(int));
+ n = bfs(w, h, ctx->sparegrid2,
+ ctx->pathends[i*2] % w, ctx->pathends[i*2] / w,
+ ctx->dist, ctx->list);
+ first = last = -1;
+if (ctx->sparegrid3[ctx->pathends[i*2]] != i ||
+ ctx->sparegrid3[ctx->pathends[i*2+1]] != i) return FALSE;/* FIXME */
+ for (j = 0; j < n; j++) {
+ jp = ctx->list[j];
+ assert(ctx->dist[jp] == j);
+ assert(ctx->sparegrid2[jp] == i);
+
+ /*
+ * Wipe out the original path in sparegrid.
+ */
+ if (ctx->sparegrid[jp] == i)
+ ctx->sparegrid[jp] = -1;
+
+ /*
+ * Be prepared to shorten the path at either end if
+ * the endpoints have been stomped on.
+ */
+ if (ctx->sparegrid3[jp] == i) {
+ if (first < 0)
+ first = jp;
+ last = jp;
+ }
+
+ if (ctx->sparegrid3[jp] != i) {
+ int jx = jp % w, jy = jp / w;
+ int dx, dy;
+ for (dy = -1; dy <= +1; dy++)
+ for (dx = -1; dx <= +1; dx++) {
+ int newp, newv;
+ if (!dy && !dx)
+ continue; /* central square */
+ if (jx+dx < 0 || jx+dx >= w ||
+ jy+dy < 0 || jy+dy >= h)
+ continue; /* out of range */
+ newp = (jy+dy)*w+(jx+dx);
+ newv = ctx->sparegrid3[newp];
+ if (newv >= 0 && (newv == i ||
+ ctx->pathspare[newv] == 2))
+ continue; /* can't use this square */
+ ctx->sparegrid3[newp] = i;
+ }
+ }
+ }
+
+ if (first < 0 || last < 0)
+ return FALSE; /* path is completely wiped out! */
+
+ /*
+ * Now we've covered sparegrid3 in possible squares for
+ * the new layout of path i. Find the actual layout
+ * we're going to use by bfs: we want the shortest path
+ * from one endpoint to the other.
+ */
+ n = bfs(w, h, ctx->sparegrid3, first % w, first / w,
+ ctx->dist, ctx->list);
+ if (ctx->dist[last] < 2) {
+ /*
+ * Either there is no way to get between the path's
+ * endpoints, or the remaining endpoints simply
+ * aren't far enough apart to make the path viable
+ * any more. This means the entire push operation
+ * has failed.
+ */
+ return FALSE;
+ }
+
+ /*
+ * Write the new path into sparegrid. Also save the new
+ * endpoint locations, in case they've changed.
+ */
+ jp = last;
+ j = ctx->dist[jp];
+ while (1) {
+ int d;
+
+ if (ctx->sparegrid[jp] >= 0) {
+ if (ctx->pathspare[ctx->sparegrid[jp]] == 2)
+ return FALSE; /* somehow we've hit a fixed path */
+ ctx->pathspare[ctx->sparegrid[jp]] = 1; /* broken */
+ }
+ ctx->sparegrid[jp] = i;
+
+ if (j == 0)
+ break;
+
+ /*
+ * Now look at the neighbours of jp to find one
+ * which has dist[] one less.
+ */
+ for (d = 0; d < 4; d++) {
+ int jx = (jp % w) + DX(d), jy = (jp / w) + DY(d);
+ if (jx >= 0 && jx < w && jy >= 0 && jy < w &&
+ ctx->dist[jy*w+jx] == j-1) {
+ jp = jy*w+jx;
+ j--;
+ break;
+ }
+ }
+ assert(d < 4);
+ }
+
+ ctx->sparepathends[i*2] = first;
+ ctx->sparepathends[i*2+1] = last;
+//printf("new ends of path %d: %d,%d\n", i, first, last);
+ ctx->pathspare[i] = 2; /* fixed */
+ }
+ }
+
+ /*
+ * If we got here, the extension was successful!
+ */
+ memcpy(ctx->grid, ctx->sparegrid, w*h*sizeof(int));
+ memcpy(ctx->pathends, ctx->sparepathends, ctx->npaths*2*sizeof(int));
+ return TRUE;
+}
+
+/*
+ * Tries to add a new path to the grid.
+ */
+static int add_path(struct genctx *ctx, random_state *rs)
+{
+ int w = ctx->w, h = ctx->h;
+ int i, ii, n;
+
+ /*
+ * Our strategy is:
+ * - randomly choose an empty square in the grid
+ * - do a BFS from that point to find a long path starting
+ * from it
+ * - if we run out of viable empty squares, return failure.
+ */
+
+ /*
+ * Use `sparegrid' to collect a list of empty squares.
+ */
+ n = 0;
+ for (i = 0; i < w*h; i++)
+ if (ctx->grid[i] == -1)
+ ctx->sparegrid[n++] = i;
+
+ /*
+ * Shuffle the grid.
+ */
+ for (i = n; i-- > 1 ;) {
+ int k = random_upto(rs, i+1);
+ if (k != i) {
+ int t = ctx->sparegrid[i];
+ ctx->sparegrid[i] = ctx->sparegrid[k];
+ ctx->sparegrid[k] = t;
+ }
+ }
+
+ /*
+ * Loop over it trying to add paths. This looks like a
+ * horrifying N^4 algorithm (that is, (w*h)^2), but I predict
+ * that in fact the worst case will very rarely arise because
+ * when there's lots of grid space an attempt will succeed very
+ * quickly.
+ */
+ for (ii = 0; ii < n; ii++) {
+ int i = ctx->sparegrid[ii];
+ int y = i / w, x = i % w, nsq;
+ int r, c, j;
+
+ /*
+ * BFS from here to find long paths.
+ */
+ nsq = bfs(w, h, ctx->grid, x, y, ctx->dist, ctx->list);
+
+ /*
+ * If there aren't any long enough, give up immediately.
+ */
+ assert(nsq > 0); /* must be the start square at least! */
+ if (ctx->dist[ctx->list[nsq-1]] < 3)
+ continue;
+
+ /*
+ * Find the first viable endpoint in ctx->list (i.e. the
+ * first point with distance at least three). I could
+ * binary-search for this, but that would be O(log N)
+ * whereas in fact I can get a constant time bound by just
+ * searching up from the start - after all, there can be at
+ * most 13 points at _less_ than distance 3 from the
+ * starting one!
+ */
+ for (j = 0; j < nsq; j++)
+ if (ctx->dist[ctx->list[j]] >= 3)
+ break;
+ assert(j < nsq); /* we tested above that there was one */
+
+ /*
+ * Now we know that any element of `list' between j and nsq
+ * would be valid in principle. However, we want a few long
+ * paths rather than many small ones, so select only those
+ * elements which are either the maximum length or one
+ * below it.
+ */
+ while (ctx->dist[ctx->list[j]] + 1 < ctx->dist[ctx->list[nsq-1]])
+ j++;
+ r = j + random_upto(rs, nsq - j);
+ j = ctx->list[r];
+
+ /*
+ * And that's our endpoint. Mark the new path on the grid.
+ */
+ c = newpath(ctx);
+ ctx->pathends[c*2] = i;
+ ctx->pathends[c*2+1] = j;
+ ctx->grid[j] = c;
+ while (j != i) {
+ int d, np, index, pts[4];
+ np = 0;
+ for (d = 0; d < 4; d++) {
+ int xn = (j % w) + DX(d), yn = (j / w) + DY(d);
+ if (xn >= 0 && xn < w && yn >= 0 && yn < w &&
+ ctx->dist[yn*w+xn] == ctx->dist[j] - 1)
+ pts[np++] = yn*w+xn;
+ }
+ if (np > 1)
+ index = random_upto(rs, np);
+ else
+ index = 0;
+ j = pts[index];
+ ctx->grid[j] = c;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * The main grid generation loop.
+ */
+static void gridgen_mainloop(struct genctx *ctx, random_state *rs)
+{
+ int w = ctx->w, h = ctx->h;
+ int i, n;
+
+ /*
+ * The generation algorithm doesn't always converge. Loop round
+ * until it does.
+ */
+ while (1) {
+ for (i = 0; i < w*h; i++)
+ ctx->grid[i] = -1;
+ ctx->npaths = 0;
+
+ while (1) {
+ /*
+ * See if the grid is full.
+ */
+ for (i = 0; i < w*h; i++)
+ if (ctx->grid[i] < 0)
+ break;
+ if (i == w*h)
+ return;
+
+#ifdef GENERATION_DIAGNOSTICS
+ {
+ int x, y;
+ for (y = 0; y < h; y++) {
+ printf("|");
+ for (x = 0; x < w; x++) {
+ if (ctx->grid[y*w+x] >= 0)
+ printf("%2d", ctx->grid[y*w+x]);
+ else
+ printf(" .");
+ }
+ printf(" |\n");
+ }
+ }
+#endif
+ /*
+ * Try adding a path.
+ */
+ if (add_path(ctx, rs)) {
+#ifdef GENERATION_DIAGNOSTICS
+ printf("added path\n");
+#endif
+ continue;
+ }
+
+ /*
+ * Try extending a path. First list all the possible
+ * extensions.
+ */
+ for (i = 0; i < ctx->npaths * 8; i++)
+ ctx->extends[i] = i;
+ n = i;
+
+ /*
+ * Then shuffle the list.
+ */
+ for (i = n; i-- > 1 ;) {
+ int k = random_upto(rs, i+1);
+ if (k != i) {
+ int t = ctx->extends[i];
+ ctx->extends[i] = ctx->extends[k];
+ ctx->extends[k] = t;
+ }
+ }
+
+ /*
+ * Now try each one in turn until one works.
+ */
+ for (i = 0; i < n; i++) {
+ int p, d, e;
+ p = ctx->extends[i];
+ d = p % 4;
+ p /= 4;
+ e = p % 2;
+ p /= 2;
+
+#ifdef GENERATION_DIAGNOSTICS
+ printf("trying to extend path %d end %d (%d,%d) in dir %d\n", p, e,
+ ctx->pathends[p*2+e] % w,
+ ctx->pathends[p*2+e] / w, d);
+#endif
+ if (extend_path(ctx, p, e, d)) {
+#ifdef GENERATION_DIAGNOSTICS
+ printf("extended path %d end %d (%d,%d) in dir %d\n", p, e,
+ ctx->pathends[p*2+e] % w,
+ ctx->pathends[p*2+e] / w, d);
+#endif
+ break;
+ }
+ }
+
+ if (i < n)
+ continue;
+
+ break;
+ }
+ }
+}
+
+/*
+ * Wrapper function which deals with the boring bits such as
+ * removing the solution from the generated grid, shuffling the
+ * numeric labels and creating/disposing of the context structure.
+ */
+static int *gridgen(int w, int h, random_state *rs)
+{
+ struct genctx *ctx;
+ int *ret;
+ int i;
+
+ ctx = new_genctx(w, h);
+
+ gridgen_mainloop(ctx, rs);
+
+ /*
+ * There is likely to be an ordering bias in the numbers
+ * (longer paths on lower numbers due to there having been more
+ * grid space when laying them down). So we must shuffle the
+ * numbers. We use ctx->pathspare for this.
+ *
+ * This is also as good a time as any to shift to numbering
+ * from 1, for display to the user.
+ */
+ for (i = 0; i < ctx->npaths; i++)
+ ctx->pathspare[i] = i+1;
+ for (i = ctx->npaths; i-- > 1 ;) {
+ int k = random_upto(rs, i+1);
+ if (k != i) {
+ int t = ctx->pathspare[i];
+ ctx->pathspare[i] = ctx->pathspare[k];
+ ctx->pathspare[k] = t;
+ }
+ }
+
+ /* FIXME: remove this at some point! */
+ {
+ int y, x;
+ for (y = 0; y < h; y++) {
+ printf("|");
+ for (x = 0; x < w; x++) {
+ assert(ctx->grid[y*w+x] >= 0);
+ printf("%2d", ctx->pathspare[ctx->grid[y*w+x]]);
+ }
+ printf(" |\n");
+ }
+ printf("\n");
+ }
+
+ /*
+ * Clear the grid, and write in just the endpoints.
+ */
+ for (i = 0; i < w*h; i++)
+ ctx->grid[i] = 0;
+ for (i = 0; i < ctx->npaths; i++) {
+ ctx->grid[ctx->pathends[i*2]] =
+ ctx->grid[ctx->pathends[i*2+1]] = ctx->pathspare[i];
+ }
+
+ ret = ctx->grid;
+ ctx->grid = NULL;
+
+ free_genctx(ctx);
+
+ return ret;
+}
+
+#ifdef TEST_GEN
+
+#define TEST_GENERAL
+
+int main(void)
+{
+ int w = 10, h = 8;
+ random_state *rs = random_init("12345", 5);
+ int x, y, i, *grid;
+
+ for (i = 0; i < 10; i++) {
+ grid = gridgen(w, h, rs);
+
+ for (y = 0; y < h; y++) {
+ printf("|");
+ for (x = 0; x < w; x++) {
+ if (grid[y*w+x] > 0)
+ printf("%2d", grid[y*w+x]);
+ else
+ printf(" .");
+ }
+ printf(" |\n");
+ }
+ printf("\n");
+
+ sfree(grid);
+ }
+
+ return 0;
+}
+#endif
+
+#ifdef TEST_GENERAL
+#include <stdarg.h>
+
+void fatal(char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "fatal error: ");
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+ exit(1);
+}
+#endif
--- /dev/null
+++ b/unfinished/pearl.R
@@ -1,0 +1,17 @@
+# -*- makefile -*-
+
+PEARL = pearl dsf
+
+pearl : [X] GTK COMMON PEARL
+
+pearl : [G] WINDOWS COMMON PEARL
+
+ALL += PEARL
+
+!begin gtk
+GAMES += pearl
+!end
+
+!begin >list.c
+ A(pearl) \
+!end
--- /dev/null
+++ b/unfinished/pearl.c
@@ -1,0 +1,1401 @@
+/*
+ * pearl.c: Nikoli's `Masyu' puzzle. Currently this is a blank
+ * puzzle file with nothing but a test solver-generator.
+ */
+
+/*
+ * TODO:
+ *
+ * - The generation method appears to be fundamentally flawed. I
+ * think generating a random loop and then choosing a clue set
+ * is simply not a viable approach, because on a test run of
+ * 10,000 attempts, it generated _six_ viable puzzles. All the
+ * rest of the randomly generated loops failed to be soluble
+ * even given a maximal clue set. Also, the vast majority of the
+ * clues were white circles (straight clues); black circles
+ * (corners) seem very uncommon.
+ * + So what can we do? One possible approach would be to
+ * adjust the random loop generation so that it created loops
+ * which were in some heuristic sense more likely to be
+ * viable Masyu puzzles. Certainly a good start on that would
+ * be to arrange that black clues actually _came up_ slightly
+ * more often, but I have no idea whether that would be
+ * sufficient.
+ * + A second option would be to throw the entire mechanism out
+ * and instead write a different generator from scratch which
+ * evolves the solution along with the puzzle: place a few
+ * clues, nail down a bit of the loop, place another clue,
+ * nail down some more, etc. It's unclear whether this can
+ * sensibly be done, though.
+ *
+ * - Puzzle playing UI and everything else apart from the
+ * generator...
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define NOCLUE 0
+#define CORNER 1
+#define STRAIGHT 2
+
+#define R 1
+#define U 2
+#define L 4
+#define D 8
+
+#define DX(d) ( ((d)==R) - ((d)==L) )
+#define DY(d) ( ((d)==D) - ((d)==U) )
+
+#define F(d) (((d << 2) | (d >> 2)) & 0xF)
+#define C(d) (((d << 3) | (d >> 1)) & 0xF)
+#define A(d) (((d << 1) | (d >> 3)) & 0xF)
+
+#define LR (L | R)
+#define RL (R | L)
+#define UD (U | D)
+#define DU (D | U)
+#define LU (L | U)
+#define UL (U | L)
+#define LD (L | D)
+#define DL (D | L)
+#define RU (R | U)
+#define UR (U | R)
+#define RD (R | D)
+#define DR (D | R)
+#define BLANK 0
+#define UNKNOWN 15
+
+#define bLR (1 << LR)
+#define bRL (1 << RL)
+#define bUD (1 << UD)
+#define bDU (1 << DU)
+#define bLU (1 << LU)
+#define bUL (1 << UL)
+#define bLD (1 << LD)
+#define bDL (1 << DL)
+#define bRU (1 << RU)
+#define bUR (1 << UR)
+#define bRD (1 << RD)
+#define bDR (1 << DR)
+#define bBLANK (1 << BLANK)
+
+enum {
+ COL_BACKGROUND,
+ NCOLOURS
+};
+
+struct game_params {
+ int FIXME;
+};
+
+struct game_state {
+ int FIXME;
+};
+
+static game_params *default_params(void)
+{
+ game_params *ret = snew(game_params);
+
+ ret->FIXME = 0;
+
+ return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+ return FALSE;
+}
+
+static void free_params(game_params *params)
+{
+ sfree(params);
+}
+
+static game_params *dup_params(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)
+{
+}
+
+static char *encode_params(game_params *params, int full)
+{
+ return dupstr("FIXME");
+}
+
+static config_item *game_configure(game_params *params)
+{
+ return NULL;
+}
+
+static game_params *custom_params(config_item *cfg)
+{
+ return NULL;
+}
+
+static char *validate_params(game_params *params, int full)
+{
+ return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ */
+
+int pearl_solve(int w, int h, char *clues, char *result)
+{
+ int W = 2*w+1, H = 2*h+1;
+ short *workspace;
+ int *dsf, *dsfsize;
+ int x, y, b, d;
+ int ret = -1;
+
+ /*
+ * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature
+ * of the square (x,y), as a logical OR of bitfields.
+ *
+ * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates
+ * whether the horizontal edge between (x,y) and (x+1,y) is
+ * connected (1), disconnected (2) or unknown (3).
+ *
+ * workspace[(2*y+1)*W+(2*x)], indicates the same about the
+ * vertical edge between (x,y) and (x,y+1).
+ *
+ * Initially, every square is considered capable of being in
+ * any of the seven possible states (two straights, four
+ * corners and empty), except those corresponding to clue
+ * squares which are more restricted.
+ *
+ * Initially, all edges are unknown, except the ones around the
+ * grid border which are known to be disconnected.
+ */
+ workspace = snewn(W*H, short);
+ for (x = 0; x < W*H; x++)
+ workspace[x] = 0;
+ /* Square states */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ switch (clues[y*w+x]) {
+ case CORNER:
+ workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD;
+ break;
+ case STRAIGHT:
+ workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD;
+ break;
+ default:
+ workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK;
+ break;
+ }
+ /* Horizontal edges */
+ for (y = 0; y <= h; y++)
+ for (x = 0; x < w; x++)
+ workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3);
+ /* Vertical edges */
+ for (y = 0; y < h; y++)
+ for (x = 0; x <= w; x++)
+ workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3);
+
+ /*
+ * We maintain a dsf of connected squares, together with a
+ * count of the size of each equivalence class.
+ */
+ dsf = snewn(w*h, int);
+ dsfsize = snewn(w*h, int);
+
+ /*
+ * Now repeatedly try to find something we can do.
+ */
+ while (1) {
+
+#ifdef SOLVER_DIAGNOSTICS
+ for (y = 0; y < H; y++) {
+ for (x = 0; x < W; x++)
+ printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]);
+ printf("\n");
+ }
+#endif
+
+ int done_something = FALSE;
+
+ /*
+ * Go through the square state words, and discard any
+ * square state which is inconsistent with known facts
+ * about the edges around the square.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ for (b = 0; b < 0xD; b++)
+ if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
+ /*
+ * If any edge of this square is known to
+ * be connected when state b would require
+ * it disconnected, or vice versa, discard
+ * the state.
+ */
+ for (d = 1; d <= 8; d += d) {
+ int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+ if (workspace[ey*W+ex] ==
+ ((b & d) ? 2 : 1)) {
+ workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<b);
+#ifdef SOLVER_DIAGNOSTICS
+ printf("edge (%d,%d)-(%d,%d) rules out state"
+ " %d for square (%d,%d)\n",
+ ex/2, ey/2, (ex+1)/2, (ey+1)/2,
+ b, x, y);
+#endif
+ done_something = TRUE;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Consistency check: each square must have at
+ * least one state left!
+ */
+ if (!workspace[(2*y+1)*W+(2*x+1)]) {
+#ifdef SOLVER_DIAGNOSTICS
+ printf("edge check at (%d,%d): inconsistency\n", x, y);
+ ret = 0;
+ goto cleanup;
+#endif
+ }
+ }
+
+ /*
+ * Now go through the states array again, and nail down any
+ * unknown edge if one of its neighbouring squares makes it
+ * known.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ int edgeor = 0, edgeand = 15;
+
+ for (b = 0; b < 0xD; b++)
+ if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
+ edgeor |= b;
+ edgeand &= b;
+ }
+
+ /*
+ * Now any bit clear in edgeor marks a disconnected
+ * edge, and any bit set in edgeand marks a
+ * connected edge.
+ */
+
+ /* First check consistency: neither bit is both! */
+ if (edgeand & ~edgeor) {
+#ifdef SOLVER_DIAGNOSTICS
+ printf("square check at (%d,%d): inconsistency\n", x, y);
+ ret = 0;
+ goto cleanup;
+#endif
+ }
+
+ for (d = 1; d <= 8; d += d) {
+ int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+
+ if (!(edgeor & d) && workspace[ey*W+ex] == 3) {
+ workspace[ey*W+ex] = 2;
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("possible states of square (%d,%d) force edge"
+ " (%d,%d)-(%d,%d) to be disconnected\n",
+ x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+ } else if ((edgeand & d) && workspace[ey*W+ex] == 3) {
+ workspace[ey*W+ex] = 1;
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("possible states of square (%d,%d) force edge"
+ " (%d,%d)-(%d,%d) to be connected\n",
+ x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+ }
+ }
+ }
+
+ if (done_something)
+ continue;
+
+ /*
+ * Now for longer-range clue-based deductions (using the
+ * rules that a corner clue must connect to two straight
+ * squares, and a straight clue must connect to at least
+ * one corner square).
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ switch (clues[y*w+x]) {
+ case CORNER:
+ for (d = 1; d <= 8; d += d) {
+ int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+ int fx = ex + DX(d), fy = ey + DY(d);
+ int type = d | F(d);
+
+ if (workspace[ey*W+ex] == 1) {
+ /*
+ * If a corner clue is connected on any
+ * edge, then we can immediately nail
+ * down the square beyond that edge as
+ * being a straight in the appropriate
+ * direction.
+ */
+ if (workspace[fy*W+fx] != (1<<type)) {
+ workspace[fy*W+fx] = (1<<type);
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("corner clue at (%d,%d) forces square "
+ "(%d,%d) into state %d\n", x, y,
+ fx/2, fy/2, type);
+#endif
+
+ }
+ } else if (workspace[ey*W+ex] == 3) {
+ /*
+ * Conversely, if a corner clue is
+ * separated by an unknown edge from a
+ * square which _cannot_ be a straight
+ * in the appropriate direction, we can
+ * mark that edge as disconnected.
+ */
+ if (!(workspace[fy*W+fx] & (1<<type))) {
+ workspace[ey*W+ex] = 2;
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("corner clue at (%d,%d), plus square "
+ "(%d,%d) not being state %d, "
+ "disconnects edge (%d,%d)-(%d,%d)\n",
+ x, y, fx/2, fy/2, type,
+ ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+
+ }
+ }
+ }
+
+
+ break;
+ case STRAIGHT:
+ /*
+ * If a straight clue is between two squares
+ * neither of which is capable of being a
+ * corner connected to it, then the straight
+ * clue cannot point in that direction.
+ */
+ for (d = 1; d <= 2; d += d) {
+ int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
+ int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
+ int type = d | F(d);
+
+ if (!(workspace[(2*y+1)*W+(2*x+1)] & (1<<type)))
+ continue;
+
+ if (!(workspace[fy*W+fx] & ((1<<(F(d)|A(d))) |
+ (1<<(F(d)|C(d))))) &&
+ !(workspace[gy*W+gx] & ((1<<( d |A(d))) |
+ (1<<( d |C(d)))))) {
+ workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<type);
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("straight clue at (%d,%d) cannot corner at "
+ "(%d,%d) or (%d,%d) so is not state %d\n",
+ x, y, fx/2, fy/2, gx/2, gy/2, type);
+#endif
+ }
+
+ }
+
+ /*
+ * If a straight clue with known direction is
+ * connected on one side to a known straight,
+ * then on the other side it must be a corner.
+ */
+ for (d = 1; d <= 8; d += d) {
+ int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
+ int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
+ int type = d | F(d);
+
+ if (workspace[(2*y+1)*W+(2*x+1)] != (1<<type))
+ continue;
+
+ if (!(workspace[fy*W+fx] &~ (bLR|bUD)) &&
+ (workspace[gy*W+gx] &~ (bLU|bLD|bRU|bRD))) {
+ workspace[gy*W+gx] &= (bLU|bLD|bRU|bRD);
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("straight clue at (%d,%d) connecting to "
+ "straight at (%d,%d) makes (%d,%d) a "
+ "corner\n", x, y, fx/2, fy/2, gx/2, gy/2);
+#endif
+ }
+
+ }
+ break;
+ }
+
+ if (done_something)
+ continue;
+
+ /*
+ * Now detect shortcut loops.
+ */
+
+ {
+ int nonblanks, loopclass;
+
+ dsf_init(dsf, w*h);
+ for (x = 0; x < w*h; x++)
+ dsfsize[x] = 1;
+
+ /*
+ * First go through the edge entries and update the dsf
+ * of which squares are connected to which others. We
+ * also track the number of squares in each equivalence
+ * class, and count the overall number of
+ * known-non-blank squares.
+ *
+ * In the process of doing this, we must notice if a
+ * loop has already been formed. If it has, we blank
+ * out any square which isn't part of that loop
+ * (failing a consistency check if any such square does
+ * not have BLANK as one of its remaining options) and
+ * exit the deduction loop with success.
+ */
+ nonblanks = 0;
+ loopclass = -1;
+ for (y = 1; y < H-1; y++)
+ for (x = 1; x < W-1; x++)
+ if ((y ^ x) & 1) {
+ /*
+ * (x,y) are the workspace coordinates of
+ * an edge field. Compute the normal-space
+ * coordinates of the squares it connects.
+ */
+ int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
+ int bx = x/2, by = y/2, bc = by*w+bx;
+
+ /*
+ * If the edge is connected, do the dsf
+ * thing.
+ */
+ if (workspace[y*W+x] == 1) {
+ int ae, be;
+
+ ae = dsf_canonify(dsf, ac);
+ be = dsf_canonify(dsf, bc);
+
+ if (ae == be) {
+ /*
+ * We have a loop!
+ */
+ if (loopclass != -1) {
+ /*
+ * In fact, we have two
+ * separate loops, which is
+ * doom.
+ */
+#ifdef SOLVER_DIAGNOSTICS
+ printf("two loops found in grid!\n");
+#endif
+ ret = 0;
+ goto cleanup;
+ }
+ loopclass = ae;
+ } else {
+ /*
+ * Merge the two equivalence
+ * classes.
+ */
+ int size = dsfsize[ae] + dsfsize[be];
+ dsf_merge(dsf, ac, bc);
+ ae = dsf_canonify(dsf, ac);
+ dsfsize[ae] = size;
+ }
+ }
+ } else if ((y & x) & 1) {
+ /*
+ * (x,y) are the workspace coordinates of a
+ * square field. If the square is
+ * definitely not blank, count it.
+ */
+ if (!(workspace[y*W+x] & bBLANK))
+ nonblanks++;
+ }
+
+ /*
+ * If we discovered an existing loop above, we must now
+ * blank every square not part of it, and exit the main
+ * deduction loop.
+ */
+ if (loopclass != -1) {
+#ifdef SOLVER_DIAGNOSTICS
+ printf("loop found in grid!\n");
+#endif
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ if (dsf_canonify(dsf, y*w+x) != loopclass) {
+ if (workspace[(y*2+1)*W+(x*2+1)] & bBLANK) {
+ workspace[(y*2+1)*W+(x*2+1)] = bBLANK;
+ } else {
+ /*
+ * This square is not part of the
+ * loop, but is known non-blank. We
+ * have goofed.
+ */
+#ifdef SOLVER_DIAGNOSTICS
+ printf("non-blank square (%d,%d) found outside"
+ " loop!\n", x, y);
+#endif
+ ret = 0;
+ goto cleanup;
+ }
+ }
+ /*
+ * And we're done.
+ */
+ ret = 1;
+ break;
+ }
+
+ /*
+ * Now go through the workspace again and mark any edge
+ * which would cause a shortcut loop (i.e. would
+ * connect together two squares in the same equivalence
+ * class, and that equivalence class does not contain
+ * _all_ the known-non-blank squares currently in the
+ * grid) as disconnected. Also, mark any _square state_
+ * which would cause a shortcut loop as disconnected.
+ */
+ for (y = 1; y < H-1; y++)
+ for (x = 1; x < W-1; x++)
+ if ((y ^ x) & 1) {
+ /*
+ * (x,y) are the workspace coordinates of
+ * an edge field. Compute the normal-space
+ * coordinates of the squares it connects.
+ */
+ int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
+ int bx = x/2, by = y/2, bc = by*w+bx;
+
+ /*
+ * If the edge is currently unknown, and
+ * sits between two squares in the same
+ * equivalence class, and the size of that
+ * class is less than nonblanks, then
+ * connecting this edge would be a shortcut
+ * loop and so we must not do so.
+ */
+ if (workspace[y*W+x] == 3) {
+ int ae, be;
+
+ ae = dsf_canonify(dsf, ac);
+ be = dsf_canonify(dsf, bc);
+
+ if (ae == be) {
+ /*
+ * We have a loop. Is it a shortcut?
+ */
+ if (dsfsize[ae] < nonblanks) {
+ /*
+ * Yes! Mark this edge disconnected.
+ */
+ workspace[y*W+x] = 2;
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("edge (%d,%d)-(%d,%d) would create"
+ " a shortcut loop, hence must be"
+ " disconnected\n", x/2, y/2,
+ (x+1)/2, (y+1)/2);
+#endif
+ }
+ }
+ }
+ } else if ((y & x) & 1) {
+ /*
+ * (x,y) are the workspace coordinates of a
+ * square field. Go through its possible
+ * (non-blank) states and see if any gives
+ * rise to a shortcut loop.
+ *
+ * This is slightly fiddly, because we have
+ * to check whether this square is already
+ * part of the same equivalence class as
+ * the things it's joining.
+ */
+ int ae = dsf_canonify(dsf, (y/2)*w+(x/2));
+
+ for (b = 2; b < 0xD; b++)
+ if (workspace[y*W+x] & (1<<b)) {
+ /*
+ * Find the equivalence classes of
+ * the two squares this one would
+ * connect if it were in this
+ * state.
+ */
+ int e = -1;
+
+ for (d = 1; d <= 8; d += d) if (b & d) {
+ int xx = x/2 + DX(d), yy = y/2 + DY(d);
+ int ee = dsf_canonify(dsf, yy*w+xx);
+
+ if (e == -1)
+ ee = e;
+ else if (e != ee)
+ e = -2;
+ }
+
+ if (e >= 0) {
+ /*
+ * This square state would form
+ * a loop on equivalence class
+ * e. Measure the size of that
+ * loop, and see if it's a
+ * shortcut.
+ */
+ int loopsize = dsfsize[e];
+ if (e != ae)
+ loopsize++;/* add the square itself */
+ if (loopsize < nonblanks) {
+ /*
+ * It is! Mark this square
+ * state invalid.
+ */
+ workspace[y*W+x] &= ~(1<<b);
+ done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+ printf("square (%d,%d) would create a "
+ "shortcut loop in state %d, "
+ "hence cannot be\n",
+ x/2, y/2, b);
+#endif
+ }
+ }
+ }
+ }
+ }
+
+ if (done_something)
+ continue;
+
+ /*
+ * If we reach here, there is nothing left we can do.
+ * Return 2 for ambiguous puzzle.
+ */
+ ret = 2;
+ goto cleanup;
+ }
+
+ /*
+ * If we reach _here_, it's by `break' out of the main loop,
+ * which means we've successfully achieved a solution. This
+ * means that we expect every square to be nailed down to
+ * exactly one possibility. Transcribe those possibilities into
+ * the result array.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ for (b = 0; b < 0xD; b++)
+ if (workspace[(2*y+1)*W+(2*x+1)] == (1<<b)) {
+ result[y*w+x] = b;
+ break;
+ }
+ assert(b < 0xD); /* we should have had a break by now */
+ }
+
+ cleanup:
+ sfree(dsfsize);
+ sfree(dsf);
+ sfree(workspace);
+ assert(ret >= 0);
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Loop generator.
+ */
+
+void pearl_loopgen(int w, int h, char *grid, random_state *rs)
+{
+ int *options, *mindist, *maxdist, *list;
+ int x, y, d, total, n, area, limit;
+
+ /*
+ * We're eventually going to have to return a w-by-h array
+ * containing line segment data. However, it's more convenient
+ * while actually generating the loop to consider the problem
+ * as a (w-1) by (h-1) array in which some squares are `inside'
+ * and some `outside'.
+ *
+ * I'm going to use the top left corner of my return array in
+ * the latter manner until the end of the function.
+ */
+
+ /*
+ * To begin with, all squares are outside (0), except for one
+ * randomly selected one which is inside (1).
+ */
+ memset(grid, 0, w*h);
+ x = random_upto(rs, w-1);
+ y = random_upto(rs, h-1);
+ grid[y*w+x] = 1;
+
+ /*
+ * I'm also going to need an array to store the possible
+ * options for the next extension of the grid.
+ */
+ options = snewn(w*h, int);
+ for (x = 0; x < w*h; x++)
+ options[x] = 0;
+
+ /*
+ * And some arrays and a list for breadth-first searching.
+ */
+ mindist = snewn(w*h, int);
+ maxdist = snewn(w*h, int);
+ list = snewn(w*h, int);
+
+ /*
+ * Now we repeatedly scan the grid for feasible squares into
+ * which we can extend our loop, pick one, and do it.
+ */
+ area = 1;
+
+ while (1) {
+#ifdef LOOPGEN_DIAGNOSTICS
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++)
+ printf("%d", grid[y*w+x]);
+ printf("\n");
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Our primary aim in growing this loop is to make it
+ * reasonably _dense_ in the target rectangle. That is, we
+ * want the maximum over all squares of the minimum
+ * distance from that square to the loop to be small.
+ *
+ * Therefore, we start with a breadth-first search of the
+ * grid to find those minimum distances.
+ */
+ {
+ int head = 0, tail = 0;
+ int i;
+
+ for (i = 0; i < w*h; i++) {
+ mindist[i] = -1;
+ if (grid[i]) {
+ mindist[i] = 0;
+ list[tail++] = i;
+ }
+ }
+
+ while (head < tail) {
+ i = list[head++];
+ y = i / w;
+ x = i % w;
+ for (d = 1; d <= 8; d += d) {
+ int xx = x + DX(d), yy = y + DY(d);
+ if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
+ mindist[yy*w+xx] < 0) {
+ mindist[yy*w+xx] = mindist[i] + 1;
+ list[tail++] = yy*w+xx;
+ }
+ }
+ }
+
+ /*
+ * Having done the BFS, we now backtrack along its path
+ * to determine the most distant square that each
+ * square is on the shortest path to. This tells us
+ * which of the loop extension candidates (all of which
+ * are squares marked 1) is most desirable to extend
+ * into in terms of minimising the maximum distance
+ * from any empty square to the nearest loop square.
+ */
+ for (head = tail; head-- > 0 ;) {
+ int max;
+
+ i = list[head];
+ y = i / w;
+ x = i % w;
+
+ max = mindist[i];
+
+ for (d = 1; d <= 8; d += d) {
+ int xx = x + DX(d), yy = y + DY(d);
+ if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
+ mindist[yy*w+xx] > mindist[i] &&
+ maxdist[yy*w+xx] > max) {
+ max = maxdist[yy*w+xx];
+ }
+ }
+
+ maxdist[i] = max;
+ }
+ }
+
+ /*
+ * A square is a viable candidate for extension of our loop
+ * if and only if the following conditions are all met:
+ * - It is currently labelled 0.
+ * - At least one of its four orthogonal neighbours is
+ * labelled 1.
+ * - If you consider its eight orthogonal and diagonal
+ * neighbours to form a ring, that ring contains at most
+ * one contiguous run of 1s. (It must also contain at
+ * _least_ one, of course, but that's already guaranteed
+ * by the previous condition so there's no need to test
+ * it separately.)
+ */
+ total = 0;
+ for (y = 0; y < h-1; y++)
+ for (x = 0; x < w-1; x++) {
+ int ring[8];
+ int rx, neighbours, runs, dist;
+
+ dist = maxdist[y*w+x];
+ options[y*w+x] = 0;
+
+ if (grid[y*w+x])
+ continue; /* it isn't labelled 0 */
+
+ neighbours = 0;
+ for (rx = 0, d = 1; d <= 8; rx += 2, d += d) {
+ int x2 = x + DX(d), y2 = y + DY(d);
+ int x3 = x2 + DX(A(d)), y3 = y2 + DY(A(d));
+ int g2 = (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h ?
+ grid[y2*w+x2] : 0);
+ int g3 = (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h ?
+ grid[y3*w+x3] : 0);
+ ring[rx] = g2;
+ ring[rx+1] = g3;
+ if (g2)
+ neighbours++;
+ }
+
+ if (!neighbours)
+ continue; /* it doesn't have a 1 neighbour */
+
+ runs = 0;
+ for (rx = 0; rx < 8; rx++)
+ if (ring[rx] && !ring[(rx+1) & 7])
+ runs++;
+
+ if (runs > 1)
+ continue; /* too many runs of 1s */
+
+ /*
+ * Now we know this square is a viable extension
+ * candidate. Mark it.
+ *
+ * FIXME: probabilistic prioritisation based on
+ * perimeter perturbation? (Wow, must keep that
+ * phrase.)
+ */
+ options[y*w+x] = dist * (4-neighbours) * (4-neighbours);
+ total += options[y*w+x];
+ }
+
+ if (!total)
+ break; /* nowhere to go! */
+
+ /*
+ * Now pick a random one of the viable extension squares,
+ * and extend into it.
+ */
+ n = random_upto(rs, total);
+ for (y = 0; y < h-1; y++)
+ for (x = 0; x < w-1; x++) {
+ assert(n >= 0);
+ if (options[y*w+x] > n)
+ goto found; /* two-level break */
+ n -= options[y*w+x];
+ }
+ assert(!"We shouldn't ever get here");
+ found:
+ grid[y*w+x] = 1;
+ area++;
+
+ /*
+ * We terminate the loop when around 7/12 of the grid area
+ * is full, but we also require that the loop has reached
+ * all four edges.
+ */
+ limit = random_upto(rs, (w-1)*(h-1)) + 13*(w-1)*(h-1);
+ if (24 * area > limit) {
+ int l = FALSE, r = FALSE, u = FALSE, d = FALSE;
+ for (x = 0; x < w; x++) {
+ if (grid[0*w+x])
+ u = TRUE;
+ if (grid[(h-2)*w+x])
+ d = TRUE;
+ }
+ for (y = 0; y < h; y++) {
+ if (grid[y*w+0])
+ l = TRUE;
+ if (grid[y*w+(w-2)])
+ r = TRUE;
+ }
+ if (l && r && u && d)
+ break;
+ }
+ }
+
+ sfree(list);
+ sfree(maxdist);
+ sfree(mindist);
+ sfree(options);
+
+#ifdef LOOPGEN_DIAGNOSTICS
+ printf("final loop:\n");
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++)
+ printf("%d", grid[y*w+x]);
+ printf("\n");
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Now convert this array of 0s and 1s into an array of path
+ * components.
+ */
+ for (y = h; y-- > 0 ;) {
+ for (x = w; x-- > 0 ;) {
+ /*
+ * Examine the four grid squares of which (x,y) are in
+ * the bottom right, to determine the output for this
+ * square.
+ */
+ int ul = (x > 0 && y > 0 ? grid[(y-1)*w+(x-1)] : 0);
+ int ur = (y > 0 ? grid[(y-1)*w+x] : 0);
+ int dl = (x > 0 ? grid[y*w+(x-1)] : 0);
+ int dr = grid[y*w+x];
+ int type = 0;
+
+ if (ul != ur) type |= U;
+ if (dl != dr) type |= D;
+ if (ul != dl) type |= L;
+ if (ur != dr) type |= R;
+
+ assert((bLR|bUD|bLU|bLD|bRU|bRD|bBLANK) & (1 << type));
+
+ grid[y*w+x] = type;
+
+ }
+ }
+
+#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS
+ printf("as returned:\n");
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ int type = grid[y*w+x];
+ char s[5], *p = s;
+ if (type & L) *p++ = 'L';
+ if (type & R) *p++ = 'R';
+ if (type & U) *p++ = 'U';
+ if (type & D) *p++ = 'D';
+ *p = '\0';
+ printf("%3s", s);
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+}
+
+static char *new_game_desc(game_params *params, random_state *rs,
+ char **aux, int interactive)
+{
+ char *grid, *clues;
+ int *clueorder;
+ int w = 10, h = 10;
+ int x, y, d, ret, i;
+
+#if 0
+ clues = snewn(7*7, char);
+ memcpy(clues,
+ "\0\1\0\0\2\0\0"
+ "\0\0\0\2\0\0\0"
+ "\0\0\0\2\0\0\1"
+ "\2\0\0\2\0\0\0"
+ "\2\0\0\0\0\0\1"
+ "\0\0\1\0\0\2\0"
+ "\0\0\2\0\0\0\0", 7*7);
+ grid = snewn(7*7, char);
+ printf("%d\n", pearl_solve(7, 7, clues, grid));
+#elif 0
+ clues = snewn(10*10, char);
+ memcpy(clues,
+ "\0\0\2\0\2\0\0\0\0\0"
+ "\0\0\0\0\2\0\0\0\1\0"
+ "\0\0\1\0\1\0\2\0\0\0"
+ "\0\0\0\2\0\0\2\0\0\0"
+ "\1\0\0\0\0\2\0\0\0\2"
+ "\0\0\2\0\0\0\0\2\0\0"
+ "\0\0\1\0\0\0\2\0\0\0"
+ "\2\0\0\0\1\0\0\0\0\2"
+ "\0\0\0\0\0\0\2\2\0\0"
+ "\0\0\1\0\0\0\0\0\0\1", 10*10);
+ grid = snewn(10*10, char);
+ printf("%d\n", pearl_solve(10, 10, clues, grid));
+#elif 0
+ clues = snewn(10*10, char);
+ memcpy(clues,
+ "\0\0\0\0\0\0\1\0\0\0"
+ "\0\1\0\1\2\0\0\0\0\2"
+ "\0\0\0\0\0\0\0\0\0\1"
+ "\2\0\0\1\2\2\1\0\0\0"
+ "\1\0\0\0\0\0\0\1\0\0"
+ "\0\0\2\0\0\0\0\0\0\2"
+ "\0\0\0\2\1\2\1\0\0\2"
+ "\2\0\0\0\0\0\0\0\0\0"
+ "\2\0\0\0\0\1\1\0\2\0"
+ "\0\0\0\2\0\0\0\0\0\0", 10*10);
+ grid = snewn(10*10, char);
+ printf("%d\n", pearl_solve(10, 10, clues, grid));
+#endif
+
+ grid = snewn(w*h, char);
+ clues = snewn(w*h, char);
+ clueorder = snewn(w*h, int);
+
+ while (1) {
+ pearl_loopgen(w, h, grid, rs);
+
+#ifdef GENERATION_DIAGNOSTICS
+ printf("grid array:\n");
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ int type = grid[y*w+x];
+ char s[5], *p = s;
+ if (type & L) *p++ = 'L';
+ if (type & R) *p++ = 'R';
+ if (type & U) *p++ = 'U';
+ if (type & D) *p++ = 'D';
+ *p = '\0';
+ printf("%2s ", s);
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Set up the maximal clue array.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ int type = grid[y*w+x];
+
+ clues[y*w+x] = NOCLUE;
+
+ if ((bLR|bUD) & (1 << type)) {
+ /*
+ * This is a straight; see if it's a viable
+ * candidate for a straight clue. It qualifies if
+ * at least one of the squares it connects to is a
+ * corner.
+ */
+ for (d = 1; d <= 8; d += d) if (type & d) {
+ int xx = x + DX(d), yy = y + DY(d);
+ assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
+ if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx]))
+ break;
+ }
+ if (d <= 8) /* we found one */
+ clues[y*w+x] = STRAIGHT;
+ } else if ((bLU|bLD|bRU|bRD) & (1 << type)) {
+ /*
+ * This is a corner; see if it's a viable candidate
+ * for a corner clue. It qualifies if all the
+ * squares it connects to are straights.
+ */
+ for (d = 1; d <= 8; d += d) if (type & d) {
+ int xx = x + DX(d), yy = y + DY(d);
+ assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
+ if (!((bLR|bUD) & (1 << grid[yy*w+xx])))
+ break;
+ }
+ if (d > 8) /* we didn't find a counterexample */
+ clues[y*w+x] = CORNER;
+ }
+ }
+
+#ifdef GENERATION_DIAGNOSTICS
+ printf("clue array:\n");
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+
+ /*
+ * See if we can solve the puzzle just like this.
+ */
+ ret = pearl_solve(w, h, clues, grid);
+ assert(ret > 0); /* shouldn't be inconsistent! */
+ if (ret != 1)
+ continue; /* go round and try again */
+
+ /*
+ * Now shuffle the grid points and gradually remove the
+ * clues to find a minimal set which still leaves the
+ * puzzle soluble.
+ */
+ for (i = 0; i < w*h; i++)
+ clueorder[i] = i;
+ shuffle(clueorder, w*h, sizeof(*clueorder), rs);
+ for (i = 0; i < w*h; i++) {
+ int clue;
+
+ y = clueorder[i] / w;
+ x = clueorder[i] % w;
+
+ if (clues[y*w+x] == 0)
+ continue;
+
+ clue = clues[y*w+x];
+ clues[y*w+x] = 0; /* try removing this clue */
+
+ ret = pearl_solve(w, h, clues, grid);
+ assert(ret > 0);
+ if (ret != 1)
+ clues[y*w+x] = clue; /* oops, put it back again */
+ }
+
+#ifdef FINISHED_PUZZLE
+ printf("clue array:\n");
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+
+ break; /* got it */
+ }
+
+ sfree(grid);
+ sfree(clues);
+ sfree(clueorder);
+
+ return dupstr("FIXME");
+}
+
+static char *validate_desc(game_params *params, char *desc)
+{
+ return NULL;
+}
+
+static game_state *new_game(midend *me, game_params *params, char *desc)
+{
+ game_state *state = snew(game_state);
+
+ state->FIXME = 0;
+
+ return state;
+}
+
+static game_state *dup_game(game_state *state)
+{
+ game_state *ret = snew(game_state);
+
+ ret->FIXME = state->FIXME;
+
+ return ret;
+}
+
+static void free_game(game_state *state)
+{
+ sfree(state);
+}
+
+static char *solve_game(game_state *state, game_state *currstate,
+ char *aux, char **error)
+{
+ return NULL;
+}
+
+static char *game_text_format(game_state *state)
+{
+ return NULL;
+}
+
+static game_ui *new_ui(game_state *state)
+{
+ return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(game_ui *ui)
+{
+ return NULL;
+}
+
+static void decode_ui(game_ui *ui, char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+ game_state *newstate)
+{
+}
+
+struct game_drawstate {
+ int tilesize;
+ int FIXME;
+};
+
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+ int x, int y, int button)
+{
+ return NULL;
+}
+
+static game_state *execute_move(game_state *state, char *move)
+{
+ return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(game_params *params, int tilesize,
+ int *x, int *y)
+{
+ *x = *y = 10 * tilesize; /* FIXME */
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+ 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]);
+
+ *ncolours = NCOLOURS;
+ return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+{
+ struct game_drawstate *ds = snew(struct game_drawstate);
+
+ ds->tilesize = 0;
+ ds->FIXME = 0;
+
+ return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+ sfree(ds);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
+ game_state *state, int dir, game_ui *ui,
+ float animtime, float flashtime)
+{
+ /*
+ * The initial contents of the window are not guaranteed and
+ * can vary with front ends. To be on the safe side, all games
+ * should start by drawing a big background-colour rectangle
+ * covering the whole window.
+ */
+ draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
+}
+
+static float game_anim_length(game_state *oldstate, game_state *newstate,
+ int dir, game_ui *ui)
+{
+ return 0.0F;
+}
+
+static float game_flash_length(game_state *oldstate, game_state *newstate,
+ int dir, game_ui *ui)
+{
+ return 0.0F;
+}
+
+static int game_timing_state(game_state *state, game_ui *ui)
+{
+ return TRUE;
+}
+
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame pearl
+#endif
+
+const struct game thegame = {
+ "Pearl", NULL,
+ default_params,
+ game_fetch_preset,
+ decode_params,
+ encode_params,
+ free_params,
+ dup_params,
+ FALSE, game_configure, custom_params,
+ validate_params,
+ new_game_desc,
+ validate_desc,
+ new_game,
+ dup_game,
+ free_game,
+ FALSE, solve_game,
+ FALSE, game_text_format,
+ new_ui,
+ free_ui,
+ encode_ui,
+ decode_ui,
+ game_changed_state,
+ interpret_move,
+ execute_move,
+ 20 /* FIXME */, game_compute_size, game_set_size,
+ game_colours,
+ game_new_drawstate,
+ game_free_drawstate,
+ game_redraw,
+ game_anim_length,
+ game_flash_length,
+ FALSE, FALSE, game_print_size, game_print,
+ FALSE, /* wants_statusbar */
+ FALSE, game_timing_state,
+ 0, /* flags */
+};
--- /dev/null
+++ b/unfinished/sokoban.R
@@ -1,0 +1,15 @@
+# -*- makefile -*-
+
+sokoban : [X] GTK COMMON sokoban
+
+sokoban : [G] WINDOWS COMMON sokoban
+
+ALL += sokoban
+
+!begin gtk
+GAMES += sokoban
+!end
+
+!begin >list.c
+ A(sokoban) \
+!end
--- /dev/null
+++ b/unfinished/sokoban.c
@@ -1,0 +1,1451 @@
+/*
+ * sokoban.c: An implementation of the well-known Sokoban barrel-
+ * pushing game. Random generation is too simplistic to be
+ * credible, but the rest of the gameplay works well enough to use
+ * it with hand-written level descriptions.
+ */
+
+/*
+ * TODO:
+ *
+ * - I think it would be better to ditch the `prev' array, and
+ * instead make the `dist' array strictly monotonic (by having
+ * each distance be something like I*A+S, where A is the grid
+ * area, I the number of INITIAL squares trampled on, and S the
+ * number of harmless spaces moved through). This would permit
+ * the path-tracing when a pull is actually made to choose
+ * randomly from all the possible shortest routes, which would
+ * be superior in terms of eliminating directional bias.
+ * + So when tracing the path back to the current px,py, we
+ * look at all four adjacent squares, find the minimum
+ * distance, check that it's _strictly smaller_ than that of
+ * the current square, and restrict our choice to precisely
+ * those squares with that minimum distance.
+ * + The other place `prev' is currently used is in the check
+ * for consistency of a pull. We would have to replace the
+ * check for whether prev[ny*w+nx]==oy*w+ox with a check that
+ * made sure there was at least one adjacent square with a
+ * smaller distance which _wasn't_ oy*w+ox. Then when we did
+ * the path-tracing we'd also have to take this special case
+ * into account.
+ *
+ * - More discriminating choice of pull. (Snigger.)
+ * + favour putting targets in clumps
+ * + try to shoot for a reasonably consistent number of barrels
+ * (adjust willingness to generate a new barrel depending on
+ * how many are already present)
+ * + adjust willingness to break new ground depending on how
+ * much is already broken
+ *
+ * - generation time parameters:
+ * + enable NetHack mode (and find a better place for the hole)
+ * + decide how many of the remaining Is should be walls
+ *
+ * - at the end of generation, randomly position the starting
+ * player coordinates, probably by (somehow) reusing the same
+ * bfs currently inside the loop.
+ *
+ * - possible backtracking?
+ *
+ * - IWBNI we could spot completely unreachable bits of level at
+ * the outside, and not bother drawing grid lines for them. The
+ * NH levels currently look a bit weird with grid lines on the
+ * outside of the boundary.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/*
+ * Various subsets of these constants are used during game
+ * generation, game play, game IDs and the game_drawstate.
+ */
+#define INITIAL 'i' /* used only in game generation */
+#define SPACE 's'
+#define WALL 'w'
+#define PIT 'p'
+#define DEEP_PIT 'd'
+#define TARGET 't'
+#define BARREL 'b'
+#define BARRELTARGET 'f' /* target is 'f'illed */
+#define PLAYER 'u' /* yo'u'; used in game IDs */
+#define PLAYERTARGET 'v' /* bad letter: v is to u as t is to s */
+#define INVALID '!' /* used in drawstate to force redraw */
+/*
+ * We also support the use of any capital letter as a barrel, which
+ * will be displayed with that letter as a label. (This facilitates
+ * people distributing annotated game IDs for particular Sokoban
+ * levels, so they can accompany them with verbal instructions
+ * about pushing particular barrels in particular ways.) Therefore,
+ * to find out whether something is a barrel, we need a test
+ * function which does a bit more than just comparing to BARREL.
+ *
+ * When resting on target squares, capital-letter barrels are
+ * replaced with their control-character value (A -> ^A).
+ */
+#define IS_PLAYER(c) ( (c)==PLAYER || (c)==PLAYERTARGET )
+#define IS_BARREL(c) ( (c)==BARREL || (c)==BARRELTARGET || \
+ ((c)>='A' && (c)<='Z') || ((c)>=1 && (c)<=26) )
+#define IS_ON_TARGET(c) ( (c)==TARGET || (c)==BARRELTARGET || \
+ (c)==PLAYERTARGET || ((c)>=1 && (c)<=26) )
+#define TARGETISE(b) ( (b)==BARREL ? BARRELTARGET : (b)-('A'-1) )
+#define DETARGETISE(b) ( (b)==BARRELTARGET ? BARREL : (b)+('A'-1) )
+#define BARREL_LABEL(b) ( (b)>='A'&&(b)<='Z' ? (b) : \
+ (b)>=1 && (b)<=26 ? (b)+('A'-1) : 0 )
+
+#define DX(d) (d == 0 ? -1 : d == 2 ? +1 : 0);
+#define DY(d) (d == 1 ? -1 : d == 3 ? +1 : 0);
+
+#define FLASH_LENGTH 0.3F
+
+enum {
+ COL_BACKGROUND,
+ COL_TARGET,
+ COL_PIT,
+ COL_DEEP_PIT,
+ COL_BARREL,
+ COL_PLAYER,
+ COL_TEXT,
+ COL_GRID,
+ COL_OUTLINE,
+ COL_HIGHLIGHT,
+ COL_LOWLIGHT,
+ COL_WALL,
+ NCOLOURS
+};
+
+struct game_params {
+ int w, h;
+ /*
+ * FIXME: a parameter involving degree of filling in?
+ */
+};
+
+struct game_state {
+ game_params p;
+ unsigned char *grid;
+ int px, py;
+ int completed;
+};
+
+static game_params *default_params(void)
+{
+ game_params *ret = snew(game_params);
+
+ ret->w = 12;
+ ret->h = 10;
+
+ return ret;
+}
+
+static void free_params(game_params *params)
+{
+ sfree(params);
+}
+
+static game_params *dup_params(game_params *params)
+{
+ game_params *ret = snew(game_params);
+ *ret = *params; /* structure copy */
+ return ret;
+}
+
+static const struct game_params sokoban_presets[] = {
+ { 12, 10 },
+ { 16, 12 },
+ { 20, 16 },
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+ game_params p, *ret;
+ char *retname;
+ char namebuf[80];
+
+ if (i < 0 || i >= lenof(sokoban_presets))
+ return FALSE;
+
+ p = sokoban_presets[i];
+ ret = dup_params(&p);
+ sprintf(namebuf, "%dx%d", ret->w, ret->h);
+ retname = dupstr(namebuf);
+
+ *params = ret;
+ *name = retname;
+ return TRUE;
+}
+
+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);
+ }
+}
+
+static char *encode_params(game_params *params, int full)
+{
+ char data[256];
+
+ sprintf(data, "%dx%d", params->w, params->h);
+
+ return dupstr(data);
+}
+
+static config_item *game_configure(game_params *params)
+{
+ config_item *ret;
+ char buf[80];
+
+ ret = snewn(3, config_item);
+
+ ret[0].name = "Width";
+ ret[0].type = C_STRING;
+ sprintf(buf, "%d", params->w);
+ ret[0].sval = dupstr(buf);
+ ret[0].ival = 0;
+
+ ret[1].name = "Height";
+ ret[1].type = C_STRING;
+ sprintf(buf, "%d", params->h);
+ ret[1].sval = dupstr(buf);
+ ret[1].ival = 0;
+
+ ret[2].name = NULL;
+ ret[2].type = C_END;
+ ret[2].sval = NULL;
+ ret[2].ival = 0;
+
+ return ret;
+}
+
+static game_params *custom_params(config_item *cfg)
+{
+ game_params *ret = snew(game_params);
+
+ ret->w = atoi(cfg[0].sval);
+ ret->h = atoi(cfg[1].sval);
+
+ return ret;
+}
+
+static char *validate_params(game_params *params, int full)
+{
+ if (params->w < 4 || params->h < 4)
+ return "Width and height must both be at least 4";
+
+ return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Game generation mechanism.
+ *
+ * To generate a Sokoban level, we begin with a completely blank
+ * grid and make valid inverse moves. Grid squares can be in a
+ * number of states. The states are:
+ *
+ * - INITIAL: this square has not as yet been touched by any
+ * inverse move, which essentially means we haven't decided what
+ * it is yet.
+ *
+ * - SPACE: this square is a space.
+ *
+ * - TARGET: this square is a space which is also the target for a
+ * barrel.
+ *
+ * - BARREL: this square contains a barrel.
+ *
+ * - BARRELTARGET: this square contains a barrel _on_ a target.
+ *
+ * - WALL: this square is a wall.
+ *
+ * - PLAYER: this square contains the player.
+ *
+ * - PLAYERTARGET: this square contains the player on a target.
+ *
+ * We begin with every square of the in state INITIAL, apart from a
+ * solid ring of WALLs around the edge. We randomly position the
+ * PLAYER somewhere. Thereafter our valid moves are:
+ *
+ * - to move the PLAYER in one direction _pulling_ a barrel after
+ * us. For this to work, we must have SPACE or INITIAL in the
+ * direction we're moving, and BARREL or BARRELTARGET in the
+ * direction we're moving away from. We leave SPACE or TARGET
+ * respectively in the vacated square.
+ *
+ * - to create a new barrel by transforming an INITIAL square into
+ * BARRELTARGET.
+ *
+ * - to move the PLAYER freely through SPACE and TARGET squares,
+ * leaving SPACE or TARGET where it started.
+ *
+ * - to move the player through INITIAL squares, carving a tunnel
+ * of SPACEs as it goes.
+ *
+ * We try to avoid destroying INITIAL squares wherever possible (if
+ * there's a path to where we want to be using only SPACE, then we
+ * should always use that). At the end of generation, every square
+ * still in state INITIAL is one which was not required at any
+ * point during generation, which means we can randomly choose
+ * whether to make it SPACE or WALL.
+ *
+ * It's unclear as yet what the right strategy for wall placement
+ * should be. Too few WALLs will yield many alternative solutions
+ * to the puzzle, whereas too many might rule out so many
+ * possibilities that the intended solution becomes obvious.
+ */
+
+static void sokoban_generate(int w, int h, unsigned char *grid, int moves,
+ int nethack, random_state *rs)
+{
+ struct pull {
+ int ox, oy, nx, ny, score;
+ };
+
+ struct pull *pulls;
+ int *dist, *prev, *heap;
+ int x, y, px, py, i, j, d, heapsize, npulls;
+
+ pulls = snewn(w * h * 4, struct pull);
+ dist = snewn(w * h, int);
+ prev = snewn(w * h, int);
+ heap = snewn(w * h, int);
+
+ /*
+ * Configure the initial grid.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ grid[y*w+x] = (x == 0 || y == 0 || x == w-1 || y == h-1 ?
+ WALL : INITIAL);
+ if (nethack)
+ grid[1] = DEEP_PIT;
+
+ /*
+ * Place the player.
+ */
+ i = random_upto(rs, (w-2) * (h-2));
+ x = 1 + i % (w-2);
+ y = 1 + i / (w-2);
+ grid[y*w+x] = SPACE;
+ px = x;
+ py = y;
+
+ /*
+ * Now loop around making random inverse Sokoban moves. In this
+ * loop we aim to make one actual barrel-pull per iteration,
+ * plus as many free moves as are necessary to get into
+ * position for that pull.
+ */
+ while (moves-- >= 0) {
+ /*
+ * First enumerate all the viable barrel-pulls we can
+ * possibly make, counting two pulls of the same barrel in
+ * different directions as different. We also include pulls
+ * we can perform by creating a new barrel. Each pull is
+ * marked with the amount of violence it would have to do
+ * to the grid.
+ */
+ npulls = 0;
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ for (d = 0; d < 4; d++) {
+ int dx = DX(d);
+ int dy = DY(d);
+ int nx = x + dx, ny = y + dy;
+ int npx = nx + dx, npy = ny + dy;
+ int score = 0;
+
+ /*
+ * The candidate move is to put the player at
+ * (nx,ny), and move him to (npx,npy), pulling
+ * a barrel at (x,y) to (nx,ny). So first we
+ * must check that all those squares are within
+ * the boundaries of the grid. For this it is
+ * sufficient to check npx,npy.
+ */
+ if (npx < 0 || npx >= w || npy < 0 || npy >= h)
+ continue;
+
+ /*
+ * (x,y) must either be a barrel, or a square
+ * which we can convert into a barrel.
+ */
+ switch (grid[y*w+x]) {
+ case BARREL: case BARRELTARGET:
+ break;
+ case INITIAL:
+ if (nethack)
+ continue;
+ score += 10 /* new_barrel_score */;
+ break;
+ case DEEP_PIT:
+ if (!nethack)
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ /*
+ * (nx,ny) must either be a space, or a square
+ * which we can convert into a space.
+ */
+ switch (grid[ny*w+nx]) {
+ case SPACE: case TARGET:
+ break;
+ case INITIAL:
+ score += 3 /* new_space_score */;
+ break;
+ default:
+ continue;
+ }
+
+ /*
+ * (npx,npy) must also either be a space, or a
+ * square which we can convert into a space.
+ */
+ switch (grid[npy*w+npx]) {
+ case SPACE: case TARGET:
+ break;
+ case INITIAL:
+ score += 3 /* new_space_score */;
+ break;
+ default:
+ continue;
+ }
+
+ /*
+ * That's sufficient to tag this as a possible
+ * pull right now. We still don't know if we
+ * can reach the required player position, but
+ * that's a job for the subsequent BFS phase to
+ * tell us.
+ */
+ pulls[npulls].ox = x;
+ pulls[npulls].oy = y;
+ pulls[npulls].nx = nx;
+ pulls[npulls].ny = ny;
+ pulls[npulls].score = score;
+#ifdef GENERATION_DIAGNOSTICS
+ printf("found potential pull: (%d,%d)-(%d,%d) cost %d\n",
+ pulls[npulls].ox, pulls[npulls].oy,
+ pulls[npulls].nx, pulls[npulls].ny,
+ pulls[npulls].score);
+#endif
+ npulls++;
+ }
+#ifdef GENERATION_DIAGNOSTICS
+ printf("found %d potential pulls\n", npulls);
+#endif
+
+ /*
+ * If there are no pulls available at all, we give up.
+ *
+ * (FIXME: or perhaps backtrack?)
+ */
+ if (npulls == 0)
+ break;
+
+ /*
+ * Now we do a BFS from our current position, to find all
+ * the squares we can get the player into.
+ *
+ * This BFS is unusually tricky. We want to give a positive
+ * distance only to squares which we have to carve through
+ * INITIALs to get to, which means we can't just stick
+ * every square we reach on the end of our to-do list.
+ * Instead, we must maintain our list as a proper priority
+ * queue.
+ */
+ for (i = 0; i < w*h; i++)
+ dist[i] = prev[i] = -1;
+
+ heap[0] = py*w+px;
+ heapsize = 1;
+ dist[py*w+px] = 0;
+
+#define PARENT(n) ( ((n)-1)/2 )
+#define LCHILD(n) ( 2*(n)+1 )
+#define RCHILD(n) ( 2*(n)+2 )
+#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0)
+
+ while (heapsize > 0) {
+ /*
+ * Pull the smallest element off the heap: it's at
+ * position 0. Move the arbitrary element from the very
+ * end of the heap into position 0.
+ */
+ y = heap[0] / w;
+ x = heap[0] % w;
+
+ heapsize--;
+ heap[0] = heap[heapsize];
+
+ /*
+ * Now repeatedly move that arbitrary element down the
+ * heap by swapping it with the more suitable of its
+ * children.
+ */
+ i = 0;
+ while (1) {
+ int lc, rc;
+
+ lc = LCHILD(i);
+ rc = RCHILD(i);
+
+ if (lc >= heapsize)
+ break; /* we've hit bottom */
+
+ if (rc >= heapsize) {
+ /*
+ * Special case: there is only one child to
+ * check.
+ */
+ if (dist[heap[i]] > dist[heap[lc]])
+ SWAP(heap[i], heap[lc]);
+
+ /* _Now_ we've hit bottom. */
+ break;
+ } else {
+ /*
+ * The common case: there are two children and
+ * we must check them both.
+ */
+ if (dist[heap[i]] > dist[heap[lc]] ||
+ dist[heap[i]] > dist[heap[rc]]) {
+ /*
+ * Pick the more appropriate child to swap with
+ * (i.e. the one which would want to be the
+ * parent if one were above the other - as one
+ * is about to be).
+ */
+ if (dist[heap[lc]] > dist[heap[rc]]) {
+ SWAP(heap[i], heap[rc]);
+ i = rc;
+ } else {
+ SWAP(heap[i], heap[lc]);
+ i = lc;
+ }
+ } else {
+ /* This element is in the right place; we're done. */
+ break;
+ }
+ }
+ }
+
+ /*
+ * OK, that's given us (x,y) for this phase of the
+ * search. Now try all directions from here.
+ */
+
+ for (d = 0; d < 4; d++) {
+ int dx = DX(d);
+ int dy = DY(d);
+ int nx = x + dx, ny = y + dy;
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h)
+ continue;
+ if (grid[ny*w+nx] != SPACE && grid[ny*w+nx] != TARGET &&
+ grid[ny*w+nx] != INITIAL)
+ continue;
+ if (dist[ny*w+nx] == -1) {
+ dist[ny*w+nx] = dist[y*w+x] + (grid[ny*w+nx] == INITIAL);
+ prev[ny*w+nx] = y*w+x;
+
+ /*
+ * Now insert ny*w+nx at the end of the heap,
+ * and move it down to its appropriate resting
+ * place.
+ */
+ i = heapsize;
+ heap[heapsize++] = ny*w+nx;
+
+ /*
+ * Swap element n with its parent repeatedly to
+ * preserve the heap property.
+ */
+
+ while (i > 0) {
+ int p = PARENT(i);
+
+ if (dist[heap[p]] > dist[heap[i]]) {
+ SWAP(heap[p], heap[i]);
+ i = p;
+ } else
+ break;
+ }
+ }
+ }
+ }
+
+#undef PARENT
+#undef LCHILD
+#undef RCHILD
+#undef SWAP
+
+#ifdef GENERATION_DIAGNOSTICS
+ printf("distance map:\n");
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ int d = dist[i*w+j];
+ int c;
+ if (d < 0)
+ c = '#';
+ else if (d >= 36)
+ c = '!';
+ else if (d >= 10)
+ c = 'A' - 10 + d;
+ else
+ c = '0' + d;
+ putchar(c);
+ }
+ putchar('\n');
+ }
+#endif
+
+ /*
+ * Now we can go back through the `pulls' array, adjusting
+ * the score for each pull depending on how hard it is to
+ * reach its starting point, and also throwing out any
+ * whose starting points are genuinely unreachable even
+ * with the possibility of carving through INITIAL squares.
+ */
+ for (i = j = 0; i < npulls; i++) {
+#ifdef GENERATION_DIAGNOSTICS
+ printf("potential pull (%d,%d)-(%d,%d)",
+ pulls[i].ox, pulls[i].oy,
+ pulls[i].nx, pulls[i].ny);
+#endif
+ x = pulls[i].nx;
+ y = pulls[i].ny;
+ if (dist[y*w+x] < 0) {
+#ifdef GENERATION_DIAGNOSTICS
+ printf(" unreachable\n");
+#endif
+ continue; /* this pull isn't feasible at all */
+ } else {
+ /*
+ * Another nasty special case we have to check is
+ * whether the initial barrel location (ox,oy) is
+ * on the path used to reach the square. This can
+ * occur if that square is in state INITIAL: the
+ * pull is initially considered valid on the basis
+ * that the INITIAL can become BARRELTARGET, and
+ * it's also considered reachable on the basis that
+ * INITIAL can be turned into SPACE, but it can't
+ * be both at once.
+ *
+ * Fortunately, if (ox,oy) is on the path at all,
+ * it must be only one space from the end, so this
+ * is easy to spot and rule out.
+ */
+ if (prev[y*w+x] == pulls[i].oy*w+pulls[i].ox) {
+#ifdef GENERATION_DIAGNOSTICS
+ printf(" goes through itself\n");
+#endif
+ continue; /* this pull isn't feasible at all */
+ }
+ pulls[j] = pulls[i]; /* structure copy */
+ pulls[j].score += dist[y*w+x] * 3 /* new_space_score */;
+#ifdef GENERATION_DIAGNOSTICS
+ printf(" reachable at distance %d (cost now %d)\n",
+ dist[y*w+x], pulls[j].score);
+#endif
+ j++;
+ }
+ }
+ npulls = j;
+
+ /*
+ * Again, if there are no pulls available at all, we give
+ * up.
+ *
+ * (FIXME: or perhaps backtrack?)
+ */
+ if (npulls == 0)
+ break;
+
+ /*
+ * Now choose which pull to make. On the one hand we should
+ * prefer pulls which do less damage to the INITIAL squares
+ * (thus, ones for which we can already get into position
+ * via existing SPACEs, and for which the barrel already
+ * exists and doesn't have to be invented); on the other,
+ * we want to avoid _always_ preferring such pulls, on the
+ * grounds that that will lead to levels without very much
+ * stuff in.
+ *
+ * When creating new barrels, we prefer creations which are
+ * next to existing TARGET squares.
+ *
+ * FIXME: for the moment I'll make this very simple indeed.
+ */
+ i = random_upto(rs, npulls);
+
+ /*
+ * Actually make the pull, including carving a path to get
+ * to the site if necessary.
+ */
+ x = pulls[i].nx;
+ y = pulls[i].ny;
+ while (prev[y*w+x] >= 0) {
+ int p;
+
+ if (grid[y*w+x] == INITIAL)
+ grid[y*w+x] = SPACE;
+
+ p = prev[y*w+x];
+ y = p / w;
+ x = p % w;
+ }
+ px = 2*pulls[i].nx - pulls[i].ox;
+ py = 2*pulls[i].ny - pulls[i].oy;
+ if (grid[py*w+px] == INITIAL)
+ grid[py*w+px] = SPACE;
+ if (grid[pulls[i].ny*w+pulls[i].nx] == TARGET)
+ grid[pulls[i].ny*w+pulls[i].nx] = BARRELTARGET;
+ else
+ grid[pulls[i].ny*w+pulls[i].nx] = BARREL;
+ if (grid[pulls[i].oy*w+pulls[i].ox] == BARREL)
+ grid[pulls[i].oy*w+pulls[i].ox] = SPACE;
+ else if (grid[pulls[i].oy*w+pulls[i].ox] != DEEP_PIT)
+ grid[pulls[i].oy*w+pulls[i].ox] = TARGET;
+ }
+
+ sfree(heap);
+ sfree(prev);
+ sfree(dist);
+ sfree(pulls);
+
+ if (grid[py*w+px] == TARGET)
+ grid[py*w+px] = PLAYERTARGET;
+ else
+ grid[py*w+px] = PLAYER;
+}
+
+static char *new_game_desc(game_params *params, random_state *rs,
+ char **aux, int interactive)
+{
+ int w = params->w, h = params->h;
+ char *desc;
+ int desclen, descpos, descsize, prev, count;
+ unsigned char *grid;
+ int i, j;
+
+ /*
+ * FIXME: perhaps some more interesting means of choosing how
+ * many moves to try?
+ */
+ grid = snewn(w*h, unsigned char);
+ sokoban_generate(w, h, grid, w*h, FALSE, rs);
+
+ desclen = descpos = descsize = 0;
+ desc = NULL;
+ prev = -1;
+ count = 0;
+ for (i = 0; i < w*h; i++) {
+ if (descsize < desclen + 40) {
+ descsize = desclen + 100;
+ desc = sresize(desc, descsize, char);
+ desc[desclen] = '\0';
+ }
+ switch (grid[i]) {
+ case INITIAL:
+ j = 'w'; /* FIXME: make some of these 's'? */
+ break;
+ case SPACE:
+ j = 's';
+ break;
+ case WALL:
+ j = 'w';
+ break;
+ case TARGET:
+ j = 't';
+ break;
+ case BARREL:
+ j = 'b';
+ break;
+ case BARRELTARGET:
+ j = 'f';
+ break;
+ case DEEP_PIT:
+ j = 'd';
+ break;
+ case PLAYER:
+ j = 'u';
+ break;
+ case PLAYERTARGET:
+ j = 'v';
+ break;
+ default:
+ j = '?';
+ break;
+ }
+ assert(j != '?');
+ if (j != prev) {
+ desc[desclen++] = j;
+ descpos = desclen;
+ prev = j;
+ count = 1;
+ } else {
+ count++;
+ desclen = descpos + sprintf(desc+descpos, "%d", count);
+ }
+ }
+
+ sfree(grid);
+
+ return desc;
+}
+
+static char *validate_desc(game_params *params, char *desc)
+{
+ int w = params->w, h = params->h;
+ int area = 0;
+ int nplayers = 0;
+
+ while (*desc) {
+ int c = *desc++;
+ int n = 1;
+ if (*desc && isdigit((unsigned char)*desc)) {
+ n = atoi(desc);
+ while (*desc && isdigit((unsigned char)*desc)) desc++;
+ }
+
+ area += n;
+
+ if (c == PLAYER || c == PLAYERTARGET)
+ nplayers += n;
+ else if (c == INITIAL || c == SPACE || c == WALL || c == TARGET ||
+ c == PIT || c == DEEP_PIT || IS_BARREL(c))
+ /* ok */;
+ else
+ return "Invalid character in game description";
+ }
+
+ if (area > w*h)
+ return "Too much data in game description";
+ if (area < w*h)
+ return "Too little data in game description";
+ if (nplayers < 1)
+ return "No starting player position specified";
+ if (nplayers > 1)
+ return "More than one starting player position specified";
+
+ return NULL;
+}
+
+static game_state *new_game(midend *me, game_params *params, 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, unsigned char);
+ state->px = state->py = -1;
+ state->completed = FALSE;
+
+ i = 0;
+
+ while (*desc) {
+ int c = *desc++;
+ int n = 1;
+ if (*desc && isdigit((unsigned char)*desc)) {
+ n = atoi(desc);
+ while (*desc && isdigit((unsigned char)*desc)) desc++;
+ }
+
+ if (c == PLAYER || c == PLAYERTARGET) {
+ state->py = i / w;
+ state->px = i % w;
+ c = IS_ON_TARGET(c) ? TARGET : SPACE;
+ }
+
+ while (n-- > 0)
+ state->grid[i++] = c;
+ }
+
+ assert(i == w*h);
+ assert(state->px != -1 && state->py != -1);
+
+ return state;
+}
+
+static game_state *dup_game(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, unsigned char);
+ memcpy(ret->grid, state->grid, w*h);
+ ret->px = state->px;
+ ret->py = state->py;
+ ret->completed = state->completed;
+
+ return ret;
+}
+
+static void free_game(game_state *state)
+{
+ sfree(state->grid);
+ sfree(state);
+}
+
+static char *solve_game(game_state *state, game_state *currstate,
+ char *aux, char **error)
+{
+ return NULL;
+}
+
+static char *game_text_format(game_state *state)
+{
+ return NULL;
+}
+
+static game_ui *new_ui(game_state *state)
+{
+ return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(game_ui *ui)
+{
+ return NULL;
+}
+
+static void decode_ui(game_ui *ui, char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+ game_state *newstate)
+{
+}
+
+struct game_drawstate {
+ game_params p;
+ int tilesize;
+ int started;
+ unsigned short *grid;
+};
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE)
+#define HIGHLIGHT_WIDTH (TILESIZE / 10)
+#define COORD(x) ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+/*
+ * I'm going to need to do most of the move-type analysis in both
+ * interpret_move and execute_move, so I'll abstract it out into a
+ * subfunction. move_type() returns -1 for an illegal move, 0 for a
+ * movement, and 1 for a push.
+ */
+int move_type(game_state *state, int dx, int dy)
+{
+ int w = state->p.w, h = state->p.h;
+ int px = state->px, py = state->py;
+ int nx, ny, nbx, nby;
+
+ assert(dx >= -1 && dx <= +1);
+ assert(dy >= -1 && dy <= +1);
+ assert(dx || dy);
+
+ nx = px + dx;
+ ny = py + dy;
+
+ /*
+ * Disallow any move that goes off the grid.
+ */
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h)
+ return -1;
+
+ /*
+ * Examine the target square of the move to see whether it's a
+ * space, a barrel, or a wall.
+ */
+
+ if (state->grid[ny*w+nx] == WALL ||
+ state->grid[ny*w+nx] == PIT ||
+ state->grid[ny*w+nx] == DEEP_PIT)
+ return -1; /* this one's easy; just disallow it */
+
+ if (IS_BARREL(state->grid[ny*w+nx])) {
+ /*
+ * This is a push move. For a start, that means it must not
+ * be diagonal.
+ */
+ if (dy && dx)
+ return -1;
+
+ /*
+ * Now find the location of the third square involved in
+ * the push, and stop if it's off the edge.
+ */
+ nbx = nx + dx;
+ nby = ny + dy;
+ if (nbx < 0 || nbx >= w || nby < 0 || nby >= h)
+ return -1;
+
+ /*
+ * That third square must be able to accept a barrel.
+ */
+ if (state->grid[nby*w+nbx] == SPACE ||
+ state->grid[nby*w+nbx] == TARGET ||
+ state->grid[nby*w+nbx] == PIT ||
+ state->grid[nby*w+nbx] == DEEP_PIT) {
+ /*
+ * The push is valid.
+ */
+ return 1;
+ } else {
+ return -1;
+ }
+ } else {
+ /*
+ * This is just an ordinary move. We've already checked the
+ * target square, so the only thing left to check is that a
+ * diagonal move has a space on one side to have notionally
+ * gone through.
+ */
+ if (dx && dy &&
+ state->grid[(py+dy)*w+px] != SPACE &&
+ state->grid[(py+dy)*w+px] != TARGET &&
+ state->grid[py*w+(px+dx)] != SPACE &&
+ state->grid[py*w+(px+dx)] != TARGET)
+ return -1;
+
+ /*
+ * Otherwise, the move is valid.
+ */
+ return 0;
+ }
+}
+
+static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+ int x, int y, int button)
+{
+ int dx, dy;
+ char *move;
+
+ /*
+ * Diagonal movement is supported as it is in NetHack: it's
+ * for movement only (never pushing), and one of the two
+ * squares adjacent to both the source and destination
+ * squares must be free to move through. In other words, it
+ * is only a shorthand for two orthogonal moves and cannot
+ * change the nature of the actual puzzle game.
+ */
+ if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
+ dx = 0, dy = -1;
+ else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
+ dx = 0, dy = +1;
+ else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
+ dx = -1, dy = 0;
+ else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
+ dx = +1, dy = 0;
+ else if (button == (MOD_NUM_KEYPAD | '7'))
+ dx = -1, dy = -1;
+ else if (button == (MOD_NUM_KEYPAD | '9'))
+ dx = +1, dy = -1;
+ else if (button == (MOD_NUM_KEYPAD | '1'))
+ dx = -1, dy = +1;
+ else if (button == (MOD_NUM_KEYPAD | '3'))
+ dx = +1, dy = +1;
+ else
+ return NULL;
+
+ if (move_type(state, dx, dy) < 0)
+ return NULL;
+
+ move = snewn(2, char);
+ move[1] = '\0';
+ move[0] = '5' - 3*dy + dx;
+ return move;
+}
+
+static game_state *execute_move(game_state *state, char *move)
+{
+ int w = state->p.w, h = state->p.h;
+ int px = state->px, py = state->py;
+ int dx, dy, nx, ny, nbx, nby, type, m, i, freebarrels, freetargets;
+ game_state *ret;
+
+ if (*move < '1' || *move == '5' || *move > '9' || move[1])
+ return NULL; /* invalid move string */
+
+ m = *move - '0';
+ dx = (m + 2) % 3 - 1;
+ dy = 2 - (m + 2) / 3;
+ type = move_type(state, dx, dy);
+ if (type < 0)
+ return NULL;
+
+ ret = dup_game(state);
+
+ nx = px + dx;
+ ny = py + dy;
+ nbx = nx + dx;
+ nby = ny + dy;
+
+ if (type) {
+ int b;
+
+ /*
+ * Push.
+ */
+ b = ret->grid[ny*w+nx];
+ if (IS_ON_TARGET(b)) {
+ ret->grid[ny*w+nx] = TARGET;
+ b = DETARGETISE(b);
+ } else
+ ret->grid[ny*w+nx] = SPACE;
+
+ if (ret->grid[nby*w+nbx] == PIT)
+ ret->grid[nby*w+nbx] = SPACE;
+ else if (ret->grid[nby*w+nbx] == DEEP_PIT)
+ /* do nothing - the pit eats the barrel and remains there */;
+ else if (ret->grid[nby*w+nbx] == TARGET)
+ ret->grid[nby*w+nbx] = TARGETISE(b);
+ else
+ ret->grid[nby*w+nbx] = b;
+ }
+
+ ret->px = nx;
+ ret->py = ny;
+
+ /*
+ * Check for completion. This is surprisingly complicated,
+ * given the presence of pits and deep pits, and also the fact
+ * that some Sokoban levels with pits have fewer pits than
+ * barrels (due to providing spares, e.g. NetHack's). I think
+ * the completion condition in fact must be that the game
+ * cannot become any _more_ complete. That is, _either_ there
+ * are no remaining barrels not on targets, _or_ there is a
+ * good reason why any such barrels cannot be placed. The only
+ * available good reason is that there are no remaining pits,
+ * no free target squares, and no deep pits at all.
+ */
+ if (!ret->completed) {
+ freebarrels = FALSE;
+ freetargets = FALSE;
+ for (i = 0; i < w*h; i++) {
+ int v = ret->grid[i];
+
+ if (IS_BARREL(v) && !IS_ON_TARGET(v))
+ freebarrels = TRUE;
+ if (v == DEEP_PIT || v == PIT ||
+ (!IS_BARREL(v) && IS_ON_TARGET(v)))
+ freetargets = TRUE;
+ }
+
+ if (!freebarrels || !freetargets)
+ ret->completed = TRUE;
+ }
+
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(game_params *params, int tilesize,
+ int *x, int *y)
+{
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ struct { int tilesize; } ads, *ds = &ads;
+ ads.tilesize = tilesize;
+
+ *x = 2 * BORDER + 1 + params->w * TILESIZE;
+ *y = 2 * BORDER + 1 + params->h * TILESIZE;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+ game_params *params, int tilesize)
+{
+ ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+ float *ret = snewn(3 * NCOLOURS, float);
+ int i;
+
+ game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+ ret[COL_OUTLINE * 3 + 0] = 0.0F;
+ ret[COL_OUTLINE * 3 + 1] = 0.0F;
+ ret[COL_OUTLINE * 3 + 2] = 0.0F;
+
+ ret[COL_PLAYER * 3 + 0] = 0.0F;
+ ret[COL_PLAYER * 3 + 1] = 1.0F;
+ ret[COL_PLAYER * 3 + 2] = 0.0F;
+
+ ret[COL_BARREL * 3 + 0] = 0.6F;
+ ret[COL_BARREL * 3 + 1] = 0.3F;
+ ret[COL_BARREL * 3 + 2] = 0.0F;
+
+ ret[COL_TARGET * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0];
+ ret[COL_TARGET * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1];
+ ret[COL_TARGET * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2];
+
+ ret[COL_PIT * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0] / 2;
+ ret[COL_PIT * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1] / 2;
+ ret[COL_PIT * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2] / 2;
+
+ ret[COL_DEEP_PIT * 3 + 0] = 0.0F;
+ ret[COL_DEEP_PIT * 3 + 1] = 0.0F;
+ ret[COL_DEEP_PIT * 3 + 2] = 0.0F;
+
+ ret[COL_TEXT * 3 + 0] = 1.0F;
+ ret[COL_TEXT * 3 + 1] = 1.0F;
+ ret[COL_TEXT * 3 + 2] = 1.0F;
+
+ ret[COL_GRID * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0];
+ ret[COL_GRID * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1];
+ ret[COL_GRID * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2];
+
+ ret[COL_OUTLINE * 3 + 0] = 0.0F;
+ ret[COL_OUTLINE * 3 + 1] = 0.0F;
+ ret[COL_OUTLINE * 3 + 2] = 0.0F;
+
+ for (i = 0; i < 3; i++) {
+ ret[COL_WALL * 3 + i] = (3 * ret[COL_BACKGROUND * 3 + i] +
+ 1 * ret[COL_HIGHLIGHT * 3 + i]) / 4;
+ }
+
+ *ncolours = NCOLOURS;
+ return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, 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->p = state->p; /* structure copy */
+ ds->grid = snewn(w*h, unsigned short);
+ for (i = 0; i < w*h; i++)
+ ds->grid[i] = INVALID;
+ ds->started = FALSE;
+
+ return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+ sfree(ds->grid);
+ sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v)
+{
+ int tx = COORD(x), ty = COORD(y);
+ int bg = (v & 0x100 ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+ v &= 0xFF;
+
+ clip(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1);
+ draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, bg);
+
+ if (v == WALL) {
+ int coords[6];
+
+ coords[0] = tx + TILESIZE;
+ coords[1] = ty + TILESIZE;
+ coords[2] = tx + TILESIZE;
+ coords[3] = ty + 1;
+ coords[4] = tx + 1;
+ coords[5] = ty + TILESIZE;
+ draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+
+ coords[0] = tx + 1;
+ coords[1] = ty + 1;
+ draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+ draw_rect(dr, tx + 1 + HIGHLIGHT_WIDTH, ty + 1 + HIGHLIGHT_WIDTH,
+ TILESIZE - 2*HIGHLIGHT_WIDTH,
+ TILESIZE - 2*HIGHLIGHT_WIDTH, COL_WALL);
+ } else if (v == PIT) {
+ draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ TILESIZE*3/7, COL_PIT, COL_OUTLINE);
+ } else if (v == DEEP_PIT) {
+ draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ TILESIZE*3/7, COL_DEEP_PIT, COL_OUTLINE);
+ } else {
+ if (IS_ON_TARGET(v)) {
+ draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ TILESIZE*3/7, COL_TARGET, COL_OUTLINE);
+ }
+ if (IS_PLAYER(v)) {
+ draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ TILESIZE/3, COL_PLAYER, COL_OUTLINE);
+ } else if (IS_BARREL(v)) {
+ char str[2];
+
+ draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ TILESIZE/3, COL_BARREL, COL_OUTLINE);
+ str[1] = '\0';
+ str[0] = BARREL_LABEL(v);
+ if (str[0]) {
+ draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+ FONT_VARIABLE, TILESIZE/2,
+ ALIGN_VCENTRE | ALIGN_HCENTRE, COL_TEXT, str);
+ }
+ }
+ }
+
+ unclip(dr);
+ draw_update(dr, tx, ty, TILESIZE, TILESIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
+ game_state *state, int dir, game_ui *ui,
+ float animtime, float flashtime)
+{
+ int w = state->p.w, h = state->p.h /*, wh = w*h */;
+ int x, y;
+ int flashtype;
+
+ if (flashtime &&
+ !((int)(flashtime * 3 / FLASH_LENGTH) % 2))
+ flashtype = 0x100;
+ else
+ flashtype = 0;
+
+ /*
+ * Initialise a fresh drawstate.
+ */
+ if (!ds->started) {
+ int wid, ht;
+
+ /*
+ * Blank out the window initially.
+ */
+ game_compute_size(&ds->p, TILESIZE, &wid, &ht);
+ draw_rect(dr, 0, 0, wid, ht, COL_BACKGROUND);
+ draw_update(dr, 0, 0, wid, ht);
+
+ /*
+ * Draw the grid lines.
+ */
+ for (y = 0; y <= h; y++)
+ draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y),
+ COL_LOWLIGHT);
+ for (x = 0; x <= w; x++)
+ draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h),
+ COL_LOWLIGHT);
+
+ ds->started = TRUE;
+ }
+
+ /*
+ * Draw the grid contents.
+ */
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ int v = state->grid[y*w+x];
+ if (y == state->py && x == state->px) {
+ if (v == TARGET)
+ v = PLAYERTARGET;
+ else {
+ assert(v == SPACE);
+ v = PLAYER;
+ }
+ }
+
+ v |= flashtype;
+
+ if (ds->grid[y*w+x] != v) {
+ draw_tile(dr, ds, x, y, v);
+ ds->grid[y*w+x] = v;
+ }
+ }
+
+}
+
+static float game_anim_length(game_state *oldstate, game_state *newstate,
+ int dir, game_ui *ui)
+{
+ return 0.0F;
+}
+
+static float game_flash_length(game_state *oldstate, game_state *newstate,
+ int dir, game_ui *ui)
+{
+ if (!oldstate->completed && newstate->completed)
+ return FLASH_LENGTH;
+ else
+ return 0.0F;
+}
+
+static int game_timing_state(game_state *state, game_ui *ui)
+{
+ return TRUE;
+}
+
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame sokoban
+#endif
+
+const struct game thegame = {
+ "Sokoban", NULL,
+ default_params,
+ game_fetch_preset,
+ 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,
+ FALSE, solve_game,
+ FALSE, game_text_format,
+ new_ui,
+ free_ui,
+ encode_ui,
+ decode_ui,
+ 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,
+ FALSE, FALSE, game_print_size, game_print,
+ FALSE, /* wants_statusbar */
+ FALSE, game_timing_state,
+ 0, /* flags */
+};