216 lines
5.8 KiB
Rust
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);
|
|
}
|
|
}
|