Skip to content

Commit 79a7a9a

Browse files
authored
Merge pull request #8 from sraaphorst/D06
Day 06 complete.
2 parents a17bea4 + e98bcb8 commit 79a7a9a

File tree

4 files changed

+290
-1
lines changed

4 files changed

+290
-1
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
My attempt in Kotlin to complete the [2024 Advent of Code](https://adventofcode.com/2024).
44

5-
**Current status:** Day 5 complete.
5+
**Current status:** Day 5 complete.
6+
7+
**Last updated:** 2024-12-05.

src/main/kotlin/day06/day06.kt

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Advent of Code 2024, Day 06.
2+
// By Sebastian Raaphorst, 2024.
3+
4+
// NOTE: Trying to use pure FP in this question made part 2 run extremely slowly.
5+
// Mutable data structures are needed to avoid having to continuously copy structures.
6+
7+
package day06
8+
9+
import common.day
10+
import common.readInput
11+
12+
typealias Point = Pair<Int, Int>
13+
14+
/**
15+
* The direction that the guard is facing and moves in.
16+
*/
17+
enum class Direction(val delta: Point) {
18+
NORTH(-1 to 0),
19+
SOUTH(1 to 0),
20+
EAST(0 to 1),
21+
WEST(0 to -1);
22+
23+
fun clockwise(): Direction = when (this) {
24+
NORTH -> EAST
25+
EAST -> SOUTH
26+
SOUTH -> WEST
27+
WEST -> NORTH
28+
}
29+
}
30+
31+
private operator fun Point.plus(other: Point): Point =
32+
(first + other.first) to (second + other.second)
33+
34+
typealias Orientation = Pair<Direction, Point>
35+
36+
data class MapGrid(val rows: Int,
37+
val cols: Int,
38+
val boundaries: Set<Point>) {
39+
fun isBoundary(point: Point): Boolean =
40+
point in boundaries
41+
42+
fun isOutOfBounds(point: Point): Boolean =
43+
point.first !in 0 until rows || point.second !in 0 until cols
44+
}
45+
46+
data class Guard(val startPosition: Point,
47+
val map: MapGrid) {
48+
/**
49+
* Simulate the guard's path and return either:
50+
* 1. A set of visited points if it escapes.
51+
* 2. Null if it enters a loop.
52+
*/
53+
fun move(addedPoint: Point? = null): Set<Point>? {
54+
val visitedPoints = mutableSetOf<Point>()
55+
val visitedStates = mutableSetOf<Orientation>()
56+
57+
var currentPosition = startPosition
58+
var currentDir = Direction.NORTH
59+
60+
while (!map.isOutOfBounds(currentPosition)) {
61+
visitedPoints.add(currentPosition)
62+
63+
// If we return to a state we already visited, we have detected a cycle.
64+
val state = currentDir to currentPosition
65+
if (state in visitedStates) return null
66+
visitedStates.add(state)
67+
68+
// Move forward or turn if hitting a boundary
69+
val nextPosition = currentPosition + currentDir.delta
70+
if (map.isBoundary(nextPosition) || (addedPoint != null && nextPosition == addedPoint))
71+
currentDir = currentDir.clockwise()
72+
else
73+
currentPosition = nextPosition
74+
}
75+
return visitedPoints
76+
}
77+
}
78+
79+
/**
80+
* Parse the input into a Guard and MapGrid.
81+
*/
82+
fun parseProblem(input: String): Guard {
83+
var startPosition: Point? = null
84+
val barriers = mutableSetOf<Point>()
85+
86+
input.trim().lines().forEachIndexed { x, row ->
87+
row.forEachIndexed { y, ch -> when (ch) {
88+
'^' -> startPosition = Point(x, y)
89+
'#' -> barriers.add(Point(x, y))
90+
} }
91+
}
92+
93+
val rows = input.lines().size
94+
val cols = input.lines().first().length
95+
val map = MapGrid(rows, cols, barriers)
96+
return Guard(startPosition ?: error("No start position found"), map)
97+
}
98+
99+
fun answer1(guard: Guard): Int =
100+
guard.move()?.size ?: error("Could not calculate")
101+
102+
fun answer2(guard: Guard): Int {
103+
val originalPath = guard.move() ?: error("Could not calculate")
104+
105+
// We want the number of nulls, i.e. the number of times the guard gets in a cycle.
106+
return originalPath.count { candidatePoint ->
107+
guard.move(candidatePoint) == null
108+
}
109+
}
110+
111+
fun main() {
112+
val input = parseProblem(readInput({}::class.day()).trim())
113+
114+
println("--- Day 6: Guard Gallivant ---")
115+
116+
// Part 1: 5208
117+
println("Part 1: ${answer1(input)}")
118+
119+
// Part 2: 1972
120+
println("Part 2: ${answer2(input)}")
121+
}

0 commit comments

Comments
 (0)