adventofcode-2020/day-12/src/main.rs

226 lines
6.6 KiB
Rust

use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
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<Self, String> {
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<Vec<Action>, String> {
input.lines().map(Action::new).collect()
}
fn simulate_ship_movement(actions: &Vec<Action>) -> (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<Action>) -> (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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}
}