penguins  1.0.0
game.c
Go to the documentation of this file.
1 #include "game.h"
2 #include "movement.h"
3 #include "placement.h"
4 #include "utils.h"
5 #include <assert.h>
6 #include <stdbool.h>
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
15 Game* game_new(void) {
16  Game* self = malloc(sizeof(*self));
17  self->phase = GAME_PHASE_NONE;
18  self->players = NULL;
19  self->players_count = -1;
20  self->penguins_per_player = -1;
21  self->board_width = -1;
22  self->board_height = -1;
23  self->board_grid = NULL;
24  self->tile_attributes = NULL;
25  self->current_player_index = -1;
26  self->log_disabled = false;
27  self->log_buffer = NULL;
28  self->log_capacity = 0;
29  self->log_length = 0;
30  self->log_current = 0;
31  return self;
32 }
33 
36 Game* game_clone(const Game* other) {
37  Game* self = memdup(other, sizeof(*self));
38  if (other->players) {
39  self->players = memdup(other->players, sizeof(*other->players) * other->players_count);
40  for (int i = 0; i < other->players_count; i++) {
41  Player *player = &self->players[i], *other_player = &other->players[i];
42  player->name = other_player->name ? strdup(other_player->name) : NULL;
43  player->penguins = memdup(
44  other_player->penguins, sizeof(*other_player->penguins) * other_player->penguins_count
45  );
46  }
47  }
48  if (self->board_grid) {
49  self->board_grid = memdup(
50  other->board_grid, sizeof(*other->board_grid) * other->board_width * other->board_height
51  );
52  }
53  if (self->tile_attributes) {
54  self->tile_attributes = memdup(
55  other->tile_attributes,
56  sizeof(*self->tile_attributes) * other->board_width * other->board_height
57  );
58  }
59  if (self->log_buffer) {
60  self->log_buffer = memdup(other->log_buffer, sizeof(*self->log_buffer) * other->log_capacity);
61  }
62  return self;
63 }
64 
68 void game_free(Game* self) {
69  if (self == NULL) return;
70  if (self->players) {
71  for (int i = 0; i < self->players_count; i++) {
72  free_and_clear(self->players[i].name);
73  free_and_clear(self->players[i].penguins);
74  }
75  free_and_clear(self->players);
76  }
77  free_and_clear(self->board_grid);
78  free_and_clear(self->tile_attributes);
79  free_and_clear(self->log_buffer);
80  free(self);
81 }
82 
87 uint32_t game_compute_state_hash(const Game* self) {
88  uint32_t state = FNV32_INITIAL_STATE;
89 #define fnv32_hash_value(value) state = fnv32_hash(state, &value, sizeof(value))
90 #define fnv32_hash_array(ptr, len) state = fnv32_hash(state, ptr, sizeof(*ptr) * len)
91  fnv32_hash_value(self->phase);
92  for (int i = 0; i < self->players_count; i++) {
93  const Player* player = &self->players[i];
94  fnv32_hash_value(player->id);
95  fnv32_hash_value(player->points);
97  for (int i = 0; i < player->penguins_count; i++) {
98  const Coords* coords = &player->penguins[i];
99  fnv32_hash_value(coords->x);
100  fnv32_hash_value(coords->y);
101  }
102  fnv32_hash_value(player->moves_count);
103  }
104  fnv32_hash_value(self->players_count);
105  fnv32_hash_value(self->penguins_per_player);
106  fnv32_hash_value(self->board_width);
107  fnv32_hash_value(self->board_height);
108  if (self->board_grid) {
109  fnv32_hash_array(self->board_grid, self->board_width * self->board_height);
110  }
111  fnv32_hash_value(self->current_player_index);
112 #undef fnv32_hash_value
113 #undef fnv32_hash_array
114  return state;
115 }
116 
121 void game_set_log_capacity(Game* self, size_t capacity) {
122  self->log_capacity = capacity;
123  self->log_length = my_min(self->log_length, capacity);
124  self->log_current = my_min(self->log_current, capacity);
125  self->log_buffer = realloc(self->log_buffer, sizeof(*self->log_buffer) * capacity);
126 }
127 
138  if (self->log_disabled) {
139  return NULL;
140  }
141  if (self->log_current >= self->log_capacity) {
142  game_set_log_capacity(self, my_max(self->log_capacity * 2, 1));
143  }
144  GameLogEntry* entry = &self->log_buffer[self->log_current];
145  entry->type = type;
146  self->log_current += 1;
147  // If some entries were undone previously (so log_current < log_length), this
148  // line will discard all of them.
149  self->log_length = self->log_current;
150  return entry;
151 }
152 
156 const GameLogEntry* game_pop_log_entry(Game* self, GameLogEntryType expected_type) {
157  assert(self->log_current > 0);
158  const GameLogEntry* entry = &self->log_buffer[self->log_current - 1];
159  assert(entry->type == expected_type);
160  // Unsure about this, but let's check the type even in the release mode.
161  // Interpreting an entry as the wrong type is nasty even by C standards.
162  if (entry->type != expected_type) {
163  return NULL;
164  }
165  self->log_current -= 1;
166  return entry;
167 }
168 
173 const GameLogEntry* game_get_log_entry(const Game* self, size_t idx) {
174  assert(idx < self->log_length);
175  return &self->log_buffer[idx];
176 }
177 
181 void game_set_phase(Game* self, GamePhase phase) {
182  if (self->phase == phase) return;
183  GameLogEntry* entry;
184  if ((entry = game_push_log_entry(self, GAME_LOG_ENTRY_PHASE_CHANGE)) != NULL) {
185  GameLogPhaseChange* entry_data = &entry->data.phase_change;
186  entry_data->old_phase = self->phase;
187  entry_data->new_phase = phase;
188  }
189  self->phase = phase;
190 }
191 
195 void game_set_current_player(Game* self, int idx) {
196  if (self->current_player_index == idx) return;
197  GameLogEntry* entry;
198  if ((entry = game_push_log_entry(self, GAME_LOG_ENTRY_PLAYER_CHANGE)) != NULL) {
199  GameLogPlayerChange* entry_data = &entry->data.player_change;
200  entry_data->old_player_index = self->current_player_index;
201  entry_data->new_player_index = idx;
202  }
203  self->current_player_index = idx;
204 }
205 
209 void game_begin_setup(Game* self) {
210  assert(self->phase == GAME_PHASE_NONE);
212 }
213 
224 void game_end_setup(Game* self) {
225  assert(self->phase == GAME_PHASE_SETUP);
226  assert(self->board_width > 0);
227  assert(self->board_height > 0);
228  assert(self->board_grid != NULL);
229  assert(self->tile_attributes != NULL);
230  assert(self->players_count >= 0);
231  if (self->players_count != 0) {
232  assert(self->players != NULL);
233  }
234  assert(self->penguins_per_player >= 0);
235  for (int i = 0; i < self->players_count; i++) {
236  assert(self->players[i].name != NULL);
237  if (self->penguins_per_player != 0) {
238  assert(self->players[i].penguins != NULL);
239  }
240  }
242 }
243 
248 void game_set_penguins_per_player(Game* self, int value) {
249  assert(self->phase == GAME_PHASE_SETUP);
250  assert(value >= 0);
251  self->penguins_per_player = value;
252  for (int i = 0; i < self->players_count; i++) {
253  Player* player = &self->players[i];
254  player->penguins_count = my_min(player->penguins_count, self->penguins_per_player);
255  player->penguins =
256  realloc(player->penguins, sizeof(*player->penguins) * self->penguins_per_player);
257  }
258 }
259 
264 void game_set_players_count(Game* self, int count) {
265  assert(self->phase == GAME_PHASE_SETUP);
266  assert(count >= 0);
267  assert(self->players == NULL);
268  self->players = malloc(sizeof(*self->players) * count);
269  self->players_count = count;
270  for (int i = 0; i < count; i++) {
271  Player* player = &self->players[i];
272  player->id = (short)(i + 1);
273  player->name = NULL;
274  player->points = 0;
275  player->penguins_count = 0;
276  player->penguins = malloc(sizeof(*player->penguins) * my_max(0, self->penguins_per_player));
277  player->moves_count = 0;
278  player->color = 0;
279  }
280 }
281 
287 void game_set_player_name(Game* self, int idx, const char* name) {
288  assert(self->phase == GAME_PHASE_SETUP);
289  Player* player = game_get_player(self, idx);
290  free_and_clear(player->name);
291  player->name = name ? strdup(name) : NULL;
292 }
293 
297 void game_add_player_penguin(Game* self, int idx, Coords coords) {
298  Player* player = game_get_player(self, idx);
299  assert(0 <= player->penguins_count && player->penguins_count < self->penguins_per_player);
300  player->penguins[player->penguins_count++] = coords;
301 }
302 
306 void game_remove_player_penguin(Game* self, int idx, Coords coords) {
307  Player* player = game_get_player(self, idx);
308  Coords* penguin = game_find_player_penguin(self, idx, coords);
309  assert(penguin != NULL);
310  // The penguin pointer will be within the boundaries of the penguins array,
311  // this is legal.
312  int penguin_idx = (int)(penguin - player->penguins);
313  for (int i = penguin_idx; i < player->penguins_count - 1; i++) {
314  player->penguins[i] = player->penguins[i + 1];
315  }
316  player->penguins_count -= 1;
317 }
318 
330  // Notice that we don't return immediately after making a phase transition:
331  // this allows handling multiple consequent transitions in situations like if
332  // no player can make any moves after the placement phase ends, the game
333  // should just end.
334  if (self->phase == GAME_PHASE_SETUP) {
335  game_end_setup(self);
336  }
337  if (self->phase == GAME_PHASE_SETUP_DONE) {
338  placement_begin(self);
339  }
340  if (self->phase == GAME_PHASE_PLACEMENT) {
341  int result = placement_switch_player(self);
342  if (result < 0) {
343  placement_end(self);
344  movement_begin(self);
345  }
346  }
347  if (self->phase == GAME_PHASE_MOVEMENT) {
348  int result = movement_switch_player(self);
349  if (result < 0) {
350  movement_end(self);
351  game_end(self);
352  }
353  }
354 }
355 
358 void game_end(Game* self) {
359  assert(self->phase == GAME_PHASE_SETUP_DONE);
360  game_set_current_player(self, -1);
362 }
363 
368 void game_rewind_state_to_log_entry(Game* self, size_t target_entry) {
369  bool prev_log_disabled = self->log_disabled;
370  // No new entries should be created if we are redoing stuff.
371  self->log_disabled = true;
372 
373  // Undo
374  while (self->log_current > target_entry) {
375  const GameLogEntry* entry = game_get_log_entry(self, self->log_current - 1);
376  switch (entry->type) {
378  const GameLogPhaseChange* entry_data =
380  assert(self->phase == entry_data->new_phase);
381  // Phase switching within the undo/redo system is currently performed
382  // without calling the actual phase changing functions (movement_begin,
383  // game_end etc) as a simplification. Currently they don't really
384  // have much functionality besides checking preconditions and setting
385  // the current player, so there isn't much point in calling them anyway.
386  self->phase = entry_data->old_phase;
387  break;
388  }
390  const GameLogPlayerChange* entry_data =
392  assert(self->current_player_index == entry_data->new_player_index);
393  self->current_player_index = entry_data->old_player_index;
394  break;
395  }
396  case GAME_LOG_ENTRY_PLACEMENT: undo_place_penguin(self); break;
397  case GAME_LOG_ENTRY_MOVEMENT: undo_move_penguin(self); break;
398  }
399  }
400 
401  // Redo
402  for (; self->log_current < target_entry; self->log_current += 1) {
403  const GameLogEntry* entry = game_get_log_entry(self, self->log_current);
404  switch (entry->type) {
406  const GameLogPhaseChange* entry_data = &entry->data.phase_change;
407  assert(self->phase == entry_data->old_phase);
408  // The comment about phase changes from above applies here as well.
409  self->phase = entry_data->new_phase;
410  break;
411  }
413  const GameLogPlayerChange* entry_data = &entry->data.player_change;
414  assert(self->current_player_index == entry_data->old_player_index);
415  self->current_player_index = entry_data->new_player_index;
416  break;
417  }
419  const GameLogPlacement* entry_data = &entry->data.placement;
420  place_penguin(self, entry_data->target);
421  break;
422  }
424  const GameLogMovement* entry_data = &entry->data.movement;
425  move_penguin(self, entry_data->penguin, entry_data->target);
426  break;
427  }
428  }
429  }
430 
431  self->log_disabled = prev_log_disabled;
432 }
433 
434 extern bool game_check_player_index(const Game* self, int idx);
435 extern Player* game_get_player(const Game* self, int idx);
436 extern Player* game_get_current_player(const Game* self);
437 extern int game_find_player_by_id(const Game* self, short id);
438 extern Coords* game_find_player_penguin(const Game* self, int idx, Coords coords);
#define fnv32_hash_array(ptr, len)
#define fnv32_hash_value(value)
The core of the unified game logic library, contains the Game struct.
GamePhase
The values of Game::phase.
Definition: game.h:55
@ GAME_PHASE_PLACEMENT
Set by placement_begin.
Definition: game.h:59
@ GAME_PHASE_MOVEMENT
Set by movement_begin.
Definition: game.h:60
@ GAME_PHASE_SETUP
Set by game_begin_setup.
Definition: game.h:57
@ GAME_PHASE_NONE
The default phase, set when a Game is initially constructed.
Definition: game.h:56
@ GAME_PHASE_SETUP_DONE
Set by game_end_setup, placement_end, movement_end.
Definition: game.h:58
@ GAME_PHASE_END
Set by game_end.
Definition: game.h:61
GameLogEntryType
The values of GameLogEntry::type.
Definition: game.h:87
@ GAME_LOG_ENTRY_PLAYER_CHANGE
See GameLogPlayerChange.
Definition: game.h:89
@ GAME_LOG_ENTRY_PHASE_CHANGE
See GameLogPhaseChange.
Definition: game.h:88
@ GAME_LOG_ENTRY_PLACEMENT
See GameLogPlacement.
Definition: game.h:90
@ GAME_LOG_ENTRY_MOVEMENT
See GameLogMovement.
Definition: game.h:91
Movement phase functions.
Placement phase functions.
A pair of 2D coordinates, used for addressing the Game::board_grid.
Definition: utils.h:15
int x
Definition: utils.h:16
int y
Definition: utils.h:17
An entry in the Game::log_buffer, implemented as a tagged union.
Definition: game.h:156
union GameLogEntry::GameLogEntryData data
GameLogEntryType type
Definition: game.h:157
A GameLogEntry created by move_penguin.
Definition: game.h:113
Coords penguin
Definition: game.h:114
Coords target
Definition: game.h:115
A GameLogEntry created by game_set_phase.
Definition: game.h:95
GamePhase old_phase
Definition: game.h:96
GamePhase new_phase
Definition: game.h:97
A GameLogEntry created by place_penguin.
Definition: game.h:107
Coords target
Definition: game.h:108
A GameLogEntry created by game_set_current_player.
Definition: game.h:101
int old_player_index
Definition: game.h:102
int new_player_index
Definition: game.h:103
The central struct of the application, holds the game data and settings.
Definition: game.h:237
void game_set_penguins_per_player(Game *self, int value)
Sets Game::penguins_per_player (the value mustn't be negative) and allocates Player::penguins lists o...
Definition: game.c:248
void undo_place_penguin(Game *game)
Removes a GameLogPlacement entry from the log and undoes it.
Definition: placement.c:115
int movement_switch_player(Game *game)
Performs the player switching logic for the movement phase.
Definition: movement.c:31
Player * game_get_current_player(const Game *self)
A shorthand for calling game_get_player with Game::current_player_index.
Definition: game.h:401
int game_find_player_by_id(const Game *self, short id)
Returns an index of the player or -1 if no such player was found.
Definition: game.h:407
void move_penguin(Game *game, Coords start, Coords target)
Creates a GameLogMovement entry. The requested move must be valid.
Definition: movement.c:118
void game_set_log_capacity(Game *self, size_t capacity)
Sets Game::log_capacity and allocates that many elements in Game::log_buffer. If the new capacity is ...
Definition: game.c:121
const GameLogEntry * game_get_log_entry(const Game *self, size_t idx)
Returns a pointer to the entry at the given index. Note that the returned pointer is const because th...
Definition: game.c:173
void game_add_player_penguin(Game *self, int idx, Coords coords)
Definition: game.c:297
void game_set_current_player(Game *self, int idx)
Sets Game::current_player_index and creates a GameLogPlayerChange log entry.
Definition: game.c:195
Player * players
The list of players with length players_count. Initialized with game_set_players_count....
Definition: game.h:249
void game_begin_setup(Game *self)
Switches to the GAME_PHASE_SETUP phase, can only be called in GAME_PHASE_NONE. Should be called right...
Definition: game.c:209
void game_end(Game *self)
Switches to the GAME_PHASE_END phase.
Definition: game.c:358
void game_set_phase(Game *self, GamePhase phase)
Sets the current Game::phase and creates a GameLogPhaseChange log entry.
Definition: game.c:181
void game_remove_player_penguin(Game *self, int idx, Coords coords)
Definition: game.c:306
void game_advance_state(Game *self)
The all-in-one phase switcher that progresses of the game.
Definition: game.c:329
const GameLogEntry * game_pop_log_entry(Game *self, GameLogEntryType expected_type)
Pops the last entry off the top of the stack if its type matches the expected_type (this is used as a...
Definition: game.c:156
int board_width
Use setup_board for setting this.
Definition: game.h:264
uint32_t game_compute_state_hash(const Game *self)
Computes a hash of the game state part of the Game, i.e. the fields that change while playing the gam...
Definition: game.c:87
short * tile_attributes
Stores auxilary data of grid tiles for use in the UIs. Use setup_board for initializing,...
Definition: game.h:330
size_t log_capacity
The total number of elements log_buffer was allocated for (i.e. pushing more requires reallocating it...
Definition: game.h:348
short * board_grid
A 2D grid represented as a 1D array which stores the tiles of the board. Use setup_board for initiali...
Definition: game.h:306
GameLogEntry * log_buffer
The stack of log entries. Use game_push_log_entry, game_pop_log_entry and game_get_log_entry for modi...
Definition: game.h:343
void game_end_setup(Game *self)
Verifies that all fields have been initialized and configured and switches the phase from GAME_PHASE_...
Definition: game.c:224
Game * game_clone(const Game *other)
Creates a (deep) copy of another Game.
Definition: game.c:36
void placement_begin(Game *game)
Enters the GAME_PHASE_PLACEMENT phase, can only be called in GAME_PHASE_SETUP_DONE.
Definition: placement.c:11
Coords * game_find_player_penguin(const Game *self, int idx, Coords coords)
Finds a penguin with the given coordinates in the Player::penguins list of a player at idx and return...
Definition: game.h:420
int board_height
Use setup_board for setting this.
Definition: game.h:266
void game_rewind_state_to_log_entry(Game *self, size_t target_entry)
Successively undoes or redoes log entries in order to reset the game state to the entry at the given ...
Definition: game.c:368
int placement_switch_player(Game *game)
Performs the player switching logic for the placement phase.
Definition: placement.c:32
void movement_begin(Game *game)
Enters the GAME_PHASE_MOVEMENT phase, can only be called in GAME_PHASE_SETUP_DONE.
Definition: movement.c:11
void game_set_player_name(Game *self, int idx, const char *name)
Sets the Player::name of a player at the given index. Only available in the GAME_PHASE_SETUP phase.
Definition: game.c:287
int players_count
Use game_set_players_count for setting this.
Definition: game.h:251
void place_penguin(Game *game, Coords target)
Creates a GameLogPlacement entry. The requested placement must be valid.
Definition: placement.c:93
void movement_end(Game *game)
Exits the GAME_PHASE_MOVEMENT phase and switches to GAME_PHASE_SETUP_DONE.
Definition: movement.c:20
Player * game_get_player(const Game *self, int idx)
Returns a pointer to the player at the given index. Fails if the index isn't within the bounds of the...
Definition: game.h:394
void placement_end(Game *game)
Exits the GAME_PHASE_PLACEMENT phase and switches to GAME_PHASE_SETUP_DONE.
Definition: placement.c:20
Game * game_new(void)
Constructs a Game. Allocates memory for storing the struct itself, setting all fields to default valu...
Definition: game.c:15
void undo_move_penguin(Game *game)
Removes a GameLogMovement entry from the log and undoes it.
Definition: movement.c:142
bool game_check_player_index(const Game *self, int idx)
Checks if idx is within the bounds of Game::players.
Definition: game.h:387
void game_free(Game *self)
Destroys a Game, freeing the memory allocated for the struct itself and all associated internal lists...
Definition: game.c:68
GameLogEntry * game_push_log_entry(Game *self, GameLogEntryType type)
Creates a GameLogEntry, sets its GameLogEntry::type, pushes it on top of the stack (reallocating the ...
Definition: game.c:137
void game_set_players_count(Game *self, int count)
Sets Game::players_count (the value mustn't be negative) and allocates the Game::players list....
Definition: game.c:264
Holds the data of the players of the Game.
Definition: game.h:68
short id
The unique ID, usually (but not necessarily) is just index + 1.
Definition: game.h:70
char * name
Use game_set_player_name to set this.
Definition: game.h:72
int moves_count
The number of moves (penguin placements and movements) made by the player.
Definition: game.h:81
int color
The color of the penguins, currently used only in the TUI.
Definition: game.h:83
Coords * penguins
The list of the positions of all of the player's penguins.
Definition: game.h:79
int points
The score of the player, i.e. the number of collected fish.
Definition: game.h:74
int penguins_count
The length of the penguins array.
Definition: game.h:76
GameLogMovement movement
Definition: game.h:162
GameLogPlacement placement
Definition: game.h:161
GameLogPhaseChange phase_change
Definition: game.h:159
GameLogPlayerChange player_change
Definition: game.h:160
void * memdup(const void *src, size_t size)
A shorthand for malloc + memcpy (analogous to strdup).
Definition: utils.c:79
#define free_and_clear(ptr)
Calls free on a pointer and then sets it to NULL.
Definition: utils.h:92
#define my_min(x, y)
Compares two numbers and returns the smaller one.
Definition: utils.h:101
#define FNV32_INITIAL_STATE
A constant for fnv32_hash.
Definition: utils.h:138
#define my_max(x, y)
Compares two numbers and returns the larger one.
Definition: utils.h:99