Skip to content

Commit d44828e

Browse files
committed
Solution 2018-17 (Reservoir Research)
1 parent fc29286 commit d44828e

File tree

2 files changed

+236
-13
lines changed

2 files changed

+236
-13
lines changed

src/main/kotlin/de/ronny_h/aoc/year2018/day17/ReservoirResearch.kt

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,44 @@ package de.ronny_h.aoc.year2018.day17
22

33
import de.ronny_h.aoc.AdventOfCode
44
import de.ronny_h.aoc.extensions.grids.Coordinates
5+
import de.ronny_h.aoc.year2018.day17.Direction.*
56
import java.io.ByteArrayOutputStream
67
import java.io.PrintStream
8+
import kotlin.collections.MutableMap.MutableEntry
79
import kotlin.math.max
810
import kotlin.math.min
911
import kotlin.text.Charsets.UTF_8
1012

11-
fun main() = ReservoirResearch().run(0, 0)
13+
fun main() = ReservoirResearch().run(31934, 0)
1214

1315
class 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+
2331
class 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+
52145
class 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
}

src/test/kotlin/de/ronny_h/aoc/year2018/day17/ReservoirResearchTest.kt

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,133 @@ class ReservoirResearchTest : StringSpec({
3737
VerticalSliceOfGround(input).toString() shouldBe expectedSlice
3838
}
3939

40-
"part 1" {
41-
val input = listOf("")
42-
ReservoirResearch().part1(input) shouldBe 0
40+
"let the water flow according to the puzzle's example" {
41+
val expectedSlice = """
42+
......+.......
43+
......|.....#.
44+
.#..#||||...#.
45+
.#..#~~#|.....
46+
.#..#~~#|.....
47+
.#~~~~~#|.....
48+
.#~~~~~#|.....
49+
.#######|.....
50+
........|.....
51+
...|||||||||..
52+
...|#~~~~~#|..
53+
...|#~~~~~#|..
54+
...|#~~~~~#|..
55+
...|#######|..
56+
""".trimIndent()
57+
58+
val slice = VerticalSliceOfGround(input)
59+
slice.waterFlow(slice.springCoordinates)
60+
slice.toString() shouldBe expectedSlice
61+
}
62+
63+
"overflow within a bin" {
64+
val input = """
65+
x=498, y=2..6
66+
x=505, y=2..6
67+
x=502, y=3..4
68+
y=6, x=498..505
69+
""".asList()
70+
val expectedInit = """
71+
...+......
72+
..........
73+
.#......#.
74+
.#...#..#.
75+
.#...#..#.
76+
.#......#.
77+
.########.
78+
""".trimIndent()
79+
val expectedFlooded = """
80+
....+.......
81+
.||||||||||.
82+
.|#~~~~~~#|.
83+
.|#~~~#~~#|.
84+
.|#~~~#~~#|.
85+
.|#~~~~~~#|.
86+
.|########|.
87+
""".trimIndent()
88+
89+
val slice = VerticalSliceOfGround(input)
90+
slice.toString() shouldBe expectedInit
91+
slice.waterFlow(slice.springCoordinates)
92+
slice.toString() shouldBe expectedFlooded
93+
}
94+
95+
"plateau within a bin" {
96+
val input = """
97+
x=496, y=2..6
98+
x=503, y=2..6
99+
x=499, y=3..4
100+
x=500, y=3..4
101+
y=6, x=496..503
102+
""".asList()
103+
val expectedInit = """
104+
.....+....
105+
..........
106+
.#......#.
107+
.#..##..#.
108+
.#..##..#.
109+
.#......#.
110+
.########.
111+
""".trimIndent()
112+
val expectedFlooded = """
113+
......+.....
114+
.||||||||||.
115+
.|#~~~~~~#|.
116+
.|#~~##~~#|.
117+
.|#~~##~~#|.
118+
.|#~~~~~~#|.
119+
.|########|.
120+
""".trimIndent()
121+
122+
val slice = VerticalSliceOfGround(input)
123+
slice.toString() shouldBe expectedInit
124+
slice.waterFlow(slice.springCoordinates)
125+
slice.toString() shouldBe expectedFlooded
126+
}
127+
128+
"two springs into one bin" {
129+
val input = """
130+
x=500, y=2..2
131+
x=499, y=5..7
132+
x=503, y=3..7
133+
y=7, x=499..503
134+
""".asList()
135+
val expectedInit = """
136+
..+....
137+
.......
138+
..#....
139+
.....#.
140+
.....#.
141+
.#...#.
142+
.#...#.
143+
.#####.
144+
""".trimIndent()
145+
val expectedFlooded = """
146+
...+....
147+
..|||...
148+
..|#|...
149+
..|.|.#.
150+
.|||||#.
151+
.|#~~~#.
152+
.|#~~~#.
153+
.|#####.
154+
""".trimIndent()
155+
156+
val slice = VerticalSliceOfGround(input)
157+
slice.toString() shouldBe expectedInit
158+
slice.waterFlow(slice.springCoordinates)
159+
slice.toString() shouldBe expectedFlooded
160+
}
161+
162+
"part 1: the number of tiles the water can reach" {
163+
ReservoirResearch().part1(input) shouldBe 57
43164
}
44165

45-
"part 2" {
46-
val input = listOf("")
47-
ReservoirResearch().part2(input) shouldBe 0
166+
"part 2: the number of tiles with resting water" {
167+
ReservoirResearch().part2(input) shouldBe 29
48168
}
49169
})

0 commit comments

Comments
 (0)