feat: implement the ESP32 opponent
This commit is contained in:
+6
-2
@@ -1,7 +1,7 @@
|
||||
CC ?= cc
|
||||
CFLAGS ?= -std=c11 -Wall -Wextra -Werror -I../../include
|
||||
|
||||
all: test_command_queue test_game_domain
|
||||
all: test_command_queue test_game_domain test_bot_player
|
||||
|
||||
test_command_queue: test_command_queue.c ../../src/command_queue.c ../../src/application.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
@@ -9,9 +9,13 @@ test_command_queue: test_command_queue.c ../../src/command_queue.c ../../src/app
|
||||
run: all
|
||||
./test_command_queue
|
||||
./test_game_domain
|
||||
./test_bot_player
|
||||
|
||||
test_game_domain: test_game_domain.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
test_bot_player: test_bot_player.c ../../src/bot_player.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f test_command_queue test_game_domain
|
||||
rm -f test_command_queue test_game_domain test_bot_player
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "bot_player.h"
|
||||
#include "fleet_generator.h"
|
||||
#include "game_engine.h"
|
||||
|
||||
typedef struct { uint32_t value; } test_random_t;
|
||||
typedef struct { uint32_t delay_ms; uint8_t calls; bool accepted; } test_scheduler_t;
|
||||
|
||||
static uint32_t next_random(void *context) {
|
||||
test_random_t *random = context;
|
||||
random->value = random->value * 1664525U + 1013904223U;
|
||||
return random->value;
|
||||
}
|
||||
|
||||
static bool schedule_after(void *context, uint32_t delay_ms) {
|
||||
test_scheduler_t *scheduler = context;
|
||||
scheduler->delay_ms = delay_ms;
|
||||
++scheduler->calls;
|
||||
return scheduler->accepted;
|
||||
}
|
||||
|
||||
static bot_player_t new_bot(test_random_t *random, test_scheduler_t *scheduler) {
|
||||
bot_player_t bot;
|
||||
bot_player_init(&bot, (random_source_t){.next_u32 = next_random, .context = random},
|
||||
(scheduler_t){.schedule_after_ms = schedule_after, .context = scheduler});
|
||||
return bot;
|
||||
}
|
||||
|
||||
static void record(bot_player_t *bot, uint8_t x, uint8_t y, bool hit, bool sunk) {
|
||||
const bot_shot_result_t result = {.hit = hit, .sunk = sunk};
|
||||
bot_player_record_result(bot, (coordinate_t){x, y}, &result);
|
||||
}
|
||||
|
||||
static bool is_adjacent(coordinate_t source, coordinate_t candidate) {
|
||||
const int16_t dx = (int16_t)source.x - candidate.x;
|
||||
const int16_t dy = (int16_t)source.y - candidate.y;
|
||||
return (dx == 0 && (dy == 1 || dy == -1)) || (dy == 0 && (dx == 1 || dx == -1));
|
||||
}
|
||||
|
||||
static void test_targeting_and_cleanup(void) {
|
||||
test_random_t random = {.value = 3};
|
||||
test_scheduler_t scheduler = {.accepted = true};
|
||||
bot_player_t bot = new_bot(&random, &scheduler);
|
||||
coordinate_t next = {0};
|
||||
|
||||
record(&bot, 0, 0, true, false);
|
||||
assert(bot_player_next_shot(&bot, &next));
|
||||
assert(is_adjacent((coordinate_t){0, 0}, next));
|
||||
|
||||
bot_player_init(&bot, (random_source_t){.next_u32 = next_random, .context = &random},
|
||||
(scheduler_t){.schedule_after_ms = schedule_after, .context = &scheduler});
|
||||
record(&bot, 4, 4, true, false);
|
||||
record(&bot, 5, 4, true, false);
|
||||
record(&bot, 6, 4, false, false);
|
||||
assert(bot_player_next_shot(&bot, &next));
|
||||
assert(next.x == 3U && next.y == 4U);
|
||||
|
||||
bot_player_init(&bot, (random_source_t){.next_u32 = next_random, .context = &random},
|
||||
(scheduler_t){.schedule_after_ms = schedule_after, .context = &scheduler});
|
||||
record(&bot, 0, 0, true, true);
|
||||
assert(bot.knowledge[0] == BOT_CELL_BLOCKED);
|
||||
assert(bot.knowledge[1] == BOT_CELL_BLOCKED);
|
||||
assert(bot.knowledge[kBoardWidth] == BOT_CELL_BLOCKED);
|
||||
assert(bot_player_next_shot(&bot, &next));
|
||||
assert(next.x > 1U || next.y > 1U);
|
||||
}
|
||||
|
||||
static void test_nonblocking_schedule_and_cancel(void) {
|
||||
test_random_t random = {.value = 9};
|
||||
test_scheduler_t scheduler = {.accepted = true};
|
||||
bot_player_t bot = new_bot(&random, &scheduler);
|
||||
assert(bot_player_schedule_turn(&bot));
|
||||
assert(bot.turn_pending && scheduler.calls == 1U);
|
||||
assert(scheduler.delay_ms >= 500U && scheduler.delay_ms <= 900U);
|
||||
assert(!bot_player_schedule_turn(&bot));
|
||||
bot_player_cancel_turn(&bot);
|
||||
assert(!bot.turn_pending);
|
||||
scheduler.accepted = false;
|
||||
assert(!bot_player_schedule_turn(&bot));
|
||||
assert(!bot.turn_pending);
|
||||
}
|
||||
|
||||
static void test_final_bot_shot_uses_only_result(void) {
|
||||
test_random_t random = {.value = 17};
|
||||
test_scheduler_t scheduler = {.accepted = true};
|
||||
bot_player_t bot = new_bot(&random, &scheduler);
|
||||
for (uint8_t index = 1; index < kBoardCellCount; ++index) bot.knowledge[index] = BOT_CELL_BLOCKED;
|
||||
game_engine_t engine;
|
||||
game_engine_init(&engine);
|
||||
engine.state.phase = PHASE_IN_PROGRESS;
|
||||
engine.state.current_player = 1U;
|
||||
engine.state.boards[0].ships_alive = 1U;
|
||||
engine.state.boards[0].ships[0] = (ship_t){.x = 0, .y = 0, .length = 1, .horizontal = true};
|
||||
engine.state.boards[0].cells[0] = CELL_SHIP;
|
||||
|
||||
coordinate_t target = {0, 0};
|
||||
shot_result_t result = {0};
|
||||
assert(bot_player_next_shot(&bot, &target));
|
||||
/* The strategy sees this result, never engine.state.boards[0]. */
|
||||
assert(game_engine_shot(&engine, 1U, target, &result) == GAME_RESULT_OK);
|
||||
bot_player_record_result(&bot, target, &(bot_shot_result_t){.hit = result.hit, .sunk = result.sunk});
|
||||
assert(result.hit && result.sunk && result.finished);
|
||||
assert(engine.state.phase == PHASE_FINISHED && engine.state.winner == 1U);
|
||||
}
|
||||
|
||||
static fleet_generator_t generator_for(test_random_t *random) {
|
||||
return (fleet_generator_t){.random = {.next_u32 = next_random, .context = random}};
|
||||
}
|
||||
|
||||
static coordinate_t first_available_target(const board_t *board) {
|
||||
for (uint8_t index = 0; index < kBoardCellCount; ++index) {
|
||||
if (board->cells[index] == CELL_WATER || board->cells[index] == CELL_SHIP) {
|
||||
return (coordinate_t){(uint8_t)(index % kBoardWidth), (uint8_t)(index / kBoardWidth)};
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return (coordinate_t){0, 0};
|
||||
}
|
||||
|
||||
static void test_ten_thousand_games_without_hidden_board_access(void) {
|
||||
for (uint32_t seed = 0; seed < 10000U; ++seed) {
|
||||
test_random_t random = {.value = seed + 1000U};
|
||||
test_scheduler_t scheduler = {.accepted = true};
|
||||
const fleet_generator_t generator = generator_for(&random);
|
||||
game_engine_t engine;
|
||||
game_engine_init(&engine);
|
||||
assert(game_engine_start(&engine, seed + 1U, MODE_BOT, &generator) == GAME_RESULT_OK);
|
||||
bot_player_t bot = new_bot(&random, &scheduler);
|
||||
bool bot_shot[kBoardCellCount] = {0};
|
||||
for (uint16_t step = 0; step < 200U && engine.state.phase == PHASE_IN_PROGRESS; ++step) {
|
||||
const uint8_t player = engine.state.current_player;
|
||||
coordinate_t coordinate;
|
||||
shot_result_t result = {0};
|
||||
if (player == 1U) {
|
||||
assert(bot_player_next_shot(&bot, &coordinate));
|
||||
const uint8_t index = (uint8_t)(coordinate.y * kBoardWidth + coordinate.x);
|
||||
assert(!bot_shot[index]);
|
||||
bot_shot[index] = true;
|
||||
assert(game_engine_shot(&engine, player, coordinate, &result) == GAME_RESULT_OK);
|
||||
bot_player_record_result(&bot, coordinate, &(bot_shot_result_t){.hit = result.hit, .sunk = result.sunk});
|
||||
} else {
|
||||
coordinate = first_available_target(&engine.state.boards[1]);
|
||||
assert(game_engine_shot(&engine, player, coordinate, &result) == GAME_RESULT_OK);
|
||||
}
|
||||
}
|
||||
assert(engine.state.phase == PHASE_FINISHED);
|
||||
assert(engine.state.winner < kPlayerCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_targeting_and_cleanup();
|
||||
test_nonblocking_schedule_and_cancel();
|
||||
test_final_bot_shot_uses_only_result();
|
||||
test_ten_thousand_games_without_hidden_board_access();
|
||||
puts("bot player tests passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user