Skip to content

Instantly share code, notes, and snippets.

@0e4ef622
Created December 9, 2019 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0e4ef622/0f44c1fa4145def86648b44398fb1b27 to your computer and use it in GitHub Desktop.
Save 0e4ef622/0f44c1fa4145def86648b44398fb1b27 to your computer and use it in GitHub Desktop.
intcode vm
use std::collections::VecDeque;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Status {
Ready,
WaitingForInput,
Finished,
}
#[derive(Clone, Debug)]
pub struct Icvm {
program: Vec<i128>,
input: VecDeque<i128>,
output: VecDeque<i128>,
pc: usize,
status: Status,
base: i128,
}
impl Icvm {
/// Create a new Intcode VM.
pub fn new(program: Vec<i128>) -> Self {
Self {
program,
input: VecDeque::new(),
output: VecDeque::new(),
pc: 0,
status: Status::Ready,
base: 0,
}
}
/// Run until completion, or until the program needs more input.
pub fn run(&mut self) {
while self.status == Status::Ready {
self.step();
}
}
/// Step one instruction forward.
pub fn step(&mut self) {
// starting from 1
let op = self.program[self.pc] % 100;
let j = match op {
1 => { *self.get_param(3) = *self.get_param(1) + *self.get_param(2); 4 }
2 => { *self.get_param(3) = *self.get_param(1) * *self.get_param(2); 4 }
3 => if let Some(i) = self.input.pop_front() { *self.get_param(1) = i; 2 }
else { self.status = Status::WaitingForInput; 0 }
4 => { let o = *self.get_param(1); self.output.push_back(o); 2 },
5 => if *self.get_param(1) != 0 { self.pc = *self.get_param(2) as usize; 0 } else { 3 }
6 => if *self.get_param(1) == 0 { self.pc = *self.get_param(2) as usize; 0 } else { 3 }
7 => { *self.get_param(3) = (*self.get_param(1) < *self.get_param(2)) as i128; 4 }
8 => { *self.get_param(3) = (*self.get_param(1) == *self.get_param(2)) as i128; 4 }
9 => { self.base += *self.get_param(1); 2 }
99 => { self.status = Status::Finished; 0 }
_ => panic!("bad opcode"),
};
self.pc += j;
}
fn get_param(&mut self, n: u32) -> &mut i128 {
let mode = self.program[self.pc] / 10i128.pow(n+1) % 10;
let pc = self.pc;
let pg = &mut self.program[..]; // magically avoids lifetime issues
match mode {
0 => &mut pg[pg[pc + n as usize] as usize],
1 => &mut pg[pc + n as usize],
2 => &mut pg[(pg[self.pc + n as usize] + self.base) as usize],
_ => panic!("bad mode"),
}
}
/// The current program counter, or instruction pointer.
pub fn pc(&self) -> usize {
self.pc
}
/// The current status.
pub fn status(&self) -> Status {
self.status
}
/// The program this VM contains.
pub fn program(&self) -> &[i128] {
&self.program
}
/// Add an input to the VM.
pub fn push_input(&mut self, i: i128) {
self.input.push_back(i);
if self.status == Status::WaitingForInput {
self.status = Status::Ready;
}
}
/// Add multiple inputs to the VM.
pub fn push_inputs<I>(&mut self, i: I)
where
I: IntoIterator<Item = i128>
{
self.input.extend(i);
if self.status == Status::WaitingForInput && !self.input.is_empty() {
self.status = Status::Ready;
}
}
/// Read the current input queue.
pub fn inputs(&self) -> impl Iterator<Item = i128> + '_ {
self.input.iter().copied()
}
/// Pop an output from the output queue.
pub fn pop_output(&mut self) -> Option<i128> {
self.output.pop_front()
}
/// Read the outputs without popping them off the output queue.
pub fn outputs(&self) -> impl Iterator<Item = i128> + '_ {
self.output.iter().copied()
}
/// Read the outputs while popping them off the output queue. See [`Vec::drain`].
///
/// [`Vec::drain`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain
pub fn drain_outputs(&mut self) -> impl Iterator<Item = i128> + '_ {
self.output.drain(..)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment