use anyhow::Result; use std::env::args; use std::fs::File; use std::io::{BufRead, BufReader}; use std::ops::{Add, AddAssign, Mul}; use std::path::Path; use std::convert::TryFrom; use std::error::Error; use std::fmt; #[derive(Clone, Copy, Debug, PartialEq)] enum FerryDirection { North, East, South, West, } #[derive(Clone, Copy, Debug)] enum FerryAction { North(isize), East(isize), South(isize), West(isize), Left(isize), Right(isize), Forward(isize), } #[derive(Clone, Copy)] struct FerryPos { x: isize, y: isize, direction: FerryDirection, } impl FerryPos { fn get_manhattan(&self) -> isize { self.x.abs() + self.y.abs() } fn move_forward(&mut self, steps: isize) { match self.direction { FerryDirection::North => *self += FerryAction::North(steps), FerryDirection::East => *self += FerryAction::East(steps), FerryDirection::South => *self += FerryAction::South(steps), FerryDirection::West => *self += FerryAction::West(steps), } } fn turn(&mut self, degrees: isize) { let mut steps = degrees / 90; while steps < 0 { steps += 4; } for _step in 0..steps { self.direction = match self.direction { FerryDirection::North => FerryDirection::East, FerryDirection::East => FerryDirection::South, FerryDirection::South => FerryDirection::West, FerryDirection::West => FerryDirection::North, }; } } fn rotate(&mut self, degrees: isize) { let mut steps = degrees / 90; while steps < 0 { steps += 4; } for step in 0..steps { let tmp = self.x; match step % 4 { 0 => { self.x = -self.y; self.y = tmp; }, 1 => { self.x = -self.y; self.y = tmp; }, 2 => { self.x = -self.y; self.y = tmp; }, 3 => { self.x = -self.y; self.y = tmp; }, _ => panic!("Invalid step count"), }; } } } impl fmt::Debug for FerryPos { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FerryPos") .field("direction", &self.direction) .field("x", &self.x) .field("y", &self.y) .finish() } } impl AddAssign for FerryPos { fn add_assign(&mut self, other: FerryAction) { match other { FerryAction::North(steps) => *self = Self { direction: self.direction, x: self.x, y: self.y - steps, }, FerryAction::East(steps) => *self = Self { direction: self.direction, x: self.x + steps, y: self.y, }, FerryAction::South(steps) => *self = Self { direction: self.direction, x: self.x, y: self.y + steps, }, FerryAction::West(steps) => *self = Self { direction: self.direction, x: self.x - steps, y: self.y, }, FerryAction::Forward(steps) => self.move_forward(steps), FerryAction::Left(degrees) => self.turn(-degrees), FerryAction::Right(degrees) => self.turn(degrees), }; } } impl Add for FerryPos { type Output = FerryPos; fn add(self, other: FerryPos) -> Self{ FerryPos { direction: self.direction, x: self.x + other.x, y: self.y + other.y, } } } impl AddAssign for FerryPos { fn add_assign(&mut self, other: Self) { *self = *self + other; } } impl Mul for FerryPos { type Output = Self; fn mul(self, rhs: isize) -> Self { let mut res = FerryPos { direction: self.direction, x: 0, y: 0, }; for _i in 0..rhs { res += self; } res } } #[derive(Debug)] enum FerryActionError { Error, } impl fmt::Display for FerryActionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FerryActionError") } } impl Error for FerryActionError { } impl TryFrom for FerryAction { type Error = FerryActionError; fn try_from(string: String) -> Result { FerryAction::try_from(string.as_str()) } } impl TryFrom<&str> for FerryAction { type Error = FerryActionError; fn try_from(string: &str) -> Result { let action = string.get(0..1).ok_or(FerryActionError::Error)?; let distance = string.get(1..).ok_or(FerryActionError::Error)?; let num = distance.parse().or(Err(FerryActionError::Error))?; match action { "N" => Ok(FerryAction::North(num)), "E" => Ok(FerryAction::East(num)), "S" => Ok(FerryAction::South(num)), "W" => Ok(FerryAction::West(num)), "R" => Ok(FerryAction::Right(num)), "L" => Ok(FerryAction::Left(num)), "F" => Ok(FerryAction::Forward(num)), _ => Err(FerryActionError::Error), } } } fn read_input>(filename: T) -> Result> { let f = File::open(filename)?; let reader = BufReader::new(f); reader.lines() .filter(|v| &v.as_ref().unwrap_or(&String::from("")). get(0..1) != &Some("#")) // Allow comment lines starting with #-character .map(|v| Ok(FerryAction::try_from(v?)?)) .collect() } fn part1(movements: &[FerryAction]) -> isize { let mut ferry_pos = FerryPos { x: 0, y: 0, direction: FerryDirection::East, }; for movement in movements { ferry_pos += *movement; } ferry_pos.get_manhattan() } fn part2(actions: &[FerryAction]) -> isize { let mut ferry_pos = FerryPos { x: 0, y: 0, direction: FerryDirection::East, }; let mut waypoint_pos = FerryPos { x: 10, y: -1, direction: FerryDirection::East, }; for action in actions { match action { FerryAction::Forward(steps) => ferry_pos += waypoint_pos.clone() * *steps, FerryAction::Left(degrees) => waypoint_pos.rotate(-*degrees), FerryAction::Right(degrees) => waypoint_pos.rotate(*degrees), FerryAction::East(steps) => waypoint_pos += FerryPos { direction: FerryDirection::East, x: *steps, y: 0, }, FerryAction::North(steps) => waypoint_pos += FerryPos { direction: FerryDirection::East, x: 0, y: -*steps, }, FerryAction::West(steps) => waypoint_pos += FerryPos { direction: FerryDirection::East, x: -*steps, y: 0, }, FerryAction::South(steps) => waypoint_pos += FerryPos { direction: FerryDirection::East, x: 0, y: *steps, }, } } ferry_pos.get_manhattan() } fn main() { let (do_part_1, do_part_2) = aoc::do_parts(); let filename = match args().nth(1) { Some(f) => f, None => { eprintln!("Missing input filename"); std::process::exit(1); }, }; match read_input(filename) { Ok(input) => { if do_part_1 { let solution = part1(&input); println!("Part1, {}", solution); } if do_part_2 { let solution = part2(&input); println!("Part2, {}", solution); } }, Err(err) => eprintln!("Could not read input: {}", err), } }