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.
1
15
pub struct Input {
2
16
size : usize ,
3
17
algorithm : [ u8 ; 512 ] ,
4
18
pixels : [ u8 ; 40_000 ] ,
5
19
}
6
20
7
21
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 ( ) ;
18
25
let size = bits. len ( ) - 2 ;
19
26
let algorithm = bits[ 0 ] [ ..512 ] . try_into ( ) . unwrap ( ) ;
20
27
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.
21
30
let mut pixels = [ 0 ; 40_000 ] ;
22
31
for ( i, row) in bits[ 2 ..] . iter ( ) . enumerate ( ) {
23
32
let start = ( i + 50 ) * 200 + 50 ;
@@ -47,6 +56,8 @@ fn enhance(input: &Input, steps: usize) -> usize {
47
56
48
57
for _ in 0 ..steps {
49
58
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.
50
61
let helper = |sx, sy, shift| {
51
62
let result = if sx < end && sy >= start && sy < end {
52
63
pixels[ ( sy * 200 + sx) as usize ] as usize
@@ -56,9 +67,17 @@ fn enhance(input: &Input, steps: usize) -> usize {
56
67
result << shift
57
68
} ;
58
69
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.
59
75
let mut index = if default == 1 { 0b11011011 } else { 0b00000000 } ;
60
76
61
77
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.
62
81
index = ( ( index << 1 ) & 0b110110110 )
63
82
+ helper ( x + 1 , y - 1 , 6 )
64
83
+ helper ( x + 1 , y, 3 )
@@ -68,9 +87,12 @@ fn enhance(input: &Input, steps: usize) -> usize {
68
87
}
69
88
}
70
89
90
+ // Boundaries expand by one each turn
71
91
pixels = next;
72
92
start -= 1 ;
73
93
end += 1 ;
94
+
95
+ // Calculate the next value for edge pixels beyond the boundary.
74
96
if default == 0 {
75
97
default = algorithm[ 0 ] ;
76
98
} else {
0 commit comments