def calculate_distance_field(game: Game, source: Pt, target: Pt): # Calculate and return a distance field from the target, over visited cells only sentinel = object() front = deque([source, sentinel]) distfield = {source: 0} generation = 1 found = False while front: p = front.popleft() if p == sentinel: if found: break assert front generation += 1 front.append(sentinel) continue for d in Action.DIRS.keys(): p2 = p + d if (p2 not in distfield and game.in_bounds(p2) and game.is_passable(p2)): distfield[p2] = generation front.append(p2) if p2 == target: found = True return distfield
def solve(self, task: str) -> SolverResult: task = Task.parse(task) min_score = None min_actions = [] min_extra = {} turnlist = [[]] if self.turns: turnlist += [[Action.turnCCW()], [Action.turnCCW(), Action.turnCCW()], [Action.turnCW()]] # turnlist = [[Action.turnCW()]] for turns in turnlist: game = Game(GridTask(task)) for t in turns: game.apply_action(t) expected_score, actions, extra = solve(game, min_score) if expected_score is None: # solution has exceeded the best score and stopped continue if min_score is None or expected_score < min_score: min_score, min_actions, min_extra = expected_score, actions, extra return SolverResult(data=compose_actions(min_actions), expected_score=min_score, extra=min_extra)
def run_for_errors(actions: List[Action]): task_data = Path(utils.project_root() / 'tasks' / 'part-0-mock' / 'prob-2003.desc').read_text() task = GridTask(Task.parse(task_data)) game = Game(task) try: for a in actions: game.apply_action(a) except InvalidActionException: return else: assert False
def recur(game: Game, depth=0, first_action=None): # process attempted new state at the beginning of the recursive call nonlocal nodes_evaluated, best_score, best_action nodes_evaluated += 1 bot = game.bot success = game.is_wrapped(target) delay_penalty = -2 # game.turn and distfield[bot.pos] essentially count the same thing and negate each other, # but: # - for searches terminated due to max_depth turn is irrelevant because it's the same, so # they automatically optimize for lower distfield # - for successfully terminated searches we ignore distfield and turn becomes relevant # - don't reward success beyond the naturally better score because of the above penalties # - only compare best scores for completed searchesso that the first point holds (otherwise # node score can be worse than that of its children) score = -game.remaining_unwrapped score += game.turn * delay_penalty if not success: score += distfield[bot.pos] * delay_penalty # hack: collect boosters we want to use booster = [b for b in game.game.boosters if b.pos == bot.pos] if booster: [booster] = booster if booster.code in 'B': score += 1000 success = True key = (bot.pos, bot.int_direction) if key in scores and scores[key] > score: return scores[key] = score if success or depth >= max_depth: if first_action and score > best_score: best_action = first_action best_score = score return for dp, action in Action.DIRS.items(): # never move backward, also means we never run into walls if distfield.get(bot.pos + dp, 99999) <= distfield[bot.pos]: recur(game.apply_action(action), depth + 1, first_action if first_action else action) recur(game.apply_action(Action.turnCW()), depth + 1, first_action if first_action else Action.turnCW()) recur(game.apply_action(Action.turnCCW()), depth + 1, first_action if first_action else Action.turnCCW())
def fill_stats(self, game: Game): self.children_cnt = 1 self.wrapped_children_cnt = game.is_wrapped(self.pos) for c in self.children: cnt, wcnt = c.fill_stats(game) self.children_cnt += cnt self.wrapped_children_cnt += wcnt return self.children_cnt, self.wrapped_children_cnt
def calculate_spanning_tree(game: Game, pos: Pt): front = deque([pos]) visited = {pos: Treenode(pos, None)} found = None while front: p = front.popleft() pnode = visited[p] for d in Action.DIRS.keys(): p2 = p + d if (p2 not in visited and game.in_bounds(p2) and game.is_passable(p2)): visited[p2] = Treenode(p2, pnode) pnode.children.append(visited[p2]) front.append(p2) visited[pos].fill_stats(game) return visited
def main(): path = os.path.join(utils.get_data_dir(), 'qualifier/problem_4.json') with open(path) as fin: data = json.load(fin) m = re.match('.*/problem_(\\d+)\\.json', path) assert m data['problemId'] = int(m.group(1)) game = Game(data, data['sourceSeeds'][0]) ui = Gui(game, buttons=Gui.DEFAULT_BUTTONS_INTERACTIVE) while True: cmd = ui.wait_for_action() log.debug('{!r}'.format(cmd)) if cmd is None: log.debug('done') break game.execute_char(CHARS_BY_COMMAND[cmd][0]) ui.update(game)
def solve(self, task: str) -> SolverResult: task = Task.parse(task) min_score = None # min_actions = [] # min_extra = {} turnlist = { 'Eastborne': [], 'Nordish': [Action.turnCCW()], 'Westerling': [Action.turnCCW(), Action.turnCCW()], 'Southerner': [Action.turnCW()] } params = {} params['boostlist'] = 'B' if self.drill: params['boostlist'] += 'L' if self.teleport: params['boostlist'] += 'R' params['attach func'] = manip_configs[self.manipconf] for direction in turnlist: logger.info(f'{direction} started working') params['best score'] = min_score game = Game(GridTask(task)) for t in turnlist[direction]: game.apply_action(t) expected_score, actions, extra = solve(game, params) logger.info(f'{direction} finished with {expected_score} score') if expected_score is None: # solution has exceeded the best score and stopped continue if min_score is None or expected_score < min_score: min_score, min_actions, min_extra = expected_score, actions, extra return SolverResult(data=compose_actions(min_actions), expected_score=min_score, extra=min_extra)
def attach_lined(game: Game, extras: dict, botindex=0): bot = game.bots[botindex] d = None for d in Action.DIRS.keys(): if d in bot.manipulator: break assert d is not None ccw = d.rotated_ccw() i = 0 while True: candidate = d + ccw * i if candidate not in bot.manipulator: break candidate = d - ccw * i if candidate not in bot.manipulator: break i += 1 game.apply_action(Action.attach(candidate.x, candidate.y))
def solve(task: Task, max_depth) -> Tuple[int, List[List[Action]], dict]: task = GridTask(task) game = Game(task) st_root = game.bots[0].pos spanning_tree = calculate_spanning_tree(game, st_root) target = None while not game.finished(): while game.inventory['B'] > 0: logger.info('attach extension') action = attach_lined(game) wrap_updates = game.apply_action(action) for p in wrap_updates: assert game.is_wrapped(p) spanning_tree[p].wrap() bot = game.bots[0] if target is None: target = spanning_tree[bot.pos].get_next_unwrapped().pos distfield = calculate_distance_field(game, target, bot.pos) #print_distfield(game, distfield) action = get_action(game, max_depth, distfield, target) assert action wrap_updates = game.apply_action(action) for p in wrap_updates: assert game.is_wrapped(p) spanning_tree[p].wrap() if game.is_wrapped(target): target = None score = game.finished() logger.info(game.inventory) extra = dict(final_manipulators=len(game.bots[0].manipulator)) return score, game.get_actions(), extra
def solve(task: Task, max_depth) -> Tuple[int, List[List[Action]], dict]: task = GridTask(task) game = Game(task) while not game.finished(): while game.inventory['B'] > 0: logger.info('attach extension') game.apply_action(Action.attach(1, len(game.bots[0].manipulator) - 2)) action = get_action(game, max_depth) game.apply_action(action) score = game.finished() logger.info(game.inventory) extra = dict(final_manipulators = len(game.bots[0].manipulator)) return score, game.get_actions(), extra
def attach_manip(game: Game, bot_area, botindex=0): bot = game.bots[botindex] d = None for d in Action.DIRS.keys(): if d in bot.manipulator: break assert d is not None ccw = d.rotated_ccw() i = 0 while True: candidate = d + ccw * i if candidate not in bot.manipulator: break candidate = d - ccw * i if candidate not in bot.manipulator: break i += 1 for d in Action.DIRS.keys(): if candidate + d not in bot_area: bot_area.append(candidate + d) game.apply_action(Action.attach(candidate.x, candidate.y))
def main(): s = Path(utils.project_root() / 'tasks' / 'part-1-initial' / 'prob-145.desc').read_text() solver = BoostySolver([]) t = time() sol = solver.solve(s) print(f'time elapsed: {time() - t:.4}') print(sol) task = Task.parse(s) score, sol, _ = solve(Game(GridTask(task))) print("score:", score) sol = compose_actions(sol) print(sol) print(len(sol), 'time units') sol_path = Path(utils.project_root() / 'outputs' / 'example-01-boosty.sol') sol_path.write_text(sol) print('result saved to', sol_path)
def run_one_bot_game(actions: List[Action]): task_data = Path(utils.project_root() / 'tasks' / 'part-0-mock' / 'prob-2003.desc').read_text() task = GridTask(Task.parse(task_data)) game = Game(task) for a in actions: game.apply_action(a) solution = compose_actions(game.get_actions()) expected_score = game.finished() er = validate.run(task_data, solution) assert er.time is not None assert er.time == expected_score
def run_cloned_game(actions: List[List[Action]]): task_data = Path(utils.project_root() / 'tasks' / 'part-0-mock' / 'prob-2003.desc').read_text() task = GridTask(Task.parse(task_data)) game = Game(task) indices = [0] * len(actions) current = 0 while not game.finished(): if current == 0: botcount = len(game.bots) i = indices[current] game.apply_action(actions[current][i], current) indices[current] += 1 current = (current + 1) % botcount solution = compose_actions(game.get_actions()) expected_score = game.finished() er = validate.run(task_data, solution) assert er.time is not None assert er.time == expected_score
def draw_initial(self, game: Game): stdscr, pad = self.stdscr, self.pad def char(p, char, fgcolor): offset = Pt(1, 1) if game.in_bounds(p) and game.is_wrapped(p): bg_color = curses.COLOR_BLUE else: bg_color = curses.COLOR_BLACK p = p + offset pad.addstr(self.height - p.y + 1, p.x * 2, char + ' ', colormapping[fgcolor, bg_color]) #borders #for some reason the left border is not shown for x in range(game.width + 2): char(Pt(x - 1, -1), 'H', FgColors.Dungeon) char(Pt(x - 1, game.height), 'H', FgColors.Dungeon) for y in range(game.height + 2): char(Pt(-1, y - 1), 'H', FgColors.Dungeon) char(Pt(game.width, y - 1), 'H', FgColors.Dungeon) for p, c in game.enumerate_grid(): char(p, c, FgColors.Dungeon)
def solve(game: Game, params: dict) -> Tuple[Optional[int], List[List[Action]], dict]: cnt = 0 params['booster_count'] = sum(1 for b in game.boosters if b.code == 'B') map_center = Pt(game.grid.width // 2, game.grid.height // 2) teleport_list = [] while not game.finished(): cnt += 1 if cnt % 1000 == 0: logging.info(f'{game.remaining_unwrapped} unwrapped') bot = game.bots[0] extensions = { b.pos for b in game.boosters if b.code in params['boostlist'] } prev = {bot.pos: None} frontier = [bot.pos] # searching target depth = 0 while frontier: best_rank = 0 dst = None for p in frontier: rank = 0 if p in extensions: rank += 5 for m in bot.manipulator: q = p + m if (game.in_bounds(q) and game.grid[q] == '.' and not game.is_wrapped(q) and visible(game.grid, q, p)): rank += 1 if rank > best_rank: best_rank = rank dst = p if dst is not None: break new_frontier = [] if depth == 0 and 'R' in params['boostlist']: new_frontier = teleport_list[:] for p in new_frontier: prev[p] = bot.pos for p in frontier: for d in Action.DIRS.keys(): p2 = p + d if (p2 not in prev and game.in_bounds(p2) and (game.grid[p2] == '.' or bot.drill_timer - depth > 0)): prev[p2] = p new_frontier.append(p2) frontier = new_frontier depth += 1 assert dst is not None assert dst != bot.pos # teleport if 'R' in params['boostlist'] and game.inventory['R'] > 0: if map_center.manhattan_dist( bot.pos) < map_center.manhattan_dist(dst): game.apply_action(Action.reset()) logger.info('reset teleport') teleport_list.append(bot.pos) # gathering path path = [] p = dst while p != bot.pos: d = p - prev[p] if d not in Action.DIRS.keys(): assert 'R' in params['boostlist'] and p in teleport_list path.append(Action.teleport(p.x, p.y)) logger.info('teleport away') else: path.append(Action.DIRS[d]) p = prev[p] assert p is not None path.reverse() assert depth == len(path), (depth, len(path)) for a in path: game.apply_action(a) if params['best score'] is not None and game.turn >= params[ 'best score']: return None, [], {} # boosters if game.inventory['B'] > 0: logger.info('attach extension') params['attach func'](game, params, 0) if 'L' in params['boostlist'] and game.inventory['L'] > 0: logger.info('use drill') game.apply_action(Action.drill()) score = game.finished() logger.info(game.inventory) extra = dict(final_manipulators=len(game.bots[0].manipulator)) return score, game.get_actions(), extra
def draw(self, game: Game, extra_status=''): stdscr, pad = self.stdscr, self.pad def char(p, char, fgcolor): offset = Pt(1, 1) if game.in_bounds(p) and game.is_wrapped(p): bg_color = curses.COLOR_BLUE else: bg_color = curses.COLOR_BLACK p = p + offset pad.addstr(self.height - p.y + 1, p.x * 2, char + ' ', colormapping[fgcolor, bg_color]) bot = game.bots[(self.current - 1) % len(game.bots)] area = max( bot.pos.manhattan_dist(bot.pos + m) for m in bot.manipulator) for y in range(bot.pos.y - area, bot.pos.y + area + 1): for x in range(bot.pos.x - area, bot.pos.x + area + 1): p = Pt(x, y) if not game.in_bounds(p): continue char(p, game.grid[p], FgColors.Dungeon) for b in game.boosters: char(b.pos, b.code, FgColors.Booster) for t in game.teleport_spots: char(t, '+', FgColors.Spot) for c in game.clone_spawn: char(c, 'X', FgColors.Spot) for b in game.bots: for m in bot.manipulator: w = b.pos + m if game.in_bounds(w) and geom.visible(game.grid, b.pos, w): char(m, '*', FgColors.Manipulator) for b in game.bots: char(b.pos, '@', FgColors.InactivePlayer) char(game.bots[self.current].pos, '@', FgColors.Player) offset = self.screen_offset(game.size(), bot.pos) pad.refresh(offset.y, offset.x, 0, 0, curses.LINES - 2, curses.COLS - 1) if self.last_error: status_line = self.last_error.ljust(curses.COLS)[:curses.COLS - 1] self.last_error = '' stdscr.addstr( curses.LINES - 1, 0, status_line, colormapping[curses.COLOR_YELLOW | BRIGHT, curses.COLOR_RED]) else: status_line = f'turn={game.turn} ' + \ f'pos=({bot.pos.x}, {bot.pos.y}) ' + \ f'unwrapped={game.remaining_unwrapped} ' status_line += ' '.join(f'{b}={game.inventory[b]}' for b in Booster.PICKABLE) if game.bots[self.current].wheels_timer: status_line += f' WHEELS({game.bots[self.current].wheels_timer})' if game.bots[self.current].drill_timer: status_line += f' DRILL({game.bots[self.current].drill_timer})' if extra_status: status_line += ' ' status_line += extra_status # LMAO: "It looks like simply writing the last character of a window is impossible with curses, for historical reasons." status_line = status_line.ljust(curses.COLS)[:curses.COLS - 1] stdscr.addstr( curses.LINES - 1, 0, status_line, colormapping[curses.COLOR_YELLOW | BRIGHT, curses.COLOR_BLUE]) stdscr.refresh()
def interactive(task_number): task_data = utils.get_problem_raw(task_number) use_db = task_number <= 999 if use_db: conn = db.get_conn() cur = conn.cursor() cur.execute( ''' SELECT id, data FROM tasks WHERE name = %s ''', [f'prob-{task_number:03d}']) [task_id, task_data_db] = cur.fetchone() task_data_db = zlib.decompress(task_data_db).decode() assert task_data_db == task_data task = GridTask(Task.parse(task_data)) game = Game(task) score = None with contextlib.closing(Display(game)) as display: code, c = '', '' display.draw_initial(game) while not score: display.draw(game, f'lastchar = {code} {c!r}') code = display.stdscr.getch() action = None c = chr(code).upper() if c in '\x1B': break if c in Action.SIMPLE: action = Action.simple(c) # to perform complex action type it without spaces: B(1,-1) elif c in Action.PARAM: while c[-1] != ')': code = display.stdscr.getch() c = c + chr(code).upper() action = Action.parameterized(c) if display.current == 0: botcount = len(game.bots) if action: try: game.apply_action(action, display.current) except InvalidActionException as exc: display.draw_error(str(exc)) else: display.current = (display.current + 1) % botcount score = game.finished() if score is not None: print(f'Score: {score}') result = validate_replay(task_data, score, game.get_actions()) print(result) if use_db: submit_replay(conn, task_id, result) else: mock_solutions = utils.project_root( ) / 'outputs' / 'mock_solutions' mock_solutions.mkdir(parents=True, exist_ok=True) with mock_solutions / f'prob-{task_number}.sol' as fin: fin.write_text(result.solution)
def solve(task: Task, turns: str, bestscore=None) -> Tuple[Optional[int], List[List[Action]], dict]: task = GridTask(task) game = Game(task) cnt = 0 for t in turns: game.apply_action(Action.simple(t)) while not game.finished(): cnt += 1 if cnt % 1000 == 0: logging.info(f'{game.remaining_unwrapped} unwrapped') extensions = {b.pos for b in game.boosters if b.code == 'B'} prev = {game.bots[0].pos: None} frontier = [game.bots[0].pos] while frontier: best_rank = 0 dst = None for p in frontier: rank = 0 if p in extensions: rank += 5 rank += len( manipulators_will_wrap(game.grid, game._wrapped, p, game.bots[0].manipulator)) if rank > best_rank: best_rank = rank dst = p if dst is not None: break new_frontier = [] for p in frontier: for d in Action.DIRS.keys(): p2 = p + d if (p2 not in prev and game.in_bounds(p2) and game.grid[p2] == '.'): prev[p2] = p new_frontier.append(p2) frontier = new_frontier assert dst is not None assert dst != game.bots[0].pos path = [] p = dst while p != game.bots[0].pos: d = p - prev[p] path.append(Action.DIRS[d]) p = prev[p] assert p is not None path.reverse() for a in path: game.apply_action(a) if bestscore is not None and game.turn >= bestscore: return None, [], {} if game.inventory['B'] > 0: logger.info('attach extension') attach_to = Pt(1, len(game.bots[0].manipulator) - 2) for t in turns: attach_to = turn_pt(attach_to, t) game.apply_action(Action.attach(attach_to.x, attach_to.y)) score = game.finished() logger.info(game.inventory) extra = dict(final_manipulators=len(game.bots[0].manipulator)) return score, game.get_actions(), extra
def test_wheel_timer(): task_data = Path(utils.project_root() / 'tasks' / 'part-0-mock' / 'wheels.desc').read_text() task = GridTask(Task.parse(task_data)) actionlist = ('QQDD' + 'F' + 'ZZZZZZZZZZZZZZZZZZZZ' + 'F' + 'ZZZZZZZZZZZZZZZZZZZZ' + 'ZZZZZZZZZ' + 'ZZZZZZZZZZZZZZZZZZZZ' + 'ZZZZZZZZZZZZZZZZZZZZ' + 'ZZZZZZZZZZ' + 'D') game = Game(task) for a in actionlist: game.apply_action(Action.simple(a)) solution = compose_actions(game.get_actions()) expected_score = game.finished() assert expected_score is None game = Game(task) for a in actionlist: game.apply_action(Action.simple(a)) game.apply_action(Action.WSAD('D')) solution = compose_actions(game.get_actions()) expected_score = game.finished() er = validate.run(task_data, solution) assert er.time is not None assert er.time == expected_score
def attach_squared(game: Game, extras: dict, botindex=0): bot = game.bots[botindex] d = None for d in Action.DIRS.keys(): if d in bot.manipulator: break assert d is not None ccw = d.rotated_ccw() shoulder = extras['booster_count'] // 4 candidate = None if candidate is None: i = 0 while i <= shoulder / 2: candidate = d + ccw * i if candidate not in bot.manipulator: break candidate = d - ccw * i if candidate not in bot.manipulator: break i += 1 else: candidate = None if candidate is None: left = d + ccw * (shoulder // 2) right = d - ccw * (shoulder // 2) i = 1 while i < shoulder: candidate = left + d * i if candidate not in bot.manipulator: break candidate = right + d * i if candidate not in bot.manipulator: break i += 1 else: candidate = None if candidate is None: left = d * shoulder + ccw * (shoulder // 2) right = d * shoulder - ccw * (shoulder // 2) i = 1 while i < shoulder / 2: candidate = left - ccw * i if candidate not in bot.manipulator: break candidate = right + ccw * i if candidate not in bot.manipulator: break i += 1 else: candidate = None if candidate is None: i = 1 while d * (-i) in bot.manipulator: i += 1 candidate = d * (-i) game.apply_action(Action.attach(candidate.x, candidate.y))
c, b = it c = c.lower() cc = ord(c) - ord("n") # print(cc, b) print(c, cc % 6, COMMAND_BY_CHAR[c], b) print_seq() print() json = { "width": 20, "height": 20, "filled": [], "sourceLength": 1, "units": [{"pivot": {"x": 0, "y": 1}, "members": [{"x": 0, "y": 0}]}], } marks = [] g = Game(json, 0) ui = Gui(g) for it in s.split(): c, b = it print(it, COMMAND_BY_CHAR[c.lower()]) if b == "1": marks.append(next(iter(g.get_current_figure_cells()))) ui.update(g, marks) action = ui.wait_for_action() if action is None: break g.execute_char(c)
def solve(self, task: str) -> SolverResult: task = Task.parse(task) task = GridTask(task) game = Game(task) if not game.clone_spawn: return SolverResult(data=Pass(), expected_score=None, extra=dict(reason='no clone spawns')) if not game.inventory['C'] and not game.map_contains_booster('C'): return SolverResult(data=Pass(), expected_score=None, extra=dict(reason='no clone boosters')) cnt = 0 while not game.finished(): cnt += 1 if cnt % 200 == 0: logging.info( f'{len(game.bots)} bots; {game.remaining_unwrapped} unwrapped' ) used_bots = [False for bot in game.bots] actions = [None for bot in game.bots] orig_bots = game.bots[:] if game.clone_spawn and game.inventory['C']: logging.info('need spawn') df = DistanceField(game.grid) df.build(game.clone_spawn) dist = df.dist best_rank = 1e10 best_bot = None best_action = None for bot_index, bot in enumerate(orig_bots): if dist[bot.pos] == 0: continue for d, a in Action.DIRS.items(): p = bot.pos + d if not game.grid.in_bounds(p): continue rank = dist[p] if rank < best_rank: best_rank = rank best_bot = bot_index best_action = a #assert best_bot is not None #game.apply_action(best_action, best_bot) if best_bot is not None: actions[best_bot] = best_action used_bots[best_bot] = True boosters = [b.pos for b in game.boosters if b.code == 'C'] if not game.finished() and game.clone_spawn and not game.inventory[ 'C'] and boosters and not all(used_bots): df = DistanceField(game.grid) df.build(boosters) dist = df.dist best_rank = 1e10 best_bot = None best_action = None for bot_index, bot in enumerate(orig_bots): if used_bots[bot_index]: continue for d, a in Action.DIRS.items(): p = bot.pos + d if not game.grid.in_bounds(p): continue rank = dist[p] if rank < best_rank: best_rank = rank best_bot = bot_index best_action = a assert best_bot is not None actions[best_bot] = best_action used_bots[best_bot] = True if game.finished(): break df = DistanceField(game.grid) starts = list_unwrapped(game._wrapped) df.build(starts) dist = df.dist for bot_index, bot in enumerate(orig_bots): if game.finished(): break if used_bots[bot_index]: game.apply_action(actions[bot_index], bot_index) continue if game.inventory['C'] and bot.pos in game.clone_spawn: game.apply_action(Action.clone(), bot_index) logging.info('clone!!!') continue if game.inventory['B'] > 0: logger.info('attach extension') game.apply_action( Action.attach(1, len(game.bots[0].manipulator) - 2)) continue best_rank = 1e10 best_action = None for d, a in Action.DIRS.items(): p = bot.pos + d if not game.grid.in_bounds(p): continue rank = dist[p] for j, bot2 in enumerate(orig_bots): if j < bot_index: d = bot.pos.manhattan_dist(bot2.pos) rank += 10 * max(0, 30 - d) if rank < best_rank: best_rank = rank best_action = a assert best_action is not None game.apply_action(best_action, bot_index) expected_score = score = game.finished() extra = dict(final_bots=len(game.bots)) return SolverResult(data=compose_actions(game.get_actions()), expected_score=expected_score, extra=extra)
def solve(task: Task) -> Tuple[int, List[List[Action]], dict]: task = GridTask(task) game = Game(task) cnt = 0 while not game.finished(): cnt += 1 if cnt % 1000 == 0: logging.info(f'{game.remaining_unwrapped} unwrapped') prev = {game.bots[0].pos: None} frontier = [game.bots[0].pos] while frontier: best_rank = 0 dst = None for p in frontier: rank = len( manipulators_will_wrap(game.grid, game._wrapped, p, game.bots[0].manipulator)) if rank > best_rank: best_rank = rank dst = p if dst is not None: break new_frontier = [] for p in frontier: for d in Action.DIRS.keys(): p2 = p + d if (p2 not in prev and game.in_bounds(p2) and game.grid[p2] == '.'): prev[p2] = p new_frontier.append(p2) frontier = new_frontier assert dst is not None assert dst != game.bots[0].pos path = [] p = dst while p != game.bots[0].pos: d = p - prev[p] path.append(Action.DIRS[d]) p = prev[p] assert p is not None path.reverse() for a in path: game.apply_action(a) if game.inventory['B'] > 0: logger.info('attach extension') game.apply_action( Action.attach(1, len(game.bots[0].manipulator) - 2)) score = game.finished() logger.info(game.inventory) extra = dict(final_manipulators=len(game.bots[0].manipulator)) return score, game.get_actions(), extra