2018 Day 15 ugly solution (external dep)
This commit is contained in:
parent
4c583274ab
commit
c4ae38569b
|
@ -0,0 +1,32 @@
|
|||
################################
|
||||
###################........#####
|
||||
###################..G..#G..####
|
||||
####################........####
|
||||
##..G###############G......#####
|
||||
###..G###############.....######
|
||||
#####.######..######....G##..###
|
||||
#####.........####............##
|
||||
#########...#####.............##
|
||||
#########...####..............##
|
||||
#########E#####.......GE......##
|
||||
#########............E...G...###
|
||||
######.###....#####..G........##
|
||||
#.G#....##...#######.........###
|
||||
##.#....##GG#########.........##
|
||||
#....G#....E#########....#....##
|
||||
#...........#########.......####
|
||||
#####..G....#########...##....##
|
||||
#####....G..#########.#.......##
|
||||
#######...G..#######G.....#...##
|
||||
######....E...#####............#
|
||||
######...GG.......E......#...E.#
|
||||
#######.G...#....#..#...#.....##
|
||||
#######..........#####..####.###
|
||||
########.......E################
|
||||
#######..........###############
|
||||
########.............###########
|
||||
#########...#...##....##########
|
||||
#########.....#.#..E..##########
|
||||
################.....###########
|
||||
################.##E.###########
|
||||
################################
|
|
@ -0,0 +1,219 @@
|
|||
with open('day15-input-dev', 'r') as file:
|
||||
data = [l.strip('\n') for l in file]
|
||||
# data = [
|
||||
# '#######',
|
||||
# '#.G...#',
|
||||
# '#...EG#',
|
||||
# '#.#.#G#',
|
||||
# '#..G#E#',
|
||||
# '#.....#',
|
||||
# '#######',
|
||||
# ]
|
||||
import numpy as np
|
||||
from pathfinding.core.grid import Grid
|
||||
from pathfinding.finder.dijkstra import DijkstraFinder
|
||||
import sys
|
||||
|
||||
grid = np.zeros((len(data), len(data[0])), dtype=np.int64)
|
||||
cells = {'#': 0, '.': 1, 'G': 1, 'E': 1}
|
||||
goblins = []
|
||||
elves = []
|
||||
starting_hp = 200
|
||||
starting_atk = 3
|
||||
starting_atk_elf = 29
|
||||
adjacent = np.array([[0,-1], [-1,0], [1,0], [0,1]], dtype=np.int64)
|
||||
enemy_teams = [goblins, elves]
|
||||
unit_positions = []
|
||||
pathfind_grid = None
|
||||
# pathfinder = AStarFinder()
|
||||
pathfinder = DijkstraFinder()
|
||||
UID = 0
|
||||
|
||||
class Unit:
|
||||
def __init__(self, x, y, starting_hp, atk, team):
|
||||
global UID
|
||||
self.team = team
|
||||
self.atk = atk
|
||||
self.hp = starting_hp
|
||||
self.pos = np.array([x, y], dtype=np.int64)
|
||||
self.uid = UID
|
||||
UID += 1
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.uid == other.uid
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.pos[0]
|
||||
@property
|
||||
def y(self):
|
||||
return self.pos[1]
|
||||
|
||||
def distance(self, pos):
|
||||
return np.abs(pos - self.pos).sum()
|
||||
|
||||
def path_to(self, pos, exhaustive=False):
|
||||
if not exhaustive:
|
||||
pathfind_grid = Grid(matrix=grid2)
|
||||
start = pathfind_grid.node(*tuple(self.pos))
|
||||
end = pathfind_grid.node(*tuple(pos))
|
||||
path, runs = pathfinder.find_path(start, end, pathfind_grid)
|
||||
return path
|
||||
else:
|
||||
paths = []
|
||||
for d in range(len(adjacent)):
|
||||
try:
|
||||
trystart = tuple(self.pos + adjacent[d])
|
||||
if grid2[trystart] and trystart not in unit_positions:
|
||||
pathfind_grid = Grid(matrix=grid2.T)
|
||||
start = pathfind_grid.node(*trystart)
|
||||
end = pathfind_grid.node(*tuple(pos))
|
||||
path, runs = pathfinder.find_path(start, end, pathfind_grid)
|
||||
# print(pathfind_grid.grid_str(path=path, start=start, end=end))
|
||||
if len(path) > 0:
|
||||
paths.append(path)
|
||||
except Exception:
|
||||
pass
|
||||
if paths:
|
||||
paths = sorted(paths, key=lambda x: len(x))
|
||||
# print(paths)
|
||||
return paths[0]
|
||||
return []
|
||||
|
||||
def path_distance(self, pos):
|
||||
return len(self.path_to(pos, True)) or 90000 # Sorting hack
|
||||
|
||||
def path_distance_and_priority(self, pos):
|
||||
path = self.path_to(pos, True)
|
||||
if path:
|
||||
for i in range(len(adjacent)):
|
||||
if path[0] == tuple(self.pos + adjacent[i]):
|
||||
priority = i
|
||||
break
|
||||
return len(path), priority
|
||||
return (90000, 90000) # Sorting hack
|
||||
|
||||
def __repr__(self):
|
||||
return f'[{"Elf" if not self.team else "Gob"}, Position {self.x},{self.y}, HP {self.hp}]'
|
||||
|
||||
|
||||
for y, row in enumerate(data):
|
||||
for x, c in enumerate(row):
|
||||
grid[x,y] = cells[c]
|
||||
if c == 'E':
|
||||
elves.append(Unit(x, y, starting_hp, starting_atk_elf, 0))
|
||||
elif c == 'G':
|
||||
goblins.append(Unit(x, y, starting_hp, starting_atk, 1))
|
||||
grid2 = grid.copy()
|
||||
|
||||
def turn_sort(unit):
|
||||
return unit.y, unit.x
|
||||
|
||||
|
||||
def target_sort(unit):
|
||||
return unit.hp, unit.y, unit.x
|
||||
|
||||
|
||||
def do_round():
|
||||
global unit_positions, pathfind_grid
|
||||
combatants = sorted(goblins + elves, key=turn_sort)
|
||||
i = 0
|
||||
while i < len(combatants):
|
||||
unit_positions = [tuple(u.pos) for u in combatants]
|
||||
grid2[:] = grid[:]
|
||||
for p in unit_positions:
|
||||
grid2[p] = -1
|
||||
unit = combatants[i]
|
||||
i += 1
|
||||
enemy_team = enemy_teams[unit.team]
|
||||
|
||||
# If no enemies left, end combat
|
||||
if len(enemy_team) == 0:
|
||||
return
|
||||
|
||||
# If adjacent to an enemy, attack it
|
||||
adjacent_enemies = []
|
||||
for enemy in enemy_team:
|
||||
if unit.distance(enemy.pos) == 1:
|
||||
adjacent_enemies.append(enemy)
|
||||
if adjacent_enemies:
|
||||
# print(sorted(adjacent_enemies, key=target_sort))
|
||||
target = sorted(adjacent_enemies, key=target_sort)[0]
|
||||
# print(f'Unit #{unit.uid} attacking unit {target.uid}')
|
||||
target.hp -= unit.atk
|
||||
if target.hp <= 0:
|
||||
if target.team == 0:
|
||||
print('Elf died, aborting!')
|
||||
sys.exit(1)
|
||||
enemy_team.remove(target)
|
||||
j = combatants.index(target)
|
||||
combatants.pop(j)
|
||||
if j < i:
|
||||
i -= 1
|
||||
continue
|
||||
|
||||
# Find all cells to move to
|
||||
target_cells = {tuple(t.pos + adjacent[j]) for t in enemy_team for j in range(4)}
|
||||
target_cells = {c for c in target_cells if grid[c] == 1 and c not in unit_positions}
|
||||
target_cells = sorted([np.array(c) for c in target_cells], key=lambda x: unit.path_distance_and_priority(x))
|
||||
# Move to closest target cell
|
||||
if target_cells:
|
||||
path = unit.path_to(target_cells[0], exhaustive=True)
|
||||
if path:
|
||||
# print(f'Unit #{unit.uid} moving from {unit.pos} to {path[0]}')
|
||||
unit.pos[:] = path[0]
|
||||
else:
|
||||
pass
|
||||
# print(f'Unit #{unit.uid} cannot move!')
|
||||
|
||||
# If adjacent to an enemy, attack it
|
||||
adjacent_enemies = []
|
||||
for enemy in enemy_team:
|
||||
if unit.distance(enemy.pos) == 1:
|
||||
adjacent_enemies.append(enemy)
|
||||
if adjacent_enemies:
|
||||
target = sorted(adjacent_enemies, key=target_sort)[0]
|
||||
# print(f'Unit #{unit.uid} attacking unit {target.uid}')
|
||||
target.hp -= unit.atk
|
||||
if target.hp <= 0:
|
||||
enemy_team.remove(target)
|
||||
j = combatants.index(target)
|
||||
combatants.pop(j)
|
||||
if j < i:
|
||||
i -= 1
|
||||
continue
|
||||
|
||||
def print_state():
|
||||
char_array = np.zeros_like(grid)
|
||||
char_array[(grid == 0)] = ord('#')
|
||||
char_array[(grid == 1)] = ord('.')
|
||||
unit_strs = [[] for i in range(len(char_array))]
|
||||
for g in goblins:
|
||||
char_array[g.x, g.y] = ord('G')
|
||||
unit_strs[g.y].append((f'G({g.hp})', g.x))
|
||||
for e in elves:
|
||||
char_array[e.x, e.y] = ord('E')
|
||||
unit_strs[e.y].append((f'E({e.hp})', e.x))
|
||||
for row in range(len(char_array)):
|
||||
maprow =''.join([chr(c) for c in char_array.T[row]])
|
||||
unithps = ', '.join([s[0] for s in sorted(unit_strs[row], key=lambda x: x[1])])
|
||||
print(maprow, unithps)
|
||||
|
||||
|
||||
|
||||
def do_exterminatus(quit_after=9999999):
|
||||
rounds = 0
|
||||
while len(goblins) and len(elves) and rounds < quit_after:
|
||||
print(f'Round #{rounds}')
|
||||
print_state()
|
||||
do_round()
|
||||
rounds += 1
|
||||
return rounds
|
||||
|
||||
|
||||
round = do_exterminatus()
|
||||
if goblins:
|
||||
num = sum(u.hp for u in goblins)
|
||||
else:
|
||||
num = sum(u.hp for u in elves)
|
||||
print(round, num, round*num)
|
Loading…
Reference in New Issue