1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! # Day 1: No Time for a Taxicab
//!
//! Santa's sleigh uses a very high-precision clock to guide its movements, and the clock's
//! oscillator is regulated by stars. Unfortunately, the stars have been stolen... by the Easter
//! Bunny. To save Christmas, Santa needs you to retrieve all **fifty stars** by December 25th.
//!
//! Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent
//! calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants **one
//! star**. Good luck!
//!
//! You're airdropped near **Easter Bunny Headquarters** in a city somewhere. "Near", unfortunately,
//! is as close as you can get - the instructions on the Easter Bunny Recruiting Document the Elves
//! intercepted start here, and nobody had time to work them out further.
//!
//! The Document indicates that you should start at the given coordinates (where you just landed)
//! and face North. Then, follow the provided sequence: either turn left (`L`) or right (`R`) 90
//! degrees, then walk forward the given number of blocks, ending at a new intersection.
//!
//! There's no time to follow such ridiculous instructions on foot, though, so you take a moment and
//! work out the destination. Given that you can only walk on the [street grid of the city], how far
//! is the shortest path to the destination?
//!
//! For example:
//!
//! - Following `R2, L3` leaves you `2` blocks East and `3` blocks North, or `5` blocks away.
//! - `R2, R2, R2` leaves you `2` blocks due South of your starting position, which is `2` blocks
//!   away.
//! - `R5, L5, R5, R3` leaves you `12` blocks away.
//!
//! **How many blocks away** is Easter Bunny HQ?
//!
//! [street grid of the city]: https://en.wikipedia.org/wiki/Taxicab_geometry
//!
//! ## Part Two
//!
//! Then, you notice the instructions continue on the back of the Recruiting Document. Easter Bunny
//! HQ is actually at the first location you visit twice.
//!
//! For example, if your instructions are `R8, R4, R4, R8`, the first location you visit twice is
//! `4` blocks away, due East.
//!
//! How many blocks away is the **first location you visit twice?**

use std::collections::HashSet;

use anyhow::{bail, Context, Result};

pub const INPUT: &str = include_str!("d01.txt");

pub fn solve_part_one(input: &str) -> Result<i32> {
    let instructions = parse_input(input)?;
    let mut direction = Direction::North;
    let mut position = (0, 0);

    for (turn, steps) in instructions {
        direction = direction.turn(turn);
        position = match direction {
            Direction::North => (position.0, position.1 - steps),
            Direction::East => (position.0 + steps, position.1),
            Direction::South => (position.0, position.1 + steps),
            Direction::West => (position.0 - steps, position.1),
        }
    }

    Ok(position.0.abs() + position.1.abs())
}

pub fn solve_part_two(input: &str) -> Result<i32> {
    let instructions = parse_input(input)?;
    let mut direction = Direction::North;
    let mut position = (0, 0);
    let mut history = HashSet::new();

    for (turn, steps) in instructions {
        direction = direction.turn(turn);
        let stepper: fn((i32, i32)) -> (i32, i32) = match direction {
            Direction::North => |p| (p.0, p.1 - 1),
            Direction::East => |p| (p.0 + 1, p.1),
            Direction::South => |p| (p.0, p.1 + 1),
            Direction::West => |p| (p.0 - 1, p.1),
        };

        for _ in 0..steps {
            position = stepper(position);
            if history.contains(&position) {
                return Ok(position.0.abs() + position.1.abs());
            } else {
                history.insert(position);
            }
        }
    }

    bail!("no location visited twice")
}

fn parse_input(input: &str) -> Result<Vec<(Turn, i32)>> {
    input
        .lines()
        .next()
        .context("input is empty")?
        .split(", ")
        .map(|instruction| {
            let direction = match instruction.chars().next() {
                Some('L') => Turn::Left,
                Some('R') => Turn::Right,
                Some(s) => bail!("invalid direction `{}`", s),
                None => bail!("missing direction"),
            };
            let steps = instruction[1..].parse().context("invalid steps number")?;

            Ok((direction, steps))
        })
        .collect()
}

#[derive(Clone, Copy)]
enum Turn {
    Left,
    Right,
}

#[derive(Clone, Copy)]
enum Direction {
    North,
    East,
    South,
    West,
}

impl Direction {
    fn turn(self, turn: Turn) -> Self {
        match turn {
            Turn::Left => match self {
                Self::North => Self::West,
                Self::East => Self::North,
                Self::South => Self::East,
                Self::West => Self::South,
            },
            Turn::Right => match self {
                Self::North => Self::East,
                Self::East => Self::South,
                Self::South => Self::West,
                Self::West => Self::North,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part_one() {}

    #[test]
    fn part_two() {}
}