126 lines
4.5 KiB
Scala
126 lines
4.5 KiB
Scala
package aoc.y2019
|
|
|
|
import cats._
|
|
import cats.implicits._
|
|
import cats.data.{Kleisli, State}
|
|
|
|
object Intcode {
|
|
|
|
case class Machine(memory: Vector[Int], pc: Int = 0, input: Vector[Int] = Vector.empty, output: Vector[Int] = Vector.empty)
|
|
type Operation = State[Machine, Unit]
|
|
|
|
type Instruction = Kleisli[State[Machine, *], LazyList[Boolean], Unit]
|
|
val instructions: Map[Int, Instruction] = Map(
|
|
1 -> Instruction.add,
|
|
2 -> Instruction.multiply,
|
|
3 -> Instruction.input,
|
|
4 -> Instruction.output,
|
|
5 -> Instruction.jumpIfTrue,
|
|
6 -> Instruction.jumpIfFalse,
|
|
7 -> Instruction.lessThan,
|
|
8 -> Instruction.equals,
|
|
)
|
|
|
|
def parse(instructions: Map[Int, Instruction] = instructions): State[Machine, (Instruction, LazyList[Boolean])] = State.inspect {
|
|
machine: Machine =>
|
|
val instructionAndFlags = machine.memory(machine.pc)
|
|
val instruction: Instruction = instructions(instructionAndFlags % 100)
|
|
val flags: LazyList[Boolean] = {
|
|
val flagString = instructionAndFlags.toString.reverseIterator.drop(2)
|
|
val flagIterator = flagString.map(_ == '1')
|
|
LazyList.from(flagIterator) ++ LazyList.continually(false)
|
|
}
|
|
(instruction, flags)
|
|
}
|
|
def step(instructions: Map[Int, Instruction] = instructions): Operation = for {
|
|
(instruction, flags) <- parse(instructions)
|
|
_ <- advance(1)
|
|
result <- instruction.run(flags)
|
|
} yield result
|
|
def run(instructions: Map[Int, Instruction] = instructions, haltCondition: State[Machine, Boolean] = isHalted): Operation =
|
|
step(instructions).untilM[Vector](haltCondition).map(_.last)
|
|
|
|
val noop: Operation = State.pure(())
|
|
|
|
def goto(address: Int): Operation = State.modify(_.copy(pc = address))
|
|
val location: State[Machine, Int] = State.inspect(_.pc)
|
|
def advance(by: Int): Operation = location.flatMap(c => goto(c + by))
|
|
|
|
def set(address: Int, value: Int): Operation = State.modify { machine =>
|
|
val newMemory = machine.memory.updated(address, value)
|
|
machine.copy(memory = newMemory)
|
|
}
|
|
def get(address: Int): State[Machine, Int] = State.inspect(_.memory(address))
|
|
|
|
def input(value: Int): Operation = State.modify { machine =>
|
|
machine.copy(input = machine.input :+ value)
|
|
}
|
|
val pullInput: State[Machine, Int] = State { machine =>
|
|
(machine.copy(input = machine.input.tail), machine.input.head)
|
|
}
|
|
|
|
def output(value: Int): Operation = State.modify { machine =>
|
|
machine.copy(output = machine.output :+ value)
|
|
}
|
|
|
|
val isHalted: State[Machine, Boolean] = State.inspect(machine => machine.memory(machine.pc) % 100 == 99)
|
|
val watingForInput: State[Machine, Boolean] = State.inspect(machine => machine.memory(machine.pc) % 100 == 3 && machine.input.isEmpty)
|
|
|
|
object Instruction {
|
|
|
|
def trans(operandCount: Int, operation: Kleisli[State[Machine, *], Vector[Int], Unit]): Instruction = Kleisli {
|
|
flags: LazyList[Boolean] =>
|
|
State.inspect { machine: Machine =>
|
|
machine.memory.view
|
|
.slice(machine.pc, machine.pc + operandCount)
|
|
.zip(flags)
|
|
.map {
|
|
case (argument, true) => argument
|
|
case (argument, false) => machine.memory(argument)
|
|
}
|
|
.toVector
|
|
}.flatMap(operation.run)
|
|
}
|
|
def op(operandCount: Int, operation: Vector[Int] => Int): Instruction =
|
|
trans(operandCount, Kleisli(operation.map { result =>
|
|
for {
|
|
loc <- location
|
|
outputAddr <- get(loc + operandCount)
|
|
_ <- set(outputAddr, result)
|
|
advance <- advance(operandCount + 1)
|
|
} yield advance
|
|
}))
|
|
|
|
val add: Instruction = op(operandCount = 2, { case Vector(a, b) => a + b })
|
|
val multiply: Instruction = op(operandCount = 2, { case Vector(a, b) => a * b })
|
|
val input: Instruction = Kleisli { _ =>
|
|
for {
|
|
loc <- location
|
|
toAddr <- get(loc)
|
|
input <- pullInput
|
|
_ <- set(toAddr, input)
|
|
advance <- advance(1)
|
|
} yield advance
|
|
}
|
|
val output: Instruction = trans(1, Kleisli {
|
|
case Vector(value) => Intcode.output(value) *> advance(1)
|
|
})
|
|
val jumpIfTrue: Instruction = trans(2, Kleisli {
|
|
case Vector(condition, newAddr) => if(condition != 0) goto(newAddr) else advance(2)
|
|
})
|
|
val jumpIfFalse: Instruction = trans(2, Kleisli {
|
|
case Vector(condition, newAddr) => if(condition == 0) goto(newAddr) else advance(2)
|
|
})
|
|
val lessThan: Instruction = op(2, {
|
|
case Vector(a, b) if a < b => 1
|
|
case Vector(_, _) => 0
|
|
})
|
|
val equals: Instruction = op(2, {
|
|
case Vector(a, b) if a == b => 1
|
|
case Vector(_, _) => 0
|
|
})
|
|
|
|
}
|
|
|
|
}
|