penguins  1.0.0
bot.c File Reference

Go to the source code of this file.

Data Structures

struct  BotFloodFillCtx
 See bot_flood_fill_count_fish. More...
 

Macros

#define bot_alloc_buf(buf, capacity, size)    ((size_t)1 * size > capacity ? (buf = realloc(buf, sizeof(*buf) * size), capacity = size) : 0)
 A helper for allocating the buffers cached within the BotState. More...
 
#define stack_push(x1, x2, y, dy)    flood_fill_push(&stack, &stack_len, &stack_cap, x1, x2, y, dy, alloc_stack, data)
 

Functions

void init_bot_parameters (BotParameters *self)
 Initializes all fields of the given BotParameters to default values. More...
 
BotStatebot_state_new (const BotParameters *params, Game *game, Rng *rng)
 Constructs a BotState (similarly game_new). More...
 
void bot_state_free (BotState *self)
 Recursively destroys a BotState and its substates (similarly to game_free). More...
 
BotStatebot_enter_substate (BotState *self)
 Allocates BotState::substate if necessary and returns it. More...
 
static int distance (Coords start, Coords end)
 
static int pick_best_score (int scores_length, int *scores)
 
bool bot_compute_placement (BotState *self, Coords *out_target)
 Computes the best placement for the current player given the current game state. More...
 
int bot_rate_placement (BotState *self, Coords penguin)
 Assigns a score to the placement at the given coordinates. More...
 
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. More...
 
BotMovebot_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. More...
 
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. More...
 
int bot_rate_move (BotState *self, BotMove move)
 The heart of the bot, assigns a score to the given move according to its usefulness. More...
 
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. More...
 
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. More...
 
static bool bot_flood_fill_check (int x, int y, void *data)
 See bot_flood_fill_count_fish and flood_fill. More...
 
static void bot_flood_fill_mark (int x, int y, void *data)
 See bot_flood_fill_count_fish and flood_fill. More...
 
static FillSpanbot_flood_fill_alloc_stack (size_t capacity, void *data)
 See bot_flood_fill_count_fish and flood_fill. More...
 
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 algorithm. More...
 
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)
 
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. More...
 

Macro Definition Documentation

◆ bot_alloc_buf

#define bot_alloc_buf (   buf,
  capacity,
  size 
)     ((size_t)1 * size > capacity ? (buf = realloc(buf, sizeof(*buf) * size), capacity = size) : 0)

A helper for allocating the buffers cached within the BotState.

Checks if the buffer has enough capacity for data of the requested size, if not – reallocates it at the requested size. The capacity and size arguments are the number of elements (not the number of bytes), and are implicitly converted to size_t (the multiplication by 1 is used for that).

Definition at line 108 of file bot.c.

◆ stack_push

#define stack_push (   x1,
  x2,
  y,
  dy 
)     flood_fill_push(&stack, &stack_len, &stack_cap, x1, x2, y, dy, alloc_stack, data)

Function Documentation

◆ init_bot_parameters()

void init_bot_parameters ( BotParameters self)

Initializes all fields of the given BotParameters to default values.

Definition at line 31 of file bot.c.

Referenced by GamePanel::GamePanel().

◆ bot_state_new()

BotState * bot_state_new ( const BotParameters params,
Game game,
Rng rng 
)

Constructs a BotState (similarly game_new).

Definition at line 42 of file bot.c.

Referenced by BotThread::BotThread().

◆ bot_state_free()

void bot_state_free ( BotState self)

Recursively destroys a BotState and its substates (similarly to game_free).

Definition at line 75 of file bot.c.

◆ bot_enter_substate()

BotState * bot_enter_substate ( BotState self)

Allocates BotState::substate if necessary and returns it.

Definition at line 94 of file bot.c.

◆ distance()

static int distance ( Coords  start,
Coords  end 
)
inlinestatic

The only distance function that is relevant for us since penguins can move only along the axes.

See also
https://en.wikipedia.org/wiki/Taxicab_geometry

Definition at line 114 of file bot.c.

Referenced by BotState::bot_rate_move(), and BotState::bot_rate_placement().

◆ pick_best_score()

static int pick_best_score ( int  scores_length,
int *  scores 
)
static

◆ bot_compute_placement()

bool bot_compute_placement ( BotState self,
Coords out_target 
)

Computes the best placement for the current player given the current game state.

The algorithm for the placement phase is very simple:

  1. Generate a list of valid tiles for placement.
  2. Assign a score to every tile according to the rules in bot_rate_placement.
  3. Pick the tile with the highest score.
Returns
true if the function managed to find a placement, false if there are no possible placements for the player or if the computation was cancelled. The coordinates of the resulting tile are written to out_target.

Definition at line 143 of file bot.c.

Referenced by BotPlacementThread::Entry().

◆ bot_rate_placement()

int bot_rate_placement ( BotState self,
Coords  penguin 
)

Assigns a score to the placement at the given coordinates.

Evaluates the nearby tiles to determine how good the placement location is.

Definition at line 185 of file bot.c.

◆ bot_compute_move()

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.

The overall algorithm is roughly the following:

  1. Generate a list of all possible moves with bot_generate_all_moves_list.
  2. Evaluate all moves in the list by calling bot_rate_moves_list.
  3. Assign a score to every move using bot_rate_move.
    1. Some basic rules are considered first, such as: collecting more fish means a higher score, longer moves mean a lower score.
    2. Then, rules for making the bot aggressive are applied: if the move resulted in blocking an opponent's penguin, its score is increased dramatically.
    3. The scores of obviously stupid moves (such as blocking yourself with your own penguin) are reduced significantly so as to not take them seriously.
    4. And lastly, the most important part of the algorithm takes place: the move is applied to the Game instance, and the algorithm is repeated recursively from step 1 (generate a list of all moves available from there, assign scores to each and so on and so on). The best next move is selected and its score is added to the score of the currently considered move – this allows the bot to see future opportunities, including locating and blocking other penguins, which makes it REALLY aggressive (sometimes, though rarely, it even can discover combos). Afterwards, the applied move is undone.
  4. Finally, the move with the highest is selected and returned.

Note that when evaluating moves recursively, the provided Game is modified instead of creating a copy of it each time. However, recursive evaluation currently has a significant limitation – it only considers sequences of moves of a single penguin, and doesn't take the opponents' moves into account at all (doing otherwise consumes too much computing time).

Returns
true if a move was found, false if there were no moves available for the player or if the computation was cancelled. If found, the move is written to out_penguin and out_target.

The aggressive strategy proved to be very effective against other bots, which are usually passive (meaning that they simply try to collect fish) - it first blocks all other players and then collects the rest of the fish. It is even somewhat effective against humans because its aggressiveness essentially relies on the opponent making a mistake, which is not an unreasonable assumption when playing against a human (especially myself). Although, consequently, the stupidest bots, which are simply making literally the first available move after scanning the board (this is what the BOT_MOVEMENT_FIRST_POSSIBLE strategy does), ended up being the algorithm's fatal weakness (well, not exactly fatal, it can still win against those) – primarily because they are very persistent at just moving in a single direction.

Definition at line 295 of file bot.c.

Referenced by BotMovementThread::Entry().

◆ bot_generate_all_moves_list()

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.

Returns
A pointer to the resulting list, whose length is written to the moves_count argument.

Definition at line 330 of file bot.c.

◆ bot_rate_moves_list()

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 at line 369 of file bot.c.

◆ bot_rate_move()

int bot_rate_move ( BotState self,
BotMove  move 
)

The heart of the bot, assigns a score to the given move according to its usefulness.

In case a cancellation is requested (see BotState::cancelled), this function starts unwinding the recursive calls and returns some garbage score, but otherwise the move computation is stopped.

Definition at line 433 of file bot.c.

◆ bot_quick_junction_check()

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.

A "junction" is a state when then bot has to choose between two or more directions because once it makes a move the other paths will become inaccessible. This function only performs a quick heuristic check which can only tell if the current tile is definitely not a junction, so it is used to know if a further more time-consuming flood fill test is necessary.

Definition at line 565 of file bot.c.

◆ bot_flood_fill_reset_grid()

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 at line 620 of file bot.c.

◆ bot_flood_fill_check()

static bool bot_flood_fill_check ( int  x,
int  y,
void *  data 
)
static

See bot_flood_fill_count_fish and flood_fill.

Definition at line 638 of file bot.c.

◆ bot_flood_fill_mark()

static void bot_flood_fill_mark ( int  x,
int  y,
void *  data 
)
static

See bot_flood_fill_count_fish and flood_fill.

Definition at line 651 of file bot.c.

◆ bot_flood_fill_alloc_stack()

static FillSpan * bot_flood_fill_alloc_stack ( size_t  capacity,
void *  data 
)
static

See bot_flood_fill_count_fish and flood_fill.

Definition at line 661 of file bot.c.

◆ bot_flood_fill_count_fish()

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 algorithm.

Returns the number of reachable fish, the counted tiles are marked on the provided fill grid with marker_value, which must be non-zero. Additionally is used for junction checks.

See also
flood_fill

Definition at line 677 of file bot.c.

◆ flood_fill_push()

static void flood_fill_push ( FillSpan **  stack,
size_t *  stack_len,
size_t *  stack_cap,
int  x1,
int  x2,
int  y,
int  dy,
FillSpan *(*)(size_t capacity, void *data)  alloc_stack,
void *  data 
)
static

Definition at line 690 of file bot.c.

◆ flood_fill()

void flood_fill ( int  x,
int  y,
bool(*)(int x, int y, void *data)  check,
void(*)(int x, int y, void *data)  mark,
FillSpan *(*)(size_t capacity, void *data)  alloc_stack,
void *  data 
)

An implementation of flood fill using the span filling algorithm.

This is essentially the algorithm behind the "bucket" tool in paint programs. The code was pretty much copied (and translated from pseudocode) from Wikipedia.

Parameters
xthe starting point
ythe starting point
checka function that returns true if the given cell should be marked
marka function that marks the given cell
alloc_stacka function that allocates that stack for FillSpan s
dataextra data to be passed into the provided functions
See also
https://en.wikipedia.org/wiki/Flood_fill#Span_Filling
https://github.com/erich666/GraphicsGems/blob/c3263439c281da62df4a559ec8164cf8c9eb88ca/gems/SeedFill.c

Definition at line 725 of file bot.c.

Referenced by BotState::bot_flood_fill_count_fish().