@@ -2,33 +2,44 @@ package de.ronny_h.aoc.year2018.day17
22
33import de.ronny_h.aoc.AdventOfCode
44import de.ronny_h.aoc.extensions.grids.Coordinates
5+ import de.ronny_h.aoc.year2018.day17.Direction.*
56import java.io.ByteArrayOutputStream
67import java.io.PrintStream
8+ import kotlin.collections.MutableMap.MutableEntry
79import kotlin.math.max
810import kotlin.math.min
911import kotlin.text.Charsets.UTF_8
1012
11- fun main () = ReservoirResearch ().run (0 , 0 )
13+ fun main () = ReservoirResearch ().run (31934 , 0 )
1214
1315class ReservoirResearch : AdventOfCode <Int >(2018 , 17 ) {
1416 override fun part1 (input : List <String >): Int {
15- return 0
17+ val slice = VerticalSliceOfGround (input)
18+ slice.waterFlow(slice.springCoordinates)
19+ return slice.countTilesOfWater()
1620 }
1721
1822 override fun part2 (input : List <String >): Int {
19- return 0
23+ val slice = VerticalSliceOfGround (input)
24+ slice.waterFlow(slice.springCoordinates)
25+ return slice.countTilesOfRestingWater()
2026 }
2127}
2228
29+ private const val clay = ' #'
30+
2331class VerticalSliceOfGround (input : List <String >) {
24- private val clay = ' #'
2532 private val sand = ' .'
2633 private val waterSpring = ' +'
34+ private val waterFalling = ' |'
35+ private val waterResting = ' ~'
36+
37+ val springCoordinates = Coordinates (500 , 0 )
2738
28- private val slice = MapBackedCharGrid (Coordinates ( 500 , 0 ) , sand)
39+ private val slice = MapBackedCharGrid (springCoordinates , sand)
2940
3041 init {
31- slice[Coordinates ( 500 , 0 ) ] = waterSpring
42+ slice[springCoordinates ] = waterSpring
3243 input.forEach {
3344 val (single, range) = it.split(" , " )
3445 if (single.startsWith(' x' )) {
@@ -39,6 +50,70 @@ class VerticalSliceOfGround(input: List<String>) {
3950 }
4051 }
4152
53+ fun waterFlow (spring : Coordinates ) {
54+ // downwards
55+ var current = spring + DOWN
56+ while (slice[current] == sand) {
57+ if (current.y > slice.maxY) {
58+ return
59+ }
60+ slice[current] = waterFalling
61+ current + = DOWN
62+ }
63+ if (slice[current] == waterFalling) {
64+ return
65+ }
66+
67+ // sidewards & upwards
68+ var isOverflowing = false
69+ while (! isOverflowing) {
70+ current + = UP
71+ val (leftmost, isOverflowingLeft) = current.fillToThe(LEFT )
72+ if (slice[current + RIGHT ] == waterResting) {
73+ // a different recursive call has already filled up
74+ return
75+ }
76+ val (rightmost, isOverflowingRight) = current.fillToThe(RIGHT )
77+ isOverflowing = isOverflowingLeft || isOverflowingRight
78+ if (slice[current] != waterResting) {
79+ slice[leftmost.x.. rightmost.x, current.y] = if (isOverflowing) waterFalling else waterResting
80+ }
81+ }
82+ }
83+
84+ /* *
85+ * @return A pair of `the farest Coordinates in [direction] belonging to the current body of water` and
86+ * a Boolean telling if the water is overflowing.
87+ */
88+ private fun Coordinates.fillToThe (direction : Direction ): Pair <Coordinates , Boolean > {
89+ var farestInDirection = this + direction
90+ while (true ) {
91+ if (slice[farestInDirection] in listOf (sand, waterFalling)) {
92+ when (slice[farestInDirection + DOWN ]) {
93+ sand -> {
94+ waterFlow(farestInDirection)
95+ return farestInDirection to true
96+ }
97+
98+ waterFalling -> {
99+ // already been there
100+ return farestInDirection to true
101+ }
102+
103+ clay, waterResting -> {
104+ farestInDirection + = direction
105+ }
106+ }
107+ } else if (slice[farestInDirection] == clay) {
108+ farestInDirection + = direction.opposite()
109+ return farestInDirection to false
110+ }
111+ }
112+ }
113+
114+ fun countTilesOfWater (): Int = slice.count { it in listOf (waterFalling, waterResting) }
115+ fun countTilesOfRestingWater (): Int = slice.count { it == waterResting }
116+
42117 override fun toString (): String = slice.toString()
43118
44119 private fun String.intAfter (delimiter : String ): Int = substringAfter(delimiter).toInt()
@@ -49,15 +124,39 @@ class VerticalSliceOfGround(input: List<String>) {
49124 }
50125}
51126
127+ enum class Direction {
128+ UP , DOWN , LEFT , RIGHT ;
129+
130+ fun opposite () = when (this ) {
131+ UP -> DOWN
132+ DOWN -> UP
133+ LEFT -> RIGHT
134+ RIGHT -> LEFT
135+ }
136+ }
137+
138+ operator fun Coordinates.plus (direction : Direction ) = when (direction) {
139+ UP -> Coordinates (x, y - 1 )
140+ DOWN -> Coordinates (x, y + 1 )
141+ LEFT -> Coordinates (x - 1 , y)
142+ RIGHT -> Coordinates (x + 1 , y)
143+ }
144+
52145class MapBackedCharGrid (center : Coordinates , default : Char ) {
146+ private var minClayY = Int .MAX_VALUE
53147 private var minX = center.x
54148 private var maxX = center.x
55149 private var minY = center.y
56- private var maxY = center.y
150+ var maxY = center.y
151+ private set
57152
58153 private val slice = mutableMapOf<Coordinates , Char >().withDefault { default }
59154
60155 operator fun set (position : Coordinates , value : Char ) {
156+ check(value == clay || slice[position] != clay) { " clay must not be overwritten" }
157+ if (value == clay) {
158+ minClayY = min(minClayY, position.y)
159+ }
61160 minX = min(minX, position.x)
62161 minY = min(minY, position.y)
63162 maxX = max(maxX, position.x)
@@ -93,4 +192,8 @@ class MapBackedCharGrid(center: Coordinates, default: Char) {
93192 }
94193 return out .toString(UTF_8 ).trim()
95194 }
195+
196+ fun count (predicate : (Char ) -> Boolean ): Int =
197+ slice.entries.filter { it.key.y >= minClayY }.map(MutableEntry <Coordinates , Char >::value)
198+ .count(predicate)
96199}
0 commit comments