generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathY2023D16.kt
127 lines (107 loc) · 4.63 KB
/
Y2023D16.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package aockt.y2023
import aockt.util.parse
import aockt.util.spacial.Area
import aockt.util.spacial.Direction
import aockt.util.spacial.Direction.*
import aockt.util.spacial.Point
import aockt.util.spacial.move
import aockt.y2023.Y2023D16.Element.*
import io.github.jadarma.aockt.core.Solution
object Y2023D16 : Solution {
/** The type of element a beam of light can encounter. */
private enum class Element { LeftMirror, RightMirror, HorizontalSplitter, VerticalSplitter }
/**
* A mirror maze that can focus lasers.
* @param content The elements in the maze and their location.
*/
private class MirrorMaze(content: Iterable<Pair<Point, Element>>) {
private val grid: MutableMap<Point, Element> = content
.associate { it.first to it.second }
.toMutableMap()
/** The area of the maze. */
val area: Area = Area(grid.keys)
/** Get the element at the given [point], if it exists. */
operator fun get(point: Point): Element? = grid[point]
/**
* Propagate a laser beam starting from a [source] location and [heading] into a direction.
*
* @param source Where this beam starts from.
* @param heading The direction of the beam.
* @return All points in the maze that the beam and all its splits energize at least once.
* The points are not given in order, as the beam can split multiple times.
*/
fun beam(source: Point, heading: Direction): Set<Point> = buildSet {
// The laser might be split into a loop, this acts like a circuit breaker.
// Direction also is a factor, since it's valid for beams to cross each other.
val seen = mutableSetOf<Pair<Point, Direction>>()
fun beamBranch(source: Point, heading: Direction) {
var point = source
var direction = heading
while (point in area) {
if (point to direction in seen) return
seen.add(point to direction)
add(point)
direction = when (get(point)) {
null -> direction
LeftMirror -> when (direction) {
Left -> Up
Right -> Down
Down -> Right
Up -> Left
}
RightMirror -> when (direction) {
Left -> Down
Right -> Up
Up -> Right
Down -> Left
}
HorizontalSplitter -> when (direction) {
Left, Right -> direction
Up, Down -> Left.also { beamBranch(point.move(Right), Right) }
}
VerticalSplitter -> when (direction) {
Up, Down -> direction
Left, Right -> Up.also { beamBranch(point.move(Down), Down) }
}
}
point = point.move(direction)
}
}
beamBranch(source, heading)
}
}
/** Parse the [input] and return the [MirrorMaze]. */
private fun parseInput(input: String): MirrorMaze = parse {
fun parseElement(char: Char): Element? = when (char) {
'\\' -> LeftMirror
'/' -> RightMirror
'|' -> VerticalSplitter
'-' -> HorizontalSplitter
'.' -> null
else -> error("Unknown char: $char.")
}
input
.lines()
.asReversed()
.flatMapIndexed { y, line -> line.mapIndexed { x, c -> parseElement(c)?.let { Point(x, y) to it } } }
.filterNotNull()
.let(::MirrorMaze)
}
override fun partOne(input: String): Any {
val maze = parseInput(input)
return with(maze.area) { Point(xRange.first, yRange.last) }
.let { source -> maze.beam(source, Right) }
.count()
}
override fun partTwo(input: String): Any {
val maze = parseInput(input)
return buildList {
with(maze.area) {
for (y in yRange) add(maze.beam(Point(xRange.first, y), Right))
for (x in xRange) add(maze.beam(Point(x, yRange.last), Down))
for (y in yRange) add(maze.beam(Point(xRange.last, y), Left))
for (x in xRange) add(maze.beam(Point(x, yRange.first), Up))
}
}.maxOf { beam -> beam.count() }
}
}