1use utils::geometry::{Direction, Turn};
2use utils::grid;
3use utils::prelude::*;
4
5#[derive(Clone, Debug)]
7pub struct Day13 {
8 grid: Vec<u8>,
9 carts: Vec<Cart>,
10 cols: usize,
11 offsets: [isize; 4],
12}
13
14#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15struct Cart {
16 location: usize,
17 direction: Direction,
18 next_turn: Turn,
19 crashed: bool,
20}
21
22impl Day13 {
23 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
24 let mut carts = Vec::new();
25
26 let (_, cols, grid) = grid::parse(
27 input,
28 1,
29 0,
30 |b| b,
31 |b| matches!(b, b'|' | b'-' | b'/' | b'\\' | b'+' | b' '),
32 |location, b| {
33 let (track, direction) = match b {
34 b'^' => (b'|', Direction::Up),
35 b'v' => (b'|', Direction::Down),
36 b'<' => (b'-', Direction::Left),
37 b'>' => (b'-', Direction::Right),
38 _ => {
39 return Err(
40 "expected track ('|', '-', '/', '\\', '+'), cart ('^', 'v', '<', '>') or ' '",
41 );
42 }
43 };
44 carts.push(Cart {
45 location,
46 direction,
47 next_turn: Turn::Left,
48 crashed: false,
49 });
50 Ok(track)
51 },
52 )?;
53
54 Ok(Self {
55 grid,
56 carts,
57 cols,
58 offsets: [-(cols as isize), 1, cols as isize, -1],
59 })
60 }
61
62 #[must_use]
63 pub fn part1(&self) -> String {
64 let mut carts = self.carts.clone();
65
66 let mut bitset = vec![0u64; self.grid.len().div_ceil(64)];
67 for cart in &carts {
68 bitset[cart.location / 64] |= 1 << (cart.location % 64);
69 }
70
71 loop {
72 carts.sort_unstable_by_key(|cart| cart.location);
73
74 for cart in &mut carts {
75 bitset[cart.location / 64] &= !(1 << (cart.location % 64));
76
77 self.move_cart(cart);
78
79 if bitset[cart.location / 64] & (1 << (cart.location % 64)) != 0 {
80 return self.coordinates_str(cart.location);
81 }
82
83 bitset[cart.location / 64] |= 1 << (cart.location % 64);
84 }
85 }
86 }
87
88 #[must_use]
89 pub fn part2(&self) -> String {
90 assert!(
91 !self.carts.len().is_multiple_of(2),
92 "no solution found: part 2 requires an odd number of carts"
93 );
94
95 let mut carts = self.carts.clone();
96
97 let mut bitset = vec![0u64; self.grid.len().div_ceil(64)];
98 for cart in &carts {
99 bitset[cart.location / 64] |= 1 << (cart.location % 64);
100 }
101
102 while carts.len() > 1 {
103 carts.sort_unstable_by_key(|cart| cart.location);
104
105 for i in 0..carts.len() {
106 if carts[i].crashed {
107 continue;
108 }
109
110 let old_loc = carts[i].location;
111 bitset[old_loc / 64] &= !(1 << (old_loc % 64));
112
113 self.move_cart(&mut carts[i]);
114 let new_loc = carts[i].location;
115
116 if bitset[new_loc / 64] & (1 << (new_loc % 64)) != 0 {
117 for cart in carts.iter_mut() {
118 cart.crashed |= cart.location == new_loc;
119 }
120
121 bitset[new_loc / 64] &= !(1 << (new_loc % 64));
122 } else {
123 bitset[new_loc / 64] |= 1 << (new_loc % 64);
124 }
125 }
126
127 carts.retain(|cart| !cart.crashed);
128 }
129 self.coordinates_str(carts[0].location)
130 }
131
132 #[inline]
133 fn move_cart(&self, cart: &mut Cart) {
134 cart.location = cart
135 .location
136 .wrapping_add_signed(self.offsets[cart.direction as usize]);
137
138 let track = self.grid[cart.location];
139 match track {
140 b'/' => {
141 cart.direction = match cart.direction {
142 Direction::Up => Direction::Right,
143 Direction::Right => Direction::Up,
144 Direction::Down => Direction::Left,
145 Direction::Left => Direction::Down,
146 };
147 }
148 b'\\' => {
149 cart.direction = match cart.direction {
150 Direction::Up => Direction::Left,
151 Direction::Left => Direction::Up,
152 Direction::Right => Direction::Down,
153 Direction::Down => Direction::Right,
154 };
155 }
156 b'+' => {
157 cart.direction = cart.direction.turn(cart.next_turn);
158
159 cart.next_turn = match cart.next_turn {
160 Turn::Left => Turn::None,
161 Turn::None => Turn::Right,
162 Turn::Right => Turn::Left,
163 };
164 }
165 b'|' | b'-' => {}
166 b' ' | 0 => panic!("no solution found: a cart left the track"),
167 _ => unreachable!(),
168 }
169 }
170
171 fn coordinates_str(&self, location: usize) -> String {
172 format!(
173 "{},{}",
174 (location % self.cols) - 1,
175 (location / self.cols) - 1
176 )
177 }
178}
179
180examples!(Day13 -> (&'static str, &'static str) [
181 {
182 input: "/->-\\ \n| | /----\\\n| /-+--+-\\ |\n| | | | v |\n\\-+-/ \\-+--/\n \\------/ ",
183 part1: "7,3",
184 },
185 {
186 input: "/>-<\\ \n| | \n| /<+-\\\n| | | v\n\\>+</ |\n | ^\n \\<->/",
187 part2: "6,4",
188 },
189]);