예제 #1
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))
예제 #2
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()