diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24.kt b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24.kt new file mode 100644 index 00000000..700c461e --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24.kt @@ -0,0 +1,132 @@ +package me.peckb.aoc._2020.calendar.day24 + +import me.peckb.aoc._2020.calendar.day24.Day24.Step.* +import me.peckb.aoc._2020.calendar.day24.Day24.TileColor.* +import javax.inject.Inject + +import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory + +class Day24 @Inject constructor( + private val generatorFactory: InputGeneratorFactory, +) { + fun partOne(filename: String) = generatorFactory.forFile(filename).readAs(::tilePath) { input -> + val flippedTiles = initialTileLayout(input) + flippedTiles.count { it.value == BLACK } + } + + fun partTwo(filename: String) = generatorFactory.forFile(filename).readAs(::tilePath) { input -> + val flippedTiles = initialTileLayout(input) + + repeat(100) { + val tilesToFlip = mutableSetOf() + flippedTiles + .filter { flippedTiles.getOrDefault(it.key, WHITE) == BLACK } + .flatMap { it.key.neighborLocations.plus(it.key) } + .forEach { location -> + val color = flippedTiles.getOrDefault(location, WHITE) + val neighbors = location.neighborLocations + val blackNeighborTileCount = neighbors.mapNotNull { flippedTiles[it] }.count { it == BLACK } + + when (color) { + BLACK -> if (0 == blackNeighborTileCount || 2 < blackNeighborTileCount) tilesToFlip.add(location) + WHITE -> if (2 == blackNeighborTileCount) tilesToFlip.add(location) + } + } + tilesToFlip.forEach { + flippedTiles[it] = flippedTiles.getOrDefault(it, WHITE).flip() + } + } + + flippedTiles.count { it.value == BLACK } + } + + private fun initialTileLayout(input: Sequence>): MutableMap { + val flippedTiles = mutableMapOf() + + input.forEach { steps -> + var x = 0 + var y = 0 + var z = 0 + + steps.forEach { step -> + when (step) { + // East / West don't change the Z + // North East / South West don't change the Y + // South East / North West don't change the X + EAST -> { x += 1; y -= 1 } + WEST -> { x -= 1; y += 1 } + NORTH_EAST -> { x += 1; z -= 1 } + SOUTH_WEST -> { x -= 1; z += 1 } + SOUTH_EAST -> { y -= 1; z += 1 } + NORTH_WEST -> { y += 1; z -= 1 } + } + } + + val location = Location(x, y, z) + + flippedTiles[location] = flippedTiles.getOrDefault(location, WHITE).flip() + } + + return flippedTiles + } + + private fun tilePath(line: String): List { + val iterator = line.iterator() + val steps = mutableListOf() + + while(iterator.hasNext()) { + steps.add( + when (val d = iterator.nextChar()) { + 'e' -> EAST + 'w'-> WEST + 's' -> { + when (val d2 = iterator.nextChar()) { + 'e' -> SOUTH_EAST + 'w' -> SOUTH_WEST + else -> throw IllegalStateException("Invalid Step End: $d2") + } + } + 'n' -> { + when (val d2 = iterator.nextChar()) { + 'e' -> NORTH_EAST + 'w' -> NORTH_WEST + else -> throw IllegalStateException("Invalid Step End: $d2") + } + } + else -> throw IllegalStateException("Invalid Step Start: $d") + } + ) + } + + return steps + } + + enum class Step { + // e, se, sw, w, nw, and ne + EAST, SOUTH_EAST, SOUTH_WEST, WEST, NORTH_WEST, NORTH_EAST + } + + data class Location( + val x: Int, + val y: Int, + val z: Int, + ) { + val neighborLocations by lazy { + listOf( + Location(x + 1, y - 1, z), + Location(x - 1, y + 1, z), + Location(x + 1, y, z - 1), + Location(x - 1, y, z + 1), + Location(x, y - 1, z + 1), + Location(x, y + 1, z - 1), + ) + } + } + + enum class TileColor { + BLACK { override fun flip(): TileColor = WHITE }, + WHITE { override fun flip(): TileColor = BLACK }; + + abstract fun flip(): TileColor + } +} diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/README.md b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/README.md new file mode 100644 index 00000000..d5fd8f85 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day24/README.md @@ -0,0 +1 @@ +## [Day 24: Lobby Layout](https://adventofcode.com/2020/day/24) \ No newline at end of file diff --git a/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt b/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt index fcaf3bcc..029ed2e2 100644 --- a/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt +++ b/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt @@ -23,6 +23,7 @@ import me.peckb.aoc._2020.calendar.day20.Day20Test import me.peckb.aoc._2020.calendar.day21.Day21Test import me.peckb.aoc._2020.calendar.day22.Day22Test import me.peckb.aoc._2020.calendar.day23.Day23Test +import me.peckb.aoc._2020.calendar.day24.Day24Test import javax.inject.Singleton import me.peckb.aoc.DayComponent @@ -55,4 +56,5 @@ internal interface TestDayComponent : DayComponent { fun inject(day21Test: Day21Test) fun inject(day22Test: Day22Test) fun inject(day23Test: Day23Test) + fun inject(day24Test: Day24Test) } diff --git a/src/test/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24Test.kt b/src/test/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24Test.kt new file mode 100644 index 00000000..b693dfe3 --- /dev/null +++ b/src/test/kotlin/me/peckb/aoc/_2020/calendar/day24/Day24Test.kt @@ -0,0 +1,33 @@ +package me.peckb.aoc._2020.calendar.day24 + +import javax.inject.Inject + + +import me.peckb.aoc._2020.DaggerTestDayComponent +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class Day24Test { + @Inject + lateinit var day24: Day24 + + @BeforeEach + fun setup() { + DaggerTestDayComponent.create().inject(this) + } + + @Test + fun testDay24PartOne() { + assertEquals(312, day24.partOne(DAY_24)) + } + + @Test + fun testDay24PartTwo() { + assertEquals(3733, day24.partTwo(DAY_24)) + } + + companion object { + private const val DAY_24: String = "advent-of-code-input/2020/day24.input" + } +}