use crate::sim::{Board, Direction, TileKind};
pub(super) const SIZES: [(u8, u8); 4] = [(9, 7), (12, 9), (16, 11), (20, 13)];
pub(super) fn next_size(now: (u8, u8), up: bool) -> (u8, u8) {
super::setting::step_stop(&SIZES, now, up)
}
pub(super) struct Resized {
pub board: Board,
pub shift: (i16, i16),
pub cut: bool,
}
pub(super) fn resized(board: &Board, (w, h): (u8, u8)) -> Resized {
let dx = (i16::from(w) - i16::from(board.width())) / 2;
let dy = (i16::from(h) - i16::from(board.height())) / 2;
let to = |x: u8, y: u8| -> Option<(u8, u8)> {
let nx = u8::try_from(i16::from(x) + dx).ok().filter(|&nx| nx < w)?;
let ny = u8::try_from(i16::from(y) + dy).ok().filter(|&ny| ny < h)?;
Some((nx, ny))
};
let mut out = Board::new(w, h, board.seed());
*out.rules_mut() = *board.rules();
out.set_wrap(board.wrap());
let mut cut = false;
for (x, y, kind) in board.tiles() {
match to(x, y) {
Some((nx, ny)) => out.set_tile(nx, ny, kind),
None => cut |= kind != TileKind::Empty,
}
}
for (x, y, _) in board.tiles() {
for dir in [Direction::Right, Direction::Down] {
let (ox, oy) = dir.offset();
let (Ok(bx), Ok(by)) = (
u8::try_from(i32::from(x) + ox),
u8::try_from(i32::from(y) + oy),
) else {
continue;
};
if bx >= board.width() || by >= board.height() || !board.wall_at(x, y, dir) {
continue;
}
match (to(x, y), to(bx, by)) {
(Some((nx, ny)), _) => out.set_wall(nx, ny, dir, true),
(None, Some((nx, ny))) => out.set_wall(nx, ny, dir.right().right(), true),
(None, None) => cut = true,
}
}
}
for crab in board.crabs() {
let (x, y) = board.coords_u8(crab.tile);
match to(x, y) {
Some((nx, ny)) => out.spawn_crab(nx, ny, crab.dir, crab.handed, crab.kind),
None => cut = true,
}
}
for gull in board.gulls() {
let (x, y) = board.coords_u8(gull.tile);
match to(x, y) {
Some((nx, ny)) => out.spawn_gull(nx, ny, gull.dir),
None => cut = true,
}
}
Resized {
board: out,
shift: (dx, dy),
cut,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::{CrabKind, Handedness};
#[test]
fn a_resize_keeps_the_level_centred() {
let mut board = Board::new(9, 7, 5);
board.set_tile(4, 3, TileKind::Castle(0));
board.set_tile(2, 2, TileKind::Rock);
board.set_wall(4, 3, Direction::Left, true);
board.spawn_crab(6, 3, Direction::Left, Handedness::Right, CrabKind::Giant);
board.spawn_gull(3, 5, Direction::Up);
let grown = resized(&board, (12, 9));
assert!(!grown.cut);
assert_eq!(grown.shift, (1, 1));
let big = &grown.board;
assert_eq!(big.tile_at(5, 4), TileKind::Castle(0));
assert_eq!(big.tile_at(3, 3), TileKind::Rock);
assert!(big.wall_at(5, 4, Direction::Left));
assert!(
!big.wall_at(1, 4, Direction::Left),
"the old fence stays out"
);
assert!(big.wall_at(0, 4, Direction::Left), "the new fence is up");
assert_eq!(big.coords_u8(big.crabs()[0].tile), (7, 4));
assert_eq!(big.crabs()[0].kind, CrabKind::Giant);
assert_eq!(big.coords_u8(big.gulls()[0].tile), (4, 6));
let back = resized(big, (9, 7));
assert!(!back.cut);
assert_eq!(back.board.tile_at(4, 3), TileKind::Castle(0));
assert!(back.board.wall_at(4, 3, Direction::Left));
}
#[test]
fn a_shrink_that_cuts_says_so() {
let mut board = Board::new(20, 13, 5);
board.set_tile(0, 0, TileKind::Rock);
assert!(resized(&board, (9, 7)).cut);
let mut walled = Board::new(20, 13, 5);
walled.set_wall(0, 0, Direction::Right, true);
assert!(resized(&walled, (9, 7)).cut);
assert!(!resized(&Board::new(20, 13, 5), (9, 7)).cut);
}
#[test]
fn the_sizes_step_both_ways() {
assert_eq!(next_size((9, 7), true), (12, 9));
assert_eq!(next_size((9, 7), false), (20, 13));
assert_eq!(next_size((20, 13), true), (9, 7));
assert_eq!(next_size((10, 10), true), (12, 9));
assert_eq!(next_size((10, 10), false), (9, 7));
assert_eq!(
next_size((6, 5), false),
(20, 13),
"below the smallest, round"
);
}
}