Skip to content

Commit 4d55455

Browse files
committed
Day15
1 parent 124bf10 commit 4d55455

File tree

8 files changed

+259
-13
lines changed

8 files changed

+259
-13
lines changed

aoc-secret

src/main/kotlin/com/cluddles/aoc/util/Dir4.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.cluddles.aoc.util
22

3+
import java.util.EnumMap
4+
35
/**
46
* Representation of cardinal (N, E, S, W) directions
57
*
@@ -19,4 +21,17 @@ enum class Dir4(val delta: Int2d) {
1921
fun rotate(steps: Int) : Dir4 { return move(steps) }
2022
val opposite: Dir4
2123
get() = rotate(entries.size / 2)
24+
25+
fun toChar() = CHARS[ordinal]
26+
27+
companion object {
28+
private const val CHARS = "^>v<"
29+
30+
fun isValidChar(ch: Char): Boolean = CHARS.contains(ch)
31+
32+
fun fromChar(ch: Char): Dir4 {
33+
return Dir4.entries[CHARS.indexOf(ch)]
34+
}
35+
}
36+
2237
}

src/main/kotlin/com/cluddles/aoc/y2024/Day06.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,11 @@ import com.cluddles.aoc.util.CharGrid
77
import com.cluddles.aoc.util.Dir4
88
import com.cluddles.aoc.util.Grid
99
import com.cluddles.aoc.util.MutableGrid
10-
import java.util.EnumMap
1110

1211
/** Guard Gallivant */
1312
object Day06: Solver<Grid<Char>, Int> {
1413

1514
const val WALL = '#'
16-
val GUARD_DIRS = EnumMap<Dir4, Char>(Dir4::class.java).apply {
17-
putAll(listOf(Dir4.N to '^', Dir4.E to '>', Dir4.S to 'v', Dir4.W to '<'))
18-
}
1915

2016
data class Guard(var x: Int, var y: Int, var facing: Dir4 = Dir4.N)
2117

@@ -27,17 +23,17 @@ object Day06: Solver<Grid<Char>, Int> {
2723
private fun findGuard(grid: Grid<Char>): Guard {
2824
for (i in 0 until grid.width) {
2925
for (j in 0 until grid.height) {
30-
if (grid[i, j] == GUARD_DIRS[Dir4.N]) {
26+
if (grid[i, j] == Dir4.N.toChar()) {
3127
return Guard(i, j)
3228
}
3329
}
3430
}
35-
throw IllegalArgumentException("No guard found")
31+
error("No guard found")
3632
}
3733

3834
/** Mark [grid] cell as visited by [guard] */
3935
private fun visit(grid: MutableGrid<Char>, guard: Guard) {
40-
grid[guard.x, guard.y] = GUARD_DIRS[guard.facing]!!
36+
grid[guard.x, guard.y] = guard.facing.toChar()
4137
}
4238

4339
/**
@@ -75,7 +71,7 @@ object Day06: Solver<Grid<Char>, Int> {
7571

7672
override fun solvePart1(input: Grid<Char>): Int {
7773
return generateGuardPath(input)
78-
.count { GUARD_DIRS.containsValue(it) }
74+
.count { Dir4.isValidChar(it) }
7975
}
8076

8177
/** Blocks off [grid] position [x],[y] with a wall, and determines if this causes the guard to loop */
@@ -86,7 +82,7 @@ object Day06: Solver<Grid<Char>, Int> {
8682
do {
8783
moveGuardOnce(mutGrid, guard)
8884
val inBounds = mutGrid.isInBounds(guard.x, guard.y)
89-
if (inBounds && mutGrid[guard.x, guard.y] == GUARD_DIRS[guard.facing]) return true
85+
if (inBounds && mutGrid[guard.x, guard.y] == guard.facing.toChar()) return true
9086
} while (inBounds)
9187
return false
9288
}
@@ -98,7 +94,7 @@ object Day06: Solver<Grid<Char>, Int> {
9894
var looping = 0
9995
for (i in 0 until grid.width) {
10096
for (j in 0 until grid.height) {
101-
if ((i == guardStart.x && j == guardStart.y) || !GUARD_DIRS.containsValue(grid[i, j])) continue
97+
if ((i == guardStart.x && j == guardStart.y) || !Dir4.isValidChar(grid[i, j])) continue
10298
if (causesLoop(input, i, j)) looping++
10399
}
104100
}

src/main/kotlin/com/cluddles/aoc/y2024/Day14.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.cluddles.aoc.core.Solver
55
import com.cluddles.aoc.core.SolverInput
66
import com.cluddles.aoc.util.CharGrid
77
import com.cluddles.aoc.util.Int2d
8-
import java.io.File
98

109
/** Restroom Redoubt */
1110
object Day14: Solver<List<Day14.Robot>, Int> {
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package com.cluddles.aoc.y2024
2+
3+
import com.cluddles.aoc.core.Harness
4+
import com.cluddles.aoc.core.Solver
5+
import com.cluddles.aoc.core.SolverInput
6+
import com.cluddles.aoc.util.CharGrid
7+
import com.cluddles.aoc.util.Dir4
8+
import com.cluddles.aoc.util.Grid
9+
import com.cluddles.aoc.util.MutableGrid
10+
import kotlin.text.iterator
11+
12+
/** Warehouse Woes */
13+
object Day15: Solver<Day15.Input, Int> {
14+
15+
const val BOX = 'O'
16+
const val WALL = '#'
17+
const val ROBOT = '@'
18+
const val BLANK = '.'
19+
20+
const val BOX_L = '['
21+
const val BOX_R = ']'
22+
23+
data class Input(val grid: Grid<Char>, val moves: String)
24+
25+
data class Robot(var x: Int, var y: Int)
26+
27+
data class Box(val x: Int, val y: Int, val ch: Char)
28+
29+
override fun prepareInput(src: SolverInput): Input {
30+
val gridLines = mutableListOf<String>()
31+
val it = src.lines(allowBlankLines = true).iterator()
32+
while (it.hasNext()) {
33+
val l = it.next()
34+
if (l.isNotBlank()) {
35+
gridLines.add(l)
36+
} else {
37+
break
38+
}
39+
}
40+
41+
val moves = StringBuffer()
42+
while (it.hasNext()) {
43+
val l = it.next()
44+
moves.append(l.trimEnd())
45+
}
46+
47+
return Input(CharGrid(gridLines), moves.toString())
48+
}
49+
50+
private fun findRobot(grid: Grid<Char>): Robot {
51+
for (i in 0 until grid.width) {
52+
for (j in 0 until grid.height) {
53+
if (grid[i, j] == ROBOT) {
54+
return Robot(i, j)
55+
}
56+
}
57+
}
58+
error("No robot found")
59+
}
60+
61+
private fun applyMove(robot: Robot, move: Char, grid: MutableGrid<Char>) {
62+
val dir = Dir4.fromChar(move)
63+
if (canMove(grid, robot.x + dir.x, robot.y + dir.y, dir)) {
64+
grid[robot.x, robot.y] = BLANK
65+
if (dir.x != 0) {
66+
pushX(grid, robot.x + dir.x, robot.y, dir.x)
67+
} else {
68+
val adds = mutableListOf<Box>()
69+
pushY(grid, robot.x, robot.y + dir.y, dir.y, adds)
70+
for (a in adds) {
71+
grid[a.x, a.y] = a.ch
72+
}
73+
}
74+
robot.x += dir.x
75+
robot.y += dir.y
76+
grid[robot.x, robot.y] = ROBOT
77+
}
78+
}
79+
80+
private fun canMove(grid: MutableGrid<Char>, x: Int, y: Int, dir: Dir4): Boolean {
81+
return when(grid[x, y]) {
82+
WALL -> false
83+
BOX -> canMove(grid, x + dir.x, y + dir.y, dir)
84+
BOX_L -> canMove(grid, x + dir.x, y + dir.y, dir) && (dir.y == 0 || canMove(grid, x + 1, y + dir.y, dir))
85+
BOX_R -> canMove(grid, x + dir.x, y + dir.y, dir) && (dir.y == 0 || canMove(grid, x - 1, y + dir.y, dir))
86+
else -> true
87+
}
88+
}
89+
90+
// X move is trivial - just shove everything along
91+
private fun pushX(grid: MutableGrid<Char>, x: Int, y: Int, dx: Int) {
92+
val orig = grid[x, y]
93+
when (orig) {
94+
WALL -> error("Cannot push")
95+
BOX, BOX_L, BOX_R -> pushX(grid, x + dx, y, dx)
96+
else -> { return }
97+
}
98+
grid[x, y] = BLANK
99+
grid[x + dx, y] = orig
100+
}
101+
102+
// Y move is less trivial; a single box could be shoved multiple times (either by a perfectly aligned pushing box,
103+
// or two half-aligned boxes)
104+
private fun pushY(grid: MutableGrid<Char>, x: Int, y: Int, dy: Int, adds: MutableList<Box>) {
105+
val orig = grid[x, y]
106+
when (orig) {
107+
WALL -> { error("Cannot push") }
108+
BOX, BOX_L, BOX_R -> {}
109+
else -> { return }
110+
}
111+
112+
// Clear relevant cells; add boxes back into their new positions later
113+
grid[x, y] = BLANK
114+
adds.add(Box(x, y + dy, orig))
115+
when (orig) {
116+
BOX_L -> {
117+
grid[x + 1, y] = BLANK
118+
adds.add(Box(x + 1, y + dy, BOX_R))
119+
}
120+
BOX_R -> {
121+
grid[x - 1, y] = BLANK
122+
adds.add(Box(x - 1, y + dy, BOX_L))
123+
}
124+
}
125+
126+
// Call through to the next box(es) in line
127+
pushY(grid, x, y + dy, dy, adds)
128+
when (orig) {
129+
BOX_L -> pushY(grid, x + 1, y + dy, dy, adds)
130+
BOX_R -> pushY(grid, x - 1, y + dy, dy, adds)
131+
}
132+
}
133+
134+
private fun gps(grid: Grid<Char>): Int {
135+
var result = 0
136+
for (i in 0 until grid.width) {
137+
for (j in 0 until grid.height) {
138+
if (grid[i, j] == BOX || grid[i, j] == BOX_L) result += j * 100 + i
139+
}
140+
}
141+
return result
142+
}
143+
144+
private fun solve(grid: MutableGrid<Char>, moves: String): Int {
145+
val robot = findRobot(grid)
146+
for (m in moves) {
147+
applyMove(robot, m, grid)
148+
}
149+
return gps(grid)
150+
}
151+
152+
override fun solvePart1(input: Input): Int {
153+
val grid = input.grid.mutableCopy()
154+
return solve(grid, input.moves)
155+
}
156+
157+
override fun solvePart2(input: Input): Int {
158+
val grid = CharGrid(input.grid.width * 2, input.grid.height, BLANK)
159+
for (i in 0 until input.grid.width) {
160+
for (j in 0 until input.grid.height) {
161+
val str = when(input.grid[i, j]) {
162+
WALL -> "$WALL$WALL"
163+
BOX -> "$BOX_L$BOX_R"
164+
ROBOT -> "$ROBOT$BLANK"
165+
else -> "$BLANK$BLANK"
166+
}
167+
grid[i*2, j] = str[0]
168+
grid[i*2 + 1, j] = str[1]
169+
}
170+
}
171+
return solve(grid, input.moves)
172+
}
173+
174+
}
175+
176+
fun main() {
177+
Harness.run(Day15)
178+
}

src/test/kotlin/com/cluddles/aoc/y2024/Day12Test.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class Day12Test {
2121
AAABBA
2222
ABBAAA
2323
ABBAAA
24-
AAAAAA""".trimIndent()
24+
AAAAAA
25+
""".trimIndent()
2526
assertThat(solver.solvePart2(solver.prepareInput(SolverInput.fromText(text)))).isEqualTo(368)
2627
}
2728

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.cluddles.aoc.y2024
2+
3+
import com.cluddles.aoc.core.SolverInput
4+
import org.assertj.core.api.Assertions.assertThat
5+
import org.junit.jupiter.api.Test
6+
7+
class Day15Test {
8+
9+
val solver = Day15
10+
11+
val input = solver.prepareInput(SolverInput.fromPath(solver.examplePath))
12+
13+
@Test fun part1_simple() {
14+
val text = """
15+
########
16+
#..O.O.#
17+
##@.O..#
18+
#...O..#
19+
#.#.O..#
20+
#...O..#
21+
#......#
22+
########
23+
24+
<^^>>>vv<v>>v<<
25+
""".trimIndent()
26+
assertThat(solver.solvePart1(solver.prepareInput(SolverInput.fromText(text)))).isEqualTo(2028)
27+
}
28+
29+
@Test fun part1() {
30+
assertThat(solver.solvePart1(input)).isEqualTo(10092)
31+
}
32+
33+
@Test fun part2() {
34+
assertThat(solver.solvePart2(input)).isEqualTo(9021)
35+
}
36+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
##########
2+
#..O..O.O#
3+
#......O.#
4+
#.OO..O.O#
5+
6+
#O#..O...#
7+
#O..O..O.#
8+
#.OO.O.OO#
9+
#....O...#
10+
##########
11+
12+
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
13+
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
14+
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
15+
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
16+
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
17+
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
18+
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
19+
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
20+
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
21+
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^

0 commit comments

Comments
 (0)