penguins  1.0.0
BotState Struct Reference

Detailed Description

Contains temporary data created during the evaluation of bot's moves.

Unlike the Game, this is really just a complementary struct for bot_compute_move and bot_compute_placement and less of a class in the OOP sense, though it is implemented as such (refer to the documentation of Game for the explanation). The primary reason for this being the autonomous mode, where the program is invoked each time just to make a single move and is not kept alive for the entire game, meaning that we can't have some additional state (unless we store it in a file, which I didn't want to do to keep things simple). Hence the logic in the autonomous mode and, therefore, the bot algorithm, is generally stateless or, in other words, "fire and forget" – at any point of the game, you can create a BotState, which stores only the intermediate data, compute and make a move, and then destroy it (though of course the BotState objects can be reused later). You could say that this kind of makes the bot algorithm a function of the game state in the mathematical sense.

For the purposes of recursive evaluation (see bot_compute_move), the BotState is actually represented as a linked list (because why not) of substates. Also, it keeps around the previously allocated memory blocks for lists (the _cap fields are for capacity of the lists) because constantly making the OS allocate and free temporary buffers is slow, but also since this makes the overall memory management in the bot much simpler – every function can return whenever and not care about freeing stuff because bot_state_free will free everything in the end.

Additional note regarding the rating functions (bot_rate_placement and bot_rate_move): all score calculations are performed with integers and not floats because integer arithmetic is faster (though marginally on modern CPUs) than floating point one, and we need to perform A LOT of it.

Definition at line 101 of file bot.h.

Data Fields

volatile bool cancelled
 Can be set to true from another thread to cancel the move evaluation. More...
 
References to other stuff

The BotState isn't responsible for freeing these.

const BotParametersparams
 Shouldn't be changed while the bot is running (hence marked as const). More...
 
Gamegame
 May be modified during the move evaluation, but once the job is done the Game will be returned to its original state. More...
 
Rngrng
 Just the Rng, nothing special. More...
 
Substates
struct BotStatesubstate
 The link to the next recursive substate. Substates are allocated on demand by bot_enter_substate. More...
 
int depth
 The recursion depth of the current state, starts at 0 for the base state and increases in substates. More...
 
Allocation caches

See also bot_alloc_buf.

size_t tile_coords_cap
 
Coordstile_coords
 
size_t tile_scores_cap
 
int * tile_scores
 
size_t possible_steps_cap
 
PossibleStepspossible_steps
 
size_t all_moves_cap
 
BotMoveall_moves
 
size_t move_scores_cap
 
int * move_scores
 
size_t fill_grid1_cap
 
short * fill_grid1
 
size_t fill_grid2_cap
 
short * fill_grid2
 
size_t fill_stack_cap
 
FillSpanfill_stack
 

Related Functions

(Note that these are not member functions.)

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

Field Documentation

◆ params

const BotParameters* BotState::params

Shouldn't be changed while the bot is running (hence marked as const).

Definition at line 107 of file bot.h.

◆ game

Game* BotState::game

May be modified during the move evaluation, but once the job is done the Game will be returned to its original state.

Definition at line 110 of file bot.h.

Referenced by bot_flood_fill_check(), and bot_flood_fill_mark().

◆ rng

Rng* BotState::rng

Just the Rng, nothing special.

Definition at line 112 of file bot.h.

◆ substate

struct BotState* BotState::substate

The link to the next recursive substate. Substates are allocated on demand by bot_enter_substate.

Definition at line 122 of file bot.h.

Referenced by bot_enter_substate(), and bot_state_free().

◆ depth

int BotState::depth

The recursion depth of the current state, starts at 0 for the base state and increases in substates.

Definition at line 125 of file bot.h.

◆ cancelled

volatile bool BotState::cancelled

Can be set to true from another thread to cancel the move evaluation.

The volatile keyword stops the compiler from being too smart and overoptimizing stuff. For example, given this loop:

while (!self->cancelled) {
do_work();
}

The compiler will think: "Well, the cancelled flag in the condition doesn't get changed inside the loop, isn't it? No point in constantly loading its value from memory!" and optimize this into:

if (!self->cancelled) {
while (true) {
do_work();
}
}

The volatile keyword on the other hand ensures that every time the variable is used, the compiler doesn't optimize away (or reorder) the reads and writes to the memory, and they happen exactly in the way the programmer intended it in the code.

It, however, doesn't ensure synchronized access to the variable (or makes other guarantees) when it is used across multiple threads: for example, the changed value in memory might not be observed immediately by other processor cores (because of memory caches). Multithreading is not really the intended purpose of volatile and such use is generally frowned upon, but in our case its fine, since it isn't used for anything critical – the bot algorithm simply must be stopped eventually (the precise timing doesn't matter), doesn't require using compiler- or OS-specific thread synchronization facilities like mutexes or atomics (heard that C11 has those built-in), and a simple memory read of a value that almost never gets changed is generally faster than those.

See also
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
https://stackoverflow.com/a/246148/12005228
https://stackoverflow.com/a/2485733/12005228

Definition at line 171 of file bot.h.

◆ tile_coords_cap

size_t BotState::tile_coords_cap

Definition at line 177 of file bot.h.

◆ tile_coords

Coords* BotState::tile_coords

Definition at line 178 of file bot.h.

◆ tile_scores_cap

size_t BotState::tile_scores_cap

Definition at line 179 of file bot.h.

◆ tile_scores

int* BotState::tile_scores

Definition at line 180 of file bot.h.

◆ possible_steps_cap

size_t BotState::possible_steps_cap

Definition at line 182 of file bot.h.

◆ possible_steps

PossibleSteps* BotState::possible_steps

Definition at line 183 of file bot.h.

◆ all_moves_cap

size_t BotState::all_moves_cap

Definition at line 184 of file bot.h.

◆ all_moves

BotMove* BotState::all_moves

Definition at line 185 of file bot.h.

◆ move_scores_cap

size_t BotState::move_scores_cap

Definition at line 186 of file bot.h.

◆ move_scores

int* BotState::move_scores

Definition at line 187 of file bot.h.

◆ fill_grid1_cap

size_t BotState::fill_grid1_cap

Definition at line 188 of file bot.h.

◆ fill_grid1

short* BotState::fill_grid1

Definition at line 189 of file bot.h.

◆ fill_grid2_cap

size_t BotState::fill_grid2_cap

Definition at line 190 of file bot.h.

◆ fill_grid2

short* BotState::fill_grid2

Definition at line 191 of file bot.h.

◆ fill_stack_cap

size_t BotState::fill_stack_cap

Definition at line 192 of file bot.h.

Referenced by bot_flood_fill_alloc_stack().

◆ fill_stack

FillSpan* BotState::fill_stack

Definition at line 193 of file bot.h.

Referenced by bot_flood_fill_alloc_stack().

Friends And Related Function Documentation

◆ bot_state_new()

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

Constructs a BotState (similarly game_new).

Definition at line 42 of file bot.c.

Referenced by bot_enter_substate(), and run_autonomous_mode().

◆ bot_state_free()

void bot_state_free ( BotState self)
related

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

Definition at line 75 of file bot.c.

Referenced by run_autonomous_mode().

◆ bot_enter_substate()

BotState * bot_enter_substate ( BotState self)
related

Allocates BotState::substate if necessary and returns it.

Definition at line 94 of file bot.c.

Referenced by bot_rate_move().

◆ bot_compute_placement()

bool bot_compute_placement ( BotState self,
Coords out_target 
)
related

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 run_autonomous_mode().

◆ bot_rate_placement()

int bot_rate_placement ( BotState self,
Coords  penguin 
)
related

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.

Referenced by bot_compute_placement().

◆ bot_compute_move()

bool bot_compute_move ( BotState self,
Coords out_penguin,
Coords out_target 
)
related

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 run_autonomous_mode().

◆ bot_generate_all_moves_list()

BotMove * bot_generate_all_moves_list ( BotState self,
int  penguins_count,
Coords penguins,
int *  moves_count 
)
related

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.

Referenced by bot_compute_move(), and bot_rate_move().

◆ bot_rate_moves_list()

int * bot_rate_moves_list ( BotState self,
int  moves_count,
BotMove moves_list 
)
related

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.

Referenced by bot_compute_move(), and bot_rate_move().

◆ bot_rate_move()

int bot_rate_move ( BotState self,
BotMove  move 
)
related

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.

Referenced by bot_rate_moves_list().

◆ bot_quick_junction_check()

bool bot_quick_junction_check ( BotState self,
Coords  coords 
)
related

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.

Referenced by bot_rate_move(), and bot_rate_moves_list().

◆ bot_flood_fill_reset_grid()

short * bot_flood_fill_reset_grid ( BotState self,
short **  fill_grid,
size_t *  fill_grid_cap 
)
related

Allocates a grid for use in bot_flood_fill_count_fish and fills it with zeroes.

Definition at line 620 of file bot.c.

Referenced by bot_rate_move(), and bot_rate_moves_list().

◆ bot_flood_fill_check()

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

See bot_flood_fill_count_fish and flood_fill.

Definition at line 638 of file bot.c.

Referenced by bot_flood_fill_count_fish().

◆ bot_flood_fill_mark()

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

See bot_flood_fill_count_fish and flood_fill.

Definition at line 651 of file bot.c.

Referenced by bot_flood_fill_count_fish().

◆ bot_flood_fill_alloc_stack()

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

See bot_flood_fill_count_fish and flood_fill.

Definition at line 661 of file bot.c.

Referenced by bot_flood_fill_count_fish().

◆ bot_flood_fill_count_fish()

int bot_flood_fill_count_fish ( BotState self,
short *  grid,
Coords  start,
short  marker_value 
)
related

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.

Referenced by bot_rate_move(), and bot_rate_moves_list().


The documentation for this struct was generated from the following files: