#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Grid {
width: usize,
height: usize,
depth: usize,
fighters: Vec<Option<u64>>,
}
impl Grid {
pub fn new(width: usize, height: usize, depth: usize) -> Self {
let size = width * height * depth;
Self {
width,
height,
depth,
fighters: vec![None; size],
}
}
pub fn dimensions(&self) -> (usize, usize, usize) {
(self.width, self.height, self.depth)
}
#[inline]
pub fn xyz_to_index(&self, x: usize, y: usize, z: usize) -> Option<usize> {
if x < self.width && y < self.height && z < self.depth {
Some(x + y * self.width + z * self.width * self.height)
} else {
None
}
}
#[inline]
pub fn index_to_xyz(&self, index: usize) -> Option<(usize, usize, usize)> {
if index < self.fighters.len() {
let z = index / (self.width * self.height);
let remainder = index % (self.width * self.height);
let y = remainder / self.width;
let x = remainder % self.width;
Some((x, y, z))
} else {
None
}
}
#[inline]
pub fn fighter_at(&self, x: usize, y: usize, z: usize) -> Option<u64> {
self.xyz_to_index(x, y, z)
.and_then(|idx| self.fighters[idx])
}
#[inline]
pub fn set_fighter(&mut self, x: usize, y: usize, z: usize, fighter_id: Option<u64>) -> bool {
if let Some(idx) = self.xyz_to_index(x, y, z) {
self.fighters[idx] = fighter_id;
true
} else {
false
}
}
#[inline]
pub fn is_cell_empty(&self, x: usize, y: usize, z: usize) -> bool {
self.fighter_at(x, y, z).is_none()
}
pub fn neighbors(&self, x: usize, y: usize, z: usize) -> impl Iterator<Item = (usize, usize, usize)> {
let width = self.width;
let height = self.height;
let depth = self.depth;
let candidates = [
(x.wrapping_sub(1), y, z),
(x + 1, y, z),
(x, y.wrapping_sub(1), z),
(x, y + 1, z),
(x, y, z.wrapping_sub(1)),
(x, y, z + 1),
];
candidates
.into_iter()
.filter(move |(nx, ny, nz)| *nx < width && *ny < height && *nz < depth)
}
pub fn len(&self) -> usize {
self.fighters.len()
}
pub fn is_empty(&self) -> bool {
self.fighters.is_empty()
}
pub fn clear(&mut self) {
self.fighters.fill(None);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grid_new() {
let grid = Grid::new(10, 8, 2);
assert_eq!(grid.dimensions(), (10, 8, 2));
assert_eq!(grid.len(), 160);
}
#[test]
fn test_xyz_to_index() {
let grid = Grid::new(10, 8, 2);
assert_eq!(grid.xyz_to_index(0, 0, 0), Some(0));
assert_eq!(grid.xyz_to_index(9, 0, 0), Some(9));
assert_eq!(grid.xyz_to_index(0, 1, 0), Some(10));
assert_eq!(grid.xyz_to_index(0, 0, 1), Some(80));
assert_eq!(grid.xyz_to_index(10, 0, 0), None); }
#[test]
fn test_index_to_xyz() {
let grid = Grid::new(10, 8, 2);
assert_eq!(grid.index_to_xyz(0), Some((0, 0, 0)));
assert_eq!(grid.index_to_xyz(9), Some((9, 0, 0)));
assert_eq!(grid.index_to_xyz(10), Some((0, 1, 0)));
assert_eq!(grid.index_to_xyz(80), Some((0, 0, 1)));
assert_eq!(grid.index_to_xyz(160), None); }
#[test]
fn test_roundtrip() {
let grid = Grid::new(10, 8, 2);
for x in 0..10 {
for y in 0..8 {
for z in 0..2 {
let idx = grid.xyz_to_index(x, y, z).unwrap();
let (rx, ry, rz) = grid.index_to_xyz(idx).unwrap();
assert_eq!((x, y, z), (rx, ry, rz));
}
}
}
}
#[test]
fn test_set_and_get_fighter() {
let mut grid = Grid::new(10, 10, 1);
assert!(grid.is_cell_empty(5, 5, 0));
assert_eq!(grid.fighter_at(5, 5, 0), None);
let fighter_id = 0x1234567890abcdef_u64;
assert!(grid.set_fighter(5, 5, 0, Some(fighter_id)));
assert!(!grid.is_cell_empty(5, 5, 0));
assert_eq!(grid.fighter_at(5, 5, 0), Some(fighter_id));
grid.set_fighter(5, 5, 0, None);
assert!(grid.is_cell_empty(5, 5, 0));
}
#[test]
fn test_neighbors() {
let grid = Grid::new(10, 10, 1);
let neighbors: Vec<_> = grid.neighbors(5, 5, 0).collect();
assert_eq!(neighbors.len(), 4);
assert!(neighbors.contains(&(4, 5, 0)));
assert!(neighbors.contains(&(6, 5, 0)));
assert!(neighbors.contains(&(5, 4, 0)));
assert!(neighbors.contains(&(5, 6, 0)));
let corner_neighbors: Vec<_> = grid.neighbors(0, 0, 0).collect();
assert_eq!(corner_neighbors.len(), 2);
}
}