Example #1
0
static game_state *execute_move(game_state *from, char *move)
{
    int w = from->par.w, a = w*w;
    game_state *ret;
    int x, y, i, n;

    if (move[0] == 'S') {
	ret = dup_game(from);
	ret->completed = ret->cheated = TRUE;

	for (i = 0; i < a; i++) {
	    if (move[i+1] < '1' || move[i+1] > '0'+w) {
		free_game(ret);
		return NULL;
	    }
	    ret->grid[i] = move[i+1] - '0';
	    ret->pencil[i] = 0;
	}

	if (move[a+1] != '\0') {
	    free_game(ret);
	    return NULL;
	}

	return ret;
    } else if ((move[0] == 'P' || move[0] == 'R') &&
	sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
	x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) {
	if (from->clues->immutable[y*w+x])
	    return NULL;

	ret = dup_game(from);
        if (move[0] == 'P' && n > 0) {
            ret->pencil[y*w+x] ^= 1L << n;
        } else {
            ret->grid[y*w+x] = n;
            ret->pencil[y*w+x] = 0;

            if (!ret->completed && !check_errors(ret, NULL))
                ret->completed = TRUE;
        }
	return ret;
    } else if (move[0] == 'M') {
	/*
	 * Fill in absolutely all pencil marks everywhere. (I
	 * wouldn't use this for actual play, but it's a handy
	 * starting point when following through a set of
	 * diagnostics output by the standalone solver.)
	 */
	ret = dup_game(from);
	for (i = 0; i < a; i++) {
	    if (!ret->grid[i])
		ret->pencil[i] = (1L << (w+1)) - (1L << 1);
	}
	return ret;
    } else
	return NULL;		       /* couldn't parse move string */
}
Example #2
0
static game_state *execute_move(game_state *from, char *move)
{
    game_state *ret;
    int w = from->w, h = from->h, n = from->n, wh = w*h;
    int x, y, dir;

    if (!strcmp(move, "S")) {
	int i;
	ret = dup_game(from);

	/*
	 * Simply replace the grid with a solved one. For this game,
	 * this isn't a useful operation for actually telling the user
	 * what they should have done, but it is useful for
	 * conveniently being able to get hold of a clean state from
	 * which to practise manoeuvres.
	 */
	qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_int);
	for (i = 0; i < ret->w*ret->h; i++)
	    ret->grid[i] &= ~3;
	ret->used_solve = TRUE;
	ret->completed = ret->movecount = 1;

	return ret;
    }

    if (move[0] != 'M' ||
	sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 ||
	x < 0 || y < 0 || x > from->w - n || y > from->h - n)
	return NULL;		       /* can't parse this move string */

    ret = dup_game(from);
    ret->movecount++;
    do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
    ret->lastx = x;
    ret->lasty = y;
    ret->lastr = dir;

    /*
     * See if the game has been completed. To do this we simply
     * test that the grid contents are in increasing order.
     */
    if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
        ret->completed = ret->movecount;
    return ret;
}
Example #3
0
static game_state *execute_move(const game_state *from, const char *move)
{
    game_state *ret;
    float angle;
    struct solid *poly;
    int pkey[2];
    int skey[2], dkey[2];
    int i, j, dest;
    int direction;

    switch (*move) {
      case 'L': direction = LEFT; break;
      case 'R': direction = RIGHT; break;
      case 'U': direction = UP; break;
      case 'D': direction = DOWN; break;
      default: return NULL;
    }

    dest = find_move_dest(from, direction, skey, dkey);
    if (dest < 0)
        return NULL;

    ret = dup_game(from);
    ret->current = dest;

    /*
     * So we know what grid square we're aiming for, and we also
     * know the two key points (as indices in both the source and
     * destination grid squares) which are invariant between source
     * and destination.
     * 
     * Next we must roll the polyhedron on to that square. So we
     * find the indices of the key points within the polyhedron's
     * vertex array, then use those in a call to transform_poly,
     * and align the result on the new grid square.
     */
    {
        int all_pkey[4];
        align_poly(from->solid, &from->grid->squares[from->current], all_pkey);
        pkey[0] = all_pkey[skey[0]];
        pkey[1] = all_pkey[skey[1]];
        /*
         * Now pkey[0] corresponds to skey[0] and dkey[0], and
         * likewise [1].
         */
    }

    /*
     * Now find the angle through which to rotate the polyhedron.
     * Do this by finding the two faces that share the two vertices
     * we've found, and taking the dot product of their normals.
     */
    {
        int f[2], nf = 0;
        float dp;

        for (i = 0; i < from->solid->nfaces; i++) {
            int match = 0;
            for (j = 0; j < from->solid->order; j++)
                if (from->solid->faces[i*from->solid->order + j] == pkey[0] ||
                    from->solid->faces[i*from->solid->order + j] == pkey[1])
                    match++;
            if (match == 2) {
                assert(nf < 2);
                f[nf++] = i;
            }
        }

        assert(nf == 2);

        dp = 0;
        for (i = 0; i < 3; i++)
            dp += (from->solid->normals[f[0]*3+i] *
                   from->solid->normals[f[1]*3+i]);
        angle = (float)acos(dp);
    }

    /*
     * Now transform the polyhedron. We aren't entirely sure
     * whether we need to rotate through angle or -angle, and the
     * simplest way round this is to try both and see which one
     * aligns successfully!
     * 
     * Unfortunately, _both_ will align successfully if this is a
     * cube, which won't tell us anything much. So for that
     * particular case, I resort to gross hackery: I simply negate
     * the angle before trying the alignment, depending on the
     * direction. Which directions work which way is determined by
     * pure trial and error. I said it was gross :-/
     */
    {
        int all_pkey[4];
        int success;

        if (from->solid->order == 4 && direction == UP)
            angle = -angle;            /* HACK */

        poly = transform_poly(from->solid,
                              from->grid->squares[from->current].flip,
                              pkey[0], pkey[1], angle);
        flip_poly(poly, from->grid->squares[ret->current].flip);
        success = align_poly(poly, &from->grid->squares[ret->current], all_pkey);

        if (!success) {
            sfree(poly);
            angle = -angle;
            poly = transform_poly(from->solid,
                                  from->grid->squares[from->current].flip,
                                  pkey[0], pkey[1], angle);
            flip_poly(poly, from->grid->squares[ret->current].flip);
            success = align_poly(poly, &from->grid->squares[ret->current], all_pkey);
        }

        assert(success);
    }

    /*
     * Now we have our rotated polyhedron, which we expect to be
     * exactly congruent to the one we started with - but with the
     * faces permuted. So we map that congruence and thereby figure
     * out how to permute the faces as a result of the polyhedron
     * having rolled.
     */
    {
        int *newcolours = snewn(from->solid->nfaces, int);

        for (i = 0; i < from->solid->nfaces; i++)
            newcolours[i] = -1;

        for (i = 0; i < from->solid->nfaces; i++) {
            int nmatch = 0;

            /*
             * Now go through the transformed polyhedron's faces
             * and figure out which one's normal is approximately
             * equal to this one.
             */
            for (j = 0; j < poly->nfaces; j++) {
                float dist;
                int k;

                dist = 0;

                for (k = 0; k < 3; k++)
                    dist += SQ(poly->normals[j*3+k] -
                               from->solid->normals[i*3+k]);

                if (APPROXEQ(dist, 0)) {
                    nmatch++;
                    newcolours[i] = ret->facecolours[j];
                }
            }

            assert(nmatch == 1);
        }

        for (i = 0; i < from->solid->nfaces; i++)
            assert(newcolours[i] != -1);

        sfree(ret->facecolours);
        ret->facecolours = newcolours;
    }

    ret->movecount++;

    /*
     * And finally, swap the colour between the bottom face of the
     * polyhedron and the face we've just landed on.
     * 
     * We don't do this if the game is already complete, since we
     * allow the user to roll the fully blue polyhedron around the
     * grid as a feeble reward.
     */
    if (!ret->completed) {
        i = lowest_face(from->solid);
        j = ret->facecolours[i];
        ret->facecolours[i] = GET_SQUARE(ret, ret->current);
        SET_SQUARE(ret, ret->current, j);

        /*
         * Detect game completion.
         */
        j = 0;
        for (i = 0; i < ret->solid->nfaces; i++)
            if (ret->facecolours[i])
                j++;
        if (j == ret->solid->nfaces) {
            ret->completed = ret->movecount;
        }

    }

    sfree(poly);

    /*
     * Align the normal polyhedron with its grid square, to get key
     * points for non-animated display.
     */
    {
        int pkey[4];
        int success;

        success = align_poly(ret->solid, &ret->grid->squares[ret->current], pkey);
        assert(success);

        ret->dpkey[0] = pkey[0];
        ret->dpkey[1] = pkey[1];
        ret->dgkey[0] = 0;
        ret->dgkey[1] = 1;
    }


    ret->spkey[0] = pkey[0];
    ret->spkey[1] = pkey[1];
    ret->sgkey[0] = skey[0];
    ret->sgkey[1] = skey[1];
    ret->previous = from->current;
    ret->angle = angle;

    return ret;
}
Example #4
0
static game_state *execute_move(game_state *from, char *move)
{
    int gx, gy, dx, dy, ux, uy, up, p;
    game_state *ret;

    if (!strcmp(move, "S")) {
	int i;

	ret = dup_game(from);

	/*
	 * Simply replace the grid with a solved one. For this game,
	 * this isn't a useful operation for actually telling the user
	 * what they should have done, but it is useful for
	 * conveniently being able to get hold of a clean state from
	 * which to practise manoeuvres.
	 */
	for (i = 0; i < ret->n; i++)
	    ret->tiles[i] = (i+1) % ret->n;
	ret->gap_pos = ret->n-1;
	ret->used_solve = TRUE;
	ret->completed = ret->movecount = 1;

	return ret;
    }

    gx = X(from, from->gap_pos);
    gy = Y(from, from->gap_pos);

    if (move[0] != 'M' ||
	sscanf(move+1, "%d,%d", &dx, &dy) != 2 ||
	(dx == gx && dy == gy) || (dx != gx && dy != gy) ||
	dx < 0 || dx >= from->w || dy < 0 || dy >= from->h)
	return NULL;

    /*
     * Find the unit displacement from the original gap
     * position towards this one.
     */
    ux = (dx < gx ? -1 : dx > gx ? +1 : 0);
    uy = (dy < gy ? -1 : dy > gy ? +1 : 0);
    up = C(from, ux, uy);

    ret = dup_game(from);

    ret->gap_pos = C(from, dx, dy);
    assert(ret->gap_pos >= 0 && ret->gap_pos < ret->n);

    ret->tiles[ret->gap_pos] = 0;

    for (p = from->gap_pos; p != ret->gap_pos; p += up) {
        assert(p >= 0 && p < from->n);
        ret->tiles[p] = from->tiles[p + up];
	ret->movecount++;
    }

    /*
     * See if the game has been completed.
     */
    if (!ret->completed) {
        ret->completed = ret->movecount;
        for (p = 0; p < ret->n; p++)
            if (ret->tiles[p] != (p < ret->n-1 ? p+1 : 0))
                ret->completed = 0;
    }

    return ret;
}
Example #5
0
static game_state *execute_move(game_state *from, char *move)
{
    game_state *ret = dup_game(from);
    int gx = -1, gy = -1, rangeno = -1;

    if (ret->justwrong) {
	int i;
	ret->justwrong = FALSE;
	for (i = 0; i < ret->nlasers; i++)
	    if (ret->exits[i] != LASER_EMPTY)
		ret->exits[i] &= ~(LASER_OMITTED | LASER_WRONG);
    }

    if (!strcmp(move, "S")) {
        check_guesses(ret, FALSE);
        return ret;
    }

    if (from->reveal) goto badmove;
    if (!*move) goto badmove;

    switch (move[0]) {
    case 'T':
        sscanf(move+1, "%d,%d", &gx, &gy);
        if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h)
            goto badmove;
        if (GRID(ret, gx, gy) & BALL_GUESS) {
            ret->nguesses--;
            GRID(ret, gx, gy) &= ~BALL_GUESS;
        } else {
            ret->nguesses++;
            GRID(ret, gx, gy) |= BALL_GUESS;
        }
        break;

    case 'F':
        sscanf(move+1, "%d", &rangeno);
        if (ret->exits[rangeno] != LASER_EMPTY)
            goto badmove;
        if (!RANGECHECK(ret, rangeno))
            goto badmove;
        fire_laser(ret, rangeno);
        break;

    case 'R':
        if (ret->nguesses < ret->minballs ||
            ret->nguesses > ret->maxballs)
            goto badmove;
        check_guesses(ret, TRUE);
        break;

    case 'L':
        {
            int lcount = 0;
            if (strlen(move) < 2) goto badmove;
            switch (move[1]) {
            case 'B':
                sscanf(move+2, "%d,%d", &gx, &gy);
                if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h)
                    goto badmove;
                GRID(ret, gx, gy) ^= BALL_LOCK;
                break;

#define COUNTLOCK do { if (GRID(ret, gx, gy) & BALL_LOCK) lcount++; } while (0)
#define SETLOCKIF(c) do {                                       \
    if (lcount > (c)) GRID(ret, gx, gy) &= ~BALL_LOCK;          \
    else              GRID(ret, gx, gy) |= BALL_LOCK;           \
} while(0)

            case 'C':
                sscanf(move+2, "%d", &gx);
                if (gx < 1 || gx > ret->w) goto badmove;
                for (gy = 1; gy <= ret->h; gy++) { COUNTLOCK; }
                for (gy = 1; gy <= ret->h; gy++) { SETLOCKIF(ret->h/2); }
                break;

            case 'R':
                sscanf(move+2, "%d", &gy);
                if (gy < 1 || gy > ret->h) goto badmove;
                for (gx = 1; gx <= ret->w; gx++) { COUNTLOCK; }
                for (gx = 1; gx <= ret->w; gx++) { SETLOCKIF(ret->w/2); }
                break;

#undef COUNTLOCK
#undef SETLOCKIF

            default:
                goto badmove;
            }
        }
        break;

    default:
        goto badmove;
    }

    return ret;

badmove:
    free_game(ret);
    return NULL;
}
Example #6
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;
}
Example #7
0
static game_state *execute_move(game_state *from, char *move)
{
    game_state *ret;
    int x1, x2, y1, y2, xx, yy;
    int val;

    if (move[0] == 'S' && strlen(move) == from->w * from->h + 1) {
	int i;

	ret = dup_game(from);

	for (i = 0; i < ret->w * ret->h; i++)
	    ret->grid[i] = (move[i+1] == '1' ? GRID_FULL : GRID_EMPTY);

	ret->completed = ret->cheated = TRUE;

	return ret;
    } else if ((move[0] == 'F' || move[0] == 'E' || move[0] == 'U') &&
	sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
	x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
	y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {

	x2 += x1;
	y2 += y1;
	val = (move[0] == 'F' ? GRID_FULL :
		 move[0] == 'E' ? GRID_EMPTY : GRID_UNKNOWN);

	ret = dup_game(from);
	for (yy = y1; yy < y2; yy++)
	    for (xx = x1; xx < x2; xx++)
		ret->grid[yy * ret->w + xx] = val;

	/*
	 * An actual change, so check to see if we've completed the
	 * game.
	 */
	if (!ret->completed) {
	    int *rowdata = snewn(ret->rowsize, int);
	    int i, len;

	    ret->completed = TRUE;

	    for (i=0; i<ret->w; i++) {
		len = compute_rowdata(rowdata,
				      ret->grid+i, ret->h, ret->w);
		if (len != ret->rowlen[i] ||
		    memcmp(ret->rowdata+i*ret->rowsize, rowdata,
			   len * sizeof(int))) {
		    ret->completed = FALSE;
		    break;
		}
	    }
	    for (i=0; i<ret->h; i++) {
		len = compute_rowdata(rowdata,
				      ret->grid+i*ret->w, ret->w, 1);
		if (len != ret->rowlen[i+ret->w] ||
		    memcmp(ret->rowdata+(i+ret->w)*ret->rowsize, rowdata,
			   len * sizeof(int))) {
		    ret->completed = FALSE;
		    break;
		}
	    }

	    sfree(rowdata);
	}