solve.py (5659B)
1import sys 2sys.path.append("../common") 3import aoc 4 5data = aoc.data.split("\n") 6actors = list() 7 8def parse_entity(x, y): 9 global actors 10 c = data[y][x] 11 if c == "#": 12 return 1 13 else: 14 if c == "G": 15 actors.append([x, y, 200, 1, len(actors), 3]) 16 elif c == "E": 17 actors.append([x, y, 200, 0, len(actors), 3]) 18 return 0 19 20ylen = len(data) 21xlen = len(data[0]) 22 23adjacent = [[0, -1], [-1, 0], [1, 0], [0, 1]] # first in reading order 24vmap = list() 25 26def set_game(): 27 global vmap, actors 28 actors = list() 29 vmap = [[parse_entity(x, y) for x in range(xlen)] for y in range(ylen)] 30 31 32def inmap(cmap, cx, cy): 33 for i in range(len(cmap)): 34 ent = cmap[i] 35 if ent[0] == cx and ent[1] == cy: 36 return i 37 return None 38 39def iswall(x,y): 40 return vmap[y][x] != 0 41 42def isblocked(x, y): 43 return (vmap[y][x] != 0 or inmap(actors, x, y) != None) 44 45def freeAdjacent(x, y): 46 poslist = list() 47 for dir in adjacent: 48 nx = x + dir[0] 49 ny = y + dir[1] 50 if not isblocked(nx, ny): 51 poslist.append((nx,ny)) 52 return poslist 53 54def flatten(l): 55 flat = list() 56 for ll in l: 57 flat += ll 58 return flat 59 60def drawMap(): 61 global actors 62 63 for y in range(ylen): 64 for x in range(xlen): 65 ind = inmap(actors, x, y) 66 aoc.debug(("G" if actors[ind][3] == 1 else "E") \ 67 if ind != None else ("." if vmap[y][x] == 0 else "#"), end="") 68 aoc.debug() 69 70def getSteps(cmap, a1): 71 # adjacent squares 72 npos = [[a1[0] + dir[0], a1[1] + dir[1]] for dir in adjacent] 73 74 # pos of enemy and distance 75 steps = [[np[0], np[1], cmap[np[0], np[1]]] for np in npos if (np[0], np[1]) in cmap] 76 77 return steps 78 79def closestStep(a1, a2): 80 countmap = dict() 81 next = dict() 82 next[(a2[0], a2[1])] = 0 83 counter = 0 84 steps = list() 85 while len(next) > 0 and len(steps) == 0: # first steps available will be shortest 86 countmap = {**countmap, **next} # merge dictionaries 87 counter += 1 88 temp = dict() 89 for n in next: 90 for dir in adjacent: 91 nx = n[0]+dir[0] 92 ny = n[1]+dir[1] 93 if not isblocked(nx, ny) and (nx, ny) not in countmap and (nx, ny) not in temp: 94 temp[(nx,ny)] = counter 95 next = temp 96 steps = getSteps(countmap, a1) 97 98 # if reachable 99 if len(steps) != 0: 100 return sorted(steps, key = lambda x : (x[1], x[0]))[0] 101 else: 102 return [None] 103 104def move(a): 105 global actors 106 107 # best steps from enemies 108 steps = [[na[0], na[1]] + closestStep(a, na) for na in actors if na[3] != a[3]] 109 110 # only where step is possible 111 steps = [s for s in steps if s[2] != None] 112 113 # skip when none possible 114 if len(steps) == 0: 115 return 116 117 # best move 118 bestmove = sorted(steps, key = lambda x : (x[4], x[1], x[0]))[0] 119 a[0] = bestmove[2] 120 a[1] = bestmove[3] 121 122def getInrange(a): 123 global actors 124 125 inrange = list() 126 for dir in adjacent: 127 nx = a[0] + dir[0] 128 ny = a[1] + dir[1] 129 ind = inmap(actors, nx, ny) 130 if ind != None and actors[ind][3] != a[3]: 131 inrange.append(ind) 132 return inrange 133 134def next_tick(tick): 135 global actors 136 137 actors = sorted(actors, key=lambda x: (x[1], x[0]), reverse=True) 138 i = len(actors)-1 139 140 while i >= 0: 141 a = actors[i] 142 inrange = getInrange(a) # get enemies in range 143 if len(inrange) == 0: 144 move(a) 145 inrange = getInrange(a) 146 147 if len(inrange) != 0: 148 inrange = [actors[ai] + [ai] for ai in inrange] 149 150 # lowest health in reading order 151 cai = sorted(inrange, key = lambda x : (x[2], x[1], x[0]))[0][-1] 152 153 # attack player 154 actors[cai][2] -= a[5] # attack 155 if actors[cai][2] <= 0: 156 actors.pop(cai) # dead 157 aoc.debug("death -",cai) 158 if min_alive() == 0 and i != 0: # incomplete round 159 return False 160 if cai < i: i -= 1 161 162 if aoc.debug_lvl and False: 163 aoc.debug() 164 aoc.debug("small step -",a[4]) 165 drawMap() 166 status_report() 167 i -= 1 168 169 if aoc.debug_lvl: 170 aoc.debug() 171 aoc.debug("tick:", tick) 172 drawMap() 173 status_report() 174 else: 175 aoc.debug(tick) 176 177 return True 178 179def min_alive(): 180 alive = [0,0] 181 for a in actors: 182 alive[1 * (a[3] == 0)] += 1 183 return min(alive) 184 185def sum_hp(): 186 return sum(a[2] for a in actors) 187 188def status_report(): 189 global actors 190 191 sactors = sorted(actors, key = lambda x:x[4]) 192 for a in sactors: 193 aoc.debug("HP:", a[2]) 194 aoc.debug() 195 196def elves_alive(): 197 return len([a for a in actors if a[3] == 0]) 198 199def start_battle(eap): 200 global actors 201 202 set_game() 203 for a in actors: 204 if a[3] == 0: 205 a[5] = eap 206 207 elfcount = elves_alive() 208 209 ticks = 0 210 while min_alive() > 0: 211 if next_tick(ticks): 212 ticks += 1 213 status_report() 214 215 return ((sum_hp() * ticks), (elfcount - elves_alive())) 216 217def solve1(args): 218 res = start_battle(3) 219 return res[0] 220 221def solve2(args): 222 eap = 4 223 res = start_battle(eap) 224 while res[1] != 0: 225 eap += 1 226 res = start_battle(eap) 227 return res[0] 228 229aoc.run(solve1, solve2, sols=[229798, 52972])