use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
use std::thread::JoinHandle;
use std::convert::TryFrom;
use std::collections::HashMap;
solution_printer!(9, print_solution, input_generator, INPUT, solve_part_1, solve_part_2);
pub const INPUT: &str = include_str!("../input/2019/day9.txt");
pub fn solve_part_1 (program_image: &Memory) -> Intcode
{
let outputs = run_single_program_with_predefined_inputs(program_image, &[1]);
assert_eq!(outputs.len(), 1);
outputs[0]
}
pub type Intcode = i64;
pub type Memory = Vec<Intcode>;
pub type InputsPortA = [Intcode];
pub type OutputsPortB = Vec<Intcode>;
pub fn input_generator (input: &str) -> Memory
{
input.trim_end_matches('\n').split(',').map(|intcode_str| intcode_str.parse::<Intcode>().unwrap()).collect()
}
#[derive(Debug)]
enum Instruction
{
Add,
Multiply,
ReadFromPortA,
WriteToPortB,
JumpIfTrue,
JumpIfFalse,
LessThan,
Equals,
AdjustRelativeBase,
Exit,
}
pub fn run_single_program_with_predefined_inputs (program_image: &Memory, inputs: &'static InputsPortA) -> OutputsPortB
{
let (emulator_input_tx, emulator_input_rx) = mpsc::channel();
let (emulator_output_tx, emulator_output_rx) = mpsc::channel();
let (output_reader_signal_tx, output_reader_signal_rx) = mpsc::channel();
let output_reader_handle = thread::spawn(move ||
{
let mut outputs = vec![];
loop
{
match emulator_output_rx.try_recv()
{
Ok(value) =>
{
outputs.push(value);
},
Err(_) => if let Ok(_) = output_reader_signal_rx.try_recv()
{
break;
}
}
}
outputs
});
let emulator_handle =
run_intcode_computer_emulator_thread(program_image, emulator_input_rx, emulator_output_tx);
let input_writer_handle = thread::spawn(move ||
{
for &input in inputs
{
emulator_input_tx.send(input).unwrap();
}
});
input_writer_handle.join().unwrap();
emulator_handle.join().unwrap();
output_reader_signal_tx.send(true).unwrap();
output_reader_handle.join().unwrap()
}
pub struct EmulationFinished
{
pub virtual_memory: VirtualMemory,
pub pc: usize,
pub rb: usize,
pub num_cpu_cycles_emulated: u64,
pub num_inputs_read: u64,
pub num_outputs_written: u64,
}
pub struct VirtualMemory
{
real_memory: Memory,
program_image_len: usize,
virtual_address_mapping: HashMap<usize, usize>,
}
impl From<Memory> for VirtualMemory
{
fn from (program_image: Memory) -> Self
{
let program_image_len = program_image.len();
Self
{
real_memory: program_image,
program_image_len,
virtual_address_mapping: Default::default(),
}
}
}
impl VirtualMemory
{
fn read (&self, address: usize) -> Intcode
{
if address < self.program_image_len
{
self.real_memory[address]
}
else
{
match self.virtual_address_mapping.get(&address)
{
Some(&real_address) => self.real_memory[real_address],
None => 0, }
}
}
fn write (&mut self, address: usize, value: Intcode)
{
if address < self.program_image_len
{
self.real_memory[address] = value;
}
else if let Some(&real_address) = self.virtual_address_mapping.get(&address)
{
self.real_memory[real_address] = value;
}
else
{
self.real_memory.push(value);
self.virtual_address_mapping.insert(address, self.real_memory.len() - 1);
}
}
}
#[derive(Debug)]
enum ParamMode
{
PositionMode,
ImmediateMode,
RelativeMode,
}
impl TryFrom<Intcode> for ParamMode
{
type Error = &'static str;
fn try_from (value: Intcode) -> Result<Self, Self::Error>
{
match value
{
0 => Ok(Self::PositionMode),
1 => Ok(Self::ImmediateMode),
2 => Ok(Self::RelativeMode),
_ => Err("Invalid mode for parameter."),
}
}
}
pub fn run_intcode_computer_emulator_thread (program_image: &Memory, inputs_port_a_rx: Receiver<Intcode>, outputs_port_b_tx: Sender<Intcode>) -> JoinHandle<EmulationFinished>
{
let mut vmem = VirtualMemory::from(program_image.clone());
thread::spawn(move ||
{
let mut pc = 0; let mut rb = 0;
let mut num_cpu_cycles_emulated = 0;
let mut num_inputs_read = 0;
let mut num_outputs_written = 0;
loop
{
num_cpu_cycles_emulated += 1;
let opcode = vmem.read(pc);
let mut param_modes = opcode / 100;
let (instruction, (input_param_1, input_param_2), output_addr_param) = match opcode % 100
{
1 => (Instruction::Add, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), Some(vmem.read(pc+3))),
2 => (Instruction::Multiply, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), Some(vmem.read(pc+3))),
3 => (Instruction::ReadFromPortA, (None, None), Some(vmem.read(pc+1))),
4 => (Instruction::WriteToPortB, (Some(vmem.read(pc+1)), None), None),
5 => (Instruction::JumpIfTrue, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), None),
6 => (Instruction::JumpIfFalse, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), None),
7 => (Instruction::LessThan, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), Some(vmem.read(pc+3))),
8 => (Instruction::Equals, (Some(vmem.read(pc+1)), Some(vmem.read(pc+2))), Some(vmem.read(pc+3))),
9 => (Instruction::AdjustRelativeBase, (Some(vmem.read(pc+1)), None), None),
99 => (Instruction::Exit, (None, None), None),
_ => panic!("Invalid opcode {} at position {}", opcode, pc),
};
let input_param_1 = match input_param_1
{
None => None,
Some(ip1_value) =>
{
let ip1m = ParamMode::try_from(param_modes % 10).unwrap(); param_modes /= 10;
match ip1m
{
ParamMode::PositionMode => Some(vmem.read(usize::try_from(ip1_value).unwrap())),
ParamMode::ImmediateMode => Some(ip1_value),
ParamMode::RelativeMode => Some(vmem.read(usize::try_from(Intcode::try_from(rb).unwrap() + ip1_value).unwrap())),
}
}
};
let input_param_2 = match input_param_2
{
None => None,
Some(ip2_value) =>
{
let ip2m = ParamMode::try_from(param_modes % 20).unwrap(); param_modes /= 10;
match ip2m
{
ParamMode::PositionMode => Some(vmem.read(usize::try_from(ip2_value).unwrap())),
ParamMode::ImmediateMode => Some(ip2_value),
ParamMode::RelativeMode => Some(vmem.read(usize::try_from(Intcode::try_from(rb).unwrap() + ip2_value).unwrap())),
}
}
};
let output_addr_param = match output_addr_param
{
None => None,
Some(oap_value) =>
{
let oapm = ParamMode::try_from(param_modes % 10).unwrap(); param_modes /= 10;
match oapm
{
ParamMode::PositionMode => Some(oap_value),
ParamMode::ImmediateMode => panic!("Illegal mode for output address parameter: Immediate mode!"),
ParamMode::RelativeMode => Some(Intcode::try_from(rb).unwrap() + oap_value),
}
}
};
assert_eq!(param_modes, 0);
pc += usize::from(input_param_1.is_some()) + usize::from(input_param_2.is_some()) + usize::from(output_addr_param.is_some()) + 1;
match instruction
{
Instruction::Add => vmem.write(usize::try_from(output_addr_param.unwrap()).unwrap(), input_param_1.unwrap() + input_param_2.unwrap()),
Instruction::Multiply => vmem.write(usize::try_from(output_addr_param.unwrap()).unwrap(), input_param_1.unwrap() * input_param_2.unwrap()),
Instruction::ReadFromPortA => { vmem.write(usize::try_from(output_addr_param.unwrap()).unwrap(), inputs_port_a_rx.recv().unwrap()); num_inputs_read += 1; },
Instruction::WriteToPortB => { outputs_port_b_tx.send(input_param_1.unwrap()).unwrap(); num_outputs_written += 1; },
Instruction::JumpIfTrue => if input_param_1.unwrap() != 0 { pc = usize::try_from(input_param_2.unwrap()).unwrap(); },
Instruction::JumpIfFalse => if input_param_1.unwrap() == 0 { pc = usize::try_from(input_param_2.unwrap()).unwrap(); },
Instruction::LessThan => vmem.write(usize::try_from(output_addr_param.unwrap()).unwrap(), Intcode::from(input_param_1.unwrap() < input_param_2.unwrap())),
Instruction::Equals => vmem.write(usize::try_from(output_addr_param.unwrap()).unwrap(), Intcode::from(input_param_1.unwrap() == input_param_2.unwrap())),
Instruction::AdjustRelativeBase => rb = usize::try_from(Intcode::try_from(rb).unwrap() + input_param_1.unwrap()).unwrap(),
Instruction::Exit => break,
}
}
EmulationFinished
{
virtual_memory: vmem,
pc,
rb,
num_cpu_cycles_emulated,
num_inputs_read,
num_outputs_written,
}
})
}
pub fn solve_part_2 (program_image: &Memory) -> Intcode
{
let outputs = run_single_program_with_predefined_inputs(program_image, &[2]);
assert_eq!(outputs.len(), 1);
outputs[0]
}