Skip to content

Commit ef65a05

Browse files
committed
Extend GridBackand with width, height and min/max properties
... and use the MapGridBackend in ReservoirResearch.
1 parent 70e8c2a commit ef65a05

File tree

20 files changed

+245
-137
lines changed

20 files changed

+245
-137
lines changed

src/main/kotlin/de/ronny_h/aoc/extensions/grids/Grid.kt

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,24 @@ import java.nio.charset.StandardCharsets
1717
* @param fallbackElement Used for out of bound positions. Defaults to [nullElement].
1818
*/
1919
abstract class Grid<T>(
20-
val height: Int,
21-
val width: Int,
20+
height: Int, width: Int,
2221
val nullElement: T,
2322
private val fallbackElement: T = nullElement,
23+
protected val grid: GridBackend<T> = ListGridBackend(width, height, nullElement),
2424
) {
25-
26-
private val grid: GridBackend<T> = ListGridBackend(width, height, nullElement)
25+
val height: Int get() = grid.height
26+
val width: Int get() = grid.width
2727

2828
/**
2929
* A function that maps each `Char` that may occur in the `input: List<String>` to a value of type [T].
3030
*/
3131
abstract fun Char.toElementType(): T
3232

33+
/**
34+
* Overwrite in sub-classes to perform actions before the [value] is set at [position].
35+
*/
36+
open fun preSet(position: Coordinates, value: T) {}
37+
3338
/**
3439
* Constructs a Grid with the specified dimensions filled with the [nullElement] as default value and all
3540
* coordinates in [overrides] set to [overrideElement].
@@ -38,9 +43,9 @@ abstract class Grid<T>(
3843
height: Int,
3944
width: Int,
4045
nullElement: T,
41-
overrideElement: T = nullElement,
46+
overrideElement: T,
4247
overrides: List<Coordinates> = emptyList()
43-
) : this(height, width, nullElement, overrideElement) {
48+
) : this(height, width, nullElement, fallbackElement = overrideElement) {
4449
overrides.forEach { grid[it] = overrideElement }
4550
}
4651

@@ -72,14 +77,28 @@ abstract class Grid<T>(
7277
operator fun get(x: Int, y: Int): T = grid.getOrNull(x, y) ?: fallbackElement
7378

7479
operator fun set(x: Int, y: Int, value: T) {
80+
preSet(Coordinates(x, y), value)
7581
grid[x, y] = value
7682
}
7783

78-
fun getAt(position: Coordinates) = grid.getOrNull(position) ?: fallbackElement
79-
fun setAt(position: Coordinates, element: T) {
84+
operator fun get(position: Coordinates) = grid.getOrNull(position) ?: fallbackElement
85+
operator fun set(position: Coordinates, element: T) {
86+
preSet(position, element)
8087
grid[position] = element
8188
}
8289

90+
operator fun set(x: Int, yRange: IntRange, value: T) {
91+
for (y in yRange) {
92+
this[Coordinates(x, y)] = value
93+
}
94+
}
95+
96+
operator fun set(xRange: IntRange, y: Int, value: T) {
97+
for (x in xRange) {
98+
this[Coordinates(x, y)] = value
99+
}
100+
}
101+
83102
/**
84103
* @return a view of the portion of this Grid between the specified [x], [y] (inclusive) and [x] + [width], [y] + [height] (exclusive).
85104
* The returned list of lists is backed by this Grid, so non-structural changes in the returned list are reflected
@@ -117,10 +136,11 @@ abstract class Grid<T>(
117136
fun toString(
118137
overrides: Set<Coordinates> = setOf(),
119138
overrideChar: Char = '#',
139+
padding: Int = 0,
120140
): String {
121141
val out = ByteArrayOutputStream()
122142
PrintStream(out, true, StandardCharsets.UTF_8).use {
123-
printGrid(it, overrides = overrides, overrideChar = overrideChar)
143+
printGrid(it, overrides = overrides, overrideChar = overrideChar, padding = padding)
124144
}
125145
return out.toString().trim()
126146
}
@@ -148,8 +168,11 @@ abstract class Grid<T>(
148168
highlightPosition: Coordinates? = null,
149169
highlightDirection: Direction? = null,
150170
path: Map<Coordinates, Char> = mapOf(),
171+
padding: Int = 0,
151172
) {
173+
val paddingString = "$fallbackElement".repeat(padding)
152174
forEachCoordinates { position, element ->
175+
if (position.x == grid.minX) writer.print(paddingString)
153176
if (highlightPosition == position && highlightDirection != null) {
154177
writer.print(highlightDirection.asChar())
155178
} else if (path.contains(position)) {
@@ -159,7 +182,7 @@ abstract class Grid<T>(
159182
} else {
160183
writer.print(element)
161184
}
162-
if (position.x == width - 1) writer.print('\n')
185+
if (position.x == grid.maxX) writer.print("$paddingString\n")
163186
}.last()
164187
}
165188

@@ -173,7 +196,7 @@ abstract class Grid<T>(
173196
fun shortestPaths(
174197
start: Coordinates,
175198
goal: Coordinates,
176-
isVisitable: (Coordinates) -> Boolean = { getAt(it) != nullElement }
199+
isVisitable: (Coordinates) -> Boolean = { get(it) != nullElement }
177200
): List<ShortestPath<Coordinates>> {
178201
val neighbours: (Coordinates) -> List<Coordinates> = { position ->
179202
position.neighbours().filter(isVisitable)
@@ -200,7 +223,7 @@ abstract class Grid<T>(
200223
if (element == nullElement) null else position
201224
}.filterNotNull().toList(),
202225
edges = { from, to ->
203-
if (to in from.neighbours().filter { !isObstacle(getAt(it)) }) 1 else null
226+
if (to in from.neighbours().filter { !isObstacle(get(it)) }) 1 else null
204227
}
205228
)
206229
return dijkstraShortestPaths(graph, start, goals, stopAfterMinimalPathsAreFound)
@@ -235,7 +258,7 @@ abstract class Grid<T>(
235258
regionsCoordinates: MutableList<Coordinates> = mutableListOf(position),
236259
): List<Coordinates> {
237260
position.neighbours().forEach { coordinates ->
238-
if (getAt(coordinates) == value && visited.add(coordinates)) {
261+
if (get(coordinates) == value && visited.add(coordinates)) {
239262
regionsCoordinates.add(coordinates)
240263
collectRegionAt(coordinates, value, visited, regionsCoordinates)
241264
}

src/main/kotlin/de/ronny_h/aoc/extensions/grids/GridBackend.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,22 @@ interface GridBackend<T> {
2525
fun subGridAt(x: Int, y: Int, width: Int, height: Int = width): List<List<T>>
2626

2727
fun <R> mapToSequence(transform: (x: Int, y: Int) -> R): Sequence<R>
28+
29+
val width: Int
30+
val height: Int
31+
val minX: Int
32+
val minY: Int
33+
val maxX: Int
34+
val maxY: Int
35+
val entries: Set<Pair<Coordinates, T>>
2836
}
2937

30-
class ListGridBackend<T>(width: Int, height: Int, nullElement: T) : GridBackend<T> {
38+
class ListGridBackend<T>(override val width: Int, override val height: Int, nullElement: T) : GridBackend<T> {
39+
override val minX: Int get() = 0
40+
override val minY: Int get() = 0
41+
override val maxX: Int get() = width - 1
42+
override val maxY: Int get() = height - 1
43+
3144
private val grid: MutableList<MutableList<T>> = MutableList(height) { MutableList(width) { nullElement } }
3245

3346
override fun get(x: Int, y: Int) = grid[y][x]
@@ -60,13 +73,24 @@ class ListGridBackend<T>(width: Int, height: Int, nullElement: T) : GridBackend<
6073
}
6174
}
6275
}
76+
77+
override val entries: Set<Pair<Coordinates, T>>
78+
get() = mapToSequence { x, y ->
79+
Coordinates(x, y) to get(x, y)
80+
}.toSet()
6381
}
6482

6583
class MapGridBackend<T>(defaultValue: T) : GridBackend<T> {
66-
private var minX = Int.MAX_VALUE
67-
private var maxX = Int.MIN_VALUE
68-
private var minY = Int.MAX_VALUE
69-
private var maxY = Int.MIN_VALUE
84+
override val height: Int get() = maxY + 1 - minY
85+
override val width: Int get() = maxX + 1 - minX
86+
override var minX = Int.MAX_VALUE
87+
private set
88+
override var maxX = Int.MIN_VALUE
89+
private set
90+
override var minY = Int.MAX_VALUE
91+
private set
92+
override var maxY = Int.MIN_VALUE
93+
private set
7094

7195
private val grid = mutableMapOf<Coordinates, T>().withDefault { defaultValue }
7296

@@ -111,4 +135,6 @@ class MapGridBackend<T>(defaultValue: T) : GridBackend<T> {
111135
}
112136
}
113137
}
138+
139+
override val entries: Set<Pair<Coordinates, T>> get() = grid.entries.map { it.key to it.value }.toSet()
114140
}

src/main/kotlin/de/ronny_h/aoc/year2015/day18/LikeAGIFForYourYard.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ class LikeAGIFForYourYard : AdventOfCode<Int>(2015, 18) {
2929
private fun GameOfLightGrid.animateStep(): GameOfLightGrid {
3030
val nextGeneration = GameOfLightGrid(this.height, this.width)
3131
forEachCoordinates { position, light ->
32-
val neighboursOn = position.neighboursIncludingDiagonals().count { getAt(it) == on }
32+
val neighboursOn = position.neighboursIncludingDiagonals().count { get(it) == on }
3333
val nextState = if (neighboursOn == 3 || light == on && neighboursOn == 2) on else off
34-
nextGeneration.setAt(position, nextState)
34+
nextGeneration[position] = nextState
3535
}.last()
3636
return nextGeneration
3737
}
3838

3939
private fun GameOfLightGrid.withCornersStuckOn(): GameOfLightGrid {
40-
setAt(Coordinates(0, 0), on)
41-
setAt(Coordinates(width - 1, 0), on)
42-
setAt(Coordinates(0, height - 1), on)
43-
setAt(Coordinates(width - 1, height - 1), on)
40+
set(Coordinates(0, 0), on)
41+
set(Coordinates(width - 1, 0), on)
42+
set(Coordinates(0, height - 1), on)
43+
set(Coordinates(width - 1, height - 1), on)
4444
return this
4545
}
4646
}

src/main/kotlin/de/ronny_h/aoc/year2017/day19/ASeriesOfTubes.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ class RoutingDiagram(input: List<String>) : SimpleCharGrid(input, ' ') {
2525
var direction = SOUTH
2626
var steps = 0
2727

28-
while (getAt(position) != nullElement) {
29-
if (getAt(position).isLetter()) {
30-
letters.add(getAt(position))
28+
while (get(position) != nullElement) {
29+
if (get(position).isLetter()) {
30+
letters.add(get(position))
3131
}
3232

3333
position += direction
3434
steps++
3535

36-
if (getAt(position) == '+') {
36+
if (get(position) == '+') {
3737
val turnRight = direction.turnRight()
38-
val charAtRight = getAt(position + turnRight)
38+
val charAtRight = get(position + turnRight)
3939
direction = if (charAtRight == turnRight.char() || charAtRight.isLetter()) {
4040
turnRight
4141
} else {

src/main/kotlin/de/ronny_h/aoc/year2018/day13/MineCartMadness.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,22 @@ class Track(input: List<String>) : SimpleCharGrid(input, ' ') {
2929
when (element) {
3030
'<' -> {
3131
add(Cart(position, WEST))
32-
setAt(position, '-')
32+
set(position, '-')
3333
}
3434

3535
'>' -> {
3636
add(Cart(position, EAST))
37-
setAt(position, '-')
37+
set(position, '-')
3838
}
3939

4040
'^' -> {
4141
add(Cart(position, NORTH))
42-
setAt(position, '|')
42+
set(position, '|')
4343
}
4444

4545
'v' -> {
4646
add(Cart(position, SOUTH))
47-
setAt(position, '|')
47+
set(position, '|')
4848
}
4949
}
5050
}.last()
@@ -84,7 +84,7 @@ class Track(input: List<String>) : SimpleCharGrid(input, ' ') {
8484

8585
private fun Cart.move() {
8686
position += direction
87-
direction = when (getAt(position)) {
87+
direction = when (get(position)) {
8888
'+' -> direction.turn(nextTurn())
8989
'/' -> if (direction == NORTH || direction == SOUTH) direction.turnRight() else direction.turnLeft()
9090
'\\' -> if (direction == NORTH || direction == SOUTH) direction.turnLeft() else direction.turnRight()

src/main/kotlin/de/ronny_h/aoc/year2018/day15/BeverageBandits.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class CombatArea(input: List<String>, val elfAttackPower: Int = 3) : SimpleCharG
107107
}
108108

109109
val targetsInRange = targets.flatMap { target ->
110-
target.position.neighbours().filter { getAt(it) == cavern }
110+
target.position.neighbours().filter { get(it) == cavern }
111111
}
112112
val shortestPaths = shortestPaths(
113113
start = position,
@@ -153,14 +153,14 @@ class CombatArea(input: List<String>, val elfAttackPower: Int = 3) : SimpleCharG
153153

154154
private fun Player.moveTo(newPosition: Coordinates) {
155155
logger.debug { " $this moves to $newPosition" }
156-
setAt(position, cavern)
157-
setAt(newPosition, type.char)
156+
set(position, cavern)
157+
set(newPosition, type.char)
158158
position = newPosition
159159
}
160160

161161
private fun Player.die() {
162162
logger.debug { " $this died" }
163-
setAt(position, cavern)
163+
set(position, cavern)
164164
units.remove(this)
165165
if (type == Elf) {
166166
anElfDied = true

0 commit comments

Comments
 (0)