Add origin-shifting (Shift+cursors) and source-shifting (Ctrl+cursors) to Net.

(Adding modifier+cursors handling has had minor knock-on effects on the other
puzzles, so that they can continue to ignore modifiers.)

(An unfortunate side effect of this is some artifacts in exterior barrier
drawing; notably, a disconnected corner can now appear at the corner of the
grid under some circumstances. I haven't found a satisfactory way round
this yet.)

[originally from svn r5844]
This commit is contained in:
Jacob Nevins
2005-05-26 13:40:38 +00:00
parent a1be37343c
commit 865e8ad6ca
13 changed files with 208 additions and 122 deletions

2
cube.c
View File

@ -1013,6 +1013,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
int i, j, dest, mask; int i, j, dest, mask;
struct solid *poly; struct solid *poly;
button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
/* /*
* All moves are made with the cursor keys or numeric keypad. * All moves are made with the cursor keys or numeric keypad.
*/ */

View File

@ -456,6 +456,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
int gx, gy, dx, dy, ux, uy, up, p; int gx, gy, dx, dy, ux, uy, up, p;
game_state *ret; game_state *ret;
button &= ~MOD_MASK;
gx = X(from, from->gap_pos); gx = X(from, from->gap_pos);
gy = Y(from, from->gap_pos); gy = Y(from, from->gap_pos);

10
gtk.c
View File

@ -317,24 +317,26 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
{ {
frontend *fe = (frontend *)data; frontend *fe = (frontend *)data;
int keyval; int keyval;
int shift = (event->state & GDK_SHIFT_MASK) ? MOD_SHFT : 0;
int ctrl = (event->state & GDK_CONTROL_MASK) ? MOD_CTRL : 0;
if (!fe->pixmap) if (!fe->pixmap)
return TRUE; return TRUE;
if (event->keyval == GDK_Up) if (event->keyval == GDK_Up)
keyval = CURSOR_UP; keyval = shift | ctrl | CURSOR_UP;
else if (event->keyval == GDK_KP_Up || event->keyval == GDK_KP_8) else if (event->keyval == GDK_KP_Up || event->keyval == GDK_KP_8)
keyval = MOD_NUM_KEYPAD | '8'; keyval = MOD_NUM_KEYPAD | '8';
else if (event->keyval == GDK_Down) else if (event->keyval == GDK_Down)
keyval = CURSOR_DOWN; keyval = shift | ctrl | CURSOR_DOWN;
else if (event->keyval == GDK_KP_Down || event->keyval == GDK_KP_2) else if (event->keyval == GDK_KP_Down || event->keyval == GDK_KP_2)
keyval = MOD_NUM_KEYPAD | '2'; keyval = MOD_NUM_KEYPAD | '2';
else if (event->keyval == GDK_Left) else if (event->keyval == GDK_Left)
keyval = CURSOR_LEFT; keyval = shift | ctrl | CURSOR_LEFT;
else if (event->keyval == GDK_KP_Left || event->keyval == GDK_KP_4) else if (event->keyval == GDK_KP_Left || event->keyval == GDK_KP_4)
keyval = MOD_NUM_KEYPAD | '4'; keyval = MOD_NUM_KEYPAD | '4';
else if (event->keyval == GDK_Right) else if (event->keyval == GDK_Right)
keyval = CURSOR_RIGHT; keyval = shift | ctrl | CURSOR_RIGHT;
else if (event->keyval == GDK_KP_Right || event->keyval == GDK_KP_6) else if (event->keyval == GDK_KP_Right || event->keyval == GDK_KP_6)
keyval = MOD_NUM_KEYPAD | '6'; keyval = MOD_NUM_KEYPAD | '6';
else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7) else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7)

253
net.c
View File

@ -57,6 +57,13 @@
#define ROTATE_TIME 0.13F #define ROTATE_TIME 0.13F
#define FLASH_FRAME 0.07F #define FLASH_FRAME 0.07F
/* Transform physical coords to game coords using game_drawstate ds */
#define GX(x) (((x) + ds->org_x) % ds->width)
#define GY(y) (((y) + ds->org_y) % ds->height)
/* ...and game coords to physical coords */
#define RX(x) (((x) + ds->width - ds->org_x) % ds->width)
#define RY(y) (((y) + ds->height - ds->org_y) % ds->height)
enum { enum {
COL_BACKGROUND, COL_BACKGROUND,
COL_LOCKED, COL_LOCKED,
@ -82,7 +89,7 @@ struct game_aux_info {
}; };
struct game_state { struct game_state {
int width, height, cx, cy, wrapping, completed; int width, height, wrapping, completed;
int last_rotate_x, last_rotate_y, last_rotate_dir; int last_rotate_x, last_rotate_y, last_rotate_dir;
int used_solve, just_used_solve; int used_solve, just_used_solve;
unsigned char *tiles; unsigned char *tiles;
@ -1570,8 +1577,6 @@ static game_state *new_game(game_params *params, char *desc)
state = snew(game_state); state = snew(game_state);
w = state->width = params->width; w = state->width = params->width;
h = state->height = params->height; h = state->height = params->height;
state->cx = state->width / 2;
state->cy = state->height / 2;
state->wrapping = params->wrapping; state->wrapping = params->wrapping;
state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0; state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
state->completed = state->used_solve = state->just_used_solve = FALSE; state->completed = state->used_solve = state->just_used_solve = FALSE;
@ -1623,6 +1628,22 @@ static game_state *new_game(game_params *params, char *desc)
barrier(state, 0, y) |= L; barrier(state, 0, y) |= L;
barrier(state, state->width-1, y) |= R; barrier(state, state->width-1, y) |= R;
} }
} else {
/*
* We check whether this is de-facto a non-wrapping game
* despite the parameters, in case we were passed the
* description of a non-wrapping game. This is so that we
* can change some aspects of the UI behaviour.
*/
state->wrapping = FALSE;
for (x = 0; x < state->width; x++)
if (!(barrier(state, x, 0) & U) ||
!(barrier(state, x, state->height-1) & D))
state->wrapping = TRUE;
for (y = 0; y < state->width; y++)
if (!(barrier(state, 0, y) & L) ||
!(barrier(state, state->width-1, y) & R))
state->wrapping = TRUE;
} }
/* /*
@ -1644,29 +1665,19 @@ static game_state *new_game(game_params *params, char *desc)
if (barrier(state, x, y) & dir2) if (barrier(state, x, y) & dir2)
corner = TRUE; corner = TRUE;
x1 = x + X(dir), y1 = y + Y(dir); OFFSET(x1, y1, x, y, dir, state);
if (x1 >= 0 && x1 < state->width && if (barrier(state, x1, y1) & dir2)
y1 >= 0 && y1 < state->height &&
(barrier(state, x1, y1) & dir2))
corner = TRUE; corner = TRUE;
x2 = x + X(dir2), y2 = y + Y(dir2); OFFSET(x2, y2, x, y, dir2, state);
if (x2 >= 0 && x2 < state->width && if (barrier(state, x2, y2) & dir)
y2 >= 0 && y2 < state->height &&
(barrier(state, x2, y2) & dir))
corner = TRUE; corner = TRUE;
if (corner) { if (corner) {
barrier(state, x, y) |= (dir << 4); barrier(state, x, y) |= (dir << 4);
if (x1 >= 0 && x1 < state->width &&
y1 >= 0 && y1 < state->height)
barrier(state, x1, y1) |= (A(dir) << 4); barrier(state, x1, y1) |= (A(dir) << 4);
if (x2 >= 0 && x2 < state->width &&
y2 >= 0 && y2 < state->height)
barrier(state, x2, y2) |= (C(dir) << 4); barrier(state, x2, y2) |= (C(dir) << 4);
x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2); OFFSET(x3, y3, x1, y1, dir2, state);
if (x3 >= 0 && x3 < state->width &&
y3 >= 0 && y3 < state->height)
barrier(state, x3, y3) |= (F(dir) << 4); barrier(state, x3, y3) |= (F(dir) << 4);
} }
} }
@ -1683,8 +1694,6 @@ static game_state *dup_game(game_state *state)
ret = snew(game_state); ret = snew(game_state);
ret->width = state->width; ret->width = state->width;
ret->height = state->height; ret->height = state->height;
ret->cx = state->cx;
ret->cy = state->cy;
ret->wrapping = state->wrapping; ret->wrapping = state->wrapping;
ret->completed = state->completed; ret->completed = state->completed;
ret->used_solve = state->used_solve; ret->used_solve = state->used_solve;
@ -1748,7 +1757,7 @@ static char *game_text_format(game_state *state)
* completed - just call this function and see whether every square * completed - just call this function and see whether every square
* is marked active. * is marked active.
*/ */
static unsigned char *compute_active(game_state *state) static unsigned char *compute_active(game_state *state, int cx, int cy)
{ {
unsigned char *active; unsigned char *active;
tree234 *todo; tree234 *todo;
@ -1762,8 +1771,8 @@ static unsigned char *compute_active(game_state *state)
* xyd_cmp and just store direction 0 every time. * xyd_cmp and just store direction 0 every time.
*/ */
todo = newtree234(xyd_cmp_nc); todo = newtree234(xyd_cmp_nc);
index(state, active, state->cx, state->cy) = ACTIVE; index(state, active, cx, cy) = ACTIVE;
add234(todo, new_xyd(state->cx, state->cy, 0)); add234(todo, new_xyd(cx, cy, 0));
while ( (xyd = delpos234(todo, 0)) != NULL) { while ( (xyd = delpos234(todo, 0)) != NULL) {
int x1, y1, d1, x2, y2, d2; int x1, y1, d1, x2, y2, d2;
@ -1799,6 +1808,8 @@ static unsigned char *compute_active(game_state *state)
} }
struct game_ui { struct game_ui {
int org_x, org_y; /* origin */
int cx, cy; /* source tile (game coordinates) */
int cur_x, cur_y; int cur_x, cur_y;
int cur_visible; int cur_visible;
random_state *rs; /* used for jumbling */ random_state *rs; /* used for jumbling */
@ -1809,8 +1820,9 @@ static game_ui *new_ui(game_state *state)
void *seed; void *seed;
int seedsize; int seedsize;
game_ui *ui = snew(game_ui); game_ui *ui = snew(game_ui);
ui->cur_x = state->width / 2; ui->org_x = ui->org_y = 0;
ui->cur_y = state->height / 2; ui->cur_x = ui->cx = state->width / 2;
ui->cur_y = ui->cy = state->height / 2;
ui->cur_visible = FALSE; ui->cur_visible = FALSE;
get_random_seed(&seed, &seedsize); get_random_seed(&seed, &seedsize);
ui->rs = random_init(seed, seedsize); ui->rs = random_init(seed, seedsize);
@ -1833,7 +1845,9 @@ static game_state *make_move(game_state *state, game_ui *ui,
{ {
game_state *ret, *nullret; game_state *ret, *nullret;
int tx, ty, orig; int tx, ty, orig;
int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
button &= ~MOD_MASK;
nullret = NULL; nullret = NULL;
if (button == LEFT_BUTTON || if (button == LEFT_BUTTON ||
@ -1856,22 +1870,43 @@ static game_state *make_move(game_state *state, game_ui *ui,
ty = y / TILE_SIZE; ty = y / TILE_SIZE;
if (tx >= state->width || ty >= state->height) if (tx >= state->width || ty >= state->height)
return nullret; return nullret;
/* Transform from physical to game coords */
tx = (tx + ui->org_x) % state->width;
ty = (ty + ui->org_y) % state->height;
if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER || if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
return nullret; return nullret;
} else if (button == CURSOR_UP || button == CURSOR_DOWN || } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
button == CURSOR_RIGHT || button == CURSOR_LEFT) { button == CURSOR_RIGHT || button == CURSOR_LEFT) {
if (button == CURSOR_UP && ui->cur_y > 0) int dir;
ui->cur_y--; switch (button) {
else if (button == CURSOR_DOWN && ui->cur_y < state->height-1) case CURSOR_UP: dir = U; break;
ui->cur_y++; case CURSOR_DOWN: dir = D; break;
else if (button == CURSOR_LEFT && ui->cur_x > 0) case CURSOR_LEFT: dir = L; break;
ui->cur_x--; case CURSOR_RIGHT: dir = R; break;
else if (button == CURSOR_RIGHT && ui->cur_x < state->width-1) default: return nullret;
ui->cur_x++; }
else if (shift) {
return nullret; /* no cursor movement */ /*
* Move origin.
*/
if (state->wrapping) {
OFFSET(ui->org_x, ui->org_y, ui->org_x, ui->org_y, dir, state);
} else return nullret; /* disallowed for non-wrapping grids */
}
if (ctrl) {
/*
* Change source tile.
*/
OFFSET(ui->cx, ui->cy, ui->cx, ui->cy, dir, state);
}
if (!shift && !ctrl) {
/*
* Move keyboard cursor.
*/
OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
ui->cur_visible = TRUE; ui->cur_visible = TRUE;
}
return state; /* UI activity has occurred */ return state; /* UI activity has occurred */
} else if (button == 'a' || button == 's' || button == 'd' || } else if (button == 'a' || button == 's' || button == 'd' ||
button == 'A' || button == 'S' || button == 'D') { button == 'A' || button == 'S' || button == 'D') {
@ -1961,7 +1996,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
* Check whether the game has been completed. * Check whether the game has been completed.
*/ */
{ {
unsigned char *active = compute_active(ret); unsigned char *active = compute_active(ret, ui->cx, ui->cy);
int x1, y1; int x1, y1;
int complete = TRUE; int complete = TRUE;
@ -1989,6 +2024,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
struct game_drawstate { struct game_drawstate {
int started; int started;
int width, height; int width, height;
int org_x, org_y;
unsigned char *visible; unsigned char *visible;
}; };
@ -1999,6 +2035,7 @@ static game_drawstate *game_new_drawstate(game_state *state)
ds->started = FALSE; ds->started = FALSE;
ds->width = state->width; ds->width = state->width;
ds->height = state->height; ds->height = state->height;
ds->org_x = ds->org_y = -1;
ds->visible = snewn(state->width * state->height, unsigned char); ds->visible = snewn(state->width * state->height, unsigned char);
memset(ds->visible, 0xFF, state->width * state->height); memset(ds->visible, 0xFF, state->width * state->height);
@ -2096,7 +2133,11 @@ static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
draw_rect(fe, mx, my, dx, dy, colour); draw_rect(fe, mx, my, dx, dy, colour);
} }
static void draw_barrier_corner(frontend *fe, int x, int y, int dir, int phase) /*
* draw_barrier_corner() and draw_barrier() are passed physical coords
*/
static void draw_barrier_corner(frontend *fe, int x, int y, int dir, int phase,
int barrier)
{ {
int bx = WINDOW_OFFSET + TILE_SIZE * x; int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y; int by = WINDOW_OFFSET + TILE_SIZE * y;
@ -2113,18 +2154,19 @@ static void draw_barrier_corner(frontend *fe, int x, int y, int dir, int phase)
if (phase == 0) { if (phase == 0) {
draw_rect_coords(fe, bx+x1, by+y1, draw_rect_coords(fe, bx+x1, by+y1,
bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy, bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
COL_WIRE); barrier ? COL_WIRE : COL_BACKGROUND);
draw_rect_coords(fe, bx+x1, by+y1, draw_rect_coords(fe, bx+x1, by+y1,
bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy, bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
COL_WIRE); barrier ? COL_WIRE : COL_BACKGROUND);
} else { } else {
draw_rect_coords(fe, bx+x1, by+y1, draw_rect_coords(fe, bx+x1, by+y1,
bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy, bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
COL_BARRIER); barrier ? COL_BARRIER : COL_BORDER);
} }
} }
static void draw_barrier(frontend *fe, int x, int y, int dir, int phase) static void draw_barrier(frontend *fe, int x, int y, int dir, int phase,
int barrier)
{ {
int bx = WINDOW_OFFSET + TILE_SIZE * x; int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y; int by = WINDOW_OFFSET + TILE_SIZE * y;
@ -2136,14 +2178,19 @@ static void draw_barrier(frontend *fe, int x, int y, int dir, int phase)
h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER); h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
if (phase == 0) { if (phase == 0) {
draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE); draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h,
barrier ? COL_WIRE : COL_BACKGROUND);
} else { } else {
draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER); draw_rect(fe, bx+x1, by+y1, w, h,
barrier ? COL_BARRIER : COL_BORDER);
} }
} }
static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile, /*
float angle, int cursor) * draw_tile() is passed physical coordinates
*/
static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
int x, int y, int tile, int src, float angle, int cursor)
{ {
int bx = WINDOW_OFFSET + TILE_SIZE * x; int bx = WINDOW_OFFSET + TILE_SIZE * x;
int by = WINDOW_OFFSET + TILE_SIZE * y; int by = WINDOW_OFFSET + TILE_SIZE * y;
@ -2238,7 +2285,7 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
* otherwise not at all. * otherwise not at all.
*/ */
col = -1; col = -1;
if (x == state->cx && y == state->cy) if (src)
col = COL_WIRE; col = COL_WIRE;
else if (COUNT(tile) == 1) { else if (COUNT(tile) == 1) {
col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT); col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
@ -2279,7 +2326,7 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height) if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
continue; continue;
if (!(tile(state, ox, oy) & F(dir))) if (!(tile(state, GX(ox), GY(oy)) & F(dir)))
continue; continue;
px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx); px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
@ -2315,11 +2362,11 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
*/ */
for (phase = 0; phase < 2; phase++) { for (phase = 0; phase < 2; phase++) {
for (dir = 1; dir < 0x10; dir <<= 1) for (dir = 1; dir < 0x10; dir <<= 1)
if (barrier(state, x, y) & (dir << 4)) if (barrier(state, GX(x), GY(y)) & (dir << 4))
draw_barrier_corner(fe, x, y, dir << 4, phase); draw_barrier_corner(fe, x, y, dir << 4, phase, TRUE);
for (dir = 1; dir < 0x10; dir <<= 1) for (dir = 1; dir < 0x10; dir <<= 1)
if (barrier(state, x, y) & dir) if (barrier(state, GX(x), GY(y)) & dir)
draw_barrier(fe, x, y, dir, phase); draw_barrier(fe, x, y, dir, phase, TRUE);
} }
draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
@ -2328,23 +2375,34 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y, int tile,
static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
game_state *state, int dir, game_ui *ui, float t, float ft) game_state *state, int dir, game_ui *ui, float t, float ft)
{ {
int x, y, tx, ty, frame, last_rotate_dir; int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
unsigned char *active; unsigned char *active;
float angle = 0.0; float angle = 0.0;
/* /*
* Clear the screen and draw the exterior barrier lines if this * Clear the screen if this is our first call.
* is our first call.
*/ */
if (!ds->started) { if (!ds->started) {
int phase;
ds->started = TRUE; ds->started = TRUE;
draw_rect(fe, 0, 0, draw_rect(fe, 0, 0,
WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER, WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER, WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
COL_BACKGROUND); COL_BACKGROUND);
}
/*
* If the origin has changed, we need to redraw the exterior
* barrier lines.
*/
if (ui->org_x != ds->org_x || ui->org_y != ds->org_y) {
int phase;
ds->org_x = ui->org_x;
ds->org_y = ui->org_y;
moved_origin = TRUE;
draw_update(fe, 0, 0, draw_update(fe, 0, 0,
WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER, WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER); WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
@ -2352,33 +2410,25 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
for (phase = 0; phase < 2; phase++) { for (phase = 0; phase < 2; phase++) {
for (x = 0; x < ds->width; x++) { for (x = 0; x < ds->width; x++) {
if (barrier(state, x, 0) & UL) int ub = barrier(state, GX(x), GY(0));
draw_barrier_corner(fe, x, -1, LD, phase); int db = barrier(state, GX(x), GY(ds->height-1));
if (barrier(state, x, 0) & RU) draw_barrier_corner(fe, x, -1, LD, phase, ub & UL);
draw_barrier_corner(fe, x, -1, DR, phase); draw_barrier_corner(fe, x, -1, DR, phase, ub & RU);
if (barrier(state, x, 0) & U) draw_barrier(fe, x, -1, D, phase, ub & U);
draw_barrier(fe, x, -1, D, phase); draw_barrier_corner(fe, x, ds->height, RU, phase, db & DR);
if (barrier(state, x, ds->height-1) & DR) draw_barrier_corner(fe, x, ds->height, UL, phase, db & LD);
draw_barrier_corner(fe, x, ds->height, RU, phase); draw_barrier(fe, x, ds->height, U, phase, db & D);
if (barrier(state, x, ds->height-1) & LD)
draw_barrier_corner(fe, x, ds->height, UL, phase);
if (barrier(state, x, ds->height-1) & D)
draw_barrier(fe, x, ds->height, U, phase);
} }
for (y = 0; y < ds->height; y++) { for (y = 0; y < ds->height; y++) {
if (barrier(state, 0, y) & UL) int lb = barrier(state, GX(0), GY(y));
draw_barrier_corner(fe, -1, y, RU, phase); int rb = barrier(state, GX(ds->width-1), GY(y));
if (barrier(state, 0, y) & LD) draw_barrier_corner(fe, -1, y, RU, phase, lb & UL);
draw_barrier_corner(fe, -1, y, DR, phase); draw_barrier_corner(fe, -1, y, DR, phase, lb & LD);
if (barrier(state, 0, y) & L) draw_barrier(fe, -1, y, R, phase, lb & L);
draw_barrier(fe, -1, y, R, phase); draw_barrier_corner(fe, ds->width, y, UL, phase, rb & RU);
if (barrier(state, ds->width-1, y) & RU) draw_barrier_corner(fe, ds->width, y, LD, phase, rb & DR);
draw_barrier_corner(fe, ds->width, y, UL, phase); draw_barrier(fe, ds->width, y, L, phase, rb & R);
if (barrier(state, ds->width-1, y) & DR)
draw_barrier_corner(fe, ds->width, y, LD, phase);
if (barrier(state, ds->width-1, y) & R)
draw_barrier(fe, ds->width, y, L, phase);
} }
} }
} }
@ -2409,11 +2459,16 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
/* /*
* Draw any tile which differs from the way it was last drawn. * Draw any tile which differs from the way it was last drawn.
*/ */
active = compute_active(state); active = compute_active(state, ui->cx, ui->cy);
for (x = 0; x < ds->width; x++) for (x = 0; x < ds->width; x++)
for (y = 0; y < ds->height; y++) { for (y = 0; y < ds->height; y++) {
unsigned char c = tile(state, x, y) | index(state, active, x, y); unsigned char c = tile(state, GX(x), GY(y)) |
index(state, active, GX(x), GY(y));
int is_src = GX(x) == ui->cx && GY(y) == ui->cy;
int is_anim = GX(x) == tx && GY(y) == ty;
int is_cursor = ui->cur_visible &&
GX(x) == ui->cur_x && GY(y) == ui->cur_y;
/* /*
* In a completion flash, we adjust the LOCKED bit * In a completion flash, we adjust the LOCKED bit
@ -2421,9 +2476,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
* the frame number. * the frame number.
*/ */
if (frame >= 0) { if (frame >= 0) {
int rcx = RX(ui->cx), rcy = RY(ui->cy);
int xdist, ydist, dist; int xdist, ydist, dist;
xdist = (x < state->cx ? state->cx - x : x - state->cx); xdist = (x < rcx ? rcx - x : x - rcx);
ydist = (y < state->cy ? state->cy - y : y - state->cy); ydist = (y < rcy ? rcy - y : y - rcy);
dist = (xdist > ydist ? xdist : ydist); dist = (xdist > ydist ? xdist : ydist);
if (frame >= dist && frame < dist+4) { if (frame >= dist && frame < dist+4) {
@ -2433,15 +2489,13 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
} }
} }
if (index(state, ds->visible, x, y) != c || if (moved_origin ||
index(state, ds->visible, x, y) != c ||
index(state, ds->visible, x, y) == 0xFF || index(state, ds->visible, x, y) == 0xFF ||
(x == tx && y == ty) || is_src || is_anim || is_cursor) {
(ui->cur_visible && x == ui->cur_x && y == ui->cur_y)) { draw_tile(fe, state, ds, x, y, c,
draw_tile(fe, state, x, y, c, is_src, (is_anim ? angle : 0.0F), is_cursor);
(x == tx && y == ty ? angle : 0.0F), if (is_src || is_anim || is_cursor)
(ui->cur_visible && x == ui->cur_x && y == ui->cur_y));
if ((x == tx && y == ty) ||
(ui->cur_visible && x == ui->cur_x && y == ui->cur_y))
index(state, ds->visible, x, y) = 0xFF; index(state, ds->visible, x, y) = 0xFF;
else else
index(state, ds->visible, x, y) = c; index(state, ds->visible, x, y) = c;
@ -2505,16 +2559,11 @@ static float game_flash_length(game_state *oldstate,
*/ */
if (!oldstate->completed && newstate->completed && if (!oldstate->completed && newstate->completed &&
!oldstate->used_solve && !newstate->used_solve) { !oldstate->used_solve && !newstate->used_solve) {
int size; int size = 0;
size = 0; if (size < newstate->width)
if (size < newstate->cx+1) size = newstate->width;
size = newstate->cx+1; if (size < newstate->height)
if (size < newstate->cy+1) size = newstate->height;
size = newstate->cy+1;
if (size < newstate->width - newstate->cx)
size = newstate->width - newstate->cx;
if (size < newstate->height - newstate->cy)
size = newstate->height - newstate->cy;
return FLASH_FRAME * (size+4); return FLASH_FRAME * (size+4);
} }

View File

@ -1006,6 +1006,8 @@ static game_state *make_move(game_state *state, game_ui *ui,
int n, dx, dy; int n, dx, dy;
game_state *ret; game_state *ret;
button &= ~MOD_MASK;
if (button != LEFT_BUTTON && button != RIGHT_BUTTON) if (button != LEFT_BUTTON && button != RIGHT_BUTTON)
return NULL; return NULL;

View File

@ -769,6 +769,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
{ {
game_state *ret; game_state *ret;
button &= ~MOD_MASK;
x = FROMCOORD(from->w, x); x = FROMCOORD(from->w, x);
y = FROMCOORD(from->h, y); y = FROMCOORD(from->h, y);

View File

@ -320,6 +320,21 @@ controls are:
also unlock it again, but while it's locked you can't accidentally also unlock it again, but while it's locked you can't accidentally
turn it. turn it.
The following controls are not necessary to complete the game, but may
be useful:
\dt \e{Shift grid}: Shift + arrow keys
\dd On grids that wrap, you can move the origin of the grid, so that
tiles that were on opposite sides of the grid can be seen together.
\dt \e{Move centre}: Ctrl + arrow keys
\dd You can change which tile is used as the source of highlighting.
(It doesn't ultimately matter which tile this is, as every tile will
be connected to every other tile in a correct solution, but it may be
helpful in the intermediate stages of solving the puzzle.)
\dt \e{Jumble tiles}: \q{J} key \dt \e{Jumble tiles}: \q{J} key
\dd This key turns all tiles that are not locked to random \dd This key turns all tiles that are not locked to random

View File

@ -32,7 +32,10 @@ enum {
CURSOR_LEFT, CURSOR_LEFT,
CURSOR_RIGHT, CURSOR_RIGHT,
MOD_NUM_KEYPAD = 0x40000000 MOD_CTRL = 0x10000000,
MOD_SHFT = 0x20000000,
MOD_NUM_KEYPAD = 0x40000000,
MOD_MASK = 0x70000000 /* mask for all modifiers */
}; };
#define IS_MOUSE_DOWN(m) ( (unsigned)((m) - LEFT_BUTTON) <= \ #define IS_MOUSE_DOWN(m) ( (unsigned)((m) - LEFT_BUTTON) <= \

2
rect.c
View File

@ -2127,6 +2127,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
int startdrag = FALSE, enddrag = FALSE, active = FALSE; int startdrag = FALSE, enddrag = FALSE, active = FALSE;
game_state *ret; game_state *ret;
button &= ~MOD_MASK;
if (button == LEFT_BUTTON) { if (button == LEFT_BUTTON) {
startdrag = TRUE; startdrag = TRUE;
} else if (button == LEFT_RELEASE) { } else if (button == LEFT_RELEASE) {

View File

@ -572,6 +572,7 @@ static game_state *make_move(game_state *from, game_ui *ui,
int dx, dy, tx, ty, n; int dx, dy, tx, ty, n;
game_state *ret; game_state *ret;
button &= ~MOD_MASK;
if (button != LEFT_BUTTON && button != RIGHT_BUTTON) if (button != LEFT_BUTTON && button != RIGHT_BUTTON)
return NULL; return NULL;

2
solo.c
View File

@ -1828,7 +1828,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
int tx, ty; int tx, ty;
game_state *ret; game_state *ret;
button &= ~MOD_NUM_KEYPAD; /* we treat this the same as normal */ button &= ~MOD_MASK;
tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1; tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1; ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;

View File

@ -594,6 +594,8 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
game_state *ret; game_state *ret;
int dir; int dir;
button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
/* /*
* Determine the coordinates of the click. We offset by n-1 * Determine the coordinates of the click. We offset by n-1

View File

@ -1221,31 +1221,35 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
case WM_KEYDOWN: case WM_KEYDOWN:
{ {
int key = -1; int key = -1;
BYTE keystate[256];
int r = GetKeyboardState(keystate);
int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;
switch (wParam) { switch (wParam) {
case VK_LEFT: case VK_LEFT:
if (!(lParam & 0x01000000)) if (!(lParam & 0x01000000))
key = MOD_NUM_KEYPAD | '4'; key = MOD_NUM_KEYPAD | '4';
else else
key = CURSOR_LEFT; key = shift | ctrl | CURSOR_LEFT;
break; break;
case VK_RIGHT: case VK_RIGHT:
if (!(lParam & 0x01000000)) if (!(lParam & 0x01000000))
key = MOD_NUM_KEYPAD | '6'; key = MOD_NUM_KEYPAD | '6';
else else
key = CURSOR_RIGHT; key = shift | ctrl | CURSOR_RIGHT;
break; break;
case VK_UP: case VK_UP:
if (!(lParam & 0x01000000)) if (!(lParam & 0x01000000))
key = MOD_NUM_KEYPAD | '8'; key = MOD_NUM_KEYPAD | '8';
else else
key = CURSOR_UP; key = shift | ctrl | CURSOR_UP;
break; break;
case VK_DOWN: case VK_DOWN:
if (!(lParam & 0x01000000)) if (!(lParam & 0x01000000))
key = MOD_NUM_KEYPAD | '2'; key = MOD_NUM_KEYPAD | '2';
else else
key = CURSOR_DOWN; key = shift | ctrl | CURSOR_DOWN;
break; break;
/* /*
* Diagonal keys on the numeric keypad. * Diagonal keys on the numeric keypad.