Commit 2b112526 authored by David's avatar David

Puzzle 7 was super fun!

parent b97352c7
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation( "org.scala-lang.modules:scala-parallel-collections_2.13:0.2.0")
implementation("com.github.ReactiveX:RxScala:0.x-SNAPSHOT")
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %.15c{1.} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="is.kow.adventofcode._2019" level="DEBUG"/>
<Logger name="is.kow.adventofcode._2019.opcodes" level="WARN"/>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
\ No newline at end of file
3,8,1001,8,10,8,105,1,0,0,21,30,51,72,81,94,175,256,337,418,99999,3,9,101,5,9,9,4,9,99,3,9,1001,9,3,9,1002,9,2,9,1001,9,2,9,1002,9,5,9,4,9,99,3,9,1002,9,4,9,101,4,9,9,102,5,9,9,101,3,9,9,4,9,99,3,9,1002,9,4,9,4,9,99,3,9,102,3,9,9,1001,9,4,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,3,9,102,2,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,99,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,99
package is.kow.adventofcode._2019
import scala.io.Source
object Main extends App {
val source = Source.fromResource("part1.txt")
try {
val opcodes = source.getLines().toList.head.split(",").map(_.toInt).toList
println("STARTING")
val p7 = new Puzzle7
val highestPower = p7.highestPower(List(0, 1, 2, 3, 4), opcodes)
println("Part1 Highest Power: " + highestPower)
val highestPower2 = p7.highestPower(List(5, 6, 7, 8, 9), opcodes)
println("Part 2 Higest power: " + highestPower2)
} finally {
source.close()
}
}
package is.kow.adventofcode._2019
import java.util.concurrent.Executors
import com.typesafe.scalalogging.LazyLogging
import is.kow.adventofcode._2019.opcodes.IntCode
import rx.lang.scala.{Observable, Subscription}
import rx.lang.scala.subjects.{PublishSubject, ReplaySubject}
import scala.collection.mutable
import scala.collection.parallel.CollectionConverters._
import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService}
class Puzzle7 extends LazyLogging {
def highestPower(phaseSettings: List[Int], opcodes: List[Int]): Int = {
phaseSettings.permutations.toList.par.foldLeft(Int.MinValue) { (acc, settings) =>
val result = thrusterPower(settings, opcodes)
logger.info(s"SETTINGS: $settings :- $result")
if (result > acc) {
result
} else {
acc
}
}
}
def thrusterPower(phaseSettings: List[Int], opcodes: List[Int]): Int = {
//amp a, b, c, d, e
val ampA = new IntCode(opcodes)
val ampB = new IntCode(opcodes)
val ampC = new IntCode(opcodes)
val ampD = new IntCode(opcodes)
val ampE = new IntCode(opcodes)
implicit val executionContext: ExecutionContextExecutorService =
ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(5))
val inputA = ReplaySubject[Int]
inputA.onNext(phaseSettings(0))
inputA.onNext(0) //Initial input signal
inputA.doOnNext(n => s"InputA: $n")
val inputB = ReplaySubject[Int]
inputB.onNext(phaseSettings(1))
inputB.doOnNext(n => s"InputB: $n")
val inputC = ReplaySubject[Int]
inputC.onNext(phaseSettings(2))
inputC.doOnNext(n => s"InputC: $n")
val inputD = ReplaySubject[Int]
inputD.onNext(phaseSettings(3))
inputD.doOnNext(n => s"InputD: $n")
val inputE = ReplaySubject[Int]
inputE.onNext(phaseSettings(4))
inputE.doOnNext(n => s"InputE: $n")
val subs = new mutable.Stack[Subscription]()
subs.push(ampA.execute(inputA).doOnNext(n => logger.debug(s"outputA: ${n}")).subscribe(out => inputB.onNext(out)))
subs.push(ampB.execute(inputB).doOnNext(n => logger.debug(s"outputB: ${n}")).subscribe(out => inputC.onNext(out)))
subs.push(ampC.execute(inputC).doOnNext(n => logger.debug(s"outputC: ${n}")).subscribe(out => inputD.onNext(out)))
subs.push(ampD.execute(inputD).doOnNext(n => logger.debug(s"outputD: ${n}")).subscribe(out => inputE.onNext(out)))
val ampEOutput = ampE.execute(inputE).doOnNext(n => logger.debug(s"outputE: ${n}"))
//Last output signal from E is the final answer
subs.push(ampEOutput.subscribe(out => inputA.onNext(out)))
val finalOutput = ampEOutput.toBlocking.last
//Complete all inputs
inputA.onCompleted()
inputB.onCompleted()
inputC.onCompleted()
inputD.onCompleted()
inputE.onCompleted()
//Unsubscribe all
subs.foreach(sub => sub.unsubscribe())
executionContext.shutdown()
finalOutput
}
}
package is.kow.adventofcode._2019.opcodes
import com.typesafe.scalalogging.LazyLogging
import rx.lang.scala.Observable
import rx.lang.scala.subjects.PublishSubject
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext
class IntCode(data: List[Int]) extends LazyLogging {
private val publishSubject = PublishSubject[Int]()
private val outputOpCode = new OutputOpCode(publishSubject)
def execute(input: Observable[Int])(implicit executionContext: ExecutionContext): Observable[Int] = {
logger.debug("EXECUTE")
val END_OP = EndOpCode()
val OP_CODES = List(
new JumpIfFalseOpCode(),
new JumpIfTrueOpCode(),
new LessThanOpCode(),
new EqualsOpCode(),
new AddOpCode(),
new MultOpCode(),
END_OP,
outputOpCode,
new InputOpCode(input.doOnNext(i => logger.info(s"NEXTINPUT: $i")).toBlocking.toIterable.iterator)
)
@tailrec def process(completedOps: List[OpCode], acc: List[Int], opLocation: Int = 0): List[Int] = {
val currentOp = acc.drop(opLocation).head
if (END_OP.matches(currentOp)) {
acc
} else {
val maybeOp = OP_CODES.find(_.matches(currentOp))
val operated = maybeOp.map { operation =>
operation.operate(opLocation, acc)
} getOrElse {
logger.error(s"Unknown Opcode: $currentOp @ $opLocation: $acc")
throw new RuntimeException(s"Invalid Operation: $currentOp @ $opLocation")
}
val op = maybeOp.get
process(op :: completedOps, operated.data, operated.opPointer)
}
}
executionContext.execute(() => {
try {
logger.debug("IN EXECUTION CONTEXT -- STARTING")
process(List.empty, data)
publishSubject.onCompleted()
logger.debug("ALL DONE, SENT COMPLETE")
} catch {
case e: Exception =>
logger.error("some kind of failure!", e)
publishSubject.onError(e)
}
})
publishSubject
}
}
package is.kow.adventofcode._2019.opcodes
import com.typesafe.scalalogging.StrictLogging
import rx.lang.scala.observables.BlockingObservable
import rx.lang.scala.{Observable, Subject}
import scala.collection.mutable
case class OperationResult(data: List[Int], opPointer: Int)
abstract class OpCode(val opValue: Int, val parameterSize: Int) extends StrictLogging {
val opCodeFormatted: String = codeFormat(opValue)
protected final val IMMEDIATE = '1'
protected final val POSITION = '0'
def codeFormat(input: Int): String = {
("%0" + (parameterSize + 2) + "d").format(input)
}
def operate(offset: Int, data: List[Int]): OperationResult
def matches(code: Int): Boolean = {
//convert the code to a string also
val actualOpCode = codeFormat(code).slice(parameterSize, parameterSize + 2)
//Determine if the last two of the formatted thing, as an integer, are our opcode
opCodeFormatted.slice(parameterSize, parameterSize + 2) == actualOpCode
}
protected def logOperation(index: Int, name: String, opParams: String, params: Int*): Unit = {
logger.whenDebugEnabled({
val paramSize = params.foldLeft(Int.MinValue) { (acc, p) =>
val paramLength = p.toString.length
if (paramLength > acc) paramLength else acc
}
val formatString = "%4d %10s: %5s " + params.foldLeft("") { (acc, p) =>
acc + s"%${paramSize}d "
}
val intermediary = Seq(index, name, opParams) ++ params
logger.debug(formatString.format(intermediary: _*))
})
}
}
class AddOpCode() extends OpCode(1, 3) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: in1 :: in2 :: dest :: xs =>
val formattedOpParams = codeFormat(opParams)
val operand1 = if (formattedOpParams(2) == POSITION) data(in1) else in1
val operand2 = if (formattedOpParams(1) == POSITION) data(in2) else in2
logOperation(offset, "ADD", formattedOpParams, operand1, operand2)
OperationResult(data.updated(dest, operand1 + operand2), offset + 1 + parameterSize)
}
}
}
class MultOpCode() extends OpCode(2, 3) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: in1 :: in2 :: dest :: xs =>
val formattedOpParams = codeFormat(opParams)
val operand1 = if (formattedOpParams(2) == POSITION) data(in1) else in1
val operand2 = if (formattedOpParams(1) == POSITION) data(in2) else in2
logOperation(offset, "MULT", formattedOpParams, operand1, operand2)
OperationResult(data.updated(dest, operand1 * operand2), offset + 1 + parameterSize)
}
}
}
class InputOpCode(val input: Iterator[Int]) extends OpCode(3, 1) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: xs =>
logger.info("BEFORE TAKE BLOCKING")
val value: Int = input.next() //This will cause it to block, which is fine, need a timeout, or a kill
logger.info(s"INPUT VALUE: ${value}")
logOperation(offset, "INPUT", codeFormat(opParams), p1, value)
OperationResult(data.updated(p1, value), offset + 1 + parameterSize)
}
}
}
//Can have multiple outputs...
class OutputOpCode(output: Subject[Int]) extends OpCode(4, 1) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: xs =>
val fOpParams = codeFormat(opParams)
val operand1 = if (fOpParams(0) == POSITION) data(p1) else p1
logOperation(offset, "OUTPUT", codeFormat(opParams), operand1)
output.onNext(operand1)
OperationResult(data, offset + 1 + parameterSize)
}
}
}
case class EndOpCode() extends OpCode(99, 0) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
logOperation(offset, "END", codeFormat(99))
OperationResult(data, offset)
}
}
class JumpIfTrueOpCode() extends OpCode(5, 2) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: p2 :: xs =>
val formattedOpParams = codeFormat(opParams)
val operand1 = if (formattedOpParams(1) == POSITION) data(p1) else p1
val operand2 = if (formattedOpParams(0) == POSITION) data(p2) else p2
logOperation(offset, "JMPTRUE", formattedOpParams, operand1, operand2)
if (operand1 != 0) {
OperationResult(data, operand2)
} else {
OperationResult(data, offset + 1 + parameterSize)
}
}
}
}
class JumpIfFalseOpCode() extends OpCode(6, 2) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: p2 :: xs =>
val formattedOpParams = codeFormat(opParams)
val operand1 = if (formattedOpParams(1) == POSITION) data(p1) else p1
val operand2 = if (formattedOpParams(0) == POSITION) data(p2) else p2
logOperation(offset, "JMPFALSE", formattedOpParams, operand1, operand2)
if (operand1 == 0) {
OperationResult(data, operand2)
} else {
OperationResult(data, offset + 1 + parameterSize)
}
}
}
}
class LessThanOpCode() extends OpCode(7, 3) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: p2 :: p3 :: xs =>
val fOpParams = codeFormat(opParams)
val operand1 = if (fOpParams(2) == POSITION) data(p1) else p1
val operand2 = if (fOpParams(1) == POSITION) data(p2) else p2
val dest = p3
val result = if (operand1 < operand2) 1 else 0
logOperation(offset, "LESSTHAN", fOpParams, operand1, operand2, dest)
OperationResult(data.updated(dest, result), offset + 1 + parameterSize)
}
}
}
class EqualsOpCode() extends OpCode(8, 3) {
override def operate(offset: Int, data: List[Int]): OperationResult = {
data.drop(offset) match {
case opParams :: p1 :: p2 :: p3 :: xs =>
val fOpParams = codeFormat(opParams)
val operand1 = if (fOpParams(2) == POSITION) data(p1) else p1
val operand2 = if (fOpParams(1) == POSITION) data(p2) else p2
val dest = p3
val result = if (operand1 == operand2) 1 else 0
logOperation(offset, "EQUALS", fOpParams, operand1, operand2, dest)
OperationResult(data.updated(dest, result), offset + 1 + parameterSize)
}
}
}
\ No newline at end of file
package is.kow.adventofcode._2019
import org.junit.runner.RunWith
import org.scalatest.funspec.AnyFunSpec
import org.scalatestplus.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class Puzzle7Spec extends AnyFunSpec {
describe("Puzzle 7 part 1") {
List(
(
List(3, 15, 3, 16, 1002, 16, 10, 16, 1, 16, 15, 15, 4, 15, 99, 0, 0),
List(4, 3, 2, 1, 0),
43210
),
(
List(3, 23, 3, 24, 1002, 24, 10, 24, 1002, 23, -1, 23, 101, 5, 23, 23, 1, 24, 23, 23, 4, 23, 99, 0, 0),
List(0, 1, 2, 3, 4),
54321
),
(
List(3, 31, 3, 32, 1002, 32, 10, 32, 1001, 31, -2, 31, 1007, 31, 0, 33, 1002, 33, 7, 33, 1, 33, 31, 31, 1, 32, 31, 31, 4, 31, 99, 0, 0, 0),
List(1, 0, 4, 3, 2),
65210
)
).foreach { case (opcodes, settings, expected) =>
it(s"expects to get $expected") {
val p7 = new Puzzle7
assert(p7.thrusterPower(settings, opcodes) == expected)
}
}
}
describe("Puzzle 7 part 2") {
List(
(
List(3, 26, 1001, 26, -4, 26, 3, 27, 1002, 27, 2, 27, 1, 27, 26, 27, 4, 27, 1001, 28, -1, 28, 1005, 28, 6, 99, 0, 0, 5),
List(9, 8, 7, 6, 5),
139629729
),
(
List(3, 52, 1001, 52, -5, 52, 3, 53, 1, 52, 56, 54, 1007, 54, 5, 55, 1005, 55, 26, 1001, 54,
-5, 54, 1105, 1, 12, 1, 53, 54, 53, 1008, 54, 0, 55, 1001, 55, 1, 55, 2, 53, 55, 53, 4,
53, 1001, 56, -1, 56, 1005, 56, 6, 99, 0, 0, 0, 0, 10),
List(9, 7, 8, 5, 6),
18216
),
).foreach { case (opcodes, settings, expected) =>
it(s"Expects to get $expected") {
val p7 = new Puzzle7
assert(p7.thrusterPower(settings, opcodes) == expected)
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment