From 17e061d2e16ff756e165b27c8d2c07d6dfac07c5 Mon Sep 17 00:00:00 2001 From: Soren Date: Sat, 7 Dec 2019 14:42:54 -0800 Subject: [PATCH] Refactor intcode machine so that it's actually usable --- aoc/src/main/scala/aoc/y2019/Day02.scala | 14 +-- aoc/src/main/scala/aoc/y2019/Day05.scala | 129 +-------------------- aoc/src/main/scala/aoc/y2019/Intcode.scala | 121 +++++++++++++++++++ build.sbt | 2 + 4 files changed, 133 insertions(+), 133 deletions(-) create mode 100644 aoc/src/main/scala/aoc/y2019/Intcode.scala diff --git a/aoc/src/main/scala/aoc/y2019/Day02.scala b/aoc/src/main/scala/aoc/y2019/Day02.scala index ce519e3..a82edf1 100644 --- a/aoc/src/main/scala/aoc/y2019/Day02.scala +++ b/aoc/src/main/scala/aoc/y2019/Day02.scala @@ -1,22 +1,20 @@ package aoc.y2019 import aoc.Day +import cats._ +import cats.implicits._ import cats.data.State object Day02 extends Day { - import Day05.Machine, Day05.MachineState, Day05.executeMachine - - def initialReplacement(noun: Int, verb: Int): MachineState = State { m => - (m.copy(m.memory.updated(1, noun).updated(2, verb)), false) - } + def initialReplacement(noun: Int, verb: Int): Intcode.Operation = Intcode.set(1, noun) *> Intcode.set(2, verb) def runMachine(memory: Vector[Int], noun: Int, verb: Int): Int = { - val initialState = Machine(memory) + val initialState = Intcode.Machine(memory) val runMachine = for { _ <- initialReplacement(noun, verb) - run <- executeMachine - } yield run + ran <- Intcode.run + } yield ran val endState = runMachine.runS(initialState).value endState.memory(0) } diff --git a/aoc/src/main/scala/aoc/y2019/Day05.scala b/aoc/src/main/scala/aoc/y2019/Day05.scala index fce6627..38d4d3c 100644 --- a/aoc/src/main/scala/aoc/y2019/Day05.scala +++ b/aoc/src/main/scala/aoc/y2019/Day05.scala @@ -7,134 +7,13 @@ import cats.data.State 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 = { val memory = input.split(",").toVector.map(_.toInt) - val initialState = Machine(memory) + val initialState = Intcode.Machine(memory) val machineInput = diagnosticNumber - val runMachine: MachineState = for { - _ <- inputNumber(machineInput) - ran <- executeMachine + val runMachine: Intcode.Operation = for { + _ <- Intcode.input(machineInput) + ran <- Intcode.run } yield ran val endState = runMachine.runS(initialState).value endState.output.last diff --git a/aoc/src/main/scala/aoc/y2019/Intcode.scala b/aoc/src/main/scala/aoc/y2019/Intcode.scala new file mode 100644 index 0000000..7803347 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/Intcode.scala @@ -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 + }) + + } + +} diff --git a/build.sbt b/build.sbt index fba6031..250c072 100644 --- a/build.sbt +++ b/build.sbt @@ -12,6 +12,8 @@ val sharedSettings = Seq( "org.typelevel" %%% "cats-free" % "2.0.0", ), 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 */ )