Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/main/kotlin/day14/day14.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Advent of Code 2024, Day 14.
// By Sebastian Raaphorst, 2024.

package day14

import common.aocreader.fetchAdventOfCodeInput
import common.runner.timedFunction
import common.vec2d.*

data class Room(val cols: Int, val rows: Int)

// In this case, the positions are the cols and the rows as opposed to the normal
// (rows, cols) input.
class Robot(private val startPos: Vec2DInt, val velocity: Vec2DInt) {
var pos = startPos

fun move(room: Room) {
pos = pos + velocity

// This is toroidal, so we need to modulo the room size.
pos = Vec2D.int((pos.x + room.cols) % room.cols, (pos.y + room.rows) % room.rows)
}

companion object {
private val regex = Regex("""p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)""")

fun parse(input: String): Robot {
val match = regex.matchEntire(input.trim()) ?: error("Invalid input $input")
val (x, y, vx, vy) = match.destructured
return Robot(Vec2D.int(x.toInt(), y.toInt()), Vec2D.int(vx.toInt(), vy.toInt()))
}
}
}

// This is a nasty hack to get the room size right, but this was a nasty puzzle.
fun parse(input: String): Pair<Room, List<Robot>> {
val room = if (input.lines().size < 20) Room(11, 7) else Room(101, 103)
val robots = input.trim().lines().map(Robot::parse)
return Pair(room, robots)
}

fun answer1(input: String): Int {
val (room, robots) = parse(input)
(1..100).forEach {
robots.forEach { robot -> robot.move(room) }
}

// Now parse the robots in all quarters.
val left = robots.filter { it.pos.x in 0 until (room.cols / 2) }
val right = robots.filter { it.pos.x in (room.cols / 2 + 1) until room.cols }
val nw = left.filter { it.pos.y in 0 until (room.rows / 2) }
val sw = left.filter { it.pos.y in (room.rows / 2 + 1) until room.rows }
val ne = right.filter { it.pos.y in 0 until (room.rows / 2) }
val se = right.filter { it.pos.y in (room.rows / 2 + 1) until room.rows }
return nw.size * sw.size * ne.size * se.size
}

// People said that when the robots were all in unique positions was the
// first time the "Easter egg" was found, so we try this, and it works.
fun answer2(input: String): Int {
val (room, robots) = parse(input)

tailrec fun aux(timer: Int = 0): Int =
if (robots.map { it.pos }.toSet().size == robots.size) timer
else {
robots.forEach { it.move(room) }
aux(timer + 1)
}

return aux()
}

fun main() {
val input = fetchAdventOfCodeInput(2024, 14)
println("--- Day 14: Restroom Redoubt ---")
timedFunction("Part 1") { answer1(input) } // 231221760
timedFunction("Part 2") { answer2(input) } // 6771
}
32 changes: 32 additions & 0 deletions src/test/kotlin/day14/day14.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Advent of Code 2024, Day 14.
// By Sebastian Raaphorst, 2024.

package day14

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class Day14 {
companion object {
val input1 =
"""
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3
""".trimIndent()
}

@Test
fun `Problem 1 example`() {
assertEquals(12, answer1(input1))
}
}
Loading