use crate::sim::board::{Board, MAX_PLAYERS, TileKind};
use crate::sim::crab::Handedness;
use crate::sim::direction::Direction;
use crate::sim::map_gen::{mirror_dir, mirrored_tile};
use crate::sim::rng::Pcg32;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Orientation {
pub flip_x: bool,
pub flip_y: bool,
pub roll: u8,
}
impl Orientation {
pub fn for_round(seed: u64, width: u8, strip: u8) -> Orientation {
let mut rng = Pcg32::new(seed, 0x0f11);
let flip_x = rng.next_u32() % 2 == 1;
let flip_y = rng.next_u32() % 2 == 1;
let roll = match strip {
0 => 0,
strip => (rng.next_u32() % u32::from((width / strip).max(1))) as u8 * strip,
};
Orientation {
flip_x,
flip_y,
roll,
}
}
pub fn is_identity(self) -> bool {
self == Orientation::default()
}
pub fn place(self, (x, y): (u8, u8), width: u8, height: u8) -> (u8, u8) {
let x = ((u16::from(x) + u16::from(self.roll)) % u16::from(width)) as u8;
(
if self.flip_x { width - 1 - x } else { x },
if self.flip_y { height - 1 - y } else { y },
)
}
pub fn apply(self, board: &Board) -> Board {
if self.is_identity() {
return board.clone();
}
debug_assert_eq!(board.ticks(), 0, "only a beach at its first tick is turned");
debug_assert!(
self.roll == 0 || board.wrap(),
"only an open beach rolls round"
);
let (w, h) = (board.width(), board.height());
let (fx, fy) = (self.flip_x, self.flip_y);
let mut out = Board::new(w, h, board.seed());
out.set_wrap(board.wrap());
*out.rules_mut() = *board.rules();
out.set_events_enabled(board.events_enabled());
for (x, y, kind) in board.tiles() {
let (ix, iy) = self.place((x, y), w, h);
out.set_tile(ix, iy, mirrored_tile(kind, fx, fy));
}
for (x, y, _) in board.tiles() {
for dir in Direction::ALL {
let (dx, dy) = dir.offset();
let (nx, ny) = (i32::from(x) + dx, i32::from(y) + dy);
let inner = (0..i32::from(w)).contains(&nx) && (0..i32::from(h)).contains(&ny);
if inner && board.wall_at(x, y, dir) {
let (ix, iy) = self.place((x, y), w, h);
let turned = mirror_dir(dir, fx, fy);
debug_assert!(
!out.on_open_rim(ix, iy, turned),
"a wall rolled onto the seam of {w}x{h}"
);
out.set_wall(ix, iy, turned, true);
}
}
}
let swap_claws = fx ^ fy;
for crab in board.crabs() {
let (ix, iy) = self.place(board.coords_u8(crab.tile), w, h);
let handed = match (swap_claws, crab.handed) {
(false, handed) => handed,
(true, Handedness::Left) => Handedness::Right,
(true, Handedness::Right) => Handedness::Left,
};
out.spawn_crab(ix, iy, mirror_dir(crab.dir, fx, fy), handed, crab.kind);
}
for gull in board.gulls() {
let (ix, iy) = self.place(board.coords_u8(gull.tile), w, h);
out.spawn_gull(ix, iy, mirror_dir(gull.dir, fx, fy));
}
for (seat, &score) in board.scores().iter().enumerate() {
if score > 0 {
out.set_score(seat as u8, score);
}
}
out
}
}
pub fn deal_pairs(board: &mut Board, seats: u8, seed: u64) {
let pairs = usize::from(seats.min(MAX_PLAYERS as u8) / 2);
let mut rng = Pcg32::new(seed, 0x0dea_1ca5);
let mut order: Vec<usize> = (0..pairs).collect();
for i in (1..pairs).rev() {
let j = rng.next_u32() as usize % (i + 1);
order.swap(i, j);
}
let mut seat_of: [u8; MAX_PLAYERS] = std::array::from_fn(|castle| castle as u8);
for (seat_pair, &castle_pair) in order.iter().enumerate() {
let flip = usize::from(rng.next_u32().is_multiple_of(2));
seat_of[castle_pair * 2 + flip] = (seat_pair * 2) as u8;
seat_of[castle_pair * 2 + 1 - flip] = (seat_pair * 2 + 1) as u8;
}
let castles: Vec<(u8, u8, u8)> = board
.tiles()
.filter_map(|(x, y, kind)| match kind {
TileKind::Castle(owner) => Some((x, y, owner)),
TileKind::Empty
| TileKind::Rock
| TileKind::Spawner(_)
| TileKind::Turnstile { .. }
| TileKind::Kelp
| TileKind::Pool => None,
})
.collect();
for (x, y, owner) in castles {
if let Some(&seat) = seat_of.get(usize::from(owner)) {
board.set_tile(x, y, TileKind::Castle(seat));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::board::Spawner;
use crate::sim::crab::CrabKind;
#[test]
fn a_mirror_turns_what_has_a_facing_or_a_hand() {
let mut board = Board::new(5, 3, 9);
board.set_tile(
0,
1,
TileKind::Spawner(Spawner {
dir: Direction::Right,
period: 40,
}),
);
board.set_tile(1, 0, TileKind::Turnstile { next_right: true });
board.set_tile(4, 2, TileKind::Castle(0));
board.set_wall(2, 1, Direction::Right, true);
board.spawn_crab(2, 2, Direction::Right, Handedness::Left, CrabKind::Molting);
let flipped = Orientation {
flip_x: true,
..Orientation::default()
}
.apply(&board);
assert_eq!(
flipped.tile_at(4, 1),
TileKind::Spawner(Spawner {
dir: Direction::Left,
period: 40
})
);
assert_eq!(
flipped.tile_at(3, 0),
TileKind::Turnstile { next_right: false }
);
assert_eq!(flipped.tile_at(0, 2), TileKind::Castle(0));
assert!(flipped.wall_at(2, 1, Direction::Left), "the wall's image");
assert!(!flipped.wall_at(2, 1, Direction::Right));
let crab = flipped.crabs()[0];
assert_eq!(flipped.coords_u8(crab.tile), (2, 2));
assert_eq!(
(crab.dir, crab.handed),
(Direction::Left, Handedness::Right)
);
let turned = Orientation {
flip_x: true,
flip_y: true,
roll: 0,
}
.apply(&board);
assert_eq!(
turned.tile_at(3, 2),
TileKind::Turnstile { next_right: true }
);
assert_eq!(
turned.crabs()[0].handed,
Handedness::Left,
"a half turn keeps the claw"
);
assert_eq!(turned.tile_at(0, 0), TileKind::Castle(0));
}
#[test]
fn every_round_is_drawn_from_its_seed() {
assert_eq!(
Orientation::for_round(42, 21, 7),
Orientation::for_round(42, 21, 7)
);
let mut seen = std::collections::BTreeSet::new();
for seed in 0..200u64 {
let o = Orientation::for_round(seed, 21, 7);
assert_eq!(o.roll % 7, 0);
assert!(o.roll < 21);
seen.insert((o.flip_x, o.flip_y, o.roll));
}
assert_eq!(seen.len(), 12, "four mirrors by three strips: {seen:?}");
assert!((0..50u64).all(|seed| Orientation::for_round(seed, 13, 0).roll == 0));
}
#[test]
fn the_deal_keeps_opposite_pairs() {
let mut seen = [[false; MAX_PLAYERS]; MAX_PLAYERS];
for seed in 0..300u64 {
let mut board = Board::new(6, 2, 1);
for castle in 0..6u8 {
board.set_tile(castle, castle % 2, TileKind::Castle(castle));
}
deal_pairs(&mut board, 6, seed);
let spot = |seat: u8| board.castle_of(seat).expect("every seat dealt a castle");
for pair in 0..3u8 {
let (a, b) = (spot(pair * 2), spot(pair * 2 + 1));
assert_eq!(
a.0 / 2,
b.0 / 2,
"seed {seed}: seats {} and {}",
pair * 2,
pair * 2 + 1
);
}
for seat in 0..6u8 {
seen[usize::from(seat)][usize::from(spot(seat).0)] = true;
}
}
assert!(seen.iter().flatten().all(|&s| s), "{seen:?}");
}
}