]> git.draconx.ca Git - rrace.git/blobdiff - src/game.c
Improve game_check_goal implementation slightly.
[rrace.git] / src / game.c
index bd1fef98eb5e209d2797868524857743ce99eb30..47adc9ffe37cb01a90d88647d25246fb2be53332 100644 (file)
@@ -1,12 +1,30 @@
 /*
+ * Slide puzzle core game logic
+ * Copyright © 2022 Nick Bowler
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
  * The RNG implementation is adapted from xoshiro256** and splitmix64
  * by David Blackman and Sebastiano Vigna, originally distributed under
  * the Creative Commons Zero public domain dedication.
  */
+
 #include <config.h>
 #include <limits.h>
 #include <string.h>
 #include <time.h>
+#include <gethrxtime.h>
 #include "game.h"
 
 #define B64(x) ((x) & 0xffffffffffffffff)
@@ -106,8 +124,15 @@ void game_reset(struct board *board)
        unsigned char tiles[25];
        unsigned i;
 
-       if (!rng_is_seeded())
-               game_reseed(time(NULL));
+       if (!rng_is_seeded()) {
+               unsigned long long seed;
+
+               seed = time(NULL);
+               seed += gethrxtime();
+               seed += seed << 32;
+
+               game_reseed(seed);
+       }
 
        for (i = 0; i < 24; i++) {
                tiles[i] = (i%6) + 1;
@@ -117,48 +142,69 @@ void game_reset(struct board *board)
        memset(board->goal, 0, sizeof board->goal);
 
        for (i = 0; i < 9; i++) {
-               uint_fast32_t position = board_position(i/3+1, i%3+1);
-
-               if (tiles[i] & 1)
-                       board->goal[0] |= position >> 6;
-               if (tiles[i] & 2)
-                       board->goal[1] |= position >> 6;
-               if (tiles[i] & 4)
-                       board->goal[2] |= position >> 6;
+               uint_fast32_t position = board_position(i/3+1, i%3+1) >> 6;
+
+               if (tiles[i] & 1) board->goal[0] |= position;
+               if (tiles[i] & 2) board->goal[1] |= position;
+               if (tiles[i] & 4) board->goal[2] |= position;
        }
 
        tiles[24] = TILE_EMPTY;
        shuffle(tiles, 25);
-       memset(board->game, 0, sizeof board->game);
 
+       memset(board->game, 0, sizeof board->game);
        for (i = 0; i < 25; i++) {
                unsigned x = i/5, y = i%5;
                uint_fast32_t position;
 
                position = board_position(x, y);
-               if (tiles[i] == TILE_EMPTY) {
-                       board->game[0] = 0x1ffffff ^ position;
+               if (tiles[i] != TILE_EMPTY) {
+                       if (tiles[i] & 1) board->game[0] |= position;
+                       if (tiles[i] & 2) board->game[1] |= position;
+                       if (tiles[i] & 4) board->game[2] |= position;
+               } else {
+                       board->game[3] = ~position;
                        board->x = x;
                        board->y = y;
-               } else {
-                       if (tiles[i] & 1)
-                               board->game[1] |= position;
-                       if (tiles[i] & 2)
-                               board->game[2] |= position;
-                       if (tiles[i] & 4)
-                               board->game[3] |= position;
                }
        }
+
+/* Uncomment to make every game stupidly easy -- winnable in 1 move */
+#if 0
+       /* Force empty space to the border */
+       if (board_position(board->x, board->y) & GOAL_MASK) {
+               switch (rng_uniform_int(4)) {
+               case 0: game_do_move(board, board->x, 0); break;
+               case 1: game_do_move(board, board->x, 4); break;
+               case 2: game_do_move(board, 0, board->y); break;
+               case 3: game_do_move(board, 4, board->y); break;
+               }
+       }
+
+       /* Force goal to match the current board */
+       for (i = 0; i < 3; i++) {
+               board->goal[i] = (board->game[i] & GOAL_MASK) >> GOAL_SHIFT;
+       }
+
+       /* Move empty space back to the centre */
+       if (board->x == 0 || board->x == 4) {
+               game_do_move(board, 1+rng_uniform_int(3), board->y);
+       }
+
+       if (board->y == 0 || board->y == 4) {
+               game_do_move(board, board->x, 1+rng_uniform_int(3));
+       }
+#endif
 }
 
-int game_do_move(struct board *board, int x, int y)
+uint_fast32_t game_do_move(struct board *board, int x, int y)
 {
        int bx = board->x, by = board->y;
-       uint_least32_t mask, val[4];
+       uint_least32_t ret = 0, mask, val[4];
        int i, shl, shr;
 
        if ((bx != x) == (by != y))
-               return -1;
+               return 0;
 
        if (bx == x) {
                mask = board_mask_v(x, by, y);
@@ -173,9 +219,51 @@ int game_do_move(struct board *board, int x, int y)
        for (i = 0; i < 4; i++) {
                board->game[i] ^= (val[i] = board->game[i] & mask);
                board->game[i] |= val[i] << shl >> shr;
+               ret |= board->game[i] ^ val[i];
        }
 
        board->x = x;
        board->y = y;
-       return 0;
+
+       return mask & ret;
+}
+
+uint_fast32_t game_check_goal(struct board *board)
+{
+       uint_least32_t *game = board->game;
+       uint_least16_t *goal = board->goal;
+       uint_least32_t mask = 0;
+       int i;
+
+       for (i = 0; i < 3; i++)
+               mask |= game[i] ^ ((0ul+goal[i]) << GOAL_SHIFT);
+       return mask & GOAL_MASK;
+}
+
+void game_begin(struct board *board)
+{
+       board->time_start = gethrxtime();
+}
+
+int_fast32_t game_elapsed(struct board *board)
+{
+       return (gethrxtime() - board->time_start) / 1000000;
+}
+
+int_fast32_t game_finish(struct board *board)
+{
+       int_fast32_t t = game_elapsed(board);
+       int i;
+
+       for (i = 0; i < 4; i++) {
+               board->game[i] &= GOAL_MASK;
+       }
+
+       /*
+        * Setting both x and y hole location to a bogus value will effectively
+        * disable the game since there will be no valid moves.
+        */
+       board->x = board->y = -1;
+
+       return t;
 }