Další z ne-úplně lehkých dnů. Nyní již bylo zapotřebí trochy odhadu a víry, že se člověk vydal správnou cestou 🙂 .

import com.svetylkovo.rojo.Rojo
import com.svetylkovo.rojo.annotations.Group
import com.svetylkovo.rojo.annotations.Regex
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.OutputStream

@Regex("(-?\\d+).+?(-?\\d+).+?(-?\\d+).+?(-?\\d+)")
data class Star(
    @Group(1)
    var x: Long = 0,
    @Group(2)
    var y: Long = 0,
    @Group(3)
    var vx: Int = 0,
    @Group(4)
    var vy: Int = 0
) {
    /**
     * Direction can be either 1 (forward movement) or -1 (backward movement)
     */
    fun move(direction: Int) {
        x += vx * direction
        y += vy * direction
    }
}

class NightSky(val stars: List<Star>) {

    var width: Long = 0
    var height: Long = 0

    val size get() = width * height

    private fun transposeStarsToTopLeft() {
        val xOffset = stars.asSequence()
            .map { it.x }
            .min()
            ?.times(-1) ?: 0

        val yOffset = stars.asSequence()
            .map { it.y }
            .min()
            ?.times(-1) ?: 0

        stars.forEach {
            it.x += xOffset
            it.y += yOffset
        }

        width = stars.maxBy { it.x }?.x ?: 1
        height = stars.maxBy { it.y }?.y ?: 1
    }

    fun moveTheStars(direction: Int = 1) {
        if (direction == 1) {
            transposeStarsToTopLeft()
            stars.forEach { it.move(direction) }
        } else {
            stars.forEach { it.move(direction) }
            transposeStarsToTopLeft()
        }
    }

    fun writeSkyState(out: OutputStream) {
        val writer = out.writer()

        val newline = "\n"
        val star = "#"
        val emptyness = "."

        val starsPosition = stars.asSequence()
            .map { it.x to it.y }
            .toHashSet()

        for (y in 0..height) {
            for (x in 0..width) {
                if (x to y in starsPosition) writer.write(star)
                else writer.write(emptyness)
            }
            writer.write(newline)
            writer.flush()
        }

        writer.flush()
    }

    fun getSkyStateAsString(): String {
        val out = ByteArrayOutputStream()
        writeSkyState(out)
        return out.toString()
    }

}

fun main() {

    val input = File("input10.txt").readText()
    val outDir = File("output10").apply { mkdir() }

    val stars = Rojo.of(Star::class.java).matchList(input)
    val nightSky = NightSky(stars)

    var lastSize = Long.MAX_VALUE

    //A
    println("Searching for minimal step")

    var minimalStepNumeber = 0

    for (step in 1..20000) {
        minimalStepNumeber = step

        nightSky.moveTheStars()
        if (nightSky.size > lastSize) break
        else lastSize = nightSky.size
    }

    repeat(5) { step ->
        println("Generating step $step")
        outDir.resolve("Out$step.txt")
            .outputStream()
            .use { nightSky.writeSkyState(it) }

        nightSky.moveTheStars(-1)
    }

    println("Done, let's investigate the files output!")

    //B
    println("They would have to wait ${minimalStepNumeber - 2} seconds")
}