This repository has been archived by the owner on Jan 2, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
day17.nim
162 lines (137 loc) · 5.84 KB
/
day17.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import sequtils, strformat, strutils, tables
type
Point = tuple[x,y: int]
GroundType = enum
gtSand, gtClay, gtStillWater, gtFlowingWater
var
grid = newTable[Point, GroundType]()
miny = int.high
maxy = int.low
func down(point: Point): Point =
(point.x, point.y + 1)
func isAt(grid: TableRef[Point, GroundType], gtype: GroundType, point: Point): bool =
grid.getOrDefault(point, gtSand) == gtype
func supportsWater(grid: TableRef[Point, GroundType], point: Point): bool =
let ground = grid.getOrDefault(point, gtSand)
ground == gtClay or ground == gtStillWater
func countWater(grid: TableRef[Point, GroundType]): tuple[all,still: int] =
for val in grid.values:
if val == gtFlowingWater:
result.all.inc
if val == gtStillWater:
result.all.inc
result.still.inc
# Debug utility to print the grid nicely. Requires a small terminal font size.
proc print(grid: TableRef[Point, GroundType]) =
for y in miny-1..maxy+1:
var line = fmt "{y:4} "
for x in 300..700:
case grid.getOrDefault((x, y), gtSand):
of gtSand: line &= " "
of gtClay: line &= "█"
of gtStillWater: line &= "~"
of gtFlowingWater: line &= "|"
echo line
echo ""
# These all call each other so we have to forward declare them
proc flowDown(grid: TableRef[Point, GroundType], point: Point): bool
proc flowOut(grid: TableRef[Point, GroundType], point: Point): bool
proc findEdge(grid: TableRef[Point, GroundType], point: Point, direction: int): tuple[enclosed: bool, last: int]
# flowDown() is responsible for recursively following a stream of water
# downwards until it hits something or surpasses the maximum y extent.
#
# The proc returns `true` if the downward flow has spread out (e.g.
# has filled in the bottom row of a "U" shaped area). If this happens
# the caller will also attempt to flow out to fill the next row.
proc flowDown(grid: TableRef[Point, GroundType], point: Point): bool =
let
next = point.down
cell = grid.getOrDefault(next, gtSand)
if point.y > maxy:
return false
if cell == gtSand:
# There's nothing below us, keep flowing. Check to see if the flow
# below is fills out, and if it does flow out on this row too;
# otherwise, this is just a normal downward flow and we return false
# as our parents don't need to flow out.
if grid.flowDown(next):
return grid.flowOut(point)
else:
grid[point] = gtFlowingWater
return false
elif cell == gtClay or cell == gtStillWater:
# We've reached the bottom of a hole (or the existing water
# level within a complex shape), flow outwards
return grid.flowOut(point)
elif cell == gtFlowingWater:
# We've encountered some flowing water (i.e. we have a parallel
# stream that has already flowed out below us). There's no need
# to recheck anything, just join up with the other flow.
grid[point] = gtFlowingWater
return false
# flowOut() is responsible for expanding a flow of water horizontally
# after it hits something. It scans left and right to find the maximum
# extents, and whether or not they're enclosed.
#
# If both sides are enclosed then all the cells inbetween fill with
# still water, and we return true so that the row above us flows out.
#
# Sides that aren't enclosed are flowed down. In complex shapes these
# may fill up areas below them. Where either side fills up, we
# recursively call ourselves to deal with the new situation.
#
# Finally, in other cases the cells inbetween the extents become
# flowing water and we return false.
proc flowOut(grid: TableRef[Point, GroundType], point: Point): bool =
let
left = grid.findEdge(point, -1)
right = grid.findEdge(point, 1)
if left.enclosed and right.enclosed:
# Both sides are enclosed, just fill'er up and make our
# parent recalculate.
for x in left.last..right.last:
grid[(x, point.y)] = gtStillWater
return true
var leftFilled, rightFilled: bool
if not left.enclosed:
leftFilled = grid.flowDown((left.last, point.y + 1))
if not right.enclosed:
rightFilled = grid.flowDown((right.last, point.y + 1))
if leftFilled or rightFilled:
# We've filled in some holes and now our extents are no longer
# valid. Recursively call ourselves to figure out what's what.
return flowOut(grid, point)
else:
# We didn't fill anything, so this is the layer of flowing
# water on top of a platform.
for x in left.last..right.last:
grid[(x, point.y)] = gtFlowingWater
return false
proc findEdge(grid: TableRef[Point, GroundType], point: Point, direction: int): tuple[enclosed: bool, last: int] =
var x = point.x
while grid.supportsWater((x, point.y + 1)) and not grid.isAt(gtClay, (x + direction, point.y)):
x += direction
result.last = x
result.enclosed = grid.getOrDefault((x + direction, point.y), gtSand) == gtClay
for line in readFile("data/17.txt").strip.splitlines:
let
parts = line.split(", ")
first = parts[0].substr(2).parseInt
numbers = parts[1].substr(2).split("..")
isX = parts[1][0] == 'x'
for i in numbers[0].parseInt..numbers[1].parseInt:
var x,y: int
if isX:
x = i
y = first
else:
x = first
y = i
grid[(x, y)] = gtClay
miny = min(miny, y)
maxy = max(maxy, y)
discard grid.flowDown((500, miny))
# grid.print()
let result = grid.countWater
echo result.all
echo result.still