|
| 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 | +} |
0 commit comments