aboutsummaryrefslogtreecommitdiff
path: root/src/Day25.kt
blob: 309c15e9580adddf74376be3e9016b3b2541db35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class SeaCucumbers(private val width: Int, private val height: Int, private val matrix: Array<SeaTile>) {
    companion object {
        fun fromLines(lines: List<String>) =
            Pos2D(
                lines.getOrNull(0)?.length ?: throw IllegalArgumentException("Sea must have a non-zero height!"),
                lines.size
            ).let { size ->
                SeaCucumbers(size.x, size.y, Array(size.x * size.y) {
                    when (val char = lines[it / size.x][it % size.x]) {
                        '>' -> SeaTile.Cucumber.EastFacing
                        'v' -> SeaTile.Cucumber.SouthFacing
                        '.' -> SeaTile.EmptyTile
                        else -> throw IllegalArgumentException("Found '$char', expected '>', 'v' or '.'!")
                    }
                })
            }
    }

    sealed class SeaTile {
        object EmptyTile : SeaTile()
        sealed class Cucumber : SeaTile() {
            object EastFacing : Cucumber()
            object SouthFacing : Cucumber()
        }
    }

    private fun moveIndex(index: Int, offset: Pos2D) =
        ((index / width + offset.y) % height) * width + (index + offset.x) % width

    private inline fun <reified T : SeaTile.Cucumber> stepDirection(pos: Pos2D) {
        matrix
            .asSequence()
            .withIndex()
            .map { (index, seaTile) ->
                val nextIndex = moveIndex(index, pos)
                if (seaTile is T && matrix[nextIndex] is SeaTile.EmptyTile) {
                    index to nextIndex
                } else {
                    null
                }
            }
            .filterNotNull()
            .toList()
            .forEach { (index, nextIndex) ->
                matrix[nextIndex] = matrix[index].also { matrix[index] = SeaTile.EmptyTile }
            }
    }

    private fun stepOnce() {
        stepDirection<SeaTile.Cucumber.EastFacing>(Pos2D(1, 0))
        stepDirection<SeaTile.Cucumber.SouthFacing>(Pos2D(0, 1))
    }

    fun simulate(): Int {
        var count = 0

        do {
            val previousHashCode = matrix.contentHashCode()
            stepOnce()
            count++
        } while (matrix.contentHashCode() != previousHashCode)

        return count
    }

    override fun toString(): String = matrix
        .withIndex()
        .joinToString("") { (index, seaTile) ->
            when (seaTile) {
                SeaTile.Cucumber.EastFacing -> ">"
                SeaTile.Cucumber.SouthFacing -> "v"
                SeaTile.EmptyTile -> "."
            } + (if (index % width == width - 1) "\n" else "")
        }
}

fun main() {
    fun calculate(input: List<String>) = SeaCucumbers.fromLines(input).simulate()
    
    val testInput = readInputAsLines("Day25_test")
    check(calculate(testInput) == 58)

    val input = readInputAsLines("Day25")
    println(calculate(input))
}