Dnes se mi povedla pouze část A. I přestože mi část B fungovala pro jejich příklad správně, konečný výsledek z nějakého důvodu správný nebyl – netuším proč, ale screw it 🙂 .

EDIT: Po poradě na Kotlin Slacku jsme přišli na to, že rozbourané vozíky musím odstranit ihned po kolizi ještě v rámci daného “ticku”. Řešení níže je nyní již opraveno.

import com.google.common.collect.Iterators
import com.ichipsea.kotlin.matrix.*
import com.svetylkovo.adventofcode2018.day13.Direction.*
import com.svetylkovo.adventofcode2018.toNonSeparatedPrettyString
import java.io.File

const val CORNER_TILT_RIGHT = '/'
const val CORNER_TILT_LEFT = '\\'
const val TRACK_HORIZONTAL = '-'
const val TRACK_VERTICAL = '|'
const val TRACK_INTERSECTION = '+'
const val TRACK_COLLISION = 'X'
const val EMPTINESS = ' '

enum class Direction(
    val directionCharacter: Char,
    val vector: IntArray
) {
    UP('^', intArrayOf(0, -1)),
    DOWN('v', intArrayOf(0, 1)),
    LEFT('<', intArrayOf(-1, 0)),
    RIGHT('>', intArrayOf(1, 0));

    fun swapVector() = vector.reversedArray()

    fun swapVectorInverted() = vector.reversedArray().map { it * -1 }.toIntArray()

    fun getNewDirection(trackCharacter: Char, nextTurnProvider: () -> Turn): Direction {

        return when (trackCharacter) {
            CORNER_TILT_RIGHT ->
                when (this) {
                    in UP..DOWN -> turnRight()
                    else -> turnLeft()
                }
            CORNER_TILT_LEFT -> when (this) {
                in UP..DOWN -> turnLeft()
                else -> turnRight()
            }
            TRACK_INTERSECTION -> when (nextTurnProvider()) {
                Turn.RIGHT -> turnRight()
                Turn.LEFT -> turnLeft()
                else -> this
            }
            else -> this
        }
    }

    internal fun turnLeft(): Direction {
        val newVector = if (this in UP..DOWN) swapVector() else swapVectorInverted()
        return byVector(newVector)
    }

    internal fun turnRight(): Direction {
        val newVector = if (this in UP..DOWN) swapVectorInverted() else swapVector()
        return byVector(newVector)
    }

    companion object {
        fun parseFrom(value: Char) = values().find { it.directionCharacter == value }
            ?: throw Exception("Couldn't find Direction for $value")

        fun byVector(vector: IntArray) = values().find { it.vector.contentEquals(vector) }
            ?: throw Exception("Couldn't find Direction for $vector")
    }
}

val directionCharacters = values().map { it.directionCharacter }

enum class Turn {
    LEFT, STRAIGHT, RIGHT
}

data class Cart(var x: Int, var y: Int, var direction: Direction) {
    val nextTurn: Iterator<Turn> = Iterators.cycle(Turn.values().asIterable())
    var collided = false

    fun move() {
        direction.run {
            x += vector[0]
            y += vector[1]
        }
    }

    fun tick(map: Matrix<Char>) {
        move()
        direction = direction.getNewDirection(map[x, y]) { nextTurn.next() }
    }
}

class CartTicker(var map: Matrix<Char>) {

    constructor(rawInput: String) : this(rawInput.run {
        val lines = lines()
        val width = lines.map { it.trim() }.maxBy { it.length }?.length ?: 0
        val height = lines.size

        lines.map { it.padEnd(width, EMPTINESS) }
            .flatMap { it.toList() }
            .toMutableMatrix(width, height)
    })

    val carts = map.mapIndexed { x, y, value ->
        when (value) {
            in directionCharacters -> Cart(x, y, Direction.parseFrom(value))
            else -> null
        }
    }.toList().filterNotNull().toMutableList()

    init {
        //replace carts with track symbols in the map
        map = map.map {
            if (it in directionCharacters) {
                val direction = Direction.parseFrom(it)
                when (direction) {
                    UP, DOWN -> TRACK_VERTICAL
                    LEFT, RIGHT -> TRACK_HORIZONTAL
                }
            } else it
        }
    }

    val collisions = mutableMapOf<Pair<Int, Int>, MutableList<Cart>>()
    val collidedCarts = mutableListOf<Cart>()

    fun tick() {
        carts.asSequence()
            .filterNot { it.collided }
            .sortedWith(compareBy({ it.y }, { it.x }))
            .forEach {
                it.tick(map)

                detectCollisions()

                if (collisions.isNotEmpty()) {
                    val newlyCollidedCarts = collisions.values.flatten()
                    collidedCarts+= newlyCollidedCarts
                    carts -= newlyCollidedCarts
                    collisions.clear()
                }
            }
    }

    private fun detectCollisions() {
        carts.groupBy { it.x to it.y }
            .filterValues { it.size > 1 && it.any { !it.collided } }
            .forEach {
                val coords = it.key
                it.value.forEach { it.collided = true }
                collisions.computeIfAbsent(coords) { mutableListOf() }.addAll(it.value)
            }
    }

    fun cartsPosition() = map.mapIndexed { x, y, value ->
        carts.find { it.x == x && it.y == y }?.direction?.directionCharacter ?: value
    }.toNonSeparatedPrettyString()
}


fun main() {
    val input = File("input13.txt").readText()

    val tickerA = CartTicker(input)

    while (tickerA.collidedCarts.isEmpty()) {
        tickerA.tick()
    }

    //A
    println("First collision: ${tickerA.collidedCarts.first()}")

    //B
    val tickerB = CartTicker(input)

    while (tickerB.carts.size > 1) {
        tickerB.run {
            tick()
        }
    }

    println("Last cart standing: ${tickerB.carts.single()}")

}