COL_GHOST,
COL_ZOMBIE,
COL_VAMPIRE,
+ COL_DONE,
NCOLOURS
};
-#define DIFFLIST(A) \
- A(EASY,Easy,e) \
- A(NORMAL,Normal,n) \
+#define DIFFLIST(A) \
+ A(EASY,Easy,e) \
+ A(NORMAL,Normal,n) \
A(TRICKY,Tricky,t)
#define ENUM(upper,title,lower) DIFF_ ## upper,
#define TITLE(upper,title,lower) #title,
sfree(params);
}
-static game_params *dup_params(game_params *params) {
+static game_params *dup_params(const game_params *params)
+{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
return ret;
return;
}
-static char *encode_params(game_params *params, int full) {
+static char *encode_params(const game_params *params, int full)
+{
char buf[256];
sprintf(buf, "%dx%d", params->w, params->h);
if (full)
return dupstr(buf);
}
-static config_item *game_configure(game_params *params) {
+static config_item *game_configure(const game_params *params)
+{
config_item *ret;
char buf[64];
return ret;
}
-static game_params *custom_params(config_item *cfg) {
+static game_params *custom_params(const config_item *cfg)
+{
game_params *ret = snew(game_params);
ret->w = atoi(cfg[0].sval);
return ret;
}
-static char *validate_params(game_params *params, int full) {
+static char *validate_params(const game_params *params, int full)
+{
if ((params->w * params->h ) > 54) return "Grid is too big";
if (params->w < 3) return "Width must be at least 3";
if (params->h < 3) return "Height must be at least 3";
unsigned char *pencils;
unsigned char *cell_errors;
unsigned char *hint_errors;
+ unsigned char *hints_done;
unsigned char count_errors[3];
int solved;
int cheated;
};
-static game_state *new_state(game_params *params) {
+static game_state *new_state(const game_params *params) {
int i;
game_state *state = snew(game_state);
state->common = snew(struct game_common);
state->hint_errors = snewn(2*state->common->num_paths, unsigned char);
for (i=0;i<2*state->common->num_paths;i++)
state->hint_errors[i] = FALSE;
+ state->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+ memset(state->hints_done, 0,
+ 2 * state->common->num_paths * sizeof(unsigned char));
for (i=0;i<3;i++)
state->count_errors[i] = FALSE;
return state;
}
-static game_state *dup_game(game_state *state) {
+static game_state *dup_game(const game_state *state)
+{
game_state *ret = snew(game_state);
ret->common = state->common;
}
else ret->hint_errors = NULL;
+ if (state->hints_done != NULL) {
+ ret->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+ memcpy(ret->hints_done, state->hints_done,
+ 2 * state->common->num_paths * sizeof(unsigned char));
+ }
+ else ret->hints_done = NULL;
+
ret->count_errors[0] = state->count_errors[0];
ret->count_errors[1] = state->count_errors[1];
ret->count_errors[2] = state->count_errors[2];
if (state->common->fixed != NULL) sfree(state->common->fixed);
sfree(state->common);
}
+ if (state->hints_done != NULL) sfree(state->hints_done);
if (state->hint_errors != NULL) sfree(state->hint_errors);
if (state->cell_errors != NULL) sfree(state->cell_errors);
if (state->pencils != NULL) sfree(state->pencils);
void get_unique(game_state *state, int counter, random_state *rs) {
- int p,i,c,count_uniques;
+ int p,i,c,pathlimit,count_uniques;
struct guess path_guess;
+ int *view_count;
struct entry {
struct entry *link;
struct {
struct entry *head;
struct entry *node;
- } views, single_views, loop_views, test_views;
+ } views, single_views, test_views;
struct entry test_entry;
-
+
path_guess.length = state->common->paths[counter].num_monsters;
path_guess.guess = snewn(path_guess.length,int);
path_guess.possible = snewn(path_guess.length,int);
path_guess.possible[p] =
state->guess[state->common->paths[counter].mapping[p]];
switch (path_guess.possible[p]) {
- case 1: path_guess.guess[p] = 1; break;
- case 2: path_guess.guess[p] = 2; break;
- case 3: path_guess.guess[p] = 1; break;
- case 4: path_guess.guess[p] = 4; break;
- case 5: path_guess.guess[p] = 1; break;
- case 6: path_guess.guess[p] = 2; break;
- case 7: path_guess.guess[p] = 1; break;
+ case 1: path_guess.guess[p] = 1; break;
+ case 2: path_guess.guess[p] = 2; break;
+ case 3: path_guess.guess[p] = 1; break;
+ case 4: path_guess.guess[p] = 4; break;
+ case 5: path_guess.guess[p] = 1; break;
+ case 6: path_guess.guess[p] = 2; break;
+ case 7: path_guess.guess[p] = 1; break;
}
}
views.head = NULL;
views.node = NULL;
+
+ pathlimit = state->common->paths[counter].length + 1;
+ view_count = snewn(pathlimit*pathlimit, int);
+ for (i = 0; i < pathlimit*pathlimit; i++)
+ view_count[i] = 0;
do {
- int mirror;
+ int mirror, start_view, end_view;
- views.node = snewn(1,struct entry);
- views.node->link = views.head;
- views.node->guess = snewn(path_guess.length,int);
- views.head = views.node;
- views.node->start_view = 0;
- views.node->end_view = 0;
- memcpy(views.node->guess, path_guess.guess,
- path_guess.length*sizeof(int));
-
mirror = FALSE;
+ start_view = 0;
for (p=0;p<state->common->paths[counter].length;p++) {
if (state->common->paths[counter].p[p] == -1) mirror = TRUE;
else {
if (state->common->paths[counter].p[p] ==
state->common->paths[counter].mapping[i]) {
if (path_guess.guess[i] == 1 && mirror == TRUE)
- views.node->start_view++;
+ start_view++;
if (path_guess.guess[i] == 2 && mirror == FALSE)
- views.node->start_view++;
+ start_view++;
if (path_guess.guess[i] == 4)
- views.node->start_view++;
+ start_view++;
break;
}
}
}
}
mirror = FALSE;
+ end_view = 0;
for (p=state->common->paths[counter].length-1;p>=0;p--) {
if (state->common->paths[counter].p[p] == -1) mirror = TRUE;
else {
if (state->common->paths[counter].p[p] ==
state->common->paths[counter].mapping[i]) {
if (path_guess.guess[i] == 1 && mirror == TRUE)
- views.node->end_view++;
+ end_view++;
if (path_guess.guess[i] == 2 && mirror == FALSE)
- views.node->end_view++;
+ end_view++;
if (path_guess.guess[i] == 4)
- views.node->end_view++;
+ end_view++;
break;
}
}
}
}
+
+ assert(start_view >= 0 && start_view < pathlimit);
+ assert(end_view >= 0 && end_view < pathlimit);
+ i = start_view * pathlimit + end_view;
+ view_count[i]++;
+ if (view_count[i] == 1) {
+ views.node = snewn(1,struct entry);
+ views.node->link = views.head;
+ views.node->guess = snewn(path_guess.length,int);
+ views.head = views.node;
+ views.node->start_view = start_view;
+ views.node->end_view = end_view;
+ memcpy(views.node->guess, path_guess.guess,
+ path_guess.length*sizeof(int));
+ }
} while (next_list(&path_guess, path_guess.length-1));
/* extract single entries from view list */
while (test_views.head != NULL) {
test_views.node = test_views.head;
test_views.head = test_views.head->link;
- c = 0;
- loop_views.head = views.head;
- loop_views.node = views.node;
- while (loop_views.head != NULL) {
- loop_views.node = loop_views.head;
- loop_views.head = loop_views.head->link;
- if (test_views.node->start_view == loop_views.node->start_view &&
- test_views.node->end_view == loop_views.node->end_view)
- c++;
- }
- if (c == 1) {
+ i = test_views.node->start_view * pathlimit + test_views.node->end_view;
+ if (view_count[i] == 1) {
single_views.node = snewn(1,struct entry);
single_views.node->link = single_views.head;
single_views.node->guess = snewn(path_guess.length,int);
}
}
+ sfree(view_count);
+
if (count_uniques > 0) {
test_entry.start_view = 0;
test_entry.end_view = 0;
for (i=0;i<paths[p].num_monsters;i++) {
switch (state->guess[paths[p].mapping[i]]) {
- case 1: loop.guess[i] = 1; break;
- case 2: loop.guess[i] = 2; break;
- case 3: loop.guess[i] = 1; break;
- case 4: loop.guess[i] = 4; break;
- case 5: loop.guess[i] = 1; break;
- case 6: loop.guess[i] = 2; break;
- case 7: loop.guess[i] = 1; break;
+ case 1: loop.guess[i] = 1; break;
+ case 2: loop.guess[i] = 2; break;
+ case 3: loop.guess[i] = 1; break;
+ case 4: loop.guess[i] = 4; break;
+ case 5: loop.guess[i] = 1; break;
+ case 6: loop.guess[i] = 2; break;
+ case 7: loop.guess[i] = 1; break;
}
loop.possible[i] = state->guess[paths[p].mapping[i]];
possible[paths[p].mapping[i]] = 0;
for (i=0;i<state->common->num_total;i++) {
loop.possible[i] = state->guess[i];
switch (state->guess[i]) {
- case 1: loop.guess[i] = 1; break;
- case 2: loop.guess[i] = 2; break;
- case 3: loop.guess[i] = 1; break;
- case 4: loop.guess[i] = 4; break;
- case 5: loop.guess[i] = 1; break;
- case 6: loop.guess[i] = 2; break;
- case 7: loop.guess[i] = 1; break;
+ case 1: loop.guess[i] = 1; break;
+ case 2: loop.guess[i] = 2; break;
+ case 3: loop.guess[i] = 1; break;
+ case 4: loop.guess[i] = 4; break;
+ case 5: loop.guess[i] = 1; break;
+ case 6: loop.guess[i] = 2; break;
+ case 7: loop.guess[i] = 1; break;
}
}
return pa->num_monsters - pb->num_monsters;
}
-static char *new_game_desc(game_params *params, random_state *rs,
+static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive) {
int i,count,c,w,h,r,p,g;
game_state *new;
* empty monster cells */
count = 0;
for (h=1;h<new->common->params.h+1;h++)
- for (w=1;w<new->common->params.w+1;w++) {
- c = random_upto(rs,5);
- if (c >= 2) {
- new->common->grid[w+h*(new->common->params.w+2)] = CELL_EMPTY;
- new->common->xinfo[w+h*(new->common->params.w+2)] = count++;
- }
- else if (c == 0) {
- new->common->grid[w+h*(new->common->params.w+2)] =
- CELL_MIRROR_L;
- new->common->xinfo[w+h*(new->common->params.w+2)] = -1;
- }
- else {
- new->common->grid[w+h*(new->common->params.w+2)] =
- CELL_MIRROR_R;
- new->common->xinfo[w+h*(new->common->params.w+2)] = -1;
+ for (w=1;w<new->common->params.w+1;w++) {
+ c = random_upto(rs,5);
+ if (c >= 2) {
+ new->common->grid[w+h*(new->common->params.w+2)] = CELL_EMPTY;
+ new->common->xinfo[w+h*(new->common->params.w+2)] = count++;
+ }
+ else if (c == 0) {
+ new->common->grid[w+h*(new->common->params.w+2)] =
+ CELL_MIRROR_L;
+ new->common->xinfo[w+h*(new->common->params.w+2)] = -1;
+ }
+ else {
+ new->common->grid[w+h*(new->common->params.w+2)] =
+ CELL_MIRROR_R;
+ new->common->xinfo[w+h*(new->common->params.w+2)] = -1;
+ }
}
- }
new->common->num_total = count; /* Total number of monsters in maze */
/* Puzzle is boring if it has too few monster cells. Discard
/* Grid is invalid if max. path length > threshold. Discard
* grid, make new one */
switch (new->common->params.diff) {
- case DIFF_EASY: max_length = min(new->common->params.w,new->common->params.h) + 1; break;
- case DIFF_NORMAL: max_length = (max(new->common->params.w,new->common->params.h) * 3) / 2; break;
- case DIFF_TRICKY: max_length = 9; break;
- default: max_length = 9; break;
+ case DIFF_EASY: max_length = min(new->common->params.w,new->common->params.h) + 1; break;
+ case DIFF_NORMAL: max_length = (max(new->common->params.w,new->common->params.h) * 3) / 2; break;
+ case DIFF_TRICKY: max_length = 9; break;
+ default: max_length = 9; break;
}
for (p=0;p<new->common->num_paths;p++) {
even less paths with unique solutions */
switch (new->common->params.diff) {
- case DIFF_EASY: filling = 2; break;
- case DIFF_NORMAL: filling = min( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
- case DIFF_TRICKY: filling = max( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
- default: filling = 0; break;
+ case DIFF_EASY: filling = 2; break;
+ case DIFF_NORMAL: filling = min( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
+ case DIFF_TRICKY: filling = max( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
+ default: filling = 0; break;
}
count = 0;
}
for (w=1;w<new->common->params.w+1;w++)
- for (h=1;h<new->common->params.h+1;h++) {
- c = new->common->xinfo[w+h*(new->common->params.w+2)];
- if (c >= 0) {
- if (new->guess[c] == 1) new->common->grid[w+h*(new->common->params.w+2)] = CELL_GHOST;
- if (new->guess[c] == 2) new->common->grid[w+h*(new->common->params.w+2)] = CELL_VAMPIRE;
- if (new->guess[c] == 4) new->common->grid[w+h*(new->common->params.w+2)] = CELL_ZOMBIE;
+ for (h=1;h<new->common->params.h+1;h++) {
+ c = new->common->xinfo[w+h*(new->common->params.w+2)];
+ if (c >= 0) {
+ if (new->guess[c] == 1) new->common->grid[w+h*(new->common->params.w+2)] = CELL_GHOST;
+ if (new->guess[c] == 2) new->common->grid[w+h*(new->common->params.w+2)] = CELL_VAMPIRE;
+ if (new->guess[c] == 4) new->common->grid[w+h*(new->common->params.w+2)] = CELL_ZOMBIE;
+ }
}
- }
/* Prepare path information needed by the solver (containing all hints) */
for (p=0;p<new->common->num_paths;p++) {
/* Encode grid */
count = 0;
for (y=1;y<new->common->params.h+1;y++)
- for (x=1;x<new->common->params.w+1;x++) {
- c = new->common->grid[x+y*(new->common->params.w+2)];
- if (count > 25) {
- *e++ = 'z';
- count -= 26;
- }
- if (c != CELL_MIRROR_L && c != CELL_MIRROR_R) {
- count++;
- }
- else if (c == CELL_MIRROR_L) {
- if (count > 0) *e++ = (count-1 + 'a');
- *e++ = 'L';
- count = 0;
- }
- else {
- if (count > 0) *e++ = (count-1 + 'a');
- *e++ = 'R';
- count = 0;
+ for (x=1;x<new->common->params.w+1;x++) {
+ c = new->common->grid[x+y*(new->common->params.w+2)];
+ if (count > 25) {
+ *e++ = 'z';
+ count -= 26;
+ }
+ if (c != CELL_MIRROR_L && c != CELL_MIRROR_R) {
+ count++;
+ }
+ else if (c == CELL_MIRROR_L) {
+ if (count > 0) *e++ = (count-1 + 'a');
+ *e++ = 'L';
+ count = 0;
+ }
+ else {
+ if (count > 0) *e++ = (count-1 + 'a');
+ *e++ = 'R';
+ count = 0;
+ }
}
- }
if (count > 0) *e++ = (count-1 + 'a');
/* Encode hints */
return;
}
-static game_state *new_game(midend *me, game_params *params, char *desc) {
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
+{
int i;
int n;
int count;
return state;
}
-static char *validate_desc(game_params *params, char *desc) {
+static char *validate_desc(const game_params *params, const char *desc)
+{
int i;
int w = params->w, h = params->h;
int wh = w*h;
int area;
int monsters;
int monster_count;
- char *desc_s = desc;
+ const char *desc_s = desc;
for (i=0;i<3;i++) {
if (!*desc) return "Faulty game description";
return NULL;
}
-static char *solve_game(game_state *state_start, game_state *currstate, char *aux, char **error) {
+static char *solve_game(const game_state *state_start, const game_state *currstate,
+ const char *aux, char **error)
+{
int p;
int *old_guess;
int iterative_depth;
return move;
}
-static int game_can_format_as_text_now(game_params *params) {
+static int game_can_format_as_text_now(const game_params *params)
+{
return TRUE;
}
-static char *game_text_format(game_state *state) {
+static char *game_text_format(const game_state *state)
+{
int w,h,c,r,xi,g;
char *ret;
char buf[120];
int ascii;
};
-static game_ui *new_ui(game_state *state) {
+static game_ui *new_ui(const game_state *state)
+{
game_ui *ui = snew(game_ui);
ui->hx = ui->hy = 0;
ui->hpencil = ui->hshow = ui->hcursor = 0;
return;
}
-static char *encode_ui(game_ui *ui) {
+static char *encode_ui(const game_ui *ui)
+{
return NULL;
}
-static void decode_ui(game_ui *ui, char *encoding) {
+static void decode_ui(game_ui *ui, const char *encoding)
+{
return;
}
-static void game_changed_state(game_ui *ui, game_state *oldstate, game_state *newstate) {
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
+{
/* See solo.c; if we were pencil-mode highlighting and
* somehow a square has just been properly filled, cancel
* pencil mode. */
unsigned char count_errors[3];
unsigned char *cell_errors;
unsigned char *hint_errors;
+ unsigned char *hints_done;
int hx, hy, hshow, hpencil; /* as for game_ui. */
int hflash;
int ascii;
};
+static int is_clue(const game_state *state, int x, int y)
+{
+ int h = state->common->params.h, w = state->common->params.w;
+
+ if (((x == 0 || x == w + 1) && y > 0 && y <= h) ||
+ ((y == 0 || y == h + 1) && x > 0 && x <= w))
+ return TRUE;
+
+ return FALSE;
+}
+
+static int clue_index(const game_state *state, int x, int y)
+{
+ int h = state->common->params.h, w = state->common->params.w;
+
+ if (y == 0)
+ return x - 1;
+ else if (x == w + 1)
+ return w + y - 1;
+ else if (y == h + 1)
+ return 2 * w + h - x;
+ else if (x == 0)
+ return 2 * (w + h) - y;
+
+ return -1;
+}
+
#define TILESIZE (ds->tilesize)
-#define BORDER (TILESIZE/2)
+#define BORDER (TILESIZE/4)
-static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
- int x, int y, int button) {
+static char *interpret_move(const game_state *state, game_ui *ui,
+ const game_drawstate *ds,
+ int x, int y, int button)
+{
int gx,gy;
int g,xi;
char buf[80];
gy = ((y-BORDER-2) / TILESIZE ) - 1;
if (button == 'a' || button == 'A') {
- ds->ascii = ui->ascii ? FALSE : TRUE;
+ ui->ascii = !ui->ascii;
return "";
}
+
+ if (button == 'm' || button == 'M') {
+ return dupstr("M");
+ }
if (ui->hshow == 1 && ui->hpencil == 0) {
xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)];
ui->hy = 1;
}
else switch (button) {
- case CURSOR_UP: ui->hy -= (ui->hy > 1) ? 1 : 0; break;
- case CURSOR_DOWN: ui->hy += (ui->hy < ds->h) ? 1 : 0; break;
- case CURSOR_RIGHT: ui->hx += (ui->hx < ds->w) ? 1 : 0; break;
- case CURSOR_LEFT: ui->hx -= (ui->hx > 1) ? 1 : 0; break;
- }
+ case CURSOR_UP: ui->hy -= (ui->hy > 1) ? 1 : 0; break;
+ case CURSOR_DOWN: ui->hy += (ui->hy < ds->h) ? 1 : 0; break;
+ case CURSOR_RIGHT: ui->hx += (ui->hx < ds->w) ? 1 : 0; break;
+ case CURSOR_LEFT: ui->hx -= (ui->hx > 1) ? 1 : 0; break;
+ }
ui->hshow = ui->hcursor = 1;
return "";
}
if (xi >= 0 && !state->common->fixed[xi]) {
if (button == 'g' || button == 'G' || button == '1') {
sprintf(buf,"g%d",xi);
- ui->hpencil = ui->hshow = 0;
+ if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
return dupstr(buf);
}
if (button == 'v' || button == 'V' || button == '2') {
sprintf(buf,"v%d",xi);
- ui->hpencil = ui->hshow = 0;
+ if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
return dupstr(buf);
}
if (button == 'z' || button == 'Z' || button == '3') {
sprintf(buf,"z%d",xi);
- ui->hpencil = ui->hshow = 0;
+ if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
return dupstr(buf);
}
if (button == 'e' || button == 'E' || button == CURSOR_SELECT2 ||
button == '0' || button == '\b') {
- if (!ui->hcursor) ui->hshow = 0;
sprintf(buf,"E%d",xi);
- ui->hpencil = ui->hshow = 0;
+ if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
return dupstr(buf);
}
}
}
}
}
+ } else if (button == LEFT_BUTTON) {
+ if (is_clue(state, gx, gy)) {
+ sprintf(buf, "D%d,%d", gx, gy);
+ return dupstr(buf);
+ }
}
return NULL;
valid = FALSE;
state->count_errors[0] = TRUE;
for (x=1;x<state->common->params.w+1;x++)
- for (y=1;y<state->common->params.h+1;y++) {
- xy = x+y*(state->common->params.w+2);
- if (state->common->xinfo[xy] >= 0 &&
- guess[state->common->xinfo[xy]] == 1)
- state->cell_errors[xy] = TRUE;
- }
+ for (y=1;y<state->common->params.h+1;y++) {
+ xy = x+y*(state->common->params.w+2);
+ if (state->common->xinfo[xy] >= 0 &&
+ guess[state->common->xinfo[xy]] == 1)
+ state->cell_errors[xy] = TRUE;
+ }
}
if (count_vampires > state->common->num_vampires ||
(filled && count_vampires != state->common->num_vampires) ) {
valid = FALSE;
state->count_errors[1] = TRUE;
for (x=1;x<state->common->params.w+1;x++)
- for (y=1;y<state->common->params.h+1;y++) {
- xy = x+y*(state->common->params.w+2);
- if (state->common->xinfo[xy] >= 0 &&
- guess[state->common->xinfo[xy]] == 2)
- state->cell_errors[xy] = TRUE;
- }
+ for (y=1;y<state->common->params.h+1;y++) {
+ xy = x+y*(state->common->params.w+2);
+ if (state->common->xinfo[xy] >= 0 &&
+ guess[state->common->xinfo[xy]] == 2)
+ state->cell_errors[xy] = TRUE;
+ }
}
if (count_zombies > state->common->num_zombies ||
(filled && count_zombies != state->common->num_zombies) ) {
valid = FALSE;
state->count_errors[2] = TRUE;
for (x=1;x<state->common->params.w+1;x++)
- for (y=1;y<state->common->params.h+1;y++) {
- xy = x+y*(state->common->params.w+2);
- if (state->common->xinfo[xy] >= 0 &&
- guess[state->common->xinfo[xy]] == 4)
- state->cell_errors[xy] = TRUE;
- }
+ for (y=1;y<state->common->params.h+1;y++) {
+ xy = x+y*(state->common->params.w+2);
+ if (state->common->xinfo[xy] >= 0 &&
+ guess[state->common->xinfo[xy]] == 4)
+ state->cell_errors[xy] = TRUE;
+ }
}
return valid;
}
}
- if (unfilled == 0 && count != state->common->paths[p].sightings_start) {
+ if (count > state->common->paths[p].sightings_start ||
+ count + unfilled < state->common->paths[p].sightings_start)
+ {
correct = FALSE;
state->hint_errors[state->common->paths[p].grid_start] = TRUE;
}
}
}
- if (unfilled == 0 && count != state->common->paths[p].sightings_end) {
+ if (count > state->common->paths[p].sightings_end ||
+ count + unfilled < state->common->paths[p].sightings_end)
+ {
correct = FALSE;
state->hint_errors[state->common->paths[p].grid_end] = TRUE;
}
return correct;
}
-static game_state *execute_move(game_state *state, char *move) {
- int x,n,p,i;
+static game_state *execute_move(const game_state *state, const char *move)
+{
+ int x,y,n,p,i;
char c;
int correct;
int solver;
if (c == 'z') ret->pencils[x] ^= 4;
move += n;
}
+ if (c == 'D' && sscanf(move + 1, "%d,%d%n", &x, &y, &n) == 2 &&
+ is_clue(ret, x, y)) {
+ ret->hints_done[clue_index(ret, x, y)] ^= 1;
+ move += n + 1;
+ }
+ if (c == 'M') {
+ /*
+ * Fill in absolutely all pencil marks in unfilled
+ * squares, for those who like to play by the rigorous
+ * approach of starting off in that state and eliminating
+ * things.
+ */
+ for (i = 0; i < ret->common->wh; i++)
+ if (ret->guess[i] == 7)
+ ret->pencils[i] = 7;
+ move++;
+ }
if (*move == ';') move++;
}
#define PREFERRED_TILE_SIZE 64
-static void game_compute_size(game_params *params, int tilesize,
- int *x, int *y) {
- *x = tilesize + (2 + params->w) * tilesize;
- *y = tilesize + (3 + params->h) * tilesize;
+static void game_compute_size(const game_params *params, int tilesize,
+ int *x, int *y)
+{
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ struct { int tilesize; } ads, *ds = &ads;
+ ads.tilesize = tilesize;
+
+ *x = 2*BORDER+(params->w+2)*TILESIZE;
+ *y = 2*BORDER+(params->h+3)*TILESIZE;
return;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
- game_params *params, int tilesize) {
+ const game_params *params, int tilesize)
+{
ds->tilesize = tilesize;
return;
}
#define COLOUR(ret, i, r, g, b) ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b)))
-static float *game_colours(frontend *fe, int *ncolours) {
+static float *game_colours(frontend *fe, int *ncolours)
+{
float *ret = snewn(3 * NCOLOURS, float);
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
ret[COL_VAMPIRE * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
ret[COL_VAMPIRE * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+ ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F;
+ ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F;
+ ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F;
+
*ncolours = NCOLOURS;
return ret;
}
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) {
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
int i;
struct game_drawstate *ds = snew(struct game_drawstate);
ds->hint_errors = snewn(2*state->common->num_paths,unsigned char);
for (i=0;i<2*state->common->num_paths;i++)
ds->hint_errors[i] = FALSE;
+ ds->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+ memset(ds->hints_done, 0,
+ 2 * state->common->num_paths * sizeof(unsigned char));
ds->hshow = ds->hpencil = ds->hflash = 0;
ds->hx = ds->hy = 0;
}
static void game_free_drawstate(drawing *dr, game_drawstate *ds) {
+ sfree(ds->hints_done);
sfree(ds->hint_errors);
sfree(ds->cell_errors);
sfree(ds->pencils);
}
static void draw_cell_background(drawing *dr, game_drawstate *ds,
- game_state *state, game_ui *ui, int x, int y) {
+ const game_state *state, const game_ui *ui,
+ int x, int y) {
int hon;
int dx,dy;
}
static void draw_monster_count(drawing *dr, game_drawstate *ds,
- game_state *state, int c, int hflash) {
- int dx,dy,dh;
+ const game_state *state, int c, int hflash) {
+ int dx,dy;
char buf[8];
char bufm[8];
- dy = TILESIZE/2;
- dh = TILESIZE;
+ dy = TILESIZE/4;
dx = BORDER+(ds->w+2)*TILESIZE/2+TILESIZE/4;
switch (c) {
- case 0:
- sprintf(buf,"%d",state->common->num_ghosts);
- sprintf(bufm,"G");
- dx -= 3*TILESIZE/2;
- break;
- case 1:
- sprintf(buf,"%d",state->common->num_vampires);
- sprintf(bufm,"V");
- break;
- case 2:
- sprintf(buf,"%d",state->common->num_zombies);
- sprintf(bufm,"Z");
- dx += 3*TILESIZE/2;
- break;
- }
-
+ case 0:
+ sprintf(buf,"%d",state->common->num_ghosts);
+ sprintf(bufm,"G");
+ dx -= 3*TILESIZE/2;
+ break;
+ case 1:
+ sprintf(buf,"%d",state->common->num_vampires);
+ sprintf(bufm,"V");
+ break;
+ case 2:
+ sprintf(buf,"%d",state->common->num_zombies);
+ sprintf(bufm,"Z");
+ dx += 3*TILESIZE/2;
+ break;
+ }
+
+ draw_rect(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE,
+ COL_BACKGROUND);
if (!ds->ascii) {
- draw_rect(dr,dx-2*TILESIZE/3,dy,3*TILESIZE/2,dh,COL_BACKGROUND);
- draw_monster(dr,ds,dx-TILESIZE/3,dh,2*TILESIZE/3,hflash,1<<c);
- draw_text(dr,dx,dh,FONT_VARIABLE,dy-1,ALIGN_HLEFT|ALIGN_VCENTRE,
- (state->count_errors[c] ? COL_ERROR : hflash ? COL_FLASH : COL_TEXT), buf);
- draw_update(dr,dx-2*TILESIZE/3,dy,3*TILESIZE/2,dh);
- }
- else {
- draw_rect(dr,dx-2*TILESIZE/3,dy,3*TILESIZE/2,dh,COL_BACKGROUND);
- draw_text(dr,dx-TILESIZE/3,dh,FONT_VARIABLE,dy-1,
+ draw_monster(dr, ds, dx-TILESIZE/3, dy+TILESIZE/2,
+ 2*TILESIZE/3, hflash, 1<<c);
+ } else {
+ draw_text(dr, dx-TILESIZE/3,dy+TILESIZE/2,FONT_VARIABLE,TILESIZE/2,
ALIGN_HCENTRE|ALIGN_VCENTRE,
hflash ? COL_FLASH : COL_TEXT, bufm);
- draw_text(dr,dx,dh,FONT_VARIABLE,dy-1,ALIGN_HLEFT|ALIGN_VCENTRE,
- (state->count_errors[c] ? COL_ERROR : hflash ? COL_FLASH : COL_TEXT), buf);
- draw_update(dr,dx-2*TILESIZE/3,dy,3*TILESIZE/2,dh);
}
+ draw_text(dr, dx, dy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
+ ALIGN_HLEFT|ALIGN_VCENTRE,
+ (state->count_errors[c] ? COL_ERROR :
+ hflash ? COL_FLASH : COL_TEXT), buf);
+ draw_update(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE);
return;
}
-static void draw_path_hint(drawing *dr, game_drawstate *ds, game_state *state,
- int i, int hflash, int start) {
- int dx,dy,x,y;
- int p,error;
- char buf[80];
-
- p = start ? state->common->paths[i].grid_start : state->common->paths[i].grid_end;
- range2grid(p,state->common->params.w,state->common->params.h,&x,&y);
- error = ds->hint_errors[p];
-
- dx = BORDER+(x* ds->tilesize)+(TILESIZE/2);
- dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE;
- sprintf(buf,"%d", start ? state->common->paths[i].sightings_start : state->common->paths[i].sightings_end);
- draw_rect(dr,dx-(TILESIZE/2)+2,dy-(TILESIZE/2)+2,TILESIZE-3,TILESIZE-3,COL_BACKGROUND);
- draw_text(dr,dx,dy,FONT_FIXED,TILESIZE/2,ALIGN_HCENTRE|ALIGN_VCENTRE, error ? COL_ERROR : hflash ? COL_FLASH : COL_TEXT,buf);
- draw_update(dr,dx-(TILESIZE/2)+2,dy-(TILESIZE/2)+2,TILESIZE-3,TILESIZE-3);
+static void draw_path_hint(drawing *dr, game_drawstate *ds,
+ const struct game_params *params,
+ int hint_index, int hflash, int hint) {
+ int x, y, color, dx, dy, text_dx, text_dy, text_size;
+ char buf[4];
+
+ if (ds->hint_errors[hint_index])
+ color = COL_ERROR;
+ else if (hflash)
+ color = COL_FLASH;
+ else if (ds->hints_done[hint_index])
+ color = COL_DONE;
+ else
+ color = COL_TEXT;
+
+ range2grid(hint_index, params->w, params->h, &x, &y);
+ /* Upper-left corner of the "tile" */
+ dx = BORDER + x * TILESIZE;
+ dy = BORDER + y * TILESIZE + TILESIZE;
+ /* Center of the "tile" */
+ text_dx = dx + TILESIZE / 2;
+ text_dy = dy + TILESIZE / 2;
+ /* Avoid wiping out the borders of the puzzle */
+ dx += 2;
+ dy += 2;
+ text_size = TILESIZE - 3;
+
+ sprintf(buf,"%d", hint);
+ draw_rect(dr, dx, dy, text_size, text_size, COL_BACKGROUND);
+ draw_text(dr, text_dx, text_dy, FONT_FIXED, TILESIZE / 2,
+ ALIGN_HCENTRE | ALIGN_VCENTRE, color, buf);
+ draw_update(dr, dx, dy, text_size, text_size);
return;
}
-static void draw_mirror(drawing *dr, game_drawstate *ds, game_state *state,
- int x, int y, int hflash, int mirror) {
+static void draw_mirror(drawing *dr, game_drawstate *ds,
+ const game_state *state, int x, int y,
+ int hflash, int mirror) {
int dx,dy,mx1,my1,mx2,my2;
dx = BORDER+(x* ds->tilesize)+(TILESIZE/2);
dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE;
return;
}
-static void draw_big_monster(drawing *dr, game_drawstate *ds, game_state *state,
- int x, int y, int hflash, int monster)
+static void draw_big_monster(drawing *dr, game_drawstate *ds,
+ const game_state *state, int x, int y,
+ int hflash, int monster)
{
int dx,dy;
char buf[10];
return;
}
-static void draw_pencils(drawing *dr, game_drawstate *ds, game_state *state,
- int x, int y, int pencil) {
+static void draw_pencils(drawing *dr, game_drawstate *ds,
+ const game_state *state, int x, int y, int pencil)
+{
int dx, dy;
- int monsters[4];
- int i, j, px, py;
+ int monsters[4];
+ int i, j, px, py;
char buf[10];
dx = BORDER+(x* ds->tilesize)+(TILESIZE/4);
dy = BORDER+(y* ds->tilesize)+(TILESIZE/4)+TILESIZE;
- for (i = 0, j = 1; j < 8; j *= 2)
- if (pencil & j)
- monsters[i++] = j;
- while (i < 4)
- monsters[i++] = 0;
-
- for (py = 0; py < 2; py++)
- for (px = 0; px < 2; px++)
- if (monsters[py*2+px]) {
- if (!ds->ascii) {
- draw_monster(dr, ds,
- dx + TILESIZE/2 * px, dy + TILESIZE/2 * py,
- TILESIZE/2, 0, monsters[py*2+px]);
- }
- else {
- switch (monsters[py*2+px]) {
- case 1: sprintf(buf,"G"); break;
- case 2: sprintf(buf,"V"); break;
- case 4: sprintf(buf,"Z"); break;
- }
- draw_text(dr,dx + TILESIZE/2 * px,dy + TILESIZE/2 * py,
- FONT_FIXED,TILESIZE/4,ALIGN_HCENTRE|ALIGN_VCENTRE,
- COL_TEXT,buf);
+ for (i = 0, j = 1; j < 8; j *= 2)
+ if (pencil & j)
+ monsters[i++] = j;
+ while (i < 4)
+ monsters[i++] = 0;
+
+ for (py = 0; py < 2; py++)
+ for (px = 0; px < 2; px++)
+ if (monsters[py*2+px]) {
+ if (!ds->ascii) {
+ draw_monster(dr, ds,
+ dx + TILESIZE/2 * px, dy + TILESIZE/2 * py,
+ TILESIZE/2, 0, monsters[py*2+px]);
+ }
+ else {
+ switch (monsters[py*2+px]) {
+ case 1: sprintf(buf,"G"); break;
+ case 2: sprintf(buf,"V"); break;
+ case 4: sprintf(buf,"Z"); break;
}
+ draw_text(dr,dx + TILESIZE/2 * px,dy + TILESIZE/2 * py,
+ FONT_FIXED,TILESIZE/4,ALIGN_HCENTRE|ALIGN_VCENTRE,
+ COL_TEXT,buf);
}
+ }
draw_update(dr,dx-(TILESIZE/4)+2,dy-(TILESIZE/4)+2,
(TILESIZE/2)-3,(TILESIZE/2)-3);
#define FLASH_TIME 0.7F
-static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui,
- float animtime, float flashtime) {
+static int is_hint_stale(const game_drawstate *ds, int hflash,
+ const game_state *state, int index)
+{
+ int ret = FALSE;
+ if (!ds->started) ret = TRUE;
+ if (ds->hflash != hflash) ret = TRUE;
+
+ if (ds->hint_errors[index] != state->hint_errors[index]) {
+ ds->hint_errors[index] = state->hint_errors[index];
+ ret = TRUE;
+ }
+
+ if (ds->hints_done[index] != state->hints_done[index]) {
+ ds->hints_done[index] = state->hints_done[index];
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+ const game_state *oldstate, const game_state *state,
+ int dir, const game_ui *ui,
+ float animtime, float flashtime)
+{
int i,j,x,y,xy;
- int stale, xi, c, hflash, hchanged;
+ int stale, xi, c, hflash, hchanged, changed_ascii;
hflash = (int)(flashtime * 5 / FLASH_TIME) % 2;
draw_rect(dr, BORDER+(ds->tilesize*(i+1))+1,
BORDER+(ds->tilesize*(j+2))+1, ds->tilesize-1,
ds->tilesize-1, COL_BACKGROUND);
- draw_update(dr,BORDER+TILESIZE-1,BORDER+2*TILESIZE-1,
- (ds->w)*TILESIZE+3, (ds->h)*TILESIZE+3);
+ draw_update(dr, 0, 0, 2*BORDER+(ds->w+2)*TILESIZE,
+ 2*BORDER+(ds->h+3)*TILESIZE);
}
hchanged = FALSE;
ds->hshow != ui->hshow || ds->hpencil != ui->hpencil)
hchanged = TRUE;
+ if (ds->ascii != ui->ascii) {
+ ds->ascii = ui->ascii;
+ changed_ascii = TRUE;
+ } else
+ changed_ascii = FALSE;
+
/* Draw monster count hints */
for (i=0;i<3;i++) {
stale = FALSE;
if (!ds->started) stale = TRUE;
if (ds->hflash != hflash) stale = TRUE;
- if (ds->ascii != ui->ascii) stale = TRUE;
+ if (changed_ascii) stale = TRUE;
if (ds->count_errors[i] != state->count_errors[i]) {
stale = TRUE;
ds->count_errors[i] = state->count_errors[i];
/* Draw path count hints */
for (i=0;i<state->common->num_paths;i++) {
- int p;
- stale = FALSE;
-
- if (!ds->started) stale = TRUE;
- if (ds->hflash != hflash) stale = TRUE;
+ struct path *path = &state->common->paths[i];
- p = state->common->paths[i].grid_start;
- if (ds->hint_errors[p] != state->hint_errors[p]) {
- stale = TRUE;
- ds->hint_errors[p] = state->hint_errors[p];
- }
-
- if (stale) {
- draw_path_hint(dr, ds, state, i, hflash, TRUE);
- }
-
- stale = FALSE;
-
- if (!ds->started) stale = TRUE;
- if (ds->hflash != hflash) stale = TRUE;
-
- p = state->common->paths[i].grid_end;
- if (ds->hint_errors[p] != state->hint_errors[p]) {
- stale = TRUE;
- ds->hint_errors[p] = state->hint_errors[p];
+ if (is_hint_stale(ds, hflash, state, path->grid_start)) {
+ draw_path_hint(dr, ds, &state->common->params, path->grid_start,
+ hflash, path->sightings_start);
}
- if (stale) {
- draw_path_hint(dr, ds, state, i, hflash, FALSE);
+ if (is_hint_stale(ds, hflash, state, path->grid_end)) {
+ draw_path_hint(dr, ds, &state->common->params, path->grid_end,
+ hflash, path->sightings_end);
}
-
}
/* Draw puzzle grid contents */
for (x = 1; x < ds->w+1; x++)
- for (y = 1; y < ds->h+1; y++) {
- stale = FALSE;
- xy = x+y*(state->common->params.w+2);
- xi = state->common->xinfo[xy];
- c = state->common->grid[xy];
+ for (y = 1; y < ds->h+1; y++) {
+ stale = FALSE;
+ xy = x+y*(state->common->params.w+2);
+ xi = state->common->xinfo[xy];
+ c = state->common->grid[xy];
- if (!ds->started) stale = TRUE;
- if (ds->hflash != hflash) stale = TRUE;
- if (ds->ascii != ui->ascii) stale = TRUE;
+ if (!ds->started) stale = TRUE;
+ if (ds->hflash != hflash) stale = TRUE;
+ if (changed_ascii) stale = TRUE;
- if (hchanged) {
- if ((x == ui->hx && y == ui->hy) ||
- (x == ds->hx && y == ds->hy))
- stale = TRUE;
- }
+ if (hchanged) {
+ if ((x == ui->hx && y == ui->hy) ||
+ (x == ds->hx && y == ds->hy))
+ stale = TRUE;
+ }
- if (xi >= 0 && (state->guess[xi] != ds->monsters[xi]) ) {
- stale = TRUE;
- ds->monsters[xi] = state->guess[xi];
- }
+ if (xi >= 0 && (state->guess[xi] != ds->monsters[xi]) ) {
+ stale = TRUE;
+ ds->monsters[xi] = state->guess[xi];
+ }
- if (xi >= 0 && (state->pencils[xi] != ds->pencils[xi]) ) {
- stale = TRUE;
- ds->pencils[xi] = state->pencils[xi];
- }
+ if (xi >= 0 && (state->pencils[xi] != ds->pencils[xi]) ) {
+ stale = TRUE;
+ ds->pencils[xi] = state->pencils[xi];
+ }
- if (state->cell_errors[xy] != ds->cell_errors[xy]) {
- stale = TRUE;
- ds->cell_errors[xy] = state->cell_errors[xy];
- }
+ if (state->cell_errors[xy] != ds->cell_errors[xy]) {
+ stale = TRUE;
+ ds->cell_errors[xy] = state->cell_errors[xy];
+ }
- if (stale) {
- draw_cell_background(dr, ds, state, ui, x, y);
- if (xi < 0)
- draw_mirror(dr, ds, state, x, y, hflash, c);
- else if (state->guess[xi] == 1 || state->guess[xi] == 2 ||
- state->guess[xi] == 4)
- draw_big_monster(dr, ds, state, x, y, hflash, state->guess[xi]);
- else
- draw_pencils(dr, ds, state, x, y, state->pencils[xi]);
+ if (stale) {
+ draw_cell_background(dr, ds, state, ui, x, y);
+ if (xi < 0)
+ draw_mirror(dr, ds, state, x, y, hflash, c);
+ else if (state->guess[xi] == 1 || state->guess[xi] == 2 ||
+ state->guess[xi] == 4)
+ draw_big_monster(dr, ds, state, x, y, hflash, state->guess[xi]);
+ else
+ draw_pencils(dr, ds, state, x, y, state->pencils[xi]);
+ }
}
- }
ds->hx = ui->hx; ds->hy = ui->hy;
ds->hshow = ui->hshow;
ds->hpencil = ui->hpencil;
ds->hflash = hflash;
- ui->ascii = ds->ascii;
ds->started = TRUE;
return;
}
-static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui) {
+static float game_anim_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
+{
return 0.0F;
}
-static float game_flash_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui) {
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
+{
return (!oldstate->solved && newstate->solved && !oldstate->cheated &&
!newstate->cheated) ? FLASH_TIME : 0.0F;
}
-static int game_status(game_state *state) {
+static int game_status(const game_state *state)
+{
return state->solved;
}
-static int game_timing_state(game_state *state, game_ui *ui) {
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
return TRUE;
}
-static void game_print_size(game_params *params, float *x, float *y) {
+static void game_print_size(const game_params *params, float *x, float *y)
+{
}
-static void game_print(drawing *dr, game_state *state, int tilesize) {
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
}
#ifdef COMBINED
const struct game thegame = {
"Undead", "games.undead", "undead",
default_params,
- game_fetch_preset,
+ game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,
FALSE, game_timing_state,
0, /* flags */
};
-