From f84b9740081ce2c53445f74d159d2413e83a56c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Ekstr=C3=B6m?= Date: Mon, 12 Feb 2018 16:18:03 +0100 Subject: [PATCH] Simplify tile storage using bitfields Instead of storing tiles as uint8_t, they are now stored as a struct minesweeper_tile, which still should only be 8 bits wide, but each property is addressable without bitmasking. This is a breaking API change, since some functions that were previously used to access data encoded in the tiles are no longer needed. All data can be accessed in the tile directly. --- include/minesweeper.h | 39 ++++++-------- include/minesweeper.hpp | 22 ++++---- lib/minesweeper.c | 107 ++++++++++++++++---------------------- tests/minesweeper_tests.c | 32 +++++------- 4 files changed, 86 insertions(+), 114 deletions(-) diff --git a/include/minesweeper.h b/include/minesweeper.h index fb53056..d1a3fa1 100644 --- a/include/minesweeper.h +++ b/include/minesweeper.h @@ -5,12 +5,6 @@ #include #include -enum { - TILE_OPENED = 1, - TILE_MINE = 1 << 1, - TILE_FLAG = 1 << 2 -}; - enum direction { LEFT, RIGHT, @@ -25,8 +19,15 @@ enum minesweeper_game_state { MINESWEEPER_GAME_OVER }; +struct minesweeper_tile { + uint8_t adjacent_mine_count : 4; + bool has_flag : 1; + bool has_mine : 1; + bool is_opened : 1; +}; + struct minesweeper_game; -typedef void (*minesweeper_callback) (struct minesweeper_game *game, uint8_t *tile, void *user_info); +typedef void (*minesweeper_callback) (struct minesweeper_game *game, struct minesweeper_tile *tile, void *user_info); /** * Contains data for a single minesweeper game. @@ -42,8 +43,8 @@ struct minesweeper_game { unsigned mine_count; unsigned opened_tile_count; unsigned flag_count; - uint8_t *selected_tile; /* Pointer to the tile under the cursor */ - uint8_t *data; /* Tile buffer */ + struct minesweeper_tile *selected_tile; /* Pointer to the tile under the cursor */ + struct minesweeper_tile *tiles; enum minesweeper_game_state state; minesweeper_callback tile_update_callback; /* Optional function pointer to receive tile state updates */ void *user_info; /* Can be used for anything, will be passed as a parameter to tile_update_callback */ @@ -87,44 +88,38 @@ void minesweeper_move_cursor(struct minesweeper_game *game, enum direction direc * be opened instead, to imitate the quick-open functionality of most * minesweeper games. */ -void minesweeper_open_tile(struct minesweeper_game *game, uint8_t *tile); +void minesweeper_open_tile(struct minesweeper_game *game, struct minesweeper_tile *tile); /** * Toggles a flag on an unopened tile. */ -void minesweeper_toggle_flag(struct minesweeper_game *game, uint8_t *tile); +void minesweeper_toggle_flag(struct minesweeper_game *game, struct minesweeper_tile *tile); /** * Get pointer to tile at location. Returns NULL if location if out of bounds. */ -uint8_t *minesweeper_get_tile_at(struct minesweeper_game *game, unsigned x, unsigned y); +struct minesweeper_tile *minesweeper_get_tile_at(struct minesweeper_game *game, unsigned x, unsigned y); /** * Get location of a tile. * * x/y: Pointers to integers which the result will be written to. */ -void minesweeper_get_tile_location(struct minesweeper_game *game, uint8_t *tile, unsigned *x, unsigned *y); +void minesweeper_get_tile_location(struct minesweeper_game *game, struct minesweeper_tile *tile, unsigned *x, unsigned *y); /** * Get all tiles adjacent to tile. A tile can have at most 8 adjacent tiles, * but tiles adjacent to edges of the game area will have fewer. * - * adjacent_tiles: A pointer to an array of 8 uint8_t pointers. The resulting tiles will + * adjacent_tiles: A pointer to an array of 8 tile pointers. Pointers to the resulting tiles will * be written to this array. Some tiles may be NULL, if tile is adjacent to an edge. */ -void minesweeper_get_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile, uint8_t *adjacent_tiles[8]); - -/** - * Returns the number of adjacent mines for a tile. This is - * the colored number that is shown on tiles in most minesweepers. - */ -uint8_t minesweeper_get_adjacent_mine_count(uint8_t *tile); +void minesweeper_get_adjacent_tiles(struct minesweeper_game *game, struct minesweeper_tile *tile, struct minesweeper_tile *adjacent_tiles[8]); /** * Toggles a mine on a tile, and adjusts the adjacent mine counts for all * adjacent tiles. */ -void minesweeper_toggle_mine(struct minesweeper_game *game, uint8_t *tile); +void minesweeper_toggle_mine(struct minesweeper_game *game, struct minesweeper_tile *tile); #endif diff --git a/include/minesweeper.hpp b/include/minesweeper.hpp index 0f3bfce..8ee07e7 100644 --- a/include/minesweeper.hpp +++ b/include/minesweeper.hpp @@ -29,8 +29,8 @@ namespace Minesweeper { } private: - Tile(uint8_t *internal, minesweeper_game *game): internal(internal), game(game) {} - uint8_t *internal; + Tile(minesweeper_tile *internal, minesweeper_game *game): internal(internal), game(game) {} + minesweeper_tile *internal; minesweeper_game *game; }; @@ -55,7 +55,7 @@ namespace Minesweeper { minesweeper_game *internal; }; - extern "C" void callbackHandler(minesweeper_game *game, uint8_t *tile, void *context) { + extern "C" void callbackHandler(minesweeper_game *game, struct minesweeper_tile *tile, void *context) { Game *gameObject = (Game *)context; unsigned x, y; minesweeper_get_tile_location(game, tile, &x, &y); Tile tileObject = gameObject->tileAt(x, y); @@ -106,14 +106,14 @@ namespace Minesweeper { } inline Tile Game::selectedTile() { - uint8_t *tilePtr = internal->selected_tile; + minesweeper_tile *tilePtr = internal->selected_tile; if (tilePtr == NULL) throw std::logic_error("No tile is selected. Call setCursor() first."); return Tile(tilePtr, this->internal); } inline Tile Game::tileAt(unsigned x, unsigned y) { - uint8_t *tilePtr = minesweeper_get_tile_at(internal, x, y); + minesweeper_tile *tilePtr = minesweeper_get_tile_at(internal, x, y); if (tilePtr == NULL) throw std::out_of_range("Tile is out of bounds for this game."); return Tile(tilePtr, this->internal); @@ -128,7 +128,7 @@ namespace Minesweeper { } inline uint8_t Tile::adjacentMineCount() { - return minesweeper_get_adjacent_mine_count(internal); + return internal->adjacent_mine_count; } inline void Tile::toggleMine() { @@ -136,24 +136,24 @@ namespace Minesweeper { } inline bool Tile::hasMine() { - return *internal & TILE_MINE; + return internal->has_mine; } inline bool Tile::hasFlag() { - return *internal & TILE_FLAG; + return internal->has_flag; } inline bool Tile::isOpened() { - return *internal & TILE_OPENED; + return internal->is_opened; } inline std::vector Tile::adjacentTiles() { - uint8_t *tiles[8]; + minesweeper_tile *tiles[8]; minesweeper_get_adjacent_tiles(game, internal, tiles); std::vector list; for (int i = 0; i < 8; i++) { - uint8_t *tilePtr = tiles[i]; + minesweeper_tile *tilePtr = tiles[i]; if (tilePtr != NULL) { list.push_back(Tile(tilePtr, this->game)); } diff --git a/lib/minesweeper.c b/lib/minesweeper.c index 8cca95b..3cc9491 100644 --- a/lib/minesweeper.c +++ b/lib/minesweeper.c @@ -8,7 +8,7 @@ struct minesweeper_game *minesweeper_init(unsigned width, unsigned height, float /* Place a game object in the start of the buffer, and treat the rest of the buffer as tile storage. */ struct minesweeper_game *game = (struct minesweeper_game *)buffer; - game->data = buffer + sizeof(struct minesweeper_game); + game->tiles = (struct minesweeper_tile *)buffer + sizeof(struct minesweeper_game); game->tile_update_callback = NULL; game->state = MINESWEEPER_PENDING_START; game->width = width; @@ -18,13 +18,13 @@ struct minesweeper_game *minesweeper_init(unsigned width, unsigned height, float game->opened_tile_count = 0; game->selected_tile = NULL; game->user_info = NULL; - memset(game->data, 0, width * height); + memset(game->tiles, 0, width * height); generate_mines(game, mine_density); return game; } size_t minesweeper_minimum_buffer_size(unsigned width, unsigned height) { - return sizeof(struct minesweeper_game) + width * height; + return sizeof(struct minesweeper_game) + sizeof(struct minesweeper_tile) * width * height; } bool is_out_of_bounds(struct minesweeper_game *b, unsigned x, unsigned y) { @@ -36,19 +36,19 @@ bool is_out_of_bounds(struct minesweeper_game *b, unsigned x, unsigned y) { * will return NULL if the tile is out of bounds. This * is to simplify code that enumerates "adjacent" tiles. */ -uint8_t *minesweeper_get_tile_at(struct minesweeper_game *game, unsigned x, unsigned y) { +struct minesweeper_tile *minesweeper_get_tile_at(struct minesweeper_game *game, unsigned x, unsigned y) { if (is_out_of_bounds(game, x, y)) return NULL; - return &game->data[game->width * y + x]; + return &game->tiles[game->width * y + x]; } -void minesweeper_get_tile_location(struct minesweeper_game *game, uint8_t *tile, unsigned *x, unsigned *y) { - unsigned tile_index = tile - game->data; +void minesweeper_get_tile_location(struct minesweeper_game *game, struct minesweeper_tile *tile, unsigned *x, unsigned *y) { + unsigned tile_index = tile - game->tiles; *y = tile_index / game->width; *x = tile_index % game->width; } -void minesweeper_get_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile, uint8_t *adjacent_tiles[8]) { +void minesweeper_get_adjacent_tiles(struct minesweeper_game *game, struct minesweeper_tile *tile, struct minesweeper_tile *adjacent_tiles[8]) { unsigned x, y; minesweeper_get_tile_location(game, tile, &x, &y); adjacent_tiles[0] = minesweeper_get_tile_at(game, x - 1, y - 1); adjacent_tiles[1] = minesweeper_get_tile_at(game, x - 1, y); @@ -60,61 +60,46 @@ void minesweeper_get_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile adjacent_tiles[7] = minesweeper_get_tile_at(game, x + 1, y + 1); } -/** - * We use the last 4 bits of a tile for tile data such as - * opened, mine, flag etc. the first 4 stores a count of - * adjacent mines. This function gets that value. - */ -uint8_t minesweeper_get_adjacent_mine_count(uint8_t *tile) { - return (*tile & 0xF0) >> 4; -} - /** * When attempting to open a tile that's already opened, the game * can "auto open" adjacent tiles if it's surrounded by the correct * amount of flagged tiles. This function counts the surrounding * flagged tiles. */ -uint8_t count_adjacent_flags(struct minesweeper_game *game, uint8_t *tile) { +uint8_t count_adjacent_flags(struct minesweeper_game *game, struct minesweeper_tile *tile) { uint8_t count = 0; - uint8_t *adjacent_tiles[8]; + struct minesweeper_tile *adjacent_tiles[8]; uint8_t i; minesweeper_get_adjacent_tiles(game, tile, adjacent_tiles); for (i = 0; i < 8; i++) { - uint8_t *adj_tile = adjacent_tiles[i]; - if (adj_tile && !(*adj_tile & TILE_OPENED) && *adj_tile & TILE_FLAG) { + struct minesweeper_tile *adj_tile = adjacent_tiles[i]; + if (adj_tile && !(adj_tile->is_opened) && adj_tile->has_flag) { count++; } } return count; } -void adjust_adjacent_mine_count(uint8_t *tile, int8_t value) { - uint8_t new_value = minesweeper_get_adjacent_mine_count(tile) + value; - uint8_t shifted_value = new_value << 4; - *tile = shifted_value | (*tile & 0x0F); -} - -void minesweeper_toggle_mine(struct minesweeper_game *game, uint8_t *tile) { +void minesweeper_toggle_mine(struct minesweeper_game *game, struct minesweeper_tile *tile) { uint8_t i; - uint8_t *adjacent_tiles[8]; + struct minesweeper_tile *adjacent_tiles[8]; int8_t count_modifier = -1; if (!tile) { return; } - *tile ^= TILE_MINE; - if (*tile & TILE_MINE) { + tile->has_mine = !tile->has_mine; + if (tile->has_mine) { count_modifier = 1; } game->mine_count += count_modifier; - /* Increase the mine counts on all adjacent tiles */ + /* Increase or decrease the mine counts on all adjacent tiles */ minesweeper_get_adjacent_tiles(game, tile, adjacent_tiles); for (i = 0; i < 8; i++) { if (adjacent_tiles[i]) { - adjust_adjacent_mine_count(adjacent_tiles[i], count_modifier); + adjacent_tiles[i]->adjacent_mine_count += count_modifier; } } } @@ -124,23 +109,23 @@ void generate_mines(struct minesweeper_game *game, float density) { unsigned mine_count = tile_count * density; unsigned i; for (i = 0; i < mine_count; i++) { - uint8_t *random_tile = &game->data[rand() % tile_count]; - if (!(*random_tile & TILE_MINE)) { + struct minesweeper_tile *random_tile = &game->tiles[rand() % tile_count]; + if (!random_tile->has_mine) { minesweeper_toggle_mine(game, random_tile); } } } -void send_update_callback(struct minesweeper_game *game, uint8_t *tile) { +void send_update_callback(struct minesweeper_game *game, struct minesweeper_tile *tile) { if (game->tile_update_callback != NULL) { game->tile_update_callback(game, tile, game->user_info); } } -void minesweeper_toggle_flag(struct minesweeper_game *game, uint8_t *tile) { - if (tile && !(*tile & TILE_OPENED)) { - game->flag_count += (*tile & TILE_FLAG) ? -1 : 1; - *tile ^= TILE_FLAG; +void minesweeper_toggle_flag(struct minesweeper_game *game, struct minesweeper_tile *tile) { + if (tile && !tile->is_opened) { + game->flag_count += tile->has_flag ? -1 : 1; + tile->has_flag = !tile->has_flag; send_update_callback(game, tile); } } @@ -149,29 +134,28 @@ static inline bool all_tiles_opened(struct minesweeper_game *game) { return game->opened_tile_count == game->width * game->height - game->mine_count; } -void open_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile); +void open_adjacent_tiles(struct minesweeper_game *game, struct minesweeper_tile *tile); -void _open_tile(struct minesweeper_game *game, uint8_t *tile, bool cascade) { - if (*tile & TILE_OPENED) { +void _open_tile(struct minesweeper_game *game, struct minesweeper_tile *tile, bool cascade) { + if (tile->is_opened) { /* If this tile is already opened and has a mine count, * it should open all adjacent tiles instead. This mimics * the behaviour in the original minesweeper where you can * right click opened tiles to open adjacent tiles quickly. */ - uint8_t adj_mine_count = minesweeper_get_adjacent_mine_count(tile); - if (adj_mine_count > 0 && adj_mine_count == count_adjacent_flags(game, tile) && cascade) + if (tile->adjacent_mine_count > 0 && tile->adjacent_mine_count == count_adjacent_flags(game, tile) && cascade) open_adjacent_tiles(game, tile); return; } - if (*tile & TILE_FLAG) { + if (tile->has_flag) { return; } - *tile |= TILE_OPENED; + tile->is_opened = true; game->opened_tile_count += 1; send_update_callback(game, tile); - if (*tile & TILE_MINE) { + if (tile->has_mine) { game->state = MINESWEEPER_GAME_OVER; return; } @@ -181,7 +165,7 @@ void _open_tile(struct minesweeper_game *game, uint8_t *tile, bool cascade) { return; } - if (minesweeper_get_adjacent_mine_count(tile) != 0) { + if (tile->adjacent_mine_count != 0) { return; } @@ -189,12 +173,12 @@ void _open_tile(struct minesweeper_game *game, uint8_t *tile, bool cascade) { open_adjacent_tiles(game, tile); } -void minesweeper_open_tile(struct minesweeper_game *game, uint8_t *tile) { +void minesweeper_open_tile(struct minesweeper_game *game, struct minesweeper_tile *tile) { if (game->state == MINESWEEPER_PENDING_START) { game->state = MINESWEEPER_PLAYING; // Delete any potential mine on the first opened tile - if (*tile & TILE_MINE) { + if (tile->has_mine) { minesweeper_toggle_mine(game, tile); } } @@ -203,28 +187,26 @@ void minesweeper_open_tile(struct minesweeper_game *game, uint8_t *tile) { void open_line_segments(struct minesweeper_game *game, unsigned x1, unsigned x2, unsigned y) { unsigned x; - uint8_t* tile; + struct minesweeper_tile *tile; for (x = x1; x <= x2 && (tile = minesweeper_get_tile_at(game, x, y)); x++) { - if (!(*tile & TILE_OPENED)) + if (!tile->is_opened) _open_tile(game, tile, true); } } -void open_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile) { - unsigned tile_index = tile - game->data; +void open_adjacent_tiles(struct minesweeper_game *game, struct minesweeper_tile *tile) { + unsigned tile_index = tile - game->tiles; unsigned ty = tile_index / game->width; unsigned tx = tile_index % game->width; - unsigned mine_count; unsigned lx, rx; - uint8_t* subtile; + struct minesweeper_tile *subtile; // Search for left boundary for (lx = tx - 1; (subtile = minesweeper_get_tile_at(game, lx, ty)); lx--) { - mine_count = minesweeper_get_adjacent_mine_count(subtile); - if (*subtile & TILE_OPENED && mine_count != 0) + if (subtile->is_opened && subtile->adjacent_mine_count != 0) break; _open_tile(game, subtile, false); - if (mine_count != 0) + if (subtile->adjacent_mine_count != 0) break; } @@ -234,11 +216,10 @@ void open_adjacent_tiles(struct minesweeper_game *game, uint8_t *tile) { // Search for right boundary for (rx = tx + 1; (subtile = minesweeper_get_tile_at(game, rx, ty)); rx++) { - mine_count = minesweeper_get_adjacent_mine_count(subtile); - if (*subtile & TILE_OPENED && mine_count != 0) + if (subtile->is_opened && subtile->adjacent_mine_count != 0) break; _open_tile(game, subtile, false); - if (mine_count != 0) + if (subtile->adjacent_mine_count != 0) break; } diff --git a/tests/minesweeper_tests.c b/tests/minesweeper_tests.c index 51ba3b8..a320b2a 100644 --- a/tests/minesweeper_tests.c +++ b/tests/minesweeper_tests.c @@ -14,12 +14,12 @@ static char * test_init() { puts("Test: Initialization..."); game_buffer = malloc(minesweeper_minimum_buffer_size(width, height)); game = minesweeper_init(width, height, 1.0, game_buffer); - mu_assert("Error: game data must be offset in the buffer.", game->data - sizeof(struct minesweeper_game) == (uint8_t *)game); + mu_assert("Error: tile data must be offset in the buffer.", (uint8_t *)game->tiles - sizeof(struct minesweeper_game) == (uint8_t *)game); mu_assert("Error: after init, state must be pending_start", game->state == MINESWEEPER_PENDING_START); return 0; } -static int count_adjacent_tiles(uint8_t **adjacent_tiles) { +static int count_adjacent_tiles(struct minesweeper_tile **adjacent_tiles) { int i; int count = 0; for (i = 0; i < 8; i++) { @@ -31,7 +31,7 @@ static int count_adjacent_tiles(uint8_t **adjacent_tiles) { } static char * test_get_tile() { - uint8_t *tile = minesweeper_get_tile_at(game, 10, 10); + struct minesweeper_tile *tile = minesweeper_get_tile_at(game, 10, 10); puts("Test: Get tile..."); mu_assert("Error: the tile at (10, 10) should exist after init.", tile != NULL); @@ -44,8 +44,8 @@ static char * test_get_tile() { } static char * test_get_adjacent_tiles() { - uint8_t *tile = minesweeper_get_tile_at(game, 0, 0); - uint8_t *adjacent_tiles[8]; + struct minesweeper_tile *tile = minesweeper_get_tile_at(game, 0, 0); + struct minesweeper_tile *adjacent_tiles[8]; puts("Test: Get adjacent tiles..."); minesweeper_get_adjacent_tiles(game, tile, adjacent_tiles); mu_assert("Error: the tile at (0, 0) should have 3 adjacent tiles.", count_adjacent_tiles(adjacent_tiles) == 3); @@ -65,7 +65,7 @@ static char * test_open_first_tile() { minesweeper_set_cursor(game, width / 2, height / 2); minesweeper_open_tile(game, game->selected_tile); mu_assert("Error: after opening the first tile, state should be: playing", game->state == MINESWEEPER_PLAYING); - mu_assert("Error: there must not be a mine under the first opened tile", !(*game->selected_tile & TILE_MINE)); + mu_assert("Error: there must not be a mine under the first opened tile", !(game->selected_tile->has_mine)); return 0; } @@ -78,11 +78,10 @@ static char * test_open_mine() { } static char * test_adjacent_mine_counts() { - uint8_t *center_tile; - uint8_t *left_tile; - uint8_t *right_tile; - uint8_t *adj_tiles[8]; - uint8_t mine_count; + struct minesweeper_tile *center_tile; + struct minesweeper_tile *left_tile; + struct minesweeper_tile *right_tile; + struct minesweeper_tile *adj_tiles[8]; int i; puts("Test: Adjacent mine counters..."); @@ -94,8 +93,7 @@ static char * test_adjacent_mine_counts() { minesweeper_toggle_mine(game, left_tile); minesweeper_toggle_mine(game, right_tile); - mine_count = minesweeper_get_adjacent_mine_count(center_tile); - mu_assert("Error: the tile at (10, 10) must have a mine_count of 2 after mines have been placed at (9, 10) and (11, 10).", mine_count == 2); + mu_assert("Error: the tile at (10, 10) must have a mine_count of 2 after mines have been placed at (9, 10) and (11, 10).", center_tile->adjacent_mine_count == 2); // Toggle mines on all tiles adjacent to the center tile, except // left and right tiles which already have mines @@ -106,12 +104,10 @@ static char * test_adjacent_mine_counts() { } } - mine_count = minesweeper_get_adjacent_mine_count(center_tile); - mu_assert("Error: the tile at (10, 10) must have a mine_count of 8 after mines have been placed at every tile around it.", mine_count == 8); + mu_assert("Error: the tile at (10, 10) must have a mine_count of 8 after mines have been placed at every tile around it.", center_tile->adjacent_mine_count == 8); minesweeper_toggle_mine(game, left_tile); - mine_count = minesweeper_get_adjacent_mine_count(center_tile); - mu_assert("Error: the tile at (10, 10) must have a mine_count of 7 after the mine has been toggled off on the tile to the left of it.", mine_count == 7); + mu_assert("Error: the tile at (10, 10) must have a mine_count of 7 after the mine has been toggled off on the tile to the left of it.", center_tile->adjacent_mine_count == 7); return 0; } @@ -127,7 +123,7 @@ static char * test_win_state() { return 0; } -void callback(struct minesweeper_game *game, uint8_t *tile, void *user_info) { +void callback(struct minesweeper_game *game, struct minesweeper_tile *tile, void *user_info) { int *callback_count = (int *)user_info; *callback_count = *callback_count + 1; }