<reponame>Artemis21/AOC19<filename>code/day15.py
import random


class Command:
    @staticmethod
    def write(master, val):
        address = master.next(literal=True)
        if address >= len(master.code):
            master.extra[address] = val
        else:
            master.code[address] = val


class Add(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        cls.write(master, a+b)


class Mult(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        cls.write(master, a*b)


class Input(Command):
    params = 0
    @classmethod
    def run(cls, master):
        val = master.stdin()
        cls.write(master, val)


class Output(Command):
    params = 1
    @classmethod
    def run(cls, inp, master):
        master.stdout(inp)


class TrueJump(Command):
    params = 2
    @classmethod
    def run(cls, inp, jump, master):
        if inp:
            master.pointer = jump
            master.increase = 0


class FalseJump(Command):
    params = 2
    @classmethod
    def run(cls, inp, jump, master):
        if not inp:
            master.pointer = jump
            master.increase = 0


class Less(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        if a < b:
            cls.write(master, 1)
        else:
            cls.write(master, 0)


class Equal(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        if a == b:
            cls.write(master, 1)
        else:
            cls.write(master, 0)


class ChangeBase(Command):
    params = 1
    @classmethod
    def run(cls, am, master):
        master.relbase += am


CMDS = {
    1: Add, 2: Mult, 3: Input, 4: Output, 5: TrueJump, 6: FalseJump, 7: Less,
    8: Equal, 9: ChangeBase
}


class Computer:
    def __init__(self, code, inputs=[]):
        self.code = code
        self.pointer = 0
        self.relbase = 0
        self.extra = {}
        self.relmode = False
        self.increase = 0

    def run(self):
        while not self.loop():
            pass

    def loop(self):
        op = self.next(command=True)
        cmd = self.get_cmd(op)
        if not cmd:
            return True    # OP 99
        modes = self.get_modes(op, cmd.params+1)
        inps = self.get_inps(modes[:-1])
        if modes and modes[-1] == '2':
            self.relmode = True
        cmd.run(*inps, self)
        self.pointer += self.increase
        self.increase = 0
        self.relmode = False

    def get_cmd(self, raw_op):
        str_op = str(raw_op)
        if len(str_op) < 3:
            num = raw_op
        else:
            num = int(str_op[-2:])
        if num == 99:
            return False
        return CMDS[num]

    def get_modes(self, raw_op, params):    # tested
        str_op = str(raw_op)
        given_modes = ''
        if len(str_op) > 2:
            given_modes = str_op[:-2]
        full_modes = ''
        for i in range(-1, -params-1, -1):
            try:
                full_modes += given_modes[i]
            except IndexError:
                full_modes += '0'
        return full_modes

    def get_inps(self, modes):
        ret = []
        for mode in modes:
            ret.append(self.next(mode=mode))
        return ret

    def get_param(self, mode, raw, literal=False):
        if mode == '2':
            if literal:
                return raw + self.relbase
            raw += self.relbase
            mode = '0'
        if literal:
            return raw
        if mode == '0':
            if raw < len(self.code):
                return self.code[raw]
            else:
                return self.extra.get(raw, 0)
        elif mode == '1':
            return raw

    def next(self, literal=False, command=False, mode='0'):
        raw = self.code[self.pointer + self.increase]
        self.increase += 1
        if command:
            return raw
        if mode == '0' and self.relmode:
            mode = '2'
        return self.get_param(mode, raw, literal)


def get_inp():
    with open('code/15.txt') as f:
        return list(map(int, f.read().split(',')))


class Robot(Computer):
    def __init__(self, code, pointer=0, x=0, y=0, grid={}):
        super().__init__(code)
        self.pointer = pointer
        self.x, self.y = x, y
        self.grid = grid
        self.done = False
        self.dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)]
        self.dircodes = [3, 1, 4, 2]
        self.dir = 0
        self.trydirs = [1, 0, -1, 2]
        self.n = 0

    def run(self):
        while self.n < 5000:
            self.loop()

    def stdin(self):
        find = {}
        for idx, xy in enumerate(self.dirs):
            dx, dy = xy
            nx = dx + self.x
            ny = dy + self.y
            s = self.grid.get((nx, ny), -1)
            find[idx] = (s, nx, ny)
        if -1 in [i[0] for i in find.values()]:
            for j in find:
                if find[j][0] == -1:
                    self.nx, self.ny = find[j][1:]
                    return self.dircodes[j]
        for i in self.trydirs:
            dir = self.dir + i
            dir %= 4
            dx, dy = self.dirs[dir]
            nx, ny = self.x + dx, self.y + dy
            s = self.grid.get((nx, ny), -1)
            if s:
                self.dir = dir
                self.nx, self.ny = nx, ny
                return self.dircodes[self.dir]

    def stdout(self, out):
        self.n += 1
        self.grid[(self.nx, self.ny)] = out
        if out:
            self.x, self.y = self.nx, self.ny


def find_shortest(grid):
    stage = [[(0, 0)]]
    answer = None
    while True:
        next_stage = []
        for old in stage:
            x, y = old[-1]
            for dx, dy in ((-1, 0), (0, -1), (1, 0), (0, 1)):
                nx, ny = x + dx, y + dy
                if (nx, ny) in old:
                    continue
                if (nx < -21) or (nx > 20) or (ny < -21) or (ny > 20):
                    continue
                s = grid[(nx, ny)]
                if not s:
                    continue
                if s == 2:
                    answer = len(old)
                    break
                new = list(old)
                new.append((nx, ny))
                next_stage.append(new)
            if answer:
                break
        if answer:
            break
        stage = next_stage
    return answer


def flood_fill(grid):
    done = False
    n = 0
    while not done:
        new = dict(grid)
        for x, y in grid:
            if grid[(x, y)] != 2:
                continue
            for dx, dy in ((-1, 0), (0, -1), (1, 0), (0, 1)):
                nx, ny = x + dx, y + dy
                if (nx < -21) or (nx > 20) or (ny < -21) or (ny > 20):
                    continue
                s = grid[(nx, ny)]
                if not s:
                    continue
                new[(nx, ny)] = 2
        done = new == grid
        n += 1
        grid = new
    return n - 1


def part_a():
    r = Robot(get_inp())
    r.run()
    return find_shortest(r.grid)


def part_b():
    r = Robot(get_inp())
    r.run()
    return flood_fill(r.grid)

import random


class Command:
    @staticmethod
    def write(master, val):
        address = master.next(literal=True)
        if address >= len(master.code):
            master.extra[address] = val
        else:
            master.code[address] = val


class Add(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        cls.write(master, a+b)


class Mult(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        cls.write(master, a*b)


class Input(Command):
    params = 0
    @classmethod
    def run(cls, master):
        val = master.stdin()
        cls.write(master, val)


class Output(Command):
    params = 1
    @classmethod
    def run(cls, inp, master):
        master.stdout(inp)


class TrueJump(Command):
    params = 2
    @classmethod
    def run(cls, inp, jump, master):
        if inp:
            master.pointer = jump
            master.increase = 0


class FalseJump(Command):
    params = 2
    @classmethod
    def run(cls, inp, jump, master):
        if not inp:
            master.pointer = jump
            master.increase = 0


class Less(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        if a < b:
            cls.write(master, 1)
        else:
            cls.write(master, 0)


class Equal(Command):
    params = 2
    @classmethod
    def run(cls, a, b, master):
        if a == b:
            cls.write(master, 1)
        else:
            cls.write(master, 0)


class ChangeBase(Command):
    params = 1
    @classmethod
    def run(cls, am, master):
        master.relbase += am


CMDS = {
    1: Add, 2: Mult, 3: Input, 4: Output, 5: TrueJump, 6: FalseJump, 7: Less,
    8: Equal, 9: ChangeBase
}


class Computer:
    def __init__(self, code, inputs=[]):
        self.code = code
        self.pointer = 0
        self.relbase = 0
        self.extra = {}
        self.relmode = False
        self.increase = 0

    def run(self):
        while not self.loop():
            pass

    def loop(self):
        op = self.next(command=True)
        cmd = self.get_cmd(op)
        if not cmd:
            return True    # OP 99
        modes = self.get_modes(op, cmd.params+1)
        inps = self.get_inps(modes[:-1])
        if modes and modes[-1] == '2':
            self.relmode = True
        cmd.run(*inps, self)
        self.pointer += self.increase
        self.increase = 0
        self.relmode = False

    def get_cmd(self, raw_op):
        str_op = str(raw_op)
        if len(str_op) < 3:
            num = raw_op
        else:
            num = int(str_op[-2:])
        if num == 99:
            return False
        return CMDS[num]

    def get_modes(self, raw_op, params):    # tested
        str_op = str(raw_op)
        given_modes = ''
        if len(str_op) > 2:
            given_modes = str_op[:-2]
        full_modes = ''
        for i in range(-1, -params-1, -1):
            try:
                full_modes += given_modes[i]
            except IndexError:
                full_modes += '0'
        return full_modes

    def get_inps(self, modes):
        ret = []
        for mode in modes:
            ret.append(self.next(mode=mode))
        return ret

    def get_param(self, mode, raw, literal=False):
        if mode == '2':
            if literal:
                return raw + self.relbase
            raw += self.relbase
            mode = '0'
        if literal:
            return raw
        if mode == '0':
            if raw < len(self.code):
                return self.code[raw]
            else:
                return self.extra.get(raw, 0)
        elif mode == '1':
            return raw

    def next(self, literal=False, command=False, mode='0'):
        raw = self.code[self.pointer + self.increase]
        self.increase += 1
        if command:
            return raw
        if mode == '0' and self.relmode:
            mode = '2'
        return self.get_param(mode, raw, literal)


def get_inp():
    with open('code/15.txt') as f:
        return list(map(int, f.read().split(',')))


class Robot(Computer):
    def __init__(self, code, pointer=0, x=0, y=0, grid={}):
        super().__init__(code)
        self.pointer = pointer
        self.x, self.y = x, y
        self.grid = grid
        self.done = False
        self.dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)]
        self.dircodes = [3, 1, 4, 2]
        self.dir = 0
        self.trydirs = [1, 0, -1, 2]
        self.n = 0

    def run(self):
        while self.n < 5000:
            self.loop()

    def stdin(self):
        find = {}
        for idx, xy in enumerate(self.dirs):
            dx, dy = xy
            nx = dx + self.x
            ny = dy + self.y
            s = self.grid.get((nx, ny), -1)
            find[idx] = (s, nx, ny)
        if -1 in [i[0] for i in find.values()]:
            for j in find:
                if find[j][0] == -1:
                    self.nx, self.ny = find[j][1:]
                    return self.dircodes[j]
        for i in self.trydirs:
            dir = self.dir + i
            dir %= 4
            dx, dy = self.dirs[dir]
            nx, ny = self.x + dx, self.y + dy
            s = self.grid.get((nx, ny), -1)
            if s:
                self.dir = dir
                self.nx, self.ny = nx, ny
                return self.dircodes[self.dir]

    def stdout(self, out):
        self.n += 1
        self.grid[(self.nx, self.ny)] = out
        if out:
            self.x, self.y = self.nx, self.ny


def find_shortest(grid):
    stage = [[(0, 0)]]
    answer = None
    while True:
        next_stage = []
        for old in stage:
            x, y = old[-1]
            for dx, dy in ((-1, 0), (0, -1), (1, 0), (0, 1)):
                nx, ny = x + dx, y + dy
                if (nx, ny) in old:
                    continue
                if (nx < -21) or (nx > 20) or (ny < -21) or (ny > 20):
                    continue
                s = grid[(nx, ny)]
                if not s:
                    continue
                if s == 2:
                    answer = len(old)
                    break
                new = list(old)
                new.append((nx, ny))
                next_stage.append(new)
            if answer:
                break
        if answer:
            break
        stage = next_stage
    return answer


def flood_fill(grid):
    done = False
    n = 0
    while not done:
        new = dict(grid)
        for x, y in grid:
            if grid[(x, y)] != 2:
                continue
            for dx, dy in ((-1, 0), (0, -1), (1, 0), (0, 1)):
                nx, ny = x + dx, y + dy
                if (nx < -21) or (nx > 20) or (ny < -21) or (ny > 20):
                    continue
                s = grid[(nx, ny)]
                if not s:
                    continue
                new[(nx, ny)] = 2
        done = new == grid
        n += 1
        grid = new
    return n - 1


def part_a():
    r = Robot(get_inp())
    r.run()
    return find_shortest(r.grid)


def part_b():
    r = Robot(get_inp())
    r.run()
    return flood_fill(r.grid)
# OP 99 # tested