X-Git-Url: https://git.draconx.ca/gitweb/rrace.git/blobdiff_plain/2529a9651d160ab3a17118d778f5e5584d040765..583fc520dddf5125bc676777a537f0632f2acb55:/src/game.c?ds=sidebyside
diff --git a/src/game.c b/src/game.c
index bd1fef9..ab70478 100644
--- a/src/game.c
+++ b/src/game.c
@@ -1,11 +1,29 @@
/*
+ * Slide puzzle core game logic
+ * Copyright © 2022-2023 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 .
+ *
* 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
#include
#include
+#include
#include
#include "game.h"
@@ -101,13 +119,37 @@ void game_reseed(unsigned long long seed)
rng_state[3] = splitmix64(&seed);
}
+#define assign_tile(planes, tile, bit) do { \
+ 0[planes] |= (((tile)>>0) & 1ul) << (bit); \
+ 1[planes] |= (((tile)>>1) & 1ul) << (bit); \
+ 2[planes] |= (((tile)>>2) & 1ul) << (bit); \
+} while (0)
+
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;
+
+ /*
+ * Try to get a reasonable initial seed.
+ *
+ * Reasonable in this context means:
+ *
+ * - roughly even distribution of 1/0 bits, and
+ * - unlikely to generate the same seed twice in succession.
+ */
+ game_begin(board);
+
+ seed = time(NULL);
+ seed += board->time_start;
+ seed += (unsigned long long)getpid() << 16;
+ seed += seed << 32;
+
+ game_reseed(seed);
+ }
for (i = 0; i < 24; i++) {
tiles[i] = (i%6) + 1;
@@ -116,15 +158,13 @@ void game_reset(struct board *board)
shuffle(tiles, 24);
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;
+ /*
+ * Goal bitmap has 2-tile "gaps" between rows; it doesn't matter what
+ * these are set to and since we have a random permutation it doesn't
+ * matter which of the 24 nonempty tiles we pick for the actual goal.
+ */
+ for (i = 0; i < 13; i++) {
+ assign_tile(board->goal, tiles[i], i);
}
tiles[24] = TILE_EMPTY;
@@ -132,33 +172,51 @@ void game_reset(struct board *board)
memset(board->game, 0, sizeof board->game);
for (i = 0; i < 25; i++) {
- unsigned x = i/5, y = i%5;
- uint_fast32_t position;
+ assign_tile(board->game, tiles[i], i);
- position = board_position(x, y);
if (tiles[i] == TILE_EMPTY) {
- board->game[0] = 0x1ffffff ^ 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;
+ board->game[3] = (1ul<x = i%5;
+ board->y = i/5;
+ }
+ }
+
+/* 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 +231,41 @@ 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;
+}
+
+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;
}