2023-12-17 23:04:56 -08:00
|
|
|
use itertools::Itertools;
|
2023-12-18 02:03:48 -08:00
|
|
|
use std::collections::LinkedList;
|
2023-12-17 23:04:56 -08:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{BufRead, BufReader, Lines};
|
|
|
|
use std::time::Instant;
|
|
|
|
|
|
|
|
// BOILERPLATE
|
|
|
|
type InputIter = Lines<BufReader<File>>;
|
|
|
|
|
|
|
|
fn get_input() -> InputIter {
|
|
|
|
let f = File::open("input").unwrap();
|
|
|
|
let br = BufReader::new(f);
|
|
|
|
br.lines()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let start = Instant::now();
|
|
|
|
let ans1 = problem1(get_input());
|
|
|
|
let duration = start.elapsed();
|
|
|
|
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
let ans2 = problem2(get_input());
|
|
|
|
let duration = start.elapsed();
|
|
|
|
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATA
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
|
|
enum Direction {
|
|
|
|
Left,
|
|
|
|
Right,
|
|
|
|
Up,
|
|
|
|
Down,
|
|
|
|
}
|
|
|
|
|
2023-12-18 01:54:31 -08:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
|
|
enum Turn {
|
|
|
|
LeftNinety,
|
|
|
|
RightNinety,
|
|
|
|
OneEighty,
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:04:56 -08:00
|
|
|
impl Direction {
|
|
|
|
const fn all() -> &'static [Self; 4] {
|
2023-12-18 02:03:48 -08:00
|
|
|
&[Direction::Left, Direction::Right, Direction::Up, Direction::Down]
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
const fn opposite(&self) -> Self {
|
|
|
|
match self {
|
|
|
|
Direction::Left => Direction::Right,
|
|
|
|
Direction::Right => Direction::Left,
|
|
|
|
Direction::Up => Direction::Down,
|
|
|
|
Direction::Down => Direction::Up,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const fn offset(&self) -> (isize, isize) {
|
|
|
|
match self {
|
|
|
|
Direction::Left => (-1, 0),
|
|
|
|
Direction::Right => (1, 0),
|
|
|
|
Direction::Up => (0, -1),
|
|
|
|
Direction::Down => (0, 1),
|
|
|
|
}
|
|
|
|
}
|
2023-12-18 01:54:31 -08:00
|
|
|
fn turn_kind(&self, next_dir: Direction) -> Turn {
|
|
|
|
if *self == next_dir {
|
|
|
|
Turn::None
|
|
|
|
} else if self.opposite() == next_dir {
|
|
|
|
Turn::OneEighty
|
|
|
|
} else {
|
|
|
|
match self {
|
|
|
|
Direction::Left if next_dir == Direction::Up => Turn::RightNinety,
|
|
|
|
Direction::Left if next_dir == Direction::Down => Turn::LeftNinety,
|
|
|
|
Direction::Right if next_dir == Direction::Up => Turn::LeftNinety,
|
|
|
|
Direction::Right if next_dir == Direction::Down => Turn::RightNinety,
|
|
|
|
Direction::Up if next_dir == Direction::Left => Turn::LeftNinety,
|
|
|
|
Direction::Up if next_dir == Direction::Right => Turn::RightNinety,
|
|
|
|
Direction::Down if next_dir == Direction::Right => Turn::LeftNinety,
|
|
|
|
Direction::Down if next_dir == Direction::Left => Turn::RightNinety,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&str> for Direction {
|
|
|
|
fn from(s: &str) -> Self {
|
|
|
|
match s {
|
|
|
|
"L" => Direction::Left,
|
|
|
|
"R" => Direction::Right,
|
|
|
|
"U" => Direction::Up,
|
|
|
|
"D" => Direction::Down,
|
|
|
|
s => panic!("{} is not a valid direction", s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&Direction> for char {
|
|
|
|
fn from(dir: &Direction) -> Self {
|
|
|
|
match dir {
|
|
|
|
Direction::Left => '←',
|
|
|
|
Direction::Right => '→',
|
|
|
|
Direction::Up => '↑',
|
|
|
|
Direction::Down => '↓',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct DigInstruction {
|
|
|
|
dir: Direction,
|
|
|
|
count: usize,
|
|
|
|
color: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&str> for DigInstruction {
|
|
|
|
fn from(s: &str) -> Self {
|
|
|
|
let mut parts = s.split_ascii_whitespace();
|
|
|
|
let (dir, count, color) = (
|
|
|
|
parts.next().unwrap(),
|
|
|
|
parts.next().unwrap(),
|
2023-12-18 02:03:48 -08:00
|
|
|
parts.next().unwrap().chars().skip(2).take(6).collect::<String>(),
|
2023-12-17 23:04:56 -08:00
|
|
|
);
|
|
|
|
Self {
|
|
|
|
dir: dir.into(),
|
|
|
|
count: count.parse().unwrap(),
|
|
|
|
color: color.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-18 01:54:31 -08:00
|
|
|
impl DigInstruction {
|
|
|
|
fn part2_transform(&mut self) {
|
|
|
|
let (distance_s, direction_s) = self.color.split_at(5);
|
|
|
|
self.count = usize::from_str_radix(distance_s, 16).unwrap();
|
|
|
|
self.dir = match direction_s {
|
|
|
|
"0" => Direction::Right,
|
|
|
|
"1" => Direction::Down,
|
|
|
|
"2" => Direction::Left,
|
|
|
|
"3" => Direction::Up,
|
2023-12-18 02:03:48 -08:00
|
|
|
s => panic!("`{}` is not a valid direction code", s),
|
2023-12-18 01:54:31 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:04:56 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct DigPlan {
|
|
|
|
instructions: Vec<DigInstruction>,
|
|
|
|
}
|
|
|
|
|
2023-12-18 01:54:31 -08:00
|
|
|
impl DigPlan {
|
|
|
|
fn part2_transform(&mut self) {
|
|
|
|
for i in &mut self.instructions {
|
|
|
|
i.part2_transform();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:04:56 -08:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct DigTile {
|
|
|
|
position: Position,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for DigTile {
|
|
|
|
fn default() -> Self {
|
2023-12-18 02:03:48 -08:00
|
|
|
Self { position: (0, 0) }
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Position = (isize, isize);
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct DigHole {
|
|
|
|
tiles_loop: LinkedList<DigTile>,
|
2023-12-18 01:54:31 -08:00
|
|
|
area: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
// determinant of positions p1 and p2
|
|
|
|
fn det(p1: Position, p2: Position) -> i64 {
|
|
|
|
((p1.0 * p2.1) - (p1.1 * p2.0)) as i64
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DigHole {
|
|
|
|
fn new() -> Self {
|
|
|
|
DigHole {
|
|
|
|
tiles_loop: LinkedList::new(),
|
2023-12-18 01:54:31 -08:00
|
|
|
area: 0,
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
}
|
2023-12-18 01:54:31 -08:00
|
|
|
fn pos_offset_n(&self, pos: Position, offset: (isize, isize), n: usize) -> Position {
|
|
|
|
(pos.0 + offset.0 * n as isize, pos.1 + offset.1 * n as isize)
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
fn run_plan(&mut self, plan: &DigPlan) {
|
|
|
|
let mut cur_pos = (0, 0);
|
|
|
|
|
2023-12-18 02:03:48 -08:00
|
|
|
self.tiles_loop.push_back(DigTile { position: cur_pos });
|
2023-12-18 01:54:31 -08:00
|
|
|
let mut move_offset;
|
|
|
|
for (idx, i) in plan.instructions.iter().enumerate() {
|
|
|
|
let prev_instruction = if idx > 0 {
|
|
|
|
&plan.instructions[idx - 1]
|
2023-12-17 23:04:56 -08:00
|
|
|
} else {
|
2023-12-18 01:54:31 -08:00
|
|
|
&plan.instructions[plan.instructions.len() - 1]
|
|
|
|
};
|
2023-12-18 02:03:48 -08:00
|
|
|
let Some(next_instruction) = plan.instructions.get(idx + 1).or(Some(&plan.instructions[0])) else {
|
2023-12-18 01:54:31 -08:00
|
|
|
panic!()
|
|
|
|
};
|
|
|
|
let cur_turn = prev_instruction.dir.turn_kind(i.dir);
|
|
|
|
let next_turn = i.dir.turn_kind(next_instruction.dir);
|
2023-12-18 02:03:48 -08:00
|
|
|
|
|
|
|
// Point needs to live on the 'outside' corner of the character. to achieve this we need to offset the move
|
|
|
|
// by the following. Found this empirically but there's probably some mathematical principle behind it...
|
2023-12-18 01:54:31 -08:00
|
|
|
move_offset = match (cur_turn, next_turn) {
|
|
|
|
(Turn::RightNinety, Turn::RightNinety) => 1,
|
|
|
|
(Turn::RightNinety, Turn::LeftNinety) => 0,
|
|
|
|
(Turn::LeftNinety, Turn::LeftNinety) => -1,
|
|
|
|
(Turn::LeftNinety, Turn::RightNinety) => 0,
|
|
|
|
t => panic!("turn {:?} not allowed here", t),
|
|
|
|
};
|
|
|
|
|
2023-12-18 02:03:48 -08:00
|
|
|
cur_pos = self.pos_offset_n(cur_pos, i.dir.offset(), (i.count as isize + move_offset) as usize);
|
|
|
|
self.tiles_loop.push_back(DigTile { position: cur_pos });
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
2023-12-18 01:54:31 -08:00
|
|
|
|
|
|
|
// Shoelace formula
|
|
|
|
// https://en.wikipedia.org/wiki/Shoelace_formula
|
|
|
|
let double_area: i64 = self
|
|
|
|
.tiles_loop
|
|
|
|
.iter()
|
|
|
|
.tuple_windows()
|
|
|
|
.map(|(a, b)| det(a.position, b.position))
|
|
|
|
.sum();
|
|
|
|
self.area = (double_area / 2).abs() as u64;
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: BufRead> From<Lines<T>> for DigPlan {
|
|
|
|
fn from(lines: Lines<T>) -> Self {
|
|
|
|
Self {
|
2023-12-18 02:03:48 -08:00
|
|
|
instructions: lines.map(|line| DigInstruction::from(line.unwrap().as_str())).collect(),
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PROBLEM 1 solution
|
|
|
|
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
|
|
|
let plan = DigPlan::from(input);
|
|
|
|
let mut dig = DigHole::new();
|
|
|
|
dig.run_plan(&plan);
|
2023-12-18 01:54:31 -08:00
|
|
|
dig.area
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// PROBLEM 2 solution
|
|
|
|
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
2023-12-18 01:54:31 -08:00
|
|
|
let mut plan = DigPlan::from(input);
|
|
|
|
let mut dig = DigHole::new();
|
|
|
|
plan.part2_transform();
|
|
|
|
dig.run_plan(&plan);
|
|
|
|
dig.area
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::*;
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
const EXAMPLE: &str = &"R 6 (#70c710)
|
|
|
|
D 5 (#0dc571)
|
|
|
|
L 2 (#5713f0)
|
|
|
|
D 2 (#d2c081)
|
|
|
|
R 2 (#59c680)
|
|
|
|
D 2 (#411b91)
|
|
|
|
L 5 (#8ceee2)
|
|
|
|
U 2 (#caa173)
|
|
|
|
L 1 (#1b58a2)
|
|
|
|
U 2 (#caa171)
|
|
|
|
R 2 (#7807d2)
|
|
|
|
U 3 (#a77fa3)
|
|
|
|
L 2 (#015232)
|
|
|
|
U 2 (#7a21e3)";
|
|
|
|
|
2023-12-18 01:54:31 -08:00
|
|
|
const AREA16_SQUARE: &str = &"R 3 (#000000)
|
|
|
|
D 3 (#000000)
|
|
|
|
L 3 (#000000)
|
|
|
|
U 4 (#000000";
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn area16_square() {
|
|
|
|
let c = Cursor::new(AREA16_SQUARE);
|
|
|
|
assert_eq!(problem1(c.lines()), 16);
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:04:56 -08:00
|
|
|
#[test]
|
|
|
|
fn problem1_example() {
|
|
|
|
let c = Cursor::new(EXAMPLE);
|
|
|
|
assert_eq!(problem1(c.lines()), 62);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn problem2_example() {
|
|
|
|
let c = Cursor::new(EXAMPLE);
|
2023-12-18 01:54:31 -08:00
|
|
|
assert_eq!(problem2(c.lines()), 952408144115);
|
2023-12-17 23:04:56 -08:00
|
|
|
}
|
|
|
|
}
|