diff --git a/fifteen.c b/fifteen.c index 224a712..d02d0c0 100644 --- a/fifteen.c +++ b/fifteen.c @@ -449,13 +449,44 @@ static char *game_text_format(const game_state *state) return ret; } +struct game_ui { + /* + * User-preference option: invert the direction of arrow-key + * control, so that the arrow on the key you press indicates in + * which direction you want the _space_ to move, rather than in + * which direction you want a tile to move to fill the space. + */ + bool invert_cursor; +}; + +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int invert_cursor = -1; + + if (!initialised) { + initialised = true; + invert_cursor = getenv_bool("FIFTEEN_INVERT_CURSOR", -1); + } + + if (invert_cursor != -1) + ui_out->invert_cursor = invert_cursor; +} + static game_ui *new_ui(const game_state *state) { - return NULL; + struct game_ui *ui = snew(struct game_ui); + + ui->invert_cursor = false; + + legacy_prefs_override(ui); + + return ui; } static void free_ui(game_ui *ui) { + sfree(ui); } static void game_changed_state(game_ui *ui, const game_state *oldstate, @@ -708,11 +739,8 @@ static char *interpret_move(const game_state *state, game_ui *ui, if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h) return NULL; /* out of bounds */ } else if (IS_CURSOR_MOVE(button)) { - static int invert_cursor = -1; - if (invert_cursor == -1) - invert_cursor = getenv_bool("FIFTEEN_INVERT_CURSOR", false); button = flip_cursor(button); /* the default */ - if (invert_cursor) + if (ui->invert_cursor) button = flip_cursor(button); /* undoes the first flip */ move_cursor(button, &nx, &ny, state->w, state->h, false); } else if ((button == 'h' || button == 'H') && !state->completed) { diff --git a/lightup.c b/lightup.c index ca0d962..779437b 100644 --- a/lightup.c +++ b/lightup.c @@ -1833,13 +1833,37 @@ static char *game_text_format(const game_state *state) struct game_ui { int cur_x, cur_y; bool cur_visible; + + /* + * User preference: when a square contains both a black blob for + * 'user is convinced this isn't a light' and a yellow highlight + * for 'this square is lit by a light', both of which rule out it + * being a light, should we still bother to show the blob? + */ + bool draw_blobs_when_lit; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int draw_blobs_when_lit = -1; + + if (!initialised) { + initialised = true; + draw_blobs_when_lit = getenv_bool("LIGHTUP_LIT_BLOBS", -1); + } + + if (draw_blobs_when_lit != -1) + ui_out->draw_blobs_when_lit = draw_blobs_when_lit; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); ui->cur_x = ui->cur_y = 0; ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); + ui->draw_blobs_when_lit = true; + legacy_prefs_override(ui); return ui; } @@ -2130,7 +2154,7 @@ static unsigned int tile_flags(game_drawstate *ds, const game_state *state, return ret; } -static void tile_redraw(drawing *dr, game_drawstate *ds, +static void tile_redraw(drawing *dr, game_drawstate *ds, const game_ui *ui, const game_state *state, int x, int y) { unsigned int ds_flags = GRID(ds, flags, x, y); @@ -2160,10 +2184,7 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS, lcol, COL_BLACK); } else if ((ds_flags & DF_IMPOSSIBLE)) { - static int draw_blobs_when_lit = -1; - if (draw_blobs_when_lit < 0) - draw_blobs_when_lit = getenv_bool("LIGHTUP_LIT_BLOBS", true); - if (!(ds_flags & DF_LIT) || draw_blobs_when_lit) { + if (!(ds_flags & DF_LIT) || ui->draw_blobs_when_lit) { int rlen = TILE_SIZE / 4; draw_rect(dr, dx + TILE_SIZE/2 - rlen/2, dy + TILE_SIZE/2 - rlen/2, @@ -2208,7 +2229,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing); if (ds_flags != GRID(ds, flags, x, y)) { GRID(ds, flags, x, y) = ds_flags; - tile_redraw(dr, ds, state, x, y); + tile_redraw(dr, ds, ui, state, x, y); } } } diff --git a/loopy.c b/loopy.c index e7bf9c0..0f9dc52 100644 --- a/loopy.c +++ b/loopy.c @@ -868,13 +868,67 @@ static char *encode_solve_move(const game_state *state) return ret; } +struct game_ui { + /* + * User preference: should grid lines in LINE_NO state be drawn + * very faintly so users can still see where they are, or should + * they be completely invisible? + */ + bool draw_faint_lines; + + /* + * User preference: when clicking an edge that has only one + * possible edge connecting to one (or both) of its ends, should + * that edge also change to the same state as the edge we just + * clicked? + */ + enum { + AF_OFF, /* no, all grid edges are independent in the UI */ + AF_FIXED, /* yes, but only based on the grid itself */ + AF_ADAPTIVE /* yes, and consider edges user has already set to NO */ + } autofollow; +}; + +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int draw_faint_lines = -1; + static int autofollow = -1; + + if (!initialised) { + char *env; + + initialised = true; + draw_faint_lines = getenv_bool("LOOPY_FAINT_LINES", -1); + + if ((env = getenv("LOOPY_AUTOFOLLOW")) != NULL) { + if (!strcmp(env, "off")) + autofollow = AF_OFF; + else if (!strcmp(env, "fixed")) + autofollow = AF_FIXED; + else if (!strcmp(env, "adaptive")) + autofollow = AF_ADAPTIVE; + } + } + + if (draw_faint_lines != -1) + ui_out->draw_faint_lines = draw_faint_lines; + if (autofollow != -1) + ui_out->autofollow = autofollow; +} + static game_ui *new_ui(const game_state *state) { - return NULL; + game_ui *ui = snew(game_ui); + ui->draw_faint_lines = true; + ui->autofollow = AF_OFF; + legacy_prefs_override(ui); + return ui; } static void free_ui(game_ui *ui) { + sfree(ui); } static void game_changed_state(game_ui *ui, const game_state *oldstate, @@ -3024,73 +3078,58 @@ static char *interpret_move(const game_state *state, game_ui *ui, movesize = 80; movebuf = snewn(movesize, char); movelen = sprintf(movebuf, "%d%c", i, (int)button_char); - { - static enum { OFF, FIXED, ADAPTIVE, DUNNO } autofollow = DUNNO; - if (autofollow == DUNNO) { - const char *env = getenv("LOOPY_AUTOFOLLOW"); - if (env && !strcmp(env, "off")) - autofollow = OFF; - else if (env && !strcmp(env, "fixed")) - autofollow = FIXED; - else if (env && !strcmp(env, "adaptive")) - autofollow = ADAPTIVE; - else - autofollow = OFF; - } - if (autofollow != OFF) { - int dotid; - for (dotid = 0; dotid < 2; dotid++) { - grid_dot *dot = (dotid == 0 ? e->dot1 : e->dot2); - grid_edge *e_this = e; + if (ui->autofollow != AF_OFF) { + int dotid; + for (dotid = 0; dotid < 2; dotid++) { + grid_dot *dot = (dotid == 0 ? e->dot1 : e->dot2); + grid_edge *e_this = e; - while (1) { - int j, n_found; - grid_edge *e_next = NULL; + while (1) { + int j, n_found; + grid_edge *e_next = NULL; - for (j = n_found = 0; j < dot->order; j++) { - grid_edge *e_candidate = dot->edges[j]; - int i_candidate = e_candidate - g->edges; - if (e_candidate != e_this && - (autofollow == FIXED || - state->lines[i] == LINE_NO || - state->lines[i_candidate] != LINE_NO)) { - e_next = e_candidate; - n_found++; - } + for (j = n_found = 0; j < dot->order; j++) { + grid_edge *e_candidate = dot->edges[j]; + int i_candidate = e_candidate - g->edges; + if (e_candidate != e_this && + (ui->autofollow == AF_FIXED || + state->lines[i] == LINE_NO || + state->lines[i_candidate] != LINE_NO)) { + e_next = e_candidate; + n_found++; } - - if (n_found != 1 || - state->lines[e_next - g->edges] != state->lines[i]) - break; - - if (e_next == e) { - /* - * Special case: we might have come all the - * way round a loop and found our way back to - * the same edge we started from. In that - * situation, we must terminate not only this - * while loop, but the 'for' outside it that - * was tracing in both directions from the - * starting edge, because if we let it trace - * in the second direction then we'll only - * find ourself traversing the same loop in - * the other order and generate an encoded - * move string that mentions the same set of - * edges twice. - */ - goto autofollow_done; - } - - dot = (e_next->dot1 != dot ? e_next->dot1 : e_next->dot2); - if (movelen > movesize - 40) { - movesize = movesize * 5 / 4 + 128; - movebuf = sresize(movebuf, movesize, char); - } - e_this = e_next; - movelen += sprintf(movebuf+movelen, "%d%c", - (int)(e_this - g->edges), button_char); } + + if (n_found != 1 || + state->lines[e_next - g->edges] != state->lines[i]) + break; + + if (e_next == e) { + /* + * Special case: we might have come all the way + * round a loop and found our way back to the same + * edge we started from. In that situation, we + * must terminate not only this while loop, but + * the 'for' outside it that was tracing in both + * directions from the starting edge, because if + * we let it trace in the second direction then + * we'll only find ourself traversing the same + * loop in the other order and generate an encoded + * move string that mentions the same set of edges + * twice. + */ + goto autofollow_done; + } + + dot = (e_next->dot1 != dot ? e_next->dot1 : e_next->dot2); + if (movelen > movesize - 40) { + movesize = movesize * 5 / 4 + 128; + movebuf = sresize(movebuf, movesize, char); + } + e_this = e_next; + movelen += sprintf(movebuf+movelen, "%d%c", + (int)(e_this - g->edges), button_char); } autofollow_done:; } @@ -3261,7 +3300,7 @@ static const int loopy_line_redraw_phases[] = { }; #define NPHASES lenof(loopy_line_redraw_phases) -static void game_redraw_line(drawing *dr, game_drawstate *ds, +static void game_redraw_line(drawing *dr, game_drawstate *ds,const game_ui *ui, const game_state *state, int i, int phase) { grid *g = state->game_grid; @@ -3287,10 +3326,7 @@ static void game_redraw_line(drawing *dr, game_drawstate *ds, grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2); if (line_colour == COL_FAINT) { - static int draw_faint_lines = -1; - if (draw_faint_lines < 0) - draw_faint_lines = getenv_bool("LOOPY_FAINT_LINES", true); - if (draw_faint_lines) + if (ui->draw_faint_lines) draw_thick_line(dr, ds->tilesize/24.0, x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5, @@ -3326,7 +3362,7 @@ static bool boxes_intersect(int x0, int y0, int w0, int h0, } static void game_redraw_in_rect(drawing *dr, game_drawstate *ds, - const game_state *state, + const game_ui *ui, const game_state *state, int x, int y, int w, int h) { grid *g = state->game_grid; @@ -3347,7 +3383,7 @@ static void game_redraw_in_rect(drawing *dr, game_drawstate *ds, for (i = 0; i < g->num_edges; i++) { edge_bbox(ds, g, &g->edges[i], &bx, &by, &bw, &bh); if (boxes_intersect(x, y, w, h, bx, by, bw, bh)) - game_redraw_line(dr, ds, state, i, phase); + game_redraw_line(dr, ds, ui, state, i, phase); } } for (i = 0; i < g->num_dots; i++) { @@ -3504,7 +3540,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, int w = grid_width * ds->tilesize / g->tilesize; int h = grid_height * ds->tilesize / g->tilesize; - game_redraw_in_rect(dr, ds, state, + game_redraw_in_rect(dr, ds, ui, state, 0, 0, w + 2*border + 1, h + 2*border + 1); } else { @@ -3515,7 +3551,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, int x, y, w, h; face_text_bbox(ds, g, f, &x, &y, &w, &h); - game_redraw_in_rect(dr, ds, state, x, y, w, h); + game_redraw_in_rect(dr, ds, ui, state, x, y, w, h); } for (i = 0; i < nedges; i++) { @@ -3523,7 +3559,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, int x, y, w, h; edge_bbox(ds, g, e, &x, &y, &w, &h); - game_redraw_in_rect(dr, ds, state, x, y, w, h); + game_redraw_in_rect(dr, ds, ui, state, x, y, w, h); } } diff --git a/map.c b/map.c index b8f5970..31dc96c 100644 --- a/map.c +++ b/map.c @@ -45,12 +45,6 @@ static bool verbose = false; #define FIVE (FOUR+1) #define SIX (FOUR+2) -/* - * Ghastly run-time configuration option, just for Gareth (again). - */ -static int flash_type = -1; -static float flash_length; - /* * Difficulty levels. I do some macro ickery here to ensure that my * enum and the various forms of my name list always match up. @@ -2292,8 +2286,37 @@ struct game_ui { int cur_x, cur_y, cur_lastmove; bool cur_visible, cur_moved; + + /* + * User preference to enable alternative versions of the + * completion flash. Some users have found the colour-cycling + * default version to be a bit eye-twisting. + */ + enum { + FLASH_CYCLIC, /* cycle the four colours of the map */ + FLASH_EACH_TO_WHITE, /* turn each colour white in turn */ + FLASH_ALL_TO_WHITE /* flash the whole map to white in one go */ + } flash_type; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int flash_type = -1; + + if (!initialised) { + char *env; + + initialised = true; + + if ((env = getenv("MAP_ALTERNATIVE_FLASH")) != NULL) + flash_type = FLASH_EACH_TO_WHITE; + } + + if (flash_type != -1) + ui_out->flash_type = flash_type; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); @@ -2305,6 +2328,8 @@ static game_ui *new_ui(const game_state *state) ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); ui->cur_moved = false; ui->cur_lastmove = 0; + ui->flash_type = FLASH_CYCLIC; + legacy_prefs_override(ui); return ui; } @@ -2882,6 +2907,11 @@ static void draw_square(drawing *dr, game_drawstate *ds, draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); } +static float flash_length(const game_ui *ui) +{ + return (ui->flash_type == FLASH_EACH_TO_WHITE ? 0.50F : 0.30F); +} + static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, @@ -2905,10 +2935,10 @@ static void game_redraw(drawing *dr, game_drawstate *ds, } if (flashtime) { - if (flash_type == 1) - flash = (int)(flashtime * FOUR / flash_length); + if (ui->flash_type == FLASH_EACH_TO_WHITE) + flash = (int)(flashtime * FOUR / flash_length(ui)); else - flash = 1 + (int)(flashtime * THREE / flash_length); + flash = 1 + (int)(flashtime * THREE / flash_length(ui)); } else flash = -1; @@ -2927,12 +2957,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds, bv = FOUR; if (flash >= 0) { - if (flash_type == 1) { + if (ui->flash_type == FLASH_EACH_TO_WHITE) { if (tv == flash) tv = FOUR; if (bv == flash) bv = FOUR; - } else if (flash_type == 2) { + } else if (ui->flash_type == FLASH_ALL_TO_WHITE) { if (flash % 2) tv = bv = FOUR; } else { @@ -3062,15 +3092,7 @@ static float game_flash_length(const game_state *oldstate, { if (!oldstate->completed && newstate->completed && !oldstate->cheated && !newstate->cheated) { - if (flash_type < 0) { - char *env = getenv("MAP_ALTERNATIVE_FLASH"); - if (env) - flash_type = atoi(env); - else - flash_type = 0; - flash_length = (flash_type == 1 ? 0.50F : 0.30F); - } - return flash_length; + return flash_length(ui); } else return 0.0F; } diff --git a/misc.c b/misc.c index d9695c8..6b85533 100644 --- a/misc.c +++ b/misc.c @@ -202,7 +202,7 @@ char *fgetline(FILE *fp) return ret; } -bool getenv_bool(const char *name, bool dflt) +int getenv_bool(const char *name, int dflt) { char *env = getenv(name); if (env == NULL) return dflt; diff --git a/pearl.c b/pearl.c index 745985a..7caaae2 100644 --- a/pearl.c +++ b/pearl.c @@ -1859,8 +1859,41 @@ struct game_ui { int curx, cury; /* grid position of keyboard cursor */ bool cursor_active; /* true iff cursor is shown */ + + /* + * User preference: general visual style of the GUI. GUI_MASYU is + * how this puzzle is traditionally presented, with clue dots in + * the middle of grid squares, and the solution loop connecting + * square-centres. GUI_LOOPY shifts the grid by half a square in + * each direction, so that the clue dots are at _vertices_ of the + * grid and the solution loop follows the grid edges, which you + * could argue is more logical. + */ + enum { GUI_MASYU, GUI_LOOPY } gui_style; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int gui_style = -1; + + if (!initialised) { + initialised = true; + + switch (getenv_bool("PEARL_GUI_LOOPY", -1)) { + case 0: + gui_style = GUI_MASYU; + break; + case 1: + gui_style = GUI_LOOPY; + break; + } + } + + if (gui_style != -1) + ui_out->gui_style = gui_style; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); @@ -1871,6 +1904,9 @@ static game_ui *new_ui(const game_state *state) ui->cursor_active = getenv_bool("PUZZLES_SHOW_CURSOR", false); ui->curx = ui->cury = 0; + ui->gui_style = GUI_MASYU; + legacy_prefs_override(ui); + return ui; } @@ -1903,7 +1939,7 @@ static const char *current_key_label(const game_ui *ui, #define HALFSZ (ds->halfsz) #define TILE_SIZE (ds->halfsz*2 + 1) -#define BORDER ((get_gui_style() == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2)) +#define BORDER ((ui->gui_style == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2)) #define BORDER_WIDTH (max(TILE_SIZE / 32, 1)) @@ -1919,21 +1955,6 @@ static const char *current_key_label(const game_ui *ui, #define DS_FLASH (1 << 21) #define DS_CURSOR (1 << 22) -enum { GUI_MASYU, GUI_LOOPY }; - -static int get_gui_style(void) -{ - static int gui_style = -1; - - if (gui_style == -1) { - if (getenv_bool("PEARL_GUI_LOOPY", false)) - gui_style = GUI_LOOPY; - else - gui_style = GUI_MASYU; - } - return gui_style; -} - struct game_drawstate { int halfsz; bool started; @@ -2422,8 +2443,8 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds) } static void draw_lines_specific(drawing *dr, game_drawstate *ds, - int x, int y, unsigned int lflags, - unsigned int shift, int c) + const game_ui *ui, int x, int y, + unsigned int lflags, unsigned int shift, int c) { int ox = COORD(x), oy = COORD(y); int t2 = HALFSZ, t16 = HALFSZ/4; @@ -2472,7 +2493,7 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui, COL_CURSOR_BACKGROUND : COL_BACKGROUND); - if (get_gui_style() == GUI_LOOPY) { + if (ui->gui_style == GUI_LOOPY) { /* Draw small dot, underneath any lines. */ draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID); } else { @@ -2499,7 +2520,7 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui, draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK); draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK); } else { - if (get_gui_style() == GUI_LOOPY) { + if (ui->gui_style == GUI_LOOPY) { /* draw grid lines connecting centre of cells */ draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID); } @@ -2509,11 +2530,11 @@ static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui, /* Draw each of the four directions, where laid (or error, or drag, etc.) * Order is important here, specifically for the eventual colours of the * exposed end caps. */ - draw_lines_specific(dr, ds, x, y, lflags, 0, + draw_lines_specific(dr, ds, ui, x, y, lflags, 0, (lflags & DS_FLASH ? COL_FLASH : COL_BLACK)); - draw_lines_specific(dr, ds, x, y, lflags, DS_ESHIFT, COL_ERROR); - draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGOFF); - draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGON); + draw_lines_specific(dr, ds, ui, x, y, lflags, DS_ESHIFT, COL_ERROR); + draw_lines_specific(dr, ds, ui, x, y, lflags, DS_DSHIFT, COL_DRAGOFF); + draw_lines_specific(dr, ds, ui, x, y, lflags, DS_DSHIFT, COL_DRAGON); /* Draw a clue, if present */ if (clue != NOCLUE) { @@ -2540,7 +2561,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, bool force = false; if (!ds->started) { - if (get_gui_style() == GUI_MASYU) { + if (ui->gui_style == GUI_MASYU) { /* * Black rectangle which is the main grid. */ @@ -2657,7 +2678,7 @@ static void game_print(drawing *dr, const game_state *state, const game_ui *ui, game_drawstate *ds = game_new_drawstate(dr, state); game_set_size(dr, ds, NULL, tilesize); - if (get_gui_style() == GUI_MASYU) { + if (ui->gui_style == GUI_MASYU) { /* Draw grid outlines (black). */ for (x = 0; x <= w; x++) draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black); @@ -2689,7 +2710,8 @@ static void game_print(drawing *dr, const game_state *state, const game_ui *ui, int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ; int clue = state->shared->clues[y*w+x]; - draw_lines_specific(dr, ds, x, y, state->lines[y*w+x], 0, black); + draw_lines_specific(dr, ds, ui, x, y, + state->lines[y*w+x], 0, black); if (clue != NOCLUE) { int c = (clue == CORNER) ? black : white; diff --git a/puzzles.h b/puzzles.h index cb7fbca..b7c34f7 100644 --- a/puzzles.h +++ b/puzzles.h @@ -377,7 +377,9 @@ char *fgetline(FILE *fp); char *bin2hex(const unsigned char *in, int inlen); unsigned char *hex2bin(const char *in, int outlen); -bool getenv_bool(const char *name, bool dflt); +/* Returns 0 or 1 if the environment variable is set, or dflt if not. + * dflt may be a third value if it needs to be. */ +int getenv_bool(const char *name, int dflt); /* Mixes two colours in specified proportions. */ void colour_mix(const float src1[3], const float src2[3], float p, diff --git a/range.c b/range.c index 1fbb87c..cd5e89e 100644 --- a/range.c +++ b/range.c @@ -1225,13 +1225,49 @@ static char *game_text_format(const game_state *state) struct game_ui { puzzle_size r, c; /* cursor position */ bool cursor_show; + + /* + * User preference option to swap the left and right mouse + * buttons. + * + * The original puzzle submitter thought it would be more useful + * to have the left button turn an empty square into a dotted one, + * on the grounds that that was what you did most often; I (SGT) + * felt instinctively that the left button ought to place black + * squares and the right button place dots, on the grounds that + * that was consistent with many other puzzles in which the left + * button fills in the data used by the solution checker while the + * right button places pencil marks for the user's convenience. + * + * My first beta-player wasn't sure either, so I thought I'd + * pre-emptively put in a 'configuration' mechanism just in case. + */ + bool swap_buttons; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static int initialised = false; + static int swap_buttons = -1; + + if (!initialised) { + initialised = true; + swap_buttons = getenv_bool("RANGE_SWAP_BUTTONS", -1); + } + + if (swap_buttons != -1) + ui_out->swap_buttons = swap_buttons; +} + static game_ui *new_ui(const game_state *state) { struct game_ui *ui = snew(game_ui); ui->r = ui->c = 0; ui->cursor_show = getenv_bool("PUZZLES_SHOW_CURSOR", false); + + ui->swap_buttons = false; + legacy_prefs_override(ui); + return ui; } @@ -1298,36 +1334,12 @@ static char *interpret_move(const game_state *state, game_ui *ui, } if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { - /* - * Utterly awful hack, exactly analogous to the one in Slant, - * to configure the left and right mouse buttons the opposite - * way round. - * - * The original puzzle submitter thought it would be more - * useful to have the left button turn an empty square into a - * dotted one, on the grounds that that was what you did most - * often; I (SGT) felt instinctively that the left button - * ought to place black squares and the right button place - * dots, on the grounds that that was consistent with many - * other puzzles in which the left button fills in the data - * used by the solution checker while the right button places - * pencil marks for the user's convenience. - * - * My first beta-player wasn't sure either, so I thought I'd - * pre-emptively put in a 'configuration' mechanism just in - * case. - */ - { - static int swap_buttons = -1; - if (swap_buttons < 0) - swap_buttons = getenv_bool("RANGE_SWAP_BUTTONS", false); - if (swap_buttons) { - if (button == LEFT_BUTTON) - button = RIGHT_BUTTON; - else - button = LEFT_BUTTON; - } - } + if (ui->swap_buttons) { + if (button == LEFT_BUTTON) + button = RIGHT_BUTTON; + else + button = LEFT_BUTTON; + } } switch (button) { diff --git a/signpost.c b/signpost.c index d166b0c..787239c 100644 --- a/signpost.c +++ b/signpost.c @@ -1387,8 +1387,32 @@ struct game_ui { bool dragging, drag_is_from; int sx, sy; /* grid coords of start cell */ int dx, dy; /* pixel coords of drag posn */ + + /* + * Trivial and foolish configurable option done on purest whim. + * With this option enabled, the victory flash is done by rotating + * each square in the opposite direction from its immediate + * neighbours, so that they behave like a field of interlocking + * gears. With it disabled, they all rotate in the same direction. + * Choose for yourself which is more brain-twisting :-) + */ + bool gear_mode; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int gear_mode = -1; + + if (!initialised) { + initialised = true; + gear_mode = getenv_bool("SIGNPOST_GEARS", -1); + } + + if (gear_mode != -1) + ui_out->gear_mode = gear_mode; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); @@ -1402,6 +1426,9 @@ static game_ui *new_ui(const game_state *state) ui->dragging = false; ui->sx = ui->sy = ui->dx = ui->dy = 0; + ui->gear_mode = false; + legacy_prefs_override(ui); + return ui; } @@ -2143,26 +2170,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, if (state->nums[i] != ds->nums[i] || f != ds->f[i] || dirp != ds->dirp[i] || force || !ds->started) { - int sign; - { - /* - * Trivial and foolish configurable option done on - * purest whim. With this option enabled, the - * victory flash is done by rotating each square - * in the opposite direction from its immediate - * neighbours, so that they behave like a field of - * interlocking gears. With it disabled, they all - * rotate in the same direction. Choose for - * yourself which is more brain-twisting :-) - */ - static int gear_mode = -1; - if (gear_mode < 0) - gear_mode = getenv_bool("SIGNPOST_GEARS", false); - if (gear_mode) - sign = 1 - 2 * ((x ^ y) & 1); - else - sign = 1; - } + int sign = (ui->gear_mode ? 1 - 2 * ((x ^ y) & 1) : 1); tile_redraw(dr, ds, BORDER + x * TILE_SIZE, BORDER + y * TILE_SIZE, diff --git a/slant.c b/slant.c index 5230f25..ee39d74 100644 --- a/slant.c +++ b/slant.c @@ -1581,13 +1581,39 @@ static char *game_text_format(const game_state *state) struct game_ui { int cur_x, cur_y; bool cur_visible; + + /* + * User preference option to swap the left and right mouse + * buttons. There isn't a completely obvious mapping of left and + * right buttons to the two directions of slash, and at least one + * player turned out not to have the same intuition as me. + */ + bool swap_buttons; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int swap_buttons = -1; + + if (!initialised) { + initialised = true; + swap_buttons = getenv_bool("SLANT_SWAP_BUTTONS", -1); + } + + if (swap_buttons != -1) + ui_out->swap_buttons = swap_buttons; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); ui->cur_x = ui->cur_y = 0; ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); + + ui->swap_buttons = false; + legacy_prefs_override(ui); + return ui; } diff --git a/towers.c b/towers.c index 012c8b3..0c2bf3a 100644 --- a/towers.c +++ b/towers.c @@ -1159,8 +1159,37 @@ struct game_ui { * allowed on immutable squares. */ bool hcursor; + + /* + * User preference option which can be set to FALSE to disable the + * 3D graphical style, and instead just display the puzzle as if + * it was a Sudoku variant, i.e. each square just has a digit in + * it. + * + * I was initially a bit uncertain about whether the 3D style + * would be the right thing, on the basis that it uses up space in + * the cells and makes it hard to use many pencil marks. Actually + * nobody seems to have complained, but having put in the option + * while I was still being uncertain, it seems silly not to leave + * it in just in case. + */ + int three_d; }; +static void legacy_prefs_override(struct game_ui *ui_out) +{ + static bool initialised = false; + static int three_d = -1; + + if (!initialised) { + initialised = true; + three_d = getenv_bool("TOWERS_2D", -1); + } + + if (three_d != -1) + ui_out->three_d = three_d; +} + static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); @@ -1169,6 +1198,9 @@ static game_ui *new_ui(const game_state *state) ui->hpencil = false; ui->hshow = ui->hcursor = getenv_bool("PUZZLES_SHOW_CURSOR", false); + ui->three_d = true; + legacy_prefs_override(ui); + return ui; } @@ -1224,7 +1256,6 @@ static const char *current_key_label(const game_ui *ui, struct game_drawstate { int tilesize; - bool three_d; /* default 3D graphics are user-disableable */ long *tiles; /* (w+2)*(w+2) temp space */ long *drawn; /* (w+2)*(w+2)*4: current drawn data */ bool *errtmp; @@ -1356,7 +1387,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, tx = FROMCOORD(x); ty = FROMCOORD(y); - if (ds->three_d) { + if (ui->three_d) { /* * In 3D mode, just locating the mouse click in the natural * square grid may not be sufficient to tell which tower the @@ -1630,7 +1661,6 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) int i; ds->tilesize = 0; - ds->three_d = !getenv_bool("TOWERS_2D", false); ds->tiles = snewn((w+2)*(w+2), long); ds->drawn = snewn((w+2)*(w+2)*4, long); for (i = 0; i < (w+2)*(w+2)*4; i++) @@ -1648,8 +1678,8 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds) sfree(ds); } -static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, - int x, int y, long tile) +static void draw_tile(drawing *dr, game_drawstate *ds, const game_ui *ui, + struct clues *clues, int x, int y, long tile) { int w = clues->w /* , a = w*w */; int tx, ty, bg; @@ -1661,7 +1691,7 @@ static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND; /* draw tower */ - if (ds->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) { + if (ui->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) { int coords[8]; int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w); int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w); @@ -1762,10 +1792,10 @@ static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues, * to put the pencil marks. */ /* Start with the whole square, minus space for impinging towers */ - pl = tx + (ds->three_d ? X_3D_DISP(w,w) : 0); + pl = tx + (ui->three_d ? X_3D_DISP(w,w) : 0); pr = tx + TILESIZE; pt = ty; - pb = ty + TILESIZE - (ds->three_d ? Y_3D_DISP(w,w) : 0); + pb = ty + TILESIZE - (ui->three_d ? Y_3D_DISP(w,w) : 0); /* * We arrange our pencil marks in a grid layout, with @@ -1901,13 +1931,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds, ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) { clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); - draw_tile(dr, ds, state->clues, x-1, y-1, tr); + draw_tile(dr, ds, ui, state->clues, x-1, y-1, tr); if (x > 0) - draw_tile(dr, ds, state->clues, x-2, y-1, tl); + draw_tile(dr, ds, ui, state->clues, x-2, y-1, tl); if (y <= w) - draw_tile(dr, ds, state->clues, x-1, y, br); + draw_tile(dr, ds, ui, state->clues, x-1, y, br); if (x > 0 && y <= w) - draw_tile(dr, ds, state->clues, x-2, y, bl); + draw_tile(dr, ds, ui, state->clues, x-2, y, bl); unclip(dr); draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE);