In [1]:
def readfile(path):
    with open(path) as input_file: 
        return input_file.read().strip()

def params(program, i, count):
    modes = str(program[i] // 100)[::-1]
    result = []
    
    for x in range(count - 1):
        arg = program[i + 1 + x]
        if x >= len(modes) or modes[x] == "0":
            result.append(program[arg])
        elif modes[x] == "2":
            result.append(program[program.offset + arg])
        else:
            result.append(arg)
            
    output = program[i + count]
    if len(modes) >= count and modes[count - 1] == "2":
        output += program.offset
        
    result.append(output) # output
    return result

def add(program, i, **kwargs):
    assert program[i] % 100 == 1
    a, b, out = params(program, i, 3)
    program[out] = a + b
    return i + 4, None
    
def mul(program, i, **kwargs):
    assert program[i] % 100 == 2
    a, b, out = params(program, i, 3)
    program[out] = a * b
    return i + 4, None

def inp(program, i, input_value, **kwargs):
    assert program[i] % 100 == 3
    out, = params(program, i, 1)
    program[out] = input_value
    # print(f"> {program[i]} program[{out}] = {program[out]}")
    return i + 2, None

def out(program, i, **kwargs):
    assert program[i] % 100 == 4
    val, _ = params(program, i, 2)
    # print(f"> {val}")
    return i + 2, val
    
def jit(program, i, **kwargs):
    assert program[i] % 100 == 5
    test, ip, _ = params(program, i, 3)
    if test != 0:
        return ip, None
    return i + 3, None

def jif(program, i, **kwargs):
    assert program[i] % 100 == 6
    test, ip, _ = params(program, i, 3)
    if test == 0:
        return ip, None
    return i + 3, None

def lt(program, i, **kwargs):
    assert program[i] % 100 == 7
    a, b, out = params(program, i, 3)
    program[out] = int(a < b)
    return i + 4, None

def eq(program, i, **kwargs):
    assert program[i] % 100 == 8
    a, b, out = params(program, i, 3)
    program[out] = int(a == b)
    return i + 4, None

def rbo(program, i, **kwargs):
    assert program[i] % 100 == 9
    delta, _ = params(program, i, 2)
    program.offset += delta
    # print (f"> rbo = {program.offset}" )
    return i + 2, None

operations = { 1: add, 2: mul, 3: inp, 4: out, 5: jit, 6: jif, 7: lt, 8: eq, 9: rbo }

class Program(list):
    def __init__(self, *args, **kwargs):
        super(Program, self).__init__(args[0])
        self.offset = 0

def compute(program, debug=False):
    program = Program(program)
    
    l = len(program)
    i = 0
    output_value = None
    last_output = None
    
    while i < l:
        op = program[i]
        if op == 99:
            if debug: 
                print(f">> exit {output_value}")
            return 
        
        if debug:
            print(f">> {i}, {list(enumerate(program))[i:i+5]}")
            
        if op % 100 == 3:
            input_value = yield
        else:
            input_value = None
            
        i, output_value = operations[op % 100](program, i, input_value=input_value)
        
        if output_value is not None:
            yield output_value
        
    raise Exception("Didn't terminate properly")
    
def process(listing):
    lst = list(map(int, listing.split(",")))
    lst.extend([0] * 100000)
    return lst
In [2]:
input15 = readfile("AoC/input15.txt")
In [38]:
def print_grid(grid, x=0, y=0):
    grid[(x, y)] = "@"
    keys = grid.keys()
    minx = min(x[0] for x in keys)
    miny = min(x[1] for x in keys)
    maxx = max(x[0] for x in keys)
    maxy = max(x[1] for x in keys)

    for y in range(miny, maxy + 1):
        for x in range(minx, maxx + 1):
            print(grid.get((x, y), " "), end="")
        print()

    return "Printed"
In [50]:
dirs = {1: (0, -1), 2: (0, 1), 3: (-1, 0), 4: (1, 0)}
opp = {1: 2, 2: 1, 3: 4, 4: 3}

def walk_droid():
    droid = compute(process(input15))

    grid = {}
    queue = [[]]
    visited = set()
    visited.add((0, 0))

    oxy = None
    oxy_pos = None
    testset = set([1, 2])

    while queue:
        steps = queue.pop(0)
        x, y = 0, 0
        for step in steps:
            assert next(droid) == None
            assert droid.send(step) in testset
            x += dirs[step][0]
            y += dirs[step][1]

        for d in range(1, 5):
            assert next(droid) == None
            result = droid.send(d)

            nx, ny = (x + dirs[d][0], y + dirs[d][1])
            ch = '#'
            if result == 1 or result == 2:
                ch = '.' if result == 1 else 'o'
                assert next(droid) == None
                assert droid.send(opp[d]) in testset

                if (nx, ny) not in visited:
                    queue.append(steps[:] + [d])
                    visited.add((nx, ny))
                    if result == 2:
                        oxy = len(steps) + 1
                        oxy_pos = nx, ny
            else:
                assert result == 0
            grid[(nx, ny)] = ch

        for step in steps[::-1]:
            assert next(droid) == None
            assert droid.send(opp[step]) in testset

    return oxy, oxy_pos, grid

oxy, oxypos, grid = walk_droid()
In [51]:
print_grid(grid)
 ####### ############# ############# ### 
#.......#.............#.............#...#
#.#####.#.#.#.#######.#.#.#########.###.#
#o#.....#.#.#.#.....#.#.#.#.......#...#.#
 ##.###.#.#.###.###.###.#.#.###.#####.#.#
#...#...#.#...#.#.#.....#.#.#.#.......#.#
#.#######.###.#.#.#######.#.#.#########.#
#.#.......#.#.#...#.....#.#.#...#.......#
#.#.#######.#.###.#.###.#.#.#.#.#####.#.#
#.#.........#.....#...#...#...#.....#.#.#
#.#.#######.#########.#### ########.###.#
#...#...#.#.#.......#.#...#.......#...#.#
 ####.#.#.#.#.###.###.#.###.###.#####.#.#
#.....#.#.....#...#...#.......#.....#...#
#.#####.###### ####.###.#######.#.#####.#
#.#...#.......#...#...#.#.....#.#.....#.#
#.###.#######.#.#.#.#.###.###.#####.###.#
#...........#...#.#.#.....#...#.....#...#
 ##########.#####.#.#######.###.#####.## 
#.....#.#...#...#.#...#.....#.#.....#.#.#
#.###.#.#.#####.#.#####.#####.#.###.#.#.#
#.#.....#.......#.#..@#.....#...#.#...#.#
#.#####.#######.#.#.###.###.#.###.#####.#
#...#...#.....#.#...#...#...#...#...#...#
#.#.#.###.###.#.#########.#####.###.#.#.#
#.#.#.#...#...#.#.....#...#...#...#...#.#
#.#.#.#####.###.###.#.#.###.#.###.###.#.#
#.#.#.....#.#...#...#...#...#...#...#.#.#
#.#.#####.#.#.###.## ####.## ##.###.###.#
#.#.#...#.#.#.#...#.#.......#.....#...#.#
#.#.#.###.#.#.###.#.#.#######.###.###.#.#
#.#.#.....#.#.....#.......#...#.....#.#.#
 ##.#.#####.###########.###.###.#####.#.#
#...#.#.....#.........#.#...#.#...#...#.#
#.###.#.###.#.#######.###.###.#.###.###.#
#.#.....#.....#...#.......#...#.#...#...#
#.#.#### ####.#.#.#####.#####.#.#.###.#.#
#.#.#...#...#.#.#.....#.#...#...#.....#.#
#.###.#.#.#.###.#####.###.#.###########.#
#.....#...#.........#.....#.............#
 ##### ### ######### ##### ############# 
Out[51]:
'Printed'
In [52]:
oxypos
Out[52]:
(-20, -18)
In [53]:
def deepest(grid, oxypos):
    q = [oxypos]
    depths = {oxypos: 0}
    while q:
        cur = q.pop(0)
        for d in range(1, 5):
            nx = (cur[0] + dirs[d][0], cur[1] + dirs[d][1])

            if grid[nx] == '.' and nx not in depths:
                q.append(nx)
                depths[nx] = depths[cur] + 1
    return max(depths.values())
deepest(grid, oxypos)
Out[53]:
376
In [ ]: