AoC2020/src/day12.rs

216 lines
5.8 KiB
Rust

use std::io::BufRead;
use std::str::FromStr;
use std::convert::TryFrom;
use num_enum::{TryFromPrimitive, IntoPrimitive};
#[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
enum Direction {
North = 0,
East = 1,
South = 2,
West = 3,
}
#[derive(Debug)]
enum Rotation {
Left,
Right,
}
#[derive(Debug)]
enum RotationMode {
Origin,
Point,
}
#[derive(Debug)]
enum ActionType {
Direction(Direction),
Rotation(Rotation),
Forward
}
#[derive(Debug)]
struct Action {
atype: ActionType,
value: isize,
}
#[derive(Debug)]
struct Ship {
x: isize,
y: isize,
direction: Direction,
rotation_mode: RotationMode,
waypoint: Option<Box<Ship>>
}
impl Direction {
fn rotate(&self, rotation: &Rotation, degree: isize) -> Self {
let direction_int: u8 = (*self).into();
let shifts = degree / 90;
let new_direction_int = match rotation {
Rotation::Left => (direction_int as isize - shifts + 4) as u8 % 4,
Rotation::Right => (direction_int as isize + shifts) as u8 % 4,
};
Direction::try_from(new_direction_int).unwrap()
}
fn translate(&self, value: isize) -> (isize, isize) {
match self {
Direction::East => (value, 0),
Direction::West => (-value, 0),
Direction::North => (0, value),
Direction::South => (0, -value),
}
}
}
impl Ship {
fn new() -> Self {
Ship {
x: 0,
y: 0,
rotation_mode: RotationMode::Point,
direction: Direction::East,
waypoint: None
}
}
fn with_waypoint(x: isize, y: isize) -> Self {
Ship {
waypoint: Some(Box::new(Ship {
x: x,
y: y,
rotation_mode: RotationMode::Origin,
..Self::new()
})),
..Self::new()
}
}
fn execute(&mut self, action: Action) {
if let Some(waypoint) = self.waypoint.as_mut() {
match action.atype {
ActionType::Forward => self.move_towards_waypoint(action.value),
_ => waypoint.execute(action),
}
} else {
match action.atype {
ActionType::Direction(direction) => self.translate(&direction, action.value),
ActionType::Rotation(rotation) => self.rotate(&rotation, action.value),
ActionType::Forward => self.move_forward(action.value),
}
}
}
fn move_towards_waypoint(&mut self, value: isize) {
if let Some(waypoint) = &self.waypoint {
self.x += waypoint.x * value;
self.y += waypoint.y * value;
}
}
fn move_forward(&mut self, value: isize) {
let direction = self.direction;
self.translate(&direction, value);
}
fn rotate(&mut self, rotation: &Rotation, degree: isize) {
match self.rotation_mode {
RotationMode::Origin => self.rotate_around_origin(rotation, degree),
RotationMode::Point => self.rotate_around_self(rotation, degree),
}
}
fn rotate_around_self(&mut self, rotation: &Rotation, degree: isize) {
self.direction = self.direction.rotate(rotation, degree)
}
fn rotate_around_origin(&mut self, rotation: &Rotation, degree: isize) {
let new_coord = match Direction::North.rotate(rotation, degree) {
Direction::North => (self.x, self.y),
Direction::East => (self.y, -self.x),
Direction::South => (-self.x, -self.y),
Direction::West => (-self.y, self.x),
};
self.x = new_coord.0;
self.y = new_coord.1;
}
fn translate(&mut self, direction: &Direction, value: isize) {
let translation = direction.translate(value);
self.x += translation.0;
self.y += translation.1;
}
fn distance(&self) -> usize {
(self.x.abs() + self.y.abs()) as usize
}
}
impl FromStr for Action {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut it = s.chars();
let atype = match it.next().unwrap() {
'N' => ActionType::Direction(Direction::North),
'S' => ActionType::Direction(Direction::South),
'E' => ActionType::Direction(Direction::East),
'W' => ActionType::Direction(Direction::West),
'R' => ActionType::Rotation(Rotation::Right),
'L' => ActionType::Rotation(Rotation::Left),
'F' => ActionType::Forward,
_ => return Err(()),
};
let value: isize = it.collect::<String>().parse().unwrap();
Ok(Action {
atype,
value,
})
}
}
pub fn part1<F: BufRead> (input: F) {
let mut actions: Vec<_> = input.lines()
.filter_map(|line| line.unwrap().parse::<Action>().ok())
.collect();
let mut ship = Ship::new();
for action in actions.drain(..) {
ship.execute(action);
}
println!("{}", ship.distance());
}
pub fn part2<F: BufRead> (input: F) {
let mut actions: Vec<_> = input.lines()
.filter_map(|line| line.unwrap().parse::<Action>().ok())
.collect();
let mut ship = Ship::with_waypoint(10, 1);
for action in actions.drain(..) {
ship.execute(action);
}
println!("{}", ship.distance());
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_waypoint() {
let mut actions: Vec<Action> = ["F10", "N3", "F7", "R90", "F11"].iter().filter_map(|s| s.parse().ok()).collect();
let mut ship = Ship::with_waypoint(10, 1);
for action in actions.drain(..) {
println!("{:#?}", action);
ship.execute(action);
println!("{:#?}", ship);
}
assert_eq!(ship.distance(), 286);
}
}