class Bot: NORTH = 'north' SOUTH = 'south' WEST = 'west' EAST = 'east' def __init__(self, m): self.__m = m self.__cpu = Machine(self.__m[:]) self.__history = [] self.__move_count = 0 self.__safe_items = { 'asterisk', 'polygon', 'tambourine', 'mug', 'cake', 'jam', 'easter egg', 'klein bottle' } if verbose: self.__cpu.toggle_verbose() def map(self, _display=True): history = self.__history p = 0 move = self.NORTH tries = {} inventory = set() seen_items = set() safe_items = self.__safe_items checkpoint_map = [] disable_pickups = False self.__cpu.run() o = ''.join(list(map(chr, self.__cpu.dump_output(clear=True)))) print('MAIN OUTPUT:', o) while True and not self.__cpu.halted(): sleep(0.2) ''' here, we will use (D)epth (F)irst (S)earch algo to traverse the entire map, collect all items and find our way to the checkpoint. ''' p = self.location(o) if p not in tries: tries[p] = set(self.moves(o)) if tries[p]: print(f'AVAILABLE TRIES FOR [ {p.upper()} ]:', tries[p]) backtrack = False prev = move move = tries[p].pop() if tries[p] and prev == self.reverse(move): prev = move move = tries[p].pop() tries[p].add(prev) else: backtrack = True if not history: ''' no where to backtrack to. this will happen when we've explored every other path and are forced to backtrack all the back to the beginning. END PROGRAM ''' print('NO HISTORY') if checkpoint_map and len(seen_items) == len(safe_items): print('ALL SET: NAVIGATE TO CHECKPOINT') print('SEEN ITEMS:', seen_items) print('Hit enter to continue >_') sys.stdin.readline() path = checkpoint_map[::-1] while path: self.instruct(path.pop()) move = self.EAST else: print('MISSING ITEMS OR NO CHECKPOINT PATH') print('PATH:', checkpoint_map) print('SEEN ITEMS:', seen_items) break else: move = self.reverse(history.pop()) o = self.instruct(move) for item in self.items(o): if item not in 'molten lava escape pod giant electromagnet photons infinite loop' and ( not disable_pickups or item not in seen_items): self.instruct(f'take {item}') inventory.add(item) seen_items.add(item) else: pass # print('SKIPPED:', item) if 'checkpoint' in o: print('CHECKPOINT') ''' Save path to checkpoint for later ''' checkpoint_map = history.copy() ''' Make sure ALL safe items are collected before initiating brute force. ''' if len(seen_items) != len(safe_items): continue else: print('============ ALL SET ================') self.instruct('inv') print('=====================================') print('Hit enter to continue >_') sys.stdin.readline() self.brute() break if "You can't go that way" not in o: if not backtrack: history.append(move) def brute(self): ''' try all possible combinations of items at the checkpoint. ''' for i in range(3, 6): print(f'TRYING SETS OF {i}') print('-------------------------------------') print('Hit enter to continue >_') sys.stdin.readline() item_combinations = set(combinations(self.__safe_items, i)) while item_combinations: current_set = item_combinations.pop() print('TRYING WITH FOLLOWING INVENTORY:', current_set) _o = self.items(self.instruct('inv', silent=True)) for item in [x for x in _o if x not in current_set]: self.instruct(f'drop {item}', silent=True) for item in [x for x in current_set if x not in _o]: self.instruct(f'take {item}', silent=True) _o = self.instruct('inv') ''' TRY ''' _o = self.instruct('east') if not re.search(r'(heavier|lighter) than', _o): print('YAYYYY (?):', _o) return sleep(0.2) print('SECURITY BREACH FAILED. TRY AGAIN.') def instruct(self, command: str, join: bool = True, silent: bool = False): ''' - convert text command to ASCII and feed to computer. - return output ''' if not silent: print('INSTR:', command) cpu = self.__cpu for c in map(ord, command): cpu.run(c) cpu.run(10) # return o = list(map(chr, cpu.dump_output(clear=True))) if not silent: print('OUTPUT:', ''.join(o)) return ''.join(o) if join else o def moves(self, o): ''' extract available moves from output ''' res = re.findall(r'- (north|south|east|west)', ''.join(o)) if res: pass # print('MOVES:', res) return res def items(self, o): ''' extract items from output ''' res = re.findall(r'- ([ a-z]+)', o) if res: res = [ x for x in res if x not in {'north', 'south', 'east', 'west'} ] # if res: print('ITEMS:', res) return res def location(self, o): ''' extract location from output ''' res = re.findall(r'== ([ a-z]+) ==', o.lower()) return res[0] def reverse(self, direction=None): if direction == None: direction = self.__history[-1] return { self.NORTH: self.SOUTH, self.SOUTH: self.NORTH, self.WEST: self.EAST, self.EAST: self.WEST }[direction]
class Arcade9000Turbo: def __init__(self, m): self.__cpu = Machine(m) self.__minx = self.__miny = self.__maxx = self.__maxy = 0 ''' we only need to track (x) ''' self.__player_x = 0 self.__ball_x = 0 self.__canvas = defaultdict(lambda: ' ') self.__scores = 0 self.__tiles = {0: ' ', 1: '|', 2: '⬜', 3: '➖', 4: '⚪'} if verbose: self.__cpu.toggle_verbose() def play(self, auto: bool = False): self.__cpu.run() while not self.__cpu.halted() or self.__cpu.has_output(): x = self.__cpu.output() y = self.__cpu.output() if x == -1 and y == 0: ''' player's current score ''' self.__scores = self.__cpu.output() self.display() elif x != None and y != None: ''' draw a tile to the screen ''' self.draw(x, y, self.__cpu.output()) self.display() elif auto and self.__cpu.waiting(): m = 0 if self.__ball_x > self.__player_x: m = 1 print('_/_ >') elif self.__ball_x < self.__player_x: m = -1 print('_\\_ >') else: print('_|_ >') self.__cpu.run(m) elif self.__cpu.waiting(): print('_|_ >') self.input() def input(self): try: self.__cpu.run(int(sys.stdin.readline())) except ValueError: print('Invalid input (hint: enter -1, 0, or 1 ):') self.input() def display(self): grid = [[' '] * (self.__maxx + 1) for _ in range(self.__maxy + 1)] count = 0 for (x, y), v in self.__canvas.items(): if x < 0 or y < 0: break if v == self.__tiles[2]: count += 1 grid[y][x] = v subprocess.call("clear") for l in grid: print(''.join(l)) print('\n') print('.' * (self.__maxx + 1)) print('Score: {0}, Blocks: {1}'.format(self.__scores, count)) def draw(self, x, y, tile_id): self.__canvas[(x, y)] = self.__tiles[tile_id] self.__minx = min(self.__minx, x) self.__maxx = max(self.__maxx, x) self.__miny = min(self.__miny, y) self.__maxy = max(self.__maxy, y) if tile_id == 4: ''' track ball displacement ''' self.__ball_x = x elif tile_id == 3: ''' track player position ''' self.__player_x = x
class Bot: SCAFFOLD = 35 SPACE = 46 CORNER = 64 NORTH = 94 SOUTH = 118 WEST = 60 EAST = 62 LN = 10 TILES = {35: '⬜', 46: '.', 2: '⭐'} def __init__(self, m): self.__m = m self.__cpu = Machine(self.__m[:]) self.__history = [] self.__bounds = (0, 0, 0, 0) self.__pos = (0, 0) self.__bot_pos = (0, 0, ord('^')) self.__moves = [] self.__map = OrderedDict() self.__alignment_parameters = 0 self.__star_dust = 0 if verbose: self.__cpu.toggle_verbose() def run(self, display=True): moves = self.traverse() m = self.__m[:] m[0] = 2 cpu = self.__cpu = Machine(m) subs = self.subroutines(moves) if verbose: cpu.toggle_verbose() ''' 1. MAIN ''' for instr in map(ord, subs['MAIN']): cpu.run(instr) cpu.run(10) # return self.video(display) ''' 2. SUBROUTINES ''' for i in range(3): key = ('A', 'B', 'C')[i] for instr in map(ord, subs[key]): cpu.run(instr) self.video(display) cpu.run(10) # return self.video(display) ''' 3. VIDEO FEED: YES ''' cpu.run(ord('y' if display else 'n')) cpu.run(10) # return self.video(display) return self.video(display) def ready(self): return self.__cpu.halted() def subroutines(self, moves): routines = {} ''' Initially solved manually not proud but this was DAY THREE 😳 FUNC_MAIN = 'A,A,B,B,C,B,C,B,C,A' FUNC_A = 'L,10,L,10,R,6' FUNC_B = 'R,12,L,12,L,12' FUNC_C = 'L,6,L,10,R,12,R,12' routines[0] = FUNC_MAIN routines[A] = FUNC_A routines[B] = FUNC_B routines[C] = FUNC_C UPDATE: DAY AFTER ------------------------------------------ build list of candidate functions (collect repeated sequences) 1. begin from [i:i+2] 2. count occurences of sub in rest of string 3. if zero, increment i 4. if repetitions found, store count, increment j and try [i:j] until j >= j + (len(moves) - j) // 2 5. increment i and goto step 1 ''' path, n, seen = ','.join(moves), '', set() counts, l, i, j = {}, len(moves), 0, 2 while i < l: n = ','.join(moves[i:j]) if n not in seen: seen.add(n) c = ','.join(moves[j:]).count(n) if c > 1: counts[n] = c if j >= j + (l - j) // 2 or c == 0: i += 1 j = i + 2 else: j += 1 print('PATH:', path, '\n') # for p, c in counts.items(): # print('PATH:', p, '==>', c) ''' do a little checksum using combinations of candidate functions to narrow down list and group candidate functions into trios. ''' h = path.replace(',', '') # for checksum trios = set() total_count = 0 for _abc in combinations(counts.keys(), 3): total_count += 1 a, b, c = map(lambda s: s.replace(',', ''), _abc) ''' check if total length of the overall path matches combined steps of candidate functions (minus commas). ''' checksum = (h.count(a) * len(a)) + h.count(b) * len(b) + h.count(c) * len(c) if len(h) == checksum: trios.add(_abc) print( f'ABC CANDIDATES: {len(trios)} trios found out of {total_count} combinations.' ) ''' simulate traversal with each set of functions and stop when we find a function trio that takes us all the way to the end of the maze. ''' abc, fn_queue, queue = {}, [], '' while trios and queue != path: func_set = trios.pop() abc, queue, fn_queue = dict(zip('ABC', func_set)), '', [] while queue != path: match = False for i, fn in enumerate(func_set): new_queue = f'{queue},{fn}' if queue != '' else fn if path.startswith(new_queue): queue = new_queue fn_queue.append('ABC'[i]) match = True break if not match: break print('ABC DEFS:', abc, fn_queue) ''' DONE !! return subroutines ''' routines['MAIN'] = ','.join(fn_queue) for k, f in abc.items(): routines[k] = f return routines def map(self, display=True): cpu = self.__cpu cpu.run() self.video(display) m, acc, inter, corners = self.__map, 0, 0, [] for (x, y), v in m.items(): if v != Bot.SCAFFOLD: continue n = neighbours(m, (x, y), lambda np, v: v == Bot.SCAFFOLD) if len(n) == 4: acc += (x - 1) * y m[(x, y)] = 79 inter += 1 elif len(n) == 2 and corner((x, y), n): m[(x, y)] = 64 corners.append((x, y)) self.__alignment_parameters = acc return self.__map, self.__bot_pos def get_map(self): return self.__map def video(self, show=True): cpu = self.__cpu pos = self.__pos = (0, 0) while cpu.has_output(): o = cpu.output() if o == self.LN: x, y = pos if x == 0: pos = (x, 0) else: pos = (0, y + 1) self.update_bounds(pos) elif o > 127: self.__star_dust = o break else: '''capture robot position and direction''' if o in {self.NORTH, self.SOUTH, self.WEST, self.EAST}: if self.__bot_pos[0] == 0: self.__bot_pos = (*pos, o) self.__map[pos] = o x, y = pos pos = (x + 1, y) self.update_bounds(pos) pos = (0, 0) self.update_bounds(pos) if show: display(self.__map, self.get_bounds()) return self.__map, self.__bot_pos def traverse(self): m = self.__map.copy() prev = None x, y, facing = self.__bot_pos pos = tuple([x, y]) d, facing = self.turn(facing, pos, None) moves = [] while True: s, pos, prev = self.steps(pos, facing) # print('DEBUG', s, pos, chr(m[pos]), prev, chr(d), chr(facing)) if s == 0: break moves.append('{},{}'.format(chr(d), s)) if pos not in m or m.get(pos, None) != self.CORNER: # print('DEBUG HALT NOT A CORNER', pos, s) break d, facing = self.turn(facing, pos, prev) self.__moves = moves self.__bot_pos = (*pos, facing) return moves def steps(self, pos, facing): m, steps, prev = self.__map, 0, None p = tuple(pos) p = self.move(p, facing) while p in m and m.get(p) not in {self.CORNER, self.SPACE}: steps += 1 prev = tuple(pos) pos = p p = self.move(p, facing) if m.get(p, None) == self.CORNER: steps += 1 prev = tuple(pos) pos = p return steps, pos, prev def turn(self, facing, pos, prev): ''' turn() is doing 2 things: 1. figure out which side the neighbouring scaffold node is on 2. determine where robot is facing after turn ''' n = neighbours(self.__map, pos, lambda np, v: v == Bot.SCAFFOLD and np != prev)[0] if n[0] != pos[0]: # N < -- > S f = ord('<') if n[0] < pos[0] else ord('>') else: # W < -- > E f = ord('^') if n[1] < pos[1] else ord('v') if '{} {}'.format(chr(facing), chr(f)) in {'^ <', '< v', 'v >', '> ^'}: return (ord('L'), f) elif '{} {}'.format(chr(facing), chr(f)) in {'^ >', '> v', 'v <', '< ^'}: return (ord('R'), f) def move(self, pos, facing): dnswe = { self.NORTH: (0, -1), self.SOUTH: (0, 1), self.WEST: (-1, 0), self.EAST: (1, 0) } return tuple(a + b for a, b in zip(pos, dnswe[facing])) def get_bounds(self): _, _, maxx, maxy = self.__bounds return (maxx, maxy) def update_bounds(self, xy): mxy = list(self.__bounds) self.__bounds = tuple([min(p1, p2) for p1, p2 in zip(mxy[:2], xy)] + [max(p1, p2) for p1, p2 in zip(mxy[2:], xy)]) def get_stardust_count(self): return self.__star_dust def get_alignment_parameters(self): return self.__alignment_parameters
class Robot: def __init__(self, m): self.__cpu = Machine(m) ''' {current, min, max} ''' self.__x, self.__y = (0, 0, 0), (0, 0, 0) self.__panels = defaultdict(lambda: '.') self.__color = 0 ''' < : 0 ^ : 1 > : 2 v : 3 ''' self.__direction = 1 if verbose: self.__cpu.toggle_verbose() def start(self, start_color): if start_color: self.__panels[(0, 0)] = '#' while not self.__cpu.halted(): self.__cpu.run(1 if self.__panels[(self.__x[0], self.__y[0])] == '#' else 0) self.paint(self.__cpu.output()) self.turn(self.__cpu.output()) self.move() def display(self): _, minx, maxx = self.__x _, miny, maxy = self.__y grid = [[' ']*(maxx+1) for _ in range(maxy+1)] for (x, y), v in self.__panels.items(): if x < 0 or y < 0: break if v == '#': grid[y][x] = v for l in grid: print(''.join(l)) print('\n', '--------------------------------------\n', 'COUNT', len(self.__panels), '\n\n') def turn(self, lr): if verbose: print('FROM ', ['<', '^', '>', 'v'][self.__direction]*4, lr) if lr == 0: self.left() else: self.right() if verbose: print('TURNED ', ['<', '^', '>', 'v'][self.__direction]*4) def left(self): self.__direction += (3 if self.__direction == 0 else -1) return self.__direction def right(self): self.__direction += (-3 if self.__direction == 3 else 1) return self.__direction def move(self): x, minx, maxx = self.__x y, miny, maxy = self.__y # <- left if self.__direction == 0: if verbose: print('MOV <----', x) x -= 1 minx = min(minx, x) if verbose: print('x:', x) # -> right elif self.__direction == 2: if verbose: print('MOV ---->', x) x += 1 maxx = max(maxx, x) if verbose: print('x:', x) # -> up elif self.__direction == 1: if verbose: print('MOV ^^^^^^', y) y -= 1 miny = min(miny, y) if verbose: print('y:', y) # -> down elif self.__direction == 3: if verbose: print('MOV vvvvvv', y) y += 1 maxy = max(maxy, y) if verbose: print('y:', y) self.__x, self.__y = (x, minx, maxx), (y, miny, maxy) def paint(self, color = None): if color != None: self.__color = color self.__panels[(self.__x[0], self.__y[0])] = ('.' if self.__color == 0 else '#')