aoc-2018-python

Advent of Code 2018 Solutions in Python
git clone https://git.sinitax.com/sinitax/aoc-2018-python
Log | Files | Refs | README | sfeed.txt

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])