aboutsummaryrefslogtreecommitdiff
path: root/aoc-2021-kotlin/src/Day25.kt
blob: 36a2f75ac219207d83d906c96a0faffa42564716 (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
86
87
object Day25 {
    private 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 '.'!")
                        }
                    })
                }
        }

        private 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 singlePart(input: List<String>) = SeaCucumbers.fromLines(input).simulate()
}

fun main() {
    val testInput = readInputAsLines("Day25_test")
    check(Day25.singlePart(testInput) == 58)

    val input = readInputAsLines("Day25")
    println(Day25.singlePart(input))
}