DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 15: Oxygen System

Collapse
 
jbristow profile image
Jon Bristow

Really ugly kotlin solution.

I did this without state, and this caused me some wacky problems. I'll reply with a cleaner version later.

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.left
import arrow.core.right
import arrow.core.some
import intcode.CurrentState
import intcode.handleCodePoint
import intcode.toIntCodeProgram
import java.util.LinkedList

sealed class ShipTile {
    object Empty : ShipTile()
    object Oxygen : ShipTile()
    object Wall : ShipTile()
}

data class RepairRobot(
    val code: MutableMap<Long, Long>,
    val position: PointL = PointL(0, 0),
    val map: MutableMap<PointL, ShipTile> = mutableMapOf(PointL(0, 0) to ShipTile.Empty),
    val lastInstruction: Option<Direction> = Option.empty(),
    val state: Either<String, CurrentState> = CurrentState().right(),
    val instructions: LinkedList<Direction> = LinkedList()
)

object Day15 {

    private const val FILENAME = "src/main/resources/day15.txt"
    private val fileData = FILENAME.toIntCodeProgram()
    private tailrec fun RepairRobot.move(): RepairRobot {
        return when {
            instructions.size > 2500 -> this.copy(state = "reached mapping limit".left())
            state is Either.Left<String> -> this
            state is Either.Right<CurrentState> -> when (state.b.pointer) {
                is None -> this
                is Some<Long> -> {
                    val nRobot = this.processReturn().sendDirection(instructions)
                    nRobot.copy(state = handleCodePoint(code, nRobot.state))
                        .move()
                }
                else -> this
            }
            else -> this
        }
    }

    fun part1() {
        val r = RepairRobot(
            code = fileData.toMutableMap(), instructions = LinkedList(allDirections())
        )
            .move()
        println(r.state)
        val path = r.map.path(r.position, listOf(PointL(0, 0) to 0))
        println(path)
        println(r.map.size)
        r.printScreen()
        println(
            fillWithOxygen(
                r.map.filter { it.key != r.position && it.value == ShipTile.Empty }.map { it.key }.toSet(),
                setOf(r.position)
            )
        )
    }

    private tailrec fun fillWithOxygen(
        map: Set<PointL>,
        position: Set<PointL>,
        minutes: Int = 0,
        filled: Int = 0
    ): Int {
        println("map:${map.size}")
        println("position:${position.size}")
        println("minutes:$minutes")
        println("filled:$filled")
        println()
        return when {
            map.isEmpty() -> minutes
            else -> {
                val filling = position.flatMap { p ->
                    allDirections().map(p::inDirection).filter { it in map }
                }.toSet()
                val unfilled = map - filling
                fillWithOxygen(unfilled, filling, minutes + 1, filled + filling.size)
            }
        }
    }
}

private tailrec fun MutableMap<PointL, ShipTile>.path(
    end: PointL,
    queue: List<Pair<PointL, Int>>,
    seen: Set<PointL> = emptySet()
): Int {
    return when {
        queue.isEmpty() -> throw Error("not found")
        queue.first().first == end -> queue.first().second
        else -> {
            val (p, dist) = queue.first()
            val candidates = listOf(
                p.inDirection(Direction.Up()),
                p.inDirection(Direction.Left()),
                p.inDirection(Direction.Down()),
                p.inDirection(Direction.Right())
            ).filter { it !in seen && this[it] != null && this[it] != ShipTile.Wall }
                .map { it to dist + 1 }
            path(
                end, queue.drop(1) + candidates, seen + queue.first().first
            )
        }
    }
}

fun main() {
    Day15.part1()
}

private fun RepairRobot.sendDirection(
    instructions: LinkedList<Direction>
): RepairRobot {

    return when {
        state is Either.Right<CurrentState> && state.b.waitingForInput
                && instructions.isNotEmpty() -> {
            val nInstr = instructions.pop()
            state.b.inputs.add(nInstr.toLong())
            copy(state = state, lastInstruction = nInstr.some())
        }
        state is Either.Right<CurrentState> && state.b.waitingForInput -> copy(state = "No more instructions".left())
        else -> this
    }
}

fun PointL.inDirection(direction: Direction): PointL =
    when (direction) {
        is Direction.Up -> PointL.y.set(this, this.y - 1)
        is Direction.Down -> PointL.y.set(this, this.y + 1)
        is Direction.Left -> PointL.x.set(this, this.x - 1)
        is Direction.Right -> PointL.x.set(this, this.x + 1)
    }

private tailrec fun RepairRobot.processReturn(): RepairRobot {
    return when (state) {
        is Either.Left<String> -> this
        is Either.Right<CurrentState> -> {
            if (state.b.output.isNotEmpty()) {
                val rNext = when (state.b.output.pop()) {
                    0L -> {
                        lastInstruction.map { dir ->
                            map[position.inDirection(dir)] = ShipTile.Wall
                        }
                        this.copy(
                            lastInstruction = Option.empty(),
                            position = position
                        )
                    }
                    1L -> {
                        lastInstruction.map { dir ->
                            map[position.inDirection(dir)] = ShipTile.Empty
                            instructions.push(dir.reverse().turnLeft())
                            instructions.push(dir)
                            instructions.push(dir.turnLeft())
                        }
                        this.copy(
                            lastInstruction = Option.empty(),
                            position = lastInstruction.fold({ position }, { position.inDirection(it) })
                        )
                    }
                    2L -> {
                        lastInstruction.map { dir ->
                            map[position.inDirection(dir)] = ShipTile.Oxygen
                            instructions.push(dir.reverse().turnLeft())
                            instructions.push(dir)
                            instructions.push(dir.turnLeft())
                        }
                        this.copy(
                            lastInstruction = Option.empty(),
                            position = lastInstruction.fold({ position }, { position.inDirection(it) })
//                            state = "Found Oxygen ${lastInstruction.fold( { position }, { position.inDirection(it) })}".left()
                        )
                    }
                    else -> this
                }

                rNext.processReturn()
            } else {
                this
            }
        }
    }
}

fun allDirections(): Collection<Direction> {
    return listOf(
        Direction.Left(),
        Direction.Up(),
        Direction.Right(),
        Direction.Down()
    )
}

private fun Direction.reverse(): Direction {
    return this.turnLeft().turnLeft()
}

fun RepairRobot.printScreen() {
    println(instructions.size)
    val toConsider = map + (position to ShipTile.Empty)
    val topLeft = PointL(
        toConsider.keys.map(PointL::x).min() ?: 0L,
        toConsider.keys.map(PointL::y).min() ?: 0L
    )
    val bottomRight = PointL(
        toConsider.keys.map(PointL::x).max() ?: 0L,
        toConsider.keys.map(PointL::y).max() ?: 0L
    )
    println(
        (topLeft.y..bottomRight.y).joinToString("\n") { y ->
            (topLeft.x..bottomRight.x).joinToString("") { x ->
                when (val p = PointL(x, y)) {
                    position -> lastInstruction.fold({ "B" }, { it.glyph })
                    else -> map[p].toGlyph()
                }
            }
        }
    )
    println("---")
}

private fun ShipTile?.toGlyph(): String {
    return when (this) {
        is ShipTile.Empty -> "."
        is ShipTile.Oxygen -> "O"
        is ShipTile.Wall -> "#"
        null -> " "
    }
}