예제 #1
0
    def _obey_scrolling_order(self, motion, the_plot):
        """Look for a scrolling order in the `Plot` object and apply if present.

    Examines the `Plot` object to see if any entity preceding this `MazeWalker`
    in the update order has issued a scrolling order (see
    `protocols/scrolling.py`). If so, apply the additive inverse of the motion
    in the scrolling order so as to remain "stationary" with respect to the
    moving environment. (We expect that egocentric `MazeWalker`s will apply the
    motion itself soon after we return so that they remain stationary with
    respect to the board.)

    (Egocentric `MazeWalker`s only.) Makes certain that this `MazeWalker` is
    known to scrolling protocol participants as an egocentric entity, and
    verifies that any non-None scrolling order is identical to the motion that
    the `MazeWalker` is expected to perform.

    No effort is made to verify that the world scrolling around an egocentric
    `MazeWalker` will wind up putting the `MazeWalker` in an impossible
    position.

    Args:
      motion: the motion that this `MazeWalker` will execute during this game
          iteration (see docstring for `_move`).
      the_plot: this pycolab game's `Plot` object.

    Raises:
      RuntimeError: this `MazeWalker` is egocentric, and the current non-None
          scrolling order and the `MazeWalker`s motion have no components in
          common.
    """
        if self._egocentric_scroller:
            scrolling.participate_as_egocentric(self, the_plot,
                                                self._scrolling_group)

        order = scrolling.get_order(self, the_plot, self._scrolling_group)
        if order is not None:
            self._raw_move((-order[0], -order[1]))
            if (self._egocentric_scroller and order[0] != motion[0]
                    and order[1] != motion[1]):
                raise RuntimeError(
                    'An egocentric MazeWalker corresponding to {} received a scroll '
                    'order {} that has no component in common with the motion {}, '
                    'which the MazeWalker was to carry out during the same game '
                    'iteration'.format(repr(self.character), order, motion))
예제 #2
0
    def testProtocol(self):
        """Verify correct behaviour of the scrolling protocol helpers."""

        # In this test, we pretend to be an Engine. Here are components we need:
        the_plot = plot.Plot()
        sprite = tt.TestSprite(corner=things.Sprite.Position(11, 10),
                               position=things.Sprite.Position(5, 5),
                               character='P')
        drape = tt.TestDrape(curtain=np.zeros((10, 10), dtype=np.bool),
                             character='X')

        # Registration of scrolling participants, in the default scrolling group.
        scrolling.participate_as_egocentric(sprite, the_plot)
        scrolling.participate_as_egocentric(drape, the_plot)

        self.assertEqual(
            {sprite, drape},
            set(scrolling.egocentric_participants(sprite, the_plot)))

        # Registration of scrolling participants, in a custom scrolling group.
        the_plot = plot.Plot()  # Use a fresh Plot object.
        scrolling.participate_as_egocentric(sprite,
                                            the_plot,
                                            scrolling_group='!')
        scrolling.participate_as_egocentric(drape,
                                            the_plot,
                                            scrolling_group='!')

        self.assertEqual({sprite, drape},
                         set(
                             scrolling.egocentric_participants(
                                 sprite, the_plot, scrolling_group='!')))

        # Verify that no scrolling order exists for this scrolling group yet.
        self.assertIsNone(
            scrolling.get_order(sprite, the_plot, scrolling_group='!'))

        # Confirm an exception if an entity requests scrolling information for a
        # scrolling group it doesn't belong to.
        with self.assertRaisesRegexp(scrolling.Error, 'known to belong'):
            scrolling.get_order(sprite, the_plot, scrolling_group='cantaloupe')

        # Start over with a fresh Plot, and go back to the default scrolling group.
        the_plot = plot.Plot()
        scrolling.participate_as_egocentric(sprite, the_plot)
        scrolling.participate_as_egocentric(drape, the_plot)
        for i in range(101):  # Now let's advance the game a few steps.
            the_plot.frame = i  # There will be an error unless we go one at a time.

        # The Sprite and Drape advertise that they will be able to execute certain
        # sets of motions at the next game iteration.
        scrolling.permit(sprite, the_plot, motions=[(0, 0), (0, 1), (1, 0)])
        scrolling.permit(drape, the_plot, motions=[(0, 0), (1, 1)])

        # But that permission is for the next game iteration. In the current
        # iteration, no scrolling is permitted.
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(0, 0)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(0, 1)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(1, 0)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(1, 1)))

        # Now let's pretend it's the next game iteration and query again. Only the
        # one scrolling direction that's compatible with everyone is permissible.
        the_plot.frame = 101
        self.assertTrue(scrolling.is_possible(drape, the_plot, motion=(0, 0)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(0, 1)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(1, 0)))
        self.assertFalse(scrolling.is_possible(drape, the_plot, motion=(1, 1)))

        # The Sprite and Drape now advertise where they can scroll at iter. 102.
        scrolling.permit(sprite, the_plot, motions=[(0, 1), (1, 1)])
        scrolling.permit(drape, the_plot, motions=[(0, 0), (1, 0), (1, 1)])

        # We advance to frame 102 and try to have the drape order some scrolling.
        the_plot.frame = 102
        with self.assertRaisesRegexp(scrolling.Error, 'impossible'):
            scrolling.order(drape, the_plot, motion=(1, 0))  # Illegal for 'P'.
        scrolling.order(drape, the_plot, motion=(1, 1))

        # We confirm that the sprite can receive the order.
        self.assertEqual((1, 1), scrolling.get_order(sprite, the_plot))
예제 #3
0
    def _maybe_move(self, the_plot, motion):
        """Handle all aspects of single-row and/or single-column scrolling.

    Implements every aspect of deciding whether to scroll one step in any of the
    nine possible gridworld directions (includes staying put). This amounts to:

    1. Checking for scrolling orders from other entities (see
       `protocols/scrolling.py`), and, if present, applying them
       indiscriminately and returning.
    2. Determining whether this `Scrolly` should scroll (e.g. one of the
       sprites is encroaching on the board margins). If not, returning.
    3. Determining whether this `Scrolly` can scroll---that is, it's not
       constrained by egocentric entities, it wouldn't wind up scrolling the
       board off of the pattern, and so on. If not, returning.
    4. Issuing a scrolling order for the scroll, and updating the curtain from
       the pattern.

    Args:
      the_plot: this pycolab game's `Plot` object.
      motion: a 2-tuple indicating the number of rows and columns that the
          game board should move over the pattern if scrolling is both
          appropriate and possible. See class docstring for more details.

    Raises:
      scrolling.Error: another game entity has issued a scrolling order which
          does not have any component in common with `motion`.
    """
        # Save our last board location for pattern_position_prescroll.
        if self._last_maybe_move_frame < the_plot.frame:
            self._last_maybe_move_frame = the_plot.frame
            self._prescroll_northwest_corner = self._northwest_corner

        # First, was a scrolling order already issued by some other entity in this
        # scrolling group? If so, verify that it was the same motion as `motion` in
        # at least one dimension; if it was, apply it without doing any other
        # checking. Otherwise, die.
        scrolling_order = scrolling.get_order(self, the_plot,
                                              self._scrolling_group)
        if scrolling_order:
            if motion[0] != scrolling_order[0] and motion[
                    1] != scrolling_order[1]:
                raise scrolling.Error(
                    'The Scrolly corresponding to character {} received a fresh '
                    'scrolling order, {}, which has no component in common with the'
                    'current action-selected motion, which is {}.'.format(
                        self.character, scrolling_order, motion))
            self._northwest_corner = things.Sprite.Position(
                row=scrolling_order[0] + self._northwest_corner[0],
                col=scrolling_order[1] + self._northwest_corner[1])
            self._update_curtain()
            return

        # Short-circuit: nothing to do here if instructions say "stay put". But just
        # in case the whole pattern itself has been changed, we update the curtain.
        if motion == self._STAY:
            self._update_curtain()
            return

        # If here, the decision to scroll is ours! The rest of this (long) method
        # is divided into handling the two cases we need to consider:

        #############
        # Case 1: The user made scrolling mandatory (i.e. whenever possible).
        #############

        if not self._have_margins:
            # The main complication in this case has to do with circumstances where
            # only one component of the scrolling motions is possible. The user made
            # scrolling *mandatory*, so we want to scroll as much as we can.

            # The first thing we do is check for the legality of the motion itself.
            # Any scrolling order we issue is issued to accommodate the motion that
            # we expect egocentric entities to take. If they won't all do that motion,
            # there's no good reasson to accommodate it.
            if scrolling.is_possible(self, the_plot, motion,
                                     self._scrolling_group):
                # The motion is legal, so now we determine where on the pattern the
                # motion would move the northwest corner of the game board. From this,
                # determine whether and which components of the motion would scroll the
                # game board off of the pattern.
                possible_board_edge_north = self._northwest_corner[0] + motion[
                    0]
                possible_board_edge_west = self._northwest_corner[1] + motion[1]
                can_scroll_vertically = (0 <= possible_board_edge_north <=
                                         self._northwest_corner_limit[0])
                can_scroll_horizontally = (0 <= possible_board_edge_west <=
                                           self._northwest_corner_limit[1])

                # The scrolling order that we'll issue and execute will only contain
                # the components of the motion that will *not* scroll the game board
                # off of the pattern. This may mean that we issue a scrolling order that
                # was not expressly by other egocentric game entities. See the "loose
                # interpretation of 'legal' scrolling motions" discussion in the class
                # docstring and elsewhere.
                scrolling_order = (motion[0] if can_scroll_vertically else 0,
                                   motion[1] if can_scroll_horizontally else 0)
                self._northwest_corner = things.Sprite.Position(
                    row=scrolling_order[0] + self._northwest_corner[0],
                    col=scrolling_order[1] + self._northwest_corner[1])
                scrolling.order(self,
                                the_plot,
                                scrolling_order,
                                self._scrolling_group,
                                check_possible=False)

            # Whether we've scrolled or not, update the curtain just in case the whole
            # pattern itself has been changed.
            self._update_curtain()
            return

        #############
        # Case 2: We'll only consider scrolling if one of the visible egocentric
        # sprites will move from the centre region of the board into the margins.
        #############

        action_demands_vertical_scrolling = False
        action_demands_horizontal_scrolling = False

        egocentric_participants = scrolling.egocentric_participants(
            self, the_plot, self._scrolling_group)
        for entity in egocentric_participants:
            # Short-circuit if we already know we're scrolling both ways.
            if (action_demands_vertical_scrolling
                    and action_demands_horizontal_scrolling):
                break

            # See if this entity adds to the axes along which we should scroll because
            # it threatens to enter or move more deeply into a margin. Here we assume
            # that our motion argument is also a motion that this egocentric game
            # entity expects it should attempt to make, relative to the whole world
            # scenery. (This may mean no motion relative to the game board itself,
            # because it's an egocentric entity, after all.)
            if not isinstance(entity, things.Sprite): continue
            burrowing_vertical, burrowing_horizontal = (
                self._sprite_burrows_into_a_margin(entity, motion))
            action_demands_vertical_scrolling |= burrowing_vertical
            action_demands_horizontal_scrolling |= burrowing_horizontal

        # If we don't need to scroll, then we won't do it, and we can stop right
        # here! But just in case the whole pattern itself has been changed, we
        # update the curtain first.
        if not (action_demands_vertical_scrolling
                or action_demands_horizontal_scrolling):
            self._update_curtain()
            return

        # We know we should scroll, now to see what we'd actually do and where we'd
        # wind up (i.e. where the northwest corner of the board would lie on the
        # whole pattern) if we did it. Note here that we might be concocting a
        # scrolling order that may not have been expressly permitted by other
        # egocentric game entities. See the "loose interpretation of 'legal'
        # scrolling motions" discussion in the class docstring and elsewhere.
        scrolling_order = (
            motion[0] if action_demands_vertical_scrolling else 0,
            motion[1] if action_demands_horizontal_scrolling else 0)
        possible_northwest_corner = things.Sprite.Position(
            row=scrolling_order[0] + self._northwest_corner[0],
            col=scrolling_order[1] + self._northwest_corner[1])

        # We know we should scroll, now to see whether we can. If we can, do it,
        # and order all other participants in this scrolling group to do it as well.
        we_can_actually_scroll = (0 <= possible_northwest_corner[0] <=
                                  self._northwest_corner_limit[0])
        we_can_actually_scroll &= (0 <= possible_northwest_corner[1] <=
                                   self._northwest_corner_limit[1])
        # Note how this test checks for the legality of the *motion*, not the
        # scrolling order itself. This check also lies at the heart of the "loose
        # interpretation of 'legal' scrolling motions" described in the class
        # docstring and elsewhere. The scrolling order we derived just above is
        # meant to accommodate this motion on the part of all of the egocentric
        # entities, but if the motion itself is illegal for them, we won't scroll
        # anywhere at all.
        we_can_actually_scroll &= (scrolling.is_possible(
            self, the_plot, motion, self._scrolling_group))
        if we_can_actually_scroll:
            self._northwest_corner = possible_northwest_corner
            scrolling.order(self,
                            the_plot,
                            scrolling_order,
                            self._scrolling_group,
                            check_possible=False)

        # Whether we've scrolled or not, update the curtain just in case the whole
        # pattern itself has been changed.
        self._update_curtain()