Skip to content

Commit 489bb26

Browse files
committed
Add solution for Y2024D15.
1 parent d48685d commit 489bb26

File tree

6 files changed

+233
-1
lines changed

6 files changed

+233
-1
lines changed

Diff for: README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ _"Anything that's worth doing, is worth overdoing."_
1515
Table of contents to jump straight to the problem you're looking for.
1616

1717
<details open>
18-
<summary>2024 (28x⭐)</summary>
18+
<summary>2024 (30x⭐)</summary>
1919

2020
| Day | Title | Stars |
2121
|:---:|:-----------------------------------------------------------|:-----:|
@@ -33,6 +33,7 @@ Table of contents to jump straight to the problem you're looking for.
3333
| 12 | [Garden Groups](solutions/aockt/y2024/Y2024D12.kt) | ⭐⭐ |
3434
| 13 | [Claw Contraption](solutions/aockt/y2024/Y2024D13.kt) | ⭐⭐ |
3535
| 14 | [Restroom Redoubt](solutions/aockt/y2024/Y2024D14.kt) | ⭐⭐ |
36+
| 15 | [Warehouse Woes](solutions/aockt/y2024/Y2024D15.kt) | ⭐⭐ |
3637

3738
</details>
3839

Diff for: inputs/aockt/y2024/d15/input.txt

22.1 KB
Binary file not shown.

Diff for: inputs/aockt/y2024/d15/solution_part1.txt

30 Bytes
Binary file not shown.

Diff for: inputs/aockt/y2024/d15/solution_part2.txt

30 Bytes
Binary file not shown.

Diff for: solutions/aockt/y2024/Y2024D15.kt

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package aockt.y2024
2+
3+
import aockt.util.parse
4+
import aockt.util.spacial.Direction
5+
import aockt.util.spacial.Direction.*
6+
import aockt.util.spacial.*
7+
import aockt.y2024.Y2024D15.Warehouse.Tile
8+
import aockt.y2024.Y2024D15.Warehouse.Tile.*
9+
import io.github.jadarma.aockt.core.Solution
10+
11+
object Y2024D15 : Solution {
12+
13+
/** Model of a lanternfish food warehouse. */
14+
private class Warehouse(val data: MutableGrid<Tile>) : Grid<Tile> by data {
15+
16+
/** The types of tile a singular grid cell can have. */
17+
enum class Tile { Wall, Empty, Robot, Crate, WideCrateLeft, WideCrateRight }
18+
19+
/** The current robot position. */
20+
private lateinit var robot: Point
21+
22+
/**
23+
* Thorough input validation to ensure later assumptions:
24+
* - All edges are walls.
25+
* - Only one robot.
26+
* - Wide crate halves are properly paired.
27+
*/
28+
init {
29+
var foundRobot = false
30+
data.points().forEach { (p, v) ->
31+
if (p.x == 0L || p.y == 0L || p.x == width - 1L || p.y == height - 1L) {
32+
require(v == Wall) { "Invalid warehouse map. Edges should be walls." }
33+
}
34+
35+
if (v == Robot) {
36+
require(!foundRobot) { "There can only be one robot." }
37+
foundRobot = true
38+
robot = p
39+
}
40+
41+
if (v == WideCrateLeft) {
42+
require(getOrNull(p.move(Right)) == WideCrateRight) { "Wide crate cannot have split halves." }
43+
}
44+
45+
if (v == WideCrateRight) {
46+
require(getOrNull(p.move(Left)) == WideCrateLeft) { "Wide crate cannot have split halves." }
47+
}
48+
}
49+
50+
require(foundRobot) { "Warehouse must have exactly one robot, but none was found." }
51+
}
52+
53+
/** Calculate the sum of crates' GPS signals. */
54+
fun boxGpsSignal(): Long = points().sumOf { (p, v) ->
55+
when (v) {
56+
Crate, WideCrateLeft -> (height - 1 - p.y) * 100 + p.x
57+
else -> 0L
58+
}
59+
}
60+
61+
/**
62+
* Attempt to move the robot in a [direction] and move crates accordingly.
63+
* This mutates the warehouse and returns a reference to the same instance.
64+
*/
65+
fun move(direction: Direction): Warehouse = apply { shift(direction) }
66+
67+
/** Checks if the robot can move in a [direction]. It can do so if it is an empty space, or a movable crate. */
68+
private fun canMove(direction: Direction): Boolean {
69+
70+
fun recurse(point: Point): Boolean {
71+
val next = point.move(direction)
72+
return when (get(point)) {
73+
Empty -> true
74+
Wall -> false
75+
WideCrateLeft -> recurse(next) && (direction is Horizontal || recurse(next.move(Right)))
76+
WideCrateRight -> recurse(next) && (direction is Horizontal || recurse(next.move(Left)))
77+
Crate, Robot -> recurse(next)
78+
}
79+
}
80+
81+
return recurse(robot)
82+
}
83+
84+
/** Moves the robot in a [direction] and moves crates if necessary. Only performs the action if it is valid. */
85+
private fun shift(direction: Direction) {
86+
87+
if (!canMove(direction)) return
88+
89+
fun recurse(point: Point) {
90+
val current = get(point)
91+
val next = point.move(direction)
92+
93+
if (current == Empty) return
94+
check(current != Wall) { "Tried to shift a wall." }
95+
96+
recurse(next)
97+
data[next] = current
98+
data[point] = Empty
99+
if (current == Robot) robot = next
100+
101+
if (direction is Horizontal) return
102+
103+
if (current == WideCrateLeft) {
104+
recurse(next.move(Right))
105+
data[next.move(Right)] = WideCrateRight
106+
data[point.move(Right)] = Empty
107+
}
108+
109+
if (current == WideCrateRight) {
110+
recurse(next.move(Left))
111+
data[next.move(Left)] = WideCrateLeft
112+
data[point.move(Left)] = Empty
113+
}
114+
}
115+
116+
recurse(robot)
117+
}
118+
}
119+
120+
/**
121+
* Parse the [input] and return the [Warehouse] layout and the robot movements.
122+
* If [wide], returns the part two warehouse.
123+
*/
124+
private fun parseInput(input: String, wide: Boolean): Pair<Warehouse, List<Direction>> = parse {
125+
val (originalLayout, movements) = input.split("\n\n", limit = 2)
126+
val layout = if (!wide) originalLayout else originalLayout
127+
.replace("#", "##")
128+
.replace(".", "..")
129+
.replace("O", "[]")
130+
.replace("@", "@.")
131+
132+
val directions = movements.mapNotNull {
133+
when (it) {
134+
'<' -> Left
135+
'>' -> Right
136+
'^' -> Up
137+
'v' -> Down
138+
'\n' -> null
139+
else -> error("Invalid direction: $it")
140+
}
141+
}
142+
143+
val tiles = MutableGrid(layout) {
144+
when (it) {
145+
'#' -> Wall
146+
'.' -> Empty
147+
'O' -> Crate
148+
'[' -> WideCrateLeft
149+
']' -> WideCrateRight
150+
'@' -> Robot
151+
else -> error("Invalid map tile: $it")
152+
}
153+
}
154+
155+
Warehouse(tiles) to directions
156+
}
157+
158+
/** Common solution. */
159+
private fun solve(input: String, wide: Boolean): Long =
160+
parseInput(input, wide)
161+
.let { (warehouse, movements) -> movements.fold(warehouse, Warehouse::move) }
162+
.boxGpsSignal()
163+
164+
override fun partOne(input: String): Long = solve(input, wide = false)
165+
override fun partTwo(input: String): Long = solve(input, wide = true)
166+
}

Diff for: tests/aockt/y2024/Y2024D15Test.kt

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package aockt.y2024
2+
3+
import io.github.jadarma.aockt.test.AdventDay
4+
import io.github.jadarma.aockt.test.AdventSpec
5+
6+
@AdventDay(2024, 15, "Warehouse Woes")
7+
class Y2024D15Test : AdventSpec<Y2024D15>({
8+
val example1 = """
9+
########
10+
#..O.O.#
11+
##@.O..#
12+
#...O..#
13+
#.#.O..#
14+
#...O..#
15+
#......#
16+
########
17+
18+
<^^>>>vv<v>>v<<
19+
""".trimIndent()
20+
21+
val example2 = """
22+
##########
23+
#..O..O.O#
24+
#......O.#
25+
#.OO..O.O#
26+
#..O@..O.#
27+
#O#..O...#
28+
#O..O..O.#
29+
#.OO.O.OO#
30+
#....O...#
31+
##########
32+
33+
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
34+
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
35+
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
36+
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
37+
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
38+
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
39+
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
40+
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
41+
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
42+
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
43+
""".trimIndent()
44+
45+
val example3 = """
46+
#######
47+
#...#.#
48+
#.....#
49+
#..OO@#
50+
#..O..#
51+
#.....#
52+
#######
53+
54+
<vv<<^^<<^^
55+
""".trimIndent()
56+
partOne {
57+
example1 shouldOutput 2028
58+
example2 shouldOutput 10092
59+
}
60+
61+
partTwo {
62+
example2 shouldOutput 9021
63+
example3 shouldOutput 618
64+
}
65+
})

0 commit comments

Comments
 (0)