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.