The first part of all the problems. (Thanks, airplanes!)
This commit is contained in:
19
inputs/day20_example1
Normal file
19
inputs/day20_example1
Normal file
@@ -0,0 +1,19 @@
|
||||
A
|
||||
A
|
||||
#######.#########
|
||||
#######.........#
|
||||
#######.#######.#
|
||||
#######.#######.#
|
||||
#######.#######.#
|
||||
##### B ###.#
|
||||
BC...## C ###.#
|
||||
##.## ###.#
|
||||
##...DE F ###.#
|
||||
##### G ###.#
|
||||
#########.#####.#
|
||||
DE..#######...###.#
|
||||
#.#########.###.#
|
||||
FG..#########.....#
|
||||
###########.#####
|
||||
Z
|
||||
Z
|
||||
37
inputs/day20_example2
Normal file
37
inputs/day20_example2
Normal file
@@ -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
|
||||
15
src/args.rs
15
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.");
|
||||
}
|
||||
|
||||
120
src/bugs.rs
Normal file
120
src/bugs.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::fs;
|
||||
use std::str;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct BugMap {
|
||||
has_bug: Vec<bool>,
|
||||
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());
|
||||
}
|
||||
175
src/cards.rs
Normal file
175
src/cards.rs
Normal file
@@ -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<u32>) -> Vec<u32> {
|
||||
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<Shuffle>
|
||||
}
|
||||
|
||||
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<u32> {
|
||||
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<u32> = (0..10007).collect();
|
||||
let result = shuffles.shuffle(&deck);
|
||||
assert_eq!(2939, find_card(&result, 2019));
|
||||
}
|
||||
274
src/donut.rs
Normal file
274
src/donut.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::str;
|
||||
|
||||
struct Maze<TileType> {
|
||||
data: Vec<TileType>,
|
||||
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<T: Clone> Maze<T> {
|
||||
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<InputTile> {
|
||||
fn new(f: &str) -> Maze<InputTile> {
|
||||
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<Tile> {
|
||||
fn new(f: &str) -> Maze<Tile> {
|
||||
let inputmap: Maze<InputTile> = Maze::<InputTile>::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::<Tile>::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::<Tile>::new("inputs/day20_example2");
|
||||
let path = maze.find_path();
|
||||
assert_eq!(58, path.len() - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn day20() {
|
||||
let maze = Maze::<Tile>::new("inputs/day20");
|
||||
let path = maze.find_path();
|
||||
assert_eq!(606, path.len() - 1);
|
||||
}
|
||||
16
src/main.rs
16
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
240
src/maze.rs
Normal file
240
src/maze.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::fs;
|
||||
use std::str;
|
||||
|
||||
struct Maze {
|
||||
data: Vec<Tile>,
|
||||
keyset: HashSet<char>,
|
||||
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<char>,
|
||||
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<HashSet<char>>>) -> 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);
|
||||
}
|
||||
126
src/santafind.rs
Normal file
126
src/santafind.rs
Normal file
@@ -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<dyn FnOnce(i64) -> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user