static void key_handler (game_time t, int val) { switch (val) { case 1: if (! crash_detected && can_jump()) jump (t); break; case 2: if (! crash_detected) fire_laser (t); break; case 3: lives = 1; print_message ("aborted at user's request"); mode_change (crash_mode, 0); break; } }
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; }
/* 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; }