day15 part1 movement

This commit is contained in:
Johannes Schaefer
2018-12-18 16:59:41 +01:00
parent 6f2e046080
commit 2bf8edf315
4 changed files with 382 additions and 2 deletions

9
input/day15.txt Normal file
View File

@@ -0,0 +1,9 @@
#########
#G..G..G#
#.......#
#.......#
#G..E..G#
#.......#
#.......#
#G..G..G#
#########

View File

@@ -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();
} }

370
src/tasks/day15.rs Normal file
View File

@@ -0,0 +1,370 @@
use crate::utils;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt::Display;
pub fn task1() {
let input = utils::read_file("input/day15.txt");
let mut game = Game::from_input(input.lines().collect());
println!("{}", game);
game.round();
println!("{}", game);
game.round();
println!("{}", game);
game.round();
println!("{}", game);
game.round();
println!("{}", game);
}
#[derive(Clone, PartialEq)]
enum Tile {
Empty,
Wall,
Unit(WarriorType),
}
#[derive(Clone, Copy, PartialEq)]
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)
}
}
}
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) {
//println!("Path from {:?}: {:?}", start, path);
Some((delta + path.len() * 10, *path.first().unwrap_or(start)))
} else {
None
}
})
.min_by_key(|(d, _)| *d)
{
Some(best)
} else {
None
}
}
fn round(&mut self) {
self.units.sort_unstable_by_key(|it| it.position);
for curr in 0..self.units.len() {
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)
{
// println!(
// "{:?} -> {:?} ({:?})",
// self.units[curr].position, next_target_position, next_position
// );
let curr_pos = self.units[curr].position;
self.tiles[curr_pos.0][curr_pos.1] = Tile::Empty;
self.units[curr].position = next_position;
self.tiles[next_position.0][next_position.1] =
Tile::Unit(self.units[curr].warrior_type);
} else {
panic!("We have a reachable target but no path to it!");
}
}
}
}
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: 300,
});
Unit(Elve)
}
'G' => {
units.push(Warrior {
warrior_type: Goblin,
position: Position(x, y),
health: 300,
});
Unit(Goblin)
}
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 => '#',
Unit(WarriorType::Elve) => 'E',
Unit(WarriorType::Goblin) => 'G',
});
}
}
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> {
// println!("{}", self);
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 day12;
pub mod day13; pub mod day13;
pub mod day14; pub mod day14;
pub mod day15;