#!/usr/bin/env kotlin import java.io.File import java.lang.RuntimeException import java.util.Scanner data class MoveResult(val newLocation: Pair, val newDirection: Direction) data class MoveHistory(val location: Pair, val direction: Direction) enum class Direction(val offset: Pair) { NORTH(Pair(0, -1)), EAST(Pair(1, 0)), SOUTH(Pair(0, 1)), WEST(Pair(-1, 0)); fun move(currentLocation: Pair): Pair { 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, blocks: Set> ): MoveResult { var currentDirection = this do { val potentialMove = currentDirection.move(currentLocation) if (potentialMove !in blocks) { return MoveResult(potentialMove, currentDirection) } currentDirection = currentDirection.turnRight() } while (currentDirection != this) throw RuntimeException("Nowhere to go! $currentLocation $this") } } fun isPositionInTheRoom(player: Pair, xMax: Int, yMax: Int): Boolean { return player.first in 0..xMax && player.second in 0..yMax } val scanner = Scanner(System.`in`) val visited = mutableSetOf>() val history = mutableListOf() var playerLocation = Pair(0, 0) var playerDirection: Direction = Direction.NORTH val blocks = mutableSetOf>() val potentialBlocks = mutableSetOf>() 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++ } yMax-- // Should represent the max index. val initialPosition = playerLocation.copy() while (isPositionInTheRoom(playerLocation, xMax, yMax)) { visited.add(playerLocation) if (checkForLoopIfObstacleInFront(playerLocation, playerDirection, history)) { potentialBlocks.add(playerDirection.move(playerLocation)) } history.add(MoveHistory(playerLocation, playerDirection)) 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, playerDirection: Direction, history: MutableList ): Boolean { val potentialBlockLocation = playerDirection.move(playerLocation) val nextMove = playerDirection.moveByPartOneRules(playerLocation, blocks) val alreadyVisited = potentialBlockLocation in visited val pathAlreadyBlocked = potentialBlockLocation != nextMove.newLocation val positionNotInTheRoom = !isPositionInTheRoom(potentialBlockLocation, xMax, yMax) if (alreadyVisited || pathAlreadyBlocked || positionNotInTheRoom) { return false } var localLocation = playerLocation var localDirection = playerDirection val localHistory = history.toMutableList() val localBlocks = blocks.toMutableSet() localBlocks.add(potentialBlockLocation) while (MoveHistory(localLocation, localDirection) !in localHistory) { localHistory.add(MoveHistory(localLocation, localDirection)) val (newLocation, newDirection) = localDirection.moveByPartOneRules(localLocation, localBlocks) localLocation = newLocation localDirection = newDirection if (!isPositionInTheRoom(localLocation, xMax, yMax)) { return false } } return true; }