1
+ const isLow = ( val , col , row , rows ) => {
2
+ // Collect points in each cardinal direction
3
+ const points = [ ]
4
+ // TODO: If supporting diagonal checks, use this logic instead to loop
5
+ // for (let x = -1; x <= 1; x++) {
6
+ // for (let y = -1; y <= 1; y++) {
7
+ // if(x != 0 && y != 0)
8
+ // if(rows[row + y] && rows[row + y][col + x]
9
+ // }
10
+ // }
11
+ if ( rows [ row - 1 ] && rows [ row - 1 ] [ col ] ) { points . push ( parseInt ( rows [ row - 1 ] [ col ] ) ) }
12
+ if ( rows [ row + 1 ] && rows [ row + 1 ] [ col ] ) { points . push ( parseInt ( rows [ row + 1 ] [ col ] ) ) }
13
+ if ( rows [ row ] && rows [ row ] [ col - 1 ] ) { points . push ( parseInt ( rows [ row ] [ col - 1 ] ) ) }
14
+ if ( rows [ row ] && rows [ row ] [ col + 1 ] ) { points . push ( parseInt ( rows [ row ] [ col + 1 ] ) ) }
15
+
16
+ // NOTE - if the value is the same as a neighbor,
17
+ // that isn't counted as a low (even though together, they can be a low)
18
+ // ... this might be a concern for part 2 ....
19
+ return ( val < Math . min ( ...points ) ) // value should be lower than any other points
20
+ }
21
+
1
22
const findLocalLows = ( data ) => {
2
23
const lows = [ ]
3
24
const rows = data . split ( '\n' )
4
25
let checked = 0
5
26
6
- const isLow = ( val , col , row ) => {
7
- // Collect points in each cardinal direction
8
- const points = [ ]
9
- // TODO: If supporting diagonal checks, use this logic instead to loop
10
- // for (let x = -1; x <= 1; x++) {
11
- // for (let y = -1; y <= 1; y++) {
12
- // if(x != 0 && y != 0)
13
- // if(rows[row + y] && rows[row + y][col + x]
14
- // }
15
- // }
16
- if ( rows [ row - 1 ] && rows [ row - 1 ] [ col ] ) { points . push ( parseInt ( rows [ row - 1 ] [ col ] ) ) }
17
- if ( rows [ row + 1 ] && rows [ row + 1 ] [ col ] ) { points . push ( parseInt ( rows [ row + 1 ] [ col ] ) ) }
18
- if ( rows [ row ] && rows [ row ] [ col - 1 ] ) { points . push ( parseInt ( rows [ row ] [ col - 1 ] ) ) }
19
- if ( rows [ row ] && rows [ row ] [ col + 1 ] ) { points . push ( parseInt ( rows [ row ] [ col + 1 ] ) ) }
20
-
21
- // NOTE - if the value is the same as a neighbor,
22
- // that isn't counted as a low (even though together, they can be a low)
23
- // ... this might be a concern for part 2 ....
24
- return ( val < Math . min ( ...points ) ) // value should be lower than any other points
25
- }
26
-
27
27
rows . forEach ( ( row , rowIdx ) => {
28
28
for ( let c = 0 ; c < row . length ; c ++ ) {
29
29
const cell = parseInt ( row [ c ] )
30
- if ( isLow ( cell , c , rowIdx ) ) {
30
+ if ( isLow ( cell , c , rowIdx , rows ) ) {
31
31
lows . push ( cell )
32
32
console . debug ( `Found low at ${ c } ,${ rowIdx } : ${ cell } ` )
33
33
}
@@ -39,6 +39,118 @@ const findLocalLows = (data) => {
39
39
return lows
40
40
}
41
41
42
+ const flow = ( col , row , map , data , source ) => {
43
+ // Don't test invalid points
44
+ if ( col < 0 || col >= map . coords [ 0 ] . length ) {
45
+ console . debug ( `${ col } ,${ row } is out of bounds` )
46
+ // Exceeds map horizontally
47
+ return {
48
+ map,
49
+ result : false
50
+ }
51
+ }
52
+ if ( row < 0 || row >= map . coords . length ) {
53
+ console . debug ( `${ col } ,${ row } is out of bounds` )
54
+ // Exceeds map vertically
55
+ return {
56
+ map,
57
+ result : false
58
+ }
59
+ }
60
+
61
+ // If the point is a peak, register and don't continue
62
+ if ( parseInt ( data [ row ] [ col ] ) === 9 ) {
63
+ console . debug ( `${ col } ,${ row } is a peak.` )
64
+ // Peaks aren't part of basins
65
+ map . coords [ row ] [ col ] = 'p'
66
+ return {
67
+ map,
68
+ result : false
69
+ }
70
+ }
71
+
72
+ // If the point is higher than the source, we can't drain
73
+ // BIG ASSUMPTION here about equal-height points
74
+ if ( data [ row ] [ col ] >= source ) {
75
+ console . debug ( `${ col } ,${ row } is higher (${ data [ row ] [ col ] } >= ${ source } ). Water can't flow uphill.` )
76
+ return {
77
+ map,
78
+ result : false
79
+ }
80
+ }
81
+
82
+ // If the point already mapped to a basin, don't recalculate its flow
83
+ if ( map . coords [ row ] && map . coords [ row ] [ col ] ) {
84
+ console . debug ( `${ col } ,${ row } is already known to be in basin ${ map . coords [ row ] [ col ] } ` )
85
+ return {
86
+ map,
87
+ result : map . coords [ row ] [ col ]
88
+ }
89
+ }
90
+
91
+ // If we've reached a low point, stop tracing
92
+ if ( isLow ( data [ row ] [ col ] , col , row , data ) ) {
93
+ console . debug ( `${ col } ,${ row } is a low point in basin.` )
94
+ // register a basin with an area of 1
95
+ map . basins . push ( 1 )
96
+ // mark the low point to the basin
97
+ map . coords [ row ] [ col ] = map . basins . length - 1
98
+ console . debug ( `registered basin ${ map . basins . length - 1 } ` )
99
+ return {
100
+ map,
101
+ result : map . coords [ row ] [ col ]
102
+ }
103
+ // HUGE ASSUMPTION that each basin only has 1 low point
104
+ }
105
+
106
+ console . debug ( `checking where point ${ col } ,${ row } drains to` )
107
+
108
+ // Check the next points in each cardinal direction
109
+ const drains = [ ]
110
+ let result = false
111
+ result = flow ( col + 1 , row , map , data , data [ row ] [ col ] ) // right
112
+ map = result . map
113
+ drains . push ( result . result )
114
+ result = flow ( col - 1 , row , map , data , data [ row ] [ col ] ) // left
115
+ map = result . map
116
+ drains . push ( result . result )
117
+ result = flow ( col , row - 1 , map , data , data [ row ] [ col ] ) // up
118
+ map = result . map
119
+ drains . push ( result . result )
120
+ result = flow ( col , row + 1 , map , data , data [ row ] [ col ] ) // down
121
+ map = result . map
122
+ drains . push ( result . result )
123
+
124
+ const results = drains . filter ( ( c ) => c !== false )
125
+ if ( results . length > 1 ) {
126
+ console . warn ( 'Point has more than one possilbe drain.' )
127
+ const uniqueDrains = [ ...new Set ( results ) ]
128
+ if ( uniqueDrains . length > 1 ) {
129
+ console . debug ( drains )
130
+ throw new Error ( 'Point drains into multiple drains. Data might be bad.' )
131
+ }
132
+ // Otherwise, all drains go to the same basin, so that's the same as having 1 drain
133
+ }
134
+ if ( results . length === 0 ) {
135
+ console . debug ( drains )
136
+ throw new Error ( 'Point is not the low, but has no drains. Data might be bad.' )
137
+ }
138
+
139
+ const basin = parseInt ( results [ 0 ] )
140
+
141
+ // Mark the point as belonging to the basin it drains into
142
+ map . coords [ row ] [ col ] = basin
143
+ // Track the area of the basin so we don't have to recalculate it later
144
+ map . basins [ basin ] ++
145
+
146
+ // return the findings recursively
147
+ return {
148
+ map,
149
+ result : map . coords [ row ] [ col ]
150
+ }
151
+ }
152
+
42
153
module . exports = {
43
- findLocalLows
154
+ findLocalLows,
155
+ flow
44
156
}
0 commit comments