diff --git a/aoc/src/main/scala/aoc/y2019/Day02.scala b/aoc/src/main/scala/aoc/y2019/Day02.scala index 95ac80d..c4cbe50 100644 --- a/aoc/src/main/scala/aoc/y2019/Day02.scala +++ b/aoc/src/main/scala/aoc/y2019/Day02.scala @@ -1,31 +1,32 @@ package aoc.y2019 import aoc.Day +import aoc.y2019.intcode.{Machine, Step} import cats._ import cats.implicits._ import cats.data.State object Day02 extends Day { - def initialReplacement(noun: Int, verb: Int): Intcode.Operation = Intcode.set(1, noun) *> Intcode.set(2, verb) + def initialReplacement(noun: Int, verb: Int): Step = Machine.set(1, noun) *> Machine.set(2, verb) - def runMachine(memory: Vector[Int], noun: Int, verb: Int): Int = { - val initialState = Intcode.Machine(memory) + def runMachine(memory: Vector[BigInt], noun: Int, verb: Int): BigInt = { + val initialState = Machine(memory) val runMachine = for { _ <- initialReplacement(noun, verb) - ran <- Intcode.run() + ran <- Machine.run() } yield ran val endState = runMachine.runS(initialState).value endState.memory(0) } override def part1(input: String): String = { - val memory = input.split(",").map(_.toInt).toVector + val memory = input.split(",").map(BigInt(_)).toVector runMachine(memory, 12, 2).toString } override def part2(input: String): String = { - val memory = input.split(",").map(_.toInt).toVector + val memory = input.split(",").map(BigInt(_)).toVector val magic = 19690720 val solutions = for { noun <- 0 to 99 diff --git a/aoc/src/main/scala/aoc/y2019/Day05.scala b/aoc/src/main/scala/aoc/y2019/Day05.scala index edeed4e..49e1d16 100644 --- a/aoc/src/main/scala/aoc/y2019/Day05.scala +++ b/aoc/src/main/scala/aoc/y2019/Day05.scala @@ -3,18 +3,19 @@ package aoc.y2019 import cats._ import cats.implicits._ import aoc.Day +import aoc.y2019.intcode.{Machine, Step} import cats.data.State object Day05 extends Day { - def diagnostic(input: String, diagnosticNumber: Int): Int = { - val memory = input.split(",").toVector.map(_.toInt) - val initialState = Intcode.Machine(memory) + def diagnostic(input: String, diagnosticNumber: Int): BigInt = { + val memory = input.split(",").map(BigInt(_)).toVector + val initialState = Machine(memory) val machineInput = diagnosticNumber - val runMachine: Intcode.Operation = for { - _ <- Intcode.input(machineInput) - ran <- Intcode.run() - } yield ran + val runMachine: Step = for { + _ <- Machine.input(machineInput) + ran <- Machine.run() + } yield ran.last val endState = runMachine.runS(initialState).value endState.output.last } diff --git a/aoc/src/main/scala/aoc/y2019/Day07.scala b/aoc/src/main/scala/aoc/y2019/Day07.scala index d336099..2cbd44b 100644 --- a/aoc/src/main/scala/aoc/y2019/Day07.scala +++ b/aoc/src/main/scala/aoc/y2019/Day07.scala @@ -1,22 +1,24 @@ package aoc.y2019 import aoc.Day +import aoc.y2019.intcode.Machine import cats._ import cats.implicits._ + import scala.annotation.tailrec object Day07 extends Day { - @tailrec def runAmplifiers(machine: Intcode.Machine, order: Seq[Int], input: Int = 0): Int = { + @tailrec def runAmplifiers(machine: Machine, order: Seq[Int], input: BigInt = 0): BigInt = { if(order.isEmpty) { input } else { val firstInput = order.head val secondInput = input val runAmplifier = for { - _ <- Intcode.input(firstInput) - _ <- Intcode.input(secondInput) - ran <- Intcode.run() + _ <- Machine.input(firstInput) + _ <- Machine.input(secondInput) + ran <- Machine.run() } yield ran val output = runAmplifier.runS(machine).value.output.head runAmplifiers(machine, order.tail, output) @@ -24,28 +26,28 @@ object Day07 extends Day { } override def part1(input: String): String = { - val memory = input.split(",").toVector.map(_.toInt) - val initialState = Intcode.Machine(memory) + val memory = input.split(",").map(BigInt(_)).toVector + val initialState = Machine(memory) val maximumSignal = (0 to 4).permutations.map(runAmplifiers(initialState, _)).max maximumSignal.toString } - def loopAmplifiers(base: Intcode.Machine, initialInputs: Seq[Int]): Int = { + def loopAmplifiers(base: Machine, initialInputs: Seq[Int]): BigInt = { val numAmplifiers = initialInputs.size val initialMachines = Vector.fill(numAmplifiers)(base).zip(initialInputs).map { case (machine, phase) => machine.copy(input = machine.input :+ phase) } val haltCondition = for { - waitingForInput <- Intcode.watingForInput - isHalted <- Intcode.isHalted + waitingForInput <- Machine.waitingForInput + isHalted <- Machine.isHalted } yield waitingForInput || isHalted - val runToNextMachine = Intcode.run(haltCondition = haltCondition) - @tailrec def go(index: Int, machines: Vector[Intcode.Machine], lastOutput: Int = 0): Vector[Intcode.Machine] = { - if(machines.traverse(machine => Intcode.isHalted.runA(machine)).value.forall(identity)) machines + val runToNextMachine = Machine.run(haltCondition = haltCondition) + @tailrec def go(index: Int, machines: Vector[Machine], lastOutput: BigInt = 0): Vector[Machine] = { + if(machines.traverse(machine => Machine.isHalted.runA(machine)).value.forall(identity)) machines else { val thisMachine = machines(index) val inputAndRun = for { - inputLastOutput <- Intcode.input(lastOutput) + inputLastOutput <- Machine.input(lastOutput) runAsFarAsPossible<- runToNextMachine } yield runAsFarAsPossible val ranMachine = inputAndRun.runS(thisMachine).value @@ -59,8 +61,8 @@ object Day07 extends Day { } override def part2(input: String): String = { - val memory = input.split(",").toVector.map(_.toInt) - val initialState = Intcode.Machine(memory) + val memory = input.split(",").map(BigInt(_)).toVector + val initialState = Machine(memory) val maximumSignal = (5 to 9).permutations.map(loopAmplifiers(initialState, _)).max maximumSignal.toString } diff --git a/aoc/src/main/scala/aoc/y2019/Day09.scala b/aoc/src/main/scala/aoc/y2019/Day09.scala new file mode 100644 index 0000000..3f7f392 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/Day09.scala @@ -0,0 +1,21 @@ +package aoc.y2019 + +import aoc.Day +import aoc.y2019.intcode.Machine + +object Day09 extends Day { + + def boost(input: String, value: BigInt): BigInt = { + val code = input.split(",").map(BigInt(_)).toVector + val initialState = Machine(code) + val runMachine = for { + _ <- Machine.input(value) + ran <- Machine.run() + } yield ran + runMachine.runS(initialState).value.output.last + } + + override def part1(input: String): String = boost(input, 1).toString + + override def part2(input: String): String = boost(input, 2).toString +} diff --git a/aoc/src/main/scala/aoc/y2019/Intcode.scala b/aoc/src/main/scala/aoc/y2019/Intcode.scala deleted file mode 100644 index 8500285..0000000 --- a/aoc/src/main/scala/aoc/y2019/Intcode.scala +++ /dev/null @@ -1,125 +0,0 @@ -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 - }) - - } - -} diff --git a/aoc/src/main/scala/aoc/y2019/intcode/Instruction.scala b/aoc/src/main/scala/aoc/y2019/intcode/Instruction.scala new file mode 100644 index 0000000..22aceba --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/intcode/Instruction.scala @@ -0,0 +1,76 @@ +package aoc.y2019.intcode + +import cats._ +import cats.implicits._ +import cats.data.{Kleisli, State} + +object Instruction { + + val add: Instruction = op(2, _.sum) + val multiply: Instruction = op(2, _.product) + val input: Instruction = ins(0, Kleisli(_ => Machine.pull)) + val output: Instruction = trans(1, Kleisli(_.traverse(Machine.output).map(_.last))) + val jumpIfTrue: Instruction = trans(2, Kleisli { + case Vector(a, b) if a != 0 => Machine.goto(b) + case _ => Machine.advance(2) + }, advance = false) + val jumpIfFalse: Instruction = trans(2, Kleisli { + case Vector(a, b) if a == 0 => Machine.goto(b) + case _ => Machine.advance(2) + }, advance = false) + val lessThan: Instruction = op(2, { + case Vector(a, b) if a < b => 1 + case _ => 0 + }) + val equals: Instruction = op(2, { + case Vector(a, b) if a == b => 1 + case _ => 0 + }) + val adjustBase: Instruction = trans(1, Kleisli(_.traverse(Machine.adjustOffset).map(_.last))) + + val defaultInstructions: Map[Int, Instruction] = Map( + 1 -> add, + 2 -> multiply, + 3 -> input, + 4 -> output, + 5 -> jumpIfTrue, + 6 -> jumpIfFalse, + 7 -> lessThan, + 8 -> equals, + 9 -> adjustBase, + ) + + def op(operandCount: Int, operation: Vector[BigInt] => BigInt): Instruction = + ins(operandCount, Kleisli(operation.map(State.pure))) + def ins(operandCount: Int, operation: Kleisli[Operation, Vector[BigInt], BigInt]): Instruction = + transOp(operandCount, operation.map(_.some)) + def trans(argCount: Int, operation: Kleisli[Operation, Vector[BigInt], Unit], advance: Boolean = true): Instruction = + transOp(argCount, operation.map(_ => None), advance) + + def transOp(argCount: Int, operation: Kleisli[Operation, Vector[BigInt], Option[BigInt]], advance: Boolean = true): Instruction = + Kleisli { + case (operandModes: OperandModes, flags: Flags) => + val operandFuncs = flags.take(argCount).toList.map(operandModes(_).get) + for { + currentLoc <- Machine.location + argVals <- (currentLoc until currentLoc + argCount) + .toVector + .traverse(Machine.get) + args <- argVals + .zip(operandFuncs) + .traverse { case (arg: BigInt, f: Kleisli[Operation, BigInt, BigInt]) => f.run(arg) } + runOperation <- operation.run(args) + _ <- if(advance) Machine.advance(argCount) else Machine.noop + maybeOutput <- runOperation match { + case Some(output) => + for { + outputValue <- Machine.get(currentLoc + argCount) + _ <- operandModes(flags(argCount)).set.run((outputValue, output)) + advance <- Machine.advance(1) + } yield advance + case None => State.pure[Machine, Unit](()) + } + } yield maybeOutput + } + +} diff --git a/aoc/src/main/scala/aoc/y2019/intcode/Machine.scala b/aoc/src/main/scala/aoc/y2019/intcode/Machine.scala new file mode 100644 index 0000000..2bd6634 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/intcode/Machine.scala @@ -0,0 +1,68 @@ +package aoc.y2019.intcode + +import cats._ +import cats.implicits._ +import cats.data.State + +case class Machine( + memory: Map[BigInt, BigInt] = Map.empty, + pc: BigInt = 0, + input: Vector[BigInt] = Vector.empty, + output: Vector[BigInt] = Vector.empty, + relativeBase: BigInt = 0 +) + +object Machine { + def apply(code: Vector[BigInt]): Machine = + Machine(code.zipWithIndex.map[(BigInt, BigInt)] { case (v, i) => (i, v) }.toMap) + + def parseInstruction(instructions: Map[Int, Instruction] = Instruction.defaultInstructions): Operation[(Instruction, Flags)] = + for { + loc <- location + instructionAndFlags <- get(loc) + instruction = instructions((instructionAndFlags % 100).toInt) + flags = { + val flagString = instructionAndFlags.toString.reverseIterator.drop(2) + val flagIterator = flagString.map(_ - '0') + LazyList.from(flagIterator) ++ LazyList.continually(0) + } + } yield (instruction, flags) + def step(instructions: Map[Int, Instruction] = Instruction.defaultInstructions, operandModes: OperandModes = OperandMode.defaultOperandModes): Step = + for { + (instruction: Instruction, flags: Flags) <- parseInstruction(instructions) + _ <- advance(1) + result <- instruction.run(operandModes, flags) + } yield result + def run(instructions: Map[Int, Instruction] = Instruction.defaultInstructions, operandModes: OperandModes = OperandMode.defaultOperandModes, haltCondition: Operation[Boolean] = isHalted): Operation[Vector[Unit]] = + step(instructions, operandModes).untilM(haltCondition) + + val noop: Step = State.pure(()) + + def goto(address: BigInt): Step = State.modify(_.copy(pc = address)) + val location: Operation[BigInt] = State.inspect(_.pc) + def advance(by: BigInt): Step = for { loc <- location; go <- goto(by + loc) } yield go + + val getOffset: Operation[BigInt] = State.inspect(_.relativeBase) + def setOffset(offset: BigInt): Step = State.modify(_.copy(relativeBase = offset)) + def adjustOffset(adjustment: BigInt): Step = for { offset <- getOffset; set <- setOffset(offset + adjustment) } yield set + + def set(address: BigInt, value: BigInt): Step = State.modify(m => m.copy(m.memory.updated(address, value))) + def get(address: BigInt): Operation[BigInt] = State.inspect(_.memory.getOrElse(address, 0)) + + def input(value: BigInt): Step = State.modify(m => m.copy(input = m.input :+ value)) + val pull: Operation[BigInt] = State(m => (m.copy(input = m.input.tail), m.input.head)) + + def output(value: BigInt): Step = State.modify(m => m.copy(output = m.output :+ value)) + + val isHalted: Operation[Boolean] = for { + loc <- location + instruction <- get(loc) + } yield instruction % 100 == 99 + val waitingForInput: Operation[Boolean] = for { + loc <- location + instruction <- get(loc) + onInputInstruction = instruction % 100 == 3 + noInput <- State.inspect[Machine, Boolean](_.input.isEmpty) + } yield onInputInstruction && noInput + +} diff --git a/aoc/src/main/scala/aoc/y2019/intcode/OperandMode.scala b/aoc/src/main/scala/aoc/y2019/intcode/OperandMode.scala new file mode 100644 index 0000000..aaf7429 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/intcode/OperandMode.scala @@ -0,0 +1,38 @@ +package aoc.y2019.intcode + +import cats.data.{Kleisli, State} + +case class OperandMode(get: Kleisli[Operation, BigInt, BigInt], set: Kleisli[Operation, (BigInt, BigInt), Unit]) + +object OperandMode { + + val position: OperandMode = OperandMode( + Kleisli(Machine.get), + Kleisli { case (address, value) => Machine.set(address, value) } + ) + val immediate: OperandMode = OperandMode( + Kleisli(State.pure), + Kleisli(_ => throw new IllegalArgumentException("Cannot set in immediate mode!")) + ) + val relative: OperandMode = OperandMode( + Kleisli { address => + for { + offset <- Machine.getOffset + value <- Machine.get(address + offset) + } yield value + }, + Kleisli { case (address, value) => + for { + offset <- Machine.getOffset + set <- Machine.set(address + offset, value) + } yield set + } + ) + + val defaultOperandModes: Map[Int, OperandMode] = Map( + 0 -> position, + 1 -> immediate, + 2 -> relative, + ) + +} diff --git a/aoc/src/main/scala/aoc/y2019/intcode/package.scala b/aoc/src/main/scala/aoc/y2019/intcode/package.scala new file mode 100644 index 0000000..49b8710 --- /dev/null +++ b/aoc/src/main/scala/aoc/y2019/intcode/package.scala @@ -0,0 +1,16 @@ +package aoc.y2019 + +import cats.data.{Kleisli, State} + +package object intcode { + + type Flags = LazyList[Int] + + type Operation[A] = State[Machine, A] + type Step = Operation[Unit] + + type Instruction = Kleisli[Operation, (OperandModes, Flags), Unit] + + type OperandModes = Map[Int, OperandMode] + +} diff --git a/aoc/src/main/scala/aoc/y2019/package.scala b/aoc/src/main/scala/aoc/y2019/package.scala index dae19a1..0b9358c 100644 --- a/aoc/src/main/scala/aoc/y2019/package.scala +++ b/aoc/src/main/scala/aoc/y2019/package.scala @@ -11,6 +11,7 @@ package object y2019 extends Year { "6" -> Day06, "7" -> Day07, "8" -> Day08, + "9" -> Day09, ) } diff --git a/build.sbt b/build.sbt index 250c072..19a3041 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,9 @@ val sharedSettings = Seq( 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"), + scalacOptions ++= Seq( + "-Ywarn-value-discard" + ) ) // lazy val aoc = crossProject(/* JSPlatform, */ JVMPlatform /* , NativePlatform */ )