Net: preference for how loop highlighting interacts with locking.

Net's loop highlighting detects any loop in the current state of the
grid. I've occasionally found that to be a bit of a spoiler, since
sometimes it can point out a deduction I should make before I've
figured it out for myself - e.g. when I've just locked all but two of
the squares involved in the loop, and the last two _just happen_ to be
oriented so as to complete the loop. In that situation I'd prefer if
the loop _didn't_ immediately light up and point out to me that I need
to arrange that those squares aren't connected to each other.

The simple answer is to only count edges connecting two _locked_
squares, for the purposes of loop detection. But this is obviously
unacceptable to some players - in particular, those who play without
the locking feature at all. So it should be a user preference.
This commit is contained in:
Simon Tatham
2023-04-23 14:02:39 +01:00
parent c0bd524848
commit 2d91261eb3

85
net.c
View File

@ -1135,7 +1135,8 @@ static void perturb(int w, int h, unsigned char *tiles, bool wrapping,
static int *compute_loops_inner(int w, int h, bool wrapping,
const unsigned char *tiles,
const unsigned char *barriers);
const unsigned char *barriers,
bool include_unlocked_squares);
static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, bool interactive)
@ -1464,7 +1465,8 @@ static char *new_game_desc(const game_params *params, random_state *rs,
*/
prev_loopsquares = w*h+1;
while (1) {
loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL);
loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL,
true);
this_loopsquares = 0;
for (i = 0; i < w*h; i++) {
if (loops[i]) {
@ -1919,6 +1921,7 @@ struct net_neighbour_ctx {
int w, h;
const unsigned char *tiles, *barriers;
int i, n, neighbours[4];
bool include_unlocked_squares;
};
static int net_neighbour(int vertex, void *vctx)
{
@ -1939,6 +1942,9 @@ static int net_neighbour(int vertex, void *vctx)
continue;
OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h);
v1 = y1 * ctx->w + x1;
if (!ctx->include_unlocked_squares &&
!(tile & ctx->tiles[v1] & LOCKED))
continue;
if (ctx->tiles[v1] & F(dir))
ctx->neighbours[ctx->n++] = v1;
}
@ -1952,32 +1958,39 @@ static int net_neighbour(int vertex, void *vctx)
static int *compute_loops_inner(int w, int h, bool wrapping,
const unsigned char *tiles,
const unsigned char *barriers)
const unsigned char *barriers,
bool include_unlocked_squares)
{
struct net_neighbour_ctx ctx;
struct findloopstate *fls;
int *loops;
int x, y;
int x, y, v;
fls = findloop_new_state(w*h);
ctx.w = w;
ctx.h = h;
ctx.tiles = tiles;
ctx.barriers = barriers;
ctx.include_unlocked_squares = include_unlocked_squares;
findloop_run(fls, w*h, net_neighbour, &ctx);
loops = snewn(w*h, int);
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
int x1, y1, dir;
int x1, y1, v1, dir;
int flags = 0;
v = y * w + x;
for (dir = 1; dir < 0x10; dir <<= 1) {
if ((tiles[y*w+x] & dir) &&
if ((tiles[v] & dir) &&
!(barriers && (barriers[y*w+x] & dir))) {
OFFSETWH(x1, y1, x, y, dir, w, h);
if ((tiles[y1*w+x1] & F(dir)) &&
v1 = y1 * w + x1;
if (!include_unlocked_squares &&
!(tiles[v] & tiles[v1] & LOCKED))
continue;
if ((tiles[v1] & F(dir)) &&
findloop_is_loop_edge(fls, y*w+x, y1*w+x1))
flags |= ERR(dir);
}
@ -1990,10 +2003,12 @@ static int *compute_loops_inner(int w, int h, bool wrapping,
return loops;
}
static int *compute_loops(const game_state *state)
static int *compute_loops(const game_state *state,
bool include_unlocked_squares)
{
return compute_loops_inner(state->width, state->height, state->wrapping,
state->tiles, state->imm->barriers);
state->tiles, state->imm->barriers,
include_unlocked_squares);
}
struct game_ui {
@ -2006,6 +2021,8 @@ struct game_ui {
int dragtilex, dragtiley, dragstartx, dragstarty;
bool dragged;
#endif
bool unlocked_loops;
};
static game_ui *new_ui(const game_state *state)
@ -2013,20 +2030,28 @@ static game_ui *new_ui(const game_state *state)
void *seed;
int seedsize;
game_ui *ui = snew(game_ui);
ui->org_x = ui->org_y = 0;
ui->cur_x = ui->cx = state->width / 2;
ui->cur_y = ui->cy = state->height / 2;
ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
get_random_seed(&seed, &seedsize);
ui->rs = random_new(seed, seedsize);
sfree(seed);
ui->unlocked_loops = true;
if (state) {
ui->org_x = ui->org_y = 0;
ui->cur_x = ui->cx = state->width / 2;
ui->cur_y = ui->cy = state->height / 2;
ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
get_random_seed(&seed, &seedsize);
ui->rs = random_new(seed, seedsize);
sfree(seed);
} else {
ui->rs = NULL;
}
return ui;
}
static void free_ui(game_ui *ui)
{
random_free(ui->rs);
if (ui->rs)
random_free(ui->rs);
sfree(ui);
}
@ -2060,6 +2085,28 @@ static void decode_ui(game_ui *ui, const char *encoding,
}
}
static config_item *get_prefs(game_ui *ui)
{
config_item *ret;
ret = snewn(2, config_item);
ret[0].name = "Highlight loops involving unlocked squares";
ret[0].kw = "unlocked-loops";
ret[0].type = C_BOOLEAN;
ret[0].u.boolean.bval = ui->unlocked_loops;
ret[1].name = NULL;
ret[1].type = C_END;
return ret;
}
static void set_prefs(game_ui *ui, const config_item *cfg)
{
ui->unlocked_loops = cfg[0].u.boolean.bval;
}
static void game_changed_state(game_ui *ui, const game_state *oldstate,
const game_state *newstate)
{
@ -2891,7 +2938,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
* of barriers.
*/
active = compute_active(state, ui->cx, ui->cy);
loops = compute_loops(state);
loops = compute_loops(state, ui->unlocked_loops);
for (dy = -1; dy < ds->height+1; dy++) {
for (dx = -1; dx < ds->width+1; dx++) {
@ -3270,7 +3317,7 @@ const struct game thegame = {
free_game,
true, solve_game,
false, NULL, NULL, /* can_format_as_text_now, text_format */
NULL, NULL, /* get_prefs, set_prefs */
get_prefs, set_prefs,
new_ui,
free_ui,
encode_ui,