Skip to content

Commit

Permalink
Refactor: moved intcode stuff into its own files
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeMoll committed Dec 9, 2019
1 parent 15c69cf commit b8a3dd6
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 193 deletions.
195 changes: 5 additions & 190 deletions day05.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import aoc
from enum import IntEnum
import typing, itertools
from intcode import IntcodeMachine
from instruction import * # shush I know what I'm doing

def main():
aoc.header("Sunny with a Chance of Asteroids")
Expand All @@ -12,8 +13,8 @@ def main():

def test():
for enc_mode in map(lambda x:x*100, [0,1,10,11,100,101,110,111]):
dec_mode = Instruction.decode_modes(enc_mode)
assert Instruction.encode_modes(dec_mode) == enc_mode, f"Expected {enc_mode}, got {Instruction.encode_modes(dec_mode)} (Decoded {dec_mode})"
dec_mode = IntcodeInstruction.decode_modes(enc_mode)
assert IntcodeInstruction.encode_modes(dec_mode) == enc_mode, f"Expected {enc_mode}, got {Instruction.encode_modes(dec_mode)} (Decoded {dec_mode})"

def assert_finishes(
initial_memory,
Expand All @@ -25,7 +26,7 @@ def assert_finishes(

def f():
for ele in initial_memory:
if issubclass(ele.__class__, Instruction):
if issubclass(ele.__class__, IntcodeInstruction):
# ele is an Instruction, we should expand it and flatten the result
for opcode in ele.expand(): yield opcode
else: yield ele
Expand Down Expand Up @@ -94,134 +95,6 @@ def run(initial_memory : typing.List[int], inpt : typing.List[int], instruction_
def part2():
pass

class Mode(IntEnum):
DIRECT = 0 # Operand is an address of the value
IMMEDIATE = 1 # Operand is the value

class Instruction:
def __init__(self, *args):
assert len(args) == self.OPERANDS
self.args = args

def expand(self):
return (
self.OPCODE +
Instruction.encode_modes([
Mode.IMMEDIATE if type(a) == str else Mode.DIRECT
for a in self.args
]),
*map(int, self.args)
)

@staticmethod
def exec(full_opcode : int, machine, *args):
raise NotImplemented()

@staticmethod
def decode_modes(full_opcode : int) -> typing.Sequence[Mode]:
m_str = f"{full_opcode//100:03}"
return tuple(Mode(int(c)) for c in m_str[::-1])

@staticmethod
def encode_modes(modes : typing.Sequence[Mode]):
result = 0
for i in range(len(modes)):
result += modes[i] * 10**(2+i)
return result

class IAdd(Instruction):
OPCODE = 1
OPERANDS = 3
@staticmethod
def exec(full_opcode : int, machine, op0, op1, op2):
modes = Instruction.decode_modes(full_opcode)
machine.store(
op2,
machine.fetch(op0, modes[0]) +
machine.fetch(op1, modes[1])
)

class IMult(Instruction):
OPCODE = 2
OPERANDS = 3
@staticmethod
def exec(full_opcode : int, machine, op0, op1, op2):
modes = Instruction.decode_modes(full_opcode)
machine.store(
op2,
machine.fetch(op0, modes[0]) *
machine.fetch(op1, modes[1])
)

class IHalt(Instruction):
OPCODE = 99
OPERANDS = 0
@staticmethod
def exec(full_opcode : int, machine):
machine.running = False

class IInput(Instruction):
OPCODE = 3
OPERANDS = 1
@staticmethod
def exec(full_opcode : int, machine, op):
if len(machine.input) > 0:
machine.store(op, machine.input.pop(0))
else:
machine.pc -= 2
raise EOFError("Out of input!")

class IOutput(Instruction):
OPCODE = 4
OPERANDS = 1
@staticmethod
def exec(full_opcode : int, machine, op):
modes = Instruction.decode_modes(full_opcode)
val = machine.fetch(op, modes[0])
machine.output.append(val)

class IJumpNZ(Instruction):
OPCODE = 5
OPERANDS = 2
@staticmethod
def exec(full_opcode : int, machine, op0, op1):
modes = Instruction.decode_modes(full_opcode)
if machine.fetch(op0, modes[0]) != 0:
machine.jump(machine.fetch(op1, modes[1]))

class IJumpZ(Instruction):
OPCODE = 6
OPERANDS = 2
@staticmethod
def exec(full_opcode : int, machine, op0, op1):
modes = Instruction.decode_modes(full_opcode)
if machine.fetch(op0, modes[0]) == 0:
machine.jump(machine.fetch(op1, modes[1]))

class ILessThan(Instruction):
OPCODE = 7
OPERANDS = 3
@staticmethod
def exec(full_opcode : int, machine, op0, op1, op2):
modes = Instruction.decode_modes(full_opcode)
if machine.fetch(op0, modes[0]) < machine.fetch(op1, modes[1]):
val = 1
else: val = 0

machine.store(op2, val)

class IEquals(Instruction):
OPCODE = 8
OPERANDS = 3
@staticmethod
def exec(full_opcode : int, machine, op0, op1, op2):
modes = Instruction.decode_modes(full_opcode)
if machine.fetch(op0, modes[0]) == machine.fetch(op1, modes[1]):
val = 1
else: val = 0

machine.store(op2, val)

INSTRUCTIONS_P1 = {
i.OPCODE : i for i in [IAdd, IMult, IHalt, IInput, IOutput]
}
Expand All @@ -230,63 +103,5 @@ def exec(full_opcode : int, machine, op0, op1, op2):
i.OPCODE : i for i in [IAdd, IMult, IHalt, IInput, IOutput, IJumpNZ, IJumpZ, ILessThan, IEquals]
}

class IntcodeMachine:

def __init__(self, initial_memory : typing.List[int], instruction_set : typing.Dict[int, Instruction], inpt=[]):
self.memory = {
i:initial_memory[i] for i in range(len(initial_memory))
}
self.memory_top = len(initial_memory) + 1
self.pc = -1
self.running = True
self.jumped = False
self.input = inpt
self.output = []
self.instruction_set = instruction_set

def next_pc(self):
if self.jumped:
self.jumped = False
return self.pc

self.pc += 1
while self.pc not in self.memory:
self.pc += 1
if self.pc > self.memory_top:
# Halt; run off the end of memory
raise KeyError("Run off the end of memory!")
return self.pc

def step(self) -> bool:
try: full_opcode = self.memory[self.next_pc()]
except KeyError: return False

inst = self.instruction_set[full_opcode % 100]
args = tuple(
self.memory[self.next_pc()]
for _ in range(inst.OPERANDS)
)
# print(full_opcode, *args)
inst.exec(full_opcode, self, *args)
return self.running


def fetch(self, op, mode : Mode):
if mode == Mode.IMMEDIATE: return op
# else
if op in self.memory:
return self.memory[op]
else:
raise RuntimeError(f"Uninitialised access at address {op}")

def store(self, address : int, value : int):
self.memory[address] = value
if address > self.memory_top: self.memory_top = address + 1

def jump(self, address):
self.pc = address
self.jumped = True


if __name__ == "__main__":
main()
10 changes: 7 additions & 3 deletions day07.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import aoc
from day05 import IntcodeMachine, INSTRUCTIONS_P2
from intcode import IntcodeMachine
from typing import List, Tuple
from functools import lru_cache
from itertools import permutations
from operator import itemgetter
from instruction import *

Settings = Tuple[int,int,int,int,int]
INSTRUCTIONS = {
i.OPCODE : i for i in [IAdd, IMult, IHalt, IInput, IOutput, IJumpNZ, IJumpZ, ILessThan, IEquals]
}

def main():
aoc.header("Amplification Circuit")
Expand Down Expand Up @@ -39,7 +43,7 @@ def part2(program : List[int]):
return val

def amplifier(program : List[int], input_value : int, setting : int) -> int:
m = IntcodeMachine(program, INSTRUCTIONS_P2, inpt=[setting, input_value])
m = IntcodeMachine(program, INSTRUCTIONS, inpt=[setting, input_value])
while m.step(): pass
return m.output[-1] if len(m.output) > 0 else None

Expand All @@ -54,7 +58,7 @@ def feedback_sequence(program : List[int], settings: Settings):
output_vals = set()
val = 0
amplifiers : List[IntcodeMachine] = [
IntcodeMachine(program, INSTRUCTIONS_P2, inpt=[s]) for s in settings
IntcodeMachine(program, INSTRUCTIONS, inpt=[s]) for s in settings
]

while True:
Expand Down
116 changes: 116 additions & 0 deletions day08.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import aoc
from collections import namedtuple
from typing import List, Tuple
from operator import itemgetter

Size = namedtuple('Size', ['width', 'height'])


def main():
aoc.header("Space Image Format")
aoc.run_tests()

data = aoc.get_input().readline().strip()

s = Size(width=25, height=6)
(_, layers) = aoc.output(1, part1, args=[data, s], post=itemgetter(0))
aoc.output(2, part2, args=[layers, s], post=lambda i: (i, s), output=lambda t: print_image4(t[0], t[1]))


def test():
assert part1("123451789012", Size(width=3, height=2))[0] == 2
assert part2(
decode_to_layers("0222112222120000", Size(2,2)),
Size(2,2)
) == [0,1,1,0]

def part1(data : str, size : Size) -> Tuple[int, List[List[int]]]:
layers = decode_to_layers(data, size)
l = min(layers, key=lambda l: l.count(0))
return (l.count(1) * l.count(2), layers)

def part2(layers : List[List[int]], size : Size) -> List[int]:
coords = {
(x,y) for x in range(size.width) for y in range(size.height)
}

image = [None for _ in range(size.width * size.height)]

for l in layers:
opaque_coords = set()
for x,y in coords:
px = l[x + y * size.width]
if px < 2:
image[x + y * size.width] = px
opaque_coords.add((x,y))
coords.difference_update(opaque_coords)

return image

def decode_to_layers(data : str, size : Size) -> List[List[int]]:
layer_length = size.width * size.height
return [
[
int(data[i*layer_length + j])
for j in range(layer_length)
]
for i in range(len(data) // layer_length)
]

def print_image(img : List[int], size : Size):
for y in range(size.height):
print()
for x in range(size.width):
print("█" if img[x + y*size.width] == 1 else " ", end="")
print()


blocks4 = {
(0,0,0,0): " ",
(0,0,1,1): "▄",
(1,0,1,0): "▌",
(0,1,0,1): "▐",
(1,1,0,0): "▀",
(0,0,1,0): "▖",
(0,0,0,1): "▗",
(1,0,0,0): "▘",
(1,0,1,1): "▙",
(1,0,0,1): "▚",
(1,1,1,0): "▛",
(1,1,0,1): "▜",
(0,1,0,0): "▝",
(0,1,1,0): "▞",
(0,1,1,1): "▟",
(1,1,1,1): "█"
}

def print_image4(img : List[int], size : Size):
if size.height % 2 > 0:
img.extend([0 * size.width])
size = Size(size.width, size.height+1)
if size.width % 2 > 0:
newimg = []
lines = [ img[i*size.width : (i+1)*size.width] for i in range(size.height) ]
for line in lines:
newimg.extend(line)
newimg.append(0)
img = newimg
size = Size(size.width+1, size.height)

def px(x : int, y : int):
return img[x + y * size.width]

for y in range(0, size.height, 2):
print("\n ", end="")
for x in range(0, size.width, 2):
code = (
px( x, y),
px(x+1, y),
px( x, y+1),
px(x+1, y+1)
)
print(blocks4[code], end="")
print()

if __name__ == "__main__":
main()
Loading

0 comments on commit b8a3dd6

Please sign in to comment.