use super::*;
use crate::sim::{CrabKind, Handedness, MAX_PLAYERS};
fn arena() -> Board {
let mut board = Board::new(9, 7, 5);
board.set_tile(4, 3, TileKind::Castle(1));
board
}
#[test]
fn the_easy_hand_slips_predictably() {
let straight = PlayerAction::Place {
x: 3,
y: 3,
dir: Direction::Up,
};
let slipped = PlayerAction::Place {
x: 3,
y: 3,
dir: Direction::Right,
};
let slips = |level: BotLevel, seat: PlayerId| {
(0..40)
.filter(|tick| fumble(straight, seat, level, *tick) == slipped)
.count()
};
assert_eq!(slips(BotLevel::Easy, 0), 10, "one placement in four");
assert_eq!(slips(BotLevel::Normal, 0), 5, "one in eight");
assert_eq!(slips(BotLevel::Hard, 0), 0, "fierce does not slip");
let counts: Vec<usize> = (0..MAX_PLAYERS as u8)
.map(|seat| slips(BotLevel::Easy, seat))
.collect();
assert!(counts.iter().all(|&n| n == 10), "{counts:?}");
let first: Vec<u64> = (0..MAX_PLAYERS as u8)
.map(|seat| {
(0..40)
.find(|tick| fumble(straight, seat, BotLevel::Easy, *tick) == slipped)
.unwrap_or(99)
})
.collect();
assert!(
first
.iter()
.collect::<std::collections::BTreeSet<_>>()
.len()
> 1,
"seats slip on the same tick: {first:?}"
);
assert_eq!(
fumble(PlayerAction::None, 0, BotLevel::Easy, 0),
PlayerAction::None
);
}
#[test]
fn only_fierce_chases_a_jackpot_across_the_board() {
let first_place = |level: BotLevel| {
let mut board = Board::new(15, 11, 5);
board.set_tile(7, 5, TileKind::Castle(1));
board.spawn_crab(14, 1, Direction::Up, Handedness::Left, CrabKind::Golden);
(0..40).find_map(|_| {
let action = bot_action(&board, 1, level);
board.tick_idle();
matches!(action, PlayerAction::Place { .. }).then_some(action)
})
};
let fierce = first_place(BotLevel::Hard);
assert!(
matches!(fierce, Some(PlayerAction::Place { x: 14, y: 0, .. })),
"fierce goes for the gold: {fierce:?}"
);
for level in [BotLevel::Easy, BotLevel::Normal] {
assert_eq!(
first_place(level),
None,
"{level:?} should not see a crab eleven tiles away"
);
}
}
#[test]
fn a_bot_pays_for_the_walk_to_the_tile() {
let mut board = arena();
assert!(board.place_signpost(1, 1, 1, Direction::Up));
let level = BotLevel::Normal;
assert!(hand_arrived(&board, 1, level, 2, 1));
assert!(hand_arrived(&board, 1, level, 1, 1), "already there");
assert!(!hand_arrived(&board, 1, level, 8, 6), "no teleporting");
for _ in 0..40 {
board.tick_idle();
}
assert!(!hand_arrived(&board, 1, level, 8, 6), "still walking");
board.tick_idle();
assert!(hand_arrived(&board, 1, level, 8, 6), "arrived at last");
let mut fresh = arena();
assert!(fresh.place_signpost(1, 1, 1, Direction::Up));
for _ in 0..30 {
fresh.tick_idle();
}
assert!(hand_arrived(&fresh, 1, BotLevel::Hard, 8, 6));
assert!(!hand_arrived(&fresh, 1, BotLevel::Easy, 8, 6));
let empty = arena();
assert!(hand_arrived(&empty, 1, BotLevel::Easy, 8, 6));
}
#[test]
fn the_hand_gate_is_a_pure_function_of_the_board() {
let mut board = arena();
board.spawn_gull(4, 1, Direction::Down);
let mut twin = board.clone();
for frame in 0..300 {
let mine = bot_action(&board, 1, BotLevel::Hard);
let theirs = bot_action(&twin, 1, BotLevel::Hard);
assert_eq!(mine, theirs, "two copies disagreed at frame {frame}");
let mut frame_actions = [PlayerAction::None; MAX_PLAYERS];
frame_actions[1] = mine;
board.tick(&frame_actions);
let mut twin_actions = [PlayerAction::None; MAX_PLAYERS];
twin_actions[1] = theirs;
twin.tick(&twin_actions);
}
}
#[test]
fn bot_blocks_an_incoming_gull() {
let mut board = arena();
board.spawn_gull(4, 1, Direction::Down); let action = loop {
match bot_action(&board, 1, BotLevel::Normal) {
PlayerAction::None => board.tick_idle(),
act @ (PlayerAction::Place { .. } | PlayerAction::Remove { .. }) => break act,
}
};
let PlayerAction::Place { y, dir, .. } = action else {
panic!("expected a defensive placement, got {action:?}");
};
assert_eq!(
dir,
Direction::Up,
"points the gull back where it came from"
);
assert!(y <= 3, "placed on the gull's approach");
}
#[test]
fn bot_recruits_crabs_and_banks_them() {
let mut board = arena();
board.spawn_crab(1, 1, Direction::Up, Handedness::Left, CrabKind::Common);
board.spawn_crab(7, 5, Direction::Down, Handedness::Right, CrabKind::Giant);
for _ in 0..900 {
let mut actions = [PlayerAction::None; MAX_PLAYERS];
actions[1] = bot_action(&board, 1, BotLevel::Normal);
board.tick(&actions);
}
assert!(
board.scores()[1] > 0,
"the bot routed at least one crab home (score {:?})",
board.scores()
);
}
#[test]
fn hard_defends_farther_than_normal() {
let mut probe = arena(); probe.spawn_gull(0, 2, Direction::Right); let normal = first_window_action(&probe, BotLevel::Normal);
let hard = first_window_action(&probe, BotLevel::Hard);
assert_eq!(normal, PlayerAction::None, "out of Normal's defend radius");
assert!(
matches!(hard, PlayerAction::Place { .. }),
"within Hard's defend radius, got {hard:?}"
);
}
#[test]
fn a_level_thinks_at_its_own_rate_and_no_seat_thinks_more() {
let cycles = 600u64;
for (level, cadence) in [
(BotLevel::Easy, 40u64),
(BotLevel::Normal, 20),
(BotLevel::Hard, 12),
] {
for seat in 0..MAX_PLAYERS as u8 {
let turns = (0..cycles * cadence)
.filter(|tick| level.acts_on(seat, *tick))
.count() as u64;
assert_eq!(
turns, cycles,
"{level:?} seat {seat} took {turns} turns in {cycles} windows"
);
}
}
let count = |level: BotLevel| (0..1200u64).filter(|tick| level.acts_on(1, *tick)).count();
assert_eq!(count(BotLevel::Easy) * 2, count(BotLevel::Normal));
}
#[test]
fn decision_moments_do_not_settle_on_one_parity() {
for seat in 0..MAX_PLAYERS as u8 {
let odd = (0..400u64)
.filter(|tick| BotLevel::Normal.acts_on(seat, *tick))
.filter(|tick| !tick.is_multiple_of(2))
.count();
assert!(
(4..=16).contains(&odd),
"seat {seat} landed on {odd} odd ticks out of 20 windows"
);
}
}
#[test]
fn hard_steers_gulls_at_the_leader() {
let mut board = Board::new(9, 7, 5);
board.set_tile(8, 6, TileKind::Castle(1)); board.set_tile(0, 0, TileKind::Castle(2));
board.set_score(2, 20); board.spawn_gull(2, 0, Direction::Down); let action = first_window_action(&board, BotLevel::Hard);
let PlayerAction::Place { x, y, dir } = action else {
panic!("expected an offensive placement, got {action:?}");
};
assert!(x <= 2 && y <= 2, "placed on the gull's approach ({x},{y})");
assert!(
dir == Direction::Left || dir == Direction::Up,
"points toward the leader's castle, got {dir:?}"
);
assert_eq!(
first_window_action(&board, BotLevel::Normal),
PlayerAction::None
);
}
fn first_window_action(board: &Board, level: BotLevel) -> PlayerAction {
let mut board = board.clone();
loop {
let action = bot_action(&board, 1, level);
if !level.acts_on(1, board.ticks()) {
board.tick_idle();
continue;
}
return action;
}
}
#[test]
fn hard_offense_edge_cases() {
let mut board = Board::new(9, 7, 5);
board.set_tile(0, 0, TileKind::Castle(1)); board.set_tile(8, 6, TileKind::Castle(2));
board.set_score(2, 20);
board.spawn_gull(8, 4, Direction::Down); assert_eq!(
first_window_action(&board, BotLevel::Hard),
PlayerAction::None
);
let mut board = Board::new(9, 7, 5);
board.set_tile(0, 0, TileKind::Castle(1));
board.set_tile(8, 0, TileKind::Castle(0));
board.set_tile(8, 6, TileKind::Castle(2));
board.set_score(0, 20);
board.set_score(2, 20);
board.spawn_gull(6, 6, Direction::Left); assert_eq!(
first_window_action(&board, BotLevel::Hard),
PlayerAction::None,
"the tied lead resolves to seat 0, far from this gull"
);
}
#[test]
fn fierce_shoves_gulls_into_kelp() {
let mut board = arena(); board.spawn_gull(4, 1, Direction::Down); board.set_tile(5, 2, TileKind::Kelp); for level in [BotLevel::Hard, BotLevel::Normal] {
assert_eq!(
first_window_action(&board, level),
PlayerAction::Place {
x: 4,
y: 2,
dir: Direction::Up
},
"{level:?} sends the gull back rather than around the weed"
);
}
let mut blocked = Board::new(9, 7, 5);
blocked.set_tile(4, 4, TileKind::Castle(1));
blocked.set_tile(4, 3, TileKind::Rock);
blocked.spawn_gull(4, 1, Direction::Down); blocked.set_tile(5, 2, TileKind::Kelp);
let PlayerAction::Place { x, y, dir } = first_window_action(&blocked, BotLevel::Hard) else {
panic!("expected a defensive placement");
};
assert_eq!((x, y), (4, 2), "the post lands in front of the gull");
assert_eq!(dir, Direction::Right, "pointed into the kelp");
assert_eq!(
first_window_action(&blocked, BotLevel::Normal),
PlayerAction::Place {
x: 4,
y: 2,
dir: Direction::Up
}
);
}
#[test]
fn a_fierce_kelp_shove_never_hands_a_gull_the_castle() {
let mut saw_right_hand = false;
for seed in 0..8 {
let mut board = Board::new(9, 7, seed);
board.set_tile(4, 3, TileKind::Castle(1));
board.set_score(1, 40);
board.spawn_gull(4, 1, Direction::Down);
board.set_tile(5, 2, TileKind::Kelp);
saw_right_hand |= board.gulls()[0].handed == Handedness::Right;
for _ in 0..400 {
let mut actions = [PlayerAction::None; MAX_PLAYERS];
actions[1] = bot_action(&board, 1, BotLevel::Hard);
board.tick(&actions);
}
assert_eq!(
board.scores()[1],
40,
"seed {seed}: the castle was raided ({:?})",
board.gulls()[0].handed
);
}
assert!(saw_right_hand, "no seed rolled a right-handed gull");
}
#[test]
fn fierce_routes_crabs_around_pools() {
let mut board = arena(); board.spawn_crab(1, 1, Direction::Left, Handedness::Left, CrabKind::Common);
board.set_tile(1, 1, TileKind::Pool);
let PlayerAction::Place { x, y, dir } = first_window_action(&board, BotLevel::Hard) else {
panic!("expected a recruiting placement");
};
assert_eq!((x, y), (0, 1));
assert_eq!(dir, Direction::Down, "dry route home, around the pool");
assert_eq!(
first_window_action(&board, BotLevel::Normal),
PlayerAction::Place {
x: 0,
y: 1,
dir: Direction::Right
}
);
}
#[test]
fn fierce_never_feeds_a_turnstile() {
let mut board = arena(); board.spawn_gull(4, 1, Direction::Down); board.set_tile(4, 1, TileKind::Turnstile { next_right: true });
assert_eq!(
first_window_action(&board, BotLevel::Hard),
PlayerAction::None
);
assert!(matches!(
first_window_action(&board, BotLevel::Normal),
PlayerAction::Place { .. }
));
}
#[test]
fn bot_without_a_castle_stays_idle() {
let mut board = Board::new(5, 5, 0);
board.spawn_gull(2, 0, Direction::Down);
board.spawn_crab(0, 2, Direction::Right, Handedness::Left, CrabKind::Golden);
let mut windows = 0;
for _ in 0..200 {
if BotLevel::Normal.acts_on(2, board.ticks()) {
windows += 1;
assert_eq!(
bot_action(&board, 2, BotLevel::Normal),
PlayerAction::None,
"tick {}",
board.ticks()
);
}
board.tick_idle();
}
assert_eq!(windows, 10, "every window was checked");
}