penguins  1.0.0
interactive.c
Go to the documentation of this file.
1 #include "interactive.h"
2 #include "board.h"
3 #include "game.h"
4 #include "movement.h"
5 #include "placement.h"
6 #include "utils.h"
7 #include <stdbool.h>
8 #include <stdio.h>
9 
10 #ifdef _WIN32
11 #define WIN32_LEAN_AND_MEAN
12 #include <windows.h>
13 #endif
14 
15 // More information on ANSI escape sequences:
16 // <https://en.wikipedia.org/wiki/ANSI_escape_code>
17 // <https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences>
18 
19 #define ANSI_CSI "\033["
20 #define ANSI_SGR "m"
21 #define ANSI_SGR_RESET "0"
22 #define ANSI_SGR_BOLD "1"
23 #define ANSI_SGR_FORE_COLOR "3"
24 #define ANSI_SGR_BACK_COLOR "4"
25 #define ANSI_SGR_BLACK "0"
26 #define ANSI_SGR_RED "1"
27 #define ANSI_SGR_GREEN "2"
28 #define ANSI_SGR_YELLOW "3"
29 #define ANSI_SGR_BLUE "4"
30 #define ANSI_SGR_MAGENTA "5"
31 #define ANSI_SGR_CYAN "6"
32 #define ANSI_SGR_WHITE "7"
33 
34 #define ANSI_RESET ANSI_CSI ANSI_SGR_RESET ANSI_SGR
35 
36 #define PLAYER_COLORS_COUNT 5
37 static const char* const PLAYER_ANSI_COLORS[PLAYER_COLORS_COUNT] = {
39 };
40 static const char* const PLAYER_COLOR_NAMES[PLAYER_COLORS_COUNT] = {
41  "red", "green", "yellow", "blue", "magenta"
42 };
43 
44 static void clear_screen(void) {
45  fprintf(
46  stdout,
47  ANSI_CSI "H" // move the cursor to the top left corner
48  ANSI_CSI "2J" // clear the entire screen
49  );
50  fflush(stdout);
51 }
52 
53 void print_board(const Game* game) {
54  printf(" ");
55  for (int x = 0; x < game->board_width; x++) {
56  printf("%3d", x + 1);
57  }
58  printf("\n");
59  for (int y = 0; y < game->board_height; y++) {
60  printf("%3d|", y + 1);
61  for (int x = 0; x < game->board_width; x++) {
62  Coords coords = { x, y };
63  short tile = get_tile(game, coords);
64  if (is_water_tile(tile)) {
67  printf(" 0 " ANSI_RESET);
68  } else if (is_penguin_tile(tile)) {
69  int player_idx = game_find_player_by_id(game, get_tile_player_id(tile));
70  Player* player = game_get_player(game, player_idx);
71  int color = player->color % PLAYER_COLORS_COUNT;
75  printf("p%d " ANSI_RESET, player_idx + 1);
76  } else if (is_fish_tile(tile)) {
79  printf(" %d " ANSI_RESET, get_tile_fish(tile));
80  } else {
81  printf(" ");
82  }
83  }
84  printf("|\n");
85  }
86 }
87 
88 static void display_new_turn_message(Game* game) {
89  Player* player = game_get_current_player(game);
90  printf(
91  "\nPlayer " ANSI_CSI ANSI_SGR_FORE_COLOR "%s" ANSI_SGR "%d" ANSI_RESET "'s turn.\n",
93  game->current_player_index + 1
94  );
95  printf("\n");
96 }
97 
98 static void display_error_message(const char* message) {
99  printf("\n%s\n", message);
100 }
101 
102 void print_player_stats(const Game* game) {
103  printf("id\t| name\t| score\n");
104  for (int i = 0; i < game->players_count; i++) {
105  Player* player = game_get_player(game, i);
106  printf(
107  ANSI_CSI ANSI_SGR_FORE_COLOR "%s" ANSI_SGR "%d" ANSI_RESET "\t| %s\t| %d\n",
109  i + 1,
110  player->name,
111  player->points
112  );
113  }
114 }
115 
116 static bool scan_coords(Coords* out) {
117  bool ok = scanf("%d %d", &out->x, &out->y) != 2;
118  out->x -= 1, out->y -= 1;
119  return ok;
120 }
121 
122 void print_game_state(const Game* game) {
123  print_player_stats(game);
124  printf("\n");
125  print_board(game);
126 }
127 
128 static void update_game_state_display(const Game* game) {
129  clear_screen();
130  print_game_state(game);
131 }
132 
134  Rng rng = init_stdlib_rng();
135 
136 #ifdef _WIN32
137  // Processing of ANSI sequences must be enabled on Windows. See
138  // <https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing>.
139  HANDLE out_handle = GetStdHandle(STD_OUTPUT_HANDLE);
140  DWORD out_mode = 0;
141  GetConsoleMode(out_handle, &out_mode);
142  out_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
143  SetConsoleMode(out_handle, out_mode);
144 #endif
145 
146  clear_screen();
147 
148  Game* game = game_new();
149  game_begin_setup(game);
150 
151  int players_count;
152  printf("Please input number of players:\n");
153  scanf("%d", &players_count);
154  game_set_players_count(game, players_count);
155 
156  for (int i = 0; i < players_count; i++) {
157  Player* player = game_get_player(game, i);
158 
159  char name[33];
160  printf("Player %d, please input name:\n", i + 1);
161  scanf("%32s", name);
162  game_set_player_name(game, i, name);
163 
164  printf("Player %d, select your color:\n", i + 1);
165  for (int i = 0; i < PLAYER_COLORS_COUNT; i++) {
166  printf(
167  "%d - " ANSI_CSI ANSI_SGR_FORE_COLOR "%s" ANSI_SGR "%s" ANSI_RESET "\n",
168  i + 1,
171  );
172  }
173  int color_choice;
174  do {
175  scanf("%d", &color_choice);
176  } while (!(1 <= color_choice && color_choice <= PLAYER_COLORS_COUNT));
177  player->color = color_choice - 1;
178  }
179 
180  int penguin_count;
181  printf("Please input number of penguins per player:\n");
182  scanf("%d", &penguin_count);
183  game_set_penguins_per_player(game, penguin_count);
184 
185  int board_width;
186  int board_height;
187  printf("Please specify width and height of the board\n");
188  printf("E.g.: 10 5 -> width=10, height=5\n");
189  scanf("%d %d", &board_width, &board_height);
190  setup_board(game, board_width, board_height);
191  generate_board_random(game, &rng);
192 
193  game_end_setup(game);
194 
196  interactive_placement(game);
198  interactive_movement(game);
200  game_end(game);
201 
202  game_free(game);
203 
204  return 0;
205 }
206 
207 static const char* describe_placement_result(PlacementError result) {
208  switch (result) {
209  case PLACEMENT_VALID: return "";
211  return "Inputted coordinates are outside the bounds of the board";
212  case PLACEMENT_EMPTY_TILE: return "This tile is empty, you can't select an empty (water) tile";
213  case PLACEMENT_ENEMY_PENGUIN: return "This tile is already occupied by a penguin"; break;
214  case PLACEMENT_OWN_PENGUIN: return "This tile is already occupied by a penguin"; break;
215  case PLACEMENT_MULTIPLE_FISH: return "Only a tile with just one fish can be selected"; break;
216  }
217  return "ERROR: what on god's green earth did you just select???";
218 }
219 
221  Coords target = { 0, 0 };
222  placement_begin(game);
223  while (true) {
224  int result = placement_switch_player(game);
225  if (result < 0) break;
227  handle_placement_input(game, &target);
228  place_penguin(game, target);
230  }
231  placement_end(game);
232 }
233 
234 void handle_placement_input(Game* game, Coords* selected) {
235  printf(
236  "Player %d, please input x and y coordinates to place the penguin:\n",
237  game->current_player_index + 1
238  );
239  while (true) {
240  scan_coords(selected);
241  PlacementError result = validate_placement(game, *selected);
242  if (result != PLACEMENT_VALID) {
244  continue;
245  }
246  break;
247  }
248 }
249 
250 static const char* describe_movement_result(MovementError result) {
251  switch (result) {
252  case MOVEMENT_VALID: return "";
253  case MOVEMENT_OUT_OF_BOUNDS: return "You cant move oustide the board!"; break;
254  case MOVEMENT_CURRENT_LOCATION: return "Thats your current location"; break;
255  case MOVEMENT_DIAGONAL: return "You cant move diagonaly!"; break;
256  case MOVEMENT_NOT_A_PENGUIN: return "Chose a penguin for movement"; break;
257  case MOVEMENT_NOT_YOUR_PENGUIN: return "Chose YOUR PENGUIN for movement"; break;
258  case MOVEMENT_ONTO_EMPTY_TILE: return "Can't move onto an empty tile"; break;
259  case MOVEMENT_ONTO_PENGUIN: return "Can't move onto another penguin!"; break;
260  case MOVEMENT_OVER_EMPTY_TILE: return "You cant move over an empty tile!"; break;
261  case MOVEMENT_OVER_PENGUIN: return "You cant move over another penguin!"; break;
262  case MOVEMENT_PENGUIN_BLOCKED: return "There are no possible moves for this penguin!"; break;
263  }
264  return "";
265 }
266 
268  Coords target = { 0, 0 };
269  Coords penguin = { 0, 0 };
270  movement_begin(game);
271  while (true) {
272  int result = movement_switch_player(game);
273  if (result < 0) break;
275  handle_movement_input(game, &penguin, &target);
276  move_penguin(game, penguin, target);
278  }
279  movement_end(game);
280 }
281 
282 void handle_movement_input(Game* game, Coords* penguin, Coords* target) {
283  while (true) {
284  printf("Chose a penguin\n");
285  while (true) {
286  scan_coords(penguin);
287  MovementError result = validate_movement_start(game, *penguin);
288  if (result != MOVEMENT_VALID) {
290  continue;
291  }
292  break;
293  }
294  printf("Where do you want to move?\n");
295  while (true) {
296  scan_coords(target);
297  MovementError result = validate_movement(game, *penguin, *target, NULL);
298  if (result != MOVEMENT_VALID) {
300  continue;
301  }
302  break;
303  }
304  break;
305  }
306 }
void generate_board_random(Game *game, Rng *rng)
Generates the board by setting every tile purely randomly. The resulting board will look sort of like...
Definition: board.c:26
Functions for working with the game board (and the specifics of its encoding)
#define is_fish_tile(tile)
Definition: board.h:25
#define is_penguin_tile(tile)
Definition: board.h:26
#define is_water_tile(tile)
Definition: board.h:24
#define get_tile_player_id(tile)
Definition: board.h:28
#define get_tile_fish(tile)
Definition: board.h:27
The core of the unified game logic library, contains the Game struct.
#define ANSI_SGR_BLACK
Definition: interactive.c:25
void print_game_state(const Game *game)
Definition: interactive.c:122
static const char *const PLAYER_COLOR_NAMES[PLAYER_COLORS_COUNT]
Definition: interactive.c:40
#define ANSI_SGR_CYAN
Definition: interactive.c:31
static void display_error_message(const char *message)
Definition: interactive.c:98
#define ANSI_SGR_BACK_COLOR
Definition: interactive.c:24
#define ANSI_SGR_WHITE
Definition: interactive.c:32
#define ANSI_SGR_RED
Definition: interactive.c:26
#define ANSI_SGR
Definition: interactive.c:20
void handle_placement_input(Game *game, Coords *selected)
Definition: interactive.c:234
int run_interactive_mode(void)
Definition: interactive.c:133
void interactive_movement(Game *game)
Definition: interactive.c:267
#define ANSI_SGR_BLUE
Definition: interactive.c:29
static const char * describe_movement_result(MovementError result)
Definition: interactive.c:250
static void clear_screen(void)
Definition: interactive.c:44
#define ANSI_SGR_FORE_COLOR
Definition: interactive.c:23
static bool scan_coords(Coords *out)
Definition: interactive.c:116
static const char *const PLAYER_ANSI_COLORS[PLAYER_COLORS_COUNT]
Definition: interactive.c:37
#define ANSI_SGR_GREEN
Definition: interactive.c:27
#define ANSI_SGR_BOLD
Definition: interactive.c:22
#define ANSI_SGR_MAGENTA
Definition: interactive.c:30
#define PLAYER_COLORS_COUNT
Definition: interactive.c:36
static void display_new_turn_message(Game *game)
Definition: interactive.c:88
static const char * describe_placement_result(PlacementError result)
Definition: interactive.c:207
void print_board(const Game *game)
Definition: interactive.c:53
static void update_game_state_display(const Game *game)
Definition: interactive.c:128
void interactive_placement(Game *game)
Definition: interactive.c:220
#define ANSI_CSI
Definition: interactive.c:19
void print_player_stats(const Game *game)
Definition: interactive.c:102
#define ANSI_SGR_YELLOW
Definition: interactive.c:28
#define ANSI_RESET
Definition: interactive.c:34
void handle_movement_input(Game *game, Coords *penguin, Coords *target)
Definition: interactive.c:282
The interactive-mode text user interface.
Movement phase functions.
MovementError
Definition: movement.h:16
@ MOVEMENT_OVER_EMPTY_TILE
Definition: movement.h:25
@ MOVEMENT_PENGUIN_BLOCKED
Definition: movement.h:27
@ MOVEMENT_NOT_YOUR_PENGUIN
Definition: movement.h:22
@ MOVEMENT_NOT_A_PENGUIN
Definition: movement.h:21
@ MOVEMENT_ONTO_PENGUIN
Definition: movement.h:24
@ MOVEMENT_OUT_OF_BOUNDS
Definition: movement.h:18
@ MOVEMENT_CURRENT_LOCATION
Definition: movement.h:19
@ MOVEMENT_DIAGONAL
Definition: movement.h:20
@ MOVEMENT_VALID
Definition: movement.h:17
@ MOVEMENT_OVER_PENGUIN
Definition: movement.h:26
@ MOVEMENT_ONTO_EMPTY_TILE
Definition: movement.h:23
Placement phase functions.
PlacementError
Definition: placement.h:14
@ PLACEMENT_VALID
Definition: placement.h:15
@ PLACEMENT_OWN_PENGUIN
Definition: placement.h:19
@ PLACEMENT_MULTIPLE_FISH
Definition: placement.h:20
@ PLACEMENT_ENEMY_PENGUIN
Definition: placement.h:18
@ PLACEMENT_EMPTY_TILE
Definition: placement.h:17
@ PLACEMENT_OUT_OF_BOUNDS
Definition: placement.h:16
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
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
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
ALWAYS_INLINE short get_tile(const Game *game, Coords coords)
Returns the value of the tile at coords. Fails if coords are outside the bounds.
Definition: board.h:108
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
int board_width
Use setup_board for setting this.
Definition: game.h:264
void setup_board(Game *game, int width, int height)
Sets Game::board_width and Game::board_height and allocates Game::board_grid and Game::tile_attribute...
Definition: board.c:12
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
void placement_begin(Game *game)
Enters the GAME_PHASE_PLACEMENT phase, can only be called in GAME_PHASE_SETUP_DONE.
Definition: placement.c:11
int board_height
Use setup_board for setting this.
Definition: game.h:266
PlacementError validate_placement(const Game *game, Coords target)
Definition: placement.c:70
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
int current_player_index
A negative value means that there is no current player selected. Use game_set_current_player for sett...
Definition: game.h:256
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
MovementError validate_movement(const Game *game, Coords start, Coords target, Coords *fail)
Definition: movement.c:76
void place_penguin(Game *game, Coords target)
Creates a GameLogPlacement entry. The requested placement must be valid.
Definition: placement.c:93
MovementError validate_movement_start(const Game *game, Coords start)
Definition: movement.c:58
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 game_free(Game *self)
Destroys a Game, freeing the memory allocated for the struct itself and all associated internal lists...
Definition: game.c:68
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
char * name
Use game_set_player_name to set this.
Definition: game.h:72
int color
The color of the penguins, currently used only in the TUI.
Definition: game.h:83
int points
The score of the player, i.e. the number of collected fish.
Definition: game.h:74
A wrapper around random number generators.
Definition: utils.h:129
Rng init_stdlib_rng(void)
Returns an RNG implementation based on the rand function from <stdlib.h> (and seeds it with srand).
Definition: utils.c:123