Skip to main content

year2018/
day10.rs

1use utils::geometry::Vec2;
2use utils::prelude::*;
3
4/// Recognizing text formed by converging points.
5#[derive(Clone, Debug)]
6pub struct Day10 {
7    message: String,
8    seconds: u32,
9}
10
11impl Day10 {
12    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
13        let vec2 = parser::i32()
14            .with_prefix(parser::take_while(|&x| x == b' '))
15            .repeat_n(b',')
16            .map(Vec2::from);
17
18        let mut points = vec2
19            .with_prefix("position=<")
20            .with_suffix("> velocity=<")
21            .then(vec2)
22            .with_suffix(">")
23            .repeat(parser::eol(), 2)
24            .parse_complete(input)?;
25
26        let mut seconds = 0;
27        let (mut last_x_diff, mut last_y_diff) = (i32::MAX, i32::MAX);
28        let message = 'time: loop {
29            let (mut min_x, mut max_x) = (i32::MAX, i32::MIN);
30            let (mut min_y, mut min_y_vel, mut max_y, mut max_y_vel) = (i32::MAX, 0, i32::MIN, 0);
31            for (p, v) in &points {
32                min_x = min_x.min(p.x);
33                max_x = max_x.max(p.x);
34
35                if p.y < min_y {
36                    min_y = p.y;
37                    min_y_vel = v.y;
38                }
39                if p.y > max_y {
40                    max_y = p.y;
41                    max_y_vel = v.y;
42                }
43            }
44
45            let (x_diff, y_diff) = (max_x.saturating_sub(min_x), max_y.saturating_sub(min_y));
46            if x_diff > last_x_diff
47                || y_diff > last_y_diff
48                || (x_diff == last_x_diff && y_diff == last_y_diff)
49            {
50                return Err(InputError::new(input, 0, "points never converge"));
51            }
52            (last_x_diff, last_y_diff) = (x_diff, y_diff);
53
54            // Letters are 6 wide and 10 tall, with 2 wide gaps between them
55            if y_diff != 9 || x_diff % 8 != 5 {
56                let advance_by = if y_diff >= 10 && min_y_vel.saturating_sub(max_y_vel) > 0 {
57                    ((y_diff - 9) / min_y_vel.saturating_sub(max_y_vel)).max(1)
58                } else {
59                    1
60                };
61
62                for (p, v) in &mut points {
63                    *p += *v * advance_by;
64                }
65                seconds += advance_by as u32;
66
67                continue;
68            }
69
70            let len = ((max_x - min_x + 3) / 8) as usize;
71            let mut letters = vec![0u64; len];
72            for (p, _) in &points {
73                if (p.x - min_x) % 8 >= 6 {
74                    // Point where there should be a 2-wide gap
75                    continue 'time;
76                }
77
78                letters[(p.x - min_x) as usize / 8] |=
79                    1 << (59 - ((p.x - min_x) % 8 + 6 * (p.y - min_y)));
80            }
81            break letters.into_iter().map(Self::ocr).collect::<String>();
82        };
83
84        Ok(Self { message, seconds })
85    }
86
87    #[expect(clippy::unreadable_literal)]
88    fn ocr(letter: u64) -> char {
89        //   ##    #####    ####   ######  ######   ####   #    #     ###  #    #  #       #    #
90        //  #  #   #    #  #    #  #       #       #    #  #    #      #   #   #   #       ##   #
91        // #    #  #    #  #       #       #       #       #    #      #   #  #    #       ##   #
92        // #    #  #    #  #       #       #       #       #    #      #   # #     #       # #  #
93        // #    #  #####   #       #####   #####   #       ######      #   ##      #       # #  #
94        // ######  #    #  #       #       #       #  ###  #    #      #   ##      #       #  # #
95        // #    #  #    #  #       #       #       #    #  #    #      #   # #     #       #  # #
96        // #    #  #    #  #       #       #       #    #  #    #  #   #   #  #    #       #   ##
97        // #    #  #    #  #    #  #       #       #   ##  #    #  #   #   #   #   #       #   ##
98        // #    #  #####    ####   ######  #        ### #  #    #   ###    #    #  ######  #    #
99        //
100        // #####   #####   #    #  ######
101        // #    #  #    #  #    #       #
102        // #    #  #    #   #  #        #
103        // #    #  #    #   #  #       #
104        // #####   #####     ##       #
105        // #       #  #      ##      #
106        // #       #   #    #  #    #
107        // #       #   #    #  #   #
108        // #       #    #  #    #  #
109        // #       #    #  #    #  ######
110        match letter {
111            //000000_111111_222222_333333_444444_555555_666666_777777_888888_999999
112            0b001100_010010_100001_100001_100001_111111_100001_100001_100001_100001 => 'A',
113            0b111110_100001_100001_100001_111110_100001_100001_100001_100001_111110 => 'B',
114            0b011110_100001_100000_100000_100000_100000_100000_100000_100001_011110 => 'C',
115            0b111111_100000_100000_100000_111110_100000_100000_100000_100000_111111 => 'E',
116            0b111111_100000_100000_100000_111110_100000_100000_100000_100000_100000 => 'F',
117            0b011110_100001_100000_100000_100000_100111_100001_100001_100011_011101 => 'G',
118            0b100001_100001_100001_100001_111111_100001_100001_100001_100001_100001 => 'H',
119            0b000111_000010_000010_000010_000010_000010_000010_100010_100010_011100 => 'J',
120            0b100001_100010_100100_101000_110000_110000_101000_100100_100010_100001 => 'K',
121            0b100000_100000_100000_100000_100000_100000_100000_100000_100000_111111 => 'L',
122            0b100001_110001_110001_101001_101001_100101_100101_100011_100011_100001 => 'N',
123            0b111110_100001_100001_100001_111110_100000_100000_100000_100000_100000 => 'P',
124            0b111110_100001_100001_100001_111110_100100_100010_100010_100001_100001 => 'R',
125            0b100001_100001_010010_010010_001100_001100_010010_010010_100001_100001 => 'X',
126            0b111111_000001_000001_000010_000100_001000_010000_100000_100000_111111 => 'Z',
127            _ => Self::unknown_letter(letter),
128        }
129    }
130
131    #[cold]
132    fn unknown_letter(letter: u64) -> char {
133        let mut display = String::new();
134        for b in (0..60).rev() {
135            display.push(if letter & (1 << b) == 0 { ' ' } else { '#' });
136            if b % 6 == 0 {
137                display.push('\n');
138            }
139        }
140        panic!("unknown letter {letter:#062b}:\n{display}");
141    }
142
143    #[must_use]
144    pub fn part1(&self) -> &str {
145        &self.message
146    }
147
148    #[must_use]
149    pub fn part2(&self) -> u32 {
150        self.seconds
151    }
152}
153
154examples!(Day10 -> (&'static str, u32) []);