use super::lookahead::sim_action;
use super::protocol::Act;
use crate::sim::{Board, PlayerAction, PlayerId, fair_walk, hand_steps};
pub use crate::sim::{FAIR_LIFT, FAIR_TICKS_PER_TILE};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct SeatCursor {
pub at: (u8, u8),
order: Option<Act>,
walked: u32,
steps: u32,
still: u32,
}
impl SeatCursor {
pub fn home(board: &Board, seat: PlayerId) -> SeatCursor {
let at = board
.castle_of(seat)
.unwrap_or((board.width() / 2, board.height() / 2));
SeatCursor {
at,
order: None,
walked: 0,
steps: 0,
still: FAIR_TICKS_PER_TILE,
}
}
pub fn order(&self) -> Option<Act> {
self.order
}
pub fn step(
&mut self,
board: &Board,
seat: PlayerId,
act: Act,
fair: bool,
) -> (PlayerAction, Option<Act>) {
if !fair {
if let Some(target) = act.target() {
self.at = clamp(board, target);
}
return (
sim_action(board, seat, act),
(act != Act::None).then_some(act),
);
}
match act {
Act::None | Act::Clear => {}
Act::Place { .. } | Act::Remove { .. } | Act::Move { .. } => self.order = Some(act),
}
let target = self
.order
.map(|order| clamp(board, order.target().unwrap_or(self.at)));
self.walk(target);
if act == Act::Clear {
return (sim_action(board, seat, act), Some(act));
}
match self.order {
Some(order) if target == Some(self.at) => {
self.order = None;
(sim_action(board, seat, order), Some(order))
}
_ => (PlayerAction::None, None),
}
}
fn walk(&mut self, target: Option<(u8, u8)>) {
let walking = match target {
Some(target) if target != self.at => {
if self.still >= FAIR_TICKS_PER_TILE {
self.walked = 0;
self.steps = 0;
}
let mut allowed = 1;
while fair_walk(allowed + 1) <= self.walked {
allowed += 1;
}
let n = allowed
.saturating_sub(self.steps)
.min(hand_steps(self.at, target));
self.at = along(self.at, target, n);
self.steps += n;
true
}
_ => false,
};
self.still = if walking {
0
} else {
self.still.saturating_add(1)
};
self.walked = self.walked.saturating_add(1);
}
}
fn along(from: (u8, u8), to: (u8, u8), steps: u32) -> (u8, u8) {
let axis = |a: u8, b: u8| {
let moved = u32::from(a.abs_diff(b)).min(steps) as u8;
if b >= a { a + moved } else { a - moved }
};
(axis(from.0, to.0), axis(from.1, to.1))
}
fn clamp(board: &Board, (x, y): (u8, u8)) -> (u8, u8) {
(x.min(board.width() - 1), y.min(board.height() - 1))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::{Direction, classic_arena};
#[test]
fn without_the_rule_the_hand_is_where_the_act_says() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let act = Act::Place {
x: 9,
y: 7,
dir: Direction::Up,
};
let (action, done) = hand.step(&board, 0, act, false);
assert_eq!(
action,
PlayerAction::Place {
x: 9,
y: 7,
dir: Direction::Up
}
);
assert_eq!(done, Some(act));
assert_eq!(hand.at, (9, 7));
}
#[test]
fn under_the_rule_a_placement_waits_for_the_walk_and_none_never_cancels() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let start = hand.at;
let (tx, ty) = (start.0 + 4, start.1 + 2);
let act = Act::Place {
x: tx,
y: ty,
dir: Direction::Up,
};
let mut ticks = 0;
let mut next = act;
let landed = loop {
let (action, _) = hand.step(&board, 0, next, true);
next = Act::None;
if action != PlayerAction::None {
break action;
}
ticks += 1;
assert!(ticks < 100, "the walk never ended");
};
assert_eq!(landed, sim_action(&board, 0, act));
assert_eq!(ticks, fair_walk(4));
assert_eq!(ticks, FAIR_LIFT + 2 * FAIR_TICKS_PER_TILE);
assert_eq!(hand.at, (tx, ty));
}
#[test]
fn a_neighbouring_tile_is_one_keypress_away() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let (x, y) = hand.at;
let act = Act::Place {
x: x + 1,
y: y + 1,
dir: Direction::Left,
};
let (action, done) = hand.step(&board, 0, act, true);
assert_eq!(done, Some(act), "landed on the tick it was asked for");
assert_ne!(action, PlayerAction::None);
}
#[test]
fn asking_again_for_the_tile_being_walked_to_keeps_walking() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let (x, y) = hand.at;
let far = Act::Place {
x: x + 6,
y: y + 6,
dir: Direction::Up,
};
let mut ticks = 0;
loop {
let act = if ticks % 4 == 0 { far } else { Act::None };
let (action, _) = hand.step(&board, 0, act, true);
if action != PlayerAction::None {
break;
}
ticks += 1;
assert!(ticks < 100, "a repeated order never landed");
}
assert_eq!(ticks, fair_walk(6));
}
#[test]
fn a_new_order_replaces_the_one_walking_and_clear_needs_no_walk() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
hand.step(&board, 0, Act::Move { x: 11, y: 8 }, true);
assert_eq!(hand.order(), Some(Act::Move { x: 11, y: 8 }));
let (_, done) = hand.step(&board, 0, Act::Clear, true);
assert_eq!(done, Some(Act::Clear));
assert_eq!(
hand.order(),
Some(Act::Move { x: 11, y: 8 }),
"clear leaves the walk alone"
);
hand.step(&board, 0, Act::Move { x: 0, y: 0 }, true);
assert_eq!(hand.order(), Some(Act::Move { x: 0, y: 0 }));
}
#[test]
fn clearing_every_tick_does_not_freeze_the_walk() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let (x, y) = hand.at;
let far = Act::Move { x: x + 6, y };
hand.step(&board, 0, far, true);
for _ in 0..fair_walk(6) + 5 {
hand.step(&board, 0, Act::Clear, true);
}
assert_eq!(hand.at, (x + 6, y), "the hand walked on under the clears");
}
#[test]
fn a_step_at_a_time_walks_no_faster_than_a_held_key() {
let board = classic_arena(false, 2);
let mut hand = SeatCursor::home(&board, 0);
let start = hand.at;
let ticks = 20;
for _ in 0..ticks {
let (x, y) = hand.at;
hand.step(&board, 0, Act::Move { x: x + 1, y }, true);
}
let moved = hand_steps(start, hand.at);
let held = (0..=ticks)
.filter(|&s| fair_walk(s) < ticks)
.max()
.unwrap_or(0);
assert!(
moved <= held,
"{moved} tiles in {ticks} ticks, where a held key goes {held}"
);
}
}