diff --git a/mosfet/jobs/cache_items.py b/mosfet/jobs/cache_items.py index 4b416da..6f4c44d 100644 --- a/mosfet/jobs/cache_items.py +++ b/mosfet/jobs/cache_items.py @@ -25,6 +25,7 @@ class CacheItemsStates: self.failed_transfer_slots = set() self.pending_transfer = None self.transferred_to_labeled_chest = False + self.bad_openings = set() self.is_labeled_container = False self.container_item_ids = None @@ -52,8 +53,23 @@ class CacheItemsStates: 'trapped chests:', self.trapped_chests) self.state = self.choose_trapped_chest + def _opening_in_bad_region(self, world, opening): + opening = tuple(opening) + if opening in self.bad_openings: + return True + if not self.bad_openings: + return False + nearest = min(self.bad_openings, + key=lambda bad: utils.phyp(opening, bad)) + if (world.path_to_place(opening, nearest) and + world.path_to_place(nearest, opening)): + self.bad_openings.add(opening) + return True + return False + def choose_trapped_chest(self): print('Choosing a labeled or trapped chest...') + self.bad_openings = set() w = self.g.world p = utils.pint(self.g.pos) @@ -70,15 +86,19 @@ class CacheItemsStates: self.container_item_ids = container['item_ids'] self.is_labeled_container = True for opening in w.find_cache_openings(self.area, p): - navpath = w.path_to_place(p, opening['position']) + opening_position = tuple(opening['position']) + if self._opening_in_bad_region(w, opening_position): + continue + navpath = w.path_to_place(p, opening_position) if navpath: print('Going to labeled chest', container['labels'], self.area) - self.opening = opening['position'] + self.opening = opening_position self.open_interaction = opening self.g.path = navpath self.state = self.going_to_trapped_chest return + self.bad_openings.add(opening_position) print('No path to labeled chest', self.area) if not len(self.trapped_chests): @@ -98,14 +118,18 @@ class CacheItemsStates: self.area = chest for opening in w.find_cache_openings(chest, p): - navpath = w.path_to_place(p, opening['position']) + opening_position = tuple(opening['position']) + if self._opening_in_bad_region(w, opening_position): + continue + navpath = w.path_to_place(p, opening_position) if navpath: print('navpath to trapped chest:', navpath) - self.opening = opening['position'] + self.opening = opening_position self.open_interaction = opening self.g.path = navpath self.state = self.going_to_trapped_chest return + self.bad_openings.add(opening_position) self.trapped_chests.pop(0) time.sleep(0.1) @@ -148,12 +172,17 @@ class CacheItemsStates: self.area = area openings = w.find_cache_openings(self.area, p) + self.bad_openings = set() for opening in openings: - navpath = w.path_to_place(p, opening['position']) + opening_position = tuple(opening['position']) + if self._opening_in_bad_region(w, opening_position): + continue + navpath = w.path_to_place(p, opening_position) if navpath: - self.opening = opening['position'] + self.opening = opening_position self.open_interaction = opening break + self.bad_openings.add(opening_position) else: # for print('Unable to get to cache area', self.area) self.bad_areas.append(self.area) diff --git a/tests/test_cache_items.py b/tests/test_cache_items.py index 873d598..2fb0b82 100644 --- a/tests/test_cache_items.py +++ b/tests/test_cache_items.py @@ -56,6 +56,61 @@ class CacheItemsTests(unittest.TestCase): state.choose_trapped_chest() self.assertIs(state.state.__func__, state.cleanup.__func__) + def test_mutually_reachable_opening_is_skipped_after_path_failure(self): + start = (0, 64, 0) + first = (4, 64, 0) + nearby = (4, 64, 1) + item_id = items.get_id('stone') + calls = [] + + class FakeWorld: + @staticmethod + def find_cache_openings(area, _center): + position = first if area == (10, 64, 0) else nearby + return [{'position': position}] + + def path_to_place(self, origin, target): + origin, target = tuple(origin), tuple(target) + calls.append((origin, target)) + if origin == start and target == first: + return None + if (origin, target) in ((nearby, first), (first, nearby)): + return [origin, target] + return None + + state = CacheItemsStates(Munch( + world=FakeWorld(), pos=start, + inv={9: Munch(present=True, item_id=item_id)})) + state.labeled_chests = [ + {'position': (10, 64, 0), 'positions': ((10, 64, 0),), + 'item_ids': {item_id}, 'labels': {'Stone'}}, + {'position': (11, 64, 0), 'positions': ((11, 64, 0),), + 'item_ids': {item_id}, 'labels': {'Stone'}}, + ] + state.trapped_chests = [] + state.choose_trapped_chest() + + self.assertEqual(calls, [ + (start, first), (nearby, first), (first, nearby), + ]) + self.assertIn(first, state.bad_openings) + self.assertIn(nearby, state.bad_openings) + + def test_one_way_reachability_does_not_blacklist_opening(self): + bad = (1, 64, 0) + candidate = (2, 64, 0) + + class FakeWorld: + @staticmethod + def path_to_place(start, target): + return [start, target] if tuple(start) == candidate else None + + state = CacheItemsStates(Munch()) + state.bad_openings = {bad} + + self.assertFalse(state._opening_in_bad_region(FakeWorld(), candidate)) + self.assertNotIn(candidate, state.bad_openings) + def test_cache_chest_placement_sneaks_to_avoid_opening_support_container(self): calls = []