use std::fs; fn main() -> Result<(), Box> { let input = fs::read_to_string("input")?; let actions = parse_actions(&input)?; // Part 1 let location = simulate_ship_movement(&actions); println!("{}", location.0.abs() + location.1.abs()); // Part 2 let location = simulate_waypoint_movement(&actions); println!("{}", location.0.abs() + location.1.abs()); Ok(()) } #[derive(Copy, Clone, Debug)] enum Action { North(u32), South(u32), East(u32), West(u32), Left(u32), Right(u32), Forward(u32), } impl Action { fn new(input: &str) -> Result { if input.len() < 2 { Err(format!("Invalud input line: {}", input))?; } let (action, value) = input.split_at(1); let value = value .parse() .map_err(|e| format!("Could not parse value: {}", e))?; Ok(match action { "N" => Self::North(value), "S" => Self::South(value), "E" => Self::East(value), "W" => Self::West(value), "L" => Self::Left(value), "R" => Self::Right(value), "F" => Self::Forward(value), _ => Err(format!("Could not parse action: {}", action))?, }) } fn opposite_direction(self) -> Self { match self { Action::North(x) => Action::South(x), Action::South(x) => Action::North(x), Action::East(x) => Action::West(x), Action::West(x) => Action::East(x), _ => panic!("Invalid direction"), } } fn right_direction(self) -> Self { match self { Action::North(x) => Action::East(x), Action::South(x) => Action::West(x), Action::East(x) => Action::South(x), Action::West(x) => Action::North(x), _ => panic!("Invalid direction"), } } fn left_direction(self) -> Self { match self { Action::North(x) => Action::West(x), Action::South(x) => Action::East(x), Action::East(x) => Action::North(x), Action::West(x) => Action::South(x), _ => panic!("Invalid direction"), } } } fn parse_actions(input: &str) -> Result, String> { input.lines().map(Action::new).collect() } fn simulate_ship_movement(actions: &Vec) -> (i64, i64) { let mut coords: (i64, i64) = (0, 0); let mut direction = Action::East(0); for action in actions { // If the action is Action::Forward, we instead want to move // in a direction determined by our current angle. // // So we convert Action::Forward to a concrete direction. let converted_action = if let &Action::Forward(amount) = action { match direction { Action::North(_) => Action::North(amount), Action::South(_) => Action::South(amount), Action::East(_) => Action::East(amount), Action::West(_) => Action::West(amount), _ => unreachable!("Direction can only be NSEW."), } } else { *action }; match converted_action { Action::North(amount) => { coords.1 += amount as i64; } Action::South(amount) => { coords.1 -= amount as i64; } Action::East(amount) => { coords.0 += amount as i64; } Action::West(amount) => { coords.0 -= amount as i64; } Action::Left(amount) => match amount % 360 { 0 => direction = direction, 90 => direction = direction.left_direction(), 180 => direction = direction.opposite_direction(), 270 => direction = direction.right_direction(), _ => panic!("Only 90 degree turns allowed"), }, Action::Right(amount) => match amount % 360 { 0 => direction = direction, 90 => direction = direction.right_direction(), 180 => direction = direction.opposite_direction(), 270 => direction = direction.left_direction(), _ => panic!("Only 90 degree turns allowed"), }, Action::Forward(_) => unreachable!("Converted previously"), } } coords } fn simulate_waypoint_movement(actions: &Vec) -> (i64, i64) { let mut waypoint = (1, 10); let mut ship = (0, 0); for action in actions { match *action { Action::North(amount) => { waypoint.0 += amount as i64; } Action::South(amount) => { waypoint.0 -= amount as i64; } Action::East(amount) => { waypoint.1 += amount as i64; } Action::West(amount) => { waypoint.1 -= amount as i64; } Action::Left(amount) => match amount % 360 { 0 => {} 90 => waypoint = (waypoint.1, -waypoint.0), 180 => waypoint = (-waypoint.0, -waypoint.1), 270 => waypoint = (-waypoint.1, waypoint.0), _ => panic!("Only 90 degree turns allowed"), }, Action::Right(amount) => match amount % 360 { 0 => {} 90 => waypoint = (-waypoint.1, waypoint.0), 180 => waypoint = (-waypoint.0, -waypoint.1), 270 => waypoint = (waypoint.1, -waypoint.0), _ => panic!("Only 90 degree turns allowed"), }, Action::Forward(amount) => { ship.0 += waypoint.0 * amount as i64; ship.1 += waypoint.1 * amount as i64; } } } ship } #[cfg(test)] mod tests { use super::*; use indoc::indoc; #[test] fn test_simple() -> Result<(), Box> { let input = indoc!( "F10 N3 F7 R90 F11 " ); let actions = parse_actions(&input)?; let location = simulate_ship_movement(&actions); assert_eq!(location.0.abs() + location.1.abs(), 25); Ok(()) } #[test] fn test_simple2() -> Result<(), Box> { let input = indoc!( "F10 N3 F7 R90 F11 " ); let actions = parse_actions(&input)?; let location = simulate_waypoint_movement(&actions); assert_eq!(location.0.abs() + location.1.abs(), 286); Ok(()) } }