use std::cmp;
use std::ops::{Add, Mul, Sub};
use super::*;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Cube {
x: i32,
y: i32,
z: i32,
}
impl Add for Cube {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
}
impl Sub for Cube {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
}
}
}
impl Mul<i32> for Cube {
type Output = Self;
fn mul(self, n: i32) -> Self {
Self {
x: self.x * n,
y: self.y * n,
z: self.z * n,
}
}
}
impl Mul<Cube> for i32 {
type Output = Cube;
fn mul(self, coord: Cube) -> Cube {
coord * self
}
}
impl From<(i32, i32, i32)> for Cube {
fn from((x, y, z): (i32, i32, i32)) -> Self {
if z == 0 - x - y {
Self {
x: x,
y: y,
z: z,
}
} else {
panic!("({}, {}, {}) is an invalid cube coordiante", x, y, z);
}
}
}
impl From<Axial> for Cube {
fn from(coord: Axial) -> Self {
let x = coord.q;
let z = coord.r;
let y = 0 - x - z;
Self { x: x, y: y, z: z }
}
}
impl From<MultiCoord> for Cube {
fn from(coord: MultiCoord) -> Self {
match coord.sys {
CoordSys::Axial => {
let x = coord.a;
let z = coord.b;
Self {
x: x,
y: 0 - x - z,
z: z,
}
}
CoordSys::Cube => {
Self {
x: coord.a,
y: coord.b,
z: coord.c.unwrap(),
}
}
_ => panic!("{:?} is not a Cube or Axial coordinate", coord),
}
}
}
impl Cube {
pub const ORIGIN: Cube = Cube { x: 0, y: 0, z: 0 };
const NEIGHBOR_OFFSETS: [(i32, i32, i32); 6] = [
(1, 0, -1), (1, -1, 0),
(0, -1, 1),
(-1, 0, 1), (-1, 1, 0),
(0, 1, -1),
];
const DIAGONAL_OFFSETS: [(i32, i32, i32); 6] = [
(1, -2, 1), (-1, -1, 2),
(-2, 1, 1),
(-1, 2, -1), (1, 1, -2),
(2, -1, -1),
];
pub fn from_coords(x: i32, y: i32, z: i32) -> CoordResult<Cube> {
if z == 0 - x - y {
Ok(Self { x: x, y: y, z: z })
} else {
Err("invalid Cube coordinate")
}
}
pub fn force_from_coords(x: i32, y: i32, z: i32) -> Self {
Self::from_coords(x, y, z).unwrap()
}
pub fn from_tuple((x, y, z): (i32, i32, i32)) -> CoordResult<Cube> {
Cube::from_coords(x, y, z)
}
pub fn to_tuple(self) -> (i32, i32, i32) {
(self.x, self.y, self.z)
}
pub fn x(self) -> i32 {
self.x
}
pub fn y(self) -> i32 {
self.y
}
pub fn z(self) -> i32 {
self.z
}
pub fn neighbor(self, index: usize) -> Self {
self + Self::from(Self::NEIGHBOR_OFFSETS[index % 6])
}
pub fn neighbors(self) -> Vec<Self> {
let mut coords = Vec::new();
for index in 0..6 {
coords.push(self.neighbor(index));
}
coords
}
pub fn diagonal(self, index: usize) -> Self {
self + Self::from(Self::DIAGONAL_OFFSETS[index % 6])
}
pub fn diagonals(self) -> Vec<Self> {
let mut coords = Vec::new();
for index in 0..6 {
coords.push(self.diagonal(index));
}
coords
}
pub fn dist(self, other: Self) -> i32 {
let x_dist = (self.x - other.x).abs();
let y_dist = (self.y - other.y).abs();
let z_dist = (self.z - other.z).abs();
cmp::max(cmp::max(x_dist, y_dist), z_dist)
}
pub fn rotate_cw(self, point: Self, num_turns: u32) -> Self {
let mut vector = point - self;
for _turns in 0..(num_turns % 6) {
let new_x = 0 - vector.z;
let new_y = 0 - vector.x;
let new_z = 0 - vector.y;
vector = Cube::force_from_coords(new_x, new_y, new_z);
}
vector + self
}
pub fn rotate_cc(self, point: Self, num_turns: u32) -> Self {
let mut vector = point - self;
for _turns in 0..(num_turns % 6) {
let new_x = 0 - vector.y;
let new_y = 0 - vector.z;
let new_z = 0 - vector.x;
vector = Self::force_from_coords(new_x, new_y, new_z);
}
vector + self
}
pub fn ring(self, radius: u32) -> Vec<Self> {
let mut coords = Vec::new();
if radius == 0 {
coords = vec![self];
} else {
let init_index = 2;
for side in 0..6 {
let mut next_coord: Cube =
(radius as i32) * Cube::from(Cube::NEIGHBOR_OFFSETS[side])
+ self;
let side_dir = (side + init_index) % 6;
for _coord in 0..radius {
coords.push(next_coord);
next_coord = next_coord.neighbors()[side_dir];
}
}
}
coords
}
pub fn spiral(self, radius: u32) -> Vec<Self> {
let mut coords = Vec::new();
for r in 0..=radius {
coords.append(&mut self.ring(r));
}
coords
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cube_arithmetic() {
let unit_cube_x = Cube { x: 1, y: 0, z: 0 };
let unit_cube_y = Cube { x: 0, y: 1, z: 0 };
let unit_cube_z = Cube { x: 0, y: 0, z: 1 };
assert_eq!(3 * unit_cube_x, Cube { x: 3, y: 0, z: 0 });
assert_eq!(unit_cube_y * 5, Cube { x: 0, y: 5, z: 0 });
assert_eq!(7 * unit_cube_z, Cube { x: 0, y: 0, z: 7 });
assert_eq!(
unit_cube_x + unit_cube_y + unit_cube_z,
Cube { x: 1, y: 1, z: 1 }
);
assert_eq!(
7 * unit_cube_x + 5 * unit_cube_y + 3 * unit_cube_z,
Cube { x: 7, y: 5, z: 3 }
);
assert_eq!(
3 * (unit_cube_x + unit_cube_y) - 5 * unit_cube_z - unit_cube_y,
Cube { x: 3, y: 2, z: -5 }
);
}
#[test]
fn test_cube_from_tuples() {
assert_eq!(Cube::ORIGIN, Cube::from((0, 0, 0)));
assert_eq!(Cube { x: 1, y: 2, z: -3 }, Cube::from((1, 2, -3)));
assert_eq!(Cube { x: -3, y: -4, z: 7 }, Cube::from((-3, -4, 7)));
assert_eq!(Cube { x: -5, y: 6, z: -1 }, Cube::from((-5, 6, -1)));
assert_eq!(Cube { x: 7, y: -8, z: 1 }, Cube::from((7, -8, 1)));
}
#[test]
#[should_panic]
#[allow(unused_variables)]
fn test_cube_from_invalid_tuple_1() {
let cube = Cube::from((1, 2, 0));
}
#[test]
#[should_panic]
#[allow(unused_variables)]
fn test_cube_from_invalid_tuple_2() {
let cube = Cube::from((1, -2, 0));
}
#[test]
#[should_panic]
#[allow(unused_variables)]
fn test_cube_from_invalid_tuple_3() {
let cube = Cube::from((1, 0, 0));
}
#[test]
fn test_cube_from_integers() {
assert_eq!(
Cube::ORIGIN,
Cube::force_from_coords(0, 0, 0)
);
assert_eq!(
Cube { x: -1, y: 4, z: -3 },
Cube::force_from_coords(-1, 4, -3)
);
assert_eq!(
Cube { x: 7, y: -12, z: 5 },
Cube::force_from_coords(7, -12, 5)
);
assert_eq!(
Cube { x: -17, y: 10, z: 7 },
Cube::force_from_coords(-17, 10, 7)
);
assert_eq!(
Cube { x: 7, y: -10, z: 3 },
Cube::force_from_coords(7, -10, 3)
);
assert_eq!(
Cube { x: -4, y: -8, z: 12 },
Cube::force_from_coords(-4, -8, 12)
);
}
#[test]
fn test_cube_from_axial() {
assert_eq!(
Cube::ORIGIN,
Cube::from(Axial::ORIGIN),
"Cube::ORIGIN != Axial::ORIGIN"
);
assert_eq!(
Cube::force_from_coords(1, 2, -3),
Cube::from(Axial::from_coords(1, -3)),
"Cube(1, 2, -3) != Axial(1, -3)"
);
assert_eq!(
Cube::force_from_coords(7, -11, 4),
Cube::from(Axial::from_coords(7, 4)),
"Cube(7, -11, 4) != Axial(7, 4)"
);
assert_eq!(
Cube::force_from_coords(-11, 23, -12),
Cube::from(Axial::from_coords(-11, -12)),
"Cube(-11, 23, -12) != Axial(-11, -12)"
);
assert_eq!(
Cube::force_from_coords(-10, 4, 6),
Cube::from(Axial::from_coords(-10, 6)),
"Cube(-10, 4, 6) != Axial(-10, 6)"
);
}
#[test]
fn test_cube_neighbors() {
let exp_origin_neighbors = vec![
Cube { x: 1, y: 0, z: -1 },
Cube { x: 1, y: -1, z: 0 },
Cube { x: 0, y: -1, z: 1 },
Cube { x: -1, y: 0, z: 1 },
Cube { x: -1, y: 1, z: 0 },
Cube { x: 0, y: 1, z: -1},
];
assert_eq!(
exp_origin_neighbors[0],
Cube::ORIGIN.neighbor(0),
"origin neighbor (index of 0)"
);
assert_eq!(
exp_origin_neighbors[1],
Cube::ORIGIN.neighbor(1),
"origin neighbor (index of 1)"
);
assert_eq!(
exp_origin_neighbors[2],
Cube::ORIGIN.neighbor(2),
"origin neighbor (index of 2)"
);
assert_eq!(
exp_origin_neighbors[3],
Cube::ORIGIN.neighbor(3),
"origin neighbor (index of 3)"
);
assert_eq!(
exp_origin_neighbors[4],
Cube::ORIGIN.neighbor(4),
"origin neighbor (index of 4)"
);
assert_eq!(
exp_origin_neighbors[5],
Cube::ORIGIN.neighbor(5),
"origin neighbor (index of 5)"
);
assert_eq!(
exp_origin_neighbors,
Cube::ORIGIN.neighbors(),
"origin neighbors"
);
let offset_coord = Cube { x: -4, y: 13, z: -9 };
let exp_offset_neighbors = vec![
Cube { x: -3, y: 13, z: -10 },
Cube { x: -3, y: 12, z: -9 },
Cube { x: -4, y: 12, z: -8 },
Cube { x: -5, y: 13, z: -8 },
Cube { x: -5, y: 14, z: -9 },
Cube { x: -4, y: 14, z: -10 },
];
assert_eq!(
exp_offset_neighbors[0],
offset_coord.neighbor(0),
"offset neighbor (index of 0)"
);
assert_eq!(
exp_offset_neighbors[1],
offset_coord.neighbor(1),
"offset neighbor (index of 1)"
);
assert_eq!(
exp_offset_neighbors[2],
offset_coord.neighbor(2),
"offset neighbor (index of 2)"
);
assert_eq!(
exp_offset_neighbors[3],
offset_coord.neighbor(3),
"offset neighbor (index of 3)"
);
assert_eq!(
exp_offset_neighbors[4],
offset_coord.neighbor(4),
"offset neighbor (index of 4)"
);
assert_eq!(
exp_offset_neighbors[5],
offset_coord.neighbor(5),
"offset neighbor (index of 5)"
);
assert_eq!(
exp_offset_neighbors,
offset_coord.neighbors(),
"offset neighbors"
);
}
#[test]
fn test_cube_diagonals() {
let exp_origin_diagonals = vec![
Cube { x: 1, y: -2, z: 1 },
Cube { x: -1, y: -1, z: 2 },
Cube { x: -2, y: 1, z: 1 },
Cube { x: -1, y: 2, z: -1 },
Cube { x: 1, y: 1, z: -2 },
Cube { x: 2, y: -1, z: -1},
];
assert_eq!(
exp_origin_diagonals[0],
Cube::ORIGIN.diagonal(0),
"origin diagonal (index of 0)"
);
assert_eq!(
exp_origin_diagonals[1],
Cube::ORIGIN.diagonal(1),
"origin diagonal (index of 1)"
);
assert_eq!(
exp_origin_diagonals[2],
Cube::ORIGIN.diagonal(2),
"origin diagonal (index of 2)"
);
assert_eq!(
exp_origin_diagonals[3],
Cube::ORIGIN.diagonal(3),
"origin diagonal (index of 3)"
);
assert_eq!(
exp_origin_diagonals[4],
Cube::ORIGIN.diagonal(4),
"origin diagonal (index of 4)"
);
assert_eq!(
exp_origin_diagonals[5],
Cube::ORIGIN.diagonal(5),
"origin diagonal (index of 5)"
);
assert_eq!(
exp_origin_diagonals,
Cube::ORIGIN.diagonals(),
"origin diagonals"
);
let offset_coord = Cube { x: 7, y: 3, z: -10 };
let exp_offset_diagonals = vec![
Cube { x: 8, y: 1, z: -9 },
Cube { x: 6, y: 2, z: -8 },
Cube { x: 5, y: 4, z: -9 },
Cube { x: 6, y: 5, z: -11 },
Cube { x: 8, y: 4, z: -12 },
Cube { x: 9, y: 2, z: -11},
];
assert_eq!(
exp_offset_diagonals[0],
offset_coord.diagonal(0),
"offset diagonal (index of 0)"
);
assert_eq!(
exp_offset_diagonals[1],
offset_coord.diagonal(1),
"offset diagonal (index of 1)"
);
assert_eq!(
exp_offset_diagonals[2],
offset_coord.diagonal(2),
"offset diagonal (index of 2)"
);
assert_eq!(
exp_offset_diagonals[3],
offset_coord.diagonal(3),
"offset diagonal (index of 3)"
);
assert_eq!(
exp_offset_diagonals[4],
offset_coord.diagonal(4),
"offset diagonal (index of 4)"
);
assert_eq!(
exp_offset_diagonals[5],
offset_coord.diagonal(5),
"offset diagonal (index of 5)"
);
assert_eq!(
exp_offset_diagonals,
offset_coord.diagonals(),
"offset diagonals"
);
}
#[test]
fn test_cube_rings() {
let origin_ring_0 = vec![Cube::ORIGIN];
let origin_ring_1 = vec![
Cube::force_from_coords(1, 0, -1),
Cube::force_from_coords(1, -1, 0),
Cube::force_from_coords(0, -1, 1),
Cube::force_from_coords(-1, 0, 1),
Cube::force_from_coords(-1, 1, 0),
Cube::force_from_coords(0, 1, -1),
];
let origin_ring_2 = vec![
Cube::force_from_coords(2, 0, -2),
Cube::force_from_coords(2, -1, -1),
Cube::force_from_coords(2, -2, 0),
Cube::force_from_coords(1, -2, 1),
Cube::force_from_coords(0, -2, 2),
Cube::force_from_coords(-1, -1, 2),
Cube::force_from_coords(-2, 0, 2),
Cube::force_from_coords(-2, 1, 1),
Cube::force_from_coords(-2, 2, 0),
Cube::force_from_coords(-1, 2, -1),
Cube::force_from_coords(0, 2, -2),
Cube::force_from_coords(1, 1, -2),
];
let origin_ring_5 = vec![
Cube::force_from_coords(5, 0, -5),
Cube::force_from_coords(5, -1, -4),
Cube::force_from_coords(5, -2, -3),
Cube::force_from_coords(5, -3, -2),
Cube::force_from_coords(5, -4, -1),
Cube::force_from_coords(5, -5, 0),
Cube::force_from_coords(4, -5, 1),
Cube::force_from_coords(3, -5, 2),
Cube::force_from_coords(2, -5, 3),
Cube::force_from_coords(1, -5, 4),
Cube::force_from_coords(0, -5, 5),
Cube::force_from_coords(-1, -4, 5),
Cube::force_from_coords(-2, -3, 5),
Cube::force_from_coords(-3, -2, 5),
Cube::force_from_coords(-4, -1, 5),
Cube::force_from_coords(-5, 0, 5),
Cube::force_from_coords(-5, 1, 4),
Cube::force_from_coords(-5, 2, 3),
Cube::force_from_coords(-5, 3, 2),
Cube::force_from_coords(-5, 4, 1),
Cube::force_from_coords(-5, 5, 0),
Cube::force_from_coords(-4, 5, -1),
Cube::force_from_coords(-3, 5, -2),
Cube::force_from_coords(-2, 5, -3),
Cube::force_from_coords(-1, 5, -4),
Cube::force_from_coords(0, 5, -5),
Cube::force_from_coords(1, 4, -5),
Cube::force_from_coords(2, 3, -5),
Cube::force_from_coords(3, 2, -5),
Cube::force_from_coords(4, 1, -5),
];
assert_eq!(origin_ring_0, Cube::ORIGIN.ring(0), "origin ring 0");
assert_eq!(origin_ring_1, Cube::ORIGIN.ring(1), "origin ring 1");
assert_eq!(origin_ring_2, Cube::ORIGIN.ring(2), "origin ring 2");
assert_eq!(origin_ring_5, Cube::ORIGIN.ring(5), "origin ring 5");
let offset_coord = Cube::force_from_coords(2, 3, -5);
let offset_ring_0 = vec![Cube::force_from_coords(2, 3, -5)];
let offset_ring_1 = vec![
Cube::force_from_coords(3, 3, -6),
Cube::force_from_coords(3, 2, -5),
Cube::force_from_coords(2, 2, -4),
Cube::force_from_coords(1, 3, -4),
Cube::force_from_coords(1, 4, -5),
Cube::force_from_coords(2, 4, -6),
];
let offset_ring_2 = vec![
Cube::force_from_coords(4, 3, -7),
Cube::force_from_coords(4, 2, -6),
Cube::force_from_coords(4, 1, -5),
Cube::force_from_coords(3, 1, -4),
Cube::force_from_coords(2, 1, -3),
Cube::force_from_coords(1, 2, -3),
Cube::force_from_coords(0, 3, -3),
Cube::force_from_coords(0, 4, -4),
Cube::force_from_coords(0, 5, -5),
Cube::force_from_coords(1, 5, -6),
Cube::force_from_coords(2, 5, -7),
Cube::force_from_coords(3, 4, -7),
];
assert_eq!(offset_ring_0, offset_coord.ring(0), "offset ring 0");
assert_eq!(offset_ring_1, offset_coord.ring(1), "offset ring 1");
assert_eq!(offset_ring_2, offset_coord.ring(2), "offset ring 2");
}
#[test]
fn test_cube_spirals() {
let origin_spiral_0 = vec![Cube::ORIGIN];
let origin_spiral_1 = vec![
Cube::ORIGIN,
Cube::force_from_coords(1, 0, -1),
Cube::force_from_coords(1, -1, 0),
Cube::force_from_coords(0, -1, 1),
Cube::force_from_coords(-1, 0, 1),
Cube::force_from_coords(-1, 1, 0),
Cube::force_from_coords(0, 1, -1),
];
let origin_spiral_2 = vec![
Cube::ORIGIN,
Cube::force_from_coords(1, 0, -1),
Cube::force_from_coords(1, -1, 0),
Cube::force_from_coords(0, -1, 1),
Cube::force_from_coords(-1, 0, 1),
Cube::force_from_coords(-1, 1, 0),
Cube::force_from_coords(0, 1, -1),
Cube::force_from_coords(2, 0, -2),
Cube::force_from_coords(2, -1, -1),
Cube::force_from_coords(2, -2, 0),
Cube::force_from_coords(1, -2, 1),
Cube::force_from_coords(0, -2, 2),
Cube::force_from_coords(-1, -1, 2),
Cube::force_from_coords(-2, 0, 2),
Cube::force_from_coords(-2, 1, 1),
Cube::force_from_coords(-2, 2, 0),
Cube::force_from_coords(-1, 2, -1),
Cube::force_from_coords(0, 2, -2),
Cube::force_from_coords(1, 1, -2),
];
assert_eq!(
origin_spiral_0,
Cube::ORIGIN.spiral(0),
"origin spiral 0"
);
assert_eq!(
origin_spiral_1,
Cube::ORIGIN.spiral(1),
"origin spiral 1"
);
assert_eq!(
origin_spiral_2,
Cube::ORIGIN.spiral(2),
"origin spiral 2"
);
let offset_spiral_0 = vec![Cube::force_from_coords(5, -3, -2)];
let offset_spiral_1 = vec![
Cube::force_from_coords(5, -3, -2),
Cube::force_from_coords(6, -3, -3),
Cube::force_from_coords(6, -4, -2),
Cube::force_from_coords(5, -4, -1),
Cube::force_from_coords(4, -3, -1),
Cube::force_from_coords(4, -2, -2),
Cube::force_from_coords(5, -2, -3),
];
let offset_spiral_2 = vec![
Cube::force_from_coords(5, -3, -2),
Cube::force_from_coords(6, -3, -3),
Cube::force_from_coords(6, -4, -2),
Cube::force_from_coords(5, -4, -1),
Cube::force_from_coords(4, -3, -1),
Cube::force_from_coords(4, -2, -2),
Cube::force_from_coords(5, -2, -3),
Cube::force_from_coords(7, -3, -4),
Cube::force_from_coords(7, -4, -3),
Cube::force_from_coords(7, -5, -2),
Cube::force_from_coords(6, -5, -1),
Cube::force_from_coords(5, -5, 0),
Cube::force_from_coords(4, -4, 0),
Cube::force_from_coords(3, -3, 0),
Cube::force_from_coords(3, -2, -1),
Cube::force_from_coords(3, -1, -2),
Cube::force_from_coords(4, -1, -3),
Cube::force_from_coords(5, -1, -4),
Cube::force_from_coords(6, -2, -4),
];
assert_eq!(
offset_spiral_0,
Cube::force_from_coords(5, -3, -2).spiral(0),
"offset spiral 0"
);
assert_eq!(
offset_spiral_1,
Cube::force_from_coords(5, -3, -2).spiral(1),
"offset spiral 1"
);
assert_eq!(
offset_spiral_2,
Cube::force_from_coords(5, -3, -2).spiral(2),
"offset spiral 2"
);
}
}