2019-12-09 13:20:04 +01:00
|
|
|
package intcode
|
|
|
|
|
2019-12-09 22:09:39 +01:00
|
|
|
import intcode.opcode.{Action, Jump, OpCode3, OpCode4, OpCode9, OpCode99}
|
2019-12-09 13:20:04 +01:00
|
|
|
|
2019-12-09 21:17:35 +01:00
|
|
|
import scala.collection.mutable.ListBuffer
|
|
|
|
|
2019-12-09 13:20:04 +01:00
|
|
|
/**
|
|
|
|
* Virtual IntCode machine
|
2019-12-09 22:09:39 +01:00
|
|
|
* @param input Program memory used to initialize the machine
|
2019-12-09 13:20:04 +01:00
|
|
|
*/
|
|
|
|
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 isReady: Boolean = state == Ready
|
|
|
|
def isInput: Boolean = state == Input
|
|
|
|
def getState: State = state
|
2019-12-09 21:49:31 +01:00
|
|
|
def hasOutput: Boolean = outputStream.nonEmpty
|
2019-12-09 13:20:04 +01:00
|
|
|
|
|
|
|
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:41:08 +01:00
|
|
|
final def run(): Machine =
|
2019-12-09 13:20:04 +01:00
|
|
|
{
|
|
|
|
val tuple = OpCode99.parseInt(software(pointer).toInt)
|
|
|
|
tuple match
|
|
|
|
{
|
2019-12-09 22:09:39 +01:00
|
|
|
// 99 -> Reached end of the program -> halt
|
2019-12-09 13:20:04 +01:00
|
|
|
case (_, _, _, OpCode99) =>
|
|
|
|
state = Finished
|
2019-12-11 07:45:22 +01:00
|
|
|
//println("Machine finished")
|
2019-12-09 13:20:04 +01:00
|
|
|
this
|
|
|
|
|
2019-12-09 22:09:39 +01:00
|
|
|
// 3 -> Trying to retrieve an input value from the buffer; in case of failure -> halt
|
|
|
|
case (_, _, m1, OpCode3) =>
|
2019-12-09 21:36:21 +01:00
|
|
|
//buffer is empty
|
|
|
|
if (inputStream.isEmpty)
|
|
|
|
{
|
|
|
|
state = Input
|
2019-12-11 07:45:22 +01:00
|
|
|
//println("Ran out of inputs")
|
2019-12-09 21:36:21 +01:00
|
|
|
this
|
|
|
|
}
|
|
|
|
// buffer contains inputs
|
|
|
|
else
|
|
|
|
{
|
2019-12-09 22:09:39 +01:00
|
|
|
OpCode3.input(software, relative, software(pointer+1), m1, inputStream.remove(0))
|
|
|
|
pointer += OpCode3.length
|
2019-12-09 21:36:21 +01:00
|
|
|
state = Ready
|
2019-12-09 21:41:08 +01:00
|
|
|
run()
|
2019-12-09 21:36:21 +01:00
|
|
|
}
|
2019-12-09 13:20:04 +01:00
|
|
|
|
2019-12-09 22:09:39 +01:00
|
|
|
// 4 -> Outputting value to the OutputStream buffer
|
|
|
|
case (_, _, m1, OpCode4) =>
|
|
|
|
outputStream.addOne(OpCode4.output(software, relative, software(pointer+1), m1))
|
|
|
|
pointer += OpCode4.length
|
|
|
|
state = Ready
|
|
|
|
run()
|
|
|
|
|
|
|
|
// 5, 6 -> Check condition; if true modify instruction pointer
|
2019-12-09 13:20:04 +01:00
|
|
|
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:41:08 +01:00
|
|
|
run()
|
2019-12-09 13:20:04 +01:00
|
|
|
|
2019-12-09 22:09:39 +01:00
|
|
|
// 1, 2, 7, 8 -> Perform an action
|
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:41:08 +01:00
|
|
|
run()
|
2019-12-09 13:20:04 +01:00
|
|
|
|
2019-12-09 22:09:39 +01:00
|
|
|
// 9 -> Modify relative pointer
|
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:41:08 +01:00
|
|
|
run()
|
2019-12-09 13:20:04 +01:00
|
|
|
|
|
|
|
case _ => throw new Exception("Something went wrong")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|