17. den byl o simulaci toku vody a napouštění do různých nádržích pod sebou. Nicméně ačkoliv mi voda tekla celkem pěkně, správnou odpověď jsem nezískal. Můj časový limit pro dnešek vypršel a tak si opět počkám, co bude zítra 🙂 . Můj kód, na kterém jsem skončil je zde:

import com.ichipsea.kotlin.matrix.createMutableMatrix
import com.ichipsea.kotlin.matrix.toList
import com.svetylkovo.adventofcode2018.day17.SeekerDirection.*
import com.svetylkovo.adventofcode2018.day17.SoilType.*
import com.svetylkovo.adventofcode2018.toNonSeparatedPrettyString
import com.svetylkovo.rojo.Rojo
import java.io.File
import kotlin.streams.toList

enum class SeekerDirection {
    LEFT, RIGHT, DOWN, UP
}

enum class SoilType(val sign: Char) {
    CLAY('#'), SAND('.'), SPRING('+'), WATER('W');

    override fun toString() = "$sign"
}

data class ClaySquare(var x: Int, var y: Int)

class WaterSimulator(
    val width: Int,
    val height: Int,
    val springX: Int,
    val springY: Int,
    val claySquares: List<ClaySquare>
) {
    var seekStep = 0

    var seekerX = springX
    var seekerY = springY
    var seekerDirection = DOWN

    val flowBranches = mutableListOf<Pair<Int, Int>>()

    val leftFallMap = mutableMapOf<Int, MutableList<Pair<Int, Int>>>()
    val rightFallMap = mutableMapOf<Int, MutableList<Pair<Int, Int>>>()

    val matrix = createMutableMatrix(width, height) { x, y ->
        when {
            x == springX && y == springY -> SPRING
            claySquares.any { it.x == x && it.y == y } -> CLAY
            else -> SAND
        }
    }

    fun flow() {

        seekWaterPath()

        while (flowBranches.isNotEmpty()) {
            val nextCoords = flowBranches.removeAt(0)
            seekerX = nextCoords.first
            seekerY = nextCoords.second
            seekerDirection = RIGHT

            seekWaterPath()
        }

    }

    val currentCoords get() = seekerX to seekerY

    private fun seekWaterPath() {
        seekStep++

        mainLoop@
        while (seekerX < width - 1 && seekerY < height - 1) {

            when (seekerDirection) {
                LEFT -> {
                    if (isAtLeftFallOfDifferentStep()) {
                        break@mainLoop
                    }

                    when {
                        matrix[seekerX, seekerY + 1] == SAND -> {
                            seekerDirection = DOWN
                            val leftFall = leftFallMap.computeIfAbsent(seekStep) { mutableListOf() }
                            leftFall += currentCoords
                        }
                        matrix[seekerX - 1, seekerY] == CLAY -> seekerDirection = RIGHT
                        else -> seekerX--
                    }
                }
                RIGHT -> {
                    if (isAtRightFallOfDifferentStep()) {
                        break@mainLoop
                    }

                    when {
                        matrix[seekerX, seekerY + 1] == SAND -> {
                            seekerDirection = DOWN
                            val rightFall = rightFallMap.computeIfAbsent(seekStep) { mutableListOf() }
                            rightFall += currentCoords
                        }
                        matrix[seekerX + 1, seekerY] == CLAY -> seekerDirection = UP
                        else -> seekerX++
                    }
                }
                DOWN -> {
                    when (matrix[seekerX, seekerY + 1]) {
                        WATER -> {
                            seekerDirection = LEFT
                            if (matrix[seekerX + 1, seekerY] != CLAY || matrix[seekerX + 1, seekerY + 1] != CLAY) seekerY++
                        }
                        CLAY -> seekerDirection = LEFT
                        else -> seekerY++
                    }
                }
                UP -> {
                    seekerDirection = LEFT
                    seekerY--

                    if (isAtLeftFallOfDifferentStep() && matrix[seekerX - 1, seekerY] != CLAY) seekerX--

                    if (matrix[seekerX + 1, seekerY] == SAND) {
                        flowBranches += seekerX to seekerY
                    }
                }
            }

            markCurrentAsWater()
        }
    }

    private fun isAtLeftFallOfDifferentStep() = leftFallMap
        .entries
        .find { currentCoords in it.value }
        ?.let { it.key != seekStep }
        ?: false

    private fun isAtRightFallOfDifferentStep() = rightFallMap
        .entries
        .find { currentCoords in it.value }
        ?.let { it.key != seekStep }
        ?: false

    private fun markCurrentAsWater() {
        matrix[seekerX, seekerY] = WATER
    }

    fun coutWaterSquares() = matrix.toList().count { it == WATER }

    override fun toString() = matrix.toNonSeparatedPrettyString()

    companion object {
        fun fromInput(input: String, springX: Int, springY: Int): WaterSimulator {
            val clays = Rojo.map("(\\w)=(\\d+), (\\w)=(\\d+)..(\\d+)", input) { coordA, valueA, coordB, fromB, toB ->
                if (coordA == "x") (fromB.toInt()..toB.toInt()).map { y -> ClaySquare(valueA.toInt(), y) }
                else (fromB.toInt()..toB.toInt()).map { x -> ClaySquare(x, valueA.toInt()) }
            }.toList().flatten().distinctBy { it.x to it.y }

            val xOffset = (clays.map { it.x } + springX).min() ?: 0
            val yOffset = (clays.map { it.y } + springY).min() ?: 0

            clays.forEach {
                it.x -= xOffset
                it.y -= yOffset
            }

            val offsetSpringX = springX - xOffset
            val offsetSpringY = springY - yOffset

            val width = (clays.map { it.x } + offsetSpringX).max()?.plus(1) ?: 0
            val height = (clays.map { it.y } + offsetSpringY).max()?.plus(1) ?: 0

            return WaterSimulator(width, height, offsetSpringX, offsetSpringY, clays)
        }
    }
}


fun main() {
    val input = File("input17.txt").readText()
    val waterSimulator = WaterSimulator.fromInput(input, 500, 0)
    waterSimulator.flow()

    println(waterSimulator.coutWaterSquares())

    println(waterSimulator.toString())
}