2024-12-06 08:56:47 +00:00
|
|
|
#!/usr/bin/env kotlin
|
|
|
|
import java.io.File
|
|
|
|
import java.lang.RuntimeException
|
|
|
|
import java.util.Scanner
|
|
|
|
|
2024-12-06 10:25:52 +00:00
|
|
|
data class LocationState(val location: Pair<Int, Int>, val direction: Direction)
|
2024-12-06 08:56:47 +00:00
|
|
|
enum class Direction(val offset: Pair<Int, Int>) {
|
|
|
|
NORTH(Pair(0, -1)),
|
|
|
|
EAST(Pair(1, 0)),
|
|
|
|
SOUTH(Pair(0, 1)),
|
|
|
|
WEST(Pair(-1, 0));
|
|
|
|
|
|
|
|
fun move(currentLocation: Pair<Int, Int>): Pair<Int, Int> {
|
|
|
|
return Pair(
|
|
|
|
currentLocation.first + offset.first,
|
|
|
|
currentLocation.second + offset.second
|
|
|
|
)
|
|
|
|
}
|
|
|
|
fun turnLeft(): Direction {
|
|
|
|
return when (this) {
|
|
|
|
NORTH -> WEST
|
|
|
|
WEST -> SOUTH
|
|
|
|
SOUTH -> EAST
|
|
|
|
EAST -> NORTH
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fun turnRight(): Direction {
|
|
|
|
return when (this) {
|
|
|
|
NORTH -> EAST
|
|
|
|
EAST -> SOUTH
|
|
|
|
SOUTH -> WEST
|
|
|
|
WEST -> NORTH
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fun moveByPartOneRules(
|
|
|
|
currentLocation: Pair<Int, Int>,
|
|
|
|
blocks: Set<Pair<Int, Int>>
|
2024-12-06 10:25:52 +00:00
|
|
|
): LocationState {
|
2024-12-06 08:56:47 +00:00
|
|
|
var currentDirection = this
|
|
|
|
do {
|
|
|
|
val potentialMove = currentDirection.move(currentLocation)
|
|
|
|
if (potentialMove !in blocks) {
|
2024-12-06 10:25:52 +00:00
|
|
|
return LocationState(potentialMove, currentDirection)
|
2024-12-06 08:56:47 +00:00
|
|
|
}
|
|
|
|
currentDirection = currentDirection.turnRight()
|
|
|
|
} while (currentDirection != this)
|
|
|
|
throw RuntimeException("Nowhere to go! $currentLocation $this")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fun isPositionInTheRoom(player: Pair<Int, Int>, xMax: Int, yMax: Int): Boolean {
|
|
|
|
return player.first in 0..xMax && player.second in 0..yMax
|
|
|
|
}
|
|
|
|
|
2024-12-06 10:25:52 +00:00
|
|
|
fun isLoop(
|
|
|
|
playerLocation: Pair<Int, Int>,
|
|
|
|
playerDirection: Direction,
|
|
|
|
history: List<LocationState>,
|
|
|
|
blocks: MutableSet<Pair<Int, Int>>,
|
|
|
|
potentialBlockLocation: Pair<Int, Int>,
|
|
|
|
xMax: Int,
|
|
|
|
yMax: Int
|
|
|
|
): Boolean {
|
|
|
|
var localLocation = playerLocation
|
|
|
|
var localDirection = playerDirection
|
|
|
|
val localHistory = history.toMutableList()
|
|
|
|
val localBlocks = blocks.toMutableSet()
|
|
|
|
localBlocks.add(potentialBlockLocation)
|
|
|
|
while (LocationState(localLocation, localDirection) !in localHistory) {
|
|
|
|
localHistory.add(LocationState(localLocation, localDirection))
|
|
|
|
val (newLocation, newDirection) = localDirection.moveByPartOneRules(localLocation, localBlocks)
|
|
|
|
localLocation = newLocation
|
|
|
|
localDirection = newDirection
|
|
|
|
if (!isPositionInTheRoom(localLocation, xMax, yMax)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-12-06 08:56:47 +00:00
|
|
|
|
|
|
|
val scanner = Scanner(System.`in`)
|
|
|
|
val visited = mutableSetOf<Pair<Int, Int>>()
|
2024-12-06 10:25:52 +00:00
|
|
|
val history = mutableListOf<LocationState>()
|
2024-12-06 08:56:47 +00:00
|
|
|
var playerLocation = Pair(0, 0)
|
|
|
|
var playerDirection: Direction = Direction.NORTH
|
|
|
|
val blocks = mutableSetOf<Pair<Int, Int>>()
|
|
|
|
val potentialBlocks = mutableSetOf<Pair<Int, Int>>()
|
|
|
|
var yMax = 0
|
|
|
|
var xMax = 0
|
|
|
|
while (scanner.hasNextLine()) {
|
|
|
|
val line = scanner.nextLine()
|
|
|
|
for (i in line.indices) {
|
|
|
|
if (line[i] == '.') {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (line[i] == '>') {
|
|
|
|
playerDirection = Direction.EAST
|
|
|
|
playerLocation = Pair(i, yMax)
|
|
|
|
} else if (line[i] == '<') {
|
|
|
|
playerDirection = Direction.WEST
|
|
|
|
playerLocation = Pair(i, yMax)
|
|
|
|
} else if (line[i] == '^') {
|
|
|
|
playerDirection = Direction.NORTH
|
|
|
|
playerLocation = Pair(i, yMax)
|
|
|
|
} else if (line[i] == 'v') {
|
|
|
|
playerDirection = Direction.SOUTH
|
|
|
|
playerLocation = Pair(i, yMax)
|
|
|
|
} else if (line[i] == '#') {
|
|
|
|
blocks.add(Pair(i, yMax))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xMax = xMax.coerceAtLeast(line.lastIndex)
|
|
|
|
yMax++
|
|
|
|
}
|
2024-12-06 09:31:20 +00:00
|
|
|
yMax-- // Should represent the max index.
|
2024-12-06 08:56:47 +00:00
|
|
|
val initialPosition = playerLocation.copy()
|
|
|
|
while (isPositionInTheRoom(playerLocation, xMax, yMax)) {
|
|
|
|
visited.add(playerLocation)
|
|
|
|
if (checkForLoopIfObstacleInFront(playerLocation, playerDirection, history)) {
|
|
|
|
potentialBlocks.add(playerDirection.move(playerLocation))
|
|
|
|
}
|
2024-12-06 10:25:52 +00:00
|
|
|
history.add(LocationState(playerLocation, playerDirection))
|
2024-12-06 08:56:47 +00:00
|
|
|
val (newLocation, newDirection) = playerDirection.moveByPartOneRules(playerLocation, blocks)
|
|
|
|
playerLocation = newLocation
|
|
|
|
playerDirection = newDirection
|
|
|
|
}
|
|
|
|
|
|
|
|
println("Player location: ${visited.size}")
|
|
|
|
println("Potential blocks: ${potentialBlocks.size}")
|
|
|
|
|
|
|
|
fun checkForLoopIfObstacleInFront(
|
|
|
|
playerLocation: Pair<Int, Int>,
|
|
|
|
playerDirection: Direction,
|
2024-12-06 10:25:52 +00:00
|
|
|
history: MutableList<LocationState>
|
2024-12-06 08:56:47 +00:00
|
|
|
): Boolean {
|
2024-12-06 10:25:52 +00:00
|
|
|
val potentialBlockLocation = playerDirection.moveByPartOneRules(playerLocation, blocks).location
|
2024-12-06 09:31:20 +00:00
|
|
|
val alreadyVisited = potentialBlockLocation in visited
|
|
|
|
val positionNotInTheRoom = !isPositionInTheRoom(potentialBlockLocation, xMax, yMax)
|
2024-12-06 10:25:52 +00:00
|
|
|
if (alreadyVisited || positionNotInTheRoom) {
|
2024-12-06 08:56:47 +00:00
|
|
|
return false
|
|
|
|
}
|
2024-12-06 10:25:52 +00:00
|
|
|
return isLoop(playerLocation, playerDirection, history, blocks, potentialBlockLocation, xMax, yMax)
|
2024-12-06 08:56:47 +00:00
|
|
|
}
|