226 lines
6.6 KiB
Rust
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(())
|
|
}
|
|
}
|