Coverage Summary for Class: Contraption (day16p1)
Class |
Method, %
|
Line, %
|
Contraption |
100%
(7/7)
|
100%
(14/14)
|
Contraption$toText$1 |
100%
(1/1)
|
100%
(1/1)
|
Contraption$toText$1$1 |
100%
(1/1)
|
100%
(1/1)
|
Contraption$toTextWithEnergizedTiles$1 |
100%
(1/1)
|
100%
(1/1)
|
Contraption$toTextWithEnergizedTiles$1$1 |
100%
(1/1)
|
100%
(1/1)
|
Contraption$toTextWithLightBeams$1 |
100%
(1/1)
|
100%
(1/1)
|
Contraption$toTextWithLightBeams$1$1 |
100%
(1/1)
|
100%
(1/1)
|
Total |
100%
(13/13)
|
100%
(20/20)
|
package day16p1
import day16p1.Direction.*
import day16p1.TileType.Companion.toTileType
import day16p1.TileType.EmptySpace
fun String.countEnergizedTiles(): Int {
val contraption = toContraption()
contraption.simulateLightBeam()
return contraption.countEnergizedTiles()
}
fun String.toContraption(): Contraption = Contraption(
lines().mapIndexed { row, line -> line.mapIndexed { col, ch -> Tile(ch.toTileType(), row, col) } }
)
class Contraption(val rows: List<List<Tile>>) {
fun row(row: Int): List<Tile> = rows[row]
fun toText(): String = rows.joinToString("\n") { row ->
row.joinToString("") { it.toSign().toString() }
}
fun toTextWithLightBeams(): String = rows.joinToString("\n") { row ->
row.joinToString("") { it.toSignWithLightBeam().toString() }
}
fun toTextWithEnergizedTiles(): String = rows.joinToString("\n") { row ->
row.joinToString("") { if (it.energized) "#" else "." }
}
fun simulateLightBeam() {
val beamsToFollow = mutableListOf(Right to rows[0][0])
while (beamsToFollow.isNotEmpty()) {
val (direction, tile) = beamsToFollow.removeFirst()
tile.simulateLightBeam(direction).forEach { newDirection ->
val (newRow, newCol) = newDirection.position(tile.row, tile.col)
if (newRow < 0 || newRow >= rows.size || newCol < 0 || newCol >= rows.first().size) return@forEach
beamsToFollow.add(newDirection to rows[newRow][newCol])
}
}
}
fun countEnergizedTiles(): Int = rows.asSequence().flatten().count { it.energized }
}
class Tile(val type: TileType, val row: Int, val col: Int) {
val lightBeams: MutableSet<Direction> = mutableSetOf()
var energized: Boolean = false
fun toSign(): Char = type.sign
fun toSignWithLightBeam(): Char = if (type != EmptySpace) toSign() else when (lightBeams.size) {
0 -> toSign()
1 -> lightBeams.first().sign
else -> lightBeams.size.toString()[0]
}
fun simulateLightBeam(direction: Direction): Set<Direction> {
if (lightBeams.contains(direction)) return emptySet()
energized = true
lightBeams.add(direction)
return type.directLightGoing(direction)
}
}
enum class Direction(val sign: Char) {
Right('>') {
override fun position(row: Int, col: Int) = row to col + 1
},
Left('<') {
override fun position(row: Int, col: Int) = row to col - 1
},
Up('^') {
override fun position(row: Int, col: Int) = row - 1 to col
},
Down('v') {
override fun position(row: Int, col: Int) = row + 1 to col
};
abstract fun position(row: Int, col: Int): Pair<Int, Int>
}
enum class TileType(val sign: Char) {
EmptySpace('.') {
override fun directLightGoing(direction: Direction): Set<Direction> = setOf(direction)
},
SlashMirror('/') {
override fun directLightGoing(direction: Direction): Set<Direction> = when (direction) {
Right -> setOf(Up)
Left -> setOf(Down)
Up -> setOf(Right)
Down -> setOf(Left)
}
},
BackslashMirror('\\') {
override fun directLightGoing(direction: Direction): Set<Direction> = when (direction) {
Right -> setOf(Down)
Left -> setOf(Up)
Up -> setOf(Left)
Down -> setOf(Right)
}
},
VerticalSplitter('|') {
override fun directLightGoing(direction: Direction): Set<Direction> = when (direction) {
Right -> setOf(Up, Down)
Left -> setOf(Up, Down)
else -> setOf(direction)
}
},
HorizontalSplitter('-') {
override fun directLightGoing(direction: Direction): Set<Direction> = when (direction) {
Up -> setOf(Right, Left)
Down -> setOf(Right, Left)
else -> setOf(direction)
}
};
abstract fun directLightGoing(direction: Direction): Set<Direction>
companion object {
private val typesBySign = entries.associateBy { it.sign }
fun Char.toTileType(): TileType = typesBySign.getValue(this)
}
}