33   self->placement_scan_area = 6;
 
   35   self->max_move_length = INT_MAX;
 
   36   self->recursion_limit = 4;
 
   37   self->junction_check_recursion_limit = 2;
 
   43   BotState* 
self = malloc(
sizeof(*
self));
 
   44   self->params = params;
 
   47   self->substate = NULL;
 
   49   self->cancelled = 
false;
 
   51   self->tile_coords_cap = 0;
 
   52   self->tile_coords = NULL;
 
   53   self->tile_scores_cap = 0;
 
   54   self->tile_scores = NULL;
 
   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;
 
   76   while (
self != NULL) {
 
   95   if (self->substate == NULL) {
 
   97     self->substate->depth = 
self->depth + 1;
 
   99   return self->substate;
 
  108 #define bot_alloc_buf(buf, capacity, size) \ 
  109   ((size_t)1 * size > capacity ? (buf = realloc(buf, sizeof(*buf) * size), capacity = size) : 0) 
  115   return abs(end.
x - start.
x) + abs(end.
y - start.
y);
 
  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) {
 
  144   Game* game = 
self->game;
 
  152         self->tile_coords[tiles_count++] = coords;
 
  156   if (tiles_count == 0) {
 
  162     Rng* rng = 
self->rng;
 
  163     int picked_tile_idx =
 
  165     *out_target = 
self->tile_coords[picked_tile_idx];
 
  169   bot_alloc_buf(self->tile_scores, self->tile_scores_cap, tiles_count);
 
  170   for (
int i = 0; i < tiles_count; i++) {
 
  172     if (self->cancelled) 
return false;
 
  176   assert(best_tile_idx >= 0);
 
  177   *out_target = 
self->tile_coords[best_tile_idx];
 
  187   if (self->cancelled) 
return score;
 
  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;
 
  197     for (
int y = area_start_y; y <= area_end_y; y++) {
 
  198       for (
int x = area_start_x; x <= area_end_x; x++) {
 
  201           short tile = 
get_tile(self->game, coords);
 
  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;
 
  215         short tile = 
get_tile(self->game, coords), fish, player_id;
 
  218           tile_score += 10 * fish;
 
  222           tile_score += player_id == my_player->
id ? -500 : -600;
 
  232       const int DIV_PRECISION = 1000;
 
  235       score += tile_score * 4 * DIV_PRECISION / (d * d + 1) / DIV_PRECISION;
 
  301   if (moves_count == 0) {
 
  307     Rng* rng = 
self->rng;
 
  308     int picked_move_idx =
 
  310     BotMove picked_move = 
self->all_moves[picked_move_idx];
 
  311     *out_penguin = picked_move.
penguin, *out_target = picked_move.
target;
 
  316   if (self->cancelled) 
return false;
 
  319   assert(best_index >= 0);
 
  320   BotMove picked_move = moves_list[best_index];
 
  321   *out_penguin = picked_move.
penguin, *out_target = picked_move.
target;
 
  331   BotState* 
self, 
int penguins_count, 
Coords* penguins, 
int* moves_count
 
  335   bot_alloc_buf(self->possible_steps, self->possible_steps_cap, penguins_count);
 
  337   for (
int i = 0; i < penguins_count; i++) {
 
  341       *moves_count += moves.
steps[dir];
 
  343     self->possible_steps[i] = moves;
 
  347   bot_alloc_buf(self->all_moves, self->all_moves_cap, *moves_count);
 
  349   for (
int i = 0; i < penguins_count; i++) {
 
  350     Coords penguin = penguins[i];
 
  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;
 
  362   assert(move_idx == *moves_count);
 
  363   return self->all_moves;
 
  370   if (self->cancelled) 
return NULL;
 
  371   Coords prev_penguin = { -1, -1 };
 
  373   short* fill_grid = NULL;
 
  375   bot_alloc_buf(self->move_scores, self->move_scores_cap, moves_count);
 
  376   for (
int i = 0; i < moves_count; i++) {
 
  379     if (self->cancelled) 
return NULL;
 
  384     if (self->depth == 0) {
 
  385       if (!(prev_penguin.
x == penguin.
x && prev_penguin.
y == penguin.
y)) {
 
  387           fishes_per_dir[dir] = 0;
 
  397             neighbor.
x += penguin.
x, neighbor.
y += penguin.
y;
 
  399             if (fill_grid[neighbor.
x + neighbor.
y * self->game->board_width] == 0) {
 
  400               short marker = (short)(dir + 1);
 
  408         short marker = fill_grid[target.x + target.y * 
self->game->board_width];
 
  409         int available_fish = fishes_per_dir[marker - 1];
 
  411           int missed_fish = fishes_per_dir[dir];
 
  412           if (missed_fish != 0) {
 
  414             score += 10 * (available_fish - missed_fish);
 
  420     self->move_scores[i] = score;
 
  421     prev_penguin = penguin;
 
  423   return self->move_scores;
 
  435   if (self->cancelled) 
return score;
 
  439   int move_len = 
distance(penguin, target);
 
  441   score += 64 / move_len - 8;
 
  442   short target_tile = 
get_tile(self->game, target);
 
  445   score += 10 * fish * fish;
 
  458     neighbor.
x += penguin.
x, neighbor.
y += penguin.
y;
 
  460     short other_tile = 
get_tile(self->game, neighbor);
 
  471     neighbor.
x += target.x, neighbor.
y += target.y;
 
  473     if (neighbor.
x == penguin.
x && neighbor.
y == penguin.
y) 
continue;
 
  474     short other_tile = 
get_tile(self->game, neighbor), other_player_id;
 
  476       if (other_player_id != my_player->
id) {
 
  479         if (2 <= blocked && blocked <= 3 && move_len == 1) {
 
  483         } 
else if (blocked > 2) {
 
  485           score += 500 * (blocked - 2);
 
  494   if (self->depth < self->params->recursion_limit) {
 
  497     if (self->depth <= self->params->junction_check_recursion_limit) {
 
  507           neighbor.
x += target.x, neighbor.
y += target.y;
 
  509           short marker = (short)(dir + 1);
 
  518           neighbor.
x += target.x, neighbor.
y += target.y;
 
  520           short other_tile = 
get_tile(self->game, neighbor);
 
  522             short marker = fill_grid[neighbor.
x + neighbor.
y * 
self->game->board_width];
 
  539     if (!self->cancelled) {
 
  541       if (best_index >= 0) {
 
  545         score += move_scores[best_index] * 3 / 4;
 
  574     neighbor.
x += coords.
x, neighbor.
y += coords.
y;
 
  575     connected[dir] = 
false;
 
  576     is_fish[dir] = 
false;
 
  578       short tile = 
get_tile(self->game, neighbor);
 
  587     if (is_fish[start_dir]) 
break;
 
  596     if (!is_fish[dir]) 
break;
 
  597     connected[dir] = 
true;
 
  600   for (dir = start_dir - 1; dir != start_dir; dir--) {
 
  602     if (!is_fish[dir]) 
break;
 
  603     connected[dir] = 
true;
 
  621   int w = 
self->game->board_width, h = 
self->game->board_height;
 
  623   memset(*fill_grid, 0, 
sizeof(**fill_grid) * w * h);
 
  642   if ((0 <= x && x < w && 0 <= y && y < h) && ctx->
fill_grid[x + y * w] == 0) {
 
  665   return self->fill_stack;
 
  698   FillSpan* (*alloc_stack)(
size_t capacity, 
void* data),
 
  701   if (*stack_len >= *stack_cap) {
 
  702     *stack_cap = 
my_max(*stack_cap * 2, 256);
 
  703     *stack = alloc_stack(*stack_cap, data);
 
  706   (*stack)[*stack_len] = span;
 
  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),
 
  733   if (!check(x, y, data)) 
return;
 
  735   size_t stack_len = 0;
 
  736   size_t stack_cap = 0;
 
  738 #define stack_push(x1, x2, y, dy) \ 
  739   flood_fill_push(&stack, &stack_len, &stack_cap, x1, x2, y, dy, alloc_stack, data) 
  743   while (stack_len > 0) {
 
  745     int x1 = span.
x1, x2 = span.
x2, y = span.
y, dy = span.
dy;
 
  747     if (check(x, y, data)) {
 
  748       while (check(x - 1, y, data)) {
 
  749         mark(x - 1, y, data);
 
  757       while (check(x1, y, data)) {
 
  766       while (x1 < x2 && !check(x1, y, data)) {
 
Functions for working with the game board (and the specifics of its encoding)
 
#define is_fish_tile(tile)
 
#define is_penguin_tile(tile)
 
#define is_water_tile(tile)
 
#define get_tile_player_id(tile)
 
#define get_tile_fish(tile)
 
#define bot_alloc_buf(buf, capacity, size)
A helper for allocating the buffers cached within the BotState.
 
#define stack_push(x1, x2, y, dy)
 
static int distance(Coords start, Coords end)
 
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.
 
static int pick_best_score(int scores_length, int *scores)
 
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)
 
BotPlacementStrategy
Values of BotParameters::placement_strategy.
 
@ BOT_PLACEMENT_MOST_FISH
Pick the tiles with the most fish in the vicinity.
 
@ BOT_PLACEMENT_SMART
The standard "smart" algorithm, see bot_compute_placement for its description.
 
@ BOT_PLACEMENT_RANDOM
Pick a random tile for placement.
 
@ BOT_PLACEMENT_FIRST_POSSIBLE
Pick the first possible tile (first tile in the first row).
 
BotMovementStrategy
Values of BotParameters::movement_strategy.
 
@ BOT_MOVEMENT_SMART
The standard "smart" algorithm, see bot_compute_move for its description.
 
@ BOT_MOVEMENT_RANDOM
Pick a random move.
 
@ BOT_MOVEMENT_FIRST_POSSIBLE
Pick the first possible move (also known as the "dumb" algorithm).
 
The core of the unified game logic library, contains the Game struct.
 
Movement phase functions.
 
Placement phase functions.
 
See bot_flood_fill_count_fish.
 
A penguin -> target pair.
 
Various parameters for the bot algorithm.
 
void init_bot_parameters(BotParameters *self)
Initializes all fields of the given BotParameters to default values.
 
Contains temporary data created during the evaluation of bot's moves.
 
bool bot_compute_placement(BotState *self, Coords *out_target)
Computes the best placement for the current player given the current game state.
 
static bool bot_flood_fill_check(int x, int y, void *data)
See bot_flood_fill_count_fish and flood_fill.
 
struct BotState * substate
The link to the next recursive substate. Substates are allocated on demand by bot_enter_substate.
 
static FillSpan * bot_flood_fill_alloc_stack(size_t capacity, void *data)
See bot_flood_fill_count_fish and flood_fill.
 
void bot_state_free(BotState *self)
Recursively destroys a BotState and its substates (similarly to game_free).
 
static void bot_flood_fill_mark(int x, int y, void *data)
See bot_flood_fill_count_fish and flood_fill.
 
int bot_rate_move(BotState *self, BotMove move)
The heart of the bot, assigns a score to the given move according to its usefulness.
 
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.
 
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.
 
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.
 
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.
 
Game * game
May be modified during the move evaluation, but once the job is done the Game will be returned to its...
 
BotState * bot_state_new(const BotParameters *params, Game *game, Rng *rng)
Constructs a BotState (similarly game_new).
 
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...
 
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.
 
BotState * bot_enter_substate(BotState *self)
Allocates BotState::substate if necessary and returns it.
 
int bot_rate_placement(BotState *self, Coords penguin)
Assigns a score to the placement at the given coordinates.
 
A pair of 2D coordinates, used for addressing the Game::board_grid.
 
Used internally by flood_fill.
 
The central struct of the application, holds the game data and settings.
 
ALWAYS_INLINE bool is_tile_in_bounds(const Game *game, Coords coords)
Checks if the given coords are within the bounds of the board.
 
Player * game_get_current_player(const Game *self)
A shorthand for calling game_get_player with Game::current_player_index.
 
void move_penguin(Game *game, Coords start, Coords target)
Creates a GameLogMovement entry. The requested move must be valid.
 
bool validate_placement_simple(const Game *game, Coords target)
 
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.
 
int board_width
Use setup_board for setting this.
 
int count_obstructed_directions(const Game *game, Coords penguin)
 
short * board_grid
A 2D grid represented as a 1D array which stores the tiles of the board. Use setup_board for initiali...
 
int board_height
Use setup_board for setting this.
 
PossibleSteps calculate_penguin_possible_moves(const Game *game, Coords start)
 
void undo_move_penguin(Game *game)
Removes a GameLogMovement entry from the log and undoes it.
 
Holds the data of the players of the Game.
 
short id
The unique ID, usually (but not necessarily) is just index + 1.
 
Coords * penguins
The list of the positions of all of the player's penguins.
 
int penguins_count
The length of the penguins array.
 
Exists purely to wrap an array of the numbers of steps in every possible Direction.
 
A wrapper around random number generators.
 
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).
 
const Coords NEIGHBOR_TO_COORDS[NEIGHBOR_MAX]
A table that maps Neighbor variants to relative Coords.
 
const Coords DIRECTION_TO_COORDS[DIRECTION_MAX]
A table that maps Direction variants to relative Coords.
 
const Neighbor DIRECTION_TO_NEIGHBOR[DIRECTION_MAX]
A table that maps Direction variants to corresponding Neighbor variants.
 
#define free_and_clear(ptr)
Calls free on a pointer and then sets it to NULL.
 
#define my_min(x, y)
Compares two numbers and returns the smaller one.
 
#define my_max(x, y)
Compares two numbers and returns the larger one.