use super::{Tessellation, TessellationError, validate_and_apply_delta};
use crate::game_board::BoardTransform;
use crate::{Direction, TileShape};
pub static KNOWN_DIRECTIONS: [Direction; 8] = [
Direction::Left,
Direction::Right,
Direction::Up,
Direction::Down,
Direction::NorthEast,
Direction::NorthWest,
Direction::SouthEast,
Direction::SouthWest,
];
pub static TILE_SHAPES: [TileShape; 2] = [TileShape::Square, TileShape::Octagon];
pub const ROTATION_STEP_DEGREES: u16 = 90;
#[inline]
pub const fn tile_shape_from_pos(x: usize, y: usize) -> TileShape {
let parity = (x + (y % 2)) % 2;
if parity == 0 {
TileShape::Octagon
} else {
TileShape::Square
}
}
pub const fn is_rigid_translation(dx: i64, dy: i64) -> bool { (dx + dy).rem_euclid(2) == 0 }
pub fn transformed_raw(transform: BoardTransform, x: i64, y: i64) -> (i64, i64) {
match transform {
BoardTransform::MirrorHorizontal => (x, -y),
BoardTransform::MirrorVertical => (-x, y),
BoardTransform::RotateClockwise => (-y, x),
BoardTransform::RotateCounterClockwise => (y, -x),
}
}
pub fn neighbor_checked(
x: usize, y: usize, to: Direction, width: usize, height: usize,
) -> Result<(usize, usize), TessellationError> {
if (tile_shape_from_pos(x, y) != TileShape::Octagon)
&& ((to == Direction::NorthEast)
|| (to == Direction::NorthWest)
|| (to == Direction::SouthEast)
|| (to == Direction::SouthWest))
{
return Err(TessellationError::InvalidDirectionForTile(
Tessellation::Octoban,
to,
x,
y,
));
}
let dx: i8;
let dy: i8;
match to {
Direction::Left => {
dx = -1;
dy = 0;
}
Direction::Up => {
dx = 0;
dy = -1;
}
Direction::Right => {
dx = 1;
dy = 0;
}
Direction::Down => {
dx = 0;
dy = 1;
}
Direction::NorthWest => {
dx = -1;
dy = -1;
}
Direction::NorthEast => {
dx = 1;
dy = -1;
}
Direction::SouthWest => {
dx = -1;
dy = 1;
}
Direction::SouthEast => {
dx = 1;
dy = 1;
}
}
validate_and_apply_delta(x, y, to, dx, dy, width, height)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Size2D;
mod test_transforms {
use super::*;
type Expectation = ((i64, i64), [(i64, i64); 4]);
const TRANSFORMS: [BoardTransform; 4] = [
BoardTransform::MirrorHorizontal,
BoardTransform::MirrorVertical,
BoardTransform::RotateClockwise,
BoardTransform::RotateCounterClockwise,
];
#[rustfmt::skip]
#[test]
fn it_maps_the_first_two_rows_under_every_transform() {
let expectations: [Expectation; 8] = [
((0, 0), [(0, 0), ( 0, 0), ( 0, 0), (0, 0)]),
((1, 0), [(1, 0), (-1, 0), ( 0, 1), (0, -1)]),
((2, 0), [(2, 0), (-2, 0), ( 0, 2), (0, -2)]),
((3, 0), [(3, 0), (-3, 0), ( 0, 3), (0, -3)]),
((0, 1), [(0, -1), ( 0, 1), (-1, 0), (1, 0)]),
((1, 1), [(1, -1), (-1, 1), (-1, 1), (1, -1)]),
((2, 1), [(2, -1), (-2, 1), (-1, 2), (1, -2)]),
((3, 1), [(3, -1), (-3, 1), (-1, 3), (1, -3)]),
];
for ((x, y), wanted) in expectations {
for (transform, want) in TRANSFORMS.iter().zip(wanted) {
assert_eq!(
transformed_raw(*transform, x, y), want,
"{transform:?} at ({x}, {y})"
);
}
}
}
#[rustfmt::skip]
#[test]
fn it_maps_negative_coordinates() {
let expectations: [Expectation; 8] = [
((0, -1), [( 0, 1), ( 0, -1), (1, 0), (-1, 0)]),
((1, -1), [( 1, 1), (-1, -1), (1, 1), (-1, -1)]),
((-1, 0), [(-1, 0), ( 1, 0), (0, -1), ( 0, 1)]),
((-1, -1), [(-1, 1), ( 1, -1), (1, -1), (-1, 1)]),
((-2, -1), [(-2, 1), ( 2, -1), (1, -2), (-1, 2)]),
((-1, -2), [(-1, 2), ( 1, -2), (2, -1), (-2, 1)]),
((-2, -2), [(-2, 2), ( 2, -2), (2, -2), (-2, 2)]),
((-3, -2), [(-3, 2), ( 3, -2), (2, -3), (-2, 3)]),
];
for ((x, y), wanted) in expectations {
for (transform, want) in TRANSFORMS.iter().zip(wanted) {
assert_eq!(
transformed_raw(*transform, x, y), want,
"{transform:?} at ({x}, {y})"
);
}
}
}
#[test]
fn it_returns_to_the_start_only_after_a_full_turn() {
let steps = 360 / usize::from(ROTATION_STEP_DEGREES);
assert_eq!(steps, 4);
let witness = (2_i64, 1_i64);
let mut point = witness;
for step in 1..steps {
point = transformed_raw(BoardTransform::RotateClockwise, point.0, point.1);
assert_ne!(point, witness, "home again after {step} of {steps} steps");
}
for y in -6_i64..=6 {
for x in -6_i64..=6 {
let mut point = (x, y);
for _ in 0..steps {
point = transformed_raw(BoardTransform::RotateClockwise, point.0, point.1);
}
assert_eq!(point, (x, y), "({x}, {y}) after a full turn");
}
}
}
#[test]
fn counter_clockwise_inverts_clockwise_and_mirrors_are_involutions() {
for y in -6_i64..=6 {
for x in -6_i64..=6 {
let clockwise = transformed_raw(BoardTransform::RotateClockwise, x, y);
assert_eq!(
transformed_raw(
BoardTransform::RotateCounterClockwise,
clockwise.0,
clockwise.1
),
(x, y),
"CCW . CW at ({x}, {y})"
);
for mirror in [
BoardTransform::MirrorHorizontal,
BoardTransform::MirrorVertical,
] {
let once = transformed_raw(mirror, x, y);
assert_eq!(
transformed_raw(mirror, once.0, once.1),
(x, y),
"{mirror:?} twice at ({x}, {y})"
);
}
}
}
}
#[test]
fn it_reports_which_translations_are_rigid() {
let rigid = (-3_i64..=3)
.flat_map(|dy| (-3_i64..=3).map(move |dx| (dx, dy)))
.filter(|(dx, dy)| is_rigid_translation(*dx, *dy))
.count();
assert_eq!(rigid, 25);
}
#[test]
fn it_preserves_tile_parity_under_every_transform() {
for transform in TRANSFORMS {
for y in -8_i64..=8 {
for x in -8_i64..=8 {
let (tx, ty) = transformed_raw(transform, x, y);
assert_eq!(
(x + y).rem_euclid(2),
(tx + ty).rem_euclid(2),
"{transform:?} changed tile parity at ({x}, {y})"
);
}
}
}
}
#[test]
fn it_rotates_every_direction_by_one_step() {
let relabelled = [
(Direction::Up, Direction::Right),
(Direction::Right, Direction::Down),
(Direction::Down, Direction::Left),
(Direction::Left, Direction::Up),
(Direction::NorthEast, Direction::SouthEast),
(Direction::SouthEast, Direction::SouthWest),
(Direction::SouthWest, Direction::NorthWest),
(Direction::NorthWest, Direction::NorthEast),
];
for y in 0_usize..4 {
for x in 0_usize..4 {
for (direction, rotated) in relabelled {
if tile_shape_from_pos(x + 64, y + 64) != TileShape::Octagon
&& !KNOWN_DIRECTIONS[..4].contains(&direction)
{
continue;
}
assert_rotated_direction(x, y, direction, rotated);
}
}
}
}
}
#[cfg(test)]
fn assert_rotated_direction(x: usize, y: usize, direction: Direction, rotated: Direction) {
const ORIGIN: usize = 64;
const SHIFT: i64 = 128;
const SIZE: usize = 256;
let turned = |(cx, cy): (usize, usize)| {
let (tx, ty) = transformed_raw(
BoardTransform::RotateClockwise,
i64::try_from(cx).unwrap(),
i64::try_from(cy).unwrap(),
);
(
usize::try_from(tx + SHIFT).unwrap(),
usize::try_from(ty + SHIFT).unwrap(),
)
};
let (column, row) = (x + ORIGIN, y + ORIGIN);
let neighbor = neighbor_checked(column, row, direction, SIZE, SIZE)
.expect("the source neighbour is on board");
let source = turned((column, row));
assert_eq!(
neighbor_checked(source.0, source.1, rotated, SIZE, SIZE),
Ok(turned(neighbor)),
"a clockwise turn should carry {direction:?} onto {rotated:?}, at ({column}, {row})"
);
}
fn is_octagon(x: usize, y: usize) -> bool { tile_shape_from_pos(x, y) == TileShape::Octagon }
#[rustfmt::skip]
#[test]
fn test_top_left() {
let size = Size2D::new(5, 5);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 0;
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 0))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((0, 1))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Ok((1, 1))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_top_right_column_odd() {
let size = Size2D::new(4, 4);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 3;
assert_eq!(column % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 0))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((3, 1))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_top_right_column_even() {
let size = Size2D::new(5, 5);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 4;
assert_eq!(column % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 0))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((4, 1))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Ok((3, 1))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_left_row_odd() {
let size = Size2D::new(4, 4);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 0;
assert_eq!(row % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 3))),
(Direction::Up , Ok((0, 2))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_left_row_even() {
let size = Size2D::new(5, 5);
let width = size.width;
let height = size.height;
let row: usize = 4;
let column: usize = 0;
assert_eq!(row % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 4))),
(Direction::Up , Ok((0, 3))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Ok((1, 3))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_right_row_even_column_even() {
let size = Size2D::new(5, 5);
let width = size.width;
let height = size.height;
let row: usize = 4;
let column: usize = 4;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 4))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((4, 3))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Ok((3, 3))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_right_row_even_column_odd() {
let size = Size2D::new(4, 5);
let width = size.width;
let height = size.height;
let row: usize = 4;
let column: usize = 3;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 4))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((3, 3))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_right_row_odd_column_even() {
let size = Size2D::new(5, 4);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 4;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 0);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 3))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((4, 2))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_bottom_right_row_odd_column_odd() {
let size = Size2D::new(4, 4);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 3;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 1);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 3))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((3, 2))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Ok((2, 2))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_top_column_odd() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 3;
assert_eq!(column % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 0))),
(Direction::Right , Ok((4, 0))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((3, 1))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_top_column_even() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 4;
assert_eq!(column % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 0))),
(Direction::Right , Ok((5, 0))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((4, 1))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Ok((3, 1))),
(Direction::SouthEast, Ok((5, 1))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_bottom_column_odd_row_even() {
let size = Size2D::new(10, 5);
let width = size.width;
let height = size.height;
let row: usize = 4;
let column: usize = 3;
assert_eq!(column % 2, 1);
assert_eq!(row % 2, 0);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 4))),
(Direction::Right , Ok((4, 4))),
(Direction::Up , Ok((3, 3))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_bottom_column_even_row_even() {
let size = Size2D::new(10, 5);
let width = size.width;
let height = size.height;
let row: usize = 4;
let column: usize = 4;
assert_eq!(column % 2, 0);
assert_eq!(row % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 4))),
(Direction::Right , Ok((5, 4))),
(Direction::Up , Ok((4, 3))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Ok((3, 3))),
(Direction::NorthEast, Ok((5, 3))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_bottom_column_odd_row_odd() {
let size = Size2D::new(10, 4);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 3;
assert_eq!(column % 2, 1);
assert_eq!(row % 2, 1);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 3))),
(Direction::Right , Ok((4, 3))),
(Direction::Up , Ok((3, 2))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Ok((2, 2))),
(Direction::NorthEast, Ok((4, 2))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_bottom_column_even_row_odd() {
let size = Size2D::new(10, 4);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 4;
assert_eq!(column % 2, 0);
assert_eq!(row % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 3))),
(Direction::Right , Ok((5, 3))),
(Direction::Up , Ok((4, 2))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_left_row_odd() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 0;
assert_eq!(row % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 3))),
(Direction::Up , Ok((0, 2))),
(Direction::Down , Ok((0, 4))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_left_row_even() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 0;
assert_eq!(row % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 2))),
(Direction::Up , Ok((0, 1))),
(Direction::Down , Ok((0, 3))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Ok((1, 1))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Ok((1, 3))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_right_row_odd_column_odd() {
let size = Size2D::new(4, 10);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 3;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 1);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 3))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((3, 2))),
(Direction::Down , Ok((3, 4))),
(Direction::NorthWest, Ok((2, 2))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Ok((2, 4))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_right_row_even_column_odd() {
let size = Size2D::new(4, 10);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 3;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 2))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((3, 1))),
(Direction::Down , Ok((3, 3))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_right_row_odd_column_even() {
let size = Size2D::new(5, 10);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 4;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 0);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 3))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((4, 2))),
(Direction::Down , Ok((4, 4))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_right_row_even_column_even() {
let size = Size2D::new(5, 10);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 4;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((3, 2))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((4, 1))),
(Direction::Down , Ok((4, 3))),
(Direction::NorthWest, Ok((3, 1))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Ok((3, 3))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[test]
fn test_middle_row_odd_column_odd() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 3;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 1);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left, Ok((2, 3))),
(Direction::Right, Ok((4, 3))),
(Direction::Up, Ok((3, 2))),
(Direction::Down, Ok((3, 4))),
(Direction::NorthWest, Ok((2, 2))),
(Direction::NorthEast, Ok((4, 2))),
(Direction::SouthWest, Ok((2, 4))),
(Direction::SouthEast, Ok((4, 4))),
];
for (direction, result) in &expectations {
assert_eq!(neighbor_checked(column, row, *direction, width, height), *result);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_row_odd_column_even() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 3;
let column: usize = 2;
assert_eq!(row % 2, 1);
assert_eq!(column % 2, 0);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((1, 3))),
(Direction::Right , Ok((3, 3))),
(Direction::Up , Ok((2, 2))),
(Direction::Down , Ok((2, 4))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_middle_row_even_column_odd() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 3;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 1);
assert!(!is_octagon(column, row));
let expectations = [
(Direction::Left , Ok((2, 2))),
(Direction::Right , Ok((4, 2))),
(Direction::Up , Ok((3, 1))),
(Direction::Down , Ok((3, 3))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[test]
fn test_middle_row_even_column_even() {
let size = Size2D::new(10, 10);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 2;
assert_eq!(row % 2, 0);
assert_eq!(column % 2, 0);
assert!(is_octagon(column, row));
let expectations = [
(Direction::Left, Ok((1, 2))),
(Direction::Right, Ok((3, 2))),
(Direction::Up, Ok((2, 1))),
(Direction::Down, Ok((2, 3))),
(Direction::NorthWest, Ok((1, 1))),
(Direction::NorthEast, Ok((3, 1))),
(Direction::SouthWest, Ok((1, 3))),
(Direction::SouthEast, Ok((3, 3))),
];
for (direction, result) in &expectations {
assert_eq!(neighbor_checked(column, row, *direction, width, height), *result);
}
}
#[rustfmt::skip]
#[test]
fn test_board1x1() {
let size = Size2D::new(1, 1);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 0;
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_board2x1_left() {
let size = Size2D::new(2, 1);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 0;
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Ok((1, 0))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_board2x1_right() {
let size = Size2D::new(2, 1);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 1;
let expectations = [
(Direction::Left , Ok((0, 0))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_board3x1_right() {
let size = Size2D::new(3, 1);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 2;
let expectations = [
(Direction::Left , Ok((1, 0))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_1x2_top() {
let size = Size2D::new(1, 2);
let width = size.width;
let height = size.height;
let row: usize = 0;
let column: usize = 0;
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Err(TessellationError::OffBoard(column, row, Direction::Up))),
(Direction::Down , Ok((0, 1))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_1x2_bottom() {
let size = Size2D::new(1, 2);
let width = size.width;
let height = size.height;
let row: usize = 1;
let column: usize = 0;
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((0, 0))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthWest, column, row))),
(Direction::NorthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::NorthEast, column, row))),
(Direction::SouthWest, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthWest, column, row))),
(Direction::SouthEast, Err(TessellationError::InvalidDirectionForTile(Tessellation::Octoban, Direction::SouthEast, column, row))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
#[rustfmt::skip]
#[test]
fn test_1x3_bottom() {
let size = Size2D::new(1, 3);
let width = size.width;
let height = size.height;
let row: usize = 2;
let column: usize = 0;
let expectations = [
(Direction::Left , Err(TessellationError::OffBoard(column, row, Direction::Left))),
(Direction::Right , Err(TessellationError::OffBoard(column, row, Direction::Right))),
(Direction::Up , Ok((0, 1))),
(Direction::Down , Err(TessellationError::OffBoard(column, row, Direction::Down))),
(Direction::NorthWest, Err(TessellationError::OffBoard(column, row, Direction::NorthWest))),
(Direction::NorthEast, Err(TessellationError::OffBoard(column, row, Direction::NorthEast))),
(Direction::SouthWest, Err(TessellationError::OffBoard(column, row, Direction::SouthWest))),
(Direction::SouthEast, Err(TessellationError::OffBoard(column, row, Direction::SouthEast))),
];
for (direction, result) in &expectations {
assert_eq!(
neighbor_checked(column, row, *direction, width, height),
*result
);
}
}
}