/**
* Used in :
* - 2019/5
* - 2019/7
* - 2019/9
* - 2019/11
* - 2019/13
* - 2019/15
* - 2019/17
* - 2019/19
* - 2019/21
* - 2019/23
* - 2019/25
*/
/**
* Incode computer class
* @param Set $ops The program operations
* @param array|int List of pre-defined input to use. "getInput" method can be overwritten to have dynamic inputs
*/
#[AllowDynamicProperties]
class Intcode
{
public function __construct($program, $input = []) {
$this->ops = $program instanceof Set ? (array) $program : (array) $program->numbers();
$this->i = 0;
$this->output = new Set([]);
$this->input = new Set((array) $input);
$this->relative_base = 0;
$this->ended = false;
$this->stop = false;
}
/**
* Get the next operation to run, with its parameters
*
* @return array
*/
public function getNextOP()
{
$op = str_split("00000" . $this->ops[$this->i]);
$modes = array_reverse(array_map("intval", array_slice($op, 0, -2)));
$op = (int) implode("", array_slice($op, -2));
// Number of parameters for each commands
$pcount = match ($op) {
1, 2 => 3,
3, 4 => 1,
5, 6 => 2,
7, 8 => 3,
9 => 1,
99 => 0
};
// Get the parameters based on the paramters modes
$params = [];
for ($k = 0; $k < $pcount; $k++) {
$value = $this->ops[$this->i + $k + 1] ?? 0;
switch ($modes[$k] ?? 0) {
case 0: // Position mode
$params[] = $this->ops[$value] ?? 0;
break;
case 1: // Immediate mode
$params[] = $value;
break;
case 2: // Relative mode
$params[] = $this->ops[$this->relative_base + $value] ?? 0;
break;
}
}
// Move the op pointer
$this->i += 1 + $pcount;
return [$op, $params, set(array_slice($modes, 0, $pcount))];
}
/**
* Executre the next operation
*
* @return bool Can continue (not waiting for input or stopped)
*/
public function next()
{
$this->stop = false;
// Get OP code and parameters
[$op, $p, $modes] = $this->getNextOP();
// Last parameter is write position, offset if relative mode
$write_pos = $this->ops[$this->i - 1];
$write_mode = $modes->last();
if ($write_mode == 2) {
$write_pos += $this->relative_base;
}
// Hangs waiting for an input
if ($op == 3 && ($input = $this->getInput()) === null) {
$this->i -= 2;
return false;
}
// Execute
match ($op) {
1 => $this->ops[$write_pos] = $p[0] + $p[1],
2 => $this->ops[$write_pos] = $p[0] * $p[1],
3 => $this->ops[$write_pos] = $input,
4 => $this->sendOutput($p[0]),
5 => $this->i = $p[0] ? $p[1] : $this->i,
6 => $this->i = $p[0] ? $this->i : $p[1],
7 => $this->ops[$write_pos] = $p[0] < $p[1] ? 1 : 0,
8 => $this->ops[$write_pos] = $p[0] == $p[1] ? 1 : 0,
9 => $this->relative_base += $p[0],
default => null
};
if ($op == 99) {
$this->ended = true;
return false;
}
return !$this->stop;
}
/**
* Run the program, updating the pointer, the output and the state
*
* @return mixed
*/
public function run()
{
while ($this->next()) {}
return $this->onStop();
}
/**
* Output a value
*
* @param int $value
* @return void
*/
public function sendOutput($value)
{
$this->output[] = $value;
return $value;
}
/**
* Get an input
*
* @return int
*/
public function getInput()
{
return $this->input->shift();
}
/**
* Return value when the Intcode computer halts
*
* @return mixed
*/
public function onStop()
{
return $this->output->last();
}
}