Skip to content

Commit 42ce619

Browse files
authored
Day 15 2024 (#275)
Day 15 2024
1 parent 118883c commit 42ce619

File tree

4 files changed

+267
-0
lines changed

4 files changed

+267
-0
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package me.peckb.aoc._2024.calendar.day15
2+
3+
import javax.inject.Inject
4+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
5+
6+
class Day15 @Inject constructor(
7+
private val generatorFactory: InputGeneratorFactory,
8+
) {
9+
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
10+
var gx = -1
11+
var gy = -1
12+
val area = mutableListOf<MutableList<Room>>()
13+
val directions = mutableListOf<Direction>()
14+
15+
var setupArea = true
16+
input.forEachIndexed setup@ { yIndex, line ->
17+
if (line.isEmpty()) {
18+
setupArea = false
19+
return@setup
20+
}
21+
if (setupArea) {
22+
val row = mutableListOf<Room>()
23+
line.forEachIndexed { xIndex, c ->
24+
when (c) {
25+
'#' -> row.add(Room.WALL)
26+
'.' -> row.add(Room.EMPTY)
27+
'O' -> row.add(Room.BOX)
28+
else -> {
29+
row.add(Room.GUARD)
30+
gx = xIndex
31+
gy = yIndex
32+
}
33+
}
34+
}
35+
area.add(row)
36+
} else {
37+
line.forEach { c ->
38+
when (c) {
39+
'<' -> directions.add(Direction.W)
40+
'^' -> directions.add(Direction.N)
41+
'>' -> directions.add(Direction.E)
42+
else -> directions.add(Direction.S)
43+
}
44+
}
45+
}
46+
}
47+
48+
directions.forEach { direction ->
49+
val (yDelta, xDelta) = when (direction) {
50+
Direction.N -> -1 to 0
51+
Direction.E -> 0 to 1
52+
Direction.S -> 1 to 0
53+
Direction.W -> 0 to -1
54+
}
55+
56+
var boxes = 1
57+
var boxEnd = area[gy + (yDelta * boxes)][gx + (xDelta * boxes)]
58+
while (boxEnd == Room.BOX) {
59+
boxes++
60+
boxEnd = area[gy + (yDelta * boxes)][gx + (xDelta * boxes)]
61+
}
62+
boxes--
63+
if (boxEnd == Room.EMPTY) {
64+
while (boxes > 0) {
65+
area[gy + (yDelta * (boxes + 1))][gx + (xDelta * (boxes + 1))] = Room.BOX
66+
boxes--
67+
}
68+
area[gy][gx] = Room.EMPTY
69+
gx += xDelta
70+
gy += yDelta
71+
area[gy][gx] = Room.GUARD
72+
}
73+
}
74+
75+
area.withIndex().sumOf { (y, row) ->
76+
row.withIndex().sumOf { (x, room) ->
77+
when (room) {
78+
Room.BOX -> (100 * y) + x
79+
else -> 0
80+
}
81+
}
82+
}
83+
}
84+
85+
fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
86+
var guard: WideRoom = WideRoom.Guard(-1, -1)
87+
val area = mutableListOf<MutableList<WideRoom>>()
88+
val directions = mutableListOf<Direction>()
89+
90+
var setupArea = true
91+
input.forEachIndexed setup@ { y, line ->
92+
if (line.isEmpty()) {
93+
setupArea = false
94+
return@setup
95+
}
96+
if (setupArea) {
97+
val row = mutableListOf<WideRoom>()
98+
line.forEachIndexed { x, c ->
99+
val x1 = x*2
100+
val x2 = x*2 + 1
101+
when (c) {
102+
'#' -> {
103+
row.add(WideRoom.Wall(y, x1))
104+
row.add(WideRoom.Wall(y, x2))
105+
}
106+
'.' -> {
107+
row.add(WideRoom.Empty(y, x1))
108+
row.add(WideRoom.Empty(y, x2))
109+
}
110+
'O' -> {
111+
row.add(WideRoom.LeftBox(y, x1))
112+
row.add(WideRoom.RightBox(y, x2))
113+
}
114+
else -> {
115+
row.add(WideRoom.Guard(y, x1).also { guard = it })
116+
row.add(WideRoom.Empty(y, x2))
117+
}
118+
}
119+
}
120+
area.add(row)
121+
} else {
122+
line.forEach { c ->
123+
when (c) {
124+
'<' -> directions.add(Direction.W)
125+
'^' -> directions.add(Direction.N)
126+
'>' -> directions.add(Direction.E)
127+
else -> directions.add(Direction.S)
128+
}
129+
}
130+
}
131+
}
132+
133+
directions.forEach { direction ->
134+
if (direction == Direction.W || direction == Direction.E) {
135+
val xDelta = when (direction) {
136+
Direction.E -> 1
137+
Direction.W -> -1
138+
else -> throw IllegalStateException("can't move N/S in an E/W block")
139+
}
140+
141+
var boxes = 1
142+
var boxEnd = area[guard.y][guard.x + (xDelta * boxes)]
143+
while (boxEnd is WideRoom.RightBox || boxEnd is WideRoom.LeftBox) {
144+
boxes++
145+
boxEnd = area[guard.y][guard.x + (xDelta * boxes)]
146+
}
147+
boxes--
148+
if (boxEnd is WideRoom.Empty) {
149+
while (boxes > 0) {
150+
val x = guard.x + (xDelta * (boxes + 1))
151+
area[guard.y][x] = area[guard.y][guard.x + (xDelta * boxes)].also { it.x = x }
152+
boxes--
153+
}
154+
area[guard.y][guard.x] = WideRoom.Empty(guard.y, guard.x)
155+
guard.x += xDelta
156+
area[guard.y][guard.x] = guard
157+
}
158+
} else { // N/S Block!
159+
val yDelta = when (direction) {
160+
Direction.N -> -1
161+
Direction.S -> 1
162+
else -> throw IllegalStateException("can't move N/S in an E/W block")
163+
}
164+
165+
var allCanMove = true
166+
var doneSearching = false
167+
val areasToMove = mutableMapOf<Int, Set<WideRoom>>()
168+
areasToMove[guard.y] = setOf(area[guard.y][guard.x])
169+
var currentY = guard.y
170+
while(allCanMove && !doneSearching) {
171+
areasToMove[currentY]?.forEach { room ->
172+
when (val nextArea = area[room.y + yDelta][room.x]) {
173+
is WideRoom.Wall -> allCanMove = false
174+
is WideRoom.LeftBox -> {
175+
areasToMove.merge(currentY + yDelta, setOf(nextArea)) { a, b -> a + b }
176+
areasToMove.merge(currentY + yDelta, setOf(area[room.y + yDelta][room.x + 1])) { a, b -> a + b }
177+
}
178+
is WideRoom.RightBox -> {
179+
areasToMove.merge(currentY + yDelta, setOf(nextArea)) { a, b -> a + b }
180+
areasToMove.merge(currentY + yDelta, setOf(area[room.y + yDelta][room.x - 1])) { a, b -> a + b }
181+
}
182+
else -> { /* ignore empty rooms */ }
183+
}
184+
} ?: run { doneSearching = true }
185+
currentY += yDelta
186+
}
187+
188+
if (allCanMove) {
189+
val sortedEntries = areasToMove.entries.sortedBy { it.key }
190+
.let { if (direction == Direction.S) { it.reversed() } else { it } }
191+
192+
sortedEntries.forEach { (y, wideRooms) ->
193+
wideRooms.forEach { room ->
194+
if (areasToMove[y - yDelta]?.contains(area[room.y - yDelta][room.x]) != true) {
195+
area[room.y][room.x] = WideRoom.Empty(room.y - yDelta, room.x)
196+
}
197+
room.y += yDelta
198+
area[room.y][room.x] = room
199+
}
200+
}
201+
}
202+
}
203+
}
204+
205+
area.withIndex().sumOf { (y, row) ->
206+
row.withIndex().sumOf { (x, room) ->
207+
when (room) {
208+
is WideRoom.LeftBox -> (100 * y) + x
209+
else -> 0
210+
}
211+
}
212+
}
213+
}
214+
}
215+
216+
enum class Room(val c: Char) {
217+
EMPTY('.'), WALL('#'), BOX('O'), GUARD('@');
218+
219+
override fun toString(): String { return c.toString() }
220+
}
221+
222+
sealed class WideRoom(val c: Char, var y: Int, var x: Int) {
223+
class Empty(y: Int, x: Int) : WideRoom('.', y, x)
224+
class Guard(y: Int, x: Int) : WideRoom('@', y, x)
225+
class Wall(y: Int, x: Int) : WideRoom('#', y, x)
226+
class LeftBox(y: Int, x: Int) : WideRoom('[', y, x)
227+
class RightBox(y: Int, x: Int) : WideRoom(']', y, x)
228+
229+
override fun toString(): String { return c.toString() }
230+
}
231+
232+
enum class Direction { N,E,S,W }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 15: Warehouse Woes](https://adventofcode.com/2024/day/15)

src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import me.peckb.aoc._2024.calendar.day11.Day11Test
1414
import me.peckb.aoc._2024.calendar.day12.Day12Test
1515
import me.peckb.aoc._2024.calendar.day13.Day13Test
1616
import me.peckb.aoc._2024.calendar.day14.Day14Test
17+
import me.peckb.aoc._2024.calendar.day15.Day15Test
1718
import javax.inject.Singleton
1819
import me.peckb.aoc.DayComponent
1920
import me.peckb.aoc.InputModule
@@ -36,4 +37,5 @@ internal interface TestDayComponent : DayComponent {
3637
fun inject(day12Test: Day12Test)
3738
fun inject(day13Test: Day13Test)
3839
fun inject(day14Test: Day14Test)
40+
fun inject(day15Test: Day15Test)
3941
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2024.calendar.day15
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2024.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day15Test {
11+
@Inject
12+
lateinit var day15: Day15
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay15PartOne() {
21+
assertEquals(1475249, day15.partOne(DAY_15))
22+
}
23+
24+
@Test
25+
fun testDay15PartTwo() {
26+
assertEquals(1509724, day15.partTwo(DAY_15))
27+
}
28+
29+
companion object {
30+
private const val DAY_15: String = "advent-of-code-input/2024/day15.input"
31+
}
32+
}

0 commit comments

Comments
 (0)