Refactor intcode machine so that it's actually usable
This commit is contained in:
parent
4047fc52ac
commit
17e061d2e1
4 changed files with 133 additions and 133 deletions
|
@ -1,22 +1,20 @@
|
||||||
package aoc.y2019
|
package aoc.y2019
|
||||||
|
|
||||||
import aoc.Day
|
import aoc.Day
|
||||||
|
import cats._
|
||||||
|
import cats.implicits._
|
||||||
import cats.data.State
|
import cats.data.State
|
||||||
|
|
||||||
object Day02 extends Day {
|
object Day02 extends Day {
|
||||||
|
|
||||||
import Day05.Machine, Day05.MachineState, Day05.executeMachine
|
def initialReplacement(noun: Int, verb: Int): Intcode.Operation = Intcode.set(1, noun) *> Intcode.set(2, verb)
|
||||||
|
|
||||||
def initialReplacement(noun: Int, verb: Int): MachineState = State { m =>
|
|
||||||
(m.copy(m.memory.updated(1, noun).updated(2, verb)), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
def runMachine(memory: Vector[Int], noun: Int, verb: Int): Int = {
|
def runMachine(memory: Vector[Int], noun: Int, verb: Int): Int = {
|
||||||
val initialState = Machine(memory)
|
val initialState = Intcode.Machine(memory)
|
||||||
val runMachine = for {
|
val runMachine = for {
|
||||||
_ <- initialReplacement(noun, verb)
|
_ <- initialReplacement(noun, verb)
|
||||||
run <- executeMachine
|
ran <- Intcode.run
|
||||||
} yield run
|
} yield ran
|
||||||
val endState = runMachine.runS(initialState).value
|
val endState = runMachine.runS(initialState).value
|
||||||
endState.memory(0)
|
endState.memory(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,134 +7,13 @@ import cats.data.State
|
||||||
|
|
||||||
object Day05 extends Day {
|
object Day05 extends Day {
|
||||||
|
|
||||||
case class Oresult(result: Option[Int], appendOutput: Vector[Int], newPointer: Option[Int])
|
|
||||||
object Oresult {
|
|
||||||
def apply(result: Int): Oresult = Oresult(result.some, Vector.empty, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed trait Operation {
|
|
||||||
val opcode: Int
|
|
||||||
val operandCount: Int
|
|
||||||
val inputCount: Int
|
|
||||||
def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult
|
|
||||||
}
|
|
||||||
case object Add extends Operation {
|
|
||||||
override val opcode: Int = 1
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(operands(0) + operands(1))
|
|
||||||
}
|
|
||||||
case object Multiply extends Operation {
|
|
||||||
override val opcode: Int = 2
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(operands(0) * operands(1))
|
|
||||||
}
|
|
||||||
case object Input extends Operation {
|
|
||||||
override val opcode: Int = 3
|
|
||||||
override val operandCount: Int = 0
|
|
||||||
override val inputCount: Int = 1
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(inputs.headOption, Vector.empty, None)
|
|
||||||
}
|
|
||||||
case object Output extends Operation {
|
|
||||||
override val opcode: Int = 4
|
|
||||||
override val operandCount: Int = 1
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(None, operands, None)
|
|
||||||
}
|
|
||||||
case object JumpIfTrue extends Operation {
|
|
||||||
override val opcode: Int = 5
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(None, Vector.empty, Option.when(operands(0) != 0)(operands(1)))
|
|
||||||
}
|
|
||||||
case object JumpIfFalse extends Operation {
|
|
||||||
override val opcode: Int = 6
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(None, Vector.empty, Option.when(operands(0) == 0)(operands(1)))
|
|
||||||
}
|
|
||||||
case object LessThan extends Operation {
|
|
||||||
override val opcode: Int = 7
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(if(operands(0) < operands(1)) 1 else 0)
|
|
||||||
}
|
|
||||||
case object Equal extends Operation {
|
|
||||||
override val opcode: Int = 8
|
|
||||||
override val operandCount: Int = 2
|
|
||||||
override val inputCount: Int = 0
|
|
||||||
override def apply(operands: Vector[Int], inputs: Vector[Int]): Oresult = Oresult(if(operands(0) == operands(1)) 1 else 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
object Operation {
|
|
||||||
val all: Map[Int, Operation] = Seq(
|
|
||||||
Add,
|
|
||||||
Multiply,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
JumpIfTrue,
|
|
||||||
JumpIfFalse,
|
|
||||||
LessThan,
|
|
||||||
Equal
|
|
||||||
).map(op => (op.opcode, op)).toMap
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Machine(memory: Vector[Int], pointer: Int = 0 , input: Vector[Int] = Vector.empty, output: Vector[Int] = Vector.empty)
|
|
||||||
type MachineState = State[Machine, Boolean]
|
|
||||||
|
|
||||||
def inputNumber(num: Int): MachineState = State { machine =>
|
|
||||||
(machine.copy(input = machine.input :+ num), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val executeMachine: MachineState = State { machine =>
|
|
||||||
lazy val states: LazyList[(Machine, Boolean)] =
|
|
||||||
(machine, false) #:: states.map {
|
|
||||||
case (state, true) => (state, true)
|
|
||||||
case (m @ Machine(memory, pointer, input, output), false) =>
|
|
||||||
memory(pointer) match {
|
|
||||||
case 99 => (m, true)
|
|
||||||
case opcode =>
|
|
||||||
val operation = Operation.all(opcode % 100)
|
|
||||||
val operandCount = operation.operandCount
|
|
||||||
val inputCount = operation.inputCount
|
|
||||||
val inputs = input.take(inputCount)
|
|
||||||
val immediateParameters = (1 to operandCount).toVector.map { operandNum =>
|
|
||||||
val divide = Math.pow(10, operandNum + 1).toInt
|
|
||||||
val modulo = Math.pow(10, operandNum + 2).toInt
|
|
||||||
((opcode % modulo) / divide) == 1
|
|
||||||
}
|
|
||||||
val operands = (1 to operandCount).toVector.zip(immediateParameters).map {
|
|
||||||
case (operandNum, false) => memory(memory(pointer + operandNum))
|
|
||||||
case (operandNum, true) => memory(pointer + operandNum)
|
|
||||||
}
|
|
||||||
val Oresult(result, appendOutput, changePointer) = operation(operands, inputs)
|
|
||||||
|
|
||||||
val newMemory = result match {
|
|
||||||
case Some(result) => memory.updated(memory(pointer + operandCount + 1), result)
|
|
||||||
case None => memory
|
|
||||||
}
|
|
||||||
val newPointer = (result, changePointer) match {
|
|
||||||
case (_, Some(newAddress)) => newAddress
|
|
||||||
case (Some(_), None) => pointer + operandCount + 2
|
|
||||||
case (None, None) => pointer + operandCount + 1
|
|
||||||
}
|
|
||||||
val newInput = input.drop(inputCount)
|
|
||||||
val newOutput = output ++ appendOutput
|
|
||||||
|
|
||||||
(Machine(newMemory, newPointer, newInput, newOutput), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
states.takeWhile(!_._2).last
|
|
||||||
}
|
|
||||||
|
|
||||||
def diagnostic(input: String, diagnosticNumber: Int): Int = {
|
def diagnostic(input: String, diagnosticNumber: Int): Int = {
|
||||||
val memory = input.split(",").toVector.map(_.toInt)
|
val memory = input.split(",").toVector.map(_.toInt)
|
||||||
val initialState = Machine(memory)
|
val initialState = Intcode.Machine(memory)
|
||||||
val machineInput = diagnosticNumber
|
val machineInput = diagnosticNumber
|
||||||
val runMachine: MachineState = for {
|
val runMachine: Intcode.Operation = for {
|
||||||
_ <- inputNumber(machineInput)
|
_ <- Intcode.input(machineInput)
|
||||||
ran <- executeMachine
|
ran <- Intcode.run
|
||||||
} yield ran
|
} yield ran
|
||||||
val endState = runMachine.runS(initialState).value
|
val endState = runMachine.runS(initialState).value
|
||||||
endState.output.last
|
endState.output.last
|
||||||
|
|
121
aoc/src/main/scala/aoc/y2019/Intcode.scala
Normal file
121
aoc/src/main/scala/aoc/y2019/Intcode.scala
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
val parse: 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)
|
||||||
|
}
|
||||||
|
val step: Operation = for {
|
||||||
|
(instruction, flags) <- parse
|
||||||
|
_ <- advance(1)
|
||||||
|
result <- instruction.run(flags)
|
||||||
|
} yield result
|
||||||
|
val run: Operation = step.untilM[Vector](State.inspect(machine => machine.memory(machine.pc) % 100 == 99)).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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ val sharedSettings = Seq(
|
||||||
"org.typelevel" %%% "cats-free" % "2.0.0",
|
"org.typelevel" %%% "cats-free" % "2.0.0",
|
||||||
),
|
),
|
||||||
mainClass := Some("tf.bug.aoc.Main"),
|
mainClass := Some("tf.bug.aoc.Main"),
|
||||||
|
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full),
|
||||||
|
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// lazy val aoc = crossProject(/* JSPlatform, */ JVMPlatform /* , NativePlatform */ )
|
// lazy val aoc = crossProject(/* JSPlatform, */ JVMPlatform /* , NativePlatform */ )
|
||||||
|
|
Loading…
Reference in a new issue