chiark / gitweb /
Add origin-shifting (Shift+cursors) and source-shifting (Ctrl+cursors) to Net.
authorJacob Nevins <jacobn@chiark.greenend.org.uk>
Thu, 26 May 2005 13:40:38 +0000 (13:40 +0000)
committerJacob Nevins <jacobn@chiark.greenend.org.uk>
Thu, 26 May 2005 13:40:38 +0000 (13:40 +0000)
(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]

13 files changed:
cube.c
fifteen.c
gtk.c
net.c
netslide.c
pattern.c
puzzles.but
puzzles.h
rect.c
sixteen.c
solo.c
twiddle.c
windows.c

diff --git a/cube.c b/cube.c
index 12e3eced8d224415c255920a8b9a5fd4ce59827a..ddf55271c64838a353abec7868a9a0fc2372cef2 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -1013,6 +1013,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
     int i, j, dest, mask;
     struct solid *poly;
 
+    button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
+
     /*
      * All moves are made with the cursor keys or numeric keypad.
      */
index 9d138b15a6c09d340bf343e2b5f22296b7467391..700c26c44a7e0fa1d2d9e2cc9b93cf6c87a466ff 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -456,6 +456,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
     int gx, gy, dx, dy, ux, uy, up, p;
     game_state *ret;
 
+    button &= ~MOD_MASK;
+
     gx = X(from, from->gap_pos);
     gy = Y(from, from->gap_pos);
 
diff --git a/gtk.c b/gtk.c
index ab10a11bc6e0a951a886407d8ee4b621c8b8bc6a..3fdfc87927208089fde20e7cff79dbabf9614a20 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -317,24 +317,26 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
     frontend *fe = (frontend *)data;
     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)
         return TRUE;
 
     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)
        keyval = MOD_NUM_KEYPAD | '8';
     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)
        keyval = MOD_NUM_KEYPAD | '2';
     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)
        keyval = MOD_NUM_KEYPAD | '4';
     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)
        keyval = MOD_NUM_KEYPAD | '6';
     else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7)
diff --git a/net.c b/net.c
index db8c3c449934124101c8e16440748976b68fc207..59effe6d56f7a996be75bcd31ebec15bb36254fa 100644 (file)
--- a/net.c
+++ b/net.c
 #define ROTATE_TIME 0.13F
 #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 {
     COL_BACKGROUND,
     COL_LOCKED,
@@ -82,7 +89,7 @@ struct game_aux_info {
 };
 
 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 used_solve, just_used_solve;
     unsigned char *tiles;
@@ -1570,8 +1577,6 @@ static game_state *new_game(game_params *params, char *desc)
     state = snew(game_state);
     w = state->width = params->width;
     h = state->height = params->height;
-    state->cx = state->width / 2;
-    state->cy = state->height / 2;
     state->wrapping = params->wrapping;
     state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
     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, 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,30 +1665,20 @@ static game_state *new_game(game_params *params, char *desc)
                 if (barrier(state, x, y) & dir2)
                     corner = TRUE;
 
-                x1 = x + X(dir), y1 = y + Y(dir);
-                if (x1 >= 0 && x1 < state->width &&
-                    y1 >= 0 && y1 < state->height &&
-                    (barrier(state, x1, y1) & dir2))
+                OFFSET(x1, y1, x, y, dir, state);
+                if (barrier(state, x1, y1) & dir2)
                     corner = TRUE;
 
-                x2 = x + X(dir2), y2 = y + Y(dir2);
-                if (x2 >= 0 && x2 < state->width &&
-                    y2 >= 0 && y2 < state->height &&
-                    (barrier(state, x2, y2) & dir))
+                OFFSET(x2, y2, x, y, dir2, state);
+                if (barrier(state, x2, y2) & dir)
                     corner = TRUE;
 
                 if (corner) {
                     barrier(state, x, y) |= (dir << 4);
-                    if (x1 >= 0 && x1 < state->width &&
-                        y1 >= 0 && y1 < state->height)
-                        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);
-                    x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2);
-                    if (x3 >= 0 && x3 < state->width &&
-                        y3 >= 0 && y3 < state->height)
-                        barrier(state, x3, y3) |= (F(dir) << 4);
+                    barrier(state, x1, y1) |= (A(dir) << 4);
+                    barrier(state, x2, y2) |= (C(dir) << 4);
+                    OFFSET(x3, y3, x1, y1, dir2, state);
+                    barrier(state, x3, y3) |= (F(dir) << 4);
                 }
             }
        }
@@ -1683,8 +1694,6 @@ static game_state *dup_game(game_state *state)
     ret = snew(game_state);
     ret->width = state->width;
     ret->height = state->height;
-    ret->cx = state->cx;
-    ret->cy = state->cy;
     ret->wrapping = state->wrapping;
     ret->completed = state->completed;
     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
  * 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;
     tree234 *todo;
@@ -1762,8 +1771,8 @@ static unsigned char *compute_active(game_state *state)
      * xyd_cmp and just store direction 0 every time.
      */
     todo = newtree234(xyd_cmp_nc);
-    index(state, active, state->cx, state->cy) = ACTIVE;
-    add234(todo, new_xyd(state->cx, state->cy, 0));
+    index(state, active, cx, cy) = ACTIVE;
+    add234(todo, new_xyd(cx, cy, 0));
 
     while ( (xyd = delpos234(todo, 0)) != NULL) {
        int x1, y1, d1, x2, y2, d2;
@@ -1799,6 +1808,8 @@ static unsigned char *compute_active(game_state *state)
 }
 
 struct game_ui {
+    int org_x, org_y; /* origin */
+    int cx, cy;       /* source tile (game coordinates) */
     int cur_x, cur_y;
     int cur_visible;
     random_state *rs; /* used for jumbling */
@@ -1809,8 +1820,9 @@ static game_ui *new_ui(game_state *state)
     void *seed;
     int seedsize;
     game_ui *ui = snew(game_ui);
-    ui->cur_x = state->width / 2;
-    ui->cur_y = state->height / 2;
+    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 = FALSE;
     get_random_seed(&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;
     int tx, ty, orig;
+    int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
 
+    button &= ~MOD_MASK;
     nullret = NULL;
 
     if (button == LEFT_BUTTON ||
@@ -1856,23 +1870,44 @@ static game_state *make_move(game_state *state, game_ui *ui,
        ty = y / TILE_SIZE;
        if (tx >= state->width || ty >= state->height)
            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 ||
            y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
            return nullret;
     } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
               button == CURSOR_RIGHT || button == CURSOR_LEFT) {
-       if (button == CURSOR_UP && ui->cur_y > 0)
-           ui->cur_y--;
-       else if (button == CURSOR_DOWN && ui->cur_y < state->height-1)
-           ui->cur_y++;
-       else if (button == CURSOR_LEFT && ui->cur_x > 0)
-           ui->cur_x--;
-       else if (button == CURSOR_RIGHT && ui->cur_x < state->width-1)
-           ui->cur_x++;
-       else
-           return nullret;            /* no cursor movement */
-       ui->cur_visible = TRUE;
-       return state;                  /* UI activity has occurred */
+        int dir;
+        switch (button) {
+          case CURSOR_UP:       dir = U; break;
+          case CURSOR_DOWN:     dir = D; break;
+          case CURSOR_LEFT:     dir = L; break;
+          case CURSOR_RIGHT:    dir = R; break;
+          default:              return nullret;
+        }
+        if (shift) {
+            /*
+             * 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;
+        }
+        return state;                 /* UI activity has occurred */
     } else if (button == 'a' || button == 's' || button == 'd' ||
               button == 'A' || button == 'S' || button == 'D') {
        tx = ui->cur_x;
@@ -1961,7 +1996,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
      * 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 complete = TRUE;
 
@@ -1989,6 +2024,7 @@ static game_state *make_move(game_state *state, game_ui *ui,
 struct game_drawstate {
     int started;
     int width, height;
+    int org_x, org_y;
     unsigned char *visible;
 };
 
@@ -1999,6 +2035,7 @@ static game_drawstate *game_new_drawstate(game_state *state)
     ds->started = FALSE;
     ds->width = state->width;
     ds->height = state->height;
+    ds->org_x = ds->org_y = -1;
     ds->visible = snewn(state->width * state->height, unsigned char);
     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);
 }
 
-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 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) {
         draw_rect_coords(fe, bx+x1, by+y1,
                          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,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
-                         COL_WIRE);
+                         barrier ? COL_WIRE : COL_BACKGROUND);
     } else {
         draw_rect_coords(fe, bx+x1, by+y1,
                          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 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);
 
     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 {
-        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 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.
      */
     col = -1;
-    if (x == state->cx && y == state->cy)
+    if (src)
         col = COL_WIRE;
     else if (COUNT(tile) == 1) {
         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)
             continue;
 
-        if (!(tile(state, ox, oy) & F(dir)))
+        if (!(tile(state, GX(ox), GY(oy)) & F(dir)))
             continue;
 
         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 (dir = 1; dir < 0x10; dir <<= 1)
-            if (barrier(state, x, y) & (dir << 4))
-                draw_barrier_corner(fe, x, y, dir << 4, phase);
+            if (barrier(state, GX(x), GY(y)) & (dir << 4))
+                draw_barrier_corner(fe, x, y, dir << 4, phase, TRUE);
         for (dir = 1; dir < 0x10; dir <<= 1)
-            if (barrier(state, x, y) & dir)
-                draw_barrier(fe, x, y, dir, phase);
+            if (barrier(state, GX(x), GY(y)) & dir)
+                draw_barrier(fe, x, y, dir, phase, TRUE);
     }
 
     draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
@@ -2328,57 +2375,60 @@ 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,
                  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;
     float angle = 0.0;
 
     /*
-     * Clear the screen and draw the exterior barrier lines if this
-     * is our first call.
+     * Clear the screen if this is our first call.
      */
     if (!ds->started) {
-        int phase;
-
         ds->started = TRUE;
 
         draw_rect(fe, 0, 0, 
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
                   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, 
                     WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
                     WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
-
+        
         for (phase = 0; phase < 2; phase++) {
 
             for (x = 0; x < ds->width; x++) {
-                if (barrier(state, x, 0) & UL)
-                    draw_barrier_corner(fe, x, -1, LD, phase);
-                if (barrier(state, x, 0) & RU)
-                    draw_barrier_corner(fe, x, -1, DR, phase);
-                if (barrier(state, x, 0) & U)
-                    draw_barrier(fe, x, -1, D, phase);
-                if (barrier(state, x, ds->height-1) & DR)
-                    draw_barrier_corner(fe, x, ds->height, RU, phase);
-                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);
+                int ub = barrier(state, GX(x), GY(0));
+                int db = barrier(state, GX(x), GY(ds->height-1));
+                draw_barrier_corner(fe, x, -1, LD, phase, ub & UL);
+                draw_barrier_corner(fe, x, -1, DR, phase, ub & RU);
+                draw_barrier(fe, x, -1, D, phase, ub & U);
+                draw_barrier_corner(fe, x, ds->height, RU, phase, db & DR);
+                draw_barrier_corner(fe, x, ds->height, UL, phase, db & LD);
+                draw_barrier(fe, x, ds->height, U, phase, db & D);
             }
 
             for (y = 0; y < ds->height; y++) {
-                if (barrier(state, 0, y) & UL)
-                    draw_barrier_corner(fe, -1, y, RU, phase);
-                if (barrier(state, 0, y) & LD)
-                    draw_barrier_corner(fe, -1, y, DR, phase);
-                if (barrier(state, 0, y) & L)
-                    draw_barrier(fe, -1, y, R, phase);
-                if (barrier(state, ds->width-1, y) & RU)
-                    draw_barrier_corner(fe, ds->width, y, UL, phase);
-                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);
+                int lb = barrier(state, GX(0), GY(y));
+                int rb = barrier(state, GX(ds->width-1), GY(y));
+                draw_barrier_corner(fe, -1, y, RU, phase, lb & UL);
+                draw_barrier_corner(fe, -1, y, DR, phase, lb & LD);
+                draw_barrier(fe, -1, y, R, phase, lb & L);
+                draw_barrier_corner(fe, ds->width, y, UL, phase, rb & RU);
+                draw_barrier_corner(fe, ds->width, y, LD, phase, rb & DR);
+                draw_barrier(fe, ds->width, y, L, phase, rb & R);
             }
         }
     }
@@ -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.
      */
-    active = compute_active(state);
+    active = compute_active(state, ui->cx, ui->cy);
 
     for (x = 0; x < ds->width; x++)
         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
@@ -2421,9 +2476,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
              * the frame number.
              */
             if (frame >= 0) {
+                int rcx = RX(ui->cx), rcy = RY(ui->cy);
                 int xdist, ydist, dist;
-                xdist = (x < state->cx ? state->cx - x : x - state->cx);
-                ydist = (y < state->cy ? state->cy - y : y - state->cy);
+                xdist = (x < rcx ? rcx - x : x - rcx);
+                ydist = (y < rcy ? rcy - y : y - rcy);
                 dist = (xdist > ydist ? xdist : ydist);
 
                 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 ||
-                (x == tx && y == ty) ||
-               (ui->cur_visible && x == ui->cur_x && y == ui->cur_y)) {
-                draw_tile(fe, state, x, y, c,
-                          (x == tx && y == ty ? angle : 0.0F),
-                         (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))
+                is_src || is_anim || is_cursor) {
+                draw_tile(fe, state, ds, x, y, c,
+                          is_src, (is_anim ? angle : 0.0F), is_cursor);
+                if (is_src || is_anim || is_cursor)
                     index(state, ds->visible, x, y) = 0xFF;
                 else
                     index(state, ds->visible, x, y) = c;
@@ -2505,16 +2559,11 @@ static float game_flash_length(game_state *oldstate,
      */
     if (!oldstate->completed && newstate->completed &&
        !oldstate->used_solve && !newstate->used_solve) {
-        int size;
-        size = 0;
-        if (size < newstate->cx+1)
-            size = newstate->cx+1;
-        if (size < newstate->cy+1)
-            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;
+        int size = 0;
+        if (size < newstate->width)
+            size = newstate->width;
+        if (size < newstate->height)
+            size = newstate->height;
         return FLASH_FRAME * (size+4);
     }
 
index b72706a99750b693de0fe93eef6d2bc6a901a26e..6bac447cf0510fa7595cfe1fc7279667ce099a43 100644 (file)
@@ -1006,6 +1006,8 @@ static game_state *make_move(game_state *state, game_ui *ui,
     int n, dx, dy;
     game_state *ret;
 
+    button &= ~MOD_MASK;
+
     if (button != LEFT_BUTTON && button != RIGHT_BUTTON)
         return NULL;
 
index 63b856cb798b7ebca69054266ae368432215dd7b..e09ed3d279054163cedaf40f76ecbc3bd00c136a 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -769,6 +769,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
 {
     game_state *ret;
 
+    button &= ~MOD_MASK;
+
     x = FROMCOORD(from->w, x);
     y = FROMCOORD(from->h, y);
 
index 865aad7798fe46c9ae0edd154c18a5dc99a26231..9635e20551391df55c2e9b3213c6225b7d7efdb2 100644 (file)
@@ -320,6 +320,21 @@ controls are:
 also unlock it again, but while it's locked you can't accidentally
 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
 
 \dd This key turns all tiles that are not locked to random
index a18aefa5473452f5db6e4c5f1ab083b3ff0a8770..ec818d56392b25207059f07ea2af52374512b243 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -32,7 +32,10 @@ enum {
     CURSOR_LEFT,
     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) <= \
diff --git a/rect.c b/rect.c
index 848dcd37428b3fa72c12936b12ce59f893b130ad..76abeed826b757592141024027f34771142d96c5 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -2127,6 +2127,8 @@ static game_state *make_move(game_state *from, game_ui *ui,
     int startdrag = FALSE, enddrag = FALSE, active = FALSE;
     game_state *ret;
 
+    button &= ~MOD_MASK;
+
     if (button == LEFT_BUTTON) {
         startdrag = TRUE;
     } else if (button == LEFT_RELEASE) {
index 966cc0f6a5c8fa5f2f25878eab48b35003483503..2bfaadd050afcd81a94e8a80f245080ebcf0a83f 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -572,6 +572,7 @@ static game_state *make_move(game_state *from, game_ui *ui,
     int dx, dy, tx, ty, n;
     game_state *ret;
 
+    button &= ~MOD_MASK;
     if (button != LEFT_BUTTON && button != RIGHT_BUTTON)
         return NULL;
 
diff --git a/solo.c b/solo.c
index ad6c2f424e20cc7400e3b8d326847472ce3d1e19..6a964ea93f13b077c515daa69e2cb9b7aeb9d934 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -1828,7 +1828,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
     int tx, ty;
     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;
     ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;
index 871bd7fe4b8d8c18ab10daa31defbabc03dbd15e..a681059eb9e255dd734537af8f98a7cc25dd765b 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -594,6 +594,8 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
     game_state *ret;
     int dir;
 
+    button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
+
     if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
        /*
         * Determine the coordinates of the click. We offset by n-1
index baa9998d466cb87e77aeaf48863dc4d6c3638460..383b742cf3a5df3a88ee8773e2bfa0eecf5286a1 100644 (file)
--- a/windows.c
+++ b/windows.c
@@ -1221,31 +1221,35 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_KEYDOWN:
        {
            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) {
              case VK_LEFT:
                if (!(lParam & 0x01000000))
                    key = MOD_NUM_KEYPAD | '4';
-               else
-                   key = CURSOR_LEFT;
+                else
+                   key = shift | ctrl | CURSOR_LEFT;
                break;
              case VK_RIGHT:
                if (!(lParam & 0x01000000))
                    key = MOD_NUM_KEYPAD | '6';
-               else
-                   key = CURSOR_RIGHT;
+                else
+                   key = shift | ctrl | CURSOR_RIGHT;
                break;
              case VK_UP:
                if (!(lParam & 0x01000000))
                    key = MOD_NUM_KEYPAD | '8';
-               else
-                   key = CURSOR_UP;
+                else
+                   key = shift | ctrl | CURSOR_UP;
                break;
              case VK_DOWN:
                if (!(lParam & 0x01000000))
                    key = MOD_NUM_KEYPAD | '2';
-               else
-                   key = CURSOR_DOWN;
+                else
+                   key = shift | ctrl | CURSOR_DOWN;
                break;
                /*
                 * Diagonal keys on the numeric keypad.