diff --git a/src/common/area.rs b/src/common/area.rs index af1cbe4..c05c588 100644 --- a/src/common/area.rs +++ b/src/common/area.rs @@ -3,22 +3,22 @@ use std::fmt::Display; use num_traits::{Num, NumAssignOps}; -use super::pos::Pos2D; +use super::pos2::Pos2; #[derive(Debug, Clone, Copy, Default)] pub struct Area where T: Num, { - lower_left: Pos2D, - upper_right: Pos2D, + lower_left: Pos2, + upper_right: Pos2, } impl Area where T: Num + Ord + Copy, { - pub fn new(p1: Pos2D, p2: Pos2D) -> Area { + pub fn new(p1: Pos2, p2: Pos2) -> Area { Area { lower_left: p1.min_components(p2), upper_right: p1.max_components(p2), @@ -30,7 +30,7 @@ impl Area where T: Num + Ord + Copy, { - pub fn extend(&self, pos: Pos2D) -> Area { + pub fn extend(&self, pos: Pos2) -> Area { if self.contains(pos) { return *self; } @@ -40,15 +40,15 @@ where upper_right: self.upper_right.max_components(pos), } } - pub fn get_lower_left(&self) -> Pos2D { + pub fn get_lower_left(&self) -> Pos2 { self.lower_left } - pub fn get_upper_right(&self) -> Pos2D { + pub fn get_upper_right(&self) -> Pos2 { self.upper_right } - pub fn contains(&self, pos: Pos2D) -> bool { + pub fn contains(&self, pos: Pos2) -> bool { self.lower_left.x() >= pos.x() && pos.x() >= self.upper_right.x() && self.lower_left.y() >= pos.y() @@ -62,7 +62,7 @@ where { pub fn from_iterator(mut iter: I) -> Option where - I: Iterator>, + I: Iterator>, { let first = *iter.next()?; let (upper, lower) = iter.fold((first, first), |(mx, mn), p| { @@ -214,12 +214,12 @@ impl<'a, T> Iterator for ColIterator<'a, T> where T: Num + Ord + NumAssignOps + Copy, { - type Item = Pos2D; + type Item = Pos2; fn next(&mut self) -> Option { if (self.ascending && self.col <= self.area.upper_right.x()) || (!self.ascending && self.col >= self.area.lower_left.x()) { - let pos = Pos2D::new(self.col, self.row); + let pos = Pos2::new(self.col, self.row); if self.ascending { self.col += T::one(); } else { @@ -266,13 +266,13 @@ impl<'a, T> Iterator for CellIterator<'a, T> where T: Num + Ord + NumAssignOps + Copy, { - type Item = Pos2D; + type Item = Pos2; fn next(&mut self) -> Option { if (self.ascending && self.row <= self.area.upper_right.y()) || (!self.ascending && self.row >= self.area.lower_left.y()) { - let pos = Pos2D::new(self.col, self.row); + let pos = Pos2::new(self.col, self.row); if self.ascending { self.col += T::one(); if self.col > self.area.upper_right.x() { @@ -300,18 +300,18 @@ mod test { #[test] fn test_cell_iterator() { - let area = Area::new(Pos2D::new(-1, -1), Pos2D::new(1, 1)); + let area = Area::new(Pos2::new(-1, -1), Pos2::new(1, 1)); let result = area.cells(true).collect::>(); let expected = vec![ - Pos2D::new(-1, -1), - Pos2D::new(0, -1), - Pos2D::new(1, -1), - Pos2D::new(-1, 0), - Pos2D::new(0, 0), - Pos2D::new(1, 0), - Pos2D::new(-1, 1), - Pos2D::new(0, 1), - Pos2D::new(1, 1), + Pos2::new(-1, -1), + Pos2::new(0, -1), + Pos2::new(1, -1), + Pos2::new(-1, 0), + Pos2::new(0, 0), + Pos2::new(1, 0), + Pos2::new(-1, 1), + Pos2::new(0, 1), + Pos2::new(1, 1), ]; assert_eq!(result, expected); } diff --git a/src/common/direction.rs b/src/common/direction.rs index 9c5118f..66faeb0 100644 --- a/src/common/direction.rs +++ b/src/common/direction.rs @@ -1,4 +1,4 @@ -use super::{pos::Pos2D, turn::Turn}; +use super::{pos2::Pos2, turn::Turn}; use num_traits::{Num, Signed}; use std::{fmt::Display, ops::Add}; use Direction::*; @@ -82,49 +82,49 @@ impl Display for Direction { } } -impl From for Pos2D +impl From for Pos2 where T: Num + Signed, { fn from(value: Direction) -> Self { match value { - East => Pos2D::new(T::one(), T::zero()), - North => Pos2D::new(T::zero(), -T::one()), - West => Pos2D::new(-T::one(), T::zero()), - South => Pos2D::new(T::zero(), T::one()), + East => Pos2::new(T::one(), T::zero()), + North => Pos2::new(T::zero(), -T::one()), + West => Pos2::new(-T::one(), T::zero()), + South => Pos2::new(T::zero(), T::one()), } } } -impl Add> for Direction { - type Output = Pos2D; +impl Add> for Direction { + type Output = Pos2; - fn add(self, rhs: Pos2D) -> Self::Output { - Pos2D::add(rhs, self) + fn add(self, rhs: Pos2) -> Self::Output { + Pos2::add(rhs, self) } } -impl Add> for &Direction { - type Output = Pos2D; +impl Add> for &Direction { + type Output = Pos2; - fn add(self, rhs: Pos2D) -> Self::Output { - Pos2D::add(rhs, *self) + fn add(self, rhs: Pos2) -> Self::Output { + Pos2::add(rhs, *self) } } -impl Add<&Pos2D> for Direction { - type Output = Pos2D; +impl Add<&Pos2> for Direction { + type Output = Pos2; - fn add(self, rhs: &Pos2D) -> Self::Output { - Pos2D::add(*rhs, self) + fn add(self, rhs: &Pos2) -> Self::Output { + Pos2::add(*rhs, self) } } -impl Add<&Pos2D> for &Direction { - type Output = Pos2D; +impl Add<&Pos2> for &Direction { + type Output = Pos2; - fn add(self, rhs: &Pos2D) -> Self::Output { - Pos2D::add(*rhs, *self) + fn add(self, rhs: &Pos2) -> Self::Output { + Pos2::add(*rhs, *self) } } diff --git a/src/common/mod.rs b/src/common/mod.rs index 35c9c55..9c8f391 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -5,5 +5,7 @@ pub mod helper; pub mod math; pub mod name; pub mod parser; -pub mod pos; +pub mod pos2; +pub mod pos3; pub mod turn; +pub mod unit_vector; diff --git a/src/common/pos.rs b/src/common/pos2.rs similarity index 59% rename from src/common/pos.rs rename to src/common/pos2.rs index a5cd42d..a4cb0a7 100644 --- a/src/common/pos.rs +++ b/src/common/pos2.rs @@ -1,57 +1,48 @@ use super::direction::Direction; use super::math::gcd; -use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed}; +use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero}; use std::fmt; -use std::ops::{Add, Div, Mul, Sub}; - -pub type Pos2D = Pos; +use std::ops::{Add, Div, Mul, Neg, Sub}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] -pub struct Pos([T; 2]); +pub struct Pos2([T; 2]); -impl Pos { +impl Pos2 { #[inline] - pub fn new(x: T, y: T) -> Pos { - Pos([x, y]) + pub fn new(x: T, y: T) -> Pos2 { + Pos2([x, y]) } + #[inline] pub fn get_x(&self) -> &T { &self.0[0] } + #[inline] pub fn get_y(&self) -> &T { &self.0[1] } } -impl From<&[T]> for Pos { - fn from(value: &[T]) -> Self { - match value.len() { - 0 => Pos::new(T::default(), T::default()), - 1 => Pos::new(value[0], T::default()), - _ => Pos::new(value[0], value[1]), - } - } -} - -impl From<[T; 2]> for Pos { +impl From<[T; 2]> for Pos2 { fn from(value: [T; 2]) -> Self { - Pos(value) + Pos2(value) } } -impl From<(T, T)> for Pos { +impl From<(T, T)> for Pos2 { fn from(value: (T, T)) -> Self { - Pos([value.0, value.1]) + Pos2([value.0, value.1]) } } -impl Pos +impl Pos2 where T: Copy, { - pub fn splat(v: T) -> Pos { - Pos::new(v, v) + #[inline] + pub fn splat(v: T) -> Pos2 { + Pos2::new(v, v) } pub fn x(&self) -> T { @@ -63,26 +54,26 @@ where } } -impl Pos +impl Pos2 where T: Num + Ord + Copy, { - pub fn normalize(self) -> Result<(Pos, T), Pos> { + pub fn normalize(self) -> Result<(Pos2, T), Pos2> { if self.x().is_zero() && self.y().is_zero() { Err(self) } else { gcd(self.x(), self.y()) - .map(|ggt| (Pos::new(self.x() / ggt, self.y() / ggt), ggt)) + .map(|ggt| (Pos2::new(self.x() / ggt, self.y() / ggt), ggt)) .ok_or(self) } } } -impl Pos +impl Pos2 where T: Float, { - pub fn normal(self) -> Result<(Pos, T), Pos> { + pub fn normal(self) -> Result<(Pos2, T), Pos2> { let length = self.length(); if length == T::zero() { Err(self) @@ -92,7 +83,7 @@ where } } -impl Pos +impl Pos2 where T: Num + NumCast, { @@ -113,20 +104,20 @@ where } } -impl Pos +impl Pos2 where T: Ord + Copy, { - pub fn max_components(self, other: Pos) -> Self { - Pos::new(self.x().max(other.x()), self.y().max(other.y())) + pub fn max_components(self, other: Pos2) -> Self { + Pos2::new(self.x().max(other.x()), self.y().max(other.y())) } - pub fn min_components(self, other: Pos) -> Self { - Pos::new(self.x().min(other.x()), self.y().min(other.y())) + pub fn min_components(self, other: Pos2) -> Self { + Pos2::new(self.x().min(other.x()), self.y().min(other.y())) } } -impl Pos +impl Pos2 where T: Signed, { @@ -135,7 +126,7 @@ where } } -impl Pos +impl Pos2 where T: Float, { @@ -144,7 +135,7 @@ where } } -impl fmt::Display for Pos +impl fmt::Display for Pos2 where T: Num + fmt::Display, { @@ -153,58 +144,82 @@ where } } -impl<'a, T, P: Into>> Add

for Pos +impl Zero for Pos2 +where + T: Num + Zero + Copy, +{ + fn zero() -> Self { + Pos2::splat(T::zero()) + } + + fn is_zero(&self) -> bool { + self.x().is_zero() && self.y().is_zero() + } +} + +impl<'a, T, P: Into>> Add

for Pos2 where T: Num + Copy, { type Output = Self; fn add(self, rhs: P) -> Self::Output { let rhs = rhs.into(); - Pos::new(self.x() + rhs.x(), self.y() + rhs.y()) + Pos2::new(self.x() + rhs.x(), self.y() + rhs.y()) } } -impl>> Sub

for Pos +impl>> Sub

for Pos2 where T: Num + Copy, { - type Output = Pos; + type Output = Pos2; fn sub(self, rhs: P) -> Self::Output { let rhs = rhs.into(); - Pos::new(self.x() - rhs.x(), self.y() - rhs.y()) + Pos2::new(self.x() - rhs.x(), self.y() - rhs.y()) } } -impl Mul for Pos +impl Mul for Pos2 where T: Num + Copy, { type Output = Self; fn mul(self, rhs: T) -> Self::Output { - Pos::new(self.x() * rhs, self.y() * rhs) + Pos2::new(self.x() * rhs, self.y() * rhs) } } -impl Div for Pos +impl Div for Pos2 where T: Num + Copy, { type Output = Self; fn div(self, rhs: T) -> Self::Output { - Pos::new(self.x() / rhs, self.y() / rhs) + Pos2::new(self.x() / rhs, self.y() / rhs) } } -impl Pos +impl Neg for Pos2 +where + T: Signed + Copy, +{ + type Output = Pos2; + + fn neg(self) -> Self::Output { + Self([-self.x(), -self.y()]) + } +} + +impl Pos2 where T: Num + Signed + Copy, { - pub fn taxicab_between(self, other: Pos) -> T { + pub fn taxicab_between(self, other: Pos2) -> T { (self - other).abs() } } -impl Pos +impl Pos2 where T: Num + Copy + CheckedAdd + CheckedSub, { @@ -213,19 +228,19 @@ where Direction::East => self .x() .checked_add(&T::one()) - .map(|x| Pos::new(x, self.y())), + .map(|x| Pos2::new(x, self.y())), Direction::North => self .y() .checked_sub(&T::one()) - .map(|y| Pos::new(self.x(), y)), + .map(|y| Pos2::new(self.x(), y)), Direction::West => self .x() .checked_sub(&T::one()) - .map(|x| Pos::new(x, self.y())), + .map(|x| Pos2::new(x, self.y())), Direction::South => self .y() .checked_add(&T::one()) - .map(|y| Pos::new(self.x(), y)), + .map(|y| Pos2::new(self.x(), y)), } } } diff --git a/src/common/pos3.rs b/src/common/pos3.rs new file mode 100644 index 0000000..d226a65 --- /dev/null +++ b/src/common/pos3.rs @@ -0,0 +1,182 @@ +#![allow(dead_code)] +use num_traits::{Num, PrimInt, Signed, Zero}; +use std::fmt; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Pos3([T; 3]); + +impl Pos3 { + #[inline] + pub const fn new(x: T, y: T, z: T) -> Pos3 { + Pos3([x, y, z]) + } + + #[inline] + pub fn get_x(&self) -> &T { + &self.0[0] + } + + #[inline] + pub fn get_y(&self) -> &T { + &self.0[1] + } + + #[inline] + pub fn get_z(&self) -> &T { + &self.0[2] + } +} + +impl Pos3 { + pub fn is_unit(&self) -> bool { + self.abs() == T::one() + } +} + +impl From<&[T]> for Pos3 { + fn from(value: &[T]) -> Self { + match value.len() { + 0 => Pos3::new(T::default(), T::default(), T::default()), + 1 => Pos3::new(value[0], T::default(), T::default()), + 2 => Pos3::new(value[0], value[1], T::default()), + _ => Pos3::new(value[0], value[1], value[2]), + } + } +} + +impl From<[T; 3]> for Pos3 { + fn from(value: [T; 3]) -> Self { + Pos3(value) + } +} + +impl From<(T, T, T)> for Pos3 { + fn from(value: (T, T, T)) -> Self { + Pos3([value.0, value.1, value.2]) + } +} + +impl Pos3 +where + T: Copy, +{ + #[inline] + pub fn splat(v: T) -> Pos3 { + Pos3::new(v, v, v) + } + + #[inline] + pub fn x(&self) -> T { + self.0[0] + } + + #[inline] + pub fn y(&self) -> T { + self.0[1] + } + + #[inline] + pub fn z(&self) -> T { + self.0[2] + } +} + +impl Zero for Pos3 +where + T: Num + Zero + Copy, +{ + fn zero() -> Self { + Pos3::splat(T::zero()) + } + + fn is_zero(&self) -> bool { + self.x().is_zero() && self.y().is_zero() && self.z().is_zero() + } +} + +impl Pos3 +where + T: Signed, +{ + pub fn abs(self) -> T { + self.get_x().abs() + self.get_y().abs() + self.get_z().abs() + } +} + +impl fmt::Display for Pos3 +where + T: Num + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({}, {}, {})", self.get_x(), self.get_y(), self.get_z()) + } +} + +impl>> Add

for Pos3 +where + T: Num + Copy, +{ + type Output = Self; + fn add(self, rhs: P) -> Self::Output { + let rhs = rhs.into(); + Pos3::new(self.x() + rhs.x(), self.y() + rhs.y(), self.z() + rhs.z()) + } +} + +impl>> Sub

for Pos3 +where + T: Num + Copy, +{ + type Output = Pos3; + fn sub(self, rhs: P) -> Self::Output { + let rhs = rhs.into(); + Pos3::new(self.x() - rhs.x(), self.y() - rhs.y(), self.z() - rhs.z()) + } +} + +impl Mul for Pos3 +where + T: Num + Copy, +{ + type Output = Self; + fn mul(self, rhs: T) -> Self::Output { + Pos3::new(self.x() * rhs, self.y() * rhs, self.z() * rhs) + } +} + +impl Div for Pos3 +where + T: Num + Copy, +{ + type Output = Self; + fn div(self, rhs: T) -> Self::Output { + Pos3::new(self.x() / rhs, self.y() / rhs, self.z() / rhs) + } +} + +impl Neg for Pos3 +where + T: Signed + Copy, +{ + type Output = Pos3; + + fn neg(self) -> Self::Output { + Pos3::new(-self.x(), -self.y(), -self.z()) + } +} + +impl Mul> for Pos3 +where + T: Num + Copy, +{ + type Output = Pos3; + + fn mul(self, rhs: Pos3) -> Self::Output { + Pos3([ + self.y() * rhs.z() - self.z() * rhs.y(), + self.z() * rhs.x() - self.x() * rhs.z(), + self.x() * rhs.y() - self.y() * rhs.x(), + ]) + } +} diff --git a/src/common/unit_vector.rs b/src/common/unit_vector.rs new file mode 100644 index 0000000..a75581d --- /dev/null +++ b/src/common/unit_vector.rs @@ -0,0 +1,57 @@ +#![allow(dead_code)] +use super::pos3::Pos3; +use std::ops::{Mul, Neg}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnitVector(Pos3); + +pub const X: UnitVector = UnitVector(Pos3::new(1, 0, 0)); +pub const NEG_X: UnitVector = UnitVector(Pos3::new(-1, 0, 0)); +pub const Y: UnitVector = UnitVector(Pos3::new(0, 1, 0)); +pub const NEG_Y: UnitVector = UnitVector(Pos3::new(0, -1, 0)); +pub const Z: UnitVector = UnitVector(Pos3::new(0, 0, 1)); +pub const NEG_Z: UnitVector = UnitVector(Pos3::new(0, 0, -1)); + +impl UnitVector { + pub fn new(vector: Pos3) -> Option { + if vector.is_unit() { + Some(UnitVector(vector)) + } else { + None + } + } + + pub fn x(self) -> i8 { + self.0.x() + } + + pub fn y(self) -> i8 { + self.0.y() + } + + pub fn z(self) -> i8 { + self.0.z() + } +} + +impl From for Pos3 { + fn from(value: UnitVector) -> Self { + value.0 + } +} + +impl Mul for UnitVector { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + UnitVector(self.0 * rhs.0) + } +} + +impl Neg for UnitVector { + type Output = Self; + + fn neg(self) -> Self::Output { + UnitVector(-self.0) + } +} diff --git a/src/days/day09/mod.rs b/src/days/day09/mod.rs index 06011d6..ee0e3da 100644 --- a/src/days/day09/mod.rs +++ b/src/days/day09/mod.rs @@ -1,5 +1,5 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{direction::Direction, file::split_lines, pos::Pos2D}; +use crate::common::{direction::Direction, file::split_lines, pos2::Pos2}; use itertools::Itertools; use std::{collections::HashSet, num::ParseIntError}; use thiserror::Error; @@ -63,27 +63,27 @@ fn parse_line(line: &str) -> Result<(Direction, usize), RopeError> { } } -fn get_closer(first: Pos2D, second: Pos2D) -> Option> { +fn get_closer(first: Pos2, second: Pos2) -> Option> { let diff = second - first; if diff.x().abs() <= 1 && diff.y().abs() <= 1 { None } else { - Some(Pos2D::new(diff.x().signum(), diff.y().signum())) + Some(Pos2::new(diff.x().signum(), diff.y().signum())) } } #[derive(Debug)] struct Rope { - head: Pos2D, - knots: Vec>, + head: Pos2, + knots: Vec>, } impl Rope { pub fn create(length: usize) -> Self { assert!(length >= 2); Rope { - head: Pos2D::new(0, 0), - knots: vec![Pos2D::new(0, 0); length - 1], + head: Pos2::new(0, 0), + knots: vec![Pos2::new(0, 0); length - 1], } } diff --git a/src/days/day12/mod.rs b/src/days/day12/mod.rs index e38383e..ed4b673 100644 --- a/src/days/day12/mod.rs +++ b/src/days/day12/mod.rs @@ -1,5 +1,5 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{file::split_lines, pos::Pos2D}; +use crate::common::{file::split_lines, pos2::Pos2}; use std::collections::{BinaryHeap, HashSet}; use thiserror::Error; @@ -43,7 +43,7 @@ enum ValleyError { struct Path { length: usize, height: char, - pos: Pos2D, + pos: Pos2, } impl PartialOrd for Path { @@ -76,7 +76,7 @@ impl Ord for Path { } impl Path { - pub fn new(length: usize, height: char, pos: Pos2D) -> Self { + pub fn new(length: usize, height: char, pos: Pos2) -> Self { Path { length, height, @@ -104,28 +104,28 @@ impl<'a> Neighbors<'a> { } } - fn next_pos(&mut self) -> Option> { + fn next_pos(&mut self) -> Option> { while self.state < 4 { self.state += 1; match self.state { 1 => { if self.path.pos.x() < self.valley.width() - 1 { - return Some(Pos2D::new(self.path.pos.x() + 1, self.path.pos.y())); + return Some(Pos2::new(self.path.pos.x() + 1, self.path.pos.y())); } } 2 => { if self.path.pos.y() > 0 { - return Some(Pos2D::new(self.path.pos.x(), self.path.pos.y() - 1)); + return Some(Pos2::new(self.path.pos.x(), self.path.pos.y() - 1)); } } 3 => { if self.path.pos.x() > 0 { - return Some(Pos2D::new(self.path.pos.x() - 1, self.path.pos.y())); + return Some(Pos2::new(self.path.pos.x() - 1, self.path.pos.y())); } } 4 => { if self.path.pos.y() < self.valley.height() - 1 { - return Some(Pos2D::new(self.path.pos.x(), self.path.pos.y() + 1)); + return Some(Pos2::new(self.path.pos.x(), self.path.pos.y() + 1)); } } _ => {} @@ -151,8 +151,8 @@ impl Iterator for Neighbors<'_> { struct Valley { map: Vec>, - start: Pos2D, - exit: Pos2D, + start: Pos2, + exit: Pos2, width: usize, } @@ -170,11 +170,11 @@ impl TryFrom<&str> for Valley { for (x, height_char) in row.chars().enumerate() { match height_char { 'S' => { - start = Some(Pos2D::new(x, y)); + start = Some(Pos2::new(x, y)); height_row.push('a') } 'E' => { - exit = Some(Pos2D::new(x, y)); + exit = Some(Pos2::new(x, y)); height_row.push('z') } 'a'..='z' => height_row.push(height_char), @@ -209,13 +209,13 @@ impl TryFrom<&str> for Valley { } impl Valley { - fn get_height(&self, pos: Pos2D) -> char { + fn get_height(&self, pos: Pos2) -> char { self.map[pos.y()][pos.x()] } fn do_walk(&self, check: F) -> Result where - F: Fn(Pos2D) -> bool, + F: Fn(Pos2) -> bool, { let mut shortest = HashSet::with_capacity(self.width * self.map.len()); let mut queue = BinaryHeap::new(); @@ -267,8 +267,8 @@ mod test { let lines = read_string(day.get_day_number(), "example01.txt")?; let valley = Valley::try_from(lines.as_str())?; assert_eq!(valley.width, 8); - assert_eq!(valley.start, Pos2D::new(0, 0)); - assert_eq!(valley.exit, Pos2D::new(5, 2)); + assert_eq!(valley.start, Pos2::new(0, 0)); + assert_eq!(valley.exit, Pos2::new(5, 2)); Ok(()) } diff --git a/src/days/day14/mod.rs b/src/days/day14/mod.rs index 3b42429..5f00a63 100644 --- a/src/days/day14/mod.rs +++ b/src/days/day14/mod.rs @@ -1,5 +1,5 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{file::split_lines, pos::Pos2D}; +use crate::common::{file::split_lines, pos2::Pos2}; use itertools::Itertools; use std::{collections::HashSet, num::ParseIntError}; use thiserror::Error; @@ -54,7 +54,7 @@ impl TryFrom<&str> for Cave { fn try_from(lines: &str) -> Result { let lines = split_lines(lines); - let mut cave: HashSet> = HashSet::new(); + let mut cave: HashSet> = HashSet::new(); for line in lines { cave.extend(Cave::parse_one(line)?.iter()); } @@ -90,7 +90,7 @@ impl TryFrom<&str> for Cave { } impl Cave { - fn get(&self, map: &[Vec], pos: Pos2D) -> bool { + fn get(&self, map: &[Vec], pos: Pos2) -> bool { if pos.y() >= self.height || pos.x() < self.min_x || pos.x() > self.max_x { false } else { @@ -98,32 +98,28 @@ impl Cave { } } - fn set(&self, map: &mut [Vec], pos: Pos2D) { + fn set(&self, map: &mut [Vec], pos: Pos2) { map[pos.y() as usize][(pos.x() - self.min_x) as usize] = true } - fn walk(from: Pos2D, to: Pos2D) -> Result>, CaveError> { + fn walk(from: Pos2, to: Pos2) -> Result>, CaveError> { if from == to { return Ok(vec![from]); } if from.x() == to.x() { if from.y() < to.y() { - Ok((from.y()..to.y()) - .map(|y| Pos2D::new(from.x(), y)) - .collect()) + Ok((from.y()..to.y()).map(|y| Pos2::new(from.x(), y)).collect()) } else { Ok(((to.y() + 1)..=from.y()) - .map(|y| Pos2D::new(from.x(), y)) + .map(|y| Pos2::new(from.x(), y)) .collect()) } } else if from.y() == to.y() { if from.x() < to.x() { - Ok((from.x()..to.x()) - .map(|x| Pos2D::new(x, from.y())) - .collect()) + Ok((from.x()..to.x()).map(|x| Pos2::new(x, from.y())).collect()) } else { Ok(((to.x() + 1)..=from.x()) - .map(|x| Pos2D::new(x, from.y())) + .map(|x| Pos2::new(x, from.y())) .collect()) } } else { @@ -131,14 +127,14 @@ impl Cave { } } - fn parse_one(line: &str) -> Result>, CaveError> { + fn parse_one(line: &str) -> Result>, CaveError> { let corners = line .split("->") .map(|coord| match coord.split(',').collect::>()[..] { [first, second] => { let x: i32 = first.trim().parse()?; let y = second.trim().parse()?; - Ok(Pos2D::new(x, y)) + Ok(Pos2::new(x, y)) } _ => Err(CaveError::NotAValidPos(coord.to_owned())), }) @@ -175,8 +171,8 @@ impl Cave { drops } - fn drop_one(&self, map: &[Vec]) -> Option> { - let mut drop = Pos2D::new(500, 0); + fn drop_one(&self, map: &[Vec]) -> Option> { + let mut drop = Pos2::new(500, 0); loop { let next_y = drop.y() + 1; if next_y > self.height { @@ -184,7 +180,7 @@ impl Cave { } let mut stuck = true; for dx in [0, -1, 1] { - let next = Pos2D::new(drop.x() + dx, next_y); + let next = Pos2::new(drop.x() + dx, next_y); if !self.get(map, next) { drop = next; stuck = false; @@ -256,7 +252,7 @@ mod test { #[test] fn test_parse() -> Result<()> { let input = "498,4 -> 498,6 -> 496,6"; - let expected = hashset! {Pos2D::new(498, 4), Pos2D::new(498, 5), Pos2D::new(498, 6), Pos2D::new(497, 6), Pos2D::new(496, 6)}; + let expected = hashset! {Pos2::new(498, 4), Pos2::new(498, 5), Pos2::new(498, 6), Pos2::new(497, 6), Pos2::new(496, 6)}; let result = Cave::parse_one(input)?; assert_eq!(result, expected); diff --git a/src/days/day15/mod.rs b/src/days/day15/mod.rs index 0875d94..43b786c 100644 --- a/src/days/day15/mod.rs +++ b/src/days/day15/mod.rs @@ -1,7 +1,7 @@ use super::template::{DayTrait, ResultType}; use crate::common::{ parser::{eol_terminated, extract_result, ignore, trim0}, - pos::Pos2D, + pos2::Pos2, }; use itertools::Itertools; use nom::{ @@ -58,7 +58,7 @@ impl DayTrait for Day { } impl Day { - fn parse_all(lines: &str) -> Result<(HashSet, HashSet>), SensorError> { + fn parse_all(lines: &str) -> Result<(HashSet, HashSet>), SensorError> { let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?; let mut sensors = HashSet::new(); @@ -70,11 +70,7 @@ impl Day { Ok((sensors, beacons)) } - fn count_coverage_at( - sensors: &HashSet, - beacons: &HashSet>, - row: i64, - ) -> i64 { + fn count_coverage_at(sensors: &HashSet, beacons: &HashSet>, row: i64) -> i64 { let ranges = sensors .iter() .filter_map(|sensor| sensor.range_at(row)) @@ -121,7 +117,7 @@ impl From>> for SensorError { #[derive(Debug)] struct Line { - start: Pos2D, + start: Pos2, is_up: bool, steps: i64, } @@ -143,16 +139,16 @@ impl Line { if one.pos.y() < two.pos.y() { is_up = true; if one.pos.y() + one.radius <= two.pos.y() { - start = Pos2D::new(one.pos.x(), one.pos.y() + one.radius + 1); + start = Pos2::new(one.pos.x(), one.pos.y() + one.radius + 1); } else { - start = Pos2D::new(two.pos.x() - two.radius - 1, two.pos.y()); + start = Pos2::new(two.pos.x() - two.radius - 1, two.pos.y()); } } else { is_up = false; if one.pos.y() - one.radius >= two.pos.y() { - start = Pos2D::new(one.pos.x(), one.pos.y() - one.radius - 1); + start = Pos2::new(one.pos.x(), one.pos.y() - one.radius - 1); } else { - start = Pos2D::new(two.pos.x() - two.radius - 1, two.pos.y()); + start = Pos2::new(two.pos.x() - two.radius - 1, two.pos.y()); } } let steps = two.pos.x().min(one.pos.x() + one.radius) - start.x(); @@ -164,7 +160,7 @@ impl Line { }) } - fn cross(&self, other: &Line) -> Option> { + fn cross(&self, other: &Line) -> Option> { if self.is_up == other.is_up { return None; } @@ -185,7 +181,7 @@ impl Line { return None; } - let pos = top_down.start + Pos2D::splat(r); + let pos = top_down.start + Pos2::splat(r); Some(pos) } } @@ -231,7 +227,7 @@ impl Range { #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] struct Sensor { - pos: Pos2D, + pos: Pos2, radius: i64, } @@ -243,16 +239,16 @@ impl Sensor { } } - fn parse_pos(input: &str) -> IResult<&str, Pos2D> { + fn parse_pos(input: &str) -> IResult<&str, Pos2> { let (input, (x, y)) = separated_pair( Sensor::component("x"), trim0(char(',')), Sensor::component("y"), )(input)?; - Ok((input, Pos2D::new(x, y))) + Ok((input, Pos2::new(x, y))) } - pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos2D)> { + pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos2)> { let input = ignore(tag("Sensor at"))(input)?; let (input, pos) = trim0(Sensor::parse_pos)(input)?; let input = ignore(tag(": closest beacon is at"))(input)?; @@ -276,7 +272,7 @@ impl Sensor { distance - self.radius - other.radius } - pub fn contains(&self, pos: Pos2D) -> bool { + pub fn contains(&self, pos: Pos2) -> bool { self.pos.taxicab_between(pos) <= self.radius } } @@ -291,10 +287,10 @@ mod test { let input = "Sensor at x=2, y=18: closest beacon is at x=-2, y=15"; let expected = ( Sensor { - pos: Pos2D::new(2, 18), + pos: Pos2::new(2, 18), radius: 7, }, - Pos2D::new(-2, 15), + Pos2::new(-2, 15), ); let result = extract_result(Sensor::parse)(input)?; assert_eq!(result, expected); @@ -305,7 +301,7 @@ mod test { #[test] fn test_width() { let sensor = Sensor { - pos: Pos2D::new(8, 7), + pos: Pos2::new(8, 7), radius: 9, }; assert_eq!(sensor.range_at(17), None); diff --git a/src/days/day22/mod.rs b/src/days/day22/mod.rs index cb584be..32b8e68 100644 --- a/src/days/day22/mod.rs +++ b/src/days/day22/mod.rs @@ -2,7 +2,8 @@ use super::template::{DayTrait, ResultType}; use crate::common::{ direction::Direction, parser::{eol_terminated, extract_result, ignore, usize}, - pos::Pos2D, + pos2::Pos2, + unit_vector::{self, UnitVector}, }; use nom::{ branch::alt, @@ -12,7 +13,8 @@ use nom::{ multi::many1, Err, IResult, Parser, }; -use std::str::FromStr; +use num_traits::Zero; +use std::{collections::HashMap, str::FromStr}; use thiserror::Error; const DAY_NUMBER: usize = 22; @@ -26,16 +28,14 @@ impl DayTrait for Day { fn part1(&self, lines: &str) -> anyhow::Result { let (world_map, instructions) = Day::parse(lines)?; - let world = WrappingWorld::new(world_map); - let walker = Walker::walk_all(&world, instructions)?; - Ok(ResultType::Integer(walker.value())) + let result = WrappingWalker::walk_all(world_map, instructions)?; + Ok(ResultType::Integer(result.value())) } fn part2(&self, lines: &str) -> anyhow::Result { let (world_map, instructions) = Day::parse(lines)?; - let world = CubeWorld::new(world_map); - let walker = Walker::walk_all(&world, instructions)?; - Ok(ResultType::Integer(walker.value())) + let result = CubeWalker::walk_all(world_map, instructions)?; + Ok(ResultType::Integer(result.value())) } } @@ -52,8 +52,12 @@ impl Day { enum WorldError { #[error("Not a valid description: {0}")] ParsingError(String), + #[error("No Starting Point found")] NoStartingPoint, + + #[error("Map is not a valid cube")] + NotAValidCube, } impl From>> for WorldError { @@ -79,6 +83,14 @@ impl WorldMap { Self { tiles } } + pub fn height(&self) -> usize { + self.tiles.len() + } + + pub fn width(&self) -> Option { + self.tiles.iter().map(|row| row.len()).max() + } + fn parse(input: &str) -> IResult<&str, WorldMap> { let tile = alt(( value(Some(false), char('#')), @@ -90,32 +102,33 @@ impl WorldMap { lines.parse(input) } - fn get_first(&self, pos: Pos2D, facing: Direction) -> Option> { - match facing { - Direction::East => self.tiles.get(pos.y()).and_then(|row| { + fn get_first(&self, map_pos: Location) -> Option { + match map_pos.facing { + Direction::East => self.tiles.get(map_pos.pos.y()).and_then(|row| { row.iter() .position(|t| t.is_some()) - .map(|x| Pos2D::new(x, pos.y())) + .map(|x| Pos2::new(x, map_pos.pos.y())) }), - Direction::West => self.tiles.get(pos.y()).and_then(|row| { + Direction::West => self.tiles.get(map_pos.pos.y()).and_then(|row| { row.iter() .rposition(|t| t.is_some()) - .map(|x| Pos2D::new(x, pos.y())) + .map(|x| Pos2::new(x, map_pos.pos.y())) }), Direction::South => self .tiles .iter() - .position(|row| pos.x() < row.len() && row[pos.x()].is_some()) - .map(|y| Pos2D::new(pos.x(), y)), + .position(|row| map_pos.pos.x() < row.len() && row[map_pos.pos.x()].is_some()) + .map(|y| Pos2::new(map_pos.pos.x(), y)), Direction::North => self .tiles .iter() - .rposition(|row| pos.x() < row.len() && row[pos.x()].is_some()) - .map(|y| Pos2D::new(pos.x(), y)), + .rposition(|row| map_pos.pos.x() < row.len() && row[map_pos.pos.x()].is_some()) + .map(|y| Pos2::new(map_pos.pos.x(), y)), } + .map(|pos| Location::new(pos, map_pos.facing)) } - fn get_tile(&self, pos: Pos2D) -> Option { + fn get_tile(&self, pos: Pos2) -> Option { self.tiles .get(pos.y()) .and_then(|row| row.get(pos.x())) @@ -124,82 +137,6 @@ impl WorldMap { } } -trait World { - fn get_map(&self) -> &WorldMap; - - fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)>; - - fn get_start(&self, pos: Pos2D, facing: Direction) -> Option> { - self.get_map().get_first(pos, facing) - } - - fn step(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { - let Some(pos) = pos.check_add(facing) else { - return self.wrap(pos, facing); - }; - - match self.get_map().get_tile(pos) { - Some(true) => Some((pos, facing)), - Some(false) => None, - None => self.wrap(pos, facing), - } - } -} - -struct WrappingWorld { - map: WorldMap, -} -impl WrappingWorld { - fn new(world_map: WorldMap) -> Self { - WrappingWorld { map: world_map } - } -} - -impl World for WrappingWorld { - fn get_map(&self) -> &WorldMap { - &self.map - } - - fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { - if let Some(pos) = self.map.get_first(pos, facing) { - match self.map.get_tile(pos) { - Some(true) => Some((pos, facing)), - Some(false) => None, - None => unreachable!(), - } - } else { - unreachable!(); - } - } -} - -struct CubeWorld { - map: WorldMap, -} -impl CubeWorld { - fn new(world_map: WorldMap) -> Self { - CubeWorld { map: world_map } - } -} - -impl World for CubeWorld { - fn get_map(&self) -> &WorldMap { - &self.map - } - - fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { - if let Some(pos) = self.map.get_first(pos, facing) { - match self.map.get_tile(pos) { - Some(true) => Some((pos, facing)), - Some(false) => None, - None => unreachable!(), - } - } else { - unreachable!(); - } - } -} - #[derive(Debug, Clone)] enum Instruction { Walk(usize), @@ -218,29 +155,34 @@ impl Instruction { } } -struct Walker { - pos: Pos2D, +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct Location { + pos: Pos2, facing: Direction, } -impl Walker { - fn new(world: &W) -> Result { - let facing = Direction::East; - if let Some(pos) = world.get_start(Pos2D::new(0, 0), facing) { - Ok(Walker { pos, facing }) - } else { - Err(WorldError::NoStartingPoint) - } +impl Location { + pub fn new(pos: Pos2, facing: Direction) -> Self { + Location { pos, facing } } - fn value(&self) -> i64 { - (self.pos.y() + 1) as i64 * 1000 - + (self.pos.x() + 1) as i64 * 4 - + Walker::face_value(self.facing) + #[inline] + fn pos(&self) -> Pos2 { + self.pos } - fn face_value(facing: Direction) -> i64 { - match facing { + fn one_step(&self) -> Option { + self.pos + .check_add(self.facing) + .map(|pos| Location::new(pos, self.facing)) + } + + pub fn value(&self) -> i64 { + (self.pos.y() + 1) as i64 * 1000 + (self.pos.x() + 1) as i64 * 4 + self.face_value() + } + + fn face_value(&self) -> i64 { + match self.facing { Direction::East => 0, Direction::North => 3, Direction::West => 2, @@ -248,33 +190,333 @@ impl Walker { } } - fn act(mut self, world: &W, instruction: Instruction) -> Self { + fn turn_left(&mut self) { + self.facing = self.facing.turn_left() + } + + fn turn_right(&mut self) { + self.facing = self.facing.turn_right() + } +} + +trait Walker: Sized { + fn turn_right(&mut self); + fn turn_left(&mut self); + fn set_location(&mut self, pos: Location); + + fn new(world_map: WorldMap) -> Result; + fn act(&mut self, instruction: Instruction) { match instruction { - Instruction::Right => self.facing = self.facing.turn_right(), - Instruction::Left => self.facing = self.facing.turn_left(), + Instruction::Right => self.turn_right(), + Instruction::Left => self.turn_left(), Instruction::Walk(steps) => { for _ in 0..steps { - let Some((next_pos, next_facing)) = world.step(self.pos, self.facing) else { + let Some(next_pos) = self.step() else { break; }; - self.pos = next_pos; - self.facing = next_facing; + self.set_location(next_pos); } } } - self } - pub fn walk_all( - world: &W, - instructions: Vec, - ) -> Result { - let mut walker = Walker::new(world)?; + fn get_map(&self) -> &WorldMap; + fn location(&self) -> Location; + + fn wrap(&self) -> Option; + fn step(&self) -> Option { + if let Some(next_pos) = self.location().one_step() { + match self.get_map().get_tile(next_pos.pos()) { + Some(true) => return Some(next_pos), + Some(false) => return None, + None => {} + } + }; + + if let Some(next_pos) = self.wrap() { + match self.get_map().get_tile(next_pos.pos()) { + Some(true) => return Some(next_pos), + Some(false) => return None, + None => {} + } + }; + + unreachable!() + } + + fn walk_all(world_map: WorldMap, instructions: Vec) -> Result + where + Self: Sized, + { + let mut walker = Self::new(world_map)?; for instruction in instructions { - walker = walker.act(world, instruction); + walker.act(instruction); } - Ok(walker) + Ok(walker.location()) + } +} + +struct WrappingWalker { + world: WorldMap, + location: Location, +} + +impl Walker for WrappingWalker { + fn new(world: WorldMap) -> Result { + if let Some(location) = world.get_first(Location::new(Pos2::new(0, 0), Direction::East)) { + Ok(WrappingWalker { world, location }) + } else { + Err(WorldError::NoStartingPoint) + } + } + + fn wrap(&self) -> Option { + self.world.get_first(self.location) + } + + fn turn_right(&mut self) { + self.location.turn_right(); + } + + fn turn_left(&mut self) { + self.location.turn_left(); + } + + fn set_location(&mut self, pos: Location) { + self.location = pos; + } + + fn location(&self) -> Location { + self.location + } + + fn get_map(&self) -> &WorldMap { + &self.world + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct CubePos { + side: UnitVector, + facing: UnitVector, +} + +impl CubePos { + fn new(side: UnitVector, facing: UnitVector) -> Self { + CubePos { side, facing } + } + + fn facing_east(&self, facing: Direction) -> UnitVector { + match facing { + Direction::East => self.facing, + Direction::North => -(self.side * self.facing), + Direction::West => -self.facing, + Direction::South => self.side * self.facing, + } + } + + fn forward(&self) -> CubePos { + CubePos { + side: self.facing, + facing: -self.side, + } + } + + #[allow(dead_code)] + fn turn_right(&mut self) { + self.facing = -(self.side * self.facing); + } + + fn turn_left(&mut self) { + self.facing = self.side * self.facing; + } +} + +struct CubeWalker { + world: WorldMap, + location: Location, + translation: HashMap, UnitVector)>, + side_length: usize, +} + +impl CubeWalker { + fn get_side_length(world: &WorldMap) -> Result { + let width = world.width().ok_or_else(|| WorldError::NotAValidCube)?; + let height = world.height(); + if width % 3 == 0 && height % 4 == 0 && width / 3 == height / 4 { + Ok(height / 4) + } else if width % 4 == 0 && height % 3 == 0 && width / 4 == height / 3 { + Ok(width / 4) + } else if width % 5 == 0 && height % 2 == 0 && width / 5 == height / 2 { + Ok(height / 2) + } else if width % 2 == 0 && height % 5 == 0 && width / 2 == height / 5 { + Ok(width / 2) + } else { + Err(WorldError::NotAValidCube) + } + } + + fn analyze_cube( + world_map: &WorldMap, + mut normal_map_pos: Location, + mut cube_pos: CubePos, + mut sides: HashMap, UnitVector)>, + side_length: usize, + ) -> HashMap, UnitVector)> { + let facing_east = cube_pos.facing_east(normal_map_pos.facing); + + sides.insert(cube_pos.side, (normal_map_pos.pos, facing_east)); + if sides.len() == 6 { + return sides; + } + + for _ in 0..4 { + if let Some(next_normal) = normal_map_pos.one_step() { + if world_map + .get_tile(next_normal.pos() * side_length) + .is_some() + { + let next_cube = cube_pos.forward(); + if !sides.contains_key(&next_cube.side) { + sides = CubeWalker::analyze_cube( + world_map, + next_normal, + next_cube, + sides, + side_length, + ); + } + } + } + normal_map_pos.turn_left(); + cube_pos.turn_left(); + } + + sides + } + + fn get_cube_position(&self, location: Location) -> Option<(CubePos, usize)> { + let normal = location.pos / self.side_length; + for (side, (pos, right)) in self.translation.iter() { + if pos == &normal { + match location.facing { + Direction::East => { + return Some(( + CubePos::new(*side, *right), + location.pos.y() - pos.y() * self.side_length, + )) + } + Direction::North => { + return Some(( + CubePos::new(*side, -(*right * *side)), + location.pos.x() - pos.x() * self.side_length, + )) + } + Direction::West => { + return Some(( + CubePos::new(*side, -*right), + (pos.y() + 1) * self.side_length - location.pos.y() - 1, + )) + } + Direction::South => { + return Some(( + CubePos::new(*side, *right * *side), + (pos.x() + 1) * self.side_length - location.pos.x() - 1, + )) + } + } + } + } + None + } +} + +impl Walker for CubeWalker { + fn new(world: WorldMap) -> Result { + if let Some(location) = world.get_first(Location::new(Pos2::zero(), Direction::East)) { + let side_length = CubeWalker::get_side_length(&world)?; + let normal_map_pos = Location::new(location.pos / side_length, location.facing); + let translation = CubeWalker::analyze_cube( + &world, + normal_map_pos, + CubePos::new(unit_vector::X, unit_vector::Y), + HashMap::new(), + side_length, + ); + if translation.len() != 6 { + return Err(WorldError::NotAValidCube); + } + Ok(CubeWalker { + world, + location, + translation, + side_length, + }) + } else { + Err(WorldError::NoStartingPoint) + } + } + + fn wrap(&self) -> Option { + let (old_pos, delta) = self.get_cube_position(self.location)?; + let new_pos = old_pos.forward(); + let (position, normal_right) = self.translation[&new_pos.side]; + let normal = CubePos::new(new_pos.side, normal_right); + + if new_pos.facing == normal.facing { + Some(Location::new( + Pos2::new( + position.x() * self.side_length, + position.y() * self.side_length + delta, + ), + Direction::East, + )) + } else if new_pos.facing == -normal.facing { + Some(Location::new( + Pos2::new( + (position.x() + 1) * self.side_length - 1, + (position.y() + 1) * self.side_length - 1 - delta, + ), + Direction::West, + )) + } else if new_pos.facing == (normal.side * normal.facing) { + Some(Location::new( + Pos2::new( + position.x() * self.side_length + delta, + (position.y() + 1) * self.side_length - 1, + ), + Direction::North, + )) + } else { + Some(Location::new( + Pos2::new( + (position.x() + 1) * self.side_length - 1 - delta, + position.y() * self.side_length, + ), + Direction::South, + )) + } + } + + fn turn_right(&mut self) { + self.location.turn_right(); + } + + fn turn_left(&mut self) { + self.location.turn_left(); + } + + fn set_location(&mut self, location: Location) { + self.location = location; + } + + fn location(&self) -> Location { + self.location + } + + fn get_map(&self) -> &WorldMap { + &self.world } } @@ -299,7 +541,7 @@ mod test { fn test_part2() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; - let expected = ResultType::Nothing; + let expected = ResultType::Integer(5031); let result = day.part2(&lines)?; assert_eq!(result, expected); @@ -324,18 +566,25 @@ mod test { let lines = read_string(day.get_day_number(), "example01.txt")?; let (world_map, _) = Day::parse(&lines)?; - let world = WrappingWorld::new(world_map); + let mut walker = WrappingWalker::new(world_map)?; + assert_eq!( + walker.location, + Location::new(Pos2::new(8, 0), Direction::East) + ); - let mut walker = Walker::new(&world)?; - assert_eq!(walker.pos, Pos2D::new(8, 0)); + walker.act(Instruction::Walk(10)); + assert_eq!( + walker.location, + Location::new(Pos2::new(10, 0), Direction::East) + ); - walker = walker.act(&world, Instruction::Walk(10)); - assert_eq!(walker.pos, Pos2D::new(10, 0)); + walker.act(Instruction::Left); - walker = walker.act(&world, Instruction::Left); - - walker = walker.act(&world, Instruction::Walk(2)); - assert_eq!(walker.pos, Pos2D::new(10, 10)); + walker.act(Instruction::Walk(2)); + assert_eq!( + walker.location, + Location::new(Pos2::new(10, 10), Direction::North) + ); Ok(()) } @@ -345,12 +594,137 @@ mod test { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let (world_map, instructions) = Day::parse(&lines)?; - let world = WrappingWorld::new(world_map); - let walker = Walker::walk_all(&world, instructions)?; - assert_eq!(walker.pos, Pos2D::new(7, 5)); - assert_eq!(walker.facing, Direction::East); - assert_eq!(walker.value(), 6032); + let result = WrappingWalker::walk_all(world_map, instructions)?; + assert_eq!(result.pos, Pos2::new(7, 5)); + assert_eq!(result.facing, Direction::East); + assert_eq!(result.value(), 6032); + + Ok(()) + } + + #[test] + fn parse_cube() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let (world_map, _) = Day::parse(&lines)?; + + let walker = CubeWalker::new(world_map)?; + + assert_eq!(walker.side_length, 4); + assert_eq!( + walker.translation.get(&unit_vector::X), + Some(&(Pos2::new(2, 0), unit_vector::Y)) + ); + assert_eq!( + walker.translation.get(&unit_vector::NEG_X), + Some(&(Pos2::new(2, 2), unit_vector::Y)) + ); + assert_eq!( + walker.translation.get(&unit_vector::Y), + Some(&(Pos2::new(3, 2), unit_vector::X)) + ); + assert_eq!( + walker.translation.get(&unit_vector::NEG_Y), + Some(&(Pos2::new(1, 1), unit_vector::NEG_Z)) + ); + assert_eq!( + walker.translation.get(&unit_vector::Z), + Some(&(Pos2::new(0, 1), unit_vector::NEG_Y)) + ); + assert_eq!( + walker.translation.get(&unit_vector::NEG_Z), + Some(&(Pos2::new(2, 1), unit_vector::Y)) + ); + + Ok(()) + } + + #[test] + fn cube_position() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let (world_map, _) = Day::parse(&lines)?; + + let walker = CubeWalker::new(world_map)?; + + let pos = Location::new(Pos2::new(11, 5), Direction::East); + let expected = (CubePos::new(unit_vector::NEG_Z, unit_vector::Y), 1); + assert_eq!(walker.get_cube_position(pos), Some(expected)); + + let pos = Location::new(Pos2::new(10, 11), Direction::South); + let expected = (CubePos::new(unit_vector::NEG_X, unit_vector::Z), 1); + assert_eq!(walker.get_cube_position(pos), Some(expected)); + + Ok(()) + } + + #[test] + fn cube_wrapping() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let (world_map, _) = Day::parse(&lines)?; + + let mut walker = CubeWalker::new(world_map)?; + + walker.location = Location::new(Pos2::new(11, 5), Direction::East); + let expected = Location::new(Pos2::new(14, 8), Direction::South); + assert_eq!(walker.wrap(), Some(expected)); + + walker.location = Location::new(Pos2::new(10, 11), Direction::South); + let expected = Location::new(Pos2::new(1, 7), Direction::North); + assert_eq!(walker.wrap(), Some(expected)); + + walker.location = Location::new(Pos2::new(14, 8), Direction::North); + let expected = Location::new(Pos2::new(11, 5), Direction::West); + assert_eq!(walker.wrap(), Some(expected)); + + walker.location = Location::new(Pos2::new(1, 7), Direction::South); + let expected = Location::new(Pos2::new(10, 11), Direction::North); + assert_eq!(walker.wrap(), Some(expected)); + + Ok(()) + } + + #[test] + fn walk_cube() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let (world_map, _) = Day::parse(&lines)?; + + let mut walker = CubeWalker::new(world_map)?; + assert_eq!( + walker.location, + Location::new(Pos2::new(8, 0), Direction::East) + ); + + walker.act(Instruction::Walk(10)); + assert_eq!( + walker.location, + Location::new(Pos2::new(10, 0), Direction::East) + ); + + walker.act(Instruction::Left); + + walker.act(Instruction::Walk(2)); + assert_eq!( + walker.location, + Location::new(Pos2::new(1, 5), Direction::South) + ); + + Ok(()) + } + + #[test] + fn walk_all_cube() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let (world_map, instructions) = Day::parse(&lines)?; + + let result = CubeWalker::walk_all(world_map, instructions)?; + assert_eq!(result.pos, Pos2::new(6, 4)); + assert_eq!(result.facing, Direction::North); + assert_eq!(result.value(), 5031); Ok(()) }