Exemple #1
0
class _ParsedRoutePath(PRecord):
    """
    Parsed / derived components for matching a router path.
    """
    parts = pvector_field(unicode)
    params = pvector_field(unicode)
    constraints = pmap_field(unicode, unicode)
    priority = field(initial=0, type=int)
Exemple #2
0
class CutScene(Scene):
    screen = Screen.CUT_SCENE
    music = pyrsistent.field(type=str, mandatory=True)
    background = pyrsistent.field(type=str, mandatory=True)
    next_scene = pyrsistent.field(type=Scene, mandatory=True)
    reset_health = pyrsistent.field(type=bool, mandatory=True, initial=False)
    dialog = pyrsistent.pvector_field(DialogItem)
Exemple #3
0
class Character(pyrsistent.PClass):
    name = pyrsistent.field(type=str, mandatory=True)
    index = pyrsistent.field(type=int, mandatory=True, initial=1)
    health = pyrsistent.field(type=int, mandatory=True)
    max_health = pyrsistent.field(type=int, mandatory=True)
    size = pyrsistent.field(type=str, mandatory=True)
    actions = pyrsistent.pvector_field(Action)

    def damage(self, amount):
        if self.health - amount < 0:
            return self.set(health=0)
        else:
            return self.set(health=self.health - amount)

    def heal(self, amount):
        if self.health + amount > self.max_health:
            return self.set(health=self.max_health)
        else:
            return self.set(health=self.health + amount)

    @property
    def is_dead(self):
        return self.health <= 0

    @property
    def is_small(self):
        return self.size == 'Small'

    @property
    def is_medium(self):
        return self.size == 'Medium'

    @property
    def is_large(self):
        return self.size == 'Large'
Exemple #4
0
class Model(pyrsistent.PClass):
    scene = pyrsistent.field(type=Scene, mandatory=True)
    effects = pyrsistent.pvector_field(str)
    music = pyrsistent.field(type=str, mandatory=True)
    music_volume = pyrsistent.field(type=int, mandatory=True)
    effects_volume = pyrsistent.field(type=int, mandatory=True)
    characters = pyrsistent.pvector_field(Character)

    def clear_effects(self):
        return self.set(effects=pyrsistent.pvector([]))

    def play_effect(self, effect):
        return self.set(effects=self.effects.append(effect))

    def set_music(self, music):
        return self.set(music=music)
Exemple #5
0
class Battle(Scene):
    screen = Screen.BATTLE
    background = pyrsistent.field(type=str, mandatory=True)
    initiative = pyrsistent.field(type=int, mandatory=True)
    enemies = pyrsistent.pvector_field(Character)
    action = pyrsistent.field(type=Action, mandatory=True)
    next_scene = pyrsistent.field(type=Scene, mandatory=True)
Exemple #6
0
class Diff(PClass):
    """
    A ``_IDiffChange`` that is simply the serial application of other diff
    changes.

    This is the object that external modules get and use to apply diffs to
    objects.

    :ivar changes: A vector of ``_IDiffChange`` s that represent a diff between
        two objects.
    """

    changes = pvector_field(object)

    def apply(self, obj):
        proxy = _TransformProxy(original=obj)
        for c in self.changes:
            proxy = c.apply(proxy)
        try:
            return proxy.commit()
        except:
            # Imported here to avoid circular dependencies.
            from ._persistence import wire_encode
            DIFF_COMMIT_ERROR(
                target_object=wire_encode(obj),
                changes=wire_encode(self.changes),
            ).write()
            raise
Exemple #7
0
class Route(PRecord):
    """
    Verbose route description.
    """
    name = field(mandatory=True, type=unicode)
    path = field(mandatory=True, type=unicode)
    method = field(mandatory=True, type=bytes)
    interceptors = pvector_field(Interceptor)

    path_re = field(mandatory=True, type=regex_type)
    path_parts = pvector_field(unicode)
    path_params = field(mandatory=True)
    path_constraints = pvector_field(unicode)
    #query_constraints = XXX

    priority = field(initial=0, type=int)
    matcher = field(mandatory=True, type=types.FunctionType)
    class X(PClass):
        y = pvector_field(int)

        def __new__(cls, **kwargs):
            items = kwargs.get('y', None)
            if items is None:
                kwargs['y'] = ()
            return super(X, cls).__new__(cls, **kwargs)
Exemple #9
0
class GameState(PClass):
    location_name = field(str)
    world = pmap_field(str, Location)
    inventory = pvector_field(Thing)

    @property
    def location(self):
        return self.world[self.location_name]
class TradingRecord(PRecord):
    ''' TODO: eventually divide this record into two:
        1) information pulled from trading exchange
        2) information generated by hft server (ie. transaction decisions)
    '''
    name = field(type=str, mandatory=True)
    description = field(type=str, mandatory=True)
    initial_usd = field(type=float,
                        invariant=cannot_be_negative,
                        mandatory=True)
    usd = field(type=float, invariant=cannot_be_negative, mandatory=True)
    crypto = field(type=float, invariant=cannot_be_negative, mandatory=True)
    buys = field(type=int, invariant=cannot_be_negative, mandatory=True)
    sells = field(type=int, invariant=cannot_be_negative, mandatory=True)
    holds = field(type=int, invariant=cannot_be_negative, mandatory=True)
    exchange_rates = field(type=sliding_window.SlidingWindow, mandatory=True)
    fees_paid = field(type=float, invariant=cannot_be_negative, mandatory=True)
    pending_sales = pvector_field(Transaction)  # TODO: Rename to pending_pairs
    transaction_window = pvector_field(Transaction)
Exemple #11
0
class _Add(PClass):
    """
    A ``_IDiffChange`` that adds an item to a ``PSet``.

    :ivar path: The path to the set to which the item will be added.

    :ivar item: The item to be added to the set.
    """
    path = pvector_field(object)
    item = field()

    def apply(self, obj):
        return obj.transform(self.path, lambda x: x.add(self.item))
Exemple #12
0
class ActionStructure(PClass):
    """
    A tree structure used to generate/compare to Eliot trees.

    Individual messages are encoded as a unicode string; actions are
    encoded as a L{ActionStructure} instance.
    """

    type = field(type=(unicode, None.__class__))
    children = pvector_field(object)  # XXX ("StubAction", unicode))
    failed = field(type=bool)

    @classmethod
    def from_written(cls, written):
        """
        Create an L{ActionStructure} or L{unicode} from a L{WrittenAction} or
        L{WrittenMessage}.
        """
        if isinstance(written, WrittenMessage):
            return written.as_dict()[MESSAGE_TYPE_FIELD]
        else:  # WrittenAction
            if not written.end_message:
                raise AssertionError("Missing end message.")
            return cls(
                type=written.action_type,
                failed=(
                    written.end_message.contents[ACTION_STATUS_FIELD] == FAILED_STATUS
                ),
                children=[cls.from_written(o) for o in written.children],
            )

    @classmethod
    def to_eliot(cls, structure_or_message, logger):
        """
        Given a L{ActionStructure} or L{unicode}, generate appropriate
        structured Eliot log mesages to given L{MemoryLogger}.
        """
        if isinstance(structure_or_message, cls):
            action = structure_or_message
            try:
                with start_action(logger, action_type=action.type):
                    for child in action.children:
                        cls.to_eliot(child, logger)
                    if structure_or_message.failed:
                        raise RuntimeError("Make the eliot action fail.")
            except RuntimeError:
                pass
        else:
            Message.new(message_type=structure_or_message).write(logger)
        return logger.messages
Exemple #13
0
class _Set(PClass):
    """
    A ``_IDiffChange`` that sets a field in a ``PClass`` or sets a key in a
    ``PMap``.

    :ivar path: The path in the nested object to the field/key to be set to a
        new value.

    :ivar value: The value to set the field/key to.
    """
    path = pvector_field(object)
    value = field()

    def apply(self, obj):
        return obj.transform(self.path, self.value)
Exemple #14
0
class _Remove(PClass):
    """
    A ``_IDiffChange`` that removes an object from a ``PSet`` or a key from a
    ``PMap`` inside a nested object tree.

    :ivar path: The path in the nested object tree of the object to be removed
        from the import set.

    :ivar item: The item to be removed from the set or the key to be removed
        from the mapping.
    """
    path = pvector_field(object)
    item = field()

    def apply(self, obj):
        return obj.transform(self.path, lambda o: o.remove(self.item))
Exemple #15
0
class _Set(PClass):
    """
    A ``_IDiffChange`` that sets a field in a ``PClass`` or sets a key in a
    ``PMap``.

    :ivar path: The path in the nested object which supports `set` operations.
    :ivar key: The key to set.
    :ivar value: The value to set.
    """
    path = pvector_field(object)
    key = field()
    value = field()

    def apply(self, obj):
        return obj.transform(
            self.path, lambda o: o.set(self.key, self.value)
        )
Exemple #16
0
class PackerConfigure(PClass):
    """
    The attributes necessary to create a custom packer configuration file from
    the prototype files in ``admin/installer/packer``.

    :ivar build_region: The AWS region to build images in.
    :ivar publish_regions: The AWS regions to publish the build images to.
    :ivar template: The prototype configuration to use as a base. One of
        `docker` or `flocker`.
    :ivar configuration_directory: The directory containing prototype
        configuration templates.
    :ivar source_ami_map: The AMI map containing base images.
    """
    build_region = field(type=RegionConstant, mandatory=True)
    publish_regions = pvector_field(item_type=RegionConstant)
    template = field(type=unicode, mandatory=True)
    configuration_directory = field(type=FilePath, initial=PACKER_TEMPLATE_DIR)
    source_ami_map = pmap_field(key_type=RegionConstant, value_type=unicode)
Exemple #17
0
class Diff(PClass):
    """
    A ``_IDiffChange`` that is simply the serial application of other diff
    changes.

    This is the object that external modules get and use to apply diffs to
    objects.

    :ivar changes: A vector of ``_IDiffChange`` s that represent a diff between
        two objects.
    """

    changes = pvector_field(object)

    def apply(self, obj):
        for c in self.changes:
            obj = c.apply(obj)
        return obj
Exemple #18
0
class SlidingWindow(PRecord):
    samples = pvector_field(SlidingWindowSample)
    maximum_size = field(type=int, mandatory=True, invariant=must_be_positive)
    # Pararmeterizes the response of the first-order filter. Larger values make
    # the filter more responsive but preserve more noise in the signal.
    first_order_filter_time_constant = field(type=float,
                                             mandatory=True,
                                             invariant=must_be_positive)
    # Pararmeterizes the response of the second-order filter. Larger values make
    # the filter more responsive but preserve more noise in the signal.
    second_order_filter_time_constant = field(type=float,
                                              mandatory=True,
                                              invariant=must_be_positive)
    # The ratio of contributions between the first and second order filters. A
    # value of 0.25 means that (1/4) of the filter response comes from the first
    # order filter and (3/4) of the response comes from the second order filter.
    filter_order_ratio = field(type=float,
                               mandatory=True,
                               invariant=must_be_zero_to_one)
Exemple #19
0
class PackerConfigure(PClass):
    """
    The attributes necessary to create a custom packer configuration file from
    the prototype files in ``admin/installer/packer``.

    :ivar build_region: The AWS region to build images in.
    :ivar publish_regions: The AWS regions to publish the build images to.
    :ivar template: The prototype configuration to use as a base. One of
        `docker` or `flocker`.
    :ivar distribution: The operating system distribution to install.
        ubuntu-14.04 is the only one implemented so far.
    :ivar configuration_directory: The directory containing prototype
        configuration templates.
    :ivar source_ami: The AMI ID to use as the base image.
    """
    build_region = field(type=RegionConstant, mandatory=True)
    publish_regions = pvector_field(item_type=RegionConstant)
    template = field(type=unicode, mandatory=True)
    distribution = field(type=unicode, mandatory=True)
    configuration_directory = field(type=FilePath, initial=PACKER_TEMPLATE_DIR)
    source_ami = field(type=unicode, mandatory=True)
Exemple #20
0
class QMemory(PRecord):
    samples = pvector_field(QMemorySample)
    maximum_size = field(type=int)
Exemple #21
0
class Aggregate(PClass):
    """
    Aggregate base class

    Intended as a base class for all domain aggregates

    Fields:
    id -- Aggregate id
    events -- Committed events modeled as PVector
    uncommitted_events -- Uncommitted events modeled as PVector
    state -- Current state projection modeled as PMap
    """

    id = field(type=str, mandatory=True)

    events = pvector_field(Event)

    uncommitted_events = pvector_field(Event)

    state = field(type=PMap)

    # Private
    def _version(self, committed=True):
        """
        Get the current version of an aggregate

        If `committed` is False then include uncommitted events
        """
        _events = self.events

        if not committed:
            _events += self.uncommitted_events

        return _events[-1].version if _events else 0

    # Public
    @classmethod
    def generate(cls, id=None):
        """
        Return a new aggregate data structure

        Parameters:
        id_ -- UUID, defaults to a randomly generated UUID if not specified
        """
        if not id:
            id = uuid4()
        else:
            # Must be a valid UUID string
            id = UUID(id)

        return cls(
            **{
                'id': str(id),
                'events': pvector(),
                'uncommitted_events': pvector(),
                'state': pmap(),
            })

    @classmethod
    def generate_from_events(cls, id_, events, committed=True, apply_map=None):
        """
        Return an aggregate from applying the supplied events and apply_map

        Parameters:
        apply_map -- A dict of event_type keys to handler functions
        id_ -- unique identity; required because an aggregate must have identity
               in order to have events associated
        events -- The events to apply
        committed -- If True then the events are added as if committed
        """
        apply_map = apply_map or cls.get_apply_map()
        aggregate = cls.generate(id_)
        return aggregate.apply_events(events,
                                      committed=committed,
                                      apply_map=apply_map)

    @classmethod
    @memoize(maxsize=1)
    def get_apply_map(cls):
        """
        Return a map of event types to apply functions

        *If apply functions are class methods they should be defined as
        staticmethod to enforce clear separation of concerns; see
        Aggregate.apply_noop as an example*
        """
        return pmap({
            # 'NothingHappened': cls.apply_noop
        })

    def apply_event(self, event, committed=False, apply_map=None):
        """
        Apply an event to the aggregate, returning a new instance

        Versions the event and aggregate & updates state

        Arguments:
        event -- Event instance to apply

        Keyword Arguments:
        committed -- If True then apply the events as committed
        apply_map -- If supplied override definition of `self.get_apply_map`
        """
        # Get the state-update
        _apply_map = apply_map or self.get_apply_map()
        apply_fn = _apply_map.get(event.type, self.apply_noop)

        # Apply state-changes
        _aggregate = apply_fn(self, event) if apply_fn else self

        # Version the event
        if not event.version:
            event = event.set('version', (self.uncommitted_version + 1))

        if not committed:
            _aggregate = _aggregate.set(
                'uncommitted_events',
                _aggregate.uncommitted_events + (event, ))
        else:
            _aggregate = _aggregate.set('events',
                                        _aggregate.events + (event, ))

        return _aggregate

    def apply_events(self, events, committed=False, apply_map=None):
        """
        Apply multiple events to an aggregate, delegates to `apply_event`
        """
        _aggregate = self
        for event in events:
            _aggregate = _aggregate.apply_event(event,
                                                committed=committed,
                                                apply_map=apply_map)
        return _aggregate

    @property
    def type(self):
        return self.__class__.__name__

    @property
    def version(self):
        return self._version()

    @property
    def uncommitted_version(self):
        return self._version(committed=False)

    def set_state(self, key, value):
        """
        Return a new aggregate with its state updated by key/value
        """
        return self.set('state', self.state.set(key, value))

    def mark_events_committed(self):
        """
        Return new aggregate with `uncommitted_events` moved to `events`

        Also effectively updates the committed version of the aggregate
        """
        return self\
            .set('events', self.events + self.uncommitted_events)\
            .set('uncommitted_events', pvector())

    @staticmethod
    def apply_noop(aggregate, event):
        """
        Aggregate event handler function that acts as a no-op

        Event handlers that do something should return a new aggregate with
        updated state.  For example:

            @staticmethod
            def apply_hello_world(aggregate):
                return aggregate.set_state('hello', 'world')

        Arguments:
        aggregate -- Aggregate instance to which the event will be applied
        event -- Event instance to "apply" to the `aggregate`
        """
        return aggregate
Exemple #22
0
class SoccerState(GameState):
    ################################################################
    ## PUBLIC METHODS
    ################################################################

    # You may use these methods when implementing the MinimaxAgent.

    # For information on these methods, see _game.GameState

    @property
    def num_players(self):
        return len(self.players)

    @property
    def current_player(self):
        return self.current_player_id

    @property
    def is_terminal(self):
        return self.winner

    @property
    def actions(self):
        player = self.current_player_obj
        actions = []
        if player.has_ball:
            actions += [Action.KICK]
        # actions += [Action.CHANGE_STANCE]
        dx = 1 if player.team == Team.RED else -1
        actions += [
            Action.move(1, 0),
            Action.move(-1, 0),
            Action.move(0, 1),
            Action.move(0, -1)
        ]

        if player.x <= 1:
            dx = 1
        elif player.x >= self.pitch.width:
            dx = -1
        else:
            dx = 1 if player.team == Team.RED else -1

        actions += [Action.move(dx, 1), Action.move(dx, -1)]
        return actions

    def reward(self, player_id):
        if not self.is_terminal:
            return None
        return 10 if self.winner == self.players[player_id].team else -10

    def act(self, action):
        player = self.current_player_obj
        state = self

        state = state._action_is_valid(action)
        if not state:
            return None

        if action == Action.KICK:
            state = self._update_kick()
        elif action == Action.CHANGE_STANCE:
            state = state.transform(self._cpk('stance'),
                                    (player.stance + 1) % 2)
        elif isinstance(action, tuple) and action[0] == Action.MOVE:
            (_, dx, dy) = action
            state = self._update_move_to(player.x + dx, player.y + dy)
        else:
            return None

        if state:
            state = state.set(current_player_id=(self.current_player + 1) %
                              self.num_players)

        return state

    ################################################################
    ## INTERNAL
    ################################################################

    # These are 'internal' methods and variables to the SoccerState
    # class. You should not use any of these methods in the
    # MinimaxAgent implementation, but can use them in your soccer
    # evaluation function.

    ## Variables
    # Each of these fields can be directly accessed by calling
    # `state.<variable_name>`.

    # The ID of the current player.
    current_player_id = field(type=int)

    # A persistent list of players. Can be accessed like a normal
    # Python list: players[0] is the first player, etc.
    #
    # Each player object players[i] is a PMap (equivalent to a Python
    # dict) with the following structure:
    #
    # players[i] = {
    #   type="player",  # The type of object
    #   index=i,        # The index of the player in the players list
    #   agent=agent,    # The agent controlling the player
    #   team={Team.RED|Team.BLUE} # The player's team
    #   x=x_pos, y=y_pos, # The current (x,y) position of the player
    #   has_ball={True,False} # True if the player currently has the ball
    # }
    players = pvector_field(PMap)

    # Contains the players of each team. Keys are teams['red'] and
    # teams['blue']. Probably should switch to using the team enum in
    # the future.
    teams = pmap_field(str, PVector)

    # Information about the ball. Has the following information:
    #
    # ball = {
    #   type='ball',          # The type of object
    #   on_field={True,False} # True if nobody has the ball.
    #   x=x_pos, y=y_pos      # The (x,y) position of the ball
    # }
    #
    # Note that the position of the ball is updated even when a player
    # has the ball.
    ball = pmap_field(str, (bool, str, int))

    # The term 'field' was ambiguous, so this is just some
    # information about the soccer field:
    #
    # pitch = {
    #   width=pitch_width       # The width of the soccer field
    #   height=pitch_height     # The height of the soccer field
    #   goal_height=goal_height # The height of the goal (along the y-axis)
    # }
    pitch = pmap_field(str, int)

    # Indicates the winner of the round. When the game hasn't
    # finished, is None; when the game is complete, is the Team that
    # won the game.
    winner = field(type=(Team, type(None)))

    @property
    def objects(self):
        """Returns the list of 'objects' (players + the ball)"""
        return list(self.players) + [self.ball]

    def dist_to_goal(self, pos, team):
        """Returns the distance between an (x, y) position and a team's goal."""
        goal_pos = (self.pitch.width+0.5, self.pitch.height/2) if team == Team.RED \
                   else (0.5, self.pitch.height/2)
        return math.sqrt(
            sum([(p1 + 0.51 - p2)**2 for p1, p2 in zip(pos, goal_pos)]))

    def at(self, x, y):
        """Returns the object at position (x,y). If no object is there, return
        None.

        """
        for obj in self.objects:
            ## invariant: no two objects occupy the same space
            if obj.x == x and obj.y == y:
                return obj
        return None

    @property
    def current_player_obj(self):
        """Returns the info for the current player in a PMap object."""
        return self.players[self.current_player]

    @property
    def player_with_ball(self):
        return next((p for p in self.players if p.has_ball), [None])

    def goal_pos(self, team):
        """Returns the position of the `teams`'s goal."""
        return self.red_goal_pos if team == Team.RED \
            else self.blue_goal_pos

    @property
    def red_goal_pos(self):
        """The position of the red team's goal."""
        return (self.pitch.width + 1, int(self.pitch.height / 2) + 1)

    @property
    def blue_goal_pos(self):
        """The position of the blue team's goal."""
        return (0, int(self.pitch.height / 2) + 1)

    @property
    def goal_top(self):
        """Returns the y-position of the top of the goal."""
        return int(self.pitch.height + self.pitch.goal_height) / 2

    @property
    def goal_bottom(self):
        """Returns the y-position of the bottom of the goal."""
        return int(self.pitch.height - self.pitch.goal_height) / 2 + 1

    @property
    def ball_in_red_goal(self):
        """Returns true if the ball is currently in the Red goal."""
        return self.ball.x > self.pitch.width and\
            self.goal_bottom <= self.ball.y and self.ball.y <= self.goal_top

    @property
    def ball_in_blue_goal(self):
        """Returns true if the ball is currently in the Blue goal."""
        return self.ball.x < 1 and\
            self.goal_bottom <= self.ball.y and self.ball.y <= self.goal_top

    def player_in_red_penalty_area(self, player_id):
        """Returns true if the player_id is in the Red penalty area."""
        player = self.players[player_id]
        return player.x >= self.pitch.width-3 and\
            self.goal_bottom-1 <= player.y and player.y <= self.goal_top+1

    def player_in_blue_penalty_area(self, player_id):
        """Returns true if the player_id is in the Blue penalty area."""
        player = self.players[player_id]
        return player.x <= 3 and\
            self.goal_bottom-1 <= player.y and player.y <= self.goal_top+1

    def _cpk(self, *keys):
        """Internal method, returns a tuple for the current player that is
        used for updating the state.

        """
        return ('players', self.current_player, *keys)

    def _update_move_to(self, x, y):
        """State update: Current player moves to pos (x,y)."""
        player = self.current_player_obj
        state = self
        if player.has_ball:
            if y < 1 or y > state.pitch.height:  ## sidelines
                state = state._update_reset(prefer_side=player.team.inverse)
            elif x < 1 or x > self.pitch.width:
                if self.goal_bottom <= y and y <= self.goal_top:
                    if x < 1:
                        state = state.set(winner=Team.BLUE)
                    else:
                        state = state.set(winner=Team.RED)
                else:
                    if x < 1 and player.team == Team.RED \
                       or x > self.pitch.width and player.team == Team.BLUE:
                        state = state._update_corner_kick()
                    else:
                        state = state._update_reset(
                            prefer_side=player.team.inverse)
            else:
                ## deal with collision
                (state, do_move) = state._update_check_collide(x, y)
                if do_move:
                    state = state.transform(self._cpk('x'), x, self._cpk('y'),
                                            y, ('ball', 'x'), x, ('ball', 'y'),
                                            y)
                    state = state._update_check_goal()
        else:
            if x < 1 or x > self.pitch.width:
                return None
            elif y < 1 or y > self.pitch.height:
                return None
            else:
                ## deal with collision
                (state, do_move) = state._update_check_collide(x, y)
                if do_move:
                    state = state.transform(self._cpk('x'), x, self._cpk('y'),
                                            y)
        return state

    def _update_corner_kick(self):
        state = self
        p2 = self.player_with_ball

        if not p2:
            return state

        goal_pos = self.goal_pos(p2.team.inverse)
        p1 = self.players[self.teams[p2.team.inverse.name][0]]
        goal_pos_x = goal_pos[0]
        dx = -1 if goal_pos_x > 1 else 1

        state = state.transform(
            ('players', p1.index, 'x'), goal_pos_x + dx,
            ('players', p1.index, 'y'), 1, ('players', p1.index, 'has_ball'),
            True, ('players', p2.index, 'x'), goal_pos_x + 3 * dx,
            ('players', p2.index, 'y'), 3, ('players', p2.index, 'has_ball'),
            False)

        return state

    def is_goal(self, dist, angle):
        return 20 * abs(angle)**2 / (dist**2) > 3e-1

    def can_shoot_from(self, x, y, team):
        (x, y) = (x + 0.5, y - 0.5)
        goal_x = self.pitch.width + 2 if team == Team.RED else 0
        goal_y1 = int(self.pitch.height - self.pitch.goal_height) / 2
        goal_y2 = int(self.pitch.height + self.pitch.goal_height) / 2
        dx = goal_x - x
        dy1 = (goal_y1 - y)
        dy2 = (goal_y2 - y)
        norm1 = math.sqrt(dx**2 + dy1**2)
        norm2 = math.sqrt(dx**2 + dy2**2)
        dy = ((goal_y1 + goal_y2) / 2 - y)

        dist = math.sqrt(dx**2 + dy**2)
        angle = math.acos((dx**2 + dy1 * dy2) / (norm1 * norm2))

        return self.is_goal(dist, angle)

    def check_kick(self, player):
        (x, y) = (player.x + 0.5, player.y - 0.5)
        goal_x = self.pitch.width + 2 if player.team == Team.RED else 0
        goal_y1 = int(self.pitch.height - self.pitch.goal_height) / 2
        goal_y2 = int(self.pitch.height + self.pitch.goal_height) / 2
        dx = goal_x - x
        dy1 = (goal_y1 - y)
        dy2 = (goal_y2 - y)
        norm1 = math.sqrt(dx**2 + dy1**2)
        norm2 = math.sqrt(dx**2 + dy2**2)
        dy = ((goal_y1 + goal_y2) / 2 - y)

        f_y1 = lambda obj_x: y + dy1 * obj_x
        f_y2 = lambda obj_x: y + dy2 * obj_x

        dist = math.sqrt(dx**2 + dy**2)
        angle = math.acos((dx**2 + dy1 * dy2) / (norm1 * norm2))

        # Check for interceptions
        intercept = (None, float("inf"))
        for obj in self.players:
            if obj.index == player.index: continue
            (obj_x, obj_y) = (obj.x + 0.5, obj.y + 0.5)
            obj_x = (obj_x - x) / dx
            if obj_y >= f_y1(obj_x) and obj_y <= f_y2(obj_x):
                new_i = (obj, math.sqrt((x - obj_x)**2 + (y - obj_y)**2))
                intercept = min([intercept, new_i], key=lambda x: x[1])

        return (dist, angle, self.is_goal(dist, angle), intercept[0])

    def _update_kick(self):
        """State update: Current player kicks towards opponent goal."""
        player = self.current_player_obj
        state = self

        (_, _, is_goal, intercept_player) = self.check_kick(player)

        if intercept_player:
            state = state._update_switch_possession(player.index,
                                                    intercept_player.index)
        elif is_goal:
            goal_pos = self.goal_pos(player.team)
            state = state.transform(('ball', 'x'), goal_pos[0], ('ball', 'y'),
                                    goal_pos[1], ('ball', 'on_field'), True,
                                    self._cpk('has_ball'), False)
            state = state._update_check_goal()
        else:
            state = state._update_reset(prefer_side=player.team.inverse)
        return state

    def _update_switch_possession(self, player_a, player_b):
        """State update: Possession of ball switches between player_a and
        player_b

        """
        state = self
        if self.players[player_a].has_ball:
            p1 = self.players[player_a]
            p2 = self.players[player_b]
        else:
            p1 = self.players[player_b]
            p2 = self.players[player_a]
        goal_pos = self.goal_pos(p1.team.inverse)
        state = state.transform(('players', p1.index, 'has_ball'), False,
                                ('players', p2.index, 'has_ball'), True,
                                ('ball', 'x'), p2.x, ('ball', 'y'), p2.y)
        state = state._update_place_between(p1.index, p2.x, p2.y, goal_pos[0],
                                            goal_pos[1])
        return state

    def _update_place_between(self, player_id, x1, y1, x2, y2):
        """State update: player_id is placed in between position (x1, y1) and
        (x2, y2)

        """
        x = int((x1 + x2) / 2)
        y = int((y1 + y2) / 2)
        state = self
        if state.at(x, y):
            if not state.at(x + 1, y) and x + 1 <= self.pitch.width:
                x += 1
            elif not state.at(x - 1, y) and x - 1 >= 1:
                x -= 1
            elif not state.at(x, y + 1) and y + 1 <= self.pitch.height:
                y += 1
            elif not state.at(x, y - 1) and y - 1 >= 1:
                y -= 1
        state = state.transform(('players', player_id, 'x'), x,
                                ('players', player_id, 'y'), y)
        return state

    def _update_reset(self, prefer_side=None, random_pos=False):
        """State update: Players and ball are reset to original position.

        :param Team prefer_side: If set to a Team, that team will
            receive the ball when the game is reset.
        :param bool random_pos: If True, players will be randomly
            placed on their side of the field.
        """
        state = self
        mid_x = int(self.pitch.width / 2) + 1
        mid_y = int(self.pitch.height / 2) + 1
        ball_offset = 0 if not prefer_side \
                      else (-1 if prefer_side == 'red' else 1)
        if not prefer_side:
            state = state.transform(('ball', 'x'), mid_x + ball_offset,
                                    ('ball', 'y'), mid_y, ('ball', 'on_field'),
                                    True)
        else:
            state = state.transform(('ball', 'on_field'), False)
        for team_name in ('red', 'blue'):
            team = self.teams[team_name]
            i = 0
            for dx in range(4):
                done = False
                for dy in range(5):
                    if not random_pos:
                        x = mid_x + (-dx - 5 if team_name == 'red' else dx + 5)
                        y = mid_y + (2 * int(dy / 2) * (-1 if dy % 2 else 1))
                        state = state\
                                .transform(('players', team[i], 'x'), x)\
                                .transform(('players', team[i], 'y'), y)\
                                .transform(('players', team[i], 'has_ball'), False)
                        if i == 0 and prefer_side == self.players[
                                team[i]].team:
                            state = state.transform(
                                ('players', team[i], 'has_ball'), True,
                                ('ball', 'x'), x, ('ball', 'y'), y)
                        i += 1
                    else:
                        x_range = range(1,mid_x) if team_name == 'red' \
                                  else range(mid_x+1,self.pitch.width)
                        y_range = range(1, self.pitch.height)
                        state = state\
                                .transform(('players', team[i], 'x'), random.choice(x_range))\
                                .transform(('players', team[i], 'y'), random.choice(y_range))
                    if i >= len(team):
                        done = True
                        break
                if done:
                    break
        return state

    def _update_check_collide(self, x, y):
        """State update: Check if there will be a collision between the
        current player and whatever is at position (x,y).

        """
        state = self
        do_move = True
        obj = state.at(x, y)
        if not obj:
            do_move = True
        elif obj.type == 'ball':  ## If we collide with the ball...
            ## Pick up the ball
            state = state.transform(('ball', 'x'), x, ('ball', 'y'), y,
                                    ('ball', 'on_field'), False,
                                    self._cpk('has_ball'), True)
            do_move = True
        elif obj.type == 'player':
            # state = state.transform(
            #     ('ball', 'x'), x, ('ball', 'y'), y,
            #     self._cpk('has_ball'), False,
            #     ('players', obj.index, 'has_ball'), True
            # )
            if self.current_player_obj.has_ball or obj.has_ball:
                state = state._update_switch_possession(
                    self.current_player, obj.index)
            do_move = False
        else:
            state = None
            do_move = False
        return (state, do_move)

    def _update_check_goal(self):
        """State update: Check if the ball is in a goal, and, if so, update
        the winner of the game.

        """
        state = self
        if self.ball_in_red_goal:
            state = state.set(winner=Team.RED)
        elif self.ball_in_blue_goal:
            state = state.set(winner=Team.BLUE)
        return state

    def draw(self):
        """Internal method, draws the current game configuration."""
        BLOCK_SIZE = B = 32
        PITCH_COLOR = (0, 200, 0)
        PLAYER_RED_COLOR = (255, 0, 0)
        PLAYER_BLUE_COLOR = (0, 0, 255)
        BALL_COLOR = (255, 255, 255)
        surf = pygame.Surface(
            ((self.pitch.width + 2) * B, (self.pitch.height + 2) * B))

        (W, H) = (surf.get_width(), surf.get_height())
        (W_p, H_p) = (self.pitch.width, self.pitch.height)
        H_g = self.pitch.goal_height
        surf.fill(PITCH_COLOR)

        # Draw grid
        for i in range(self.pitch.width + 2):
            for j in range(self.pitch.height + 2):
                s1 = surf.subsurface(
                    (i * B, (self.pitch.height - j + 1) * B, B, B))
                if 1 <= i and i <= self.pitch.width and\
                   1 <= j and j <= self.pitch.height:
                    if self.can_shoot_from(i, j,
                                           Team.RED) or self.can_shoot_from(
                                               i, j, Team.BLUE):
                        s1.fill((100, 255, 100), (1, 1, B - 2, B - 2))
                    else:
                        s1.fill((0, 255, 0), (1, 1, B - 2, B - 2))

        # Draw field lines
        pygame.draw.lines(surf, (255, 255, 255), True, [(B, B), (W - B, B),
                                                        (W - B, H - B),
                                                        (B, H - B)], 3)
        pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B),
                         (int(W / 2), H - B), 3)
        pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B),
                         (int(W / 2), H - B), 3)
        pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B),
                         (int(W / 2), H - B), 3)
        pygame.draw.circle(surf, (255, 255, 255), (int(W / 2), int(H / 2)),
                           int(B * 2.5), 3)
        pygame.draw.lines(surf, (255, 255, 255), False,
                          [(B, B * (int((H_p + H_g) / 2) + 2)),
                           (4 * B, B * (int((H_p + H_g) / 2) + 2)),
                           (4 * B, B * (int((H_p - H_g) / 2))),
                           (B, B * (int((H_p - H_g) / 2)))], 3)
        pygame.draw.lines(surf, (255, 255, 255), False,
                          [(W - B, B * (int((H_p + H_g) / 2) + 2)),
                           (W - 4 * B, B * (int((H_p + H_g) / 2) + 2)),
                           (W - 4 * B, B * (int((H_p - H_g) / 2))),
                           (W - B, B * (int((H_p - H_g) / 2)))], 3)
        # pygame.draw.circle(surf, (255, 255, 255), (W-B, int(H/2)), B*3, 3)
        # surf.fill(PITCH_COLOR, (0, 0, B-1, H))
        # surf.fill(PITCH_COLOR, (W-B+2, 0, B, H))
        font = pygame.font.SysFont("monospace", 18)
        font.set_bold(True)

        for i in range(self.pitch.width + 2):
            for j in range(self.pitch.height + 2):
                obj = self.at(i, j)
                s1 = surf.subsurface(
                    (i * B, (self.pitch.height - j + 1) * B, B, B))
                if obj and obj.type == 'ball' and obj.on_field:
                    s1.fill(BALL_COLOR, (6, 6, B - 12, B - 12))
                elif obj and obj.type == 'player':
                    c = PLAYER_RED_COLOR if obj.team == Team.RED else PLAYER_BLUE_COLOR
                    if obj.index == self.current_player:
                        s1.fill((250, 250, 0))
                    s1.fill(c, (2, 2, B - 4, B - 4))
                    if obj.has_ball:
                        pygame.draw.rect(s1, BALL_COLOR,
                                         (6, 6, B - 12, B - 12))
                    # label = font.render("R" if obj.stance == 0 else "B", 1, (0, 255, 0))
                    # s1.blit(label, (B/4, B/4))

                if (i < 1 or i > self.pitch.width) and \
                     (self.goal_bottom <= j and j <= self.goal_top):
                    for l in range(4, BLOCK_SIZE, 8):
                        pygame.draw.line(s1, (255, 255, 255), (0, l), (B, l))
                        pygame.draw.line(s1, (255, 255, 255), (l, 0), (l, B))

        GOAL_TOP = BLOCK_SIZE * (1 + int(
            (self.pitch.height + self.pitch.goal_height) / 2))
        GOAL_BOTTOM = BLOCK_SIZE * (1 + int(
            (self.pitch.height - self.pitch.goal_height) / 2))
        pygame.draw.lines(surf, (0, 0, 0), False, [(0, GOAL_TOP),
                                                   (B, GOAL_TOP),
                                                   (B, GOAL_BOTTOM),
                                                   (0, GOAL_BOTTOM)], 3)
        pygame.draw.lines(surf, (0, 0, 0), False, [(W, GOAL_TOP),
                                                   (W - B, GOAL_TOP),
                                                   (W - B, GOAL_BOTTOM),
                                                   (W, GOAL_BOTTOM)], 3)
        pygame.draw.rect(surf, PLAYER_RED_COLOR, (0, 0, 6, surf.get_height()))
        pygame.draw.rect(surf, PLAYER_BLUE_COLOR,
                         (surf.get_width() - 7, 0, 6, surf.get_height()))

        if self.is_terminal:
            winner_rect = ((1 + self.pitch.width / 2 - 3) * B,
                           (self.pitch.height / 2 + 2) * B, 6 * B, 2 * B)
            border_rect = (winner_rect[0] - 4, winner_rect[1] - 4,
                           winner_rect[2] + 8, winner_rect[3] + 8)
            label = None
            if self.winner == Team.RED:
                label = font.render("Team  RED wins!", 1, (255, 255, 255))
                pygame.draw.rect(surf, (200, 100, 100), border_rect)
                pygame.draw.rect(surf, (255, 0, 0), winner_rect)
            else:
                label = font.render("Team BLUE wins!", 1, (255, 255, 255))
                pygame.draw.rect(surf, (100, 100, 200), border_rect)
                pygame.draw.rect(surf, (0, 0, 255), winner_rect)
            surf.blit(label,
                      (winner_rect[0] + 7, winner_rect[1] + int(B / 2) + 4))

        return surf

    def __eq__(self, other):
        return hash(self) == hash(other)

    def __hash__(self):
        key = [self.current_player, (self.ball.x, self.ball.y)] \
              + [(p.x, p.y, p.stance, p.has_ball) for p in self.players]
        return hash(tuple(key))
Exemple #23
0
 class Record(PRecord):
     value = pvector_field((Something, int))
Exemple #24
0
 class Bar(PRecord):
     bar = pvector_field(Foo)
Exemple #25
0
 class Record(PRecord):
     value = pvector_field(int, optional=True)
Exemple #26
0
 class Record(PRecord):
     value = pvector_field(Something)
     value2 = pvector_field(int)
Exemple #27
0
 class Record(PRecord):
     value = pvector_field(int)
Exemple #28
0
 class Record(PRecord):
     value = pvector_field((int, str))
Exemple #29
0
 class Record(PRecord):
     value = pvector_field(int, initial=(1, 2))
Exemple #30
0
class RecordContainingContainers(PRecord):
    map = pmap_field(str, str)
    vec = pvector_field(str)
    set = pset_field(str)
Exemple #31
0
def test_pvector_field_enum_type():
    f = pvector_field(TestEnum)

    assert len(f.type) == 1
    assert TestEnum is list(f.type)[0].__type__