diff --git a/inputs/day20_example1 b/inputs/day20_example1 new file mode 100644 index 0000000..41d30f0 --- /dev/null +++ b/inputs/day20_example1 @@ -0,0 +1,19 @@ + A + A + #######.######### + #######.........# + #######.#######.# + #######.#######.# + #######.#######.# + ##### B ###.# +BC...## C ###.# + ##.## ###.# + ##...DE F ###.# + ##### G ###.# + #########.#####.# +DE..#######...###.# + #.#########.###.# +FG..#########.....# + ###########.##### + Z + Z \ No newline at end of file diff --git a/inputs/day20_example2 b/inputs/day20_example2 new file mode 100644 index 0000000..5cdd08a --- /dev/null +++ b/inputs/day20_example2 @@ -0,0 +1,37 @@ + A + A + #################.############# + #.#...#...................#.#.# + #.#.#.###.###.###.#########.#.# + #.#.#.......#...#.....#.#.#...# + #.#########.###.#####.#.#.###.# + #.............#.#.....#.......# + ###.###########.###.#####.#.#.# + #.....# A C #.#.#.# + ####### S P #####.# + #.#...# #......VT + #.#.#.# #.##### + #...#.# YN....#.# + #.###.# #####.# +DI....#.# #.....# + #####.# #.###.# +ZZ......# QG....#..AS + ###.### ####### +JO..#.#.# #.....# + #.#.#.# ###.#.# + #...#..DI BU....#..LF + #####.# #.##### +YN......# VT..#....QG + #.###.# #.###.# + #.#...# #.....# + ###.### J L J #.#.### + #.....# O F P #.#...# + #.###.#####.#.#####.#####.###.# + #...#.#.#...#.....#.....#.#...# + #.#####.###.###.#.#.#########.# + #...#.#.....#...#.#.#.#.....#.# + #.###.#####.###.###.#.#.####### + #.#.........#...#.............# + #########.###.###.############# + B J C + U P P \ No newline at end of file diff --git a/src/args.rs b/src/args.rs index d14f8f8..8ab7ed0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -18,6 +18,7 @@ pub enum Command { Amplify(Computer), Image(Image), Arcade(Arcade), + FindSanta(Computer), } fn is_number(s: String) -> Result<(), String> { @@ -120,6 +121,14 @@ impl Command { .required(true) .validator(is_file)) ) + .subcommand(SubCommand::with_name("final") + .about("run the final computer") + .arg(Arg::with_name("COMPUTER") + .index(1) + .help("The computer to run.") + .required(true) + .validator(is_file)) + ) .get_matches(); if let Some(problem1) = matches.subcommand_matches("fuel") { @@ -188,6 +197,12 @@ impl Command { let arcade = Arcade::new(38, 21, true, file); return Command::Arcade(arcade); } + + if let Some(fin) = matches.subcommand_matches("final") { + let file = fin.value_of("COMPUTER").expect("No final computer file!"); + let comp = Computer::load(&file); + return Command::FindSanta(comp); + } panic!("Failed to run a reasonable command."); } diff --git a/src/bugs.rs b/src/bugs.rs new file mode 100644 index 0000000..0cbaa89 --- /dev/null +++ b/src/bugs.rs @@ -0,0 +1,120 @@ +use std::fs; +use std::str; + +#[derive(Clone, Debug, PartialEq)] +struct BugMap { + has_bug: Vec, + width: usize, + height: usize, +} + +impl BugMap { + fn new(s: &str) -> BugMap { + let mut width = 0; + let mut height = 0; + let mut has_bug = Vec::new(); + + println!("s length {}", s.len()); + for line in s.trim().split('\n') { + println!("line: |{}|", line); + for c in line.chars() { + match c { + '.' => has_bug.push(false), + '#' => has_bug.push(true), + _ => panic!("Unexpected character in bug map"), + } + if height == 0 { + width += 1; + } + } + height += 1; + } + + BugMap{ has_bug, width, height } + } + + fn get(&self, x: usize, y: usize) -> bool { + self.has_bug[ (y * self.width) + x ] + } + + fn set(&mut self, x: usize, y: usize, v: bool) { + self.has_bug[ (y * self.width) + x ] = v; + } + + fn next(&self) -> BugMap { + let mut result = self.clone(); + + for x in 0..self.width { + for y in 0..self.height { + let above = if y > 0 { self.get(x, y - 1) } else { false }; + let below = if y < (self.height - 1) { self.get(x, y + 1) } else { false }; + let left = if x > 0 { self.get(x - 1, y) } else { false }; + let right = if x < (self.width - 1) { self.get(x + 1, y) } else {false }; + let bugs_nearby = count(above, below, left, right); + + if self.get(x, y) { + result.set(x, y, bugs_nearby == 1); + } else { + result.set(x, y, (bugs_nearby >= 1) && (bugs_nearby <= 2)); + } + + } + } + + result + } + + fn biodiversity(&self) -> u128 { + let mut result = 0; + let mut two_power = 1; + + for v in self.has_bug.iter() { + if *v { + result += two_power; + } + two_power <<= 1; + } + + result + } + + fn print(&self) { + for y in 0..self.height { + for x in 0..self.width { + print!("{}", if self.get(x, y) { '#' } else { '.' }); + } + println!(); + } + } +} + +fn count(a: bool, b: bool, c: bool, d: bool) -> u8 { + (a as u8) + (b as u8) + (c as u8) + (d as u8) +} + +fn find_duplicate(mut cur: BugMap) -> BugMap { + let mut steps = Vec::new(); + + while !steps.contains(&cur) { + steps.push(cur.clone()); + cur = cur.next(); + } + + cur +} + +#[test] +fn example() { + let map = BugMap::new("....#\n#..#.\n#..##\n..#..\n#...."); + let endpoint = find_duplicate(map); + assert_eq!(2129920, endpoint.biodiversity()); +} + +#[test] +fn day24() { + let contents = fs::read("inputs/day24").expect("Couldn't read day 24 file"); + let bugstr = str::from_utf8(&contents).expect("Couldn't read a string from day 24"); + let bugmap = BugMap::new(&bugstr); + let endpoint = find_duplicate(bugmap); + assert_eq!(32776479, endpoint.biodiversity()); +} \ No newline at end of file diff --git a/src/cards.rs b/src/cards.rs new file mode 100644 index 0000000..2b0bb8c --- /dev/null +++ b/src/cards.rs @@ -0,0 +1,175 @@ +use std::fs::read; +use std::str::{FromStr, from_utf8}; + +enum Shuffle { + DealNew, + Deal(usize), + Cut(usize), + CutBottom(usize), +} + +impl Shuffle { + fn new(s: &str) -> Shuffle { + if s.starts_with("deal with increment ") { + let amt = usize::from_str(&s[19..].trim()).expect("Couldn't parse deal with number"); + return Shuffle::Deal(amt); + } + + if s.starts_with("cut -") { + let amt = usize::from_str(&s[5..].trim()).expect("Couldn't parse cut with negative number"); + return Shuffle::CutBottom(amt); + } + + if s.starts_with("cut ") { + let amt = usize::from_str(&s[4..].trim()).expect("Couldn't parse cut with number"); + return Shuffle::Cut(amt); + } + + if s.starts_with("deal into new stack") { + return Shuffle::DealNew; + } + + panic!("Couldn't parse shuffle mechanism") + } + + fn apply(&self, mut deck: Vec) -> Vec { + match self { + Shuffle::DealNew => { + deck.reverse(); + deck + } + Shuffle::Deal(amt) => { + let mut res = deck.clone(); + let len = deck.len(); + let mut out = 0; + + for i in 0..len { + res[out] = deck[i]; + out = (out + amt) % len; + } + + res + } + Shuffle::Cut(place) => { + let len = deck.len(); + let mut result = Vec::with_capacity(len); + let mut i = *place; + + loop { + result.push(deck[i]); + i = (i + 1) % len; + if i == *place { + return result; + } + } + } + Shuffle::CutBottom(place) => { + let len = deck.len(); + let mut result = Vec::with_capacity(len); + let mut i = len - *place; + + loop { + result.push(deck[i]); + i = (i + 1) % len; + if i == (len - *place) { + return result; + } + } + } + } + } +} + +struct ShuffleOrder { + order: Vec +} + +impl ShuffleOrder { + fn from_file(s: &str) -> ShuffleOrder { + let raw = read(s).expect("Couldn't read file."); + let strs = from_utf8(&raw).expect("Couldn't get string data"); + ShuffleOrder::new(&strs) + } + + fn new(strs: &str) -> ShuffleOrder { + let mut order = Vec::new(); + + for line in strs.trim().split('\n') { + order.push( Shuffle::new(line) ); + } + + ShuffleOrder{ order } + } + + fn shuffle(&self, initial: &[u32]) -> Vec { + let mut cur = Vec::from(initial); + + for shuffle in self.order.iter() { + cur = shuffle.apply(cur); + } + + cur + } +} + +#[test] +fn base() { + let deck1 = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + assert_eq!(vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0], Shuffle::DealNew.apply(deck1)); + + let deck2 = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + assert_eq!(vec![3, 4, 5, 6, 7, 8, 9, 0, 1, 2], Shuffle::Cut(3).apply(deck2)); + + let deck3 = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + assert_eq!(vec![6, 7, 8, 9, 0, 1, 2, 3, 4, 5], Shuffle::CutBottom(4).apply(deck3)); + + let deck4 = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + assert_eq!(vec![0, 7, 4, 1, 8, 5, 2, 9, 6, 3], Shuffle::Deal(3).apply(deck4)); +} + +#[test] +fn example1() { + let shuffles = ShuffleOrder::new("deal with increment 7\ndeal into new stack\ndeal into new stack"); + let done = shuffles.shuffle(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert_eq!(vec![0, 3, 6, 9, 2, 5, 8, 1, 4, 7], done); +} + +#[test] +fn example2() { + let shuffles = ShuffleOrder::new("cut 6\ndeal with increment 7\ndeal into new stack"); + let done = shuffles.shuffle(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert_eq!(vec![3, 0, 7, 4, 1, 8, 5, 2, 9, 6], done); + +} + +#[test] +fn example3() { + let shuffles = ShuffleOrder::new("deal with increment 7\ndeal with increment 9\ncut -2"); + let done = shuffles.shuffle(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert_eq!(vec![6, 3, 0, 7, 4, 1, 8, 5, 2, 9], done); + +} + +#[test] +fn example4() { + let shuffles = ShuffleOrder::new("deal into new stack\ncut -2\ndeal with increment 7\ncut 8\ncut -4\ndeal with increment 7\ncut 3\ndeal with increment 9\ndeal with increment 3\ncut -1"); + let done = shuffles.shuffle(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert_eq!(vec![9, 2, 5, 8, 1, 4, 7, 0, 3, 6], done); +} + +fn find_card(deck: &[u32], v: u32) -> usize { + for (idx, value) in deck.iter().enumerate() { + if *value == v { + return idx; + } + } + panic!("Couldn't find card {}", v) +} + +#[test] +fn day22a() { + let shuffles = ShuffleOrder::from_file("inputs/day22"); + let deck: Vec = (0..10007).collect(); + let result = shuffles.shuffle(&deck); + assert_eq!(2939, find_card(&result, 2019)); +} \ No newline at end of file diff --git a/src/donut.rs b/src/donut.rs new file mode 100644 index 0000000..c5c9dbc --- /dev/null +++ b/src/donut.rs @@ -0,0 +1,274 @@ +use std::collections::VecDeque; +use std::fs; +use std::str; + +struct Maze { + data: Vec, + width: usize, + height: usize, +} + +#[derive(Clone, Debug, PartialEq)] +enum InputTile { + Wall, + Empty, + Blank, + Letter(char), +} + +#[derive(Clone, Debug, PartialEq)] +enum Tile { + Wall, + Empty, + Jump(String), +} + +impl<'a> From<&'a InputTile> for Tile { + fn from(x: &InputTile) -> Tile { + match x { + InputTile::Wall => Tile::Wall, + InputTile::Empty => Tile::Empty, + InputTile::Blank => Tile::Wall, + InputTile::Letter(_) => Tile::Wall, + } + } +} + +impl Maze { + fn get(&self, x: usize, y:usize) -> T { + assert!(x < self.width); + assert!(y < self.height); + self.data[ (self.width * y) + x ].clone() + } + + fn set(&mut self, x: usize, y: usize, v: T) { + self.data[ (self.width * y) + x ] = v; + } +} + +impl Maze { + fn new(f: &str) -> Maze { + let contents = fs::read(f).expect("Couldn't open input donut maze"); + let strmap = str::from_utf8(&contents).expect("Couldn't turn donut maze into string"); + let mut data = Vec::new(); + let mut width = 0; + let mut height = 0; + + for line in strmap.trim_right().split('\n') { + for c in line.chars() { + match c { + ' ' => data.push(InputTile::Blank), + '.' => data.push(InputTile::Empty), + '#' => data.push(InputTile::Wall), + x if x.is_ascii_alphabetic() => data.push(InputTile::Letter(x)), + _ => panic!("Unknown character {}", c), + } + if height == 0 { + width += 1; + } + } + height += 1; + } + + while data.len() < (width * height) { data.push(InputTile::Blank); } + + Maze{ data, width, height } + } + + fn print(&self) { + for y in 0..self.height { + for x in 0..self.width { + match self.get(x, y) { + InputTile::Blank => print!(" "), + InputTile::Empty => print!("."), + InputTile::Wall => print!("#"), + InputTile::Letter(c) => print!("{}", c), + } + } + println!(); + } + } +} + +impl Maze { + fn new(f: &str) -> Maze { + let inputmap: Maze = Maze::::new(f); + let mut top = 0; + let mut left = 0; + let mut bottom = 0; + let mut right = 0; + + // find the top left corner + for y in 0..inputmap.height { + for x in 0..inputmap.width { + if top == 0 && inputmap.get(x, y) == InputTile::Wall { + top = y; + left = x; + break; + } + } + } + + // find the bottom right corner + for y in (0..inputmap.height).rev() { + for x in (0..inputmap.width).rev() { + if bottom == 0 && inputmap.get(x, y) == InputTile::Wall { + bottom = y; + right = x; + break; + } + } + } + + let width = (right - left) + 1; + let height = (bottom - top) + 1; + let mut data = Vec::with_capacity(width * height); + // Just copy the core bits over. + for y in 0..height { + for x in 0..width { + data.push( Tile::from(&inputmap.get(x + left, y + top)) ); + } + } + + // now we go back through and add the jump points. + let mut res = Maze{ data, width, height }; + for y in 0..inputmap.height-1 { + for x in 0..inputmap.width-1 { + if let InputTile::Letter(c1) = inputmap.get(x, y) { + let mut s = String::new(); + + s.push(c1); + if let InputTile::Letter(c2) = inputmap.get(x + 1, y) { + s.push(c2); + if x + 2 < inputmap.width && inputmap.get(x + 2, y) == InputTile::Empty { + res.set( (x + 2) - left, y - top, Tile::Jump(s)); + } else if x > 0 && inputmap.get(x - 1, y) == InputTile::Empty { + res.set( (x - 1) - left, y - top, Tile::Jump(s)); + } + } else if let InputTile::Letter(c2) = inputmap.get(x, y + 1) { + s.push(c2); + if y + 2 < inputmap.height && inputmap.get(x, y + 2) == InputTile::Empty { + res.set( x - left, (y + 2) - top, Tile::Jump(s)); + } else if y > 0 && inputmap.get(x, y - 1) == InputTile::Empty { + res.set( x - left, y - 1 - top, Tile::Jump(s)); + } + } + } + } + } + + res + } + + fn origin(&self) -> (usize, usize) { + for y in 0..self.height { + for x in 0..self.width { + if self.get(x, y) == Tile::Jump("AA".to_string()) { + return (x, y); + } + } + } + panic!("Couldn't find origin!"); + } + + fn jump_from(&self, sx: usize, sy: usize) -> Option<(usize, usize)> { + if let Tile::Jump(label) = self.get(sx, sy) { + for y in 0..self.height { + for x in 0..self.width { + if (sx != x) || (sy != y) { + if let Tile::Jump(label2) = self.get(x, y) { + if label == label2 { + return Some((x, y)); + } + } + } + } + } + } + + None + } + + fn next_moves(&self, x: usize, y: usize) -> Vec<(usize, usize)> { + let mut res = Vec::new(); + + if x > 0 { res.push((x - 1, y)); } + if y > 0 { res.push((x, y - 1)); } + if x < (self.width - 1) { res.push((x + 1, y)); } + if y < (self.height - 1) { res.push((x, y + 1)); } + if let Some(target) = self.jump_from(x, y) { res.push(target); } + + res + } + + fn find_path(&self) -> Vec<(usize, usize)> { + let initial_path = vec![self.origin()]; + let mut queue = VecDeque::new(); + + queue.push_back(initial_path); + while let Some(mut cur) = queue.pop_front() { + assert_ne!(cur.len(), 0); + let (x, y) = cur[cur.len() - 1]; + let mut nexts = self.next_moves(x, y); + for next in nexts.drain(0..) { + let (nx, ny) = next; + + if let Tile::Jump(lbl) = self.get(nx, ny) { + if lbl == "ZZ".to_string() { + cur.push((nx, ny)); + return cur; + } + } + + if cur.contains(&next) { + continue; + } + + if self.get(nx, ny) == Tile::Wall { + continue; + } + + let mut newcopy = cur.clone(); + newcopy.push((nx, ny)); + queue.push_back(newcopy); + } + } + panic!("No path found!") + } + + fn print(&self) { + for y in 0..self.height { + for x in 0..self.width { + match self.get(x, y) { + Tile::Empty => print!("."), + Tile::Wall => print!("#"), + Tile::Jump(s) => print!("{}", s.chars().next().unwrap()), + } + } + println!(); + } + } +} + +#[test] +fn example1() { + let maze = Maze::::new("inputs/day20_example1"); + assert_eq!(None, maze.jump_from(7, 0)); + assert_eq!(Some((0, 6)), maze.jump_from(7, 4)); + assert_eq!(Some((7, 4)), maze.jump_from(0, 6)); + assert_eq!(23, maze.find_path().len() - 1); +} + +#[test] +fn example2() { + let maze = Maze::::new("inputs/day20_example2"); + let path = maze.find_path(); + assert_eq!(58, path.len() - 1); +} + +#[test] +fn day20() { + let maze = Maze::::new("inputs/day20"); + let path = maze.find_path(); + assert_eq!(606, path.len() - 1); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a2b7076..7a95d70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,21 @@ mod arcade; mod args; #[cfg(test)] +mod bugs; +#[cfg(test)] +mod cards; +#[cfg(test)] mod chemistry; #[cfg(test)] +mod donut; +#[cfg(test)] mod fft; mod fuel; mod image; mod machine; #[cfg(test)] +mod maze; +#[cfg(test)] mod nbody; mod orbits; #[cfg(test)] @@ -16,6 +24,7 @@ mod repair; mod robot; #[cfg(test)] mod router; +mod santafind; #[cfg(test)] mod scaffold; #[cfg(test)] @@ -29,6 +38,7 @@ mod wiremap; use crate::args::Command; use crate::fuel::calculate_fuel; use crate::orbits::Object; +use crate::santafind::find_santa; use crate::wiremap::WireMap; use std::cmp::{max,min}; use terminal_graphics::Display; @@ -184,5 +194,9 @@ fn main() { }); println!("Final score: {}", result.score); } + + Command::FindSanta(comp) => { + find_santa(comp); + } } - } + } \ No newline at end of file diff --git a/src/maze.rs b/src/maze.rs new file mode 100644 index 0000000..e6a1b34 --- /dev/null +++ b/src/maze.rs @@ -0,0 +1,240 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::fs; +use std::str; + +struct Maze { + data: Vec, + keyset: HashSet, + width: usize, + height: usize, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum Tile { + Wall, + Empty, + Door(char), + Key(char), + Entrance, +} + +impl Tile { + fn is_key(&self) -> bool { + match self { + Tile::Key(_) => true, + _ => false, + } + } +} + +impl Maze { + fn new(s: &str) -> Maze { + let mut data = Vec::new(); + let mut keyset = HashSet::new(); + let mut width = 0; + let mut height = 0; + + for line in s.trim().split('\n') { + for c in line.chars() { + match c { + '#' => data.push(Tile::Wall), + '.' => data.push(Tile::Empty), + '@' => data.push(Tile::Entrance), + kd if kd.is_ascii_lowercase() => { + data.push(Tile::Key(kd)); + keyset.insert(kd); + } + kd if kd.is_ascii_uppercase() => data.push(Tile::Door(kd.to_ascii_lowercase())), + kd => panic!("Unrecognized character: {}", kd), + } + if height == 0 { width += 1 } + } + height += 1; + } + + Maze{ data, keyset, width, height } + } + + fn get(&self, x: usize, y: usize) -> Tile { + self.data[ (y * self.width) + x ] + } + + fn origin(&self) -> (usize, usize) { + for x in 0..self.width { + for y in 0..self.height { + if self.get(x, y) == Tile::Entrance { + return (x, y); + } + } + } + panic!("No origin found?!") + } + + fn next_steps(&self, x: usize, y: usize) -> Vec<(usize, usize)> { + let mut initial_res = Vec::new(); + + if x > 0 { initial_res.push((x - 1, y)); } + if y > 0 { initial_res.push((x, y - 1)); } + if x < (self.width - 1) { initial_res.push((x + 1, y)); } + if y < (self.height - 1) { initial_res.push((x, y + 1)); } + + let mut res = Vec::new(); + + for (x, y) in initial_res { + if self.get(x, y) != Tile::Wall { + res.push((x, y)); + } + } + + res + } +} + +#[derive(Clone)] +struct SearchState { + collected_keys: HashSet, + path: Vec<(usize, usize)>, +} + +impl SearchState { + fn new(maze: &Maze) -> SearchState { + SearchState { + collected_keys: HashSet::new(), + path: vec![maze.origin()], + } + } + + fn should_prune(&self, best_cases: &mut HashMap<(usize,usize),Vec>>) -> bool { +// let mut history = vec![]; +// +// for (x, y) in self.path.iter().rev() { +// if maze.get(*x, *y).is_key() { +// break; +// } +// +// if history.contains(&(x, y)) { +// return true; +// } +// +// history.push((x, y)); +// } + + assert_ne!(self.path.len(), 0); + let lastpos = self.path[self.path.len() - 1]; + + match best_cases.get_mut(&lastpos) { + None => { + let _ = best_cases.insert(lastpos, vec![self.collected_keys.clone()]); + } + Some(seen) => { + for previous in seen.iter_mut() { + if self.collected_keys.is_subset(previous) { + return true; + } + + if previous.is_subset(&self.collected_keys) { + *previous = self.collected_keys.clone(); + break; + } + } + seen.push(self.collected_keys.clone()); + } + } + + false + } +} + +fn find_keys(maze: &Maze) -> Vec<(usize, usize)> { + let initial_state = SearchState::new(maze); + let mut queue = VecDeque::new(); + let mut best_states = HashMap::new(); + + queue.push_back(initial_state); + while let Some(state) = queue.pop_front() { + // println!("path length {} [queue length {}, have {:?}, want {:?}]", state.path.len(), queue.len(), state.collected_keys, maze.keyset); + assert_ne!(state.path.len(), 0); + let (x, y) = state.path[state.path.len() - 1]; + let mut new_items = Vec::new(); + + for (newx, newy) in maze.next_steps(x, y).drain(0..) { + match maze.get(newx, newy) { + Tile::Wall => continue, + Tile::Empty => { + let mut newstate = state.clone(); + newstate.path.push((newx, newy)); + new_items.push(newstate); + } + Tile::Door(k) => { + if state.collected_keys.contains(&k) { + let mut newstate = state.clone(); + newstate.path.push((newx, newy)); + new_items.push(newstate); + } + } + Tile::Key(k) => { + let mut newstate = state.clone(); + newstate.path.push((newx, newy)); + newstate.collected_keys.insert(k); + if newstate.collected_keys == maze.keyset { + return newstate.path; + } + new_items.push(newstate); + } + Tile::Entrance => { + let mut newstate = state.clone(); + newstate.path.push((newx, newy)); + new_items.push(newstate); + } + } + } + + for newstate in new_items.drain(0..) { + if !newstate.should_prune(&mut best_states) { + queue.push_back(newstate); + } + } + } + + panic!("Gave up finding all the keys") +} + +#[test] +fn example1() { + let example1 = Maze::new("#########\n#b.A.@.a#\n#########\n"); + assert_eq!((5, 1), example1.origin()); + let target1 = vec![(5, 1), (6, 1), (7, 1), (6, 1), (5, 1), (4, 1), (3, 1), (2, 1), (1, 1)]; + assert_eq!(target1, find_keys(&example1)); +} + +#[test] +fn example2() { + let example2 = Maze::new("########################\n#f.D.E.e.C.b.A.@.a.B.c.#\n######################.#\n#d.....................#\n########################"); + assert_eq!(86, find_keys(&example2).len() - 1); +} + +#[test] +fn example3() { + let example3 = Maze::new("########################\n#...............b.C.D.f#\n#.######################\n#.....@.a.B.c.d.A.e.F.g#\n########################"); + assert_eq!(132, find_keys(&example3).len() - 1); +} + +#[test] +fn example4() { + let example4 = Maze::new("#################\n#i.G..c...e..H.p#\n########.########\n#j.A..b...f..D.o#\n########@########\n#k.E..a...g..B.n#\n########.########\n#l.F..d...h..C.m#\n#################"); + assert_eq!(136, find_keys(&example4).len() - 1); +} + +#[test] +fn example5() { + let example5 = Maze::new("########################\n#@..............ac.GI.b#\n###d#e#f################\n###A#B#C################\n###g#h#i################\n########################"); + assert_eq!(81, find_keys(&example5).len() - 1); +} + +#[test] +fn day18a() { + let day18_contents = fs::read("inputs/day18").expect("Couldn't open day18 problem"); + let day18_str = str::from_utf8(&day18_contents).expect("Couldn't decode day18 problem"); + let maze = Maze::new(&day18_str); + assert_eq!(6098, find_keys(&maze).len() - 1); +} \ No newline at end of file diff --git a/src/santafind.rs b/src/santafind.rs new file mode 100644 index 0000000..55f0dd4 --- /dev/null +++ b/src/santafind.rs @@ -0,0 +1,126 @@ +use crate::machine::{Computer, RunResult}; + +const GATHER_STEPS: [&'static str; 34] = [ + "south", + "take monolith", + "east", + "take asterisk", + "west", + "north", + "west", + "take coin", + "north", + "east", + "take astronaut ice cream", + "west", + "south", + "east", + "north", + "north", + "take mutex", + "west", + "take astrolabe", + "west", + "take dehydrated water", + "west", + "take wreath", + "east", + "south", + "east", + "north", + "drop astronaut ice cream", + "drop wreath", + "drop coin", + "drop dehydrated water", + "drop asterisk", + "drop astrolabe", + "drop mutex", +]; + +const THINGS: [&'static str; 8] = [ + "astronaut ice cream", + "wreath", + "coin", + "dehydrated water", + "asterisk", + "astrolabe", + "mutex", + "monolith", +]; + +fn combine_commands(cmds: &[&str]) -> String { + let mut res = String::new(); + + for cmd in cmds.iter() { + res.push_str(cmd); + res.push_str("\n"); + } + + res +} + +fn select_things(c: u16) -> String { + let mut res = String::new(); + + for bit in 0..8 { + if (c >> bit) & 0x1 == 1 { + res.push_str("take "); + res.push_str(THINGS[bit]); + res.push_str("\n"); + } + } + + res +} + +fn run_computer(mut comp: Computer, buffer: &mut String) -> (Box Computer>, String) { + let mut outbuf = String::new(); + + loop { + match comp.run() { + RunResult::Continue(comp2) => comp = comp2, + RunResult::Halted(_) => panic!("Machine halted in run_computer: {}", outbuf), + RunResult::Output(c, comp2) => { + outbuf.push(c as u8 as char); + comp = comp2; + } + RunResult::Input(f) => { + if buffer.len() == 0 { + return (f, outbuf); + } + + let c = buffer.remove(0); + comp = f(c as u8 as i64); + } + } + } +} + +fn gather_everything(comp: Computer) -> Computer { + let mut gather_buffer = combine_commands(&GATHER_STEPS); + let (res, outb) = run_computer(comp, &mut gather_buffer); + println!("{}", outb); + res('\n' as u8 as i64) +} + +fn combination_works(comp: Computer, code: u16) -> bool { + let mut get_buffer = select_things(code); + let (next, _) = run_computer(comp, &mut get_buffer); + let mut north = "nv\nnorth\n".to_string(); + let (_after, outbuf) = run_computer(next('i' as u8 as i64), &mut north); + println!("outbuf: {}", outbuf); + !outbuf.contains("heavier") && !outbuf.contains("lighter") +} + +pub fn find_santa(base_computer: Computer) { + let at_checkpoint = gather_everything(base_computer); + + for code in 1..256 { + println!("------------------------------------------"); + println!("Trying code: {}", code); + if combination_works(at_checkpoint.clone(), code) { + println!("Combination {} worked.", code); + break; + } + } +} \ No newline at end of file