From 069e381aebea0674b75a8e477d90f1adda566d63 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sat, 15 Dec 2018 18:02:16 +0100 Subject: [PATCH] Partial solution for day 15 part 1. Partial in the sense that it doesn't actually work for my input, only for the samples. --- 2018/inputs/15.txt | 32 +++++ 2018/src/common.rs | 11 ++ 2018/src/day15.rs | 246 +++++++++++++++++++++++++++++++++++++- 2018/src/samples/15.1.txt | 7 ++ 2018/src/samples/15.2.txt | 7 ++ 2018/src/samples/15.3.txt | 7 ++ 2018/src/samples/15.4.txt | 7 ++ 2018/src/samples/15.5.txt | 7 ++ 2018/src/samples/15.6.txt | 9 ++ 9 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 2018/inputs/15.txt create mode 100644 2018/src/samples/15.1.txt create mode 100644 2018/src/samples/15.2.txt create mode 100644 2018/src/samples/15.3.txt create mode 100644 2018/src/samples/15.4.txt create mode 100644 2018/src/samples/15.5.txt create mode 100644 2018/src/samples/15.6.txt diff --git a/2018/inputs/15.txt b/2018/inputs/15.txt new file mode 100644 index 0000000..0d591c9 --- /dev/null +++ b/2018/inputs/15.txt @@ -0,0 +1,32 @@ +################################ +################.#.#..########## +################.#...G########## +################...############# +######..##########.#..########## +####.G...#########.G...######### +###.........######....########## +##..#.##.....#....#....######### +#G.#GG..................##.##### +##.##..##..G........G.........## +#######......G.G...............# +#######........................# +########.G....#####..E#...E.G..# +#########G...#######...........# +#########...#########.........## +#####.......#########....G...### +###.........#########.....E..### +#...........#########.........## +#..#....G..G#########........### +#..#.........#######.........### +#G.##G......E.#####...E..E..#### +##......E...............######## +#.....#G.G..............E..##### +#....#####....E........###.##### +#...#########.........####.##### +#.###########......#.#####.##### +#....##########.##...########### +#....#############....########## +##.##############E....########## +##.##############..############# +##....########################## +################################ diff --git a/2018/src/common.rs b/2018/src/common.rs index 2892e8e..29a4a38 100644 --- a/2018/src/common.rs +++ b/2018/src/common.rs @@ -3,6 +3,8 @@ use std::hash::Hash; use std::io; use std::io::Read; use std::str::FromStr; +use std::ops::Add; +use std::ops::Sub; /// Apply Erathostenes's sieve to the supplied array /// @@ -65,6 +67,15 @@ pub fn read_single_input(input: &mut Read) -> T buf.trim().parse().unwrap() } +/// Compute the manhattan distance between two points of arbitrary type. +pub fn manhattan_distance(a: (T, T), b: (T, T)) -> T +where T: Copy + Add + Sub + Ord +{ + let (xa, ya) = a; + let (xb, yb) = b; + xa.max(xb) + ya.max(yb) - xa.min(xb) - ya.min(yb) +} + /// An interface to count elements in particular categories. pub trait GroupingCount { diff --git a/2018/src/day15.rs b/2018/src/day15.rs index eb71b83..4429d30 100644 --- a/2018/src/day15.rs +++ b/2018/src/day15.rs @@ -1,19 +1,228 @@ +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; +use std::io::BufRead; +use std::io::BufReader; use std::io::Read; +use common::manhattan_distance; use common::Solution; +type Coordinate = (usize, usize); + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +struct Unit { + pos: Coordinate, + hp: u8, + power: u8, + faction: char, +} + +impl Default for Unit { + fn default() -> Self { + Unit { + pos: (0, 0), + hp: 200, + power: 3, + faction: 'E', + } + } +} + +impl Unit { + pub fn is_alive(&self) -> bool { + self.hp > 0 + } +} + #[derive(Default)] -pub struct Day15 {} +pub struct Day15 { + walls: Vec>, + units: Vec, + alive: [usize; 2], +} impl Day15 { pub fn new() -> Self { Default::default() } + + fn read_input(&mut self, input: &mut Read) { + let reader = BufReader::new(input); + + for (y, line) in reader.lines().enumerate() { + let line = line.unwrap(); + let mut current = vec![false; line.len()]; + + for (x, c) in line.chars().enumerate() { + match c { + '#' => { current[x] = true; } + 'G' => { + self.units.push(Unit { + pos: (y, x), + faction: 'G', + ..Default::default() + }); + self.alive[1] += 1; + } + 'E' => { + self.units.push(Unit { + pos: (y, x), + ..Default::default() + }); + self.alive[0] += 1; + } + '.' => {} + c => panic!("Invalid tile {}!", c), + } + } + self.walls.push(current); + } + } + + fn get_movement(&self, unit: usize) -> Option { + let initial = self.units[unit].pos; + let faction = self.units[unit].faction; + + let positions: HashSet = self.units.iter() + .filter(|x| x.faction != faction && x.is_alive()) + .map(|x| x.pos).collect(); + + let all_positions: HashSet = self.units.iter() + .filter(|x| x.is_alive()) + .map(|x| x.pos).collect(); + + let mut todo = VecDeque::new(); + let mut prev = HashMap::new(); + + todo.push_back(initial); + + while let Some((y, x)) = todo.pop_front() { + let next = [ + (y - 1, x), + (y, x - 1), + (y, x + 1), + (y + 1, x), + ]; + + for pos in &next { + if !prev.contains_key(pos) && !self.walls[pos.0][pos.1] { + prev.insert(*pos, (y, x)); + if positions.contains(pos) { + if (y, x) == initial { + return None; + } + + let mut next_pos = *pos; + // Found the nearest enemy + while let Some(prev_pos) = prev.get(&next_pos) { + if *prev_pos != initial { + next_pos = *prev_pos; + } else { + break; + } + } + + return Some(next_pos); + } else if !all_positions.contains(pos) { + todo.push_back(*pos); + } + } + } + } + None + } + + fn get_attack(&self, unit: usize) -> Option { + let initial = self.units[unit].pos; + let faction = self.units[unit].faction; + + let to_attack = self.units.iter() + .enumerate() + .filter(|(_, x)| x.faction != faction && x.is_alive()) + .filter(|(_, x)| manhattan_distance(x.pos, initial) == 1) + .min_by(|&(_, a), &(_, b)| a.hp.cmp(&b.hp).then(a.pos.cmp(&b.pos))); + + if let Some((index, _)) = to_attack { + Some(index) + } else { + None + } + } + + fn simulate(&mut self) -> bool { + self.units.sort_unstable(); + for i in 0..self.units.len() { + if !self.units[i].is_alive() { + continue; + } + + if self.alive[0] == 0 || self.alive[1] == 0 { + return false; + } + + if let Some(new_pos) = self.get_movement(i) { + self.units[i].pos = new_pos; + } + + if let Some(target) = self.get_attack(i) { + let power = self.units[i].power; + let target = &mut self.units[target]; + target.hp = target.hp.saturating_sub(power); + + if target.hp == 0 { + match target.faction { + 'E' => { self.alive[0] -= 1 } + 'G' => { self.alive[1] -= 1 }, + _ => panic!(), + }; + } + } + } + + true + } + + #[allow(dead_code)] + fn print(&self) { + let positions: HashMap<_, _> = self.units.iter() + .filter(|x| x.is_alive()) + .map(|x| (x.pos, x)) + .collect(); + + for y in 0..self.walls.len() { + let mut buf = String::new(); + let mut unit_buf = String::new(); + + for x in 0..self.walls[y].len() { + if let Some(unit) = positions.get(&(y, x)) { + buf.push(unit.faction); + + unit_buf += &format!(" {}({})", unit.faction, unit.hp); + } else if self.walls[y][x] { + buf.push('#'); + } else { + buf.push('.'); + } + } + println!("{}{}", buf, unit_buf); + } + } } impl Solution for Day15 { - fn part1(&mut self, _input: &mut Read) -> String { - unimplemented!() + fn part1(&mut self, input: &mut Read) -> String { + self.read_input(input); + let mut rounds = 0; + while self.simulate() { + rounds += 1; + //println!("Result after round {}: ", rounds); + //self.print(); + } + let result: usize = rounds * self.units.iter().map(|x| x.hp as usize) + .sum::(); + + format!("{}", result) } fn part2(&mut self, _input: &mut Read) -> String { @@ -22,4 +231,33 @@ impl Solution for Day15 { } #[cfg(test)] -mod tests {} +mod tests { + use common::Solution; + use day15::Day15; + + const SAMPLE_INPUT: [&[u8]; 6] = [ + include_bytes!("samples/15.1.txt"), + include_bytes!("samples/15.2.txt"), + include_bytes!("samples/15.3.txt"), + include_bytes!("samples/15.4.txt"), + include_bytes!("samples/15.5.txt"), + include_bytes!("samples/15.6.txt"), + ]; + + const SAMPLE_OUTPUT: [&str; 6] = [ + "27730", + "36334", + "39514", + "27755", + "28944", + "18740", + ]; + + #[test] + fn sample_part1() { + for (input, output) in SAMPLE_INPUT.iter().zip(SAMPLE_OUTPUT.iter()) { + let mut instance = Day15::new(); + assert_eq!(*output, instance.part1(&mut input.as_ref())); + } + } +} diff --git a/2018/src/samples/15.1.txt b/2018/src/samples/15.1.txt new file mode 100644 index 0000000..291d351 --- /dev/null +++ b/2018/src/samples/15.1.txt @@ -0,0 +1,7 @@ +####### +#.G...# +#...EG# +#.#.#G# +#..G#E# +#.....# +####### diff --git a/2018/src/samples/15.2.txt b/2018/src/samples/15.2.txt new file mode 100644 index 0000000..ac399d6 --- /dev/null +++ b/2018/src/samples/15.2.txt @@ -0,0 +1,7 @@ +####### +#G..#E# +#E#E.E# +#G.##.# +#...#E# +#...E.# +####### diff --git a/2018/src/samples/15.3.txt b/2018/src/samples/15.3.txt new file mode 100644 index 0000000..58f778d --- /dev/null +++ b/2018/src/samples/15.3.txt @@ -0,0 +1,7 @@ +####### +#E..EG# +#.#G.E# +#E.##E# +#G..#.# +#..E#.# +####### diff --git a/2018/src/samples/15.4.txt b/2018/src/samples/15.4.txt new file mode 100644 index 0000000..6dc1c08 --- /dev/null +++ b/2018/src/samples/15.4.txt @@ -0,0 +1,7 @@ +####### +#E.G#.# +#.#G..# +#G.#.G# +#G..#.# +#...E.# +####### diff --git a/2018/src/samples/15.5.txt b/2018/src/samples/15.5.txt new file mode 100644 index 0000000..2343d7b --- /dev/null +++ b/2018/src/samples/15.5.txt @@ -0,0 +1,7 @@ +####### +#.E...# +#.#..G# +#.###.# +#E#G#G# +#...#G# +####### diff --git a/2018/src/samples/15.6.txt b/2018/src/samples/15.6.txt new file mode 100644 index 0000000..95882b2 --- /dev/null +++ b/2018/src/samples/15.6.txt @@ -0,0 +1,9 @@ +######### +#G......# +#.E.#...# +#..##..G# +#...##..# +#...#...# +#.G...G.# +#.....G.# +#########