#include "bot_player.h" #include #include 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; }