diff --git a/README.md b/README.md index d5a60901..4f48adbc 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ includes: * [Advent of Code 2019][aoc-2019-link] (Days 1-25) * [Advent of Code 2020][aoc-2020-link] (Days 1-25) * [Advent of Code 2021][aoc-2021-link] (Days 1-25) -* [Advent of Code 2023][aoc-2023-link] (Days 1-15) +* [Advent of Code 2023][aoc-2023-link] (Days 1-16) ## Getting Started diff --git a/build.gradle.kts b/build.gradle.kts index 34521299..717ed2d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -300,4 +300,7 @@ dependencies { kover(project(":year2023:day15:hash")) kover(project(":year2023:day15:part1")) kover(project(":year2023:day15:part2")) + kover(project(":year2023:day16:beam")) + kover(project(":year2023:day16:part1")) + kover(project(":year2023:day16:part2")) } diff --git a/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Direction.kt b/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Direction.kt index be3dfdec..726f62e4 100644 --- a/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Direction.kt +++ b/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Direction.kt @@ -48,6 +48,16 @@ enum class Direction(private val clockwiseIndex: Int) { */ UP_LEFT(clockwiseIndex = 7); + /** + * Returns `true` if the direction is horizontal. + */ + fun isHorizontal(): Boolean = this == RIGHT || this == LEFT + + /** + * Returns `true` if the direction is vertical. + */ + fun isVertical(): Boolean = this == UP || this == DOWN + /** * Returns the direction given by turning 180 degrees from this one. */ diff --git a/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Pose.kt b/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Pose.kt index 95b216ca..bda0d195 100644 --- a/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Pose.kt +++ b/common/geometry/src/main/kotlin/com/curtislb/adventofcode/common/geometry/Pose.kt @@ -19,9 +19,15 @@ data class Pose(val position: Point, val direction: Direction) { * Note that [distance] is *not* the same as the Euclidean distance, as diagonally adjacent * positions are considered to have a [distance] of 1. */ - fun move(moveDirection: Direction = direction, distance: Int = 1): Pose { - return Pose(position.move(moveDirection, distance), direction) - } + fun move(moveDirection: Direction = direction, distance: Int = 1): Pose = + Pose(position.move(moveDirection, distance), direction) + + /** + * Returns the pose given by turning the actor to face a specified [newDirection] and then + * moving the actor [distance] grid units in that direction. + */ + fun turnAndMove(newDirection: Direction, distance: Int = 1): Pose = + Pose(position.move(newDirection, distance), newDirection) /** * Returns the pose given by turning the actor 180 degrees. diff --git a/common/grid/src/main/kotlin/com/curtislb/adventofcode/common/grid/GridExtensions.kt b/common/grid/src/main/kotlin/com/curtislb/adventofcode/common/grid/GridExtensions.kt index ca830839..e080e6e3 100644 --- a/common/grid/src/main/kotlin/com/curtislb/adventofcode/common/grid/GridExtensions.kt +++ b/common/grid/src/main/kotlin/com/curtislb/adventofcode/common/grid/GridExtensions.kt @@ -2,6 +2,21 @@ package com.curtislb.adventofcode.common.grid import com.curtislb.adventofcode.common.geometry.Point +/** + * Returns the number of elements in the grid for which the [predicate] function returns `true`. + */ +inline fun Grid.count(predicate: (value: T) -> Boolean): Int { + var count = 0 + for (rowIndex in rowIndices) { + for (colIndex in columnIndices) { + if (predicate(this[rowIndex, colIndex])) { + count++ + } + } + } + return count +} + /** * Performs the given [action] on each element and its row and column indices in this grid, in * row-major order. diff --git a/settings.gradle.kts b/settings.gradle.kts index 25f4478c..ab7da7b7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -467,3 +467,8 @@ include("year2023:day14:part2") include("year2023:day15:hash") include("year2023:day15:part1") include("year2023:day15:part2") + +// Day 16: The Floor Will Be Lava +include("year2023:day16:beam") +include("year2023:day16:part1") +include("year2023:day16:part2") diff --git a/year2023/day16/beam/build.gradle.kts b/year2023/day16/beam/build.gradle.kts new file mode 100644 index 00000000..bb82d735 --- /dev/null +++ b/year2023/day16/beam/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + val kotlinVersion: String by System.getProperties() + val koverVersion: String by System.getProperties() + + kotlin("jvm") version kotlinVersion + id("org.jetbrains.kotlinx.kover") version koverVersion +} + +dependencies { + val assertjVersion: String by properties + val junitJupiterVersion: String by properties + val junitPlatformVersion: String by properties + + api(project(":common:geometry")) + api(project(":common:grid")) + testImplementation("org.assertj:assertj-core:$assertjVersion") + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") +} diff --git a/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/BeamContraption.kt b/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/BeamContraption.kt new file mode 100644 index 00000000..dedb6330 --- /dev/null +++ b/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/BeamContraption.kt @@ -0,0 +1,160 @@ +package com.curtislb.adventofcode.year2023.day16.beam + +import com.curtislb.adventofcode.common.geometry.Direction +import com.curtislb.adventofcode.common.geometry.Point +import com.curtislb.adventofcode.common.geometry.Pose +import com.curtislb.adventofcode.common.grid.Grid +import com.curtislb.adventofcode.common.grid.mutableGridOf +import java.io.File + +/** + * A contraption that consists of a 2D grid of tiles, which a beam can pass through while being + * reflected by mirrors and split in multiple directions by splitters. + * + * @property grid The grid of [Tile]s that makes up the contraption. + * + * @constructor Creates a new instance of [BeamContraption] with the specified [grid] of tiles. + */ +class BeamContraption(private val grid: Grid) { + /** + * Returns the number of grid tiles that a beam--with an initial position and direction given by + * [beamStart]--passes through before exiting the contraption. + */ + fun beamEnergy(beamStart: Pose): Int { + // Keep track of energized tiles and previously seen beam poses + val energized = mutableSetOf() + val processed = mutableSetOf() + + // Use flood fill (DFS) to trace the path of the beam + val beamStack = ArrayDeque().apply { addLast(beamStart) } + while (beamStack.isNotEmpty()) { + // Pop the latest beam pose from the stack, and check if it's valid + val beam = beamStack.removeLast() + if (beam in processed || beam.position !in grid) { + continue + } + + // Process the beam pose, and mark the tile as energized + energized.add(beam.position) + processed.add(beam) + + // Push subsequent beam pose(s) onto the stack + when (grid[beam.position]) { + // Beam passes through an empty tile + Tile.EMPTY -> { + beamStack.addLast(beam.move()) + } + + // Beam is reflected by north-east mirror + Tile.MIRROR_NE -> { + beamStack.addLast(reflectNorthEast(beam)) + } + + // Beam is reflected by south-east mirror + Tile.MIRROR_SE -> { + beamStack.addLast(reflectSouthEast(beam)) + } + + // Horizontal beam is split by vertical splitter + Tile.SPLIT_NS -> { + if (beam.direction.isHorizontal()) { + beamStack.addLast(beam.turnAndMove(Direction.UP)) + beamStack.addLast(beam.turnAndMove(Direction.DOWN)) + } else { + beamStack.addLast(beam.move()) + } + } + + // Vertical beam is split by horizontal splitter + Tile.SPLIT_EW -> { + if (beam.direction.isVertical()) { + beamStack.addLast(beam.turnAndMove(Direction.LEFT)) + beamStack.addLast(beam.turnAndMove(Direction.RIGHT)) + } else { + beamStack.addLast(beam.move()) + } + } + } + } + + // Return the count of energized tiles + return energized.size + } + + /** + * Returns the maximum number of tiles passed through by any beam that enters the contraption + * grid from an edge while initially facing a cardinal direction. + */ + fun findMaxBeamEnergy(): Int { + val beamStarts = mutableListOf() + + // Check beams entering the grid from the left or right + for (rowIndex in grid.rowIndices) { + val leftPosition = Point.fromMatrixCoordinates(rowIndex, 0) + val rightPosition = Point.fromMatrixCoordinates(rowIndex, grid.lastColumnIndex) + beamStarts.add(Pose(leftPosition, Direction.RIGHT)) + beamStarts.add(Pose(rightPosition, Direction.LEFT)) + } + + // Check beams entering the grid from the top or bottom + for (colIndex in grid.columnIndices) { + val topPosition = Point.fromMatrixCoordinates(0, colIndex) + val bottomPosition = Point.fromMatrixCoordinates(grid.lastRowIndex, colIndex) + beamStarts.add(Pose(topPosition, Direction.DOWN)) + beamStarts.add(Pose(bottomPosition, Direction.UP)) + } + + return beamStarts.maxOf { beamEnergy(it) } + } + + /** + * Returns the new beam pose after the given [beam] is reflected by a [Tile.MIRROR_NE] mirror. + * + * @throws IllegalArgumentException If [beam] has a diagonal direction. + */ + private fun reflectNorthEast(beam: Pose): Pose { + val newDirection = when (beam.direction) { + Direction.UP -> Direction.RIGHT + Direction.RIGHT -> Direction.UP + Direction.DOWN -> Direction.LEFT + Direction.LEFT -> Direction.DOWN + else -> throw IllegalArgumentException("Invalid beam direction: ${beam.direction}") + } + return beam.turnAndMove(newDirection) + } + + /** + * Returns the new beam pose after the given [beam] is reflected by a [Tile.MIRROR_SE] mirror. + * + * @throws IllegalArgumentException If [beam] has a diagonal direction. + */ + private fun reflectSouthEast(beam: Pose): Pose { + val newDirection = when (beam.direction) { + Direction.UP -> Direction.LEFT + Direction.RIGHT -> Direction.DOWN + Direction.DOWN -> Direction.RIGHT + Direction.LEFT -> Direction.UP + else -> throw IllegalArgumentException("Invalid beam direction: ${beam.direction}") + } + return beam.turnAndMove(newDirection) + } + + companion object { + /** + * Returns a [BeamContraption] with a tile grid read from the given [file]. + * + * The [file] must contain lines of equal length, with each character representing a [Tile] + * located at the corresponding grid position. + * + * @throws IllegalArgumentException If [file] is not formatted correctly. + */ + fun fromFile(file: File): BeamContraption { + val grid = mutableGridOf() + file.forEachLine { line -> + val row = line.map { Tile.fromChar(it) } + grid.addShallowRow(row) + } + return BeamContraption(grid) + } + } +} diff --git a/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/Tile.kt b/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/Tile.kt new file mode 100644 index 00000000..f0f303ba --- /dev/null +++ b/year2023/day16/beam/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/beam/Tile.kt @@ -0,0 +1,47 @@ +package com.curtislb.adventofcode.year2023.day16.beam + +/** + * A type of tile that may appear in a [BeamContraption] grid. + * + * @property symbol A character that uniquely identifies the type of tile. + */ +enum class Tile(val symbol: Char) { + /** + * An empty tile, which a beam can pass through without changing direction. + */ + EMPTY('.'), + + /** + * A mirror that reflects a rightward beam upward, a leftward beam downward, and vice versa. + */ + MIRROR_NE('/'), + + /** + * A mirror that reflects a rightward beam downward, a leftward beam upward, and vice versa. + */ + MIRROR_SE('\\'), + + /** + * A splitter that splits a horizontal beam into upward and downward beams. + */ + SPLIT_NS('|'), + + /** + * A splitter that splits a vertical beam into leftward and rightward beams. + */ + SPLIT_EW('-'); + + override fun toString(): String = symbol.toString() + + companion object { + /** + * Returns the [Tile] that corresponds to the given [char]. + * + * @throws IllegalArgumentException If [char] has no corresponding [Tile]. + */ + fun fromChar(char: Char): Tile { + return entries.firstOrNull { it.symbol == char } + ?: throw IllegalArgumentException("Invalid tile char: $char") + } + } +} diff --git a/year2023/day16/input/input.txt b/year2023/day16/input/input.txt new file mode 100644 index 00000000..f1b08e13 --- /dev/null +++ b/year2023/day16/input/input.txt @@ -0,0 +1,110 @@ +\.......-...|..............-......-......................................\...................-...........\.... +|........|.....|..........................\.......-..........\..../....\...\......./...........|...\.......... +.................|..|................|..............\|/....|.............../......|........-|\.........|...... +|..\...-.|.\..\....-.................\..../...|.........................................../...|.....\/........ +......../.....\..................|..|./.....\.-./.................|......-......-......../........\........... +............../.\.......|.......-.........\.\......|..............\..........\..\............\...\............ +.....-........\....|.............../..-................-......-....../..........|................/\......-\... +.-..............................\...../.-...\..../.|..../......|..................-........................... +..........-........\.\....|.\.............../.........|......../.../..............|....|...\.-.-.............. +..........\|.-..../..................\-../......./..........--.........\......../....|..........|..\.......... +........|.\...../.....\.\.............-..................................|...../......./..........-..-........ +............/...........................-...-......../.......|..............\.......|..\..........-...-....|./ +.............|.................................|....-..../.................\...........\..........-.../....... +-..\...\...............\................-....|....-...............................\......\........../.../..... +|....\....-...\........................../........-....-....|...................................|...-......... +./........|........................\...|.../..\|\.....|.|................/.....\|......../......\.........../. +......-...-...../...../...\...|...|...............|............../........|........-.......................... +....|............-../.-...................................................................|-............|..... +...\................\/..\/..-....-...........\......./................./...../.........|......-.\............. +....................|........-....................-.....-....................--...../.......\.../........\.... +.............\......\.../..|...............-......|...........................-...-|.............\./../.|..... +...|./......................-.......................././.....-...|....................|....................... +.....\.........................-.....|./.....-..\....\./....../...........|........................-.|........ +/...\......|.|....../......-.......|\.........-.|...../..\................|......\|........................-.. +.................../...../...\................|....-/............../.............../|......../........-....... +....|./...............-............\.........\.......-/.....-......-...\......-.../|..............\..-........ +.........../.....\..../........-.........../.....|........-....................\...................|.-........ +...................................................-.......\............./............|....................... +..............................-.............|..--....................\./.........|............................ +......-..\......./../..-..........|.........-|.........../.....|............./...../....................../.\. +........./\.-...|.......\...\..............|.-.....................|................/\..-.../|.-....-...../... +....|............./....../............\.......\.-......./-..........\.\..........|...........-......../.\/.... +.-.........................../..............................-...............................-................. +.\..|..|.....-........./....\|./......\............/................................................./........ +..................../\....-..\.-.............../.....|........../../....\..............\...../..\...........|. +...\|....|../.\.....|...\-............\......|....|...................\..........\......|..|.-...../..-...\.|. +..\../......./........|.....\..............\...../...\...\-...\......|../..-|.-.......|....|........\........\ +-\................\..\.|........-.........|..................|..............................................|. +...........|...........\........................\.......|........././............./.../|..........|...||....|. +.......\....|../...\.\....................\......\..............-............./.........................\..... +................/..-......./-................-....-.....|............\............../\.../.................... +.-.......................................-|...-..\............/........../...|\..\.............-........\..-.. +.........-........-.................\../..../............................................./......|..../....... +|..-........-.......\......................................./...|.|.............\.......|.....\.\............. +...-.-........|.|.............../......|......................................-.........\..-..\.........|./... +........-.............\./................................/.|./.|....-..../.........\.............\............ +...........-.|.|........./....-.............|......\...............-..|........./.........-....-......-....... +.-...../.......................|.............|...-../.....................-....|.-.................-.......|.. +.-......-.../............-.......|............................-...../..../..\\...../......||......|..|........ +....-....\..........-...........................-.......-|...\..|...././...........|.\............\..\...|/... +..-/.........................-.....|..|\.../.....|.........................\.................|.-.............. +./.............-...\............/..........-...........................-....\../.................../\......... +...-.../.............................-........./......./..\.....................-........-/.|.......././....-. +.....................|\.......|............./.................................\.\...............|/....-....... +.-..|..-.....\...-\..............|..................../.................................-.....|-........\\\... +......./..................|.................................../.............\...-................\/..../...../ +.....-...........|........................-............-.......-./........\............./.........../.../....\ +............................................................-............../.\.............-.................. +........../.|..........................//.............................\...............................\....... +......|.....|..-../../...\.....................-............/......\..../.........|..............\..-......../ +......../...............|........./........|...-.........................\......................./............ +........\..........................||./.......-.../\\..-.......\...\|.............../..................-.....| +................-....-.............|......|.../............................-.........|........./.......|..|... +................./...-..................\.........\|.................|.|.....................-.....-...../.... +..|..........-.....\...........\|...........................-/..............||\.|...../.......||............/. +..../.......\......|....-.../...-...-...................-../................/..................../..|....\.... +.......................\..-.......|./.............\./.....\............\.....-.|......|..../.........||.|..... +...-../...............-.......................\..............\..--.............|....\...............\..|...... +./...-.................|................-.......\....................|...........-|..............-............ +...........\....................|......./...............-.............../........\.\.......................... +..............|.................|.../...............|...................\..................../......\......... +.............................|...|............-....\.....\...../.......\................./.........|.....\.... +.\...........|....................-......|.......\..........||.\........\/.......................|\........... +....../.........\..........................-..|.-.....|./............/........-.............../......./...|... +...........................\.....|.\........\........./......|.....\......../.................|.......-....-.. +.../......\.......................\../\..-...-......\.........../..\....\.../.......................|.-.....\. +.....--.-................|...-/..................../..................................\......|......./........ +................\.....\......|-|-.....-.-./...|................|.\..........-..|.........\..../...|........... +..........-...................\................\..-../...../-.................\./......................../.... +.......//............\........................-................................\................/......|...... +-|..-..........|......-............-.|...........-.............\...../....\................................... +...........-........................./.../..........................................\.............|.\......... +.....................\...\..............\|....................../.../.......|...................\.....-.....|. +.......-.......\......................................\.|............/.\.....-.....-./.../--..\..........|.../ +........../......./......................................-......\................./........................... +.../..|\...........|...........-.....-.....\/.......-.......|.....\....../...............\...-................ +.................../.........-........................-..-...\.\.........\.....|....-|..|.........../......... +.........../.........|..........-.......-.......................-\.........................-\....../.......... +...../....\.............-.........\...............|............................\.../||-......../..\.|...../... +../.....|.....\.....\......|..........-..................../.....................................\....|.|..//. +\..............-............-...........................\....\................................-............... +.\..\..|...|....................\........................\....|.........|...\.............../................. +..\...........||........\....../..........|................................/.|..-...-..|..........-..\-.....-. +|.-.\..../.....-............|.......-.....//.|.............-..../............-................................ +.....|...................|..............-.|-......\........\.....................|............................ +.............\...........................\....................-.\...\...........\...............|.....|....... +..........|..............\../.../../........\.....|-.-./....../\.........-...............\.../....-........... +.........../|.............................\............................-/.....|.......|../......||.........../ +.......-\................|./.........-....\........-.-.......-............................/................... +..........|...../.......-|..........-.........|..........-......................|/../......................... +......\.|...............................-........\.....-...................\.-................................ +............................../-....-....../..//......-\........|/............/.....\..........\...-.......... +.........-.....\../................-.........-.-................\..|..........|.........-.....-..|......\..../ +\.............\-..............\......./....................\.....................................|............ +.\./-......................................\........................................-..-......../............. +.|..\......-....\....-.........../....../..\|.....-......-.........../...-....-...............-.../........... +-.........................|\.|/...........--........../........\.\...-.....-....-........-.................... +.|.......|............-.....|...........|.......................\...\.\......-../...................-....--... +............../.....|........-...................||.......-...|.................................-............. +\................/....\........................-................-.\..-........../...\.............|-.......... diff --git a/year2023/day16/input/test_input.txt b/year2023/day16/input/test_input.txt new file mode 100644 index 00000000..d6805ce6 --- /dev/null +++ b/year2023/day16/input/test_input.txt @@ -0,0 +1,10 @@ +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... diff --git a/year2023/day16/part1/build.gradle.kts b/year2023/day16/part1/build.gradle.kts new file mode 100644 index 00000000..8bc29844 --- /dev/null +++ b/year2023/day16/part1/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + val kotlinVersion: String by System.getProperties() + val koverVersion: String by System.getProperties() + + application + kotlin("jvm") version kotlinVersion + id("org.jetbrains.kotlinx.kover") version koverVersion +} + +application { + mainClass.set("com.curtislb.adventofcode.year2023.day16.part1.Year2023Day16Part1Kt") +} + +dependencies { + val assertjVersion: String by properties + val junitJupiterVersion: String by properties + val junitPlatformVersion: String by properties + + implementation(project(":common:geometry")) + implementation(project(":year2023:day16:beam")) + testImplementation("org.assertj:assertj-core:$assertjVersion") + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") +} diff --git a/year2023/day16/part1/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1.kt b/year2023/day16/part1/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1.kt new file mode 100644 index 00000000..b0409fc9 --- /dev/null +++ b/year2023/day16/part1/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1.kt @@ -0,0 +1,120 @@ +/* +--- Day 16: The Floor Will Be Lava --- + +With the beam of light completely focused somewhere, the reindeer leads you deeper still into the +Lava Production Facility. At some point, you realize that the steel facility walls have been +replaced with cave, and the doorways are just cave, and the floor is cave, and you're pretty sure +this is actually just a giant cave. + +Finally, as you approach what must be the heart of the mountain, you see a bright light in a cavern +up ahead. There, you discover that the beam of light you so carefully focused is emerging from the +cavern wall closest to the facility and pouring all of its energy into a contraption on the opposite +side. + +Upon closer inspection, the contraption appears to be a flat, two-dimensional square grid containing +empty space (`.`), mirrors (`/` and `\`), and splitters (`|` and `-`). + +The contraption is aligned so that most of the beam bounces around the grid, but each tile on the +grid converts some of the beam's light into heat to melt the rock in the cavern. + +You note the layout of the contraption (your puzzle input). For example: + +``` +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... +``` + +The beam enters in the top-left corner from the left and heading to the right. Then, its behavior +depends on what it encounters as it moves: + +- If the beam encounters empty space (`.`), it continues in the same direction. +- If the beam encounters a mirror (`/` or `\`), the beam is reflected 90 degrees depending on the + angle of the mirror. For instance, a rightward-moving beam that encounters a `/` mirror would + continue upward in the mirror's column, while a rightward-moving beam that encounters a `\` mirror + would continue downward from the mirror's column. +- If the beam encounters the pointy end of a splitter (`|` or `-`), the beam passes through the + splitter as if the splitter were empty space. For instance, a rightward-moving beam that + encounters a `-` splitter would continue in the same direction. +- If the beam encounters the flat side of a splitter (`|` or `-`), the beam is split into two beams + going in each of the two directions the splitter's pointy ends are pointing. For instance, a + rightward-moving beam that encounters a `|` splitter would split into two beams: one that + continues upward from the splitter's column and one that continues downward from the splitter's + column. + +Beams do not interact with other beams; a tile can have many beams passing through it at the same +time. A tile is energized if that tile has at least one beam pass through it, reflect in it, or +split in it. + +In the above example, here is how the beam of light bounces around the contraption: + +``` +>|<<<\.... +|v-.\^.... +.v...|->>> +.v...v^.|. +.v...v^... +.v...v^..\ +.v../2\\.. +<->-/vv|.. +.|<<<2-|.\ +.v//.|.v.. +``` + +Beams are only shown on empty tiles; arrows indicate the direction of the beams. If a tile contains +beams moving in multiple directions, the number of distinct directions is shown instead. Here is the +same diagram but instead only showing whether a tile is energized (`#`) or not (`.`): + +``` +######.... +.#...#.... +.#...##### +.#...##... +.#...##... +.#...##... +.#..####.. +########.. +.#######.. +.#...#.#.. +``` + +Ultimately, in this example, 46 tiles become energized. + +The light isn't energizing enough tiles to produce lava; to debug the contraption, you need to start +by analyzing the current situation. With the beam starting in the top-left heading right, how many +tiles end up being energized? +*/ + +package com.curtislb.adventofcode.year2023.day16.part1 + +import com.curtislb.adventofcode.common.geometry.Direction +import com.curtislb.adventofcode.common.geometry.Point +import com.curtislb.adventofcode.common.geometry.Pose +import com.curtislb.adventofcode.year2023.day16.beam.BeamContraption +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Returns the solution to the puzzle for 2023, day 16, part 1. + * + * @param inputPath The path to the input file for this puzzle. + * @param beamStart The initial position and direction of the beam. + */ +fun solve( + inputPath: Path = Paths.get("..", "input", "input.txt"), + beamStart: Pose = Pose(Point.ORIGIN, Direction.RIGHT) +): Int { + val contraption = BeamContraption.fromFile(inputPath.toFile()) + return contraption.beamEnergy(beamStart) +} + +fun main() { + println(solve()) +} diff --git a/year2023/day16/part1/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1Test.kt b/year2023/day16/part1/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1Test.kt new file mode 100644 index 00000000..0f2c2f7e --- /dev/null +++ b/year2023/day16/part1/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part1/Year2023Day16Part1Test.kt @@ -0,0 +1,22 @@ +package com.curtislb.adventofcode.year2023.day16.part1 + +import java.nio.file.Paths +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +/** + * Tests the solution to the puzzle for 2023, day 16, part 1. + */ +class Year2023Day16Part1Test { + @Test + fun solve_withRealInput() { + val solution = solve() + assertThat(solution).isEqualTo(8116) + } + + @Test + fun solve_withTestInput() { + val solution = solve(inputPath = Paths.get("..", "input", "test_input.txt")) + assertThat(solution).isEqualTo(46) + } +} diff --git a/year2023/day16/part2/build.gradle.kts b/year2023/day16/part2/build.gradle.kts new file mode 100644 index 00000000..0215df79 --- /dev/null +++ b/year2023/day16/part2/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + val kotlinVersion: String by System.getProperties() + val koverVersion: String by System.getProperties() + + application + kotlin("jvm") version kotlinVersion + id("org.jetbrains.kotlinx.kover") version koverVersion +} + +application { + mainClass.set("com.curtislb.adventofcode.year2023.day16.part2.Year2023Day16Part2Kt") +} + +dependencies { + val assertjVersion: String by properties + val junitJupiterVersion: String by properties + val junitPlatformVersion: String by properties + + implementation(project(":year2023:day16:beam")) + testImplementation("org.assertj:assertj-core:$assertjVersion") + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") +} diff --git a/year2023/day16/part2/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2.kt b/year2023/day16/part2/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2.kt new file mode 100644 index 00000000..5231a8c5 --- /dev/null +++ b/year2023/day16/part2/src/main/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2.kt @@ -0,0 +1,68 @@ +/* +--- Part Two --- + +As you try to work out what might be wrong, the reindeer tugs on your shirt and leads you to a +nearby control panel. There, a collection of buttons lets you align the contraption so that the beam +enters from any edge tile and heading away from that edge. (You can choose either of two directions +for the beam if it starts on a corner; for instance, if the beam starts in the bottom-right corner, +it can start heading either left or upward.) + +So, the beam could start on any tile in the top row (heading downward), any tile in the bottom row +(heading upward), any tile in the leftmost column (heading right), or any tile in the rightmost +column (heading left). To produce lava, you need to find the configuration that energizes as many +tiles as possible. + +In the above example, this can be achieved by starting the beam in the fourth tile from the left in +the top row: + +``` +.|<2<\.... +|v-v\^.... +.v.v.|->>> +.v.v.v^.|. +.v.v.v^... +.v.v.v^..\ +.v.v/2\\.. +<-2-/vv|.. +.|<<<2-|.\ +.v//.|.v.. +``` + +Using this configuration, 51 tiles are energized: + +``` +.#####.... +.#.#.#.... +.#.#.##### +.#.#.##... +.#.#.##... +.#.#.##... +.#.#####.. +########.. +.#######.. +.#...#.#.. +``` + +Find the initial beam configuration that energizes the largest number of tiles; how many tiles are +energized in that configuration? +*/ + +package com.curtislb.adventofcode.year2023.day16.part2 + +import com.curtislb.adventofcode.year2023.day16.beam.BeamContraption +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Returns the solution to the puzzle for 2023, day 16, part 2. + * + * @param inputPath The path to the input file for this puzzle. + */ +fun solve(inputPath: Path = Paths.get("..", "input", "input.txt")): Int { + val contraption = BeamContraption.fromFile(inputPath.toFile()) + return contraption.findMaxBeamEnergy() +} + +fun main() { + println(solve()) +} diff --git a/year2023/day16/part2/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2Test.kt b/year2023/day16/part2/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2Test.kt new file mode 100644 index 00000000..1161be89 --- /dev/null +++ b/year2023/day16/part2/src/test/kotlin/com/curtislb/adventofcode/year2023/day16/part2/Year2023Day16Part2Test.kt @@ -0,0 +1,22 @@ +package com.curtislb.adventofcode.year2023.day16.part2 + +import java.nio.file.Paths +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +/** + * Tests the solution to the puzzle for 2023, day 16, part 2. + */ +class Year2023Day16Part2Test { + @Test + fun solve_withRealInput() { + val solution = solve() + assertThat(solution).isEqualTo(8383) + } + + @Test + fun solve_withTestInput() { + val solution = solve(inputPath = Paths.get("..", "input", "test_input.txt")) + assertThat(solution).isEqualTo(51) + } +}