Handle door navigation and turnarounds

This commit is contained in:
Tanner
2026-11-07 21:25:18 +00:00
parent be5fe260e3
commit c65705fef8
6 changed files with 497 additions and 10 deletions
+184
View File
@@ -0,0 +1,184 @@
import unittest
from munch import Munch
from mosfet.game import Game
from mosfet.info import blocks
from mosfet.path import Pathfinder
class FakeChunks:
def __init__(self, states):
self.states = states
def get_block_at(self, *position):
return self.states.get(tuple(position))
class DoorNavigationTests(unittest.TestCase):
@staticmethod
def door_state(name, half='lower', opened=False, powered=False):
data = blocks.MCD_BLOCKS[name]
return next(
state for state in range(data['minStateId'], data['maxStateId'] + 1)
if blocks.PROPS[state].get('half') == half and
blocks.PROPS[state].get('open') == str(opened).lower() and
blocks.PROPS[state].get('powered') == str(powered).lower())
def test_pathfinder_treats_openable_doors_as_passable_but_not_iron(self):
wooden = self.door_state('oak_door')
iron = self.door_state('iron_door')
opened_iron = self.door_state('iron_door', opened=True)
positions = {(1, 64, 0): wooden, (2, 64, 0): iron,
(3, 64, 0): opened_iron}
g = Munch(chunks=FakeChunks(positions), crawling=False,
unopenable_doors=set())
finder = Pathfinder(g)
self.assertTrue(finder.bair((1, 64, 0)))
self.assertFalse(finder.bair((2, 64, 0)))
self.assertTrue(finder.bair((3, 64, 0)))
g.unopenable_doors.add((1, 64, 0))
finder = Pathfinder(g)
self.assertFalse(finder.bair((1, 64, 0)))
def test_pressure_plate_opened_door_is_not_toggled_or_closed(self):
door = (1, 64, 0)
after = (2, 64, 0)
closed = self.door_state('oak_door', powered=True)
opened = self.door_state('oak_door', opened=True, powered=True)
states = {door: closed}
class FakeWorld:
@staticmethod
def block_at(*position):
return states.get(tuple(position))
toggles = []
game = Game.__new__(Game)
game.g = Munch(
world=FakeWorld(), pos=(0.5, 64, 0.5), yaw=-90.0,
path=[door, after], unopenable_doors=set(), look_at=None)
game.door_navigation = None
game._toggle_door = lambda position, state: toggles.append(
(position, state))
self.assertTrue(game.handle_path_doors())
states[door] = opened # the bot steps on a pressure plate
self.assertFalse(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'crossing')
self.assertFalse(game.door_navigation['opened_by_bot'])
game.g.pos = (2.5, 64, 0.5)
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_to_close')
game.door_navigation['deadline'] = 0
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_back')
self.assertEqual(toggles, [])
states[door] = closed
game.door_navigation['deadline'] = 0
self.assertFalse(game.handle_path_doors())
self.assertIsNone(game.door_navigation)
def test_powered_door_is_left_to_pressure_plate_after_crossing(self):
door = (1, 64, 0)
after = (2, 64, 0)
closed = self.door_state('oak_door', powered=False)
powered_open = self.door_state('oak_door', opened=True, powered=True)
released_closed = self.door_state('oak_door', powered=False)
states = {door: closed}
class FakeWorld:
@staticmethod
def block_at(*position):
return states.get(tuple(position))
toggles = []
game = Game.__new__(Game)
game.g = Munch(
world=FakeWorld(), pos=(0.5, 64, 0.5), yaw=-90.0,
path=[door, after], unopenable_doors=set(), look_at=None)
game.door_navigation = None
game._toggle_door = lambda position, state: toggles.append(
(position, state))
game.handle_path_doors()
game.door_navigation['deadline'] = 0
game.handle_path_doors()
states[door] = powered_open
game.handle_path_doors()
game.g.pos = (2.5, 64, 0.5)
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_to_close')
game.door_navigation['deadline'] = 0
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_back')
self.assertEqual(toggles, [(door, closed)])
states[door] = released_closed
game.door_navigation['deadline'] = 0
self.assertFalse(game.handle_path_doors())
self.assertIsNone(game.door_navigation)
def test_opens_door_before_crossing_and_closes_it_after(self):
door = (1, 64, 0)
after = (2, 64, 0)
closed = self.door_state('oak_door', powered=False)
opened = self.door_state('oak_door', opened=True, powered=False)
states = {door: closed}
class FakeWorld:
@staticmethod
def block_at(*position):
return states.get(tuple(position))
@staticmethod
def path_to_place(_start, _goal):
return None
toggles = []
game = Game.__new__(Game)
game.g = Munch(
world=FakeWorld(), pos=(0.5, 64, 0.5), yaw=-90.0,
path=[door, after], unopenable_doors=set(), look_at=None)
game.door_navigation = None
game._toggle_door = lambda position, state: toggles.append(
(position, state))
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'checking')
self.assertEqual(toggles, [])
game.door_navigation['deadline'] = 0
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'opening')
self.assertEqual(toggles, [(door, closed)])
states[door] = opened
self.assertFalse(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'crossing')
game.g.pos = (2.5, 64, 0.5)
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_to_close')
self.assertEqual(game.g.look_at, door)
self.assertEqual(len(toggles), 1)
game.door_navigation['deadline'] = 0
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'closing')
self.assertEqual(toggles[-1], (door, opened))
states[door] = closed
self.assertTrue(game.handle_path_doors())
self.assertEqual(game.door_navigation['phase'], 'turning_back')
self.assertEqual(game.g.look_at, (4, 64, 0))
game.door_navigation['deadline'] = 0
self.assertFalse(game.handle_path_doors())
self.assertIsNone(game.door_navigation)
if __name__ == '__main__':
unittest.main()
+22 -2
View File
@@ -2,8 +2,8 @@ import unittest
from munch import Munch
from mosfet.bot import look_direction_from_eye, update_pitch
from mosfet.vector import Point3D
from mosfet.bot import look_direction_from_eye, update_pitch, update_yaw
from mosfet.vector import Point3D, Vector3D
class LookDirection:
@@ -38,6 +38,26 @@ class TestFarmingPitch(unittest.TestCase):
update_pitch(state, directly_below)
self.assertEqual(state.pitch, 45.0)
def test_fixed_yaw_turn_returns_over_the_same_180_degree_arc(self):
state = Munch(yaw=-90.0)
no_look_direction = Vector3D((0.0, 0.0, 0.0))
for _ in range(6):
update_yaw(state, no_look_direction, fixed_target=-270.0)
self.assertEqual(state.yaw, -270.0)
for _ in range(6):
update_yaw(state, no_look_direction, fixed_target=-90.0)
self.assertEqual(state.yaw, -90.0)
def test_yaw_turns_across_wrap_without_unbounded_rotation(self):
state = Munch(yaw=630.0)
update_yaw(state, Vector3D((0, 0, -1)))
self.assertEqual(state.yaw, -60.0)
self.assertGreaterEqual(state.yaw, -180.0)
self.assertLess(state.yaw, 180.0)
def test_pitch_tracks_look_target_instead_of_a_fixed_farming_angle(self):
state = Munch(pitch=0.0)
direction_to_crop = LookDirection(45.0)