tangled
alpha
login
or
join now
jonas.tngl.sh
/
advent-of-code
0
fork
atom
Advent of Code solutions in Rust
0
fork
atom
overview
issues
pulls
pipelines
feat: day 12 of AoC 2020
jonas.tngl.sh
1 year ago
5f01575e
8ec557dd
+308
-2
2 changed files
expand all
collapse all
unified
split
aoc_2020
src
day12.rs
main.rs
+306
aoc_2020/src/day12.rs
reviewed
···
1
1
+
use aoc_companion::prelude::*;
2
2
+
use aoc_utils::linalg::Vector;
3
3
+
use itertools::Itertools;
4
4
+
5
5
+
pub(crate) struct Door {
6
6
+
instructions: Vec<Instruction>,
7
7
+
}
8
8
+
9
9
+
impl<'input> Solution<'input> for Door {
10
10
+
fn parse(input: &'input str) -> Result<Self, ParseError> {
11
11
+
let instructions = input.lines().map(|line| line.parse()).try_collect()?;
12
12
+
Ok(Door { instructions })
13
13
+
}
14
14
+
15
15
+
fn part1(&self) -> i32 {
16
16
+
final_state::<ShipState>(&self.instructions)
17
17
+
.position
18
18
+
.norm_l1()
19
19
+
}
20
20
+
21
21
+
fn part2(&self) -> i32 {
22
22
+
final_state::<WaypointState>(&self.instructions)
23
23
+
.position
24
24
+
.norm_l1()
25
25
+
}
26
26
+
}
27
27
+
28
28
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29
29
+
enum Instruction {
30
30
+
MoveBy { delta: Vector<i32, 2> },
31
31
+
Turn { quadrants: i32 },
32
32
+
MoveForward { amount: i32 },
33
33
+
}
34
34
+
35
35
+
#[derive(Debug, thiserror::Error)]
36
36
+
pub(crate) enum ParseError {
37
37
+
#[error(transparent)]
38
38
+
ParseIntError(#[from] std::num::ParseIntError),
39
39
+
#[error("first instruction byte is multibyte")]
40
40
+
MultibyteInstruction,
41
41
+
#[error("invalid instruction type {0:?}")]
42
42
+
InvalidInstructionType(char),
43
43
+
#[error("angle of {0} degrees is not axis-aligned")]
44
44
+
AngleNotAxisAligned(i32),
45
45
+
}
46
46
+
47
47
+
fn parse_quadrants(s: &str) -> Result<i32, ParseError> {
48
48
+
let angle = s.parse()?;
49
49
+
if angle % 90 == 0 {
50
50
+
Ok(angle / 90)
51
51
+
} else {
52
52
+
Err(ParseError::AngleNotAxisAligned(angle))
53
53
+
}
54
54
+
}
55
55
+
56
56
+
impl std::str::FromStr for Instruction {
57
57
+
type Err = ParseError;
58
58
+
59
59
+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
60
60
+
let (c, rest) = s
61
61
+
.split_at_checked(1)
62
62
+
.ok_or(ParseError::MultibyteInstruction)?;
63
63
+
Ok(match c {
64
64
+
"E" => Instruction::MoveBy {
65
65
+
delta: Vector([rest.parse()?, 0]),
66
66
+
},
67
67
+
"N" => Instruction::MoveBy {
68
68
+
delta: Vector([0, rest.parse()?]),
69
69
+
},
70
70
+
"W" => Instruction::MoveBy {
71
71
+
delta: Vector([-rest.parse()?, 0]),
72
72
+
},
73
73
+
"S" => Instruction::MoveBy {
74
74
+
delta: Vector([0, -rest.parse()?]),
75
75
+
},
76
76
+
"L" => Instruction::Turn {
77
77
+
quadrants: parse_quadrants(rest)?,
78
78
+
},
79
79
+
"R" => Instruction::Turn {
80
80
+
quadrants: -parse_quadrants(rest)?,
81
81
+
},
82
82
+
"F" => Instruction::MoveForward {
83
83
+
amount: rest.parse()?,
84
84
+
},
85
85
+
_ => Err(ParseError::InvalidInstructionType(
86
86
+
c.chars().next().unwrap(),
87
87
+
))?,
88
88
+
})
89
89
+
}
90
90
+
}
91
91
+
92
92
+
trait State {
93
93
+
fn turn(&mut self, quadrants: i32);
94
94
+
fn move_by(&mut self, delta: Vector<i32, 2>);
95
95
+
fn move_forward(&mut self, amount: i32);
96
96
+
97
97
+
fn execute(&mut self, instruction: Instruction) {
98
98
+
match instruction {
99
99
+
Instruction::MoveBy { delta } => self.move_by(delta),
100
100
+
Instruction::Turn { quadrants } => self.turn(quadrants),
101
101
+
Instruction::MoveForward { amount } => self.move_forward(amount),
102
102
+
}
103
103
+
}
104
104
+
}
105
105
+
106
106
+
#[derive(Debug, Clone, PartialEq, Eq)]
107
107
+
struct ShipState {
108
108
+
facing: Vector<i32, 2>,
109
109
+
position: Vector<i32, 2>,
110
110
+
}
111
111
+
112
112
+
impl Default for ShipState {
113
113
+
fn default() -> Self {
114
114
+
Self {
115
115
+
facing: Vector([1, 0]),
116
116
+
position: Vector::default(),
117
117
+
}
118
118
+
}
119
119
+
}
120
120
+
121
121
+
impl State for ShipState {
122
122
+
fn turn(&mut self, quadrants: i32) {
123
123
+
self.facing = rotated(self.facing, quadrants);
124
124
+
}
125
125
+
126
126
+
fn move_by(&mut self, delta: Vector<i32, 2>) {
127
127
+
self.position += delta;
128
128
+
}
129
129
+
130
130
+
fn move_forward(&mut self, amount: i32) {
131
131
+
self.move_by(self.facing * amount);
132
132
+
}
133
133
+
}
134
134
+
135
135
+
#[derive(Debug, Clone, PartialEq, Eq)]
136
136
+
struct WaypointState {
137
137
+
waypoint: Vector<i32, 2>,
138
138
+
position: Vector<i32, 2>,
139
139
+
}
140
140
+
141
141
+
impl Default for WaypointState {
142
142
+
fn default() -> Self {
143
143
+
Self {
144
144
+
waypoint: Vector([10, 1]),
145
145
+
position: Vector::default(),
146
146
+
}
147
147
+
}
148
148
+
}
149
149
+
150
150
+
impl State for WaypointState {
151
151
+
fn turn(&mut self, quadrants: i32) {
152
152
+
self.waypoint = rotated(self.waypoint, quadrants);
153
153
+
}
154
154
+
155
155
+
fn move_by(&mut self, delta: Vector<i32, 2>) {
156
156
+
self.waypoint += delta;
157
157
+
}
158
158
+
159
159
+
fn move_forward(&mut self, amount: i32) {
160
160
+
self.position += self.waypoint * amount;
161
161
+
}
162
162
+
}
163
163
+
164
164
+
fn rotated(mut v: Vector<i32, 2>, quadrants: i32) -> Vector<i32, 2> {
165
165
+
if (quadrants + 4) % 2 != 0 {
166
166
+
v = Vector([-v[1], v[0]]);
167
167
+
}
168
168
+
if (quadrants + 4) / 2 % 2 != 0 {
169
169
+
v *= -1;
170
170
+
}
171
171
+
v
172
172
+
}
173
173
+
174
174
+
fn final_state<S: State + Default>(instructions: &[Instruction]) -> S {
175
175
+
instructions
176
176
+
.iter()
177
177
+
.fold(S::default(), |mut state, &instruction| {
178
178
+
state.execute(instruction);
179
179
+
state
180
180
+
})
181
181
+
}
182
182
+
183
183
+
#[cfg(test)]
184
184
+
mod tests {
185
185
+
use itertools::assert_equal;
186
186
+
187
187
+
use super::*;
188
188
+
189
189
+
const EXAMPLE_INPUT: &str = "\
190
190
+
F10
191
191
+
N3
192
192
+
F7
193
193
+
R90
194
194
+
F11";
195
195
+
196
196
+
const EXAMPLE_INSTRUCTIONS: &[Instruction] = &[
197
197
+
Instruction::MoveForward { amount: 10 },
198
198
+
Instruction::MoveBy {
199
199
+
delta: Vector([0, 3]),
200
200
+
},
201
201
+
Instruction::MoveForward { amount: 7 },
202
202
+
Instruction::Turn { quadrants: -1 },
203
203
+
Instruction::MoveForward { amount: 11 },
204
204
+
];
205
205
+
206
206
+
#[test]
207
207
+
fn parse_example_input() {
208
208
+
assert_eq!(
209
209
+
EXAMPLE_INPUT
210
210
+
.lines()
211
211
+
.map(|line| line.parse().unwrap())
212
212
+
.collect::<Vec<Instruction>>(),
213
213
+
EXAMPLE_INSTRUCTIONS
214
214
+
);
215
215
+
}
216
216
+
217
217
+
#[test]
218
218
+
fn intermediate_ship_states_in_example() {
219
219
+
assert_equal(
220
220
+
EXAMPLE_INSTRUCTIONS
221
221
+
.iter()
222
222
+
.scan(ShipState::default(), |state, &instruction| {
223
223
+
state.execute(instruction);
224
224
+
Some(state.clone())
225
225
+
}),
226
226
+
[
227
227
+
ShipState {
228
228
+
facing: Vector([1, 0]),
229
229
+
position: Vector([10, 0]),
230
230
+
},
231
231
+
ShipState {
232
232
+
facing: Vector([1, 0]),
233
233
+
position: Vector([10, 3]),
234
234
+
},
235
235
+
ShipState {
236
236
+
facing: Vector([1, 0]),
237
237
+
position: Vector([17, 3]),
238
238
+
},
239
239
+
ShipState {
240
240
+
facing: Vector([0, -1]),
241
241
+
position: Vector([17, 3]),
242
242
+
},
243
243
+
ShipState {
244
244
+
facing: Vector([0, -1]),
245
245
+
position: Vector([17, -8]),
246
246
+
},
247
247
+
],
248
248
+
);
249
249
+
}
250
250
+
251
251
+
#[test]
252
252
+
fn final_ship_state_after_example_instructions() {
253
253
+
assert_eq!(
254
254
+
final_state::<ShipState>(EXAMPLE_INSTRUCTIONS),
255
255
+
ShipState {
256
256
+
facing: Vector([0, -1]),
257
257
+
position: Vector([17, -8])
258
258
+
}
259
259
+
)
260
260
+
}
261
261
+
262
262
+
#[test]
263
263
+
fn intermediate_waypoint_states_in_example() {
264
264
+
assert_equal(
265
265
+
EXAMPLE_INSTRUCTIONS
266
266
+
.iter()
267
267
+
.scan(WaypointState::default(), |state, &instruction| {
268
268
+
state.execute(instruction);
269
269
+
Some(state.clone())
270
270
+
}),
271
271
+
[
272
272
+
WaypointState {
273
273
+
waypoint: Vector([10, 1]),
274
274
+
position: Vector([100, 10]),
275
275
+
},
276
276
+
WaypointState {
277
277
+
waypoint: Vector([10, 4]),
278
278
+
position: Vector([100, 10]),
279
279
+
},
280
280
+
WaypointState {
281
281
+
waypoint: Vector([10, 4]),
282
282
+
position: Vector([170, 38]),
283
283
+
},
284
284
+
WaypointState {
285
285
+
waypoint: Vector([4, -10]),
286
286
+
position: Vector([170, 38]),
287
287
+
},
288
288
+
WaypointState {
289
289
+
waypoint: Vector([4, -10]),
290
290
+
position: Vector([214, -72]),
291
291
+
},
292
292
+
],
293
293
+
);
294
294
+
}
295
295
+
296
296
+
#[test]
297
297
+
fn final_waypoint_state_after_example_instructions() {
298
298
+
assert_eq!(
299
299
+
final_state::<WaypointState>(EXAMPLE_INSTRUCTIONS),
300
300
+
WaypointState {
301
301
+
waypoint: Vector([4, -10]),
302
302
+
position: Vector([214, -72]),
303
303
+
}
304
304
+
)
305
305
+
}
306
306
+
}
+2
-2
aoc_2020/src/main.rs
reviewed
···
11
11
mod day09;
12
12
mod day10;
13
13
mod day11;
14
14
-
// mod day12;
14
14
+
mod day12;
15
15
// mod day13;
16
16
// mod day14;
17
17
// mod day15;
···
42
42
door!(2020-12-09 ~> day09),
43
43
door!(2020-12-10 ~> day10),
44
44
door!(2020-12-11 ~> day11),
45
45
-
// door!(2020-12-12 ~> day12),
45
45
+
door!(2020-12-12 ~> day12),
46
46
// door!(2020-12-13 ~> day13),
47
47
// door!(2020-12-14 ~> day14),
48
48
// door!(2020-12-15 ~> day15),