Compare commits
9 Commits
8e1f42205c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fc803d8a0f | |||
| 3a9379f877 | |||
| 241c417a77 | |||
| 4cbe831115 | |||
| a4c33ae3d2 | |||
| 48ad7e7a96 | |||
| caf307bf2d | |||
| 2792304040 | |||
| 42ead11aa0 |
37
input/day19.txt
Normal file
37
input/day19.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
#ip 4
|
||||
addi 4 16 4
|
||||
seti 1 9 5
|
||||
seti 1 5 2
|
||||
mulr 5 2 1
|
||||
eqrr 1 3 1
|
||||
addr 1 4 4
|
||||
addi 4 1 4
|
||||
addr 5 0 0
|
||||
addi 2 1 2
|
||||
gtrr 2 3 1
|
||||
addr 4 1 4
|
||||
seti 2 6 4
|
||||
addi 5 1 5
|
||||
gtrr 5 3 1
|
||||
addr 1 4 4
|
||||
seti 1 2 4
|
||||
mulr 4 4 4
|
||||
addi 3 2 3
|
||||
mulr 3 3 3
|
||||
mulr 4 3 3
|
||||
muli 3 11 3
|
||||
addi 1 5 1
|
||||
mulr 1 4 1
|
||||
addi 1 2 1
|
||||
addr 3 1 3
|
||||
addr 4 0 4
|
||||
seti 0 2 4
|
||||
setr 4 8 1
|
||||
mulr 1 4 1
|
||||
addr 4 1 1
|
||||
mulr 4 1 1
|
||||
muli 1 14 1
|
||||
mulr 1 4 1
|
||||
addr 3 1 3
|
||||
seti 0 0 0
|
||||
seti 0 2 4
|
||||
32
input/day21.txt
Normal file
32
input/day21.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
#ip 1
|
||||
seti 123 0 2
|
||||
bani 2 456 2
|
||||
eqri 2 72 2
|
||||
addr 2 1 1
|
||||
seti 0 0 1
|
||||
seti 0 3 2
|
||||
bori 2 65536 5
|
||||
seti 4843319 1 2
|
||||
bani 5 255 4
|
||||
addr 2 4 2
|
||||
bani 2 16777215 2
|
||||
muli 2 65899 2
|
||||
bani 2 16777215 2
|
||||
gtir 256 5 4
|
||||
addr 4 1 1
|
||||
addi 1 1 1
|
||||
seti 27 4 1
|
||||
seti 0 7 4
|
||||
addi 4 1 3
|
||||
muli 3 256 3
|
||||
gtrr 3 5 3
|
||||
addr 3 1 1
|
||||
addi 1 1 1
|
||||
seti 25 0 1
|
||||
addi 4 1 4
|
||||
seti 17 0 1
|
||||
setr 4 1 5
|
||||
seti 7 3 1
|
||||
eqrr 2 0 4
|
||||
addr 4 1 1
|
||||
seti 5 3 1
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
aoc_2018::tasks::day16::task1();
|
||||
aoc_2018::tasks::day24::task2();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -15,6 +16,65 @@ pub fn task1() {
|
||||
println!("Day 16 part1: {count}");
|
||||
}
|
||||
|
||||
pub fn task2() {
|
||||
let (samples, program) = include_str!("../../input/day16.txt")
|
||||
.split_once("\n\n\n\n")
|
||||
.unwrap();
|
||||
|
||||
let code_to_op_candidates = samples
|
||||
.split("\n\n")
|
||||
.map(|sample_input| parse(sample_input))
|
||||
.map(|sample| (sample.1.opcode, matches_ops(sample)))
|
||||
.into_group_map_by(|x| x.0);
|
||||
|
||||
// let code_to_op: HashMap<usize, HashSet<Op>> =
|
||||
let mut code_to_op_candidates: HashMap<_, _> = code_to_op_candidates
|
||||
.into_iter()
|
||||
.map(|(code, ops)| {
|
||||
let mut set: HashSet<_> = HashSet::from_iter(OPS.iter().map(ToOwned::to_owned));
|
||||
for (_, candidate_set) in ops {
|
||||
set = set
|
||||
.intersection(&candidate_set)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect();
|
||||
}
|
||||
|
||||
(code, set)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut codes_to_op: HashMap<usize, Op> = HashMap::new();
|
||||
loop {
|
||||
let single = code_to_op_candidates.iter().find(|(_, ops)| ops.len() == 1);
|
||||
let Some(single) = single else {
|
||||
break;
|
||||
};
|
||||
|
||||
let code = *single.0;
|
||||
let op = *single.1.iter().next().unwrap();
|
||||
|
||||
codes_to_op.insert(code, op);
|
||||
|
||||
code_to_op_candidates.iter_mut().for_each(|(_, ops)| {
|
||||
ops.remove(&op);
|
||||
});
|
||||
}
|
||||
|
||||
if codes_to_op.len() != OPS.len() {
|
||||
panic!("Bad final opcode assignments: {codes_to_op:?}");
|
||||
}
|
||||
|
||||
let program: Vec<Instruction> = program.lines().map(parse_instruction).collect();
|
||||
|
||||
let mut registers = Registers([0, 0, 0, 0]);
|
||||
for instruction in program {
|
||||
let op: Op = codes_to_op[&instruction.opcode];
|
||||
registers = op.process(registers, instruction);
|
||||
}
|
||||
|
||||
println!("Day 16 part 2: {}", registers.0[0]);
|
||||
}
|
||||
|
||||
fn parse(sample: &str) -> Sample {
|
||||
let (before, instruction, after) = sample.lines().collect_tuple().unwrap();
|
||||
let before: Vec<_> = before
|
||||
@@ -29,6 +89,12 @@ fn parse(sample: &str) -> Sample {
|
||||
.split(", ")
|
||||
.collect();
|
||||
let after = Registers::from(after);
|
||||
let instruction = parse_instruction(instruction);
|
||||
|
||||
Sample(before, instruction, after)
|
||||
}
|
||||
|
||||
fn parse_instruction(instruction: &str) -> Instruction {
|
||||
let instruction: Vec<usize> = instruction.split(" ").map(|x| x.parse().unwrap()).collect();
|
||||
let instruction = Instruction {
|
||||
opcode: instruction[0],
|
||||
@@ -36,8 +102,7 @@ fn parse(sample: &str) -> Sample {
|
||||
b: instruction[2],
|
||||
c: instruction[3],
|
||||
};
|
||||
|
||||
Sample(before, instruction, after)
|
||||
instruction
|
||||
}
|
||||
|
||||
fn matches(sample: Sample, op: Op) -> bool {
|
||||
@@ -49,6 +114,13 @@ fn matches_count(sample: Sample) -> usize {
|
||||
vec.len()
|
||||
}
|
||||
|
||||
fn matches_ops(sample: Sample) -> HashSet<Op> {
|
||||
OPS.iter()
|
||||
.filter(|op| matches(sample, **op))
|
||||
.map(|op| op.to_owned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
const OPS: &[Op] = &[
|
||||
Op::AddR,
|
||||
Op::AddI,
|
||||
@@ -68,7 +140,7 @@ const OPS: &[Op] = &[
|
||||
Op::EqRR,
|
||||
];
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
enum Op {
|
||||
AddR,
|
||||
AddI,
|
||||
@@ -205,6 +277,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_AddR() {
|
||||
let op = Op::AddR;
|
||||
let result = op.process(
|
||||
@@ -220,6 +293,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_AddI() {
|
||||
let op = Op::AddI;
|
||||
let result = op.process(
|
||||
@@ -235,6 +309,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_MulR() {
|
||||
let op = Op::MulR;
|
||||
let result = op.process(
|
||||
@@ -250,6 +325,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_MulI() {
|
||||
let op = Op::MulI;
|
||||
let result = op.process(
|
||||
@@ -265,6 +341,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_BanR() {
|
||||
let op = Op::BanR;
|
||||
let result = op.process(
|
||||
@@ -280,6 +357,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_BanI() {
|
||||
let op = Op::BanI;
|
||||
let result = op.process(
|
||||
@@ -295,6 +373,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_BorR() {
|
||||
let op = Op::BorR;
|
||||
let result = op.process(
|
||||
@@ -310,6 +389,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_BorI() {
|
||||
let op = Op::BorI;
|
||||
let result = op.process(
|
||||
@@ -325,6 +405,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_SetR() {
|
||||
let op = Op::SetR;
|
||||
let result = op.process(
|
||||
@@ -340,6 +421,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_SetI() {
|
||||
let op = Op::SetI;
|
||||
let result = op.process(
|
||||
@@ -355,6 +437,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtIR_true() {
|
||||
let op = Op::GtIR;
|
||||
let result = op.process(
|
||||
@@ -370,6 +453,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtIR_false() {
|
||||
let op = Op::GtIR;
|
||||
let result = op.process(
|
||||
@@ -385,6 +469,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtRI_true() {
|
||||
let op = Op::GtRI;
|
||||
let result = op.process(
|
||||
@@ -400,6 +485,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtRI_false() {
|
||||
let op = Op::GtRI;
|
||||
let result = op.process(
|
||||
@@ -415,6 +501,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtRR_true() {
|
||||
let op = Op::GtRR;
|
||||
let result = op.process(
|
||||
@@ -430,6 +517,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_GtRR() {
|
||||
let op = Op::GtRR;
|
||||
let result = op.process(
|
||||
@@ -445,6 +533,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqIR_true() {
|
||||
let op = Op::EqIR;
|
||||
let result = op.process(
|
||||
@@ -460,6 +549,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqIR_false() {
|
||||
let op = Op::EqIR;
|
||||
let result = op.process(
|
||||
@@ -475,6 +565,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqRI_true() {
|
||||
let op = Op::EqRI;
|
||||
let result = op.process(
|
||||
@@ -490,6 +581,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqRI_false() {
|
||||
let op = Op::EqRI;
|
||||
let result = op.process(
|
||||
@@ -505,6 +597,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqRR_true() {
|
||||
let op = Op::EqRR;
|
||||
let result = op.process(
|
||||
@@ -520,6 +613,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_EqRR_false() {
|
||||
let op = Op::EqRR;
|
||||
let result = op.process(
|
||||
|
||||
243
src/tasks/day19.rs
Normal file
243
src/tasks/day19.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
pub fn task1() {
|
||||
let input = include_str!("../../input/day19.txt");
|
||||
let result = run1(input, 1);
|
||||
|
||||
println!("Day 19 part 1: {result}");
|
||||
}
|
||||
|
||||
pub fn task2() {
|
||||
let mut a = 1i64;
|
||||
let mut d = 948;
|
||||
if a == 1 {
|
||||
d += 10550400;
|
||||
a = 0;
|
||||
}
|
||||
|
||||
let mut f = 1;
|
||||
'outer: loop {
|
||||
let mut c = 1;
|
||||
'inner: loop {
|
||||
let b = f * c;
|
||||
if b == d {
|
||||
a = a + f;
|
||||
} else if b > d {
|
||||
// this is the important part: break the loop because otherwise we count and count
|
||||
// and count ... but cannot reach the b == d condition without resetting c.
|
||||
break 'inner;
|
||||
}
|
||||
c += 1;
|
||||
if c > d {
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
f += 1;
|
||||
if f > d {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Day 19 part 2: {a}");
|
||||
}
|
||||
|
||||
fn run1(input: &str, start_value: i32) -> i32 {
|
||||
let (config, program) = input.split_once("\n").unwrap();
|
||||
let ip_register: usize = config.split_once(" ").unwrap().1.parse().unwrap();
|
||||
|
||||
let program: Vec<Instruction> = program.lines().map(parse_instruction).collect();
|
||||
|
||||
let mut registers: Registers = [start_value, 0, 0, 0, 0, 0];
|
||||
let mut ip = 0;
|
||||
let mut count = 0;
|
||||
loop {
|
||||
//print!("ip={ip}, {registers:?}");
|
||||
|
||||
registers[ip_register] = ip as i32;
|
||||
let instruction = program.get(ip).unwrap();
|
||||
|
||||
if ip == 2 {
|
||||
println!("{registers:?}");
|
||||
}
|
||||
|
||||
//print!("{instruction:?}");
|
||||
|
||||
let op: Op = instruction.opcode;
|
||||
registers = op.process(registers, *instruction);
|
||||
|
||||
//println!("{registers:?}");
|
||||
|
||||
ip = registers[ip_register] as usize;
|
||||
ip += 1;
|
||||
|
||||
if program.get(ip).is_none() {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
println!("executed instructions: {count}");
|
||||
registers[0]
|
||||
}
|
||||
|
||||
fn parse_instruction(instruction: &str) -> Instruction {
|
||||
let instruction: Vec<_> = instruction.split(" ").collect();
|
||||
let opcode = match instruction[0] {
|
||||
"addr" => Op::AddR,
|
||||
"addi" => Op::AddI,
|
||||
"mulr" => Op::MulR,
|
||||
"muli" => Op::MulI,
|
||||
"banr" => Op::BanR,
|
||||
"bani" => Op::BanI,
|
||||
"borr" => Op::BorR,
|
||||
"bori" => Op::BorI,
|
||||
"setr" => Op::SetR,
|
||||
"seti" => Op::SetI,
|
||||
"gtir" => Op::GtIR,
|
||||
"gtri" => Op::GtRI,
|
||||
"gtrr" => Op::GtRR,
|
||||
"eqir" => Op::EqIR,
|
||||
"eqri" => Op::EqRI,
|
||||
"eqrr" => Op::EqRR,
|
||||
&_ => {
|
||||
panic!("unknown op {}", instruction[0])
|
||||
}
|
||||
};
|
||||
let instruction = Instruction {
|
||||
opcode,
|
||||
a: instruction[1].parse().unwrap(),
|
||||
b: instruction[2].parse().unwrap(),
|
||||
c: instruction[3].parse().unwrap(),
|
||||
};
|
||||
instruction
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
enum Op {
|
||||
AddR,
|
||||
AddI,
|
||||
MulR,
|
||||
MulI,
|
||||
BanR,
|
||||
BanI,
|
||||
BorR,
|
||||
BorI,
|
||||
SetR,
|
||||
SetI,
|
||||
GtIR,
|
||||
GtRI,
|
||||
GtRR,
|
||||
EqIR,
|
||||
EqRI,
|
||||
EqRR,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn process(&self, registers: Registers, instruction: Instruction) -> Registers {
|
||||
let mut result = registers.clone();
|
||||
|
||||
match self {
|
||||
Op::AddR => {
|
||||
result[instruction.c] = result[instruction.a] + result[instruction.b];
|
||||
}
|
||||
Op::AddI => {
|
||||
result[instruction.c] = result[instruction.a] + instruction.b as i32;
|
||||
}
|
||||
Op::MulR => {
|
||||
result[instruction.c] = result[instruction.a] * result[instruction.b];
|
||||
}
|
||||
Op::MulI => {
|
||||
result[instruction.c] = result[instruction.a] * instruction.b as i32;
|
||||
}
|
||||
Op::BanR => {
|
||||
result[instruction.c] = result[instruction.a].bitand(result[instruction.b]);
|
||||
}
|
||||
Op::BanI => {
|
||||
result[instruction.c] = result[instruction.a].bitand(instruction.b as i32);
|
||||
}
|
||||
Op::BorR => {
|
||||
result[instruction.c] = result[instruction.a].bitor(result[instruction.b]);
|
||||
}
|
||||
Op::BorI => {
|
||||
result[instruction.c] = result[instruction.a].bitor(instruction.b as i32);
|
||||
}
|
||||
Op::SetR => {
|
||||
result[instruction.c] = result[instruction.a];
|
||||
}
|
||||
Op::SetI => {
|
||||
result[instruction.c] = instruction.a as i32;
|
||||
}
|
||||
Op::GtIR => {
|
||||
result[instruction.c] = if instruction.a as i32 > result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::GtRI => {
|
||||
result[instruction.c] = if result[instruction.a] > instruction.b as i32 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::GtRR => {
|
||||
result[instruction.c] = if result[instruction.a] > result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqIR => {
|
||||
result[instruction.c] = if instruction.a as i32 == result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqRI => {
|
||||
result[instruction.c] = if result[instruction.a] == instruction.b as i32 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqRR => {
|
||||
result[instruction.c] = if result[instruction.a] == result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
type Registers = [i32; 6];
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Instruction {
|
||||
opcode: Op,
|
||||
a: usize,
|
||||
b: usize,
|
||||
c: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::tasks::day19::run1;
|
||||
|
||||
#[test]
|
||||
fn example1() {
|
||||
let input = "#ip 0\n\
|
||||
seti 5 0 1\n\
|
||||
seti 6 0 2\n\
|
||||
addi 0 1 0\n\
|
||||
addr 1 2 3\n\
|
||||
setr 1 0 0\n\
|
||||
seti 8 0 4\n\
|
||||
seti 9 0 5";
|
||||
assert_eq!(run1(input, 0), 6);
|
||||
}
|
||||
}
|
||||
229
src/tasks/day21.rs
Normal file
229
src/tasks/day21.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
use std::collections::HashSet;
|
||||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
pub fn task1() {
|
||||
let input = include_str!("../../input/day21.txt");
|
||||
let result = run1(input, 3007673);
|
||||
println!("{result:?}");
|
||||
}
|
||||
|
||||
fn run1(input: &str, start_value: i64) -> (i64, usize) {
|
||||
let (config, program) = input.split_once("\n").unwrap();
|
||||
let ip_register: usize = config.split_once(" ").unwrap().1.parse().unwrap();
|
||||
|
||||
let program: Vec<Instruction> = program.lines().map(parse_instruction).collect();
|
||||
|
||||
let mut registers: Registers = [start_value, 0, 0, 0, 0, 0];
|
||||
let mut ip = 0;
|
||||
let mut count = 0usize;
|
||||
let mut seen: HashSet<_> = HashSet::new();
|
||||
let mut last = 0;
|
||||
loop {
|
||||
//print!("ip={ip}, {registers:?}");
|
||||
|
||||
registers[ip_register] = ip as i64;
|
||||
let instruction = program.get(ip).unwrap();
|
||||
|
||||
//print!("{instruction:?}");
|
||||
|
||||
let op: Op = instruction.opcode;
|
||||
registers = op.process(registers, *instruction);
|
||||
|
||||
//println!("{registers:?}");
|
||||
|
||||
ip = registers[ip_register] as usize;
|
||||
ip += 1;
|
||||
|
||||
// Part 1: uncomment this and print look at register 2 to find the value for register 0
|
||||
// that would cause the program to halt
|
||||
// if ip == 28 {
|
||||
// println!("Value in register 2: {}", registers[2]);
|
||||
// }
|
||||
// Part 2: 9969507 too high
|
||||
// 16774755 highest value
|
||||
// Store all values of register 2 at the possible exit point. Once you see one you already
|
||||
// saw: you've seen all (there is a loop). The one before that was the one it took the longest
|
||||
// to reach without seeing anything twice.
|
||||
if ip == 28 {
|
||||
if seen.contains(®isters[2]) {
|
||||
println!("First double: {registers:?}");
|
||||
println!("Last was {last}");
|
||||
break;
|
||||
} else {
|
||||
seen.insert(registers[2]);
|
||||
last = registers[2];
|
||||
}
|
||||
}
|
||||
|
||||
if program.get(ip).is_none() {
|
||||
println!("exit by leaving instruction space");
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
println!("executed instructions: {count}");
|
||||
(registers[0], count)
|
||||
}
|
||||
|
||||
fn parse_instruction(instruction: &str) -> Instruction {
|
||||
let instruction: Vec<_> = instruction.split(" ").collect();
|
||||
let opcode = match instruction[0] {
|
||||
"addr" => Op::AddR,
|
||||
"addi" => Op::AddI,
|
||||
"mulr" => Op::MulR,
|
||||
"muli" => Op::MulI,
|
||||
"banr" => Op::BanR,
|
||||
"bani" => Op::BanI,
|
||||
"borr" => Op::BorR,
|
||||
"bori" => Op::BorI,
|
||||
"setr" => Op::SetR,
|
||||
"seti" => Op::SetI,
|
||||
"gtir" => Op::GtIR,
|
||||
"gtri" => Op::GtRI,
|
||||
"gtrr" => Op::GtRR,
|
||||
"eqir" => Op::EqIR,
|
||||
"eqri" => Op::EqRI,
|
||||
"eqrr" => Op::EqRR,
|
||||
&_ => {
|
||||
panic!("unknown op {}", instruction[0])
|
||||
}
|
||||
};
|
||||
let instruction = Instruction {
|
||||
opcode,
|
||||
a: instruction[1].parse().unwrap(),
|
||||
b: instruction[2].parse().unwrap(),
|
||||
c: instruction[3].parse().unwrap(),
|
||||
};
|
||||
instruction
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
enum Op {
|
||||
AddR,
|
||||
AddI,
|
||||
MulR,
|
||||
MulI,
|
||||
BanR,
|
||||
BanI,
|
||||
BorR,
|
||||
BorI,
|
||||
SetR,
|
||||
SetI,
|
||||
GtIR,
|
||||
GtRI,
|
||||
GtRR,
|
||||
EqIR,
|
||||
EqRI,
|
||||
EqRR,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn process(&self, registers: Registers, instruction: Instruction) -> Registers {
|
||||
let mut result = registers.clone();
|
||||
|
||||
match self {
|
||||
Op::AddR => {
|
||||
result[instruction.c] = result[instruction.a] + result[instruction.b];
|
||||
}
|
||||
Op::AddI => {
|
||||
result[instruction.c] = result[instruction.a] + instruction.b as i64;
|
||||
}
|
||||
Op::MulR => {
|
||||
result[instruction.c] = result[instruction.a] * result[instruction.b];
|
||||
}
|
||||
Op::MulI => {
|
||||
result[instruction.c] = result[instruction.a] * instruction.b as i64;
|
||||
}
|
||||
Op::BanR => {
|
||||
result[instruction.c] = result[instruction.a].bitand(result[instruction.b]);
|
||||
}
|
||||
Op::BanI => {
|
||||
result[instruction.c] = result[instruction.a].bitand(instruction.b as i64);
|
||||
}
|
||||
Op::BorR => {
|
||||
result[instruction.c] = result[instruction.a].bitor(result[instruction.b]);
|
||||
}
|
||||
Op::BorI => {
|
||||
result[instruction.c] = result[instruction.a].bitor(instruction.b as i64);
|
||||
}
|
||||
Op::SetR => {
|
||||
result[instruction.c] = result[instruction.a];
|
||||
}
|
||||
Op::SetI => {
|
||||
result[instruction.c] = instruction.a as i64;
|
||||
}
|
||||
Op::GtIR => {
|
||||
result[instruction.c] = if instruction.a as i64 > result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::GtRI => {
|
||||
result[instruction.c] = if result[instruction.a] > instruction.b as i64 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::GtRR => {
|
||||
result[instruction.c] = if result[instruction.a] > result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqIR => {
|
||||
result[instruction.c] = if instruction.a as i64 == result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqRI => {
|
||||
result[instruction.c] = if result[instruction.a] == instruction.b as i64 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Op::EqRR => {
|
||||
result[instruction.c] = if result[instruction.a] == result[instruction.b] {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
type Registers = [i64; 6];
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Instruction {
|
||||
opcode: Op,
|
||||
a: usize,
|
||||
b: usize,
|
||||
c: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::tasks::day21::run1;
|
||||
|
||||
#[test]
|
||||
fn example1() {
|
||||
let input = "#ip 0\n\
|
||||
seti 5 0 1\n\
|
||||
seti 6 0 2\n\
|
||||
addi 0 1 0\n\
|
||||
addr 1 2 3\n\
|
||||
setr 1 0 0\n\
|
||||
seti 8 0 4\n\
|
||||
seti 9 0 5";
|
||||
assert_eq!(run1(input, 0).0, 6);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,32 @@
|
||||
use crate::utils;
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn task1() {
|
||||
let _input = "Immune System:
|
||||
17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2
|
||||
989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3
|
||||
use itertools::Itertools;
|
||||
|
||||
Infection:
|
||||
801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
|
||||
4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4";
|
||||
use crate::utils;
|
||||
|
||||
pub fn task1() {
|
||||
let input = utils::read_file("input/day24.txt");
|
||||
let mut game = Game::create(&input);
|
||||
|
||||
let result = play(&input, 0);
|
||||
println!("Standing units after the game: {}", result.unwrap().1);
|
||||
}
|
||||
|
||||
pub fn task2() {
|
||||
let input = utils::read_file("input/day24.txt");
|
||||
|
||||
let immune_final_count = (0..)
|
||||
.map(|boost| play(&input, boost))
|
||||
.find(|result| result.map(|(team, _)| team) == Some(Team::ImmuneSystem))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
println!("Immune systems final count: {immune_final_count}");
|
||||
}
|
||||
|
||||
fn play(input: &str, boost: i64) -> Option<(Team, i64)> {
|
||||
let mut game = Game::create(&input, boost);
|
||||
|
||||
println!(
|
||||
"Immune start units: {}",
|
||||
@@ -30,31 +45,41 @@ Infection:
|
||||
.sum::<i64>()
|
||||
);
|
||||
|
||||
let mut rounds_played = 0;
|
||||
while !game.is_over() {
|
||||
game.round();
|
||||
rounds_played += 1;
|
||||
if game.draw {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
println!("{:#?}", game);
|
||||
println!("Played {} rounds", rounds_played);
|
||||
|
||||
println!(
|
||||
"Standing units after the game: {}",
|
||||
game.groups.values().map(|it| it.units).sum::<i64>()
|
||||
);
|
||||
|
||||
// 21107 too high
|
||||
// 21004 too high
|
||||
Some((game.winning_team(), game.remaining_units()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Game {
|
||||
groups: HashMap<usize, Group>,
|
||||
draw: bool,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn create(input: &str) -> Self {
|
||||
fn remaining_units(&self) -> i64 {
|
||||
self.groups.values().map(|it| it.units).sum::<i64>()
|
||||
}
|
||||
|
||||
fn winning_team(&self) -> Team {
|
||||
let teams: Vec<Team> = self
|
||||
.groups
|
||||
.values()
|
||||
.map(|group| group.team)
|
||||
.unique()
|
||||
.collect();
|
||||
if teams.len() != 1 {
|
||||
panic!("No winning team. Remaining teams: {teams:?}");
|
||||
}
|
||||
teams[0]
|
||||
}
|
||||
|
||||
fn create(input: &str, boost: i64) -> Self {
|
||||
let mut groups = HashMap::new();
|
||||
let mut team = Team::ImmuneSystem;
|
||||
for (id, line) in input.lines().enumerate() {
|
||||
@@ -67,13 +92,21 @@ impl Game {
|
||||
}
|
||||
"" => (),
|
||||
group => {
|
||||
if let Some(group) = Group::from_str(group, team, id) {
|
||||
if let Some(mut group) = Group::from_str(group, team, id) {
|
||||
if group.team == Team::ImmuneSystem {
|
||||
group.attack_damage += boost;
|
||||
}
|
||||
groups.insert(id, group);
|
||||
} else {
|
||||
panic!("bad group: {group}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Game { groups }
|
||||
Game {
|
||||
groups,
|
||||
draw: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn round(&mut self) {
|
||||
@@ -96,7 +129,6 @@ impl Game {
|
||||
.values()
|
||||
.filter(|it| it.team != group.team)
|
||||
.filter(|it| !target.values().any(|t| *t == it.id))
|
||||
// .filter(|it| group.compute_attack_damage_to(&it) >= it.hp_each)
|
||||
// .inspect(|it| {
|
||||
// println!(
|
||||
// "{} would deal {} damage to {}",
|
||||
@@ -114,10 +146,10 @@ impl Game {
|
||||
})
|
||||
{
|
||||
if group.compute_attack_damage_to(t) <= 0 {
|
||||
println!(
|
||||
"Didn't find a target where {:?} can deal positive damage.",
|
||||
group
|
||||
);
|
||||
// println!(
|
||||
// "Didn't find a target where {:?} can deal positive damage.",
|
||||
// group
|
||||
// );
|
||||
continue;
|
||||
} else {
|
||||
target.insert(group.id, t.id);
|
||||
@@ -129,18 +161,21 @@ impl Game {
|
||||
let mut all_ids_by_initiative: Vec<usize> = self.groups.values().map(|it| it.id).collect();
|
||||
all_ids_by_initiative.sort_unstable_by_key(|id| Reverse(self.groups[id].initiative));
|
||||
|
||||
self.draw = true;
|
||||
for active_id in all_ids_by_initiative {
|
||||
if !self.groups[&active_id].alive() {
|
||||
// was killed in this round
|
||||
println!("Group {} already dead", active_id);
|
||||
// println!("Group {} already dead", active_id);
|
||||
continue;
|
||||
}
|
||||
if let Some(enemy_id) = target.get(&active_id) {
|
||||
let enemy = &self.groups[enemy_id];
|
||||
let damage: i64 = self.groups[&active_id].compute_attack_damage_to(enemy);
|
||||
let dying_units = damage / enemy.hp_each;
|
||||
if let Some(enemy) = self.groups.get_mut(enemy_id) {
|
||||
enemy.units -= dying_units;
|
||||
self.groups.get_mut(enemy_id).unwrap().units -= dying_units;
|
||||
|
||||
if dying_units > 0 {
|
||||
self.draw = false;
|
||||
}
|
||||
// println!(
|
||||
// "{} dealt {} ({} units) damage to {}",
|
||||
@@ -155,8 +190,7 @@ impl Game {
|
||||
}
|
||||
|
||||
fn is_over(&self) -> bool {
|
||||
self.groups.is_empty()
|
||||
|| self.groups.iter().all(|(_, it)| it.team == Team::Infection)
|
||||
self.groups.iter().all(|(_, it)| it.team == Team::Infection)
|
||||
|| self
|
||||
.groups
|
||||
.iter()
|
||||
@@ -164,7 +198,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
enum Team {
|
||||
Infection,
|
||||
ImmuneSystem,
|
||||
@@ -186,8 +220,9 @@ struct Group {
|
||||
impl Group {
|
||||
fn from_str(input: &str, team: Team, id: usize) -> Option<Self> {
|
||||
// 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
|
||||
// 2347 units each with 3322 hit points with an attack that does 12 cold damage at initiative 2
|
||||
use regex::Regex;
|
||||
let regex = Regex::new(r"(\d+) units each with (\d+) hit points \((.+)\) with an attack that does (\d+) (\w+) damage at initiative (\d+)").unwrap();
|
||||
let regex = Regex::new(r"(\d+) units each with (\d+) hit points(.*)with an attack that does (\d+) (\w+) damage at initiative (\d+)").unwrap();
|
||||
if let Some(m) = regex.captures(input) {
|
||||
let units: i64 = m[1].parse().unwrap();
|
||||
let hp_each: i64 = m[2].parse().unwrap();
|
||||
@@ -196,7 +231,8 @@ impl Group {
|
||||
let initiative: u64 = m[6].parse().unwrap();
|
||||
let mut weaknesses: Vec<String> = Vec::new();
|
||||
let mut immunities: Vec<String> = Vec::new();
|
||||
for part in m[3].split("; ") {
|
||||
let attributes = m[3].trim().trim_start_matches("(").trim_end_matches(")");
|
||||
for part in attributes.split("; ") {
|
||||
if let Some(stripped) = part.strip_prefix("weak to ") {
|
||||
weaknesses = stripped.split(", ").map(|it| it.to_string()).collect();
|
||||
} else if let Some(stripped) = part.strip_prefix("immune to ") {
|
||||
@@ -246,3 +282,26 @@ impl Group {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::tasks::day24::play;
|
||||
|
||||
const INPUT: &str = "Immune System:
|
||||
17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2
|
||||
989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3
|
||||
|
||||
Infection:
|
||||
801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
|
||||
4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4";
|
||||
|
||||
#[test]
|
||||
fn example1() {
|
||||
assert_eq!(5216, play(INPUT, 0).unwrap().1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example2() {
|
||||
assert_eq!(51, play(INPUT, 1570).unwrap().1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ pub mod day15;
|
||||
pub mod day16;
|
||||
pub mod day17;
|
||||
pub mod day18;
|
||||
pub mod day19;
|
||||
pub mod day20;
|
||||
pub mod day21;
|
||||
pub mod day22;
|
||||
pub mod day23;
|
||||
pub mod day24;
|
||||
|
||||
Reference in New Issue
Block a user