Beispiel #1
0
/*
 * Not-guaranteed-soluble grid generator; kept as a legacy, and in
 * case someone finds the slightly odd quality of the guaranteed-
 * soluble grids to be aesthetically displeasing or finds its CPU
 * utilisation to be excessive.
 */
static void gen_grid_random(int w, int h, int nc, int *grid, random_state *rs)
{
    int i, j, c;
    int n = w * h;

    for (i = 0; i < n; i++)
	grid[i] = 0;

    /*
     * Our sole concession to not gratuitously generating insoluble
     * grids is to ensure we have at least two of every colour.
     */
    for (c = 1; c <= nc; c++) {
	for (j = 0; j < 2; j++) {
	    do {
		i = (int)random_upto(rs, n);
	    } while (grid[i] != 0);
	    grid[i] = c;
	}
    }

    /*
     * Fill in the rest of the grid at random.
     */
    for (i = 0; i < n; i++) {
	if (grid[i] == 0)
	    grid[i] = (int)random_upto(rs, nc)+1;
    }
}
Beispiel #2
0
static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    int nballs = params->minballs, i;
    char *grid, *ret;
    unsigned char *bmp;

    if (params->maxballs > params->minballs)
        nballs += random_upto(rs, params->maxballs - params->minballs + 1);

    grid = snewn(params->w*params->h, char);
    memset(grid, 0, params->w * params->h * sizeof(char));

    bmp = snewn(nballs*2 + 2, unsigned char);
    memset(bmp, 0, (nballs*2 + 2) * sizeof(unsigned char));

    bmp[0] = params->w;
    bmp[1] = params->h;

    for (i = 0; i < nballs; i++) {
        int x, y;

        do {
            x = random_upto(rs, params->w);
            y = random_upto(rs, params->h);
        } while (grid[y*params->w + x]);

        grid[y*params->w + x] = 1;

        bmp[(i+1)*2 + 0] = x;
        bmp[(i+1)*2 + 1] = y;
#ifdef ANDROID
        if (android_cancelled()) {
            sfree(grid);
            sfree(bmp);
            return NULL;
        }
#endif
    }
    sfree(grid);

    obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, FALSE);
    ret = bin2hex(bmp, nballs*2 + 2);
    sfree(bmp);

    return ret;
}
Beispiel #3
0
static char *new_game_desc(const game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    struct grid_data data;
    int i, j, k, m, area, facesperclass;
    int *flags;
    char *desc, *p;

    /*
     * Enumerate the grid squares, dividing them into equivalence
     * classes as appropriate. (For the tetrahedron, there is one
     * equivalence class for each face; for the octahedron there
     * are two classes; for the other two solids there's only one.)
     */

    area = grid_area(params->d1, params->d2, solids[params->solid]->order);
    if (params->solid == TETRAHEDRON)
	data.nclasses = 4;
    else if (params->solid == OCTAHEDRON)
	data.nclasses = 2;
    else
	data.nclasses = 1;
    data.gridptrs[0] = snewn(data.nclasses * area, int);
    for (i = 0; i < data.nclasses; i++) {
	data.gridptrs[i] = data.gridptrs[0] + i * area;
	data.nsquares[i] = 0;
    }
    data.squareindex = 0;
    enum_grid_squares(params, classify_grid_square_callback, &data);

    facesperclass = solids[params->solid]->nfaces / data.nclasses;

    for (i = 0; i < data.nclasses; i++)
	assert(data.nsquares[i] >= facesperclass);
    assert(data.squareindex == area);

    /*
     * So now we know how many faces to allocate in each class. Get
     * on with it.
     */
    flags = snewn(area, int);
    for (i = 0; i < area; i++)
	flags[i] = FALSE;

    for (i = 0; i < data.nclasses; i++) {
	for (j = 0; j < facesperclass; j++) {
            int n = random_upto(rs, data.nsquares[i]);

	    assert(!flags[data.gridptrs[i][n]]);
	    flags[data.gridptrs[i][n]] = TRUE;

	    /*
	     * Move everything else up the array. I ought to use a
	     * better data structure for this, but for such small
	     * numbers it hardly seems worth the effort.
	     */
	    while (n < data.nsquares[i]-1) {
		data.gridptrs[i][n] = data.gridptrs[i][n+1];
		n++;
	    }
	    data.nsquares[i]--;
	}
    }

    /*
     * Now we know precisely which squares are blue. Encode this
     * information in hex. While we're looping over this, collect
     * the non-blue squares into a list in the now-unused gridptrs
     * array.
     */
    desc = snewn(area / 4 + 40, char);
    p = desc;
    j = 0;
    k = 8;
    m = 0;
    for (i = 0; i < area; i++) {
	if (flags[i]) {
	    j |= k;
	} else {
	    data.gridptrs[0][m++] = i;
	}
	k >>= 1;
	if (!k) {
	    *p++ = "0123456789ABCDEF"[j];
	    k = 8;
	    j = 0;
	}
    }
    if (k != 8)
	*p++ = "0123456789ABCDEF"[j];

    /*
     * Choose a non-blue square for the polyhedron.
     */
    sprintf(p, ",%d", data.gridptrs[0][random_upto(rs, m)]);

    sfree(data.gridptrs[0]);
    sfree(flags);

    return desc;
}
Beispiel #4
0
/*
 * Guaranteed-soluble grid generator.
 */
static void gen_grid(int w, int h, int nc, int *grid, random_state *rs)
{
    int wh = w*h, tc = nc+1;
    int i, j, k, c, x, y, pos, n;
    int *list, *grid2;
    int ok, failures = 0;

    /*
     * We'll use `list' to track the possible places to put our
     * next insertion. There are up to h places to insert in each
     * column: in a column of height n there are n+1 places because
     * we can insert at the very bottom or the very top, but a
     * column of height h can't have anything at all inserted in it
     * so we have up to h in each column. Likewise, with n columns
     * present there are n+1 places to fit a new one in between but
     * we can't insert a column if there are already w; so there
     * are a maximum of w new columns too. Total is wh + w.
     */
    list = snewn(wh + w, int);
    grid2 = snewn(wh, int);

    do {
        /*
         * Start with two or three squares - depending on parity of w*h
         * - of a random colour.
         */
        for (i = 0; i < wh; i++)
            grid[i] = 0;
        j = 2 + (wh % 2);
        c = 1 + random_upto(rs, nc);
	if (j <= w) {
	    for (i = 0; i < j; i++)
		grid[(h-1)*w+i] = c;
	} else {
	    assert(j <= h);
	    for (i = 0; i < j; i++)
		grid[(h-1-i)*w] = c;
	}

        /*
         * Now repeatedly insert a two-square blob in the grid, of
         * whatever colour will go at the position we chose.
         */
        while (1) {
            n = 0;

            /*
             * Build up a list of insertion points. Each point is
             * encoded as y*w+x; insertion points between columns are
             * encoded as h*w+x.
             */

            if (grid[wh - 1] == 0) {
                /*
                 * The final column is empty, so we can insert new
                 * columns.
                 */
                for (i = 0; i < w; i++) {
                    list[n++] = wh + i;
                    if (grid[(h-1)*w + i] == 0)
                        break;
                }
            }

            /*
             * Now look for places to insert within columns.
             */
            for (i = 0; i < w; i++) {
                if (grid[(h-1)*w+i] == 0)
                    break;		       /* no more columns */

                if (grid[i] != 0)
                    continue;	       /* this column is full */

                for (j = h; j-- > 0 ;) {
                    list[n++] = j*w+i;
                    if (grid[j*w+i] == 0)
                        break;	       /* this column is exhausted */
                }
            }

            if (n == 0)
                break;		       /* we're done */

#ifdef GENERATION_DIAGNOSTICS
            printf("initial grid:\n");
            {
                int x,y;
                for (y = 0; y < h; y++) {
                    for (x = 0; x < w; x++) {
                        if (grid[y*w+x] == 0)
                            printf("-");
                        else
                            printf("%d", grid[y*w+x]);
                    }
                    printf("\n");
                }
            }
#endif

            /*
             * Now go through the list one element at a time in
             * random order, and actually attempt to insert
             * something there.
             */
            while (n-- > 0) {
                int dirs[4], ndirs, dir;

                i = random_upto(rs, n+1);
                pos = list[i];
                list[i] = list[n];

                x = pos % w;
                y = pos / w;

                memcpy(grid2, grid, wh * sizeof(int));

                if (y == h) {
                    /*
                     * Insert a column at position x.
                     */
                    for (i = w-1; i > x; i--)
                        for (j = 0; j < h; j++)
                            grid2[j*w+i] = grid2[j*w+(i-1)];
                    /*
                     * Clear the new column.
                     */
                    for (j = 0; j < h; j++)
                        grid2[j*w+x] = 0;
                    /*
                     * Decrement y so that our first square is actually
                     * inserted _in_ the grid rather than just below it.
                     */
                    y--;
                }

                /*
                 * Insert a square within column x at position y.
                 */
                for (i = 0; i+1 <= y; i++)
                    grid2[i*w+x] = grid2[(i+1)*w+x];

#ifdef GENERATION_DIAGNOSTICS
                printf("trying at n=%d (%d,%d)\n", n, x, y);
                grid2[y*w+x] = tc;
                {
                    int x,y;
                    for (y = 0; y < h; y++) {
                        for (x = 0; x < w; x++) {
                            if (grid2[y*w+x] == 0)
                                printf("-");
                            else if (grid2[y*w+x] <= nc)
                                printf("%d", grid2[y*w+x]);
                            else
                                printf("*");
                        }
                        printf("\n");
                    }
                }
#endif

                /*
                 * Pick our square colour so that it doesn't match any
                 * of its neighbours.
                 */
                {
                    int wrongcol[4], nwrong = 0;

                    /*
                     * List the neighbouring colours.
                     */
                    if (x > 0)
                        wrongcol[nwrong++] = grid2[y*w+(x-1)];
                    if (x+1 < w)
                        wrongcol[nwrong++] = grid2[y*w+(x+1)];
                    if (y > 0)
                        wrongcol[nwrong++] = grid2[(y-1)*w+x];
                    if (y+1 < h)
                        wrongcol[nwrong++] = grid2[(y+1)*w+x];

                    /*
                     * Eliminate duplicates. We can afford a shoddy
                     * algorithm here because the problem size is
                     * bounded.
                     */
                    for (i = j = 0 ;; i++) {
                        int pos = -1, min = 0;
                        if (j > 0)
                            min = wrongcol[j-1];
                        for (k = i; k < nwrong; k++)
                            if (wrongcol[k] > min &&
                                (pos == -1 || wrongcol[k] < wrongcol[pos]))
                                pos = k;
                        if (pos >= 0) {
                            int v = wrongcol[pos];
                            wrongcol[pos] = wrongcol[j];
                            wrongcol[j++] = v;
                        } else
                            break;
                    }
                    nwrong = j;

                    /*
                     * If no colour will go here, stop trying.
                     */
                    if (nwrong == nc)
                        continue;

                    /*
                     * Otherwise, pick a colour from the remaining
                     * ones.
                     */
                    c = 1 + random_upto(rs, nc - nwrong);
                    for (i = 0; i < nwrong; i++) {
                        if (c >= wrongcol[i])
                            c++;
                        else
                            break;
                    }
                }

                /*
                 * Place the new square.
                 * 
                 * Although I've _chosen_ the new region's colour
                 * (so that we can check adjacency), I'm going to
                 * actually place it as an invalid colour (tc)
                 * until I'm sure it's viable. This is so that I
                 * can conveniently check that I really have made a
                 * _valid_ inverse move later on.
                 */
#ifdef GENERATION_DIAGNOSTICS
                printf("picked colour %d\n", c);
#endif
                grid2[y*w+x] = tc;

                /*
                 * Now attempt to extend it in one of three ways: left,
                 * right or up.
                 */
                ndirs = 0;
                if (x > 0 &&
                    grid2[y*w+(x-1)] != c &&
                    grid2[x-1] == 0 &&
                    (y+1 >= h || grid2[(y+1)*w+(x-1)] != c) &&
                    (y+1 >= h || grid2[(y+1)*w+(x-1)] != 0) &&
                    (x <= 1 || grid2[y*w+(x-2)] != c))
                    dirs[ndirs++] = -1;    /* left */
                if (x+1 < w &&
                    grid2[y*w+(x+1)] != c &&
                    grid2[x+1] == 0 &&
                    (y+1 >= h || grid2[(y+1)*w+(x+1)] != c) &&
                    (y+1 >= h || grid2[(y+1)*w+(x+1)] != 0) &&
                    (x+2 >= w || grid2[y*w+(x+2)] != c))
                    dirs[ndirs++] = +1;    /* right */
                if (y > 0 &&
                    grid2[x] == 0 &&
                    (x <= 0 || grid2[(y-1)*w+(x-1)] != c) &&
                    (x+1 >= w || grid2[(y-1)*w+(x+1)] != c)) {
                    /*
                     * We add this possibility _twice_, so that the
                     * probability of placing a vertical domino is
                     * about the same as that of a horizontal. This
                     * should yield less bias in the generated
                     * grids.
                     */
                    dirs[ndirs++] = 0;     /* up */
                    dirs[ndirs++] = 0;     /* up */
                }

                if (ndirs == 0)
                    continue;

                dir = dirs[random_upto(rs, ndirs)];

#ifdef GENERATION_DIAGNOSTICS
                printf("picked dir %d\n", dir);
#endif

                /*
                 * Insert a square within column (x+dir) at position y.
                 */
                for (i = 0; i+1 <= y; i++)
                    grid2[i*w+x+dir] = grid2[(i+1)*w+x+dir];
                grid2[y*w+x+dir] = tc;

                /*
                 * See if we've divided the remaining grid squares
                 * into sub-areas. If so, we need every sub-area to
                 * have an even area or we won't be able to
                 * complete generation.
                 * 
                 * If the height is odd and not all columns are
                 * present, we can increase the area of a subarea
                 * by adding a new column in it, so in that
                 * situation we don't mind having as many odd
                 * subareas as there are spare columns.
                 * 
                 * If the height is even, we can't fix it at all.
                 */
                {
                    int nerrs = 0, nfix = 0;
                    k = 0;             /* current subarea size */
                    for (i = 0; i < w; i++) {
                        if (grid2[(h-1)*w+i] == 0) {
                            if (h % 2)
                                nfix++;
                            continue;
                        }
                        for (j = 0; j < h && grid2[j*w+i] == 0; j++);
                        assert(j < h);
                        if (j == 0) {
                            /*
                             * End of previous subarea.
                             */
                            if (k % 2)
                                nerrs++;
                            k = 0;
                        } else {
                            k += j;
                        }
                    }
                    if (k % 2)
                        nerrs++;
                    if (nerrs > nfix)
                        continue;      /* try a different placement */
                }

                /*
                 * We've made a move. Verify that it is a valid
                 * move and that if made it would indeed yield the
                 * previous grid state. The criteria are:
                 * 
                 *  (a) removing all the squares of colour tc (and
                 *      shuffling the columns up etc) from grid2
                 *      would yield grid
                 *  (b) no square of colour tc is adjacent to one
                 *      of colour c
                 *  (c) all the squares of colour tc form a single
                 *      connected component
                 * 
                 * We verify the latter property at the same time
                 * as checking that removing all the tc squares
                 * would yield the previous grid. Then we colour
                 * the tc squares in colour c by breadth-first
                 * search, which conveniently permits us to test
                 * that they're all connected.
                 */
                {
                    int x1, x2, y1, y2;
                    int ok = TRUE;
                    int fillstart = -1, ntc = 0;

#ifdef GENERATION_DIAGNOSTICS
                    {
                        int x,y;
                        printf("testing move (new, old):\n");
                        for (y = 0; y < h; y++) {
                            for (x = 0; x < w; x++) {
                                if (grid2[y*w+x] == 0)
                                    printf("-");
                                else if (grid2[y*w+x] <= nc)
                                    printf("%d", grid2[y*w+x]);
                                else
                                    printf("*");
                            }
                            printf("   ");
                            for (x = 0; x < w; x++) {
                                if (grid[y*w+x] == 0)
                                    printf("-");
                                else
                                    printf("%d", grid[y*w+x]);
                            }
                            printf("\n");
                        }
                    }
#endif

                    for (x1 = x2 = 0; x2 < w; x2++) {
                        int usedcol = FALSE;

                        for (y1 = y2 = h-1; y2 >= 0; y2--) {
                            if (grid2[y2*w+x2] == tc) {
                                ntc++;
                                if (fillstart == -1)
                                    fillstart = y2*w+x2;
                                if ((y2+1 < h && grid2[(y2+1)*w+x2] == c) ||
                                    (y2-1 >= 0 && grid2[(y2-1)*w+x2] == c) ||
                                    (x2+1 < w && grid2[y2*w+x2+1] == c) ||
                                    (x2-1 >= 0 && grid2[y2*w+x2-1] == c)) {
#ifdef GENERATION_DIAGNOSTICS
                                    printf("adjacency failure at %d,%d\n",
                                           x2, y2);
#endif
                                    ok = FALSE;
                                }
                                continue;
                            }
                            if (grid2[y2*w+x2] == 0)
                                break;
                            usedcol = TRUE;
                            if (grid2[y2*w+x2] != grid[y1*w+x1]) {
#ifdef GENERATION_DIAGNOSTICS
                                printf("matching failure at %d,%d vs %d,%d\n",
                                       x2, y2, x1, y1);
#endif
                                ok = FALSE;
                            }
                            y1--;
                        }

                        /*
                         * If we've reached the top of the column
                         * in grid2, verify that we've also reached
                         * the top of the column in `grid'.
                         */
                        if (usedcol) {
                            while (y1 >= 0) {
                                if (grid[y1*w+x1] != 0) {
#ifdef GENERATION_DIAGNOSTICS
                                    printf("junk at column top (%d,%d)\n",
                                           x1, y1);
#endif
                                    ok = FALSE;
                                }
                                y1--;
                            }
                        }

                        if (!ok)
                            break;

                        if (usedcol)
                            x1++;
                    }

                    if (!ok) {
                        assert(!"This should never happen");

                        /*
                         * If this game is compiled NDEBUG so that
                         * the assertion doesn't bring it to a
                         * crashing halt, the only thing we can do
                         * is to give up, loop round again, and
                         * hope to randomly avoid making whatever
                         * type of move just caused this failure.
                         */
                        continue;
                    }

                    /*
                     * Now use bfs to fill in the tc section as
                     * colour c. We use `list' to store the set of
                     * squares we have to process.
                     */
                    i = j = 0;
                    assert(fillstart >= 0);
                    list[i++] = fillstart;
#ifdef OUTPUT_SOLUTION
                    printf("M");
#endif
                    while (j < i) {
                        k = list[j];
                        x = k % w;
                        y = k / w;
#ifdef OUTPUT_SOLUTION
                        printf("%s%d", j ? "," : "", k);
#endif
                        j++;

                        assert(grid2[k] == tc);
                        grid2[k] = c;

                        if (x > 0 && grid2[k-1] == tc)
                            list[i++] = k-1;
                        if (x+1 < w && grid2[k+1] == tc)
                            list[i++] = k+1;
                        if (y > 0 && grid2[k-w] == tc)
                            list[i++] = k-w;
                        if (y+1 < h && grid2[k+w] == tc)
                            list[i++] = k+w;
                    }
#ifdef OUTPUT_SOLUTION
                    printf("\n");
#endif

                    /*
                     * Check that we've filled the same number of
                     * tc squares as we originally found.
                     */
                    assert(j == ntc);
                }

                memcpy(grid, grid2, wh * sizeof(int));

                break;		       /* done it! */
            }

#ifdef GENERATION_DIAGNOSTICS
            {
                int x,y;
                printf("n=%d\n", n);
                for (y = 0; y < h; y++) {
                    for (x = 0; x < w; x++) {
                        if (grid[y*w+x] == 0)
                            printf("-");
                        else
                            printf("%d", grid[y*w+x]);
                    }
                    printf("\n");
                }
            }
#endif

            if (n < 0)
                break;
        }

        ok = TRUE;
        for (i = 0; i < wh; i++)
            if (grid[i] == 0) {
                ok = FALSE;
                failures++;
#if defined GENERATION_DIAGNOSTICS || defined SHOW_INCOMPLETE
                {
                    int x,y;
                    printf("incomplete grid:\n");
                    for (y = 0; y < h; y++) {
                        for (x = 0; x < w; x++) {
                            if (grid[y*w+x] == 0)
                                printf("-");
                            else
                                printf("%d", grid[y*w+x]);
                        }
                        printf("\n");
                    }
                }
#endif
                break;
            }

    } while (!ok);

#if defined GENERATION_DIAGNOSTICS || defined COUNT_FAILURES
    printf("%d failures\n", failures);
#endif
#ifdef GENERATION_DIAGNOSTICS
    {
        int x,y;
        printf("final grid:\n");
        for (y = 0; y < h; y++) {
            for (x = 0; x < w; x++) {
                printf("%d", grid[y*w+x]);
            }
            printf("\n");
        }
    }
#endif

    sfree(grid2);
    sfree(list);
}
Beispiel #5
0
static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    int stop, n, i, x;
    int x1, x2, p1, p2;
    int *tiles, *used;
    char *ret;
    int retlen;

    n = params->w * params->h;

    tiles = snewn(n, int);

    if (params->movetarget) {
	int prevoffset = -1;
        int max = (params->w > params->h ? params->w : params->h);
        int *prevmoves = snewn(max, int);

	/*
	 * Shuffle the old-fashioned way, by making a series of
	 * single moves on the grid.
	 */

	for (i = 0; i < n; i++)
	    tiles[i] = i;

	for (i = 0; i < params->movetarget; i++) {
	    int start, offset, len, direction, index;
	    int j, tmp;

	    /*
	     * Choose a move to make. We can choose from any row
	     * or any column.
	     */
	    while (1) {
		j = random_upto(rs, params->w + params->h);

		if (j < params->w) {
		    /* Column. */
                    index = j;
		    start = j;
		    offset = params->w;
		    len = params->h;
		} else {
		    /* Row. */
                    index = j - params->w;
		    start = index * params->w;
		    offset = 1;
		    len = params->w;
		}

		direction = -1 + 2 * random_upto(rs, 2);

		/*
		 * To at least _try_ to avoid boring cases, check
		 * that this move doesn't directly undo a previous
		 * one, or repeat it so many times as to turn it
		 * into fewer moves in the opposite direction. (For
		 * example, in a row of length 4, we're allowed to
		 * move it the same way twice, but not three
		 * times.)
                 * 
                 * We track this for each individual row/column,
                 * and clear all the counters as soon as a
                 * perpendicular move is made. This isn't perfect
                 * (it _can't_ guaranteeably be perfect - there
                 * will always come a move count beyond which a
                 * shorter solution will be possible than the one
                 * which constructed the position) but it should
                 * sort out all the obvious cases.
		 */
                if (offset == prevoffset) {
                    tmp = prevmoves[index] + direction;
                    if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index]))
                        continue;
                }

		/* If we didn't `continue', we've found an OK move to make. */
                if (offset != prevoffset) {
                    int i;
                    for (i = 0; i < max; i++)
                        prevmoves[i] = 0;
                    prevoffset = offset;
                }
                prevmoves[index] += direction;
		break;
	    }

	    /*
	     * Make the move.
	     */
	    if (direction < 0) {
		start += (len-1) * offset;
		offset = -offset;
	    }
	    tmp = tiles[start];
	    for (j = 0; j+1 < len; j++)
		tiles[start + j*offset] = tiles[start + (j+1)*offset];
	    tiles[start + (len-1) * offset] = tmp;
	}

        sfree(prevmoves);

    } else {
Beispiel #6
0
static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    int gap, n, i, x;
    int x1, x2, p1, p2, parity;
    int *tiles, *used;
    char *ret;
    int retlen;

    n = params->w * params->h;

    tiles = snewn(n, int);
    used = snewn(n, int);

    for (i = 0; i < n; i++) {
        tiles[i] = -1;
        used[i] = FALSE;
    }

    gap = random_upto(rs, n);
    tiles[gap] = 0;
    used[0] = TRUE;

    /*
     * Place everything else except the last two tiles.
     */
    for (x = 0, i = n-1; i > 2; i--) {
        int k = random_upto(rs, i);
        int j;

        for (j = 0; j < n; j++)
            if (!used[j] && (k-- == 0))
                break;

        assert(j < n && !used[j]);
        used[j] = TRUE;

        while (tiles[x] >= 0)
            x++;
        assert(x < n);
        tiles[x] = j;
    }

    /*
     * Find the last two locations, and the last two pieces.
     */
    while (tiles[x] >= 0)
        x++;
    assert(x < n);
    x1 = x;
    x++;
    while (tiles[x] >= 0)
        x++;
    assert(x < n);
    x2 = x;

    for (i = 0; i < n; i++)
        if (!used[i])
            break;
    p1 = i;
    for (i = p1+1; i < n; i++)
        if (!used[i])
            break;
    p2 = i;

    /*
     * Determine the required parity of the overall permutation.
     * This is the XOR of:
     * 
     * 	- The chessboard parity ((x^y)&1) of the gap square. The
     * 	  bottom right counts as even.
     * 
     *  - The parity of n. (The target permutation is 1,...,n-1,0
     *    rather than 0,...,n-1; this is a cyclic permutation of
     *    the starting point and hence is odd iff n is even.)
     */
    parity = ((X(params, gap) - (params->w-1)) ^
	      (Y(params, gap) - (params->h-1)) ^
	      (n+1)) & 1;

    /*
     * Try the last two tiles one way round. If that fails, swap
     * them.
     */
    tiles[x1] = p1;
    tiles[x2] = p2;
    if (perm_parity(tiles, n) != parity) {
        tiles[x1] = p2;
        tiles[x2] = p1;
        assert(perm_parity(tiles, n) == parity);
    }

    /*
     * Now construct the game description, by describing the tile
     * array as a simple sequence of comma-separated integers.
     */
    ret = NULL;
    retlen = 0;
    for (i = 0; i < n; i++) {
        char buf[80];
        int k;

        k = sprintf(buf, "%d,", tiles[i]);

        ret = sresize(ret, retlen + k + 1, char);
        strcpy(ret + retlen, buf);
        retlen += k;
    }
    ret[retlen-1] = '\0';              /* delete last comma */

    sfree(tiles);
    sfree(used);

    return ret;
}
Beispiel #7
0
static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    int *grid;
    int w = params->w, h = params->h, n = params->n, wh = w*h;
    int i;
    char *ret;
    int retlen;
    int total_moves;

    /*
     * Set up a solved grid.
     */
    grid = snewn(wh, int);
    for (i = 0; i < wh; i++)
	grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4;

    /*
     * Shuffle it. This game is complex enough that I don't feel up
     * to analysing its full symmetry properties (particularly at
     * n=4 and above!), so I'm going to do it the pedestrian way
     * and simply shuffle the grid by making a long sequence of
     * randomly chosen moves.
     */
    total_moves = params->movetarget;
    if (!total_moves)
        /* Add a random move to avoid parity issues. */
        total_moves = w*h*n*n*2 + random_upto(rs, 2);

    do {
        int *prevmoves;
        int rw, rh;                    /* w/h of rotation centre space */

        rw = w - n + 1;
        rh = h - n + 1;
        prevmoves = snewn(rw * rh, int);
        for (i = 0; i < rw * rh; i++)
            prevmoves[i] = 0;

        for (i = 0; i < total_moves; i++) {
            int x, y, r, oldtotal, newtotal, dx, dy;

            do {
                x = random_upto(rs, w - n + 1);
                y = random_upto(rs, h - n + 1);
                r = 2 * random_upto(rs, 2) - 1;

                /*
                 * See if any previous rotations has happened at
                 * this point which nothing has overlapped since.
                 * If so, ensure we haven't either undone a
                 * previous move or repeated one so many times that
                 * it turns into fewer moves in the inverse
                 * direction (i.e. three identical rotations).
                 */
                oldtotal = prevmoves[y*rw+x];
                newtotal = oldtotal + r;
                
                /*
                 * Special case here for w==h==n, in which case
                 * there is actually no way to _avoid_ all moves
                 * repeating or undoing previous ones.
                 */
            } while ((w != n || h != n) &&
                     (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2));

            do_rotate(grid, w, h, n, params->orientable, x, y, r);

            /*
             * Log the rotation we've just performed at this point,
             * for inversion detection in the next move.
             * 
             * Also zero a section of the prevmoves array, because
             * any rotation area which _overlaps_ this one is now
             * entirely safe to perform further moves in.
             * 
             * Two rotation areas overlap if their top left
             * coordinates differ by strictly less than n in both
             * directions
             */
            prevmoves[y*rw+x] += r;
            for (dy = -n+1; dy <= n-1; dy++) {
                if (y + dy < 0 || y + dy >= rh)
                    continue;
                for (dx = -n+1; dx <= n-1; dx++) {
                    if (x + dx < 0 || x + dx >= rw)
                        continue;
                    if (dx == 0 && dy == 0)
                        continue;
                    prevmoves[(y+dy)*rw+(x+dx)] = 0;
                }
            }
        }

        sfree(prevmoves);

    } while (grid_complete(grid, wh, params->orientable));

    /*
     * Now construct the game description, by describing the grid
     * as a simple sequence of integers. They're comma-separated,
     * unless the puzzle is orientable in which case they're
     * separated by orientation letters `u', `d', `l' and `r'.
     */
    ret = NULL;
    retlen = 0;
    for (i = 0; i < wh; i++) {
        char buf[80];
        int k;

        k = sprintf(buf, "%d%c", grid[i] / 4,
		    (char)(params->orientable ? "uldr"[grid[i] & 3] : ','));

        ret = sresize(ret, retlen + k + 1, char);
        strcpy(ret + retlen, buf);
        retlen += k;
    }
    if (!params->orientable)
	ret[retlen-1] = '\0';	       /* delete last comma */

    sfree(grid);
    return ret;
}
Beispiel #8
0
/* Checks that the guessed balls in the state match up with the real balls
 * for all possible lasers (i.e. not just the ones that the player might
 * have already guessed). This is required because any layout with >4 balls
 * might have multiple valid solutions. Returns non-zero for a 'correct'
 * (i.e. consistent) layout. */
static int check_guesses(game_state *state, int cagey)
{
    game_state *solution, *guesses;
    int i, x, y, n, unused, tmp;
    int ret = 0;

    if (cagey) {
	/*
	 * First, check that each laser the player has already
	 * fired is consistent with the layout. If not, show them
	 * one error they've made and reveal no further
	 * information.
	 *
	 * Failing that, check to see whether the player would have
	 * been able to fire any laser which distinguished the real
	 * solution from their guess. If so, show them one such
	 * laser and reveal no further information.
	 */
	guesses = dup_game(state);
	/* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */
	for (x = 1; x <= state->w; x++) {
	    for (y = 1; y <= state->h; y++) {
		GRID(guesses, x, y) &= ~BALL_CORRECT;
		if (GRID(guesses, x, y) & BALL_GUESS)
		    GRID(guesses, x, y) |= BALL_CORRECT;
	    }
	}
	n = 0;
	for (i = 0; i < guesses->nlasers; i++) {
	    if (guesses->exits[i] != LASER_EMPTY &&
		guesses->exits[i] != laser_exit(guesses, i))
		n++;
	}
	if (n) {
	    /*
	     * At least one of the player's existing lasers
	     * contradicts their ball placement. Pick a random one,
	     * highlight it, and return.
	     *
	     * A temporary random state is created from the current
	     * grid, so that repeating the same marking will give
	     * the same answer instead of a different one.
	     */
	    random_state *rs = random_new((char *)guesses->grid,
					  (state->w+2)*(state->h+2) *
					  sizeof(unsigned int));
	    n = random_upto(rs, n);
	    random_free(rs);
	    for (i = 0; i < guesses->nlasers; i++) {
		if (guesses->exits[i] != LASER_EMPTY &&
		    guesses->exits[i] != laser_exit(guesses, i) &&
		    n-- == 0) {
		    state->exits[i] |= LASER_WRONG;
		    tmp = laser_exit(state, i);
		    if (RANGECHECK(state, tmp))
			state->exits[tmp] |= LASER_WRONG;
		    state->justwrong = TRUE;
		    free_game(guesses);
		    return 0;
		}
	    }
	}
	n = 0;
	for (i = 0; i < guesses->nlasers; i++) {
	    if (guesses->exits[i] == LASER_EMPTY &&
		laser_exit(state, i) != laser_exit(guesses, i))
		n++;
	}
	if (n) {
	    /*
	     * At least one of the player's unfired lasers would
	     * demonstrate their ball placement to be wrong. Pick a
	     * random one, highlight it, and return.
	     *
	     * A temporary random state is created from the current
	     * grid, so that repeating the same marking will give
	     * the same answer instead of a different one.
	     */
	    random_state *rs = random_new((char *)guesses->grid,
					  (state->w+2)*(state->h+2) *
					  sizeof(unsigned int));
	    n = random_upto(rs, n);
	    random_free(rs);
	    for (i = 0; i < guesses->nlasers; i++) {
		if (guesses->exits[i] == LASER_EMPTY &&
		    laser_exit(state, i) != laser_exit(guesses, i) &&
		    n-- == 0) {
		    fire_laser(state, i);
		    state->exits[i] |= LASER_OMITTED;
		    tmp = laser_exit(state, i);
		    if (RANGECHECK(state, tmp))
			state->exits[tmp] |= LASER_OMITTED;
		    state->justwrong = TRUE;
		    free_game(guesses);
		    return 0;
		}
	    }
	}
	free_game(guesses);
    }

    /* duplicate the state (to solution) */
    solution = dup_game(state);

    /* clear out the lasers of solution */
    for (i = 0; i < solution->nlasers; i++) {
        tmp = range2grid(solution, i, &x, &y, &unused);
        assert(tmp);
        GRID(solution, x, y) = 0;
        solution->exits[i] = LASER_EMPTY;
    }

    /* duplicate solution to guess. */
    guesses = dup_game(solution);

    /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */
    for (x = 1; x <= state->w; x++) {
        for (y = 1; y <= state->h; y++) {
            GRID(guesses, x, y) &= ~BALL_CORRECT;
            if (GRID(guesses, x, y) & BALL_GUESS)
                GRID(guesses, x, y) |= BALL_CORRECT;
        }
    }

    /* for each laser (on both game_states), fire it if it hasn't been fired.
     * If one has been fired (or received a hit) and another hasn't, we know
     * the ball layouts didn't match and can short-circuit return. */
    for (i = 0; i < solution->nlasers; i++) {
        if (solution->exits[i] == LASER_EMPTY)
            fire_laser(solution, i);
        if (guesses->exits[i] == LASER_EMPTY)
            fire_laser(guesses, i);
    }

    /* check each game_state's laser against the other; if any differ, return 0 */
    ret = 1;
    for (i = 0; i < solution->nlasers; i++) {
        tmp = range2grid(solution, i, &x, &y, &unused);
        assert(tmp);

        if (solution->exits[i] != guesses->exits[i]) {
            /* If the original state didn't have this shot fired,
             * and it would be wrong between the guess and the solution,
             * add it. */
            if (state->exits[i] == LASER_EMPTY) {
                state->exits[i] = solution->exits[i];
                if (state->exits[i] == LASER_REFLECT ||
                    state->exits[i] == LASER_HIT)
                    GRID(state, x, y) = state->exits[i];
                else {
                    /* add a new shot, incrementing state's laser count. */
                    int ex, ey, newno = state->laserno++;
                    tmp = range2grid(state, state->exits[i], &ex, &ey, &unused);
                    assert(tmp);
                    GRID(state, x, y) = newno;
                    GRID(state, ex, ey) = newno;
                }
		state->exits[i] |= LASER_OMITTED;
            } else {
		state->exits[i] |= LASER_WRONG;
	    }
            ret = 0;
        }
    }
    if (ret == 0 ||
	state->nguesses < state->minballs ||
	state->nguesses > state->maxballs) goto done;

    /* fix up original state so the 'correct' balls end up matching the guesses,
     * as we've just proved that they were equivalent. */
    for (x = 1; x <= state->w; x++) {
        for (y = 1; y <= state->h; y++) {
            if (GRID(state, x, y) & BALL_GUESS)
                GRID(state, x, y) |= BALL_CORRECT;
            else
                GRID(state, x, y) &= ~BALL_CORRECT;
        }
    }

done:
    /* fill in nright and nwrong. */
    state->nright = state->nwrong = state->nmissed = 0;
    for (x = 1; x <= state->w; x++) {
        for (y = 1; y <= state->h; y++) {
            int bs = GRID(state, x, y) & (BALL_GUESS | BALL_CORRECT);
            if (bs == (BALL_GUESS | BALL_CORRECT))
                state->nright++;
            else if (bs == BALL_GUESS)
                state->nwrong++;
            else if (bs == BALL_CORRECT)
                state->nmissed++;
        }
    }
    free_game(solution);
    free_game(guesses);
    state->reveal = 1;
    return ret;
}
Beispiel #9
0
/*
 * Generate a new complete random closed loop for the given grid.
 *
 * The method is to generate a WHITE/BLACK colouring of all the faces,
 * such that the WHITE faces will define the inside of the path, and the
 * BLACK faces define the outside.
 * To do this, we initially colour all faces GREY.  The infinite space outside
 * the grid is coloured BLACK, and we choose a random face to colour WHITE.
 * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY
 * faces, until the grid is filled with BLACK/WHITE.  As we grow the regions,
 * we avoid creating loops of a single colour, to preserve the topological
 * shape of the WHITE and BLACK regions.
 * We also try to make the boundary as loopy and twisty as possible, to avoid
 * generating paths that are uninteresting.
 * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY
 * face that can be coloured with that colour (without violating the
 * topological shape of that region).  It's not obvious, but I think this
 * algorithm is guaranteed to terminate without leaving any GREY faces behind.
 * Indeed, if there are any GREY faces at all, both the WHITE and BLACK
 * regions can be grown.
 * This is checked using assert()ions, and I haven't seen any failures yet.
 *
 * Hand-wavy proof: imagine what can go wrong...
 *
 * Could the white faces get completely cut off by the black faces, and still
 * leave some grey faces remaining?
 * No, because then the black faces would form a loop around both the white
 * faces and the grey faces, which is disallowed because we continually
 * maintain the correct topological shape of the black region.
 * Similarly, the black faces can never get cut off by the white faces.  That
 * means both the WHITE and BLACK regions always have some room to grow into
 * the GREY regions.
 * Could it be that we can't colour some GREY face, because there are too many
 * WHITE/BLACK transitions as we walk round the face? (see the
 * can_colour_face() function for details)
 * No.  Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk
 * around the face.  The two WHITE faces would be connected by a WHITE path,
 * and the BLACK faces would be connected by a BLACK path.  These paths would
 * have to cross, which is impossible.
 * Another thing that could go wrong: perhaps we can't find any GREY face to
 * colour WHITE, because it would create a loop-violation or a corner-violation
 * with the other WHITE faces?
 * This is a little bit tricky to prove impossible.  Imagine you have such a
 * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop
 * or corner violation).
 * That would cut all the non-white area into two blobs.  One of those blobs
 * must be free of BLACK faces (because the BLACK stuff is a connected blob).
 * So we have a connected GREY area, completely surrounded by WHITE
 * (including the GREY face we've tentatively coloured WHITE).
 * A well-known result in graph theory says that you can always find a GREY
 * face whose removal leaves the remaining GREY area connected.  And it says
 * there are at least two such faces, so we can always choose the one that
 * isn't the "tentative" GREY face.  Colouring that face WHITE leaves
 * everything nice and connected, including that "tentative" GREY face which
 * acts as a gateway to the rest of the non-WHITE grid.
 */
void generate_loop(grid *g, char *board, random_state *rs,
                   loopgen_bias_fn_t bias, void *biasctx)
{
    int i, j;
    int num_faces = g->num_faces;
    struct face_score *face_scores; /* Array of face_score objects */
    struct face_score *fs; /* Points somewhere in the above list */
    struct grid_face *cur_face;
    tree234 *lightable_faces_sorted;
    tree234 *darkable_faces_sorted;
    int *face_list;
    int do_random_pass;

    /* Make a board */
    memset(board, FACE_GREY, num_faces);
    
    /* Create and initialise the list of face_scores */
    face_scores = snewn(num_faces, struct face_score);
    for (i = 0; i < num_faces; i++) {
        face_scores[i].random = random_bits(rs, 31);
        face_scores[i].black_score = face_scores[i].white_score = 0;
    }
    
    /* Colour a random, finite face white.  The infinite face is implicitly
     * coloured black.  Together, they will seed the random growth process
     * for the black and white areas. */
    i = random_upto(rs, num_faces);
    board[i] = FACE_WHITE;

    /* We need a way of favouring faces that will increase our loopiness.
     * We do this by maintaining a list of all candidate faces sorted by
     * their score and choose randomly from that with appropriate skew.
     * In order to avoid consistently biasing towards particular faces, we
     * need the sort order _within_ each group of scores to be completely
     * random.  But it would be abusing the hospitality of the tree234 data
     * structure if our comparison function were nondeterministic :-).  So with
     * each face we associate a random number that does not change during a
     * particular run of the generator, and use that as a secondary sort key.
     * Yes, this means we will be biased towards particular random faces in
     * any one run but that doesn't actually matter. */

    lightable_faces_sorted = newtree234(white_sort_cmpfn);
    darkable_faces_sorted = newtree234(black_sort_cmpfn);

    /* Initialise the lists of lightable and darkable faces.  This is
     * slightly different from the code inside the while-loop, because we need
     * to check every face of the board (the grid structure does not keep a
     * list of the infinite face's neighbours). */
    for (i = 0; i < num_faces; i++) {
        grid_face *f = g->faces + i;
        struct face_score *fs = face_scores + i;
        if (board[i] != FACE_GREY) continue;
        /* We need the full colourability check here, it's not enough simply
         * to check neighbourhood.  On some grids, a neighbour of the infinite
         * face is not necessarily darkable. */
        if (can_colour_face(g, board, i, FACE_BLACK)) {
            fs->black_score = face_score(g, board, f, FACE_BLACK);
            add234(darkable_faces_sorted, fs);
        }
        if (can_colour_face(g, board, i, FACE_WHITE)) {
            fs->white_score = face_score(g, board, f, FACE_WHITE);
            add234(lightable_faces_sorted, fs);
        }
    }

    /* Colour faces one at a time until no more faces are colourable. */
    while (TRUE)
    {
        enum face_colour colour;
        tree234 *faces_to_pick;
        int c_lightable = count234(lightable_faces_sorted);
        int c_darkable = count234(darkable_faces_sorted);
        if (c_lightable == 0 && c_darkable == 0) {
            /* No more faces we can use at all. */
            break;
        }
	assert(c_lightable != 0 && c_darkable != 0);

        /* Choose a colour, and colour the best available face
         * with that colour. */
        colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK;

        if (colour == FACE_WHITE)
            faces_to_pick = lightable_faces_sorted;
        else
            faces_to_pick = darkable_faces_sorted;
        if (bias) {
            /*
             * Go through all the candidate faces and pick the one the
             * bias function likes best, breaking ties using the
             * ordering in our tree234 (which is why we replace only
             * if score > bestscore, not >=).
             */
            int j, k;
            struct face_score *best = NULL;
            int score, bestscore = 0;

            for (j = 0;
                 (fs = (struct face_score *)index234(faces_to_pick, j))!=NULL;
                 j++) {

                assert(fs);
                k = fs - face_scores;
                assert(board[k] == FACE_GREY);
                board[k] = colour;
                score = bias(biasctx, board, k);
                board[k] = FACE_GREY;
                bias(biasctx, board, k); /* let bias know we put it back */

                if (!best || score > bestscore) {
                    bestscore = score;
                    best = fs;
                }
            }
            fs = best;
        } else {
            fs = (struct face_score *)index234(faces_to_pick, 0);
        }
        assert(fs);
        i = fs - face_scores;
        assert(board[i] == FACE_GREY);
        board[i] = colour;
        if (bias)
            bias(biasctx, board, i); /* notify bias function of the change */

        /* Remove this newly-coloured face from the lists.  These lists should
         * only contain grey faces. */
        del234(lightable_faces_sorted, fs);
        del234(darkable_faces_sorted, fs);

        /* Remember which face we've just coloured */
        cur_face = g->faces + i;

        /* The face we've just coloured potentially affects the colourability
         * and the scores of any neighbouring faces (touching at a corner or
         * edge).  So the search needs to be conducted around all faces
         * touching the one we've just lit.  Iterate over its corners, then
         * over each corner's faces.  For each such face, we remove it from
         * the lists, recalculate any scores, then add it back to the lists
         * (depending on whether it is lightable, darkable or both). */
        for (i = 0; i < cur_face->order; i++) {
            grid_dot *d = cur_face->dots[i];
            for (j = 0; j < d->order; j++) {
                grid_face *f = d->faces[j];
                int fi; /* face index of f */

                if (f == NULL)
                    continue;
                if (f == cur_face)
                    continue;
                
                /* If the face is already coloured, it won't be on our
                 * lightable/darkable lists anyway, so we can skip it without 
                 * bothering with the removal step. */
                if (FACE_COLOUR(f) != FACE_GREY) continue; 

                /* Find the face index and face_score* corresponding to f */
                fi = f - g->faces;                
                fs = face_scores + fi;

                /* Remove from lightable list if it's in there.  We do this,
                 * even if it is still lightable, because the score might
                 * be different, and we need to remove-then-add to maintain
                 * correct sort order. */
                del234(lightable_faces_sorted, fs);
                if (can_colour_face(g, board, fi, FACE_WHITE)) {
                    fs->white_score = face_score(g, board, f, FACE_WHITE);
                    add234(lightable_faces_sorted, fs);
                }
                /* Do the same for darkable list. */
                del234(darkable_faces_sorted, fs);
                if (can_colour_face(g, board, fi, FACE_BLACK)) {
                    fs->black_score = face_score(g, board, f, FACE_BLACK);
                    add234(darkable_faces_sorted, fs);
                }
            }
        }
    }

    /* Clean up */
    freetree234(lightable_faces_sorted);
    freetree234(darkable_faces_sorted);
    sfree(face_scores);

    /* The next step requires a shuffled list of all faces */
    face_list = snewn(num_faces, int);
    for (i = 0; i < num_faces; ++i) {
        face_list[i] = i;
    }
    shuffle(face_list, num_faces, sizeof(int), rs);

    /* The above loop-generation algorithm can often leave large clumps
     * of faces of one colour.  In extreme cases, the resulting path can be 
     * degenerate and not very satisfying to solve.
     * This next step alleviates this problem:
     * Go through the shuffled list, and flip the colour of any face we can
     * legally flip, and which is adjacent to only one face of the opposite
     * colour - this tends to grow 'tendrils' into any clumps.
     * Repeat until we can find no more faces to flip.  This will
     * eventually terminate, because each flip increases the loop's
     * perimeter, which cannot increase for ever.
     * The resulting path will have maximal loopiness (in the sense that it
     * cannot be improved "locally".  Unfortunately, this allows a player to
     * make some illicit deductions.  To combat this (and make the path more
     * interesting), we do one final pass making random flips. */

    /* Set to TRUE for final pass */
    do_random_pass = FALSE;

    while (TRUE) {
        /* Remember whether a flip occurred during this pass */
        int flipped = FALSE;

        for (i = 0; i < num_faces; ++i) {
            int j = face_list[i];
            enum face_colour opp =
                (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE;
            if (can_colour_face(g, board, j, opp)) {
                grid_face *face = g->faces +j;
                if (do_random_pass) {
                    /* final random pass */
                    if (!random_upto(rs, 10))
                        board[j] = opp;
                } else {
                    /* normal pass - flip when neighbour count is 1 */
                    if (face_num_neighbours(g, board, face, opp) == 1) {
                        board[j] = opp;
                        flipped = TRUE;
                    }
                }
            }
        }

        if (do_random_pass) break;
        if (!flipped) do_random_pass = TRUE;
    }

    sfree(face_list);
}
Beispiel #10
0
static char *new_game_desc(game_params *params, random_state *rs,
			   char **aux, int interactive)
{
    int n = params->n, w = n+2, h = n+1, wh = w*h;
    int *grid, *grid2, *list;
    int i, j, k, len;
    char *ret;

    /*
     * Allocate space in which to lay the grid out.
     */
    grid = snewn(wh, int);
    grid2 = snewn(wh, int);
    list = snewn(2*wh, int);

    /*
     * I haven't been able to think of any particularly clever
     * techniques for generating instances of Dominosa with a
     * unique solution. Many of the deductions used in this puzzle
     * are based on information involving half the grid at a time
     * (`of all the 6s, exactly one is next to a 3'), so a strategy
     * of partially solving the grid and then perturbing the place
     * where the solver got stuck seems particularly likely to
     * accidentally destroy the information which the solver had
     * used in getting that far. (Contrast with, say, Mines, in
     * which most deductions are local so this is an excellent
     * strategy.)
     *
     * Therefore I resort to the basest of brute force methods:
     * generate a random grid, see if it's solvable, throw it away
     * and try again if not. My only concession to sophistication
     * and cleverness is to at least _try_ not to generate obvious
     * 2x2 ambiguous sections (see comment below in the domino-
     * flipping section).
     *
     * During tests performed on 2005-07-15, I found that the brute
     * force approach without that tweak had to throw away about 87
     * grids on average (at the default n=6) before finding a
     * unique one, or a staggering 379 at n=9; good job the
     * generator and solver are fast! When I added the
     * ambiguous-section avoidance, those numbers came down to 19
     * and 26 respectively, which is a lot more sensible.
     */

    do {
        domino_layout_prealloc(w, h, rs, grid, grid2, list);

        /*
         * Now we have a complete layout covering the whole
         * rectangle with dominoes. So shuffle the actual domino
         * values and fill the rectangle with numbers.
         */
        k = 0;
        for (i = 0; i <= params->n; i++)
            for (j = 0; j <= i; j++) {
                list[k++] = i;
                list[k++] = j;
            }
        shuffle(list, k/2, 2*sizeof(*list), rs);
        j = 0;
        for (i = 0; i < wh; i++)
            if (grid[i] > i) {
                /* Optionally flip the domino round. */
                int flip = -1;

                if (params->unique) {
                    int t1, t2;
                    /*
                     * If we're after a unique solution, we can do
                     * something here to improve the chances. If
                     * we're placing a domino so that it forms a
                     * 2x2 rectangle with one we've already placed,
                     * and if that domino and this one share a
                     * number, we can try not to put them so that
                     * the identical numbers are diagonally
                     * separated, because that automatically causes
                     * non-uniqueness:
                     * 
                     * +---+      +-+-+
                     * |2 3|      |2|3|
                     * +---+  ->  | | |
                     * |4 2|      |4|2|
                     * +---+      +-+-+
                     */
                    t1 = i;
                    t2 = grid[i];
                    if (t2 == t1 + w) {  /* this domino is vertical */
                        if (t1 % w > 0 &&/* and not on the left hand edge */
                            grid[t1-1] == t2-1 &&/* alongside one to left */
                            (grid2[t1-1] == list[j] ||   /* and has a number */
                             grid2[t1-1] == list[j+1] ||   /* in common */
                             grid2[t2-1] == list[j] ||
                             grid2[t2-1] == list[j+1])) {
                            if (grid2[t1-1] == list[j] ||
                                grid2[t2-1] == list[j+1])
                                flip = 0;
                            else
                                flip = 1;
                        }
                    } else {           /* this domino is horizontal */
                        if (t1 / w > 0 &&/* and not on the top edge */
                            grid[t1-w] == t2-w &&/* alongside one above */
                            (grid2[t1-w] == list[j] ||   /* and has a number */
                             grid2[t1-w] == list[j+1] ||   /* in common */
                             grid2[t2-w] == list[j] ||
                             grid2[t2-w] == list[j+1])) {
                            if (grid2[t1-w] == list[j] ||
                                grid2[t2-w] == list[j+1])
                                flip = 0;
                            else
                                flip = 1;
                        }
                    }
                }

                if (flip < 0)
                    flip = random_upto(rs, 2);

                grid2[i] = list[j + flip];
                grid2[grid[i]] = list[j + 1 - flip];
                j += 2;
            }
        assert(j == k);
    } while (params->unique && solver(w, h, n, grid2, NULL) > 1);

#ifdef GENERATION_DIAGNOSTICS
    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            putchar('0' + grid2[j*w+i]);
        }
        putchar('\n');
    }
    putchar('\n');
#endif

    /*
     * Encode the resulting game state.
     * 
     * Our encoding is a string of digits. Any number greater than
     * 9 is represented by a decimal integer within square
     * brackets. We know there are n+2 of every number (it's paired
     * with each number from 0 to n inclusive, and one of those is
     * itself so that adds another occurrence), so we can work out
     * the string length in advance.
     */

    /*
     * To work out the total length of the decimal encodings of all
     * the numbers from 0 to n inclusive:
     *  - every number has a units digit; total is n+1.
     *  - all numbers above 9 have a tens digit; total is max(n+1-10,0).
     *  - all numbers above 99 have a hundreds digit; total is max(n+1-100,0).
     *  - and so on.
     */
    len = n+1;
    for (i = 10; i <= n; i *= 10)
	len += max(n + 1 - i, 0);
    /* Now add two square brackets for each number above 9. */
    len += 2 * max(n + 1 - 10, 0);
    /* And multiply by n+2 for the repeated occurrences of each number. */
    len *= n+2;

    /*
     * Now actually encode the string.
     */
    ret = snewn(len+1, char);
    j = 0;
    for (i = 0; i < wh; i++) {
        k = grid2[i];
        if (k < 10)
            ret[j++] = '0' + k;
        else
            j += sprintf(ret+j, "[%d]", k);
        assert(j <= len);
    }
    assert(j == len);
    ret[j] = '\0';

    /*
     * Encode the solved state as an aux_info.
     */
    {
	char *auxinfo = snewn(wh+1, char);

	for (i = 0; i < wh; i++) {
	    int v = grid[i];
	    auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' :
			  v == i+w ? 'T' : v == i-w ? 'B' : '.');
	}
	auxinfo[wh] = '\0';

	*aux = auxinfo;
    }

    sfree(list);
    sfree(grid2);
    sfree(grid);

    return ret;
}
Beispiel #11
0
static void generate(random_state *rs, int w, int h, unsigned char *retgrid)
{
    float *fgrid;
    float *fgrid2;
    int step, i, j;
    float threshold;

    fgrid = snewn(w*h, float);

    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            fgrid[i*w+j] = random_upto(rs, 100000000UL) / 100000000.F;
        }
    }

    /*
     * The above gives a completely random splattering of black and
     * white cells. We want to gently bias this in favour of _some_
     * reasonably thick areas of white and black, while retaining
     * some randomness and fine detail.
     * 
     * So we evolve the starting grid using a cellular automaton.
     * Currently, I'm doing something very simple indeed, which is
     * to set each square to the average of the surrounding nine
     * cells (or the average of fewer, if we're on a corner).
     */
    for (step = 0; step < 1; step++) {
        fgrid2 = snewn(w*h, float);

        for (i = 0; i < h; i++) {
            for (j = 0; j < w; j++) {
                float sx, xbar;
                int n, p, q;

                /*
                 * Compute the average of the surrounding cells.
                 */
                n = 0;
                sx = 0.F;
                for (p = -1; p <= +1; p++) {
                    for (q = -1; q <= +1; q++) {
                        if (i+p < 0 || i+p >= h || j+q < 0 || j+q >= w)
                            continue;
			/*
			 * An additional special case not mentioned
			 * above: if a grid dimension is 2xn then
			 * we do not average across that dimension
			 * at all. Otherwise a 2x2 grid would
			 * contain four identical squares.
			 */
			if ((h==2 && p!=0) || (w==2 && q!=0))
			    continue;
                        n++;
                        sx += fgrid[(i+p)*w+(j+q)];
                    }
                }
                xbar = sx / n;

                fgrid2[i*w+j] = xbar;
            }
        }

        sfree(fgrid);
        fgrid = fgrid2;
    }

    fgrid2 = snewn(w*h, float);
    memcpy(fgrid2, fgrid, w*h*sizeof(float));
    qsort(fgrid2, w*h, sizeof(float), float_compare);
    threshold = fgrid2[w*h/2];
    sfree(fgrid2);

    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            retgrid[i*w+j] = (fgrid[i*w+j] >= threshold ? GRID_FULL :
                              GRID_EMPTY);
        }
    }

    sfree(fgrid);
}