1use utils::geometry::Vec2;
2use utils::prelude::*;
3
4#[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 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 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 match letter {
111 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) []);