Move definition of PI into puzzles.h. If nothing else, the definition in cube.c
[sgt/puzzles] / twiddle.c
index 07a735f..66e8e95 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -20,8 +20,6 @@
 #define COORD(x)  ( (x) * TILE_SIZE + BORDER )
 #define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
 
-#define PI 3.141592653589793238462643383279502884197169399
-
 #define ANIM_PER_RADIUS_UNIT 0.13F
 #define FLASH_FRAME 0.13F
 
@@ -308,7 +306,7 @@ static int grid_complete(int *grid, int wh, int orientable)
 }
 
 static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+                          game_aux_info **aux, int interactive)
 {
     int *grid;
     int w = params->w, h = params->h, n = params->n, wh = w*h;
@@ -333,38 +331,69 @@ static char *new_game_desc(game_params *params, random_state *rs,
      */
     total_moves = params->movetarget;
     if (!total_moves)
+        /* Add a random move to avoid parity issues. */
         total_moves = w*h*n*n*2 + random_upto(rs, 2);
 
     do {
-        int oldx = -1, oldy = -1, oldr = -1;
+        int *prevmoves;
+        int rw, rh;                    /* w/h of rotation centre space */
+
+        rw = w - n + 1;
+        rh = h - n + 1;
+        prevmoves = snewn(rw * rh, int);
+        for (i = 0; i < rw * rh; i++)
+            prevmoves[i] = 0;
 
         for (i = 0; i < total_moves; i++) {
-            int x, y, r;
+            int x, y, r, oldtotal, newtotal, dx, dy;
 
             do {
                 x = random_upto(rs, w - n + 1);
                 y = random_upto(rs, h - n + 1);
-                r = 1 + 2 * random_upto(rs, 2);
-            } while (x == oldx && y == oldy && (oldr == 0 || r == oldr));
-
-            do_rotate(grid, w, h, n, params->orientable,
-                      x, y, r);
+                r = 2 * random_upto(rs, 2) - 1;
+
+                /*
+                 * See if any previous rotations has happened at
+                 * this point which nothing has overlapped since.
+                 * If so, ensure we haven't either undone a
+                 * previous move or repeated one so many times that
+                 * it turns into fewer moves in the inverse
+                 * direction (i.e. three identical rotations).
+                 */
+                oldtotal = prevmoves[y*rw+x];
+                newtotal = oldtotal + r;
+            } while (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2);
+
+            do_rotate(grid, w, h, n, params->orientable, x, y, r);
 
             /*
-             * Prevent immediate reversal of a previous move, or
-             * execution of three consecutive identical moves
-             * adding up to a single inverse move. One exception is
-             * when we only _have_ one x,y setting.
+             * Log the rotation we've just performed at this point,
+             * for inversion detection in the next move.
+             * 
+             * Also zero a section of the prevmoves array, because
+             * any rotation area which _overlaps_ this one is now
+             * entirely safe to perform further moves in.
+             * 
+             * Two rotation areas overlap if their top left
+             * coordinates differ by strictly less than n in both
+             * directions
              */
-            if (w != n || h != n) {
-                if (oldx == x && oldy == y)
-                    oldr = 0;          /* now avoid _any_ move in this x,y */
-                else
-                    oldr = -r & 3;     /* only prohibit the exact inverse */
-                oldx = x;
-                oldy = y;
+            prevmoves[y*rw+x] += r;
+            for (dy = -n+1; dy <= n-1; dy++) {
+                if (y + dy < 0 || y + dy >= rh)
+                    continue;
+                for (dx = -n+1; dx <= n-1; dx++) {
+                    if (x + dx < 0 || x + dx >= rw)
+                        continue;
+                    if (dx == 0 && dy == 0)
+                        continue;
+                    prevmoves[(y+dy)*rw+(x+dx)] = 0;
+                }
             }
         }
+
+        sfree(prevmoves);
+
     } while (grid_complete(grid, wh, params->orientable));
 
     /*
@@ -428,7 +457,7 @@ static char *validate_desc(game_params *params, char *desc)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *desc)
+static game_state *new_game(midend_data *me, game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int w = params->w, h = params->h, n = params->n, wh = w*h;
@@ -587,13 +616,15 @@ static void free_ui(game_ui *ui)
 {
 }
 
-static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
-                            int button)
+static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                             int x, int y, int button)
 {
     int w = from->w, h = from->h, n = from->n, wh = w*h;
     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
@@ -604,30 +635,66 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
        y -= (n-1) * TILE_SIZE / 2;
        x = FROMCOORD(x);
        y = FROMCOORD(y);
-       if (x < 0 || x > w-n || y < 0 || y > w-n)
+       dir = (button == LEFT_BUTTON ? 1 : -1);
+       if (x < 0 || x > w-n || y < 0 || y > h-n)
            return NULL;
+    } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') {
+        x = y = 0;
+        dir = (button == 'A' ? -1 : +1);
+    } else if (button == 'b' || button == 'B' || button==MOD_NUM_KEYPAD+'9') {
+        x = w-n;
+        y = 0;
+        dir = (button == 'B' ? -1 : +1);
+    } else if (button == 'c' || button == 'C' || button==MOD_NUM_KEYPAD+'1') {
+        x = 0;
+        y = h-n;
+        dir = (button == 'C' ? -1 : +1);
+    } else if (button == 'd' || button == 'D' || button==MOD_NUM_KEYPAD+'3') {
+        x = w-n;
+        y = h-n;
+        dir = (button == 'D' ? -1 : +1);
+    } else if (button==MOD_NUM_KEYPAD+'8' && (w-n) % 2 == 0) {
+        x = (w-n) / 2;
+        y = 0;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'2' && (w-n) % 2 == 0) {
+        x = (w-n) / 2;
+        y = h-n;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'4' && (h-n) % 2 == 0) {
+        x = 0;
+        y = (h-n) / 2;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'6' && (h-n) % 2 == 0) {
+        x = w-n;
+        y = (h-n) / 2;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'5' && (w-n) % 2 == 0 && (h-n) % 2 == 0){
+        x = (w-n) / 2;
+        y = (h-n) / 2;
+        dir = +1;
+    } else {
+        return NULL;                   /* no move to be made */
+    }
 
-       /*
-        * This is a valid move. Make it.
-        */
-       ret = dup_game(from);
-       ret->just_used_solve = FALSE;  /* zero this in a hurry */
-       ret->movecount++;
-       dir = (button == LEFT_BUTTON ? 1 : -1);
-       do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
-       ret->lastx = x;
-       ret->lasty = y;
-       ret->lastr = dir;
+    /*
+     * This is a valid move. Make it.
+     */
+    ret = dup_game(from);
+    ret->just_used_solve = FALSE;  /* zero this in a hurry */
+    ret->movecount++;
+    do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
+    ret->lastx = x;
+    ret->lasty = y;
+    ret->lastr = dir;
 
-       /*
-        * See if the game has been completed. To do this we simply
-        * test that the grid contents are in increasing order.
-        */
-       if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
-           ret->completed = ret->movecount;
-       return ret;
-    }
-    return NULL;
+    /*
+     * See if the game has been completed. To do this we simply
+     * test that the grid contents are in increasing order.
+     */
+    if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
+        ret->completed = ret->movecount;
+    return ret;
 }
 
 /* ----------------------------------------------------------------------
@@ -904,7 +971,7 @@ static int highlight_colour(float angle)
 }
 
 static float game_anim_length(game_state *oldstate, game_state *newstate,
-                             int dir)
+                             int dir, game_ui *ui)
 {
     if ((dir > 0 && newstate->just_used_solve) ||
        (dir < 0 && oldstate->just_used_solve))
@@ -914,7 +981,7 @@ static float game_anim_length(game_state *oldstate, game_state *newstate,
 }
 
 static float game_flash_length(game_state *oldstate, game_state *newstate,
-                              int dir)
+                              int dir, game_ui *ui)
 {
     if (!oldstate->completed && newstate->completed &&
        !oldstate->used_solve && !newstate->used_solve)
@@ -974,7 +1041,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      */
     if (oldstate) {
        float angle;
-       float anim_max = game_anim_length(oldstate, state, dir);
+       float anim_max = game_anim_length(oldstate, state, dir, ui);
 
        if (dir > 0) {
            lastx = state->lastx;
@@ -1073,6 +1140,11 @@ static int game_wants_statusbar(void)
     return TRUE;
 }
 
+static int game_timing_state(game_state *state)
+{
+    return TRUE;
+}
+
 #ifdef COMBINED
 #define thegame twiddle
 #endif
@@ -1106,4 +1178,5 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    FALSE, game_timing_state,
 };