diff --git a/src/line_finder.rs b/src/line_finder.rs index 27acfb4..9cc0692 100644 --- a/src/line_finder.rs +++ b/src/line_finder.rs @@ -1,159 +1,162 @@ -use core::iter::FusedIterator; - -pub use crate::prelude::*; - -impl TileMap { - /// Find lines in the grid which meet particular conditions - pub const fn get_lines<'a, F: Fn(&T) -> bool>( - &'a self, - directions: &'a [Vector], - check_item: F, - min_length: usize, - ) -> impl Iterator> { - LineFinder { - grid: self, - directions, - position: Tile::NORTH_WEST, - direction_index: 0, - check_item, - min_length, - } - } -} - -#[derive(Clone, Debug)] -struct LineFinder<'a, T, const WIDTH: u8, const HEIGHT: u8, const SIZE: usize, F: Fn(&T) -> bool> { - pub grid: &'a TileMap, - pub directions: &'a [Vector], - pub check_item: F, - pub position: Tile, - pub direction_index: usize, - pub min_length: usize, -} - -/// A line in a grid -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Line<'a, T, const WIDTH: u8, const HEIGHT: u8> { - /// The value at the first tile - pub first_item: &'a T, - /// The first tile - pub origin: Tile, - /// The direction of the line - pub direction: Vector, - /// The number of tiles, including the origin - pub length: usize, -} - -impl<'a, T, const WIDTH: u8, const HEIGHT: u8> Line<'a, T, WIDTH, HEIGHT> { - #[must_use] - /// # Panics - /// If the line is invalid - pub fn positions( - & self, - ) -> impl FusedIterator> + ExactSizeIterator + Clone + use<'_, T, WIDTH, HEIGHT> { - (0..self.length).map(|x| (self.origin + (self.direction * x)).unwrap()) - } -} - -impl<'a, T, const WIDTH: u8, const HEIGHT: u8, const SIZE: usize, F: Fn(&T) -> bool> Iterator - for LineFinder<'a, T, WIDTH, HEIGHT, SIZE, F> -{ - type Item = Line<'a, T, WIDTH, HEIGHT>; - - fn next(&mut self) -> Option { - 'items: loop { - let item = &self.grid[self.position]; - if (self.check_item)(item) { - while self.direction_index < self.directions.len() { - let direction = self.directions[self.direction_index]; - self.direction_index += 1; - let mut length = 1; - let mut current = self.position; - 'len: loop { - let Some(next) = current + direction else { - break 'len; - }; - current = next; - let item2 = &self.grid[next]; - - if (self.check_item)(item2) { - length += 1; - } else { - break 'len; - } - } - if length >= self.min_length { - let line = Line { - first_item: item, - origin: self.position, - direction, - length, - }; - return Some(line); - } - } - self.direction_index = 0; - } - let Some(new_position) = self.position.try_next() else { - break 'items; - }; - self.position = new_position; - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tile::*; - use itertools::Itertools; - - #[test] - pub fn test_line_finder_none() { - let mut map: TileMap = Default::default(); - - map[Tile::new_const::<0, 1>()] = true; - map[Tile::new_const::<1, 1>()] = true; - map[Tile::new_const::<2, 0>()] = true; - map[Tile::new_const::<3, 0>()] = true; - - let lines = map - .get_lines(&[Vector::EAST, Vector::WEST], |x| *x, 4) - .collect_vec(); - - assert_eq!(lines.len(), 0); - } - - #[test] - pub fn test_line_finder_4() { - let mut map: TileMap = Default::default(); - map[Tile::new_const::<0, 0>()] = true; - map[Tile::new_const::<1, 1>()] = true; - map[Tile::new_const::<2, 2>()] = true; - map[Tile::new_const::<3, 3>()] = true; - - let lines = map - .get_lines( - &[ - Vector::SOUTH, - Vector::EAST, - Vector::SOUTH_EAST, - Vector::NORTH_EAST, - ], - |x| *x, - 4, - ) - .collect_vec(); - - assert_eq!(lines.len(), 1); - - let line = lines[0].clone(); - - assert_eq!(line.first_item, &true); - assert_eq!(line.length, 4); - assert_eq!(line.origin, Tile::new_const::<0, 0>()); - assert_eq!(line.direction, Vector::SOUTH_EAST); - } -} +use core::iter::FusedIterator; + +pub use crate::prelude::*; + +impl TileMap { + /// Find lines in the grid which meet particular conditions + pub const fn get_lines<'a, F: Fn(&T) -> bool>( + &'a self, + directions: &'a [Vector], + check_item: F, + min_length: usize, + ) -> impl Iterator> { + LineFinder { + grid: self, + directions, + position: Tile::NORTH_WEST, + direction_index: 0, + check_item, + min_length, + } + } +} + +#[derive(Clone, Debug)] +struct LineFinder<'a, T, const WIDTH: u8, const HEIGHT: u8, const SIZE: usize, F: Fn(&T) -> bool> { + pub grid: &'a TileMap, + pub directions: &'a [Vector], + pub check_item: F, + pub position: Tile, + pub direction_index: usize, + pub min_length: usize, +} + +/// A line in a grid +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Line<'a, T, const WIDTH: u8, const HEIGHT: u8> { + /// The value at the first tile + pub first_item: &'a T, + /// The first tile + pub origin: Tile, + /// The direction of the line + pub direction: Vector, + /// The number of tiles, including the origin + pub length: usize, +} + +impl Line<'_, T, WIDTH, HEIGHT> { + #[must_use] + /// # Panics + /// If the line is invalid + pub fn positions( + &self, + ) -> impl FusedIterator> + + ExactSizeIterator + + Clone + + use<'_, T, WIDTH, HEIGHT> { + (0..self.length).map(|x| (self.origin + (self.direction * x)).unwrap()) + } +} + +impl<'a, T, const WIDTH: u8, const HEIGHT: u8, const SIZE: usize, F: Fn(&T) -> bool> Iterator + for LineFinder<'a, T, WIDTH, HEIGHT, SIZE, F> +{ + type Item = Line<'a, T, WIDTH, HEIGHT>; + + fn next(&mut self) -> Option { + 'items: loop { + let item = &self.grid[self.position]; + if (self.check_item)(item) { + while self.direction_index < self.directions.len() { + let direction = self.directions[self.direction_index]; + self.direction_index += 1; + let mut length = 1; + let mut current = self.position; + 'len: loop { + let Some(next) = current + direction else { + break 'len; + }; + current = next; + let item2 = &self.grid[next]; + + if (self.check_item)(item2) { + length += 1; + } else { + break 'len; + } + } + if length >= self.min_length { + let line = Line { + first_item: item, + origin: self.position, + direction, + length, + }; + return Some(line); + } + } + self.direction_index = 0; + } + let Some(new_position) = self.position.try_next() else { + break 'items; + }; + self.position = new_position; + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tile::*; + use itertools::Itertools; + + #[test] + pub fn test_line_finder_none() { + let mut map: TileMap = Default::default(); + + map[Tile::new_const::<0, 1>()] = true; + map[Tile::new_const::<1, 1>()] = true; + map[Tile::new_const::<2, 0>()] = true; + map[Tile::new_const::<3, 0>()] = true; + + let lines = map + .get_lines(&[Vector::EAST, Vector::WEST], |x| *x, 4) + .collect_vec(); + + assert_eq!(lines.len(), 0); + } + + #[test] + pub fn test_line_finder_4() { + let mut map: TileMap = Default::default(); + map[Tile::new_const::<0, 0>()] = true; + map[Tile::new_const::<1, 1>()] = true; + map[Tile::new_const::<2, 2>()] = true; + map[Tile::new_const::<3, 3>()] = true; + + let lines = map + .get_lines( + &[ + Vector::SOUTH, + Vector::EAST, + Vector::SOUTH_EAST, + Vector::NORTH_EAST, + ], + |x| *x, + 4, + ) + .collect_vec(); + + assert_eq!(lines.len(), 1); + + let line = lines[0].clone(); + + assert_eq!(line.first_item, &true); + assert_eq!(line.length, 4); + assert_eq!(line.origin, Tile::new_const::<0, 0>()); + assert_eq!(line.direction, Vector::SOUTH_EAST); + } +} diff --git a/src/tile_map.rs b/src/tile_map.rs index 6096d08..91f5c63 100644 --- a/src/tile_map.rs +++ b/src/tile_map.rs @@ -1,560 +1,563 @@ -use core::{ - fmt::{self, Write}, - iter, - ops::{Index, IndexMut}, -}; - -use crate::prelude::*; - -#[cfg(any(test, feature = "serde"))] -use serde::{Deserialize, Serialize}; - -/// A grid -/// A map from tiles to values. -/// If the values are just booleans, use `TileSet` instead -#[must_use] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(any(test, feature = "serde"), derive(Serialize, Deserialize))] -pub struct TileMap( - #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_arrays"))] - #[cfg_attr(any(test, feature = "serde"), serde(bound(serialize = "T: Serialize")))] - #[cfg_attr( - any(test, feature = "serde"), - serde(bound(deserialize = "T: Deserialize<'de>")) - )] - [T; SIZE], -); - -impl Default - for TileMap -{ - fn default() -> Self { - debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); - Self([T::default(); SIZE]) - } -} - -impl TileMap { - #[allow(clippy::missing_panics_doc)] - pub fn from_fn) -> T>(mut cb: F) -> Self { - debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); - let arr = core::array::from_fn(|i| cb(Tile::try_from_usize(i).unwrap())); - Self(arr) - } - - #[must_use] - #[inline] - pub fn into_inner(self) -> [T; SIZE] { - let Self(inner) = self; - inner - } - - #[inline] - pub const fn from_inner(inner: [T; SIZE]) -> Self { - debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); - Self(inner) - } - - #[inline] - #[allow(clippy::missing_panics_doc)] - pub fn enumerate(&self) -> impl iter::Iterator, &'_ T)> { - self.0 - .iter() - .enumerate() - .map(|(inner, x)| (Tile::try_from_usize(inner).unwrap(), x)) - } - - #[inline] - pub fn swap(&mut self, p1: Tile, p2: Tile) { - self.0.swap(p1.into(), p2.into()); - } - - #[inline] - pub fn iter(&self) -> core::slice::Iter<'_, T> { - self.0.iter() - } - - #[inline] - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { - self.0.iter_mut() - } - - #[must_use] - #[inline] - pub fn row(&self, y: u8) -> &[T] { - let start = y * WIDTH; - let end = start + WIDTH; - &self.0[(start as usize)..(end as usize)] - } - - #[must_use] - #[inline] - pub fn row_mut(&mut self, y: u8) -> &mut [T] { - let start = y * WIDTH; - let end = start + WIDTH; - &mut self.0[(start as usize)..(end as usize)] - } - - #[must_use] - pub fn column_iter(&self, column: u8) -> impl DoubleEndedIterator + use<'_, T, WIDTH, HEIGHT, SIZE> { - (0..HEIGHT) - .map(move |y| column + (y * WIDTH)) - .map(|x| &self.0[x as usize]) - } - - /// Get the scale to make the grid take up as much as possible of a given area - #[must_use] - pub fn get_scale(total_width: f32, total_height: f32) -> f32 { - let x_multiplier = total_width / f32::from(WIDTH); - let y_multiplier = total_height / f32::from(HEIGHT); - - x_multiplier.min(y_multiplier) - } - - pub fn flip(&mut self, axes: FlipAxes) { - match axes { - FlipAxes::None => {} - FlipAxes::Horizontal => { - for y in 0..HEIGHT { - for x in 0..WIDTH / 2 { - let p1 = Tile::::new_unchecked(x, y); - let p2 = p1.flip(axes); - self.swap(p1, p2); - } - } - } - FlipAxes::Vertical => { - for y in 0..HEIGHT / 2 { - for x in 0..WIDTH { - let p1 = Tile::::new_unchecked(x, y); - let p2 = p1.flip(axes); - self.swap(p1, p2); - } - } - } - FlipAxes::Both => { - for y in 0..HEIGHT / 2 { - for x in 0..WIDTH { - let p1 = Tile::::new_unchecked(x, y); - let p2 = p1.flip(axes); - self.swap(p1, p2); - } - } - - if WIDTH % 2 != 0 { - for x in 0..(WIDTH / 2) { - let p1 = Tile::::new_unchecked(x, HEIGHT / 2); - let p2 = p1.flip(axes); - self.swap(p1, p2); - } - } - } - } - } -} - -impl TileMap { - pub fn rotate(&mut self, quarter_turns: QuarterTurns) { - //todo const once const swap is stabilized - match quarter_turns { - QuarterTurns::Zero => {} - QuarterTurns::One => { - let mut y = 0; - 'y: loop { - if y >= L / 2 { - break 'y; - } - let mut x = y; - let o_y = L - (1 + y); - 'x: loop { - if x + y + 1 >= L { - break 'x; - }; - - let o_x = L - (1 + x); - if y != o_y || x != o_x { - let p0 = Tile::new_unchecked(x, y); - let p1 = Tile::new_unchecked(y, o_x); - let p2 = Tile::new_unchecked(o_x, o_y); - let p3 = Tile::new_unchecked(o_y, x); - - //println!("Rotate {p0} {p1} {p2} {p3}"); - //0123 - self.swap(p0, p3); //3120 - self.swap(p0, p1); //1320 - self.swap(p1, p2); //1230 - } - x += 1; - } - y += 1; - } - } - QuarterTurns::Two => { - for y in 0..((L + 1) / 2) { - let o_y = L - (1 + y); - let x_max = if (y * 2) + 1 == L { L / 2 } else { L }; - for x in 0..x_max { - let o_x = L - (1 + x); - let p0 = Tile::new_unchecked(x, y); - let p3 = Tile::new_unchecked(o_x, o_y); - self.swap(p0, p3); - } - } - } - QuarterTurns::Three => { - let mut y = 0; - 'y: loop { - if y >= L / 2 { - break 'y; - } - let mut x = y; - let o_y = L - (1 + y); - 'x: loop { - if x + y + 1 >= L { - break 'x; - }; - - let o_x = L - (1 + x); - if y != o_y || x != o_x { - let p0 = Tile::new_unchecked(x, y); - let p1 = Tile::new_unchecked(y, o_x); - let p2 = Tile::new_unchecked(o_x, o_y); - let p3 = Tile::new_unchecked(o_y, x); - - //println!("Rotate {p0} {p1} {p2} {p3}"); - //0123 - self.swap(p1, p2); //0213 - self.swap(p0, p1); //2013 - self.swap(p0, p3); //3012 - } - x += 1; - } - y += 1; - } - } - } - } -} - -impl TileMap { - pub fn with_rotate(&self, quarter_turns: QuarterTurns) -> Self { - let mut grid = self.clone(); - grid.rotate(quarter_turns); - grid - } -} - -impl - TileMap -{ - pub fn with_flip(&self, axes: FlipAxes) -> Self { - let mut grid = self.clone(); - grid.flip(axes); - grid - } -} - -impl Index> for TileMap { - type Output = T; - - fn index(&self, index: Tile) -> &Self::Output { - let u: usize = index.into(); - &self.0[u] - } -} - -impl IndexMut> - for TileMap -{ - fn index_mut(&mut self, index: Tile) -> &mut Self::Output { - let u: usize = index.into(); - &mut self.0[u] - } -} - -impl AsRef<[T; SIZE]> for TileMap { - #[inline] - fn as_ref(&self) -> &[T; SIZE] { - &self.0 - } -} - -impl AsMut<[T; SIZE]> for TileMap { - #[inline] - fn as_mut(&mut self) -> &mut [T; SIZE] { - &mut self.0 - } -} - -impl<'a, T, const W: u8, const H: u8, const SIZE: usize> IntoIterator - for &'a TileMap -{ - type Item = &'a T; - type IntoIter = core::slice::Iter<'a, T>; - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, const W: u8, const H: u8, const SIZE: usize> IntoIterator - for &'a mut TileMap -{ - type Item = &'a mut T; - type IntoIter = core::slice::IterMut<'a, T>; - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl IntoIterator for TileMap { - type Item = T; - type IntoIter = core::array::IntoIter; - #[inline] - fn into_iter(self) -> Self::IntoIter { - IntoIterator::into_iter(self.0) - } -} - -impl fmt::Display - for TileMap -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.0.iter().enumerate(); - - for (i, e) in iter { - if i == 0 { - } else if !f.alternate() && i % (W as usize) == 0 { - f.write_char('\n')?; - } else { - f.write_char('|')?; - } - - e.fmt(f)?; - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use core::usize; - - use super::*; - use itertools::Itertools; - #[cfg(any(test, feature = "serde"))] - use serde_test::{assert_tokens, Token}; - - #[test] - #[should_panic(expected = "assertion failed")] - #[allow(unused_variables)] - fn test_bad_grid() { - let grid: TileMap = TileMap::default(); - } - - #[test] - fn test_flip3() { - for (axes, expected) in [ - ( - FlipAxes::None, - "0|1|2\n\ - 3|4|5\n\ - 6|7|8", - ), - ( - FlipAxes::Vertical, - "6|7|8\n\ - 3|4|5\n\ - 0|1|2", - ), - ( - FlipAxes::Horizontal, - "2|1|0\n\ - 5|4|3\n\ - 8|7|6", - ), - ( - FlipAxes::Both, - "8|7|6\n\ - 5|4|3\n\ - 2|1|0", - ), - ] { - let grid: TileMap = TileMap::from_fn(|x| x.into()).with_flip(axes); - - assert_eq!(grid.to_string(), expected); - } - } - #[test] - fn test_flip4() { - for (axes, expected) in [ - ( - FlipAxes::None, - "0|1|2|3\n\ - 4|5|6|7\n\ - 8|9|10|11\n\ - 12|13|14|15", - ), - ( - FlipAxes::Vertical, - "12|13|14|15\n\ - 8|9|10|11\n\ - 4|5|6|7\n\ - 0|1|2|3", - ), - ( - FlipAxes::Horizontal, - "3|2|1|0\n\ - 7|6|5|4\n\ - 11|10|9|8\n\ - 15|14|13|12", - ), - ( - FlipAxes::Both, - "15|14|13|12\n\ - 11|10|9|8\n\ - 7|6|5|4\n\ - 3|2|1|0", - ), - ] { - let grid: TileMap = TileMap::from_fn(|x| x.into()).with_flip(axes); - assert_eq!(grid.to_string(), expected); - } - } - - #[test] - fn test_rotate_length_1() { - test_rotation::<1, 1>("0"); - } - - #[test] - fn test_rotate_length_2() { - test_rotation::<2, 4>( - "2|0\n\ - 3|1", - ); - } - - #[test] - fn test_rotate_length_3() { - test_rotation::<3, 9>( - "6|3|0\n\ - 7|4|1\n\ - 8|5|2", - ); - } - - #[test] - fn test_rotate_length_4() { - test_rotation::<4, 16>( - "12|8|4|0\n\ - 13|9|5|1\n\ - 14|10|6|2\n\ - 15|11|7|3", - ); - } - - #[test] - fn test_rotate_length_5() { - test_rotation::<5, 25>( - "20|15|10|5|0\n\ - 21|16|11|6|1\n\ - 22|17|12|7|2\n\ - 23|18|13|8|3\n\ - 24|19|14|9|4", - ); - } - - #[test] - fn test_rotate_length_6() { - test_rotation::<6, 36>( - "30|24|18|12|6|0\n\ - 31|25|19|13|7|1\n\ - 32|26|20|14|8|2\n\ - 33|27|21|15|9|3\n\ - 34|28|22|16|10|4\n\ - 35|29|23|17|11|5", - ); - } - - fn test_rotation(e: &str) { - let original_grid: TileMap = TileMap::from_fn(|x| x.into()); - - let rotated_0 = original_grid.with_rotate(QuarterTurns::Zero); - assert_eq!(original_grid, rotated_0); - - let rotated_1 = original_grid.with_rotate(QuarterTurns::One); - - assert_eq!(rotated_1.to_string(), e); - - let rotated_2 = original_grid.with_rotate(QuarterTurns::Two); - let rotated_1x2 = original_grid - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One); - - assert_eq!(rotated_1x2, rotated_2); - - let rotated_3 = original_grid.with_rotate(QuarterTurns::Three); - let rotated_1x3 = original_grid - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One); - assert_eq!(rotated_1x3, rotated_3); - - let rotated_1x4 = original_grid - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One) - .with_rotate(QuarterTurns::One); - assert_eq!(rotated_1x4, original_grid); - } - - #[test] - fn basic_tests() { - let grid: TileMap = TileMap::from_fn(|x| x.into()); - - for i in 0..9 { - assert_eq!(grid[Tile::<3, 3>::try_from_usize(i).unwrap()], i) - } - - let str = grid.to_string(); - - assert_eq!( - str, - "0|1|2\n\ - 3|4|5\n\ - 6|7|8" - ); - - assert_eq!(format!("{grid:#}"), "0|1|2|3|4|5|6|7|8"); - - let s_flat = grid.iter().join("|"); - assert_eq!(s_flat, "0|1|2|3|4|5|6|7|8"); - } - - #[cfg(any(test, feature = "serde"))] - #[test] - fn test_serde() { - let grid: TileMap = TileMap::from_fn(|x| x.into()); - - assert_tokens( - &grid, - &[ - Token::NewtypeStruct { name: "TileMap" }, - Token::Tuple { len: 4 }, - Token::U64(0), - Token::U64(1), - Token::U64(2), - Token::U64(3), - Token::TupleEnd, - ], - ); - } - - #[test] - fn test_get_scale() { - assert_eq!(TileMap::::get_scale(12.0, 20.0), 4.0); - } -} +use core::{ + fmt::{self, Write}, + iter, + ops::{Index, IndexMut}, +}; + +use crate::prelude::*; + +#[cfg(any(test, feature = "serde"))] +use serde::{Deserialize, Serialize}; + +/// A grid +/// A map from tiles to values. +/// If the values are just booleans, use `TileSet` instead +#[must_use] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(any(test, feature = "serde"), derive(Serialize, Deserialize))] +pub struct TileMap( + #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_arrays"))] + #[cfg_attr(any(test, feature = "serde"), serde(bound(serialize = "T: Serialize")))] + #[cfg_attr( + any(test, feature = "serde"), + serde(bound(deserialize = "T: Deserialize<'de>")) + )] + [T; SIZE], +); + +impl Default + for TileMap +{ + fn default() -> Self { + debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); + Self([T::default(); SIZE]) + } +} + +impl TileMap { + #[allow(clippy::missing_panics_doc)] + pub fn from_fn) -> T>(mut cb: F) -> Self { + debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); + let arr = core::array::from_fn(|i| cb(Tile::try_from_usize(i).unwrap())); + Self(arr) + } + + #[must_use] + #[inline] + pub fn into_inner(self) -> [T; SIZE] { + let Self(inner) = self; + inner + } + + #[inline] + pub const fn from_inner(inner: [T; SIZE]) -> Self { + debug_assert!(SIZE == (WIDTH * HEIGHT) as usize); + Self(inner) + } + + #[inline] + #[allow(clippy::missing_panics_doc)] + pub fn enumerate(&self) -> impl iter::Iterator, &'_ T)> { + self.0 + .iter() + .enumerate() + .map(|(inner, x)| (Tile::try_from_usize(inner).unwrap(), x)) + } + + #[inline] + pub fn swap(&mut self, p1: Tile, p2: Tile) { + self.0.swap(p1.into(), p2.into()); + } + + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, T> { + self.0.iter() + } + + #[inline] + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { + self.0.iter_mut() + } + + #[must_use] + #[inline] + pub fn row(&self, y: u8) -> &[T] { + let start = y * WIDTH; + let end = start + WIDTH; + &self.0[(start as usize)..(end as usize)] + } + + #[must_use] + #[inline] + pub fn row_mut(&mut self, y: u8) -> &mut [T] { + let start = y * WIDTH; + let end = start + WIDTH; + &mut self.0[(start as usize)..(end as usize)] + } + + #[must_use] + pub fn column_iter( + &self, + column: u8, + ) -> impl DoubleEndedIterator + use<'_, T, WIDTH, HEIGHT, SIZE> { + (0..HEIGHT) + .map(move |y| column + (y * WIDTH)) + .map(|x| &self.0[x as usize]) + } + + /// Get the scale to make the grid take up as much as possible of a given area + #[must_use] + pub fn get_scale(total_width: f32, total_height: f32) -> f32 { + let x_multiplier = total_width / f32::from(WIDTH); + let y_multiplier = total_height / f32::from(HEIGHT); + + x_multiplier.min(y_multiplier) + } + + pub fn flip(&mut self, axes: FlipAxes) { + match axes { + FlipAxes::None => {} + FlipAxes::Horizontal => { + for y in 0..HEIGHT { + for x in 0..WIDTH / 2 { + let p1 = Tile::::new_unchecked(x, y); + let p2 = p1.flip(axes); + self.swap(p1, p2); + } + } + } + FlipAxes::Vertical => { + for y in 0..HEIGHT / 2 { + for x in 0..WIDTH { + let p1 = Tile::::new_unchecked(x, y); + let p2 = p1.flip(axes); + self.swap(p1, p2); + } + } + } + FlipAxes::Both => { + for y in 0..HEIGHT / 2 { + for x in 0..WIDTH { + let p1 = Tile::::new_unchecked(x, y); + let p2 = p1.flip(axes); + self.swap(p1, p2); + } + } + + if WIDTH % 2 != 0 { + for x in 0..(WIDTH / 2) { + let p1 = Tile::::new_unchecked(x, HEIGHT / 2); + let p2 = p1.flip(axes); + self.swap(p1, p2); + } + } + } + } + } +} + +impl TileMap { + pub fn rotate(&mut self, quarter_turns: QuarterTurns) { + //todo const once const swap is stabilized + match quarter_turns { + QuarterTurns::Zero => {} + QuarterTurns::One => { + let mut y = 0; + 'y: loop { + if y >= L / 2 { + break 'y; + } + let mut x = y; + let o_y = L - (1 + y); + 'x: loop { + if x + y + 1 >= L { + break 'x; + }; + + let o_x = L - (1 + x); + if y != o_y || x != o_x { + let p0 = Tile::new_unchecked(x, y); + let p1 = Tile::new_unchecked(y, o_x); + let p2 = Tile::new_unchecked(o_x, o_y); + let p3 = Tile::new_unchecked(o_y, x); + + //println!("Rotate {p0} {p1} {p2} {p3}"); + //0123 + self.swap(p0, p3); //3120 + self.swap(p0, p1); //1320 + self.swap(p1, p2); //1230 + } + x += 1; + } + y += 1; + } + } + QuarterTurns::Two => { + for y in 0..((L + 1) / 2) { + let o_y = L - (1 + y); + let x_max = if (y * 2) + 1 == L { L / 2 } else { L }; + for x in 0..x_max { + let o_x = L - (1 + x); + let p0 = Tile::new_unchecked(x, y); + let p3 = Tile::new_unchecked(o_x, o_y); + self.swap(p0, p3); + } + } + } + QuarterTurns::Three => { + let mut y = 0; + 'y: loop { + if y >= L / 2 { + break 'y; + } + let mut x = y; + let o_y = L - (1 + y); + 'x: loop { + if x + y + 1 >= L { + break 'x; + }; + + let o_x = L - (1 + x); + if y != o_y || x != o_x { + let p0 = Tile::new_unchecked(x, y); + let p1 = Tile::new_unchecked(y, o_x); + let p2 = Tile::new_unchecked(o_x, o_y); + let p3 = Tile::new_unchecked(o_y, x); + + //println!("Rotate {p0} {p1} {p2} {p3}"); + //0123 + self.swap(p1, p2); //0213 + self.swap(p0, p1); //2013 + self.swap(p0, p3); //3012 + } + x += 1; + } + y += 1; + } + } + } + } +} + +impl TileMap { + pub fn with_rotate(&self, quarter_turns: QuarterTurns) -> Self { + let mut grid = self.clone(); + grid.rotate(quarter_turns); + grid + } +} + +impl + TileMap +{ + pub fn with_flip(&self, axes: FlipAxes) -> Self { + let mut grid = self.clone(); + grid.flip(axes); + grid + } +} + +impl Index> for TileMap { + type Output = T; + + fn index(&self, index: Tile) -> &Self::Output { + let u: usize = index.into(); + &self.0[u] + } +} + +impl IndexMut> + for TileMap +{ + fn index_mut(&mut self, index: Tile) -> &mut Self::Output { + let u: usize = index.into(); + &mut self.0[u] + } +} + +impl AsRef<[T; SIZE]> for TileMap { + #[inline] + fn as_ref(&self) -> &[T; SIZE] { + &self.0 + } +} + +impl AsMut<[T; SIZE]> for TileMap { + #[inline] + fn as_mut(&mut self) -> &mut [T; SIZE] { + &mut self.0 + } +} + +impl<'a, T, const W: u8, const H: u8, const SIZE: usize> IntoIterator + for &'a TileMap +{ + type Item = &'a T; + type IntoIter = core::slice::Iter<'a, T>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, const W: u8, const H: u8, const SIZE: usize> IntoIterator + for &'a mut TileMap +{ + type Item = &'a mut T; + type IntoIter = core::slice::IterMut<'a, T>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl IntoIterator for TileMap { + type Item = T; + type IntoIter = core::array::IntoIter; + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIterator::into_iter(self.0) + } +} + +impl fmt::Display + for TileMap +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.0.iter().enumerate(); + + for (i, e) in iter { + if i == 0 { + } else if !f.alternate() && i % (W as usize) == 0 { + f.write_char('\n')?; + } else { + f.write_char('|')?; + } + + e.fmt(f)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::usize; + + use super::*; + use itertools::Itertools; + #[cfg(any(test, feature = "serde"))] + use serde_test::{assert_tokens, Token}; + + #[test] + #[should_panic(expected = "assertion failed")] + #[allow(unused_variables)] + fn test_bad_grid() { + let grid: TileMap = TileMap::default(); + } + + #[test] + fn test_flip3() { + for (axes, expected) in [ + ( + FlipAxes::None, + "0|1|2\n\ + 3|4|5\n\ + 6|7|8", + ), + ( + FlipAxes::Vertical, + "6|7|8\n\ + 3|4|5\n\ + 0|1|2", + ), + ( + FlipAxes::Horizontal, + "2|1|0\n\ + 5|4|3\n\ + 8|7|6", + ), + ( + FlipAxes::Both, + "8|7|6\n\ + 5|4|3\n\ + 2|1|0", + ), + ] { + let grid: TileMap = TileMap::from_fn(|x| x.into()).with_flip(axes); + + assert_eq!(grid.to_string(), expected); + } + } + #[test] + fn test_flip4() { + for (axes, expected) in [ + ( + FlipAxes::None, + "0|1|2|3\n\ + 4|5|6|7\n\ + 8|9|10|11\n\ + 12|13|14|15", + ), + ( + FlipAxes::Vertical, + "12|13|14|15\n\ + 8|9|10|11\n\ + 4|5|6|7\n\ + 0|1|2|3", + ), + ( + FlipAxes::Horizontal, + "3|2|1|0\n\ + 7|6|5|4\n\ + 11|10|9|8\n\ + 15|14|13|12", + ), + ( + FlipAxes::Both, + "15|14|13|12\n\ + 11|10|9|8\n\ + 7|6|5|4\n\ + 3|2|1|0", + ), + ] { + let grid: TileMap = TileMap::from_fn(|x| x.into()).with_flip(axes); + assert_eq!(grid.to_string(), expected); + } + } + + #[test] + fn test_rotate_length_1() { + test_rotation::<1, 1>("0"); + } + + #[test] + fn test_rotate_length_2() { + test_rotation::<2, 4>( + "2|0\n\ + 3|1", + ); + } + + #[test] + fn test_rotate_length_3() { + test_rotation::<3, 9>( + "6|3|0\n\ + 7|4|1\n\ + 8|5|2", + ); + } + + #[test] + fn test_rotate_length_4() { + test_rotation::<4, 16>( + "12|8|4|0\n\ + 13|9|5|1\n\ + 14|10|6|2\n\ + 15|11|7|3", + ); + } + + #[test] + fn test_rotate_length_5() { + test_rotation::<5, 25>( + "20|15|10|5|0\n\ + 21|16|11|6|1\n\ + 22|17|12|7|2\n\ + 23|18|13|8|3\n\ + 24|19|14|9|4", + ); + } + + #[test] + fn test_rotate_length_6() { + test_rotation::<6, 36>( + "30|24|18|12|6|0\n\ + 31|25|19|13|7|1\n\ + 32|26|20|14|8|2\n\ + 33|27|21|15|9|3\n\ + 34|28|22|16|10|4\n\ + 35|29|23|17|11|5", + ); + } + + fn test_rotation(e: &str) { + let original_grid: TileMap = TileMap::from_fn(|x| x.into()); + + let rotated_0 = original_grid.with_rotate(QuarterTurns::Zero); + assert_eq!(original_grid, rotated_0); + + let rotated_1 = original_grid.with_rotate(QuarterTurns::One); + + assert_eq!(rotated_1.to_string(), e); + + let rotated_2 = original_grid.with_rotate(QuarterTurns::Two); + let rotated_1x2 = original_grid + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One); + + assert_eq!(rotated_1x2, rotated_2); + + let rotated_3 = original_grid.with_rotate(QuarterTurns::Three); + let rotated_1x3 = original_grid + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One); + assert_eq!(rotated_1x3, rotated_3); + + let rotated_1x4 = original_grid + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One) + .with_rotate(QuarterTurns::One); + assert_eq!(rotated_1x4, original_grid); + } + + #[test] + fn basic_tests() { + let grid: TileMap = TileMap::from_fn(|x| x.into()); + + for i in 0..9 { + assert_eq!(grid[Tile::<3, 3>::try_from_usize(i).unwrap()], i) + } + + let str = grid.to_string(); + + assert_eq!( + str, + "0|1|2\n\ + 3|4|5\n\ + 6|7|8" + ); + + assert_eq!(format!("{grid:#}"), "0|1|2|3|4|5|6|7|8"); + + let s_flat = grid.iter().join("|"); + assert_eq!(s_flat, "0|1|2|3|4|5|6|7|8"); + } + + #[cfg(any(test, feature = "serde"))] + #[test] + fn test_serde() { + let grid: TileMap = TileMap::from_fn(|x| x.into()); + + assert_tokens( + &grid, + &[ + Token::NewtypeStruct { name: "TileMap" }, + Token::Tuple { len: 4 }, + Token::U64(0), + Token::U64(1), + Token::U64(2), + Token::U64(3), + Token::TupleEnd, + ], + ); + } + + #[test] + fn test_get_scale() { + assert_eq!(TileMap::::get_scale(12.0, 20.0), 4.0); + } +} diff --git a/src/tile_set.rs b/src/tile_set.rs index 30d9460..98fcb29 100644 --- a/src/tile_set.rs +++ b/src/tile_set.rs @@ -1,1113 +1,1118 @@ -use core::{ - fmt::{self, Write}, - ops::FnMut, -}; - -use crate::prelude::*; - -#[cfg(any(test, feature = "serde"))] -use serde::{Deserialize, Serialize}; - -macro_rules! tile_set { - ($name:ident, $iter_name:ident, $true_iter_name:ident, $inner: ty) => { - - /// A grid - /// A map from tiles to bools. Can store up to 256 tiles. - #[must_use] - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] - #[cfg_attr(any(test, feature = "serde"), derive(Serialize, Deserialize))] - pub struct $name($inner); - - impl Default - for $name - { - fn default() -> Self { - Self::EMPTY - } - } - - impl $name { - - /// The set where all tiles are missing - pub const EMPTY: Self = { - Self::assert_legal(); - Self(0) - }; - - /// The set where all tiles are present - pub const ALL: Self = Self(<$inner>::MAX >> (<$inner>::BITS - SIZE as u32)); - - - - #[inline] - const fn assert_legal() { - debug_assert!(SIZE == (WIDTH as usize * HEIGHT as usize) ); - debug_assert!(SIZE <= <$inner>::BITS as usize); - } - - - #[inline] - pub fn from_fn) -> bool>(mut cb: F) -> Self { - Self::assert_legal(); - - let mut result = Self::default(); - for tile in Tile::::iter_by_row() { - if cb(tile) { - result.set_bit(&tile, true); - } - } - - result - } - - #[inline] - pub const fn from_inner(inner: $inner) -> Self { - Self::assert_legal(); - Self(inner) - } - - #[must_use] - #[inline] - pub const fn into_inner(self) -> $inner { - self.0 - } - - #[must_use] - #[inline] - pub const fn is_empty(self)-> bool{ - self.0 == Self::EMPTY.0 - } - - #[inline] - pub const fn set_bit(&mut self, tile: &Tile, bit: bool) { - if bit { - self.0 |= ((1 as $inner) << tile.inner() as u32); - } else { - self.0 &= !((1 as $inner) << tile.inner() as u32); - } - } - - /// Returns a copy of self with the bit at `tile` set to `bit` - #[inline] - pub const fn with_bit_set(&self, tile: &Tile, bit: bool)-> Self{ - let inner = - if bit { - self.0 | ((1 as $inner) << tile.inner() as u32) - } else { - self.0 & !((1 as $inner) << tile.inner() as u32) - }; - - Self(inner) - } - - #[must_use] - #[inline] - pub const fn get_bit(&self, tile: &Tile) -> bool { - (self.0 >> tile.inner() as u32) & 1 == 1 - } - - #[must_use] - #[inline] - pub const fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { - $iter_name::<1> { - bottom_index: 0, - top_index: SIZE, - inner: self.0, - } - } - - #[inline] - #[must_use] - pub const fn row(&self, y: u8) -> impl DoubleEndedIterator + ExactSizeIterator { - $iter_name::<1> { - bottom_index: (y * WIDTH) as usize, - top_index: ((y + 1) * WIDTH) as usize, - inner: self.0, - } - } - - #[inline] - #[must_use] - pub const fn col(&self, x: u8) -> impl DoubleEndedIterator + ExactSizeIterator { - - $iter_name:: { - bottom_index: x as usize, - top_index: ((WIDTH * (HEIGHT - 1)) + x + 1) as usize, - inner: self.0, - } - } - - - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn shift_north(&self, rows: u8)-> Self{ - let a =self.0 >> (rows * WIDTH); - Self(a & Self::ALL.0) - } - - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn shift_south(&self, rows: u8)-> Self{ - let a =self.0 << (rows * WIDTH); - Self(a & Self::ALL.0) - } - - pub const fn shift_east(&self)-> Self{ - let a = (self.0 << 1) & !Self::COL_ZERO_MASK; - Self(a & Self::ALL.0) - } - - pub const fn shift_west(&self)-> Self{ - let a = (self.0 >> 1) & !(Self::COL_ZERO_MASK << (WIDTH - 1)); - Self(a & Self::ALL.0) - } - - const ROW_ZERO_MASK: $inner = { - let mut inner : $inner = 0; - let mut tile = Some(Tile::::NORTH_WEST); - - while let Some(t) = tile { - let i = t.inner(); - inner |= 1 << i; - tile = t.const_add(&Vector::EAST); - } - inner - }; - - #[inline] - pub const fn row_mask(y: u8) -> Self { - Self::assert_legal(); - let inner = Self::ROW_ZERO_MASK << (y * WIDTH); - - Self(inner) - } - - const COL_ZERO_MASK: $inner = { - let mut inner : $inner = 0; - let mut tile = Some(Tile::::NORTH_WEST); - - while let Some(t) = tile { - let i = t.inner(); - inner |= 1 << i; - tile = t.const_add(&Vector::SOUTH); - } - inner - }; - - #[inline] - pub const fn col_mask(x: u8) -> Self { - Self::assert_legal(); - let inner = Self::COL_ZERO_MASK << (x); - - Self(inner) - } - - #[must_use] - #[inline] - pub fn enumerate( - &self, - ) -> impl DoubleEndedIterator, bool)> + ExactSizeIterator { - self.iter() - .enumerate() - .map(|(i, x)| (Tile::try_from_usize(i).unwrap(), x)) - } - - #[must_use] - #[inline] - pub const fn iter_true_tiles(&self) -> impl ExactSizeIterator> + Clone + core::fmt::Debug + core::iter::FusedIterator + DoubleEndedIterator { - $true_iter_name::new(self) - } - - #[must_use] - #[inline] - pub const fn count(&self) -> u32 { - self.0.count_ones() - } - - /// Get the scale to make the grid take up as much as possible of a given area - #[must_use] - #[inline] - pub const fn get_scale(total_width: f32, total_height: f32) -> f32 { - let x_multiplier = total_width / (WIDTH as f32); - let y_multiplier = total_height / (HEIGHT as f32); - - if x_multiplier <= y_multiplier{ - x_multiplier - }else{ - y_multiplier - } - } - - #[inline] - pub const fn intersect(&self, rhs: &Self) -> Self { - Self(self.0 & rhs.0) - } - - #[inline] - pub const fn union(&self, rhs: &Self) -> Self { - Self(self.0 | rhs.0) - } - - #[must_use] - #[inline] - pub const fn is_subset(&self, rhs: &Self)-> bool{ - self.intersect(rhs).0 == self.0 - } - - #[must_use] - #[inline] - pub const fn is_superset(&self, rhs: &Self)-> bool{ - self.intersect(rhs).0 == rhs.0 - } - - /// Returns a new set containing all elements which belong to one set but not both - #[inline] - pub const fn symmetric_difference(&self, rhs: &Self)-> Self{ - Self(self.0 ^ rhs.0) - } - - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn negate(&self) -> Self { - Self(!self.0 & Self::ALL.0) - } - - /// The first tile in this set - #[must_use] - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn first(&self) -> Option> - { - Tile::::try_from_inner( self.0.trailing_zeros() as u8) - } - - /// Removes the first tile in this set and returns it - /// Returns `None` if the set is empty - #[must_use] - #[inline] - #[allow(clippy::cast_possible_truncation)] - #[allow(clippy::cast_lossless)] - pub const fn pop(&mut self) -> Option> - { - if self.0 == 0{ - return None; - } - let index = self.0.trailing_zeros() as $inner; - self.0 &= !((1 as $inner) << index); - Some(Tile::::from_inner_unchecked(index as u8)) - } - - /// Removes the first tile in this set and returns it - /// Returns `None` if the set is empty - #[must_use] - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn pop_last(&mut self) -> Option> - { - if self.0 == 0{ - return None; - } - let index = <$inner>::BITS - 1 - self.0.leading_zeros(); - self.0 &= !((1 as $inner) << index); - Some(Tile::::from_inner_unchecked(index as u8)) - } - - /// The last tile in this set - #[must_use] - #[inline] - #[allow(clippy::cast_possible_truncation)] - pub const fn last(&self)-> Option> - { - let Some(index) = (<$inner>::BITS - 1).checked_sub( self.0.leading_zeros()) else{return None;}; - - Some(Tile::::from_inner_unchecked(index as u8)) - } - - /// Returns the number of tiles in the set which are less than this tile. - /// Note that it returns the same result whether or not the given tile is in the set - #[must_use] - #[inline] - pub const fn tiles_before(&self, tile: Tile)-> u32{ - let s = self.0; - - let shift = <$inner>::BITS - tile.inner() as u32; - - match s.checked_shl(shift){ - Some(x)=> x.count_ones(), - None=> 0 - } - } - - /// Returns the nth tile in the set, if it is present - #[must_use] - #[inline] - pub const fn nth(&self, n: u32) -> Option> { - - if n >= self.0.count_ones(){return None;} - - let desired_ones = self.0.count_ones() - n; - - let mut shifted_away = 0u32; - let mut remaining = self.0; - - let mut chunk_size = <$inner>::BITS / 2; - - //todo test a branchless version of this - loop { - let r = remaining >> chunk_size; - if r.count_ones() == desired_ones { - return Some(Tile::::from_inner_unchecked( - (shifted_away + chunk_size + r.trailing_zeros()) as u8, - )); - } - let cmp = (r.count_ones() > desired_ones) as u32; - shifted_away += cmp * chunk_size; - remaining >>= chunk_size * cmp; - chunk_size /= 2; - } - } - - -} - -impl FromIterator> for $name{ - - #[inline] - fn from_iter(iter: T) -> Self - where T: IntoIterator> - { - Self::assert_legal(); - let mut r = Self::default(); - for x in iter { - r.set_bit(&x, true); - } - r - } - -} - -#[derive(Clone, Debug)] -struct $true_iter_name { - inner: $name::, -} - -impl core::iter::FusedIterator - for $true_iter_name{} - -impl ExactSizeIterator - for $true_iter_name -{ - #[inline] - fn len(&self) -> usize { - self.inner.count() as usize - } -} - -impl Iterator - for $true_iter_name -{ - type Item = Tile; - - #[inline] - fn next(&mut self) -> Option { - self.inner.pop() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - let size = self.len(); - (size, Some(size)) - } - - #[inline] - fn count(self) -> usize - where - Self: Sized, - { - self.inner.count() as usize - } - - #[inline] - fn last(self) -> Option - where - Self: Sized, - { - self.inner.last() - } - - #[inline] - fn max(self) -> Option - where - Self: Sized, - Self::Item: Ord, - { - self.last() - } - - #[inline] - fn min(mut self) -> Option - where - Self: Sized, - Self::Item: Ord, - { - self.next() - } - - #[inline] - fn is_sorted(self)-> bool{ - true - } - - #[inline] - #[allow(clippy::cast_possible_truncation)] - fn nth(&mut self, n: usize) -> Option { - let mut n = n as u32; - if self.inner.count() <= n{ - self.inner.0 = Default::default(); // Empty the set - return None; - } - - let mut shift: u32 = 0; - loop { - let tz = self.inner.0.trailing_zeros(); - shift += tz; - self.inner.0 >>= tz; //can't be all zeros as we checked the count - - let to = self.inner.0.trailing_ones(); - if let Some(new_n) = n.checked_sub(to) { - n = new_n; - self.inner.0 >>= to; - shift += to; - } else { - let r = Self::Item::try_from_inner((shift + n ) as u8); - if shift + n + 1 < <$inner>::BITS - { - self.inner.0 >>= n + 1; - self.inner.0 <<= shift + n + 1; - }else{ - self.inner.0 = 0; - } - - return r; - } - } - } -} - -impl core::iter::DoubleEndedIterator - for $true_iter_name{ - #[inline] - fn next_back(&mut self) -> Option { - self.inner.pop_last() - } - - #[inline] - #[allow(clippy::cast_possible_truncation)] - fn nth_back(&mut self, n: usize) -> Option { - let mut n = n as u32; - if self.inner.count() <= n{ - self.inner.0 = Default::default(); // Empty the set - return None; - } - - let mut shift: u32 = 0; - loop { - let lz = self.inner.0.leading_zeros(); - shift += lz; - self.inner.0 <<= lz; //can't be all zeros as we checked the count - - let lo = self.inner.0.leading_ones(); - if let Some(new_n) = n.checked_sub(lo) { - n = new_n; - self.inner.0 <<= lo; - shift += lo; - } else { - - let r = Self::Item::try_from_inner((<$inner>::BITS - (shift + n + 1)) as u8); - - if shift + n + 1 < <$inner>::BITS - { - self.inner.0 <<= n + 1; - self.inner.0 >>= shift + n + 1; - } - else{ - self.inner.0 = 0; - } - - return r; - } - } - } - } - -impl $true_iter_name { - #[inline] - pub const fn new(set: & $name) -> Self { - Self { - inner: *set - } - } -} - -#[derive(Clone, Debug)] -pub struct $iter_name { - inner: $inner, - bottom_index: usize, - top_index: usize, -} - -impl ExactSizeIterator for $iter_name { - #[inline] - fn len(&self) -> usize { - self.clone().count() - } -} - -impl Iterator for $iter_name { - type Item = bool; - - #[inline] - fn next(&mut self) -> Option { - - if self.bottom_index >= self.top_index { - None - } else { - let r = (self.inner >> self.bottom_index) & 1 == 1; - self.bottom_index += (STEP as usize); - Some(r) - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - let count = self.clone().count(); - (count, Some(count)) - } - - #[inline] - fn count(self) -> usize - where - Self: Sized, - { - let distance = (self.top_index.saturating_sub(self.bottom_index)) as usize; - let count = (distance / STEP as usize); - count - } -} - -impl DoubleEndedIterator for $iter_name { - #[inline] - fn next_back(&mut self) -> Option { - if self.top_index == 0{ - return None; - } - let next_index = self.top_index.saturating_sub(STEP as usize); - if self.bottom_index > next_index { - None - } else { - self.top_index = next_index; - let r = (self.inner >> self.top_index) & 1 == 1; - - Some(r) - } - } -} - - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter().enumerate(); - - for (i, e) in iter { - if i > 0 && i % (W as usize) == 0 { - if !f.alternate(){ - f.write_char('\n')?; - } - } - if e { - f.write_char('*')?; - } else { - f.write_char('_')?; - } - } - - Ok(()) - } - } - - impl fmt::Binary for $name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Binary::fmt(&self.0, f) - } - } - }; -} - -tile_set!(TileSet8, TileSetIter8, TrueTilesIter8, u8); -tile_set!(TileSet16, TileSetIter16, TrueTilesIter16, u16); -tile_set!(TileSet32, TileSetIter32, TrueTilesIter32, u32); -tile_set!(TileSet64, TileSetIter64, TrueTilesIter64, u64); -tile_set!(TileSet128, TileSetIter128, TrueTilesIter128, u128); - -#[cfg(test)] -mod tests { - use super::*; - use itertools::Itertools; - - #[test] - fn basic_tests() { - let mut grid: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.inner() % 2 == 0); - - assert_eq!(grid.to_string(), "*_*\n_*_\n*_*"); - assert_eq!(format!("{grid:#}"), "*_*_*_*_*"); - - assert_eq!(grid.count(), 5); - - for tile in Tile::<3, 3>::iter_by_row() { - assert_eq!(grid.get_bit(&tile), tile.inner() % 2 == 0) - } - - grid.set_bit(&Tile::CENTER, false); - - assert_eq!(grid.to_string(), "*_*\n___\n*_*"); - assert_eq!(grid.count(), 4); - - assert_eq!( - grid.iter_true_tiles() - .map(|x| x.inner()) - .collect_vec() - .as_slice(), - &[0, 2, 6, 8] - ); - - assert_eq!(grid.iter().count(), 9); - assert_eq!(grid.into_inner(), 325); - - assert_eq!(grid.negate().to_string(), "_*_\n***\n_*_"); - } - - #[test] - fn test_intersect() { - let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 1); - let grid_right: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 1); - - assert_eq!( - grid_left.intersect(&grid_right).to_string(), - "___\n\ - _*_\n\ - ___" - ) - } - - #[test] - fn test_tiles_before() { - fn tiles_before_slow(tile: Tile<4, 4>, set: TileSet16<4, 4, 16>) -> usize { - set.iter_true_tiles().take_while(|x| x < &tile).count() - } - - fn test_all_tiles(set: TileSet16<4, 4, 16>) { - for tile in Tile::<4, 4>::iter_by_row() { - let expected = tiles_before_slow(tile, set) as u32; - let actual = set.tiles_before(tile); - - assert_eq!( - expected, - actual, - "Set {:016b}. Index {}", - set.into_inner(), - tile.inner() - ); - } - } - - for inner in [0, 1, 2, 3, 3206, 9999, u16::MAX] { - let set = TileSet16::from_inner(inner); - - test_all_tiles(set); - } - } - - #[test] - fn test_union() { - let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 0); - let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); - - assert_eq!( - grid_left.union(&grid_top).to_string(), - "***\n\ - *__\n\ - *__" - ) - } - - #[test] - fn test_symmetric_difference() { - let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 0); - let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); - - assert_eq!( - grid_left.symmetric_difference(&grid_top).to_string(), - "_**\n\ - *__\n\ - *__" - ) - } - - #[test] - fn test_subset() { - let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); - let all: TileSet16<3, 3, 9> = TileSet16::ALL; - - assert!(grid_top.is_subset(&all)); - assert!(grid_top.is_subset(&grid_top)); - assert!(!all.is_subset(&grid_top)); - } - - #[test] - fn test_superset() { - let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); - let all: TileSet16<3, 3, 9> = TileSet16::ALL; - - assert!(!grid_top.is_superset(&all)); - assert!(grid_top.is_superset(&grid_top)); - assert!(all.is_superset(&grid_top)); - } - - #[test] - fn test_from_inner() { - assert_eq!( - TileSet16::<3, 3, 9>::from_inner(3).to_string(), - "**_\n___\n___" - ) - } - - #[test] - fn test_from_iter() { - let grid = TileSet16::<3, 3, 9>::from_iter( - [ - Tile::try_from_inner(0).unwrap(), - Tile::try_from_inner(1).unwrap(), - ] - .into_iter(), - ); - assert_eq!(grid.to_string(), "**_\n___\n___") - } - - #[test] - fn test_iter_reverse() { - let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() >= 6); - - assert_eq!( - grid.iter() - .rev() - .map(|x| x.then(|| "*").unwrap_or("_")) - .join(""), - "******______" - ); - } - - #[test] - fn test_row() { - let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() % 3 == 1); - - assert_eq!( - grid.row(0).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "_*__" - ); - assert_eq!( - grid.row(1).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "*__*" - ); - assert_eq!( - grid.row(2).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "__*_" - ); - } - - #[test] - fn test_col() { - let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() % 2 == 1); - - assert_eq!( - grid.col(0).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "_*_" - ); - assert_eq!( - grid.col(1).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "*_*" - ); - assert_eq!( - grid.col(2).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "_*_" - ); - - assert_eq!( - grid.col(3).map(|x| x.then(|| "*").unwrap_or("_")).join(""), - "*_*" - ); - } - - #[test] - fn test_enumerate() { - let grid = TileSet16::<3, 3, 9>::from_fn(|x| x.inner() == 5); - - assert_eq!( - grid.enumerate() - .map(|(t, x)| t.inner().to_string() + x.then(|| "*").unwrap_or("_")) - .join(""), - "0_1_2_3_4_5*6_7_8_" - ); - } - - #[rustfmt::skip] - #[test] - fn test_shift() { - let full_grid = TileSet16::<2, 3, 6>::ALL; - - assert_eq!(full_grid.shift_north(0), full_grid); - assert_eq!(full_grid.shift_south(0), full_grid); - - assert_eq!(full_grid.shift_north(1).to_string(), "**\n**\n__", "Shift North 1"); - assert_eq!(full_grid.shift_south(1).to_string(), "__\n**\n**", "Shift South 1"); - - assert_eq!(full_grid.shift_north(2).to_string(), "**\n__\n__", "Shift North 2"); - assert_eq!(full_grid.shift_south(2).to_string(), "__\n__\n**", "Shift South 2"); - - assert_eq!(full_grid.shift_east().to_string(), "_*\n_*\n_*", "Shift East"); - assert_eq!(full_grid.shift_west().to_string(), "*_\n*_\n*_", "Shift West"); - - } - - #[test] - fn test_row_mask() { - type Grid = TileSet16<4, 3, 12>; - assert_eq!(Grid::row_mask(0).to_string(), "****\n____\n____"); - assert_eq!(Grid::row_mask(1).to_string(), "____\n****\n____"); - assert_eq!(Grid::row_mask(2).to_string(), "____\n____\n****"); - } - - #[test] - fn test_col_mask() { - type Grid = TileSet16<4, 3, 12>; - assert_eq!(Grid::col_mask(0).to_string(), "*___\n*___\n*___"); - assert_eq!(Grid::col_mask(1).to_string(), "_*__\n_*__\n_*__"); - assert_eq!(Grid::col_mask(2).to_string(), "__*_\n__*_\n__*_"); - assert_eq!(Grid::col_mask(3).to_string(), "___*\n___*\n___*"); - } - - #[test] - fn test_get_scale() { - type Grid = TileSet16<4, 3, 12>; - - let scale_square = Grid::get_scale(100.0, 100.0); - let scale_rect = Grid::get_scale(100.0, 50.0); - - assert_eq!(scale_square, 25.0); - assert_eq!(scale_rect, 16.666_666) - } - - #[test] - fn test_all() { - type Grid = TileSet16<4, 3, 12>; - let all = Grid::ALL; - - assert_eq!("****\n****\n****", all.to_string().as_str()) - } - - #[test] - fn test_is_empty() { - type Grid = TileSet16<4, 3, 12>; - assert!(Grid::EMPTY.is_empty()); - assert!(!Grid::EMPTY.with_bit_set(&Tile::NORTH_EAST, true).is_empty()) - } - - #[test] - fn test_with_bit_set() { - type Grid = TileSet16<4, 3, 12>; - assert_eq!( - "*___\n____\n____", - Grid::EMPTY - .with_bit_set(&Tile::NORTH_WEST, true) - .to_string() - .as_str() - ); - assert_eq!( - "_***\n****\n****", - Grid::ALL - .with_bit_set(&Tile::NORTH_WEST, false) - .to_string() - .as_str() - ); - } - - #[test] - fn test_iter_length_and_count() { - type Iter = TileSetIter16<2>; - - let iter = Iter { - inner: 0, - bottom_index: 0, - top_index: 12, - }; - - let len = iter.len(); - - assert_eq!(len, 6); - - let count = iter.count(); - - assert_eq!(count, 6); - } - - #[test] - fn test_true_iter_length_and_count() { - type Grid = TileSet16<4, 3, 12>; - - let mut iter = Grid::ALL.iter_true_tiles(); - - assert_eq!(12, iter.len()); - assert_eq!(12, iter.clone().count()); - - let _ = iter.next(); - assert_eq!(11, iter.len()); - assert_eq!(11, iter.count()); - } - - #[test] - fn test_true_iter_min_max_last() { - type Grid = TileSet16<4, 3, 12>; - - let iter = Grid::from_fn(|x| x.inner() % 5 == 0).iter_true_tiles(); - - assert_eq!(0, iter.clone().min().unwrap().inner()); - assert_eq!(10, iter.clone().max().unwrap().inner()); - assert_eq!(10, iter.last().unwrap().inner()); - } - - #[test] - fn test_first() { - let mut set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); - - let expected: Vec<_> = Tile::<4, 3>::iter_by_row() - .filter(|tile| tile.x() > tile.y()) - .collect(); - let mut actual: Vec> = vec![]; - - while let Some(first) = set.first() { - set.set_bit(&first, false); - actual.push(first); - } - - assert_eq!(expected, actual); - } - - #[test] - fn test_last() { - let mut set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); - - let mut expected: Vec<_> = Tile::<4, 3>::iter_by_row() - .filter(|tile| tile.x() > tile.y()) - .collect(); - expected.reverse(); - let mut actual: Vec> = vec![]; - - while let Some(last) = set.last() { - set.set_bit(&last, false); - actual.push(last); - } - - assert_eq!(expected, actual); - } - - #[test] - fn test_iter_true_rev() { - let set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); - - let mut expected: Vec<_> = Tile::<4, 3>::iter_by_row() - .filter(|tile| tile.x() > tile.y()) - .collect(); - expected.reverse(); - let actual: Vec> = set.iter_true_tiles().rev().collect(); - - assert_eq!(expected, actual); - } - - #[test] - fn test_true_iter_nth() { - let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() > tile.y()); - let vec: Vec<_> = Tile::<9, 7>::iter_by_row() - .filter(|tile| tile.x() > tile.y()) - .collect(); - - let mut set_iter = set.iter_true_tiles(); - let mut vec_iter = vec.into_iter(); - - for n in [0usize, 1, 0, 5, 0, 1, 0, 40] { - assert_eq!(set_iter.len(), vec_iter.len()); - let actual = set_iter.nth(n); - let expected = vec_iter.nth(n); - //println!("n: {n} actual: {actual:?} expected {expected:?}"); - assert_eq!(actual, expected); - } - assert_eq!(set_iter.len(), vec_iter.len()); - } - - #[test] - fn test_true_iter_nth_2() { - let set: TileSet16<16, 1, 16> = TileSet16::ALL; - - for n in 0..=17usize { - let actual = set.iter_true_tiles().nth(n); - - assert_eq!(actual, Tile::try_from_usize(n)) - } - } - - #[test] - fn test_true_iter_nth_back() { - let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() > tile.y()); - let vec: Vec<_> = Tile::<9, 7>::iter_by_row() - .filter(|tile| tile.x() > tile.y()) - .collect(); - - let mut set_iter = set.iter_true_tiles(); - let mut vec_iter = vec.into_iter(); - - for n in [0usize, 1, 0, 5, 0, 1, 0, 40] { - assert_eq!(set_iter.len(), vec_iter.len()); - let actual = set_iter.nth_back(n); - let expected = vec_iter.nth_back(n); - //println!("n: {n} actual: {actual:?} expected {expected:?}"); - assert_eq!(actual, expected); - } - assert_eq!(set_iter.len(), vec_iter.len()); - } - - #[test] - fn test_nth_u64() { - let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() == 0); - - let nth_elements = (0..8u32).map(|n| set.nth(n)).collect_vec(); - let expected = set - .iter_true_tiles() - .map(|x| Some(x)) - .chain(std::iter::repeat(None)) - .take(8) - .collect_vec(); - - assert_eq!(nth_elements, expected) - } - - #[test] - fn test_nth_u32() { - let set: TileSet32<4, 8, 32> = TileSet32::from_fn(|tile| tile.x() == 3); - - let nth_elements = (0..8u32).map(|n| set.nth(n)).collect_vec(); - let expected = set - .iter_true_tiles() - .map(|x| Some(x)) - .chain(std::iter::repeat(None)) - .take(8) - .collect_vec(); - - assert_eq!(nth_elements, expected) - } -} +use core::{ + fmt::{self, Write}, + ops::FnMut, +}; + +use crate::prelude::*; + +#[cfg(any(test, feature = "serde"))] +use serde::{Deserialize, Serialize}; + +macro_rules! tile_set { + ($name:ident, $iter_name:ident, $true_iter_name:ident, $inner: ty) => { + /// A grid + /// A map from tiles to bools. Can store up to 256 tiles. + #[must_use] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] + #[cfg_attr(any(test, feature = "serde"), derive(Serialize, Deserialize))] + pub struct $name($inner); + + impl Default + for $name + { + fn default() -> Self { + Self::EMPTY + } + } + + impl $name { + /// The set where all tiles are missing + pub const EMPTY: Self = { + Self::assert_legal(); + Self(0) + }; + + + /// The set where all tiles are present + #[allow(clippy::cast_possible_truncation)] + pub const ALL: Self = Self(<$inner>::MAX >> (<$inner>::BITS - SIZE as u32)); + + #[inline] + const fn assert_legal() { + debug_assert!(SIZE == (WIDTH as usize * HEIGHT as usize)); + debug_assert!(SIZE <= <$inner>::BITS as usize); + } + + #[inline] + pub fn from_fn) -> bool>(mut cb: F) -> Self { + Self::assert_legal(); + + let mut result = Self::default(); + for tile in Tile::::iter_by_row() { + if cb(tile) { + result.set_bit(&tile, true); + } + } + + result + } + + #[inline] + pub const fn from_inner(inner: $inner) -> Self { + Self::assert_legal(); + Self(inner) + } + + #[must_use] + #[inline] + pub const fn into_inner(self) -> $inner { + self.0 + } + + #[must_use] + #[inline] + pub const fn is_empty(self) -> bool { + self.0 == Self::EMPTY.0 + } + + #[inline] + pub const fn set_bit(&mut self, tile: &Tile, bit: bool) { + if bit { + self.0 |= ((1 as $inner) << tile.inner() as u32); + } else { + self.0 &= !((1 as $inner) << tile.inner() as u32); + } + } + + /// Returns a copy of self with the bit at `tile` set to `bit` + #[inline] + pub const fn with_bit_set(&self, tile: &Tile, bit: bool) -> Self { + let inner = if bit { + self.0 | ((1 as $inner) << tile.inner() as u32) + } else { + self.0 & !((1 as $inner) << tile.inner() as u32) + }; + + Self(inner) + } + + #[must_use] + #[inline] + pub const fn get_bit(&self, tile: &Tile) -> bool { + (self.0 >> tile.inner() as u32) & 1 == 1 + } + + #[must_use] + #[inline] + pub const fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { + $iter_name::<1> { + bottom_index: 0, + top_index: SIZE, + inner: self.0, + } + } + + #[inline] + #[must_use] + pub const fn row( + &self, + y: u8, + ) -> impl DoubleEndedIterator + ExactSizeIterator { + $iter_name::<1> { + bottom_index: (y * WIDTH) as usize, + top_index: ((y + 1) * WIDTH) as usize, + inner: self.0, + } + } + + #[inline] + #[must_use] + pub const fn col( + &self, + x: u8, + ) -> impl DoubleEndedIterator + ExactSizeIterator { + $iter_name:: { + bottom_index: x as usize, + top_index: ((WIDTH * (HEIGHT - 1)) + x + 1) as usize, + inner: self.0, + } + } + + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn shift_north(&self, rows: u8) -> Self { + let a = self.0 >> (rows * WIDTH); + Self(a & Self::ALL.0) + } + + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn shift_south(&self, rows: u8) -> Self { + let a = self.0 << (rows * WIDTH); + Self(a & Self::ALL.0) + } + + pub const fn shift_east(&self) -> Self { + let a = (self.0 << 1) & !Self::COL_ZERO_MASK; + Self(a & Self::ALL.0) + } + + pub const fn shift_west(&self) -> Self { + let a = (self.0 >> 1) & !(Self::COL_ZERO_MASK << (WIDTH - 1)); + Self(a & Self::ALL.0) + } + + const ROW_ZERO_MASK: $inner = { + let mut inner: $inner = 0; + let mut tile = Some(Tile::::NORTH_WEST); + + while let Some(t) = tile { + let i = t.inner(); + inner |= 1 << i; + tile = t.const_add(&Vector::EAST); + } + inner + }; + + #[inline] + pub const fn row_mask(y: u8) -> Self { + Self::assert_legal(); + let inner = Self::ROW_ZERO_MASK << (y * WIDTH); + + Self(inner) + } + + const COL_ZERO_MASK: $inner = { + let mut inner: $inner = 0; + let mut tile = Some(Tile::::NORTH_WEST); + + while let Some(t) = tile { + let i = t.inner(); + inner |= 1 << i; + tile = t.const_add(&Vector::SOUTH); + } + inner + }; + + #[inline] + pub const fn col_mask(x: u8) -> Self { + Self::assert_legal(); + let inner = Self::COL_ZERO_MASK << (x); + + Self(inner) + } + + #[must_use] + #[inline] + pub fn enumerate( + &self, + ) -> impl DoubleEndedIterator, bool)> + ExactSizeIterator + { + self.iter() + .enumerate() + .map(|(i, x)| (Tile::try_from_usize(i).unwrap(), x)) + } + + #[must_use] + #[inline] + pub const fn iter_true_tiles( + &self, + ) -> impl ExactSizeIterator> + + Clone + + core::fmt::Debug + + core::iter::FusedIterator + + DoubleEndedIterator { + $true_iter_name::new(self) + } + + #[must_use] + #[inline] + pub const fn count(&self) -> u32 { + self.0.count_ones() + } + + /// Get the scale to make the grid take up as much as possible of a given area + #[must_use] + #[inline] + pub const fn get_scale(total_width: f32, total_height: f32) -> f32 { + let x_multiplier = total_width / (WIDTH as f32); + let y_multiplier = total_height / (HEIGHT as f32); + + if x_multiplier <= y_multiplier { + x_multiplier + } else { + y_multiplier + } + } + + #[inline] + pub const fn intersect(&self, rhs: &Self) -> Self { + Self(self.0 & rhs.0) + } + + #[inline] + pub const fn union(&self, rhs: &Self) -> Self { + Self(self.0 | rhs.0) + } + + #[must_use] + #[inline] + pub const fn is_subset(&self, rhs: &Self) -> bool { + self.intersect(rhs).0 == self.0 + } + + #[must_use] + #[inline] + pub const fn is_superset(&self, rhs: &Self) -> bool { + self.intersect(rhs).0 == rhs.0 + } + + /// Returns a new set containing all elements which belong to one set but not both + #[inline] + pub const fn symmetric_difference(&self, rhs: &Self) -> Self { + Self(self.0 ^ rhs.0) + } + + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn negate(&self) -> Self { + Self(!self.0 & Self::ALL.0) + } + + /// The first tile in this set + #[must_use] + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn first(&self) -> Option> { + Tile::::try_from_inner(self.0.trailing_zeros() as u8) + } + + /// Removes the first tile in this set and returns it + /// Returns `None` if the set is empty + #[must_use] + #[inline] + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_lossless)] + pub const fn pop(&mut self) -> Option> { + if self.0 == 0 { + return None; + } + let index = self.0.trailing_zeros() as $inner; + self.0 &= !((1 as $inner) << index); + Some(Tile::::from_inner_unchecked(index as u8)) + } + + /// Removes the first tile in this set and returns it + /// Returns `None` if the set is empty + #[must_use] + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn pop_last(&mut self) -> Option> { + if self.0 == 0 { + return None; + } + let index = <$inner>::BITS - 1 - self.0.leading_zeros(); + self.0 &= !((1 as $inner) << index); + Some(Tile::::from_inner_unchecked(index as u8)) + } + + /// The last tile in this set + #[must_use] + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn last(&self) -> Option> { + let Some(index) = (<$inner>::BITS - 1).checked_sub(self.0.leading_zeros()) else { + return None; + }; + + Some(Tile::::from_inner_unchecked(index as u8)) + } + + /// Returns the number of tiles in the set which are less than this tile. + /// Note that it returns the same result whether or not the given tile is in the set + #[must_use] + #[inline] + pub const fn tiles_before(&self, tile: Tile) -> u32 { + let s = self.0; + + let shift = <$inner>::BITS - tile.inner() as u32; + + match s.checked_shl(shift) { + Some(x) => x.count_ones(), + None => 0, + } + } + + /// Returns the nth tile in the set, if it is present + #[must_use] + #[inline] + #[allow(clippy::cast_possible_truncation)] + pub const fn nth(&self, n: u32) -> Option> { + if n >= self.0.count_ones() { + return None; + } + + let desired_ones = self.0.count_ones() - n; + + let mut shifted_away = 0u32; + let mut remaining = self.0; + + let mut chunk_size = <$inner>::BITS / 2; + + //todo test a branchless version of this + loop { + let r = remaining >> chunk_size; + if r.count_ones() == desired_ones { + return Some(Tile::::from_inner_unchecked( + (shifted_away + chunk_size + r.trailing_zeros()) as u8, + )); + } + let cmp = (r.count_ones() > desired_ones) as u32; + shifted_away += cmp * chunk_size; + remaining >>= chunk_size * cmp; + chunk_size /= 2; + } + } + } + + impl FromIterator> + for $name + { + #[inline] + fn from_iter(iter: T) -> Self + where + T: IntoIterator>, + { + Self::assert_legal(); + let mut r = Self::default(); + for x in iter { + r.set_bit(&x, true); + } + r + } + } + + #[derive(Clone, Debug)] + struct $true_iter_name { + inner: $name, + } + + impl core::iter::FusedIterator + for $true_iter_name + { + } + + impl ExactSizeIterator + for $true_iter_name + { + #[inline] + fn len(&self) -> usize { + self.inner.count() as usize + } + } + + impl Iterator + for $true_iter_name + { + type Item = Tile; + + #[inline] + fn next(&mut self) -> Option { + self.inner.pop() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + let size = self.len(); + (size, Some(size)) + } + + #[inline] + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() as usize + } + + #[inline] + fn last(self) -> Option + where + Self: Sized, + { + self.inner.last() + } + + #[inline] + fn max(self) -> Option + where + Self: Sized, + Self::Item: Ord, + { + self.last() + } + + #[inline] + fn min(mut self) -> Option + where + Self: Sized, + Self::Item: Ord, + { + self.next() + } + + #[inline] + fn is_sorted(self) -> bool { + true + } + + #[inline] + #[allow(clippy::cast_possible_truncation)] + fn nth(&mut self, n: usize) -> Option { + let mut n = n as u32; + if self.inner.count() <= n { + self.inner.0 = Default::default(); // Empty the set + return None; + } + + let mut shift: u32 = 0; + loop { + let tz = self.inner.0.trailing_zeros(); + shift += tz; + self.inner.0 >>= tz; //can't be all zeros as we checked the count + + let to = self.inner.0.trailing_ones(); + if let Some(new_n) = n.checked_sub(to) { + n = new_n; + self.inner.0 >>= to; + shift += to; + } else { + let r = Self::Item::try_from_inner((shift + n) as u8); + if shift + n + 1 < <$inner>::BITS { + self.inner.0 >>= n + 1; + self.inner.0 <<= shift + n + 1; + } else { + self.inner.0 = 0; + } + + return r; + } + } + } + } + + impl core::iter::DoubleEndedIterator + for $true_iter_name + { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.pop_last() + } + + #[inline] + #[allow(clippy::cast_possible_truncation)] + fn nth_back(&mut self, n: usize) -> Option { + let mut n = n as u32; + if self.inner.count() <= n { + self.inner.0 = Default::default(); // Empty the set + return None; + } + + let mut shift: u32 = 0; + loop { + let lz = self.inner.0.leading_zeros(); + shift += lz; + self.inner.0 <<= lz; //can't be all zeros as we checked the count + + let lo = self.inner.0.leading_ones(); + if let Some(new_n) = n.checked_sub(lo) { + n = new_n; + self.inner.0 <<= lo; + shift += lo; + } else { + let r = + Self::Item::try_from_inner((<$inner>::BITS - (shift + n + 1)) as u8); + + if shift + n + 1 < <$inner>::BITS { + self.inner.0 <<= n + 1; + self.inner.0 >>= shift + n + 1; + } else { + self.inner.0 = 0; + } + + return r; + } + } + } + } + + impl + $true_iter_name + { + #[inline] + pub const fn new(set: &$name) -> Self { + Self { inner: *set } + } + } + + #[derive(Clone, Debug)] + pub struct $iter_name { + inner: $inner, + bottom_index: usize, + top_index: usize, + } + + impl ExactSizeIterator for $iter_name { + #[inline] + fn len(&self) -> usize { + self.clone().count() + } + } + + impl Iterator for $iter_name { + type Item = bool; + + #[inline] + fn next(&mut self) -> Option { + if self.bottom_index >= self.top_index { + None + } else { + let r = (self.inner >> self.bottom_index) & 1 == 1; + self.bottom_index += (STEP as usize); + Some(r) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let count = self.clone().count(); + (count, Some(count)) + } + + #[inline] + fn count(self) -> usize + where + Self: Sized, + { + let distance = (self.top_index.saturating_sub(self.bottom_index)) as usize; + let count = (distance / STEP as usize); + count + } + } + + impl DoubleEndedIterator for $iter_name { + #[inline] + fn next_back(&mut self) -> Option { + if self.top_index == 0 { + return None; + } + let next_index = self.top_index.saturating_sub(STEP as usize); + if self.bottom_index > next_index { + None + } else { + self.top_index = next_index; + let r = (self.inner >> self.top_index) & 1 == 1; + + Some(r) + } + } + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter().enumerate(); + + for (i, e) in iter { + if i > 0 && i % (W as usize) == 0 { + if !f.alternate() { + f.write_char('\n')?; + } + } + if e { + f.write_char('*')?; + } else { + f.write_char('_')?; + } + } + + Ok(()) + } + } + + impl fmt::Binary for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } + } + }; +} + +tile_set!(TileSet8, TileSetIter8, TrueTilesIter8, u8); +tile_set!(TileSet16, TileSetIter16, TrueTilesIter16, u16); +tile_set!(TileSet32, TileSetIter32, TrueTilesIter32, u32); +tile_set!(TileSet64, TileSetIter64, TrueTilesIter64, u64); +tile_set!(TileSet128, TileSetIter128, TrueTilesIter128, u128); + +#[cfg(test)] +mod tests { + use super::*; + use itertools::Itertools; + + #[test] + fn basic_tests() { + let mut grid: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.inner() % 2 == 0); + + assert_eq!(grid.to_string(), "*_*\n_*_\n*_*"); + assert_eq!(format!("{grid:#}"), "*_*_*_*_*"); + + assert_eq!(grid.count(), 5); + + for tile in Tile::<3, 3>::iter_by_row() { + assert_eq!(grid.get_bit(&tile), tile.inner() % 2 == 0) + } + + grid.set_bit(&Tile::CENTER, false); + + assert_eq!(grid.to_string(), "*_*\n___\n*_*"); + assert_eq!(grid.count(), 4); + + assert_eq!( + grid.iter_true_tiles() + .map(|x| x.inner()) + .collect_vec() + .as_slice(), + &[0, 2, 6, 8] + ); + + assert_eq!(grid.iter().count(), 9); + assert_eq!(grid.into_inner(), 325); + + assert_eq!(grid.negate().to_string(), "_*_\n***\n_*_"); + } + + #[test] + fn test_intersect() { + let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 1); + let grid_right: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 1); + + assert_eq!( + grid_left.intersect(&grid_right).to_string(), + "___\n\ + _*_\n\ + ___" + ) + } + + #[test] + fn test_tiles_before() { + fn tiles_before_slow(tile: Tile<4, 4>, set: TileSet16<4, 4, 16>) -> usize { + set.iter_true_tiles().take_while(|x| x < &tile).count() + } + + fn test_all_tiles(set: TileSet16<4, 4, 16>) { + for tile in Tile::<4, 4>::iter_by_row() { + let expected = tiles_before_slow(tile, set) as u32; + let actual = set.tiles_before(tile); + + assert_eq!( + expected, + actual, + "Set {:016b}. Index {}", + set.into_inner(), + tile.inner() + ); + } + } + + for inner in [0, 1, 2, 3, 3206, 9999, u16::MAX] { + let set = TileSet16::from_inner(inner); + + test_all_tiles(set); + } + } + + #[test] + fn test_union() { + let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 0); + let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); + + assert_eq!( + grid_left.union(&grid_top).to_string(), + "***\n\ + *__\n\ + *__" + ) + } + + #[test] + fn test_symmetric_difference() { + let grid_left: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.x() == 0); + let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); + + assert_eq!( + grid_left.symmetric_difference(&grid_top).to_string(), + "_**\n\ + *__\n\ + *__" + ) + } + + #[test] + fn test_subset() { + let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); + let all: TileSet16<3, 3, 9> = TileSet16::ALL; + + assert!(grid_top.is_subset(&all)); + assert!(grid_top.is_subset(&grid_top)); + assert!(!all.is_subset(&grid_top)); + } + + #[test] + fn test_superset() { + let grid_top: TileSet16<3, 3, 9> = TileSet16::from_fn(|x| x.y() == 0); + let all: TileSet16<3, 3, 9> = TileSet16::ALL; + + assert!(!grid_top.is_superset(&all)); + assert!(grid_top.is_superset(&grid_top)); + assert!(all.is_superset(&grid_top)); + } + + #[test] + fn test_from_inner() { + assert_eq!( + TileSet16::<3, 3, 9>::from_inner(3).to_string(), + "**_\n___\n___" + ) + } + + #[test] + fn test_from_iter() { + let grid = TileSet16::<3, 3, 9>::from_iter( + [ + Tile::try_from_inner(0).unwrap(), + Tile::try_from_inner(1).unwrap(), + ] + .into_iter(), + ); + assert_eq!(grid.to_string(), "**_\n___\n___") + } + + #[test] + fn test_iter_reverse() { + let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() >= 6); + + assert_eq!( + grid.iter() + .rev() + .map(|x| x.then(|| "*").unwrap_or("_")) + .join(""), + "******______" + ); + } + + #[test] + fn test_row() { + let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() % 3 == 1); + + assert_eq!( + grid.row(0).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "_*__" + ); + assert_eq!( + grid.row(1).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "*__*" + ); + assert_eq!( + grid.row(2).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "__*_" + ); + } + + #[test] + fn test_col() { + let grid = TileSet16::<4, 3, 12>::from_fn(|x| x.inner() % 2 == 1); + + assert_eq!( + grid.col(0).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "_*_" + ); + assert_eq!( + grid.col(1).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "*_*" + ); + assert_eq!( + grid.col(2).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "_*_" + ); + + assert_eq!( + grid.col(3).map(|x| x.then(|| "*").unwrap_or("_")).join(""), + "*_*" + ); + } + + #[test] + fn test_enumerate() { + let grid = TileSet16::<3, 3, 9>::from_fn(|x| x.inner() == 5); + + assert_eq!( + grid.enumerate() + .map(|(t, x)| t.inner().to_string() + x.then(|| "*").unwrap_or("_")) + .join(""), + "0_1_2_3_4_5*6_7_8_" + ); + } + + #[rustfmt::skip] + #[test] + fn test_shift() { + let full_grid = TileSet16::<2, 3, 6>::ALL; + + assert_eq!(full_grid.shift_north(0), full_grid); + assert_eq!(full_grid.shift_south(0), full_grid); + + assert_eq!(full_grid.shift_north(1).to_string(), "**\n**\n__", "Shift North 1"); + assert_eq!(full_grid.shift_south(1).to_string(), "__\n**\n**", "Shift South 1"); + + assert_eq!(full_grid.shift_north(2).to_string(), "**\n__\n__", "Shift North 2"); + assert_eq!(full_grid.shift_south(2).to_string(), "__\n__\n**", "Shift South 2"); + + assert_eq!(full_grid.shift_east().to_string(), "_*\n_*\n_*", "Shift East"); + assert_eq!(full_grid.shift_west().to_string(), "*_\n*_\n*_", "Shift West"); + + } + + #[test] + fn test_row_mask() { + type Grid = TileSet16<4, 3, 12>; + assert_eq!(Grid::row_mask(0).to_string(), "****\n____\n____"); + assert_eq!(Grid::row_mask(1).to_string(), "____\n****\n____"); + assert_eq!(Grid::row_mask(2).to_string(), "____\n____\n****"); + } + + #[test] + fn test_col_mask() { + type Grid = TileSet16<4, 3, 12>; + assert_eq!(Grid::col_mask(0).to_string(), "*___\n*___\n*___"); + assert_eq!(Grid::col_mask(1).to_string(), "_*__\n_*__\n_*__"); + assert_eq!(Grid::col_mask(2).to_string(), "__*_\n__*_\n__*_"); + assert_eq!(Grid::col_mask(3).to_string(), "___*\n___*\n___*"); + } + + #[test] + fn test_get_scale() { + type Grid = TileSet16<4, 3, 12>; + + let scale_square = Grid::get_scale(100.0, 100.0); + let scale_rect = Grid::get_scale(100.0, 50.0); + + assert_eq!(scale_square, 25.0); + assert_eq!(scale_rect, 16.666_666) + } + + #[test] + fn test_all() { + type Grid = TileSet16<4, 3, 12>; + let all = Grid::ALL; + + assert_eq!("****\n****\n****", all.to_string().as_str()) + } + + #[test] + fn test_is_empty() { + type Grid = TileSet16<4, 3, 12>; + assert!(Grid::EMPTY.is_empty()); + assert!(!Grid::EMPTY.with_bit_set(&Tile::NORTH_EAST, true).is_empty()) + } + + #[test] + fn test_with_bit_set() { + type Grid = TileSet16<4, 3, 12>; + assert_eq!( + "*___\n____\n____", + Grid::EMPTY + .with_bit_set(&Tile::NORTH_WEST, true) + .to_string() + .as_str() + ); + assert_eq!( + "_***\n****\n****", + Grid::ALL + .with_bit_set(&Tile::NORTH_WEST, false) + .to_string() + .as_str() + ); + } + + #[test] + fn test_iter_length_and_count() { + type Iter = TileSetIter16<2>; + + let iter = Iter { + inner: 0, + bottom_index: 0, + top_index: 12, + }; + + let len = iter.len(); + + assert_eq!(len, 6); + + let count = iter.count(); + + assert_eq!(count, 6); + } + + #[test] + fn test_true_iter_length_and_count() { + type Grid = TileSet16<4, 3, 12>; + + let mut iter = Grid::ALL.iter_true_tiles(); + + assert_eq!(12, iter.len()); + assert_eq!(12, iter.clone().count()); + + let _ = iter.next(); + assert_eq!(11, iter.len()); + assert_eq!(11, iter.count()); + } + + #[test] + fn test_true_iter_min_max_last() { + type Grid = TileSet16<4, 3, 12>; + + let iter = Grid::from_fn(|x| x.inner() % 5 == 0).iter_true_tiles(); + + assert_eq!(0, iter.clone().min().unwrap().inner()); + assert_eq!(10, iter.clone().max().unwrap().inner()); + assert_eq!(10, iter.last().unwrap().inner()); + } + + #[test] + fn test_first() { + let mut set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); + + let expected: Vec<_> = Tile::<4, 3>::iter_by_row() + .filter(|tile| tile.x() > tile.y()) + .collect(); + let mut actual: Vec> = vec![]; + + while let Some(first) = set.first() { + set.set_bit(&first, false); + actual.push(first); + } + + assert_eq!(expected, actual); + } + + #[test] + fn test_last() { + let mut set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); + + let mut expected: Vec<_> = Tile::<4, 3>::iter_by_row() + .filter(|tile| tile.x() > tile.y()) + .collect(); + expected.reverse(); + let mut actual: Vec> = vec![]; + + while let Some(last) = set.last() { + set.set_bit(&last, false); + actual.push(last); + } + + assert_eq!(expected, actual); + } + + #[test] + fn test_iter_true_rev() { + let set = TileSet16::<4, 3, 12>::from_fn(|tile| tile.x() > tile.y()); + + let mut expected: Vec<_> = Tile::<4, 3>::iter_by_row() + .filter(|tile| tile.x() > tile.y()) + .collect(); + expected.reverse(); + let actual: Vec> = set.iter_true_tiles().rev().collect(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_true_iter_nth() { + let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() > tile.y()); + let vec: Vec<_> = Tile::<9, 7>::iter_by_row() + .filter(|tile| tile.x() > tile.y()) + .collect(); + + let mut set_iter = set.iter_true_tiles(); + let mut vec_iter = vec.into_iter(); + + for n in [0usize, 1, 0, 5, 0, 1, 0, 40] { + assert_eq!(set_iter.len(), vec_iter.len()); + let actual = set_iter.nth(n); + let expected = vec_iter.nth(n); + //println!("n: {n} actual: {actual:?} expected {expected:?}"); + assert_eq!(actual, expected); + } + assert_eq!(set_iter.len(), vec_iter.len()); + } + + #[test] + fn test_true_iter_nth_2() { + let set: TileSet16<16, 1, 16> = TileSet16::ALL; + + for n in 0..=17usize { + let actual = set.iter_true_tiles().nth(n); + + assert_eq!(actual, Tile::try_from_usize(n)) + } + } + + #[test] + fn test_true_iter_nth_back() { + let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() > tile.y()); + let vec: Vec<_> = Tile::<9, 7>::iter_by_row() + .filter(|tile| tile.x() > tile.y()) + .collect(); + + let mut set_iter = set.iter_true_tiles(); + let mut vec_iter = vec.into_iter(); + + for n in [0usize, 1, 0, 5, 0, 1, 0, 40] { + assert_eq!(set_iter.len(), vec_iter.len()); + let actual = set_iter.nth_back(n); + let expected = vec_iter.nth_back(n); + //println!("n: {n} actual: {actual:?} expected {expected:?}"); + assert_eq!(actual, expected); + } + assert_eq!(set_iter.len(), vec_iter.len()); + } + + #[test] + fn test_nth_u64() { + let set: TileSet64<9, 7, 63> = TileSet64::<9, 7, 63>::from_fn(|tile| tile.x() == 0); + + let nth_elements = (0..8u32).map(|n| set.nth(n)).collect_vec(); + let expected = set + .iter_true_tiles() + .map(|x| Some(x)) + .chain(std::iter::repeat(None)) + .take(8) + .collect_vec(); + + assert_eq!(nth_elements, expected) + } + + #[test] + fn test_nth_u32() { + let set: TileSet32<4, 8, 32> = TileSet32::from_fn(|tile| tile.x() == 3); + + let nth_elements = (0..8u32).map(|n| set.nth(n)).collect_vec(); + let expected = set + .iter_true_tiles() + .map(|x| Some(x)) + .chain(std::iter::repeat(None)) + .take(8) + .collect_vec(); + + assert_eq!(nth_elements, expected) + } +} diff --git a/src/tile_set256.rs b/src/tile_set256.rs index 5f5cae1..a8bd126 100644 --- a/src/tile_set256.rs +++ b/src/tile_set256.rs @@ -195,16 +195,15 @@ impl TileSet256)-> u32{ + pub fn tiles_before(&self, tile: Tile) -> u32 { let s = self.0; let shift = U256::BITS - tile.inner() as u32; - match s.checked_shl(shift){ - Some(x)=> x.count_ones(), - None=> 0 + match s.checked_shl(shift) { + Some(x) => x.count_ones(), + None => 0, } - } /// Get the scale to make the grid take up as much as possible of a given area