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() {
|
||||
// aoc_2018::tasks::day14::task1();
|
||||
aoc_2018::tasks::day14::task2();
|
||||
aoc_2018::tasks::day15::task1();
|
||||
// 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 day13;
|
||||
pub mod day14;
|
||||
pub mod day15;
|
||||
|
||||
Reference in New Issue
Block a user