Skip to content

Commit 12bda58

Browse files
committed
Document Year 2021 Day 20
1 parent 3ac96e9 commit 12bda58

File tree

1 file changed

+32
-10
lines changed

1 file changed

+32
-10
lines changed

Diff for: src/year2021/day20.rs

+32-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1+
//! # Trench Map
2+
//!
3+
//! This is a cellular automata problem, similar to Conway's Game of Life, except that the rules
4+
//! are encoded in the enhancement algorithm string, instead of being statically specified. Each
5+
//! round the initial square area of cells expands by at most one in each direction, so we can store
6+
//! the cell in a fixed size array with enough space on either side to expand into.
7+
//!
8+
//! The interesting nuance is handling the edge cells when all 9 cells are empty (index 0) or all
9+
//! 9 cell are active (index 511). The sample data encodes a blank cell in both scenarios.
10+
//! My input encoded an active cell for index 0 and a blank cell for index 511, meaning that each
11+
//! turn the edge cells toggle from set to unset.
12+
//!
13+
//! The algorithm keeps track of the bounds of the expanding square and supplies a `default` value,
14+
//! that in the example case is always zero, but in the real data toggles between zero and one.
115
pub struct Input {
216
size: usize,
317
algorithm: [u8; 512],
418
pixels: [u8; 40_000],
519
}
620

721
pub fn parse(input: &str) -> Input {
8-
fn convert(b: u8) -> u8 {
9-
match b {
10-
b'#' => 1,
11-
b'.' => 0,
12-
_ => unreachable!(),
13-
}
14-
}
15-
16-
let bits: Vec<Vec<_>> = input.lines().map(|line| line.bytes().map(convert).collect()).collect();
17-
22+
// `#` is odd and `.` is even so we can convert to one or zero by bitwise AND with 1.
23+
let bits: Vec<Vec<_>> =
24+
input.lines().map(|line| line.bytes().map(|b| b & 1).collect()).collect();
1825
let size = bits.len() - 2;
1926
let algorithm = bits[0][..512].try_into().unwrap();
2027

28+
// Offset the initial square by 50 cells in both dimensions.
29+
// The square expands by at most one in each step so this is enough room to stay within bounds.
2130
let mut pixels = [0; 40_000];
2231
for (i, row) in bits[2..].iter().enumerate() {
2332
let start = (i + 50) * 200 + 50;
@@ -47,6 +56,8 @@ fn enhance(input: &Input, steps: usize) -> usize {
4756

4857
for _ in 0..steps {
4958
for y in (start - 1)..(end + 1) {
59+
// If the pixel is within current bounds then return it, or else use the `default`
60+
// edge value specified by the enhancement algorithm.
5061
let helper = |sx, sy, shift| {
5162
let result = if sx < end && sy >= start && sy < end {
5263
pixels[(sy * 200 + sx) as usize] as usize
@@ -56,9 +67,17 @@ fn enhance(input: &Input, steps: usize) -> usize {
5667
result << shift
5768
};
5869

70+
// If the edge pixels are 1 then the inital edge will look like
71+
// [##a]
72+
// [##b]
73+
// [##c]
74+
// or 11a11b11c when encoded as an index.
5975
let mut index = if default == 1 { 0b11011011 } else { 0b00000000 };
6076

6177
for x in (start - 1)..(end + 1) {
78+
// Keeps a sliding window of the index, updated as we evaluate the row from
79+
// left to right. Shift the index left by one each turn, updating the values from
80+
// the three new rightmost pixels entering the window.
6281
index = ((index << 1) & 0b110110110)
6382
+ helper(x + 1, y - 1, 6)
6483
+ helper(x + 1, y, 3)
@@ -68,9 +87,12 @@ fn enhance(input: &Input, steps: usize) -> usize {
6887
}
6988
}
7089

90+
// Boundaries expand by one each turn
7191
pixels = next;
7292
start -= 1;
7393
end += 1;
94+
95+
// Calculate the next value for edge pixels beyond the boundary.
7496
if default == 0 {
7597
default = algorithm[0];
7698
} else {

0 commit comments

Comments
 (0)