DEV Community

Discussion on: AoC Day 13: Mine Cart Madness

Collapse
 
jbristow profile image
Jon Bristow • Edited

I promise I'll post my code eventually, I spent too long working on this animation of part1: gfycat.com/EasygoingMassiveHog

Collapse
 
jbristow profile image
Jon Bristow • Edited

Oof, I played with this too long.

Here's it all.

Kotlin Solution

private fun answer1(input: List<String>) =
    step(0, input.findCarts(), input)


private fun answer2(input: List<String>) =
    demolitionStep(0, input.findCarts(), input)

tailrec fun step(
    i: Int,
    carts: List<Cart>,
    tracks: List<String>
): Point {
    val (nextCarts, collisions) =
            moveCarts(carts = carts.sorted(), tracks = tracks)

    return when {
        collisions.isEmpty() -> step(i + 1, nextCarts, tracks)
        else -> collisions.first().loc
    }
}

tailrec fun moveCarts(
    carts: List<Cart>,
    moved: List<Cart> = emptyList(),
    collided: List<Cart> = emptyList(),
    tracks: List<String>
): Pair<List<Cart>, List<Cart>> {

    if (carts.isEmpty()) return moved to collided

    val h = carts.head.move(tracks)
    val (ucRem, cRem) = carts.tail.splitBy { it.loc == h.loc }
    val (ucMoved, cMoved) = moved.splitBy { it.loc == h.loc }

    return when {
        cRem.isEmpty() && cMoved.isEmpty() ->
            moveCarts(ucRem, ucMoved + h, collided, tracks)
        cRem.isEmpty() ->
            moveCarts(ucRem, ucMoved, collided + h + cMoved, tracks)
        cMoved.isEmpty() ->
            moveCarts(ucRem, ucMoved, collided + h + cRem, tracks)
        else ->
            moveCarts(ucRem, ucMoved, collided + h + cRem + cMoved, tracks)
    }

}

tailrec fun demolitionStep(
    i: Int,
    carts: List<Cart>,
    tracks: List<String>
): Point {
    val (nextCarts, collisions) = moveCarts(
        carts = carts.sorted(),
        tracks = tracks
    )
    return when {
        carts.count() == 1 -> carts.first().loc
        else -> demolitionStep(i + 1, nextCarts, tracks)
    }
}

enum class Choice {
    LEFT {
        override fun makeChoice(cart: Cart) = when (cart.direction) {
            Direction.RIGHT -> cart.nextPosition(Direction.UP, Choice.LEFT)
            Direction.LEFT -> cart.nextPosition(Direction.DOWN, Choice.LEFT)
            Direction.UP -> cart.nextPosition(Direction.LEFT, Choice.LEFT)
            Direction.DOWN -> cart.nextPosition(Direction.RIGHT, Choice.LEFT)
        }
    },
    RIGHT {
        override fun makeChoice(cart: Cart) = when (cart.direction) {
            Direction.LEFT -> cart.nextPosition(Direction.UP, Choice.RIGHT)
            Direction.RIGHT -> cart.nextPosition(Direction.DOWN, Choice.RIGHT)
            Direction.UP -> cart.nextPosition(Direction.RIGHT, Choice.RIGHT)
            Direction.DOWN -> cart.nextPosition(Direction.LEFT, Choice.RIGHT)
        }
    },
    STRAIGHT {
        override fun makeChoice(cart: Cart) =
            when (cart.direction) {
                Direction.RIGHT, Direction.LEFT ->
                    cart.nextPosition(cart.direction, Choice.STRAIGHT)
                Direction.DOWN, Direction.UP ->
                    cart.nextPosition(cart.direction, Choice.STRAIGHT)
            }
    };

    abstract fun makeChoice(cart: Cart): Cart
}

enum class Direction(val char: Char) {
    UP('^') {
        override fun turnBack() = LEFT
        override fun turnForward() = RIGHT
        override fun move(loc: Point) = Point(loc.x, loc.y - 1)
    },
    DOWN('v') {
        override fun turnBack() = RIGHT
        override fun turnForward() = LEFT
        override fun move(loc: Point) = Point(loc.x, loc.y + 1)
    },
    LEFT('<') {
        override fun turnBack() = UP
        override fun turnForward() = DOWN
        override fun move(loc: Point) = Point(loc.x - 1, loc.y)
    },
    RIGHT('>') {
        override fun turnBack() = DOWN
        override fun turnForward() = UP
        override fun move(loc: Point) = Point(loc.x + 1, loc.y)
    };

    abstract fun move(loc: Point): Point
    abstract fun turnBack(): Direction
    abstract fun turnForward(): Direction

    override fun toString(): String = this.char.toString()
}

fun Char.toDirection(): Direction? {
    return Direction.values().find { it.char == this }
}


fun <E> Direction?.whenNotNull(function: (Direction) -> E): E? = when {
    this != null -> function(this)
    else -> null
}

class OffTheRailsException(cart: Point) : Exception("Off the rails! $cart")

data class Cart(
    val loc: Point,
    val direction: Direction,
    val lastChoice: Choice?,
    val id: Int
) : Comparable<Cart> {
    override fun compareTo(other: Cart) =
        when (val ycomp = y.compareTo(other.y)) {
            0 -> x.compareTo(other.x)
            else -> ycomp
        }

    constructor(loc: Point, direction: Direction, lastChoice: Choice?) : this(
        loc,
        direction,
        lastChoice,
        loc.hashCode() + direction.hashCode()
    )

}

fun Cart.nextPosition(direction: Direction, lastChoice: Choice?) =
    Cart(direction.move(loc), direction, lastChoice, id)

fun Cart.nextPosition(direction: Direction) =
    Cart(direction.move(loc), direction, lastChoice, id)

// '\'
fun Cart.turnBackCorner() = nextPosition(direction.turnBack())

// '/'
fun Cart.turnForwardCorner() = nextPosition(direction.turnForward())


val Cart.x: Int get() = loc.x
val Cart.y: Int get() = loc.y

fun Cart.move(tracks: List<String>) = when (tracks[y][x]) {
    '|', '^', 'v' -> nextPosition(
        direction
    )
    '-', '<', '>' -> nextPosition(
        direction
    )
    '\\' -> turnBackCorner()
    '/' -> turnForwardCorner()
    '+' -> intersection()
    else -> throw OffTheRailsException(loc)
}

fun Cart.intersection() = when (lastChoice) {
    null, Choice.RIGHT -> Choice.LEFT.makeChoice(this)
    Choice.STRAIGHT -> Choice.RIGHT.makeChoice(this)
    Choice.LEFT -> Choice.STRAIGHT.makeChoice(this)
}

fun <E> List<E>.splitBy(predicate: (E) -> Boolean) =
    groupBy(predicate).let {
        (it[false] ?: emptyList()) to (it[true] ?: emptyList())
    }

fun List<String>.findCarts() =
    mapIndexed { y, xl ->
        xl.mapIndexedNotNull { x, c ->
            c.toDirection().whenNotNull {
                Cart(Point(x, y), it, null)
            }
        }
    }.flatten()