penguins  1.0.0
bot.c
Go to the documentation of this file.
1 // The implementation of the bot algorithm was informed mainly by my vague
2 // memory of how chess bots work, plus some non-trivial on-the-spot solutions.
3 // The code leaves a lot to be desired: it is very inefficient (it has to
4 // evaluate a lot of useless moves, performs some costly calculations twice
5 // etc), is generally a spaghetti mess, and also apparently I have implemented
6 // cancellation incorrectly (the sub states don't see it since the `cancelled`
7 // flag is set only on the root state), but I think it's best to leave it in
8 // this imperfect state.
9 //
10 // If ever desired, here are some nice projects from which some inspiration
11 // could be pulled:
12 // <https://github.com/jthemphill/htmf>
13 // <https://github.com/camc/chess>
14 // <https://github.com/ethiery/HeyThatsMyFish>
15 
16 #include "bot.h"
17 #include "board.h"
18 #include "game.h"
19 #include "movement.h"
20 #include "placement.h"
21 #include "utils.h"
22 #include <assert.h>
23 #include <limits.h>
24 #include <stdbool.h>
25 #include <stddef.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
32  self->placement_strategy = BOT_PLACEMENT_SMART;
33  self->placement_scan_area = 6;
34  self->movement_strategy = BOT_MOVEMENT_SMART;
35  self->max_move_length = INT_MAX;
36  self->recursion_limit = 4;
37  self->junction_check_recursion_limit = 2;
38 }
39 
42 BotState* bot_state_new(const BotParameters* params, Game* game, Rng* rng) {
43  BotState* self = malloc(sizeof(*self));
44  self->params = params;
45  self->game = game;
46  self->rng = rng;
47  self->substate = NULL;
48  self->depth = 0;
49  self->cancelled = false;
50 
51  self->tile_coords_cap = 0;
52  self->tile_coords = NULL;
53  self->tile_scores_cap = 0;
54  self->tile_scores = NULL;
55 
56  self->possible_steps_cap = 0;
57  self->possible_steps = NULL;
58  self->all_moves_cap = 0;
59  self->all_moves = NULL;
60  self->move_scores_cap = 0;
61  self->move_scores = NULL;
62  self->fill_grid1_cap = 0;
63  self->fill_grid1 = NULL;
64  self->fill_grid2_cap = 0;
65  self->fill_grid2 = NULL;
66  self->fill_stack_cap = 0;
67  self->fill_stack = NULL;
68 
69  return self;
70 }
71 
75 void bot_state_free(BotState* self) {
76  while (self != NULL) {
77  free_and_clear(self->tile_coords);
78  free_and_clear(self->tile_scores);
79  free_and_clear(self->possible_steps);
80  free_and_clear(self->all_moves);
81  free_and_clear(self->move_scores);
82  free_and_clear(self->fill_grid1);
83  free_and_clear(self->fill_grid2);
84  free_and_clear(self->fill_stack);
85  BotState* next = self->substate;
86  self->substate = NULL;
87  free(self);
88  self = next;
89  }
90 }
91 
95  if (self->substate == NULL) {
96  self->substate = bot_state_new(self->params, self->game, self->rng);
97  self->substate->depth = self->depth + 1;
98  }
99  return self->substate;
100 }
101 
108 #define bot_alloc_buf(buf, capacity, size) \
109  ((size_t)1 * size > capacity ? (buf = realloc(buf, sizeof(*buf) * size), capacity = size) : 0)
110 
114 static inline int distance(Coords start, Coords end) {
115  return abs(end.x - start.x) + abs(end.y - start.y);
116 }
117 
118 static int pick_best_score(int scores_length, int* scores) {
119  int best_index = -1;
120  int best_score = INT_MIN;
121  for (int i = 0; i < scores_length; i++) {
122  int score = scores[i];
123  if (score >= best_score) {
124  best_score = score;
125  best_index = i;
126  }
127  }
128  return best_index;
129 }
130 
143 bool bot_compute_placement(BotState* self, Coords* out_target) {
144  Game* game = self->game;
145 
146  bot_alloc_buf(self->tile_coords, self->tile_coords_cap, game->board_width * game->board_height);
147  int tiles_count = 0;
148  for (int y = 0; y < game->board_height; y++) {
149  for (int x = 0; x < game->board_width; x++) {
150  Coords coords = { x, y };
151  if (validate_placement_simple(game, coords)) {
152  self->tile_coords[tiles_count++] = coords;
153  }
154  }
155  }
156  if (tiles_count == 0) {
157  return false;
158  }
159 
160  BotPlacementStrategy strategy = self->params->placement_strategy;
161  if (strategy == BOT_PLACEMENT_FIRST_POSSIBLE || strategy == BOT_PLACEMENT_RANDOM) {
162  Rng* rng = self->rng;
163  int picked_tile_idx =
164  strategy == BOT_PLACEMENT_RANDOM ? rng->random_range(rng, 0, tiles_count - 1) : 0;
165  *out_target = self->tile_coords[picked_tile_idx];
166  return true;
167  }
168 
169  bot_alloc_buf(self->tile_scores, self->tile_scores_cap, tiles_count);
170  for (int i = 0; i < tiles_count; i++) {
171  self->tile_scores[i] = bot_rate_placement(self, self->tile_coords[i]);
172  if (self->cancelled) return false;
173  }
174 
175  int best_tile_idx = pick_best_score(tiles_count, self->tile_scores);
176  assert(best_tile_idx >= 0);
177  *out_target = self->tile_coords[best_tile_idx];
178  return true;
179 }
180 
185 int bot_rate_placement(BotState* self, Coords penguin) {
186  int score = 0;
187  if (self->cancelled) return score;
188  Player* my_player = game_get_current_player(self->game);
189 
190  int area_start_x = penguin.x - self->params->placement_scan_area;
191  int area_start_y = penguin.y - self->params->placement_scan_area;
192  int area_end_x = penguin.x + self->params->placement_scan_area;
193  int area_end_y = penguin.y + self->params->placement_scan_area;
194 
195  if (self->params->placement_strategy == BOT_PLACEMENT_MOST_FISH) {
196  int total_fish = 0;
197  for (int y = area_start_y; y <= area_end_y; y++) {
198  for (int x = area_start_x; x <= area_end_x; x++) {
199  Coords coords = { x, y };
200  if (is_tile_in_bounds(self->game, coords)) {
201  short tile = get_tile(self->game, coords);
202  total_fish += get_tile_fish(tile);
203  }
204  }
205  }
206  return total_fish;
207  }
208 
209  for (int y = area_start_y; y <= area_end_y; y++) {
210  for (int x = area_start_x; x <= area_end_x; x++) {
211  if (x == penguin.x && y == penguin.y) continue;
212  Coords coords = { x, y };
213  int tile_score = 0;
214  if (is_tile_in_bounds(self->game, coords)) {
215  short tile = get_tile(self->game, coords), fish, player_id;
216  if ((fish = get_tile_fish(tile))) {
217  // Prioritize areas with more fish in the vicinity
218  tile_score += 10 * fish;
219  } else if ((player_id = get_tile_player_id(tile))) {
220  // Try to distribute the players uniformly (this doesn't work well in
221  // practice however)
222  tile_score += player_id == my_player->id ? -500 : -600;
223  } else if (is_water_tile(tile)) {
224  // We don't want to put the penguin beside the water
225  tile_score += -40;
226  }
227  } else {
228  // Makes the board bounds less unattractive
229  tile_score += 10;
230  }
231  int d = distance(penguin, coords);
232  const int DIV_PRECISION = 1000;
233  // The further away the tile is from the penguin, the less effect it
234  // should have on the calculation
235  score += tile_score * 4 * DIV_PRECISION / (d * d + 1) / DIV_PRECISION;
236  }
237  }
238 
239  // This is a significant one
240  score += count_obstructed_directions(self->game, penguin) * -1000;
241 
242  return score;
243 }
244 
295 bool bot_compute_move(BotState* self, Coords* out_penguin, Coords* out_target) {
296  Player* my_player = game_get_current_player(self->game);
297  int moves_count = 0;
298  BotMove* moves_list = bot_generate_all_moves_list(
299  self, my_player->penguins_count, my_player->penguins, &moves_count
300  );
301  if (moves_count == 0) {
302  return false;
303  }
304 
305  BotMovementStrategy strategy = self->params->movement_strategy;
306  if (strategy == BOT_MOVEMENT_FIRST_POSSIBLE || strategy == BOT_MOVEMENT_RANDOM) {
307  Rng* rng = self->rng;
308  int picked_move_idx =
309  strategy == BOT_MOVEMENT_RANDOM ? rng->random_range(rng, 0, moves_count - 1) : 0;
310  BotMove picked_move = self->all_moves[picked_move_idx];
311  *out_penguin = picked_move.penguin, *out_target = picked_move.target;
312  return true;
313  }
314 
315  int* move_scores = bot_rate_moves_list(self, moves_count, moves_list);
316  if (self->cancelled) return false;
317 
318  int best_index = pick_best_score(moves_count, move_scores);
319  assert(best_index >= 0);
320  BotMove picked_move = moves_list[best_index];
321  *out_penguin = picked_move.penguin, *out_target = picked_move.target;
322  return true;
323 }
324 
331  BotState* self, int penguins_count, Coords* penguins, int* moves_count
332 ) {
333  // The total number of all moves must be known before allocating the final
334  // list, so a list of PossibleSteps structs is collected first.
335  bot_alloc_buf(self->possible_steps, self->possible_steps_cap, penguins_count);
336  *moves_count = 0;
337  for (int i = 0; i < penguins_count; i++) {
338  PossibleSteps moves = calculate_penguin_possible_moves(self->game, penguins[i]);
339  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
340  moves.steps[dir] = my_min(moves.steps[dir], self->params->max_move_length);
341  *moves_count += moves.steps[dir];
342  }
343  self->possible_steps[i] = moves;
344  }
345 
346  // Then, the PossibleSteps are expanded into actual moves.
347  bot_alloc_buf(self->all_moves, self->all_moves_cap, *moves_count);
348  int move_idx = 0;
349  for (int i = 0; i < penguins_count; i++) {
350  Coords penguin = penguins[i];
351  PossibleSteps moves = self->possible_steps[i];
352  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
353  Coords d = DIRECTION_TO_COORDS[dir];
354  Coords target = penguin;
355  for (int steps = moves.steps[dir]; steps > 0; steps--) {
356  target.x += d.x, target.y += d.y;
357  BotMove move = { penguin, target };
358  self->all_moves[move_idx++] = move;
359  }
360  }
361  }
362  assert(move_idx == *moves_count);
363  return self->all_moves;
364 }
365 
369 int* bot_rate_moves_list(BotState* self, int moves_count, BotMove* moves_list) {
370  if (self->cancelled) return NULL;
371  Coords prev_penguin = { -1, -1 };
372  int fishes_per_dir[DIRECTION_MAX];
373  short* fill_grid = NULL;
374 
375  bot_alloc_buf(self->move_scores, self->move_scores_cap, moves_count);
376  for (int i = 0; i < moves_count; i++) {
377  BotMove move = moves_list[i];
378  int score = bot_rate_move(self, move);
379  if (self->cancelled) return NULL;
380  Coords penguin = move.penguin, target = move.target;
381 
382  // This is the primary junction check: whenever a choice must be made, we
383  // choose in favor of the direction from which the most fish is accessible.
384  if (self->depth == 0) {
385  if (!(prev_penguin.x == penguin.x && prev_penguin.y == penguin.y)) {
386  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
387  fishes_per_dir[dir] = 0;
388  }
389  fill_grid = NULL;
390  if (bot_quick_junction_check(self, penguin)) {
391  fill_grid = bot_flood_fill_reset_grid(self, &self->fill_grid1, &self->fill_grid1_cap);
392  // Fill the cells of the fill grid with the directions they are
393  // reachable from and set the elements of fishes_per_dir to the total
394  // number of fish reachable in that direction.
395  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
396  Coords neighbor = DIRECTION_TO_COORDS[dir];
397  neighbor.x += penguin.x, neighbor.y += penguin.y;
398  if (!is_tile_in_bounds(self->game, neighbor)) continue;
399  if (fill_grid[neighbor.x + neighbor.y * self->game->board_width] == 0) {
400  short marker = (short)(dir + 1);
401  fishes_per_dir[dir] = bot_flood_fill_count_fish(self, fill_grid, neighbor, marker);
402  }
403  }
404  }
405  }
406 
407  if (fill_grid) {
408  short marker = fill_grid[target.x + target.y * self->game->board_width];
409  int available_fish = fishes_per_dir[marker - 1];
410  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
411  int missed_fish = fishes_per_dir[dir];
412  if (missed_fish != 0) {
413  // Will give a bonus if the chosen direction has more fish
414  score += 10 * (available_fish - missed_fish);
415  }
416  }
417  }
418  }
419 
420  self->move_scores[i] = score;
421  prev_penguin = penguin;
422  }
423  return self->move_scores;
424 }
425 
433 int bot_rate_move(BotState* self, BotMove move) {
434  int score = 0;
435  if (self->cancelled) return score;
436  Coords penguin = move.penguin, target = move.target;
437  Player* my_player = game_get_current_player(self->game);
438 
439  int move_len = distance(penguin, target);
440  // Prioritize shorter moves
441  score += 64 / move_len - 8;
442  short target_tile = get_tile(self->game, target);
443  short fish = get_tile_fish(target_tile);
444  // Prioritize collecting more fish
445  score += 10 * fish * fish;
446 
447  if (self->depth == 0 && count_obstructed_directions(self->game, penguin) == 3) {
448  // Emergency escape mode
449  score += 1000;
450  }
451  if (self->depth == 0 && count_obstructed_directions(self->game, target) == 4) {
452  // A suicide move
453  score += -10000;
454  }
455 
456  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
457  Coords neighbor = DIRECTION_TO_COORDS[dir];
458  neighbor.x += penguin.x, neighbor.y += penguin.y;
459  if (!is_tile_in_bounds(self->game, neighbor)) continue;
460  short other_tile = get_tile(self->game, neighbor);
461  if (is_penguin_tile(other_tile) && distance(penguin, target) == 1) {
462  // This was supposed to prevent the bot from chasing the penguins of the
463  // stupidest first-available-move bots, though it didn't work in
464  // practice.
465  score += -1000;
466  }
467  }
468 
469  for (int dir = 0; dir < DIRECTION_MAX; dir++) {
470  Coords neighbor = DIRECTION_TO_COORDS[dir];
471  neighbor.x += target.x, neighbor.y += target.y;
472  if (!is_tile_in_bounds(self->game, neighbor)) continue;
473  if (neighbor.x == penguin.x && neighbor.y == penguin.y) continue;
474  short other_tile = get_tile(self->game, neighbor), other_player_id;
475  if ((other_player_id = get_tile_player_id(other_tile))) {
476  if (other_player_id != my_player->id) {
477  // One side will be blocked by us, so add 1.
478  int blocked = count_obstructed_directions(self->game, neighbor) + 1;
479  if (2 <= blocked && blocked <= 3 && move_len == 1) {
480  // This is also a (non-functional) chasing protection, see the
481  // comment above.
482  score += 250;
483  } else if (blocked > 2) {
484  // The aggressiveness bonus
485  score += 500 * (blocked - 2);
486  }
487  } else {
488  // Don't block our own penguins!
489  score += -1000;
490  }
491  }
492  }
493 
494  if (self->depth < self->params->recursion_limit) {
495  move_penguin(self->game, penguin, target);
496 
497  if (self->depth <= self->params->junction_check_recursion_limit) {
498  // Another junction check, this one discourages the bot from creating
499  // junctions in the first place. This prevents it from getting itself
500  // into trouble sometimes.
501  if (bot_quick_junction_check(self, target)) {
502  short* fill_grid =
503  bot_flood_fill_reset_grid(self, &self->fill_grid2, &self->fill_grid2_cap);
504  int dir;
505  for (dir = 0; dir < DIRECTION_MAX; dir++) {
506  Coords neighbor = DIRECTION_TO_COORDS[dir];
507  neighbor.x += target.x, neighbor.y += target.y;
508  if (!is_tile_in_bounds(self->game, neighbor)) continue;
509  short marker = (short)(dir + 1);
510  if (bot_flood_fill_count_fish(self, fill_grid, neighbor, marker) > 0) {
511  break;
512  }
513  }
514  // If there was no unobstructed direction, dir will be equal to
515  // DIRECTION_MAX at the start of this second loop.
516  for (; dir < DIRECTION_MAX; dir++) {
517  Coords neighbor = DIRECTION_TO_COORDS[dir];
518  neighbor.x += target.x, neighbor.y += target.y;
519  if (!is_tile_in_bounds(self->game, neighbor)) continue;
520  short other_tile = get_tile(self->game, neighbor);
521  if (is_fish_tile(other_tile)) {
522  short marker = fill_grid[neighbor.x + neighbor.y * self->game->board_width];
523  if (marker == 0) {
524  // If a tile in some other direction is not reachable from the
525  // direction at which we ran the flood fill, then we must created
526  // junction somewhere.
527  score += -200;
528  break;
529  }
530  }
531  }
532  }
533  }
534 
535  BotState* sub = bot_enter_substate(self);
536  int moves_count = 0;
537  BotMove* moves_list = bot_generate_all_moves_list(sub, 1, &target, &moves_count);
538  int* move_scores = bot_rate_moves_list(sub, moves_count, moves_list);
539  if (!self->cancelled) {
540  int best_index = pick_best_score(moves_count, move_scores);
541  if (best_index >= 0) {
542  // The score of recursive moves is scaled by a coefficient so as to
543  // encourage the bot to make quicker gains and not dream too much about
544  // the future, since the situation might change quickly.
545  score += move_scores[best_index] * 3 / 4;
546  }
547  }
548 
549  undo_move_penguin(self->game);
550  }
551 
552  return score;
553 }
554 
566  bool in_bounds[NEIGHBOR_MAX];
567  bool is_fish[NEIGHBOR_MAX];
568  bool connected[NEIGHBOR_MAX];
569 
570  // Collect the information about neighboring tiles first
571  int dir;
572  for (dir = 0; dir < NEIGHBOR_MAX; dir++) {
573  Coords neighbor = NEIGHBOR_TO_COORDS[dir];
574  neighbor.x += coords.x, neighbor.y += coords.y;
575  connected[dir] = false;
576  is_fish[dir] = false;
577  if ((in_bounds[dir] = is_tile_in_bounds(self->game, neighbor))) {
578  short tile = get_tile(self->game, neighbor);
579  is_fish[dir] = is_fish_tile(tile);
580  }
581  }
582 
583  // Find the starting direction
584  int start_dir = NEIGHBOR_MAX;
585  for (dir = 0; dir < DIRECTION_MAX; dir++) {
586  start_dir = DIRECTION_TO_NEIGHBOR[dir];
587  if (is_fish[start_dir]) break;
588  }
589  if (dir == DIRECTION_MAX) {
590  // The tile is obstructed on all sides - definitely not a junction.
591  return false;
592  }
593 
594  // Walk in the clockwise direction
595  for (dir = start_dir; dir < NEIGHBOR_MAX; dir++) {
596  if (!is_fish[dir]) break;
597  connected[dir] = true;
598  }
599  // Walk in the counter-clockwise direction
600  for (dir = start_dir - 1; dir != start_dir; dir--) {
601  if (dir < 0) dir += NEIGHBOR_MAX;
602  if (!is_fish[dir]) break;
603  connected[dir] = true;
604  }
605 
606  for (dir = 0; dir < DIRECTION_MAX; dir++) {
607  if (is_fish[DIRECTION_TO_NEIGHBOR[dir]] && !connected[DIRECTION_TO_NEIGHBOR[dir]]) {
608  // Found an unobstructed tile which is not connected - maybe a junction.
609  return true;
610  }
611  }
612 
613  // All neighbor tiles are connected - definitely not a junction.
614  return false;
615 }
616 
620 short* bot_flood_fill_reset_grid(BotState* self, short** fill_grid, size_t* fill_grid_cap) {
621  int w = self->game->board_width, h = self->game->board_height;
622  bot_alloc_buf(*fill_grid, *fill_grid_cap, w * h);
623  memset(*fill_grid, 0, sizeof(**fill_grid) * w * h);
624  return *fill_grid;
625 }
626 
630  BotState* self;
631  short* fill_grid;
634 };
635 
638 static bool bot_flood_fill_check(int x, int y, void* data) {
639  struct BotFloodFillCtx* ctx = data;
640  Game* game = ctx->self->game;
641  int w = game->board_width, h = game->board_height;
642  if ((0 <= x && x < w && 0 <= y && y < h) && ctx->fill_grid[x + y * w] == 0) {
643  short tile = game->board_grid[x + y * w];
644  return is_fish_tile(tile);
645  }
646  return false;
647 }
648 
651 static void bot_flood_fill_mark(int x, int y, void* data) {
652  struct BotFloodFillCtx* ctx = data;
653  Game* game = ctx->self->game;
654  int w = game->board_width;
655  ctx->fish_count += game->board_grid[x + y * w];
656  ctx->fill_grid[x + y * w] = ctx->marker_value;
657 }
658 
661 static FillSpan* bot_flood_fill_alloc_stack(size_t capacity, void* data) {
662  struct BotFloodFillCtx* ctx = data;
663  BotState* self = ctx->self;
665  return self->fill_stack;
666 }
667 
677 int bot_flood_fill_count_fish(BotState* self, short* grid, Coords start, short marker_value) {
678  assert(marker_value != 0);
679  struct BotFloodFillCtx ctx;
680  ctx.self = self;
681  ctx.fill_grid = grid;
682  ctx.fish_count = 0;
684  flood_fill(
686  );
687  return ctx.fish_count;
688 }
689 
690 static void flood_fill_push(
691  FillSpan** stack,
692  size_t* stack_len,
693  size_t* stack_cap,
694  int x1,
695  int x2,
696  int y,
697  int dy,
698  FillSpan* (*alloc_stack)(size_t capacity, void* data),
699  void* data
700 ) {
701  if (*stack_len >= *stack_cap) {
702  *stack_cap = my_max(*stack_cap * 2, 256);
703  *stack = alloc_stack(*stack_cap, data);
704  }
705  FillSpan span = { x1, x2, y, dy };
706  (*stack)[*stack_len] = span;
707  *stack_len += 1;
708 }
709 
726  int x,
727  int y,
728  bool (*check)(int x, int y, void* data),
729  void (*mark)(int x, int y, void* data),
730  FillSpan* (*alloc_stack)(size_t capacity, void* data),
731  void* data
732 ) {
733  if (!check(x, y, data)) return;
734 
735  size_t stack_len = 0;
736  size_t stack_cap = 0;
737  FillSpan* stack = NULL;
738 #define stack_push(x1, x2, y, dy) \
739  flood_fill_push(&stack, &stack_len, &stack_cap, x1, x2, y, dy, alloc_stack, data)
740 
741  stack_push(x, x, y, 1);
742  stack_push(x, x, y - 1, -1);
743  while (stack_len > 0) {
744  FillSpan span = stack[--stack_len];
745  int x1 = span.x1, x2 = span.x2, y = span.y, dy = span.dy;
746  int x = x1;
747  if (check(x, y, data)) {
748  while (check(x - 1, y, data)) {
749  mark(x - 1, y, data);
750  x -= 1;
751  }
752  }
753  if (x < x1) {
754  stack_push(x, x1 - 1, y - dy, -dy);
755  }
756  while (x1 <= x2) {
757  while (check(x1, y, data)) {
758  mark(x1, y, data);
759  stack_push(x, x1, y + dy, dy);
760  if (x1 > x2) {
761  stack_push(x2 + 1, x1, y - dy, -dy);
762  }
763  x1 += 1;
764  }
765  x1 += 1;
766  while (x1 < x2 && !check(x1, y, data)) {
767  x1 += 1;
768  }
769  x = x1;
770  }
771  }
772 
773 #undef stack_push
774 }
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
#define bot_alloc_buf(buf, capacity, size)
A helper for allocating the buffers cached within the BotState.
Definition: bot.c:108
#define stack_push(x1, x2, y, dy)
static int distance(Coords start, Coords end)
Definition: bot.c:114
void flood_fill(int x, int y, bool(*check)(int x, int y, void *data), void(*mark)(int x, int y, void *data), FillSpan *(*alloc_stack)(size_t capacity, void *data), void *data)
An implementation of flood fill using the span filling algorithm.
Definition: bot.c:725
static int pick_best_score(int scores_length, int *scores)
Definition: bot.c:118
static void flood_fill_push(FillSpan **stack, size_t *stack_len, size_t *stack_cap, int x1, int x2, int y, int dy, FillSpan *(*alloc_stack)(size_t capacity, void *data), void *data)
Definition: bot.c:690
The bot algorithm.
BotPlacementStrategy
Values of BotParameters::placement_strategy.
Definition: bot.h:18
@ BOT_PLACEMENT_MOST_FISH
Pick the tiles with the most fish in the vicinity.
Definition: bot.h:27
@ BOT_PLACEMENT_SMART
The standard "smart" algorithm, see bot_compute_placement for its description.
Definition: bot.h:21
@ BOT_PLACEMENT_RANDOM
Pick a random tile for placement.
Definition: bot.h:23
@ BOT_PLACEMENT_FIRST_POSSIBLE
Pick the first possible tile (first tile in the first row).
Definition: bot.h:25
BotMovementStrategy
Values of BotParameters::movement_strategy.
Definition: bot.h:31
@ BOT_MOVEMENT_SMART
The standard "smart" algorithm, see bot_compute_move for its description.
Definition: bot.h:34
@ BOT_MOVEMENT_RANDOM
Pick a random move.
Definition: bot.h:36
@ BOT_MOVEMENT_FIRST_POSSIBLE
Pick the first possible move (also known as the "dumb" algorithm).
Definition: bot.h:38
The core of the unified game logic library, contains the Game struct.
Movement phase functions.
Placement phase functions.
See bot_flood_fill_count_fish.
Definition: bot.c:629
short * fill_grid
Definition: bot.c:631
BotState * self
Definition: bot.c:630
int fish_count
Definition: bot.c:633
short marker_value
Definition: bot.c:632
A penguin -> target pair.
Definition: bot.h:62
Coords penguin
Definition: bot.h:63
Coords target
Definition: bot.h:63
Various parameters for the bot algorithm.
Definition: bot.h:44
void init_bot_parameters(BotParameters *self)
Initializes all fields of the given BotParameters to default values.
Definition: bot.c:31
Contains temporary data created during the evaluation of bot's moves.
Definition: bot.h:101
bool bot_compute_placement(BotState *self, Coords *out_target)
Computes the best placement for the current player given the current game state.
Definition: bot.c:143
static bool bot_flood_fill_check(int x, int y, void *data)
See bot_flood_fill_count_fish and flood_fill.
Definition: bot.c:638
FillSpan * fill_stack
Definition: bot.h:193
struct BotState * substate
The link to the next recursive substate. Substates are allocated on demand by bot_enter_substate.
Definition: bot.h:122
size_t fill_stack_cap
Definition: bot.h:192
static FillSpan * bot_flood_fill_alloc_stack(size_t capacity, void *data)
See bot_flood_fill_count_fish and flood_fill.
Definition: bot.c:661
void bot_state_free(BotState *self)
Recursively destroys a BotState and its substates (similarly to game_free).
Definition: bot.c:75
static void bot_flood_fill_mark(int x, int y, void *data)
See bot_flood_fill_count_fish and flood_fill.
Definition: bot.c:651
int bot_rate_move(BotState *self, BotMove move)
The heart of the bot, assigns a score to the given move according to its usefulness.
Definition: bot.c:433
BotMove * bot_generate_all_moves_list(BotState *self, int penguins_count, Coords *penguins, int *moves_count)
Creates a list with all the possible moves (as penguin -> target pairs) of the provided penguins.
Definition: bot.c:330
short * bot_flood_fill_reset_grid(BotState *self, short **fill_grid, size_t *fill_grid_cap)
Allocates a grid for use in bot_flood_fill_count_fish and fills it with zeroes.
Definition: bot.c:620
bool bot_quick_junction_check(BotState *self, Coords coords)
A precondition for junction checks to know if a more expensive flood fill test is necessary.
Definition: bot.c:565
int * bot_rate_moves_list(BotState *self, int moves_count, BotMove *moves_list)
Applies bot_rate_move to every move in the provided list and returns a pointer to the list of scores.
Definition: bot.c:369
Game * game
May be modified during the move evaluation, but once the job is done the Game will be returned to its...
Definition: bot.h:110
BotState * bot_state_new(const BotParameters *params, Game *game, Rng *rng)
Constructs a BotState (similarly game_new).
Definition: bot.c:42
int bot_flood_fill_count_fish(BotState *self, short *grid, Coords start, short marker_value)
Counts all fish accessible within an enclosed area starting at the given point using the flood fill a...
Definition: bot.c:677
bool bot_compute_move(BotState *self, Coords *out_penguin, Coords *out_target)
Computes the best move for the current player given the current game state.
Definition: bot.c:295
BotState * bot_enter_substate(BotState *self)
Allocates BotState::substate if necessary and returns it.
Definition: bot.c:94
int bot_rate_placement(BotState *self, Coords penguin)
Assigns a score to the placement at the given coordinates.
Definition: bot.c:185
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
Used internally by flood_fill.
Definition: bot.h:67
int y
Definition: bot.h:68
int x2
Definition: bot.h:68
int x1
Definition: bot.h:68
int dy
Definition: bot.h:68
The central struct of the application, holds the game data and settings.
Definition: game.h:237
ALWAYS_INLINE bool is_tile_in_bounds(const Game *game, Coords coords)
Checks if the given coords are within the bounds of the board.
Definition: board.h:70
Player * game_get_current_player(const Game *self)
A shorthand for calling game_get_player with Game::current_player_index.
Definition: game.h:401
void move_penguin(Game *game, Coords start, Coords target)
Creates a GameLogMovement entry. The requested move must be valid.
Definition: movement.c:118
bool validate_placement_simple(const Game *game, Coords target)
Definition: placement.c:64
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
int board_width
Use setup_board for setting this.
Definition: game.h:264
int count_obstructed_directions(const Game *game, Coords penguin)
Definition: movement.h:50
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
int board_height
Use setup_board for setting this.
Definition: game.h:266
PossibleSteps calculate_penguin_possible_moves(const Game *game, Coords start)
Definition: movement.h:64
void undo_move_penguin(Game *game)
Removes a GameLogMovement entry from the log and undoes it.
Definition: movement.c:142
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
Coords * penguins
The list of the positions of all of the player's penguins.
Definition: game.h:79
int penguins_count
The length of the penguins array.
Definition: game.h:76
Exists purely to wrap an array of the numbers of steps in every possible Direction.
Definition: movement.h:35
int steps[DIRECTION_MAX]
Definition: movement.h:36
A wrapper around random number generators.
Definition: utils.h:129
int(* random_range)(struct Rng *self, int min, int max)
Generates and returns a value in the range [min; max) (i.e. from min inclusive to max exclusive).
Definition: utils.h:132
const Coords NEIGHBOR_TO_COORDS[NEIGHBOR_MAX]
A table that maps Neighbor variants to relative Coords.
Definition: utils.c:27
const Coords DIRECTION_TO_COORDS[DIRECTION_MAX]
A table that maps Direction variants to relative Coords.
Definition: utils.c:19
const Neighbor DIRECTION_TO_NEIGHBOR[DIRECTION_MAX]
A table that maps Direction variants to corresponding Neighbor variants.
Definition: utils.c:35
#define free_and_clear(ptr)
Calls free on a pointer and then sets it to NULL.
Definition: utils.h:92
@ DIRECTION_MAX
Definition: utils.h:25
#define my_min(x, y)
Compares two numbers and returns the smaller one.
Definition: utils.h:101
@ NEIGHBOR_MAX
Definition: utils.h:37
#define my_max(x, y)
Compares two numbers and returns the larger one.
Definition: utils.h:99