Compare commits

..

2 Commits

Author SHA1 Message Date
Johannes Schaefer
ac3a741f0d day15 part1 combat with errors 2018-12-18 17:46:44 +01:00
Johannes Schaefer
2bf8edf315 day15 part1 movement 2018-12-18 16:59:41 +01:00
4 changed files with 422 additions and 2 deletions

7
input/day15.txt Normal file
View File

@@ -0,0 +1,7 @@
#######
#.G...#
#...EG#
#.#.#G#
#..G#E#
#.....#
#######

View File

@@ -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
View 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(&current) {
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
}
}
}

View File

@@ -12,3 +12,4 @@ pub mod day11;
pub mod day12;
pub mod day13;
pub mod day14;
pub mod day15;