use indicatif::{ProgressBar, ProgressStyle};
use pinch_points::sim::{
BotLevel, MAX_PLAYERS, PlayerAction, bot_action, classic_arena_seeded, generate_arena,
};
#[derive(Default)]
struct Tally {
score: f64,
placed: f64,
evicted: f64,
}
fn round(
mut board: pinch_points::sim::Board,
levels: [BotLevel; MAX_PLAYERS],
) -> [Tally; MAX_PLAYERS] {
let mut out: [Tally; MAX_PLAYERS] = Default::default();
let (cap, _) = board.signpost_rule();
while !board.round_over() {
let mut actions = [PlayerAction::None; MAX_PLAYERS];
let mut would_evict = [None; MAX_PLAYERS];
for seat in 0..MAX_PLAYERS {
let action = bot_action(&board, seat as u8, levels[seat]);
if let PlayerAction::Place { x, y, .. } = action {
out[seat].placed += 1.0;
let at_cap = board.signpost_count(seat as u8) >= usize::from(cap);
if at_cap && board.signpost_at(x, y).is_none() {
would_evict[seat] = Some((x, y));
}
}
actions[seat] = action;
}
board.tick(&actions);
for (seat, aimed) in would_evict.into_iter().enumerate() {
let landed = aimed.is_some_and(|(x, y)| {
board
.signpost_at(x, y)
.is_some_and(|sp| usize::from(sp.owner) == seat)
});
if landed {
out[seat].evicted += 1.0;
}
}
}
for (seat, tally) in out.iter_mut().enumerate() {
tally.score = f64::from(board.scores()[seat]);
}
out
}
fn duel(a: BotLevel, b: BotLevel, rounds: u64, bar: &ProgressBar) -> (f64, f64, [Tally; 2]) {
let (mut a_total, mut b_total) = (0.0, 0.0);
let mut habits: [Tally; 2] = Default::default();
for seed in 0..rounds {
for swapped in [false, true] {
let (lo, hi) = if swapped { (b, a) } else { (a, b) };
let mut levels = [lo; MAX_PLAYERS];
levels[2] = hi;
levels[3] = hi;
for board in [
classic_arena_seeded(0xB0A7 ^ seed, false, 4),
generate_arena(0xF00D ^ seed, 4, 12, 9),
] {
let tally = round(board, levels);
bar.inc(1);
let low: f64 = tally[0].score + tally[1].score;
let high: f64 = tally[2].score + tally[3].score;
if swapped {
a_total += high;
b_total += low;
} else {
a_total += low;
b_total += high;
}
let (a_pair, b_pair) = if swapped {
([2, 3], [0, 1])
} else {
([0, 1], [2, 3])
};
for (slot, seats) in [(0usize, a_pair), (1, b_pair)] {
for seat in seats {
habits[slot].placed += tally[seat].placed;
habits[slot].evicted += tally[seat].evicted;
}
}
}
}
}
(a_total, b_total, habits)
}
fn main() {
let rounds: u64 = std::env::var("ROUNDS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(40);
let name = |l: BotLevel| match l {
BotLevel::Easy => "easy",
BotLevel::Normal => "normal",
BotLevel::Hard => "fierce",
};
println!(
"Head to head, seats mirrored, {} rounds a pairing.\n",
rounds * 4
);
let pairings = [
(BotLevel::Easy, BotLevel::Normal),
(BotLevel::Normal, BotLevel::Hard),
(BotLevel::Easy, BotLevel::Hard),
];
let bar = ProgressBar::new(pairings.len() as u64 * rounds * 4);
bar.set_style(
ProgressStyle::with_template(
"{spinner} [{elapsed_precise}] {bar:40} {pos}/{len} rounds ETA {eta}",
)
.expect("progress template")
.progress_chars("=> "),
);
for (a, b) in pairings {
let (lo, hi, habits) = duel(a, b, rounds, &bar);
let per = (rounds * 8) as f64; bar.suspend(|| {
println!(
"{:>6} vs {:<6} {:5.1}% / {:5.1}% placements {:5.0} / {:5.0} \
self-evictions {:5.0} / {:5.0}",
name(a),
name(b),
lo / (lo + hi) * 100.0,
hi / (lo + hi) * 100.0,
habits[0].placed / per,
habits[1].placed / per,
habits[0].evicted / per,
habits[1].evicted / per,
)
});
}
bar.finish_and_clear();
}