···11+import copy
22+import fileinput
33+from itertools import count
44+55+from utils import Point
66+77+88+class Entity:
99+ def __init__(self, x, y, type):
1010+ self.pos = Point(x, y)
1111+ self.hp = 200
1212+ self.type = type
1313+1414+ def __str__(self):
1515+ return "{}({} @ {},{})".format(self.type, self.hp, self.pos.x, self.pos.y)
1616+1717+1818+GRID = {}
1919+ENTITIES = []
2020+2121+for y, line in enumerate(fileinput.input()):
2222+ for x, t in enumerate(line.strip()):
2323+ if t == 'G' or t == 'E':
2424+ ENTITIES.append(Entity(x, y, t))
2525+ GRID[Point(x, y)] = '.'
2626+ else:
2727+ GRID[Point(x, y)] = t
2828+2929+3030+def simulate(elf_dmg=3):
3131+ grid = copy.deepcopy(GRID)
3232+ entities = copy.deepcopy(ENTITIES)
3333+ elves = set(e.pos for e in entities if e.type == 'E')
3434+ goblins = set(e.pos for e in entities if e.type == 'G')
3535+3636+ def dmg(u):
3737+ if u == 'E':
3838+ return elf_dmg
3939+ else:
4040+ return 3
4141+4242+ def dist(p, q):
4343+ horizon = [p]
4444+ seen = set()
4545+4646+ depth = 0
4747+ while horizon:
4848+ next_horizon = []
4949+ for h in horizon:
5050+ if h == q:
5151+ return depth
5252+5353+ for n in h.neighbours_4():
5454+ if n in seen:
5555+ continue
5656+5757+ if free(n):
5858+ next_horizon.append(n)
5959+ seen.add(n)
6060+6161+ horizon = next_horizon
6262+ depth += 1
6363+6464+ return 1e8
6565+6666+ def free(p):
6767+ return grid.get(p) == '.' and p not in goblins and p not in elves
6868+6969+ for curr_round in count():
7070+ entities.sort(key=lambda u: (u.pos.y, u.pos.x))
7171+ dead = set()
7272+7373+ for i, unit in enumerate(entities):
7474+ if i in dead:
7575+ continue
7676+7777+ enemies = goblins if unit.type == 'E' else elves
7878+ selves = goblins if unit.type == 'G' else elves
7979+8080+ # First, attempt to attack if there are nearby enemies
8181+ attack_can = [n for n in unit.pos.neighbours_4() if n in enemies]
8282+ if attack_can:
8383+ okays = []
8484+ for j, qnit in enumerate(entities):
8585+ if qnit.pos in attack_can and unit.type != qnit.type:
8686+ okays.append((qnit, j))
8787+ qnit, j = sorted(okays, key=lambda (qnit, j): (qnit.hp, qnit.pos.y, qnit.pos.x))[0]
8888+8989+ qnit.hp -= dmg(unit.type)
9090+ if qnit.hp <= 0:
9191+ enemies.remove(qnit.pos)
9292+ dead.add(j)
9393+9494+ if not enemies:
9595+ import sys
9696+ rr = curr_round
9797+ if i == len(entities) - len(dead):
9898+ rr += 1
9999+ score = sum(u.hp for u in entities if u.type == unit.type)
100100+ return rr * score, unit.type
101101+102102+ # Process next entity, since we can't attack and then move
103103+ continue
104104+105105+ # Otherwise, move according to pathfinding algorithm
106106+ in_range = set()
107107+ for e in enemies:
108108+ for n in e.neighbours_4():
109109+ if free(n):
110110+ in_range.add(n)
111111+112112+ horizon = [unit.pos]
113113+ seen = set()
114114+115115+ next_spot = None
116116+117117+ while horizon:
118118+ found = set()
119119+ next_horizon = []
120120+ for h in horizon:
121121+ for n in h.neighbours_4():
122122+ if n in seen:
123123+ continue
124124+125125+ if free(n):
126126+ next_horizon.append(n)
127127+ seen.add(n)
128128+ if n in in_range:
129129+ found.add(n)
130130+131131+ if found:
132132+ next_spot = sorted(found, key=lambda zz: (zz.y, zz.x))[0]
133133+134134+ poss = []
135135+ for n in unit.pos.neighbours_4():
136136+ if free(n):
137137+ poss.append((dist(n, next_spot), n))
138138+139139+ poss.sort(key=lambda z: (z[0], z[1].y, z[1].x))
140140+141141+ next_spot = poss[0][1]
142142+143143+ selves.remove(unit.pos)
144144+ selves.add(next_spot)
145145+146146+ unit.pos = next_spot if next_spot is not None else unit.pos
147147+ break
148148+149149+ horizon = next_horizon
150150+151151+ # Now try attacking again
152152+ attack_can = [n for n in unit.pos.neighbours_4() if n in enemies]
153153+ if attack_can:
154154+ # Attack
155155+ okays = []
156156+ for j, qnit in enumerate(entities):
157157+ if qnit.pos in attack_can and unit.type != qnit.type:
158158+ okays.append((qnit, j))
159159+ qnit, j = sorted(okays, key=lambda (qnit, j): (qnit.hp, qnit.pos.y, qnit.pos.x))[0]
160160+161161+ qnit.hp -= dmg(unit.type)
162162+ if qnit.hp <= 0:
163163+ enemies.remove(qnit.pos)
164164+ dead.add(j)
165165+166166+ if not enemies:
167167+ import sys
168168+ rr = curr_round
169169+ if i == len(entities) - len(dead):
170170+ rr += 1
171171+ score = sum(u.hp for u in entities if u.type == unit.type)
172172+ return rr * score, unit.type
173173+174174+ # Clean up any entities who have died
175175+ for j in reversed(sorted(dead)):
176176+ del entities[j]
177177+178178+179179+print "Outcome of initial simulation:", simulate()[0]
180180+print "Outcome of elf victory:", simulate(elf_dmg=23)[0]