Browse Source

Day 17

master
Chris Smith 5 years ago
parent
commit
724e0b9c46
3 changed files with 1959 additions and 0 deletions
  1. 2
    0
      answers/17.txt
  2. 1794
    0
      data/17.txt
  3. 163
    0
      day17.nim

+ 2
- 0
answers/17.txt View File

@@ -0,0 +1,2 @@
1
+33242
2
+27256

+ 1794
- 0
data/17.txt
File diff suppressed because it is too large
View File


+ 163
- 0
day17.nim View File

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