Compare commits
2 Commits
6f2e046080
...
ac3a741f0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac3a741f0d | ||
|
|
2bf8edf315 |
7
input/day15.txt
Normal file
7
input/day15.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#######
|
||||||
|
#.G...#
|
||||||
|
#...EG#
|
||||||
|
#.#.#G#
|
||||||
|
#..G#E#
|
||||||
|
#.....#
|
||||||
|
#######
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
// aoc_2018::tasks::day14::task1();
|
aoc_2018::tasks::day15::task1();
|
||||||
aoc_2018::tasks::day14::task2();
|
// aoc_2018::tasks::day15::task2();
|
||||||
}
|
}
|
||||||
|
|||||||
412
src/tasks/day15.rs
Normal file
412
src/tasks/day15.rs
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
use crate::utils;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
const ATTACK_POWER: i32 = 3;
|
||||||
|
const HEALTH: i32 = 200;
|
||||||
|
|
||||||
|
pub fn task1() {
|
||||||
|
let input = utils::read_file("input/day15.txt");
|
||||||
|
let mut game = Game::from_input(input.lines().collect());
|
||||||
|
println!("{}", game);
|
||||||
|
let mut round = 0;
|
||||||
|
while game.round() {
|
||||||
|
round += 1;
|
||||||
|
println!("{}", game);
|
||||||
|
println!("{:?}", game.units);
|
||||||
|
}
|
||||||
|
println!("Final full round was {}", round);
|
||||||
|
println!(
|
||||||
|
"Remaining HP: {}",
|
||||||
|
game.units.iter().map(|it| it.health).sum::<i32>()
|
||||||
|
);
|
||||||
|
println!("{:?}", game.units);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
enum Tile {
|
||||||
|
Empty,
|
||||||
|
Wall,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
enum WarriorType {
|
||||||
|
Elve,
|
||||||
|
Goblin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WarriorType {
|
||||||
|
fn enemy_type(&self) -> Self {
|
||||||
|
use self::WarriorType::*;
|
||||||
|
match self {
|
||||||
|
Elve => Goblin,
|
||||||
|
Goblin => Elve,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
struct Position(usize, usize);
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
fn neighbors(&self, width: usize, height: usize) -> Vec<Position> {
|
||||||
|
vec![
|
||||||
|
(self.0 as isize, self.1 as isize - 1),
|
||||||
|
(self.0 as isize - 1, self.1 as isize),
|
||||||
|
(self.0 as isize + 1, self.1 as isize),
|
||||||
|
(self.0 as isize, self.1 as isize + 1),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| p.0 > 0 && p.0 < width as isize - 1 && p.1 > 0 && p.1 < height as isize - 1)
|
||||||
|
.map(|it| Position(it.0 as usize, it.1 as usize))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Position {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> std::option::Option<std::cmp::Ordering> {
|
||||||
|
if self.1 == other.1 {
|
||||||
|
Some(self.0.cmp(&other.0))
|
||||||
|
} else {
|
||||||
|
Some(self.1.cmp(&other.1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Position {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
if self.1 == other.1 {
|
||||||
|
self.0.cmp(&other.0)
|
||||||
|
} else {
|
||||||
|
self.1.cmp(&other.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Warrior {
|
||||||
|
warrior_type: WarriorType,
|
||||||
|
position: Position,
|
||||||
|
health: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Game {
|
||||||
|
units: Vec<Warrior>,
|
||||||
|
/// [x, y]
|
||||||
|
tiles: Vec<Vec<Tile>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
fn closest_fighting_position(
|
||||||
|
&self,
|
||||||
|
from: Position,
|
||||||
|
target_type: WarriorType,
|
||||||
|
) -> Option<Position> {
|
||||||
|
let mut map = Map::from_game(&self, from);
|
||||||
|
|
||||||
|
for unit in self.units.iter() {
|
||||||
|
if unit.warrior_type == target_type {
|
||||||
|
for neighbor in unit.position.neighbors(self.width, self.height) {
|
||||||
|
if map.fields[neighbor.0][neighbor.1] == MapTile::Empty {
|
||||||
|
map.fields[neighbor.0][neighbor.1] = MapTile::TargetNonOccupied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.find_closest_target(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_position(&self, from: Position, to: Position) -> Option<Position> {
|
||||||
|
if from == to {
|
||||||
|
return Some(from);
|
||||||
|
}
|
||||||
|
let input = vec![
|
||||||
|
(1, Position(from.0, from.1 - 1)),
|
||||||
|
(2, Position(from.0 - 1, from.1)),
|
||||||
|
(3, Position(from.0 + 1, from.1)),
|
||||||
|
(4, Position(from.0, from.1 + 1)),
|
||||||
|
];
|
||||||
|
if let Some((_, best)) = input
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(delta, start)| {
|
||||||
|
let map = Map::from_game(&self, *start);
|
||||||
|
if let Some(path) = map.shortest_path(*start, to) {
|
||||||
|
Some((delta + path.len() * 10, *path.first().unwrap_or(start)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.min_by_key(|(d, _)| *d)
|
||||||
|
{
|
||||||
|
Some(best)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a full round was played, false if the round aborted because all
|
||||||
|
/// enemies of one party are dead
|
||||||
|
fn round(&mut self) -> bool {
|
||||||
|
self.units.sort_unstable_by_key(|it| it.position);
|
||||||
|
|
||||||
|
let mut curr = 0;
|
||||||
|
while curr < self.units.len() {
|
||||||
|
if !self
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.any(|warrior| warrior.warrior_type == self.units[curr].warrior_type.enemy_type())
|
||||||
|
{
|
||||||
|
println!("There are no enemies anymore!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// movement phase
|
||||||
|
if let Some(next_target_position) = self.closest_fighting_position(
|
||||||
|
self.units[curr].position,
|
||||||
|
self.units[curr].warrior_type.enemy_type(),
|
||||||
|
) {
|
||||||
|
if let Some(next_position) =
|
||||||
|
self.next_position(self.units[curr].position, next_target_position)
|
||||||
|
{
|
||||||
|
let curr_pos = self.units[curr].position;
|
||||||
|
self.tiles[curr_pos.0][curr_pos.1] = Tile::Empty;
|
||||||
|
self.units[curr].position = next_position;
|
||||||
|
} else {
|
||||||
|
panic!("We have a reachable target but no path to it!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attack phase
|
||||||
|
let neighbors = self.units[curr].position.neighbors(self.width, self.height);
|
||||||
|
let mut close_enemies: Vec<usize> = self
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, it)| neighbors.contains(&it.position))
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect();
|
||||||
|
close_enemies.sort_unstable_by(|a, b| {
|
||||||
|
let a = &self.units[*a];
|
||||||
|
let b = &self.units[*b];
|
||||||
|
if a.health == b.health {
|
||||||
|
a.position.cmp(&b.position)
|
||||||
|
} else {
|
||||||
|
a.health.cmp(&b.health)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(closest_index) = close_enemies.first() {
|
||||||
|
let enemy = &mut self.units[*closest_index];
|
||||||
|
enemy.health -= ATTACK_POWER;
|
||||||
|
if enemy.health <= 0 {
|
||||||
|
let enemy = self.units.remove(*closest_index);
|
||||||
|
if *closest_index < curr {
|
||||||
|
curr -= 1;
|
||||||
|
}
|
||||||
|
self.tiles[enemy.position.0][enemy.position.1] = Tile::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curr += 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_input(input: Vec<&str>) -> Self {
|
||||||
|
use self::Tile::*;
|
||||||
|
use self::WarriorType::*;
|
||||||
|
let width = input[0].len();
|
||||||
|
let height = input.len();
|
||||||
|
let mut inner_vec = Vec::new();
|
||||||
|
inner_vec.resize(height, Empty);
|
||||||
|
let mut tiles = Vec::new();
|
||||||
|
tiles.resize(width, inner_vec);
|
||||||
|
|
||||||
|
let mut units = Vec::new();
|
||||||
|
for (y, line) in input.iter().enumerate() {
|
||||||
|
for (x, c) in line.chars().enumerate() {
|
||||||
|
tiles[x][y] = match c {
|
||||||
|
'.' => Empty,
|
||||||
|
'#' => Wall,
|
||||||
|
'E' => {
|
||||||
|
units.push(Warrior {
|
||||||
|
warrior_type: Elve,
|
||||||
|
position: Position(x, y),
|
||||||
|
health: HEALTH,
|
||||||
|
});
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
'G' => {
|
||||||
|
units.push(Warrior {
|
||||||
|
warrior_type: Goblin,
|
||||||
|
position: Position(x, y),
|
||||||
|
health: HEALTH,
|
||||||
|
});
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
c => panic!("Unexpected input character '{}'", c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Game {
|
||||||
|
units,
|
||||||
|
tiles,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Game {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
use self::Tile::*;
|
||||||
|
for y in 0..self.height {
|
||||||
|
let mut line = String::with_capacity(self.width);
|
||||||
|
for x in 0..self.width {
|
||||||
|
if let Some(warrior) = self.units.iter().find(|it| it.position == Position(x, y)) {
|
||||||
|
line.push(match warrior.warrior_type {
|
||||||
|
WarriorType::Elve => 'E',
|
||||||
|
WarriorType::Goblin => 'G',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
line.push(match &self.tiles[x][y] {
|
||||||
|
Empty => '.',
|
||||||
|
Wall => '#',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_str(&line)?;
|
||||||
|
f.write_str(&"\n")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Map {
|
||||||
|
fields: Vec<Vec<MapTile>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum MapTile {
|
||||||
|
Empty,
|
||||||
|
TargetNonOccupied,
|
||||||
|
Occupied,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Map {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
use self::MapTile::*;
|
||||||
|
for y in 0..self.height {
|
||||||
|
let mut line = String::with_capacity(self.width);
|
||||||
|
for x in 0..self.width {
|
||||||
|
line.push(match &self.fields[x][y] {
|
||||||
|
Empty => '.',
|
||||||
|
Occupied => 'X',
|
||||||
|
TargetNonOccupied => 'o',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
f.write_str(&line)?;
|
||||||
|
f.write_str(&"\n")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Map {
|
||||||
|
fn from_game(game: &Game, clear: Position) -> Self {
|
||||||
|
let mut fields = Vec::with_capacity(game.width);
|
||||||
|
for x in 0..game.width {
|
||||||
|
let mut new = Vec::with_capacity(game.height);
|
||||||
|
for y in 0..game.height {
|
||||||
|
new.push(match &game.tiles[x][y] {
|
||||||
|
Tile::Empty => MapTile::Empty,
|
||||||
|
_ => MapTile::Occupied,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fields.push(new);
|
||||||
|
}
|
||||||
|
fields[clear.0][clear.1] = MapTile::Empty;
|
||||||
|
Map {
|
||||||
|
fields: fields,
|
||||||
|
width: game.width,
|
||||||
|
height: game.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shortest_path(&self, from: Position, to: Position) -> Option<Vec<Position>> {
|
||||||
|
if to == from {
|
||||||
|
return Some(vec![]);
|
||||||
|
}
|
||||||
|
if self.fields[from.0][from.1] != MapTile::Empty {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut open: VecDeque<(Option<Position>, Position)> = VecDeque::new();
|
||||||
|
open.push_back((None, from));
|
||||||
|
let mut predecessors: HashMap<Position, Option<Position>> = HashMap::new();
|
||||||
|
|
||||||
|
while let Some((predecessor, curr_pos)) = open.pop_front() {
|
||||||
|
predecessors.insert(curr_pos, predecessor);
|
||||||
|
if curr_pos == to {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for pos in curr_pos.neighbors(self.width, self.height) {
|
||||||
|
if !predecessors.contains_key(&pos) && !open.iter().any(|it| it.1 == pos) {
|
||||||
|
open.push_back((Some(curr_pos), pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Some(_)) = predecessors.get(&to) {
|
||||||
|
let mut result: Vec<Position> = Vec::new();
|
||||||
|
let mut current = to;
|
||||||
|
while let Some(Some(predecessor)) = predecessors.get(¤t) {
|
||||||
|
result.push(*predecessor);
|
||||||
|
current = *predecessor;
|
||||||
|
}
|
||||||
|
result.reverse();
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_closest_target(&self, from: Position) -> Option<Position> {
|
||||||
|
let mut open: VecDeque<(usize, Position)> = VecDeque::new();
|
||||||
|
open.push_back((0, from));
|
||||||
|
let mut visited: HashSet<Position> = HashSet::new();
|
||||||
|
let mut current_found_distance = usize::max_value();
|
||||||
|
let mut candidates: Vec<Position> = Vec::new();
|
||||||
|
|
||||||
|
while let Some((curr_dist, curr_pos)) = open.pop_front() {
|
||||||
|
if curr_dist > current_found_distance {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if self.fields[curr_pos.0][curr_pos.1] == MapTile::TargetNonOccupied {
|
||||||
|
candidates.push(curr_pos);
|
||||||
|
current_found_distance = curr_dist;
|
||||||
|
// all others would have a higher distance and therefore are not relevant
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for pos in curr_pos.neighbors(self.width, self.height) {
|
||||||
|
if !visited.contains(&pos) && !open.iter().any(|it| it.1 == pos) {
|
||||||
|
open.push_back((curr_dist + 1, pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visited.insert(curr_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.sort_unstable();
|
||||||
|
if let Some(x) = candidates.first() {
|
||||||
|
Some(*x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,3 +12,4 @@ pub mod day11;
|
|||||||
pub mod day12;
|
pub mod day12;
|
||||||
pub mod day13;
|
pub mod day13;
|
||||||
pub mod day14;
|
pub mod day14;
|
||||||
|
pub mod day15;
|
||||||
|
|||||||
Reference in New Issue
Block a user