Files
battleship/src/bot_player.c
T

183 lines
7.4 KiB
C

#include "bot_player.h"
#include <stddef.h>
#include <string.h>
static uint8_t cell_index(uint8_t x, uint8_t y) {
return (uint8_t)(y * kBoardWidth + x);
}
static bool coordinate_is_valid(coordinate_t coordinate) {
return coordinate.x < kBoardWidth && coordinate.y < kBoardHeight;
}
static uint32_t random_u32(bot_player_t *bot) {
if (bot->random.next_u32 == NULL) return 0U;
return bot->random.next_u32(bot->random.context);
}
static bool choose_unknown(const bot_player_t *bot, uint8_t start, bool checkerboard,
coordinate_t *coordinate) {
for (uint8_t offset = 0; offset < kBotKnowledgeCellCount; ++offset) {
const uint8_t index = (uint8_t)((start + offset) % kBotKnowledgeCellCount);
const uint8_t x = (uint8_t)(index % kBoardWidth);
const uint8_t y = (uint8_t)(index / kBoardWidth);
if (bot->knowledge[index] != BOT_CELL_UNKNOWN) continue;
if (checkerboard && ((x + y) & 1U) != 0U) continue;
*coordinate = (coordinate_t){x, y};
return true;
}
return false;
}
static uint8_t collect_hit_group(const bot_player_t *bot, uint8_t first, uint8_t *group,
uint8_t *min_x, uint8_t *max_x, uint8_t *min_y, uint8_t *max_y) {
uint8_t queue[kBotKnowledgeCellCount] = {0};
uint8_t count = 0;
uint8_t read = 0;
queue[count++] = first;
group[0] = first;
*min_x = *max_x = (uint8_t)(first % kBoardWidth);
*min_y = *max_y = (uint8_t)(first / kBoardWidth);
while (read < count) {
const uint8_t index = queue[read++];
const uint8_t x = (uint8_t)(index % kBoardWidth);
const uint8_t y = (uint8_t)(index / kBoardWidth);
const int8_t directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (uint8_t direction = 0; direction < 4; ++direction) {
const int16_t neighbour_x = (int16_t)x + directions[direction][0];
const int16_t neighbour_y = (int16_t)y + directions[direction][1];
if (neighbour_x < 0 || neighbour_y < 0 || neighbour_x >= kBoardWidth || neighbour_y >= kBoardHeight) continue;
const uint8_t neighbour = cell_index((uint8_t)neighbour_x, (uint8_t)neighbour_y);
if (bot->knowledge[neighbour] != BOT_CELL_HIT) continue;
bool already_present = false;
for (uint8_t item = 0; item < count; ++item) {
if (queue[item] == neighbour) {
already_present = true;
break;
}
}
if (already_present) continue;
queue[count] = neighbour;
group[count++] = neighbour;
if ((uint8_t)neighbour_x < *min_x) *min_x = (uint8_t)neighbour_x;
if ((uint8_t)neighbour_x > *max_x) *max_x = (uint8_t)neighbour_x;
if ((uint8_t)neighbour_y < *min_y) *min_y = (uint8_t)neighbour_y;
if ((uint8_t)neighbour_y > *max_y) *max_y = (uint8_t)neighbour_y;
}
}
return count;
}
static bool choose_target(bot_player_t *bot, coordinate_t *coordinate) {
uint8_t first = kBotKnowledgeCellCount;
for (uint8_t index = 0; index < kBotKnowledgeCellCount; ++index) {
if (bot->knowledge[index] == BOT_CELL_HIT) {
first = index;
break;
}
}
if (first == kBotKnowledgeCellCount) return false;
uint8_t group[kBotKnowledgeCellCount] = {0};
uint8_t min_x = 0;
uint8_t max_x = 0;
uint8_t min_y = 0;
uint8_t max_y = 0;
const uint8_t count = collect_hit_group(bot, first, group, &min_x, &max_x, &min_y, &max_y);
if (count >= 2U && min_y == max_y) {
const coordinate_t ends[2] = {{min_x == 0 ? kBoardWidth : (uint8_t)(min_x - 1U), min_y},
{max_x + 1U, min_y}};
const uint8_t first_end = (uint8_t)(random_u32(bot) & 1U);
for (uint8_t offset = 0; offset < 2; ++offset) {
const coordinate_t candidate = ends[(first_end + offset) & 1U];
if (coordinate_is_valid(candidate) && bot->knowledge[cell_index(candidate.x, candidate.y)] == BOT_CELL_UNKNOWN) {
*coordinate = candidate;
return true;
}
}
return false;
}
if (count >= 2U && min_x == max_x) {
const coordinate_t ends[2] = {{min_x, min_y == 0 ? kBoardHeight : (uint8_t)(min_y - 1U)},
{min_x, max_y + 1U}};
const uint8_t first_end = (uint8_t)(random_u32(bot) & 1U);
for (uint8_t offset = 0; offset < 2; ++offset) {
const coordinate_t candidate = ends[(first_end + offset) & 1U];
if (coordinate_is_valid(candidate) && bot->knowledge[cell_index(candidate.x, candidate.y)] == BOT_CELL_UNKNOWN) {
*coordinate = candidate;
return true;
}
}
return false;
}
const uint8_t hit_x = (uint8_t)(first % kBoardWidth);
const uint8_t hit_y = (uint8_t)(first / kBoardWidth);
const int8_t directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
const uint8_t first_direction = (uint8_t)(random_u32(bot) % 4U);
for (uint8_t offset = 0; offset < 4; ++offset) {
const int8_t *direction = directions[(first_direction + offset) % 4U];
const int16_t x = (int16_t)hit_x + direction[0];
const int16_t y = (int16_t)hit_y + direction[1];
if (x < 0 || y < 0 || x >= kBoardWidth || y >= kBoardHeight) continue;
if (bot->knowledge[cell_index((uint8_t)x, (uint8_t)y)] == BOT_CELL_UNKNOWN) {
*coordinate = (coordinate_t){(uint8_t)x, (uint8_t)y};
return true;
}
}
return false;
}
void bot_player_init(bot_player_t *bot, random_source_t random, scheduler_t scheduler) {
if (bot == NULL) return;
memset(bot, 0, sizeof(*bot));
bot->random = random;
bot->scheduler = scheduler;
}
bool bot_player_next_shot(bot_player_t *bot, coordinate_t *coordinate) {
if (bot == NULL || coordinate == NULL) return false;
bot->turn_pending = false;
if (choose_target(bot, coordinate)) return true;
const uint8_t start = (uint8_t)(random_u32(bot) % kBotKnowledgeCellCount);
return choose_unknown(bot, start, true, coordinate) || choose_unknown(bot, start, false, coordinate);
}
void bot_player_record_result(bot_player_t *bot, coordinate_t coordinate, const bot_shot_result_t *result) {
if (bot == NULL || result == NULL || !coordinate_is_valid(coordinate)) return;
const uint8_t index = cell_index(coordinate.x, coordinate.y);
bot->knowledge[index] = result->hit ? BOT_CELL_HIT : BOT_CELL_MISS;
if (!result->sunk || !result->hit) return;
uint8_t group[kBotKnowledgeCellCount] = {0};
uint8_t min_x = 0;
uint8_t max_x = 0;
uint8_t min_y = 0;
uint8_t max_y = 0;
const uint8_t count = collect_hit_group(bot, index, group, &min_x, &max_x, &min_y, &max_y);
for (uint8_t item = 0; item < count; ++item) bot->knowledge[group[item]] = BOT_CELL_BLOCKED;
const uint8_t from_x = min_x == 0 ? 0 : (uint8_t)(min_x - 1U);
const uint8_t from_y = min_y == 0 ? 0 : (uint8_t)(min_y - 1U);
const uint8_t to_x = max_x + 1U >= kBoardWidth ? (uint8_t)(kBoardWidth - 1U) : (uint8_t)(max_x + 1U);
const uint8_t to_y = max_y + 1U >= kBoardHeight ? (uint8_t)(kBoardHeight - 1U) : (uint8_t)(max_y + 1U);
for (uint8_t y = from_y; y <= to_y; ++y) {
for (uint8_t x = from_x; x <= to_x; ++x) {
const uint8_t neighbour = cell_index(x, y);
if (bot->knowledge[neighbour] == BOT_CELL_UNKNOWN) bot->knowledge[neighbour] = BOT_CELL_BLOCKED;
}
}
}
bool bot_player_schedule_turn(bot_player_t *bot) {
if (bot == NULL || bot->turn_pending || bot->scheduler.schedule_after_ms == NULL) return false;
const uint32_t delay_ms = 500U + (random_u32(bot) % 401U);
if (!bot->scheduler.schedule_after_ms(bot->scheduler.context, delay_ms)) return false;
bot->turn_pending = true;
return true;
}
void bot_player_cancel_turn(bot_player_t *bot) {
if (bot != NULL) bot->turn_pending = false;
}