advent-of-code/2019/Scala/intcode/Machine.scala

158 lines
4.1 KiB
Scala
Raw Normal View History

2019-12-09 13:20:04 +01:00
package intcode
import intcode.opcode.{Action, Input, Jump, OpCode9, OpCode99, Output}
2019-12-09 21:17:35 +01:00
import scala.collection.mutable.ListBuffer
2019-12-09 13:20:04 +01:00
/**
* Virtual IntCode machine
* @param input Memory used to initialize the machine with.
*/
class Machine(input: Array[Long])
{
2019-12-09 21:17:35 +01:00
// Tapes
2019-12-09 13:20:04 +01:00
private[this] val original: Array[Long] = input.clone() // original memory saved in case of reset
private[this] var software: Array[Long] = input.clone() // working memory used by the machine
2019-12-09 21:17:35 +01:00
// State
2019-12-09 13:20:04 +01:00
private[this] var pointer: Int = 0 // instruction pointer
private[this] var relative: Long = 0 // relative pointer
private[this] var state: State = Ready // machine state
2019-12-09 21:17:35 +01:00
// IO Buffers
private[this] val inputStream: ListBuffer[Long] = new ListBuffer[Long]
private[this] val outputStream: ListBuffer[Long] = new ListBuffer[Long]
def enqueue(number: Long): Machine =
{
inputStream.addOne(number)
this
}
def enqueue(list: List[Long]): Machine =
{
inputStream.addAll(list)
this
}
def output: Long =
outputStream.remove(0)
def outputAll: List[Long] =
{
val accumulated = outputStream.result
outputStream.clear()
accumulated
}
2019-12-09 13:20:04 +01:00
def isHalted: Boolean = state != Ready
def isIO: Boolean = state == Input || state == Output
def isReady: Boolean = state == Ready
def isInput: Boolean = state == Input
def isOutput: Boolean = state == Output
def getState: State = state
def getMem(ptr: Int): Long =
software(ptr)
def setMem(ptr: Int, value: Long): Unit =
software(ptr) = value
def reset(): Machine =
{
software = original.clone()
2019-12-09 21:17:35 +01:00
inputStream.clear()
outputStream.clear()
2019-12-09 13:20:04 +01:00
state = Ready
pointer = 0
this
}
@scala.annotation.tailrec
2019-12-09 21:17:35 +01:00
final def runUntilHalt(): Machine =
2019-12-09 13:20:04 +01:00
{
val tuple = OpCode99.parseInt(software(pointer).toInt)
tuple match
{
case (_, _, _, OpCode99) =>
state = Finished
this
2019-12-09 21:17:35 +01:00
case (_, _, m1, outputInstr: Output) =>
outputStream.addOne(outputInstr.output(software, relative, software(pointer+1), m1))
pointer += outputInstr.length
state = Ready
runUntilHalt()
2019-12-09 13:20:04 +01:00
case (_, _, _, inputInstr: Input) =>
state = Input
this
case (m3, m2, m1, instruction: Jump) =>
val (bool, jmpPtr) = instruction.checkConditionAndJump(software, relative, software(pointer+1), software(pointer+2), software(pointer+3), m1, m2, m3)
if (bool) pointer = jmpPtr
else pointer += instruction.length
state = Ready
2019-12-09 21:17:35 +01:00
runUntilHalt()
2019-12-09 13:20:04 +01:00
case (m3, m2, m1, instruction: Action) =>
instruction.exec(software, relative, software(pointer+1), software(pointer+2), software(pointer+3), m1, m2, m3)
pointer += instruction.length
state = Ready
2019-12-09 21:17:35 +01:00
runUntilHalt()
2019-12-09 13:20:04 +01:00
case (_, _, m1, OpCode9) =>
relative += OpCode9.exec(software, relative, software(pointer+1), m1)
pointer += OpCode9.length
state = Ready
2019-12-09 21:17:35 +01:00
runUntilHalt()
2019-12-09 13:20:04 +01:00
case _ => throw new Exception("Something went wrong")
}
}
def runInput(input: Long): Machine =
{
val tuple = OpCode99.parseInt(software(pointer).toInt)
tuple match
{
case (_, _, _, OpCode99) =>
state = Finished
this
case (_, _, m1, inputInstr: Input) =>
inputInstr.input(software, relative, software(pointer+1), m1, input)
pointer += inputInstr.length
state = Ready
this
case _ => throw new Exception("Unexpected instruction")
}
}
@scala.annotation.tailrec
2019-12-09 21:17:35 +01:00
final def run(): Machine =
2019-12-09 13:20:04 +01:00
{
this.state match
{
case Ready =>
2019-12-09 21:17:35 +01:00
runUntilHalt()
2019-12-09 13:20:04 +01:00
run()
case Finished =>
2019-12-09 21:17:35 +01:00
this
2019-12-09 13:20:04 +01:00
case Output =>
2019-12-09 21:17:35 +01:00
runUntilHalt()
run()
2019-12-09 13:20:04 +01:00
case Input =>
2019-12-09 21:17:35 +01:00
if (inputStream.isEmpty)
2019-12-09 13:20:04 +01:00
{
println("Ran out of inputs")
2019-12-09 21:17:35 +01:00
this
2019-12-09 13:20:04 +01:00
}
else
{
2019-12-09 21:17:35 +01:00
runInput(inputStream.remove(0))
run()
2019-12-09 13:20:04 +01:00
}
}
}
}