From b99f10727a17d84938ae75670945638ad67a8c95 Mon Sep 17 00:00:00 2001 From: Hauke Rehr Date: Sun, 9 Feb 2025 20:47:07 +0100 Subject: [PATCH] slant: add preference: fading grounded components Commit message added by SGT: The general idea is that the puzzle constraint "you must not form a loop" can also be phrased as "every grid vertex must have a path to the boundary of the grid", on the basis that in a grid with every square filled, any connected component of vertices _not_ joined to the boundary by a path must instead be surrounded by a loop blocking every way it could get there. So by changing the colour of each "grounded" (connected to the boundary) grid edge, you draw the player's attention to the components that don't yet have a path to the edge, so they can consider the possible paths. This is the kind of hint that users can very easily find to be patronising and intrusive, not to mention a spoiler if they haven't even made the leap from "no loops" to "everything must be grounded". So it's a preference, and off by default. This patch is somewhat rewritten from the submitted version, to conform to local style and also compile in older C versions. --- slant.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/slant.c b/slant.c index 54aa419..7242333 100644 --- a/slant.c +++ b/slant.c @@ -45,11 +45,13 @@ enum { COL_ERROR, COL_CURSOR, COL_FILLEDSQUARE, + COL_GROUNDED, NCOLOURS }; enum { PREF_MOUSE_BUTTON_ORDER, + PREF_FADE_GROUNDED, N_PREF_ITEMS }; @@ -94,6 +96,7 @@ typedef struct game_clues { #define ERR_VERTEX 1 #define ERR_SQUARE 2 +#define BORDER_EDGE 4 /* kind of an abuse: not an error */ struct game_state { struct game_params p; @@ -1399,6 +1402,48 @@ static bool check_completion(game_state *state) memset(state->errors, 0, W*H); + /* + * Detect and grounded-highlight edge-connected components in the grid. + */ + { + DSF *connected = dsf_new(W*H); + unsigned root_NW; + int slash; + int x, y; + + for (x = 0; x <= w; x++) { + dsf_merge(connected, x, 0); + dsf_merge(connected, h*W+x, 0); + } + for (y = 0; y <= h; y++) { + dsf_merge(connected, y*W, 0); + dsf_merge(connected, y*W+w, 0); + } + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + switch (state->soln[y*w+x]) { + case -1: + dsf_merge(connected, y*W+x, (y+1)*W+(x+1)); + break; + case +1: + dsf_merge(connected, y*W+(x+1), (y+1)*W+x); + break; + default: + continue; + } + } + } + + root_NW = dsf_canonify(connected, 0); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if ((slash = state->soln[y*w+x]) && dsf_canonify( + connected, y*W + x + (slash == 1 ? 1 : 0)) == root_NW) + state->errors[y*w+x] |= BORDER_EDGE; + dsf_free(connected); + } + /* * Detect and error-highlight loops in the grid. */ @@ -1594,6 +1639,7 @@ struct game_ui { * player turned out not to have the same intuition as me. */ bool swap_buttons; + bool fade_grounded; }; static void legacy_prefs_override(struct game_ui *ui_out) @@ -1617,6 +1663,7 @@ static game_ui *new_ui(const game_state *state) ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false); ui->swap_buttons = false; + ui->fade_grounded = false; legacy_prefs_override(ui); return ui; @@ -1636,6 +1683,11 @@ static config_item *get_prefs(game_ui *ui) ret[PREF_MOUSE_BUTTON_ORDER].u.choices.choicekws = ":\\:/"; ret[PREF_MOUSE_BUTTON_ORDER].u.choices.selected = ui->swap_buttons; + ret[PREF_FADE_GROUNDED].name = "Fade grounded components"; + ret[PREF_FADE_GROUNDED].kw = "fade-grounded"; + ret[PREF_FADE_GROUNDED].type = C_BOOLEAN; + ret[PREF_FADE_GROUNDED].u.boolean.bval = ui->fade_grounded; + ret[N_PREF_ITEMS].name = NULL; ret[N_PREF_ITEMS].type = C_END; @@ -1645,6 +1697,7 @@ static config_item *get_prefs(game_ui *ui) static void set_prefs(game_ui *ui, const config_item *cfg) { ui->swap_buttons = cfg[PREF_MOUSE_BUTTON_ORDER].u.choices.selected; + ui->fade_grounded = cfg[PREF_FADE_GROUNDED].u.boolean.bval; } static void free_ui(game_ui *ui) @@ -1705,6 +1758,7 @@ static const char *current_key_label(const game_ui *ui, #define ERR_BL 0x00010000L #define ERR_BR 0x00020000L #define CURSOR 0x00040000L +#define GROUNDED 0x00080000L struct game_drawstate { int tilesize; @@ -1873,6 +1927,10 @@ static float *game_colours(frontend *fe, int *ncolours) ret[COL_ERROR * 3 + 1] = 0.0F; ret[COL_ERROR * 3 + 2] = 0.0F; + ret[COL_GROUNDED * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; + ret[COL_GROUNDED * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.8F; + ret[COL_GROUNDED * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.8F; + *ncolours = NCOLOURS; return ret; } @@ -1957,14 +2015,16 @@ static void draw_tile(drawing *dr, game_drawstate *ds, game_clues *clues, * Draw the slash. */ if (v & BACKSLASH) { - int scol = (v & ERRSLASH) ? COL_ERROR : bscol; + int scol = ((v & ERRSLASH) ? COL_ERROR : + (v & GROUNDED) ? COL_GROUNDED : bscol); draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol); draw_line(dr, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1, scol); draw_line(dr, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1), scol); } else if (v & FORWSLASH) { - int scol = (v & ERRSLASH) ? COL_ERROR : fscol; + int scol = ((v & ERRSLASH) ? COL_ERROR : + (v & GROUNDED) ? COL_GROUNDED : fscol); draw_line(dr, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol); draw_line(dr, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1, scol); @@ -2076,6 +2136,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds, ds->todraw[(y+1)*(w+2)+x] |= ERR_TR; ds->todraw[(y+1)*(w+2)+(x+1)] |= ERR_TL; } + if (ui->fade_grounded) + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->errors[y*w+x] & BORDER_EDGE) + // dunno why but it works this way + ds->todraw[(y+1)*(W+1)+(x+1)] |= GROUNDED; /* * Now go through and draw the grid squares.