Exemple #1
0
class Notice(Object):
    name = Attr(isa=str)
    source = Attr(isa=str)
    type = Attr(isa=str, builder=lambda: "notice")
    data = Attr(isa=dict)

    def __init__(self, *args, **kwargs):
        super(Notice, self).__init__(*args, **kwargs)
        self.make_immutable()

    def make_immutable(self):
        self.amethyst_make_immutable()
class Call(Filterable):
    """
    A Call may be used to trigger a grant.

    Often a game will send Grants to clients and receive Calls from
    clients, then trigger the Grant referred to by the Call.
    """
    kwargs = Attr(isa=dict)
class TTTInterface(Object):
    engine = Attr(default=lambda: TTT_Engine(client=True))
    player = Attr(int)

    def handle(self):
        self.engine.process_queue()

    def print_board(self):
        """
        Print the current board state to screen.
        """
        for row in self.engine.board:
            print( "".join( "_" if x is None else str(x) for x in row ) )

    def on_event(self, game, seq, player, notice):
        """
        Print notification of every event for illustrative purposes.
        """
        print("Hey, player {}: {}".format(player, str(notice)))
class Grant(Filterable):
    """
    User grant, authorizes a client to call a game action by name

    :ivar str id: Unique identifier

    :ivar str name: Action name to call

    :ivar set(str) flags:

    :ivar dict kwargs: Forced parameters, will override anything requested
        by the client.

    :ivar dict defaults: Default parameters, will supplement anything
        missing from the client call.

    :ivar data:

    :ivar repeatable:

    :ivar list(str) expires: Grant ids which will be expired upon
        successful submission of the action call.
    """

    kwargs = Attr(isa=dict)
    defaults = Attr(isa=dict)
    data = Attr(isa=dict)
    repeatable = Attr(bool)
    #     mandatory = Attr(bool)
    #     immediate = Attr(bool)
    #
    #     before = Attr(tupley)
    #     after = Attr(tupley)
    #     dt_expires = Attr(int)
    expires = Attr(tupley)

    #     consumes = Attr(tupley)
    #     requires = Attr(tupley)
    #     conflicts = Attr(tupley)

    def call(self, **kwargs):
        return Call(id=self.id, name=self.name, kwargs=kwargs)
    def test_Attr_standalone(self):
        chk = Attr(int)

        self.assertTrue(callable(chk))
        self.assertEqual(chk("42"), 42)

        with self.assertRaises(TypeError):
            chk(None)
        with self.assertRaises(ValueError):
            chk("plugh")

        for chk in (Attr(int, default=0), Attr(int, default=lambda: 0)):
            self.assertTrue(callable(chk))
            self.assertEqual(chk("42"), 42)

            with self.assertRaises(TypeError):
                chk(None)
            with self.assertRaises(ValueError):
                chk("plugh")
            self.assertEqual(chk.get_default(), 0)
        class ObjOver1(Object):
            foo = Attr(int)
            bar = Attr(isa=str).strip()

            def croak(self, *args, **kwargs):
                raise Exception("Bummer")

            items = croak
            keys = croak
            values = croak
            get = croak
            set = croak
            setdefault = croak
            direct_set = croak
            pop = croak
            update = croak
            direct_update = croak
            attr_value_ok = croak
            load_data = croak
            deflate_data = croak
            inflate_new = croak
class TTT_Engine(Engine):
    board   = Attr()
    width   = Attr(default=3)
    height  = Attr(default=3)

    def __init__(self, *args, **kwargs):
        """
        Set up an empty board and load plugins.
        """
        super(TTT_Engine,self).__init__(*args, **kwargs)

        # Empty NxM board
        if self.board is None:
            self.board = [ [None] * self.width for i in range(self.height) ]

        # Most turn-based games will want the GrantManager and Turns plugin
        self.register_plugin(GrantManager())
        self.register_plugin(Turns())

        # All games need some plugins to implemnt behavior. Some games will
        # always consist of the same plugins, some may load different sets
        # of plugins depending on what game expansions are being played.
        # Some may load a base set of plugins in their constructor and
        # allow the caller to load additional plugins.
        #
        # TicTacToe needs only a single plugin.
        self.register_plugin(TicTacToe())

    def spaces_available(self):
        """
        Utility function that computes a list of available places.
        """
        avail = []
        for j, row in enumerate(self.board):
            for i, val in enumerate(row):
                if val is None:
                    avail.append( (i,j) )
        return avail
class Filterable(IFilterable, Object):
    """
    Filterable base Object.

    The object will be set immutable in the constructor (though see
    limitations in the `amethyst.core` documentation).

    If an `id` is not passed to the constructor, a random one will be
    generated.

    `name` and `type` will default to `None` and `flags weill default to an
    empty `set()`.
    """
    id = Attr(isa=str, default=nonce, OVERRIDE=True)
    name = Attr(isa=str, OVERRIDE=True)
    type = Attr(isa=str, OVERRIDE=True)
    flags = Attr(isa=set, default=set, OVERRIDE=True)

    def __init__(self, *args, **kwargs):
        super(Filterable, self).__init__(*args, **kwargs)
        self.make_immutable()

    def make_immutable(self):
        self.amethyst_make_immutable()
class EnginePlugin(Object, BasePlugin):
    """
    EnginePlugin

    :cvar AMETHYST_ENGINE_DEPENDS: Iterable collection of plugin class
        names (as strings) which this plugin delends on. An exception will
        be thrown if any dependencies are not available when the plugin is
        registered.
    :type AMETHYST_ENGINE_DEPENDS: Any iterable of str

    :cvar AMETHYST_ENGINE_METHODS: Iterable collection of methods to be
        added to the engine object which will be handled by this plugin. It
        is an error for multiple plugins to define the same method, so be
        considerate and try to prefix your method names to avoid
        collisions.
    :type AMETHYST_ENGINE_METHODS: Any iterable of str

    :cvar AMETHYST_PLUGIN_COMPAT: Plugin version number as a float or int.
        Plugin version compatibility between instances is ensured via the
        integer portion of this value. (See `compat`)
    :type AMETHYST_PLUGIN_COMPAT: float or int

    :ivar compat: Version number of instance. When plugin data is
        deserialized, this value is compared against the class variable
        `AMETHYST_PLUGIN_COMPAT`. If they do not have the same integer
        value, an exception will be thrown.
    """
    AMETHYST_ENGINE_DEPENDS = ()
    AMETHYST_ENGINE_METHODS = ()
    AMETHYST_ENGINE_DEFAULT_METHOD_PREFIX = ""
    AMETHYST_ENGINE_DEFAULT_METHOD_SUFFIX = ""
    # Compatibility: class attr hard-coded, instance attr used when passing
    # constructed objects over the wire. Allows server to verify that the
    # server plugin version is compatible with the client plugin version.
    AMETHYST_PLUGIN_COMPAT = None
    compat = Attr(float)
    id = Attr(isa=str, default=nonce)

    def __init__(self, *args, **kwargs):
        self.amethyst_method_prefix = kwargs.pop(
            "amethyst_method_prefix",
            self.AMETHYST_ENGINE_DEFAULT_METHOD_PREFIX)
        self.amethyst_method_suffix = kwargs.pop(
            "amethyst_method_suffix",
            self.AMETHYST_ENGINE_DEFAULT_METHOD_SUFFIX)
        super(EnginePlugin, self).__init__(*args, **kwargs)
        if self.compat is None:
            self.compat = self.AMETHYST_PLUGIN_COMPAT
        if self.AMETHYST_PLUGIN_COMPAT is None:
            raise PluginCompatibilityException(
                "Plugin {} does not define an api version".format(
                    self.__class__.__name__))
        if int(self.compat) != int(self.AMETHYST_PLUGIN_COMPAT):
            raise PluginCompatibilityException(
                "Plugin {} imported incompatible serialized data: Loaded {} data, this is version {}"
                .format(self.__class__.__name__, self.compat,
                        self.AMETHYST_PLUGIN_COMPAT))

    def make_mutable(self):
        self.amethyst_make_mutable()

    def make_immutable(self):
        self.amethyst_make_immutable()

    def _listener_callback(self, listener):
        def cb(*args):
            listener(self, *args)

        cb.__name__ = listener.name
        return cb

    def on_assign_to_game(self, game):
        for listener in self._listeners:
            game.register_event_listener(listener.type,
                                         self._listener_callback(listener))

    def initialize_early(self, game, attrs=None):
        pass

    def initialize(self, game, attrs=None):
        if attrs is not None:
            self.load_data(attrs, verifyclass=False)

    def initialize_late(self, game, attrs=None):
        pass

    @cached_property
    def initialization_data(self):
        return None

    def get_state(self, player):
        return copy.deepcopy(self.dict)

    def set_state(self, state):
        self.set(**state)
 class ObjB(Object):
     foo = Attr(int)
     bar = Attr(isa=str).strip()
class Turns(EnginePlugin):
    """
    Configurable attributes
    -----------------------

    These attributes can be set at plugin construction time.

    :ivar setup_rounds: List or tuple of +/-1 indicating the number and
    direction of setup rounds. Setup rounds have string round identifiers
    'setup-0', 'setup-1', .... After the setup rounds complete, the round
    will be set to numeric 0 and play will begin with player 0.


    State Attributes
    ----------------

    Do not modify these attributes directly, use the provided API methods.

    :ivar current_player: Current player number.

    :ivar current_turn: Simple strictly monotonic counter starting at 0
    tracking the current active turn.

    :ivar current_round: Rounds are tracked whenever the current player
    number wraps around, either forward or backward. Games with complicated
    turn order will probably find this value useless and will need to
    create their own round tracker (if needed). If `setup_rounds` is
    non-empty, the setup rounds will not be integers, but instead will be
    strings 'setup-0', 'setup-1', ...

    :ivar setup_state: Current index in the setup_rounds list.
    """
    AMETHYST_PLUGIN_COMPAT  = 1.0
    AMETHYST_ENGINE_METHODS = "_player _player_num _number _round _start _flag _roundflag _playerflag".split()
    AMETHYST_ENGINE_DEFAULT_METHOD_PREFIX = "turn_"

    current_turn   = Attr(int, default=-1)
    current_round  = Attr(default=-1)
    current_player = Attr(int, default=-1)

    setup_rounds   = Attr()
    setup_state    = Attr(default=-1)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Catch likely common mistake of passing a number of setup rounds
        if isinstance(self.setup_rounds, int):
            self.setup_rounds = [1] * self.setup_rounds

    def _start(self, engine, player_num=None, round=None, step=1):
        """
            mygame.turn_start()

        Start the next turn.

        Play direction can be controlled by passing the `step` paramter
        (typically 1 or -1).

        Turn can jump to a specific player by passing the `player_num`
        argument.

        Automatic round tracking can be overrideen by passing the `round`
        parameter. Setting the round to a string value will bypass
        automatic round calculation. Setting the round to a numeric value
        bypasses round calculation for the current method call, but future
        calls may auto-increment the round.

        `setup_rounds` examples:

        * Default sequence of round and player_num:

            0: 0, 1, ... N
            1: 0, 1, ... N

        * Two setup rounds using `setup_rounds=2`

            'setup-0': 0, 1, ... N
            'setup-1': 0, 1, ... N
            0: 0, 1, ... N
            1: 0, 1, ... N

        * Switchback start using `setup_rounds=(1,-1)`

            'setup-0': 0, 1, ... N
            'setup-1': N, N-1, ... 0
            0: 0, 1, ... N
            1: 0, 1, ... N
        """
        num_players = len(engine.players)

        # Setup rounds
        if self.setup_rounds and self.setup_state < len(self.setup_rounds) and player_num is None and round is None:
            if self.setup_state == -1:
                self.setup_state = 0
                player_num = 0 if self.setup_rounds[self.setup_state] > 0 else num_players - 1
            else:
                player_num = self.current_player + self.setup_rounds[self.setup_state]
                if player_num < 0 or player_num >= num_players:
                    self.setup_state += 1
                    player_num = 0 if self.setup_state >= len(self.setup_rounds) or self.setup_rounds[self.setup_state] > 0 else num_players - 1
            if self.setup_state < len(self.setup_rounds):
                round = 'setup-{}'.format(self.setup_state)
            else:
                round = 0

        # Normal rounds
        if player_num is None:
            player_num = self.current_player + step

        if round is None and isinstance(self.current_round, int):
            round = self.current_round
            if self.current_player < 0 or player_num < 0 or player_num > num_players:
                round += 1

        # Current turn is guaranteed unique and predictable turn identifier
        self.current_turn += 1
        if round is not None:
            self.current_round = round
        self.current_player = player_num % num_players

    def _player(self, engine):
        """
            mygame.turn_player()

        Return the currently active player object.
        """
        return engine.players[self.current_player]

    def _player_num(self, engine):
        """
            mygame.turn_player_num()

        Return the currently active player number.
        """
        return self.current_player

    def _number(self, engine):
        """
            mygame.turn_number()

        Return a simple strictly monotonic counter starting at 0 tracking
        the current active turn.
        """
        return self.current_turn

    def _round(self, engine):
        """
            mygame.turn_round()

        Return the current round as understood by this object. Will be
        'setup-0', 'setup-1', ... during setup phase and numeric 0, 1, ...
        afterward unless you explicitly set the round parameter in the
        _start method.
        """
        return self.current_round

    def _flag(self, engine, turn=None):
        if turn is None: turn = self.current_turn
        return "turn:turn-{}".format(turn)

    def _roundflag(self, engine, round=None):
        if round is None: round = self.current_round
        return "turn:round-{}".format(round)

    def _playerflag(self, engine, player=None):
        if player is None: player = self.current_player
        return "turn:player-{}".format(player)
 class ObjDflt(Object):
     foo = Attr(int, default=3)
     bar = Attr(default=list)
     baz = Attr(default=[])
 class ObjNest(Object):
     foo = Attr(int)
     bar = Attr(Obj)
     baz = Attr(isa=Obj)
Exemple #14
0
class Player(Filterable):
    hand = Attr(default=Pile)
    storage = Attr(default=Pile)
 class ObjSub(Obj):
     jsonhooks = {"__bob__": (lambda obj: "BOB")}
     bab = Attr(int)
     flags = Attr(isa=set)
Exemple #16
0
class Pile(Filterable):
    """
    A pile of anything, draw pile, discard pile, player hand, stack of
    creatures, ...

    Randomness is provided by cryptographically secure sources.

    :ivar stack: The actual stack of items. Defaults to a list, but you
    may pass an initial deque if you need that sort of access.
    """
    stack = Attr(isa=STACK_ALLOWED, default=list)

    def __init__(self, *args, **kwargs):
        if len(args) == 1 and isinstance(args[0], STACK_ALLOWED):
            kwargs['stack'] = args.pop()
        super().__init__(*args, **kwargs)

    def __len__(self):
        return len(self.stack)

    def __getitem__(self, idx):
        return self.stack[idx]

    def __iter__(self):
        return self.stack.__iter__()

    def __reversed__(self):
        return self.stack.__reversed__()

    def __contains__(self, needle):
        return needle in self.stack

    def pick(self, n=1, as_list=None):
        """
        Randomly remove n items assuming stack is not shuffled.

        Note: This method is not particularly efficient. If possible,
        shuffle and pop() from your piles.

        Returns item if only one is requested, else returns list.
        """
        if n is None: n = len(self.stack)
        _n, rv = n, list()
        if isinstance(self.stack, collections.deque):
            # Can't .pop(n) a deque. Do some more work:
            tmp = list(self.stack)
            while _n > 0 and tmp:
                _n -= 1
                rv.append(tmp.pop(random.randint(0, len(tmp) - 1)))
            self.stack.clear()
            self.stack.extend(tmp)
        else:
            while _n > 0 and self.stack:
                _n -= 1
                rv.append(
                    self.stack.pop(random.randint(0,
                                                  len(self.stack) - 1)))
        return ctx_return(rv, n, as_list)

    def sample(self, n=1, as_list=None):
        """
        Randomly select n items with repetition assuming stack is not
        shuffled. Stack is left unmodified.

        Returns item if only one is requested, else returns list.
        """
        if n is None: n = len(self.stack)
        k = min(n, len(self.stack))
        rv = random.sample(self.stack, k) if k else []
        return ctx_return(rv, n, as_list)

    def choices(self, n=1, as_list=None):
        """
        Randomly select n items without repetition assuming stack is not
        shuffled. Stack is left unmodified.

        Returns item if only one is requested, else returns list.
        """
        if n is None: n = len(self.stack)
        k = min(n, len(self.stack))
        rv = random.choices(self.stack, k=k) if k else []
        return ctx_return(rv, n, as_list)

    def pop(self, n=1, as_list=None):
        """
        Take top n items off of pile, returning them.

        Returns item if only one is requested, else returns list.
        """
        if n is None: n = len(self.stack)
        _n, rv = n, list()
        while _n > 0 and self.stack:
            _n -= 1
            rv.append(self.stack.pop())
        return ctx_return(rv, n, as_list)

    def peek(self, n=1, as_list=None):
        """
        Look top n items of pile without removing. Items are returned in
        list order, with the top item last.

        Returns item if only one is requested, else returns list.
        """
        if n is None: n = len(self.stack)
        if self.stack:
            j = len(self.stack)
            k = min(j, n)
            # Use itertools so we work on both list() and deque()
            rv = list(itertools.islice(self.stack, j - k, j))
        else:
            rv = list()
        return ctx_return(rv, n, as_list)

    def remove(self, filt=FILTER_ALL):
        """
        Remove items matching a Filter. Return list of the removed items.
        """
        if filt is FILTER_ALL:
            rv = list(self.stack)
            self.stack.clear()
            return rv
        remove, keep = [], []
        for item in self.stack:
            if filt.accepts(item):
                remove.append(item)
            else:
                keep.append(item)
        if remove:
            self.stack.clear()
            self.stack.extend(keep)
        return remove

    def list(self, filt=FILTER_ALL):
        """
        List items matching a Filter.
        """
        return [x for x in self.stack if filt.accepts(x)]

    def count(self, filt=FILTER_ALL):
        """
        Count number of items matching a filter.
        """
        n = 0
        for x in self.stack:
            if filt.accepts(x):
                n += 1
        return n

    def find(self, filt=FILTER_ALL):
        """
        Return list of indices of items matching the filter.
        """
        return [i for (i, x) in enumerate(self.stack) if filt.accepts(x)]

    def shuffle(self):
        random.shuffle(self.stack)

    def insert(self, idx, value):
        self.stack.insert(idx, value)

    def append(self, *args):
        self.stack.extend(args)

    def extend(self, lst):
        self.stack.extend(lst)

    def clear(self):
        self.stack.clear()

    def get(self, idx):
        return self.stack[idx]

    def set(self, idx, value):
        self.stack[idx] = value
 class ObjSer1(Object):
     amethyst_includeclass = False
     amethyst_verifyclass = False
     foo = Attr(int)
     bar = Attr(isa=str).strip()
Exemple #18
0
class Engine(amethyst.games.Engine):
    draw_pile = Attr(default=Pile)
    discard_pile = Attr(default=Pile)
    num_players = Attr(int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.register_plugin(GrantManager())
        self.register_plugin(Turns())
        self.register_plugin(ObjectStore())
        self.register_plugin(BaseGame())

    # Some shortcut methods and properties:
    def new_player(self):
        return self.set_random_player(Player(), self.num_players)

    def grant_current(self, *args, **kwargs):
        if 'kwargs' in kwargs:
            kwargs['kwargs'].setdefault('player_num', self.turn_player_num())
        else:
            kwargs['kwargs'] = dict(player_num=self.turn_player_num())
        return self.grant(self.turn_player_num(), Grant(**kwargs))

    @property
    def player(self):
        return self.turn_player()

    def initialize(self):
        super().initialize()
        cards = []
        cards.extend([
            Card(name="1", flags=set((
                "acorn",
                "number",
            ))) for i in range(30)
        ])
        cards.extend([
            Card(name="2", flags=set((
                "acorn",
                "number",
            ))) for i in range(24)
        ])
        cards.extend([
            Card(name="3", flags=set((
                "acorn",
                "number",
            ))) for i in range(18)
        ])
        cards.extend([
            Card(name="4", flags=set((
                "acorn",
                "number",
            ))) for i in range(12)
        ])
        cards.extend([
            Card(name="5", flags=set((
                "acorn",
                "number",
            ))) for i in range(9)
        ])

        cards.extend(
            [Card(name="quarrel", flags=set(("action", ))) for i in range(8)])
        cards.extend(
            [Card(name="hoard", flags=set(("action", ))) for i in range(8)])
        cards.extend(
            [Card(name="ambush", flags=set(("action", ))) for i in range(6)])
        cards.extend([
            Card(name="whirlwind", flags=set(("action", ))) for i in range(2)
        ])

        cards.append(Card(name="golden", flags=set(("acorn", ))))
        cards.append(Card(name="rotten", flags=set(("acorn", ))))
        cards.append(Card(name="winter", flags=set(("action", ))))

        self.stor_extend_shared(cards)
        self.draw_pile.extend(card.id for card in cards)
 class MyObject(Object):
     amethyst_register_type = False
     foo = Attr(int)
     bar = Attr(isa=six.text_type).strip()
Exemple #20
0
class BaseGame(amethyst.games.EnginePlugin):
    AMETHYST_PLUGIN_COMPAT = 1  # Plugin API version
    drawn_this_turn = Attr(bool)

    def initial_deal(self, game):
        game.draw_pile.shuffle()
        reshuffle = False
        discard = []
        for p in game.players:
            while len(p.hand) < 7:
                card_id = game.draw_pile.pop()
                card = game.stor_get(card_id)
                if 'action' in card.flags:
                    if card.name == "Winter":
                        reshuffle = True
                    discard.append(card_id)
                else:
                    p.hand.append(card_id)

        if reshuffle:
            game.draw_pile.extend(discard)
            game.draw_pile.shuffle()
        else:
            game.discard_pile.extend(discard)

    @action
    def begin(self, game, stash, player_num=None, hand=None):
        if game.is_server():
            self.initial_deal(game)
        elif player_num and hand:
            game.players[player_num].hand.extend(hand)
        game.schedule('start_turn')

    @begin.notify
    def begin(self, game, stash, player_num, kwargs):
        if player_num is not None:
            kwargs['player_num'] = player_num
            kwargs['hand'] = list(game.players[player_num].hand)
        return kwargs

    @action
    def start_turn(self, game, stash):
        game.turn_start()
        self.drawn_this_turn = False
        # Grant a draw action, but do not allow the client to tell us what
        # they drew. In this simple game, the forced kwarg is not necessary
        # (we unconditionally overwrite the `drawn` parameter below), but
        # forcing may be useful in more advanced games. NOTE: The forced
        # parameter is not secret - all players will see it. Use a local
        # dictionary with random keys if you need to keep a secret forced
        # parameter.
        game.grant_current(name="draw", kwargs=dict(drawn=None))

    @action
    def draw(self, game, stash, player_num, drawn=None):
        # When we draw from the server, we actually take the next card off
        # the deck. The client, however, takes the card received from the
        # server's notification (see draw.notify below).
        #
        # Notice that the server overwrites anything the client may have
        # sent in `drawn` (even though we also forced drawn=None in the grant).
        if game.is_server():
            drawn = game.draw_pile.pop()

        # Save the drawn card for notification later
        stash['drawn'] = drawn

        # OPTIONAL: The local game doesn't care about the draw pile since
        # it isn't really used. We could, however, keep it up to date if
        # our game required it:
        # ** if drawn is not None: game.draw_pile.stack.remove(drawn)

        # TODO: If an action card, play it immediately

        # Save the drawn card to the current player's hand
        if drawn is not None:
            game.player.hand.append(drawn)

        # After drawing, what can we do?
        if not self.drawn_this_turn:
            self.drawn_this_turn = True
            game.grant_current(name="store")
            game.grant_current(name="discard")
        if len(game.player.hand) < 7:
            game.grant_current(name="draw", kwargs=dict(drawn=None))

    @draw.check
    def draw(self, game, stash, player_num, drawn=None):
        return len(game.player.hand) < 7 or not self.drawn_this_turn

    @draw.notify
    def draw(self, game, stash, player_num, kwargs):
        # Send the card drawn only to the current player
        if player_num == game.turn_player_num():
            kwargs['drawn'] = stash['drawn']

    @action
    def discard(self, game, stash, player_num, card):
        game.expire()
        game.call_immediate('end_turn', kwargs=dict(player_num=player_num))

    @action
    def store(self, game, stash, player_num, cards):
        game.expire(Filter(name="draw"))
        print("STORE", cards, stash['hand_objs'])
        game.grant_current(name="store")

    @store.check
    def store(self, game, stash, player_num, cards):
        if 3 != len(cards):
            return False
        objs = game.player.hand.find(Filter(id=set(cards)))
        if 3 != len(objs):
            return False
        stash['hand_objs'] = objs
        return True

    @action
    def end_turn(self, game, stash, player_num):
        game.commit()
        game.call_immediate('start_turn')
Exemple #21
0
class ObjectStore(EnginePlugin):
    """
    Provide a "distributed" item lookup table.

    The object store is a dictionary with a public (shared) part and
    private (per-player) parts. Items added to or removed from the storage
    (via the API) will trigger a notice so that clients can keep their
    storage up to date.

    WARNING: The ObjectStore WILL NOT see changes made directly to your
    stored objects. If data internal to the stored object changes, you will
    need to call the appropriate `stor_set` in order to send the updated
    object to clients.
    """
    AMETHYST_PLUGIN_COMPAT = 1.0
    AMETHYST_ENGINE_METHODS = """
    _get _del
    _get_player _set_player _del_player _list_player
    _get_shared _set_shared _del_shared _list_shared
    """.split()
    AMETHYST_ENGINE_DEFAULT_METHOD_PREFIX = "stor_"

    # These are not meant to be accessed directly
    _storage = Attr(isa=dict, default=dict)
    _player_storage = Attr(isa=dict, default=dict)

    def _get(self, game, id):
        """
        Get an item by ID from the store. Searches the shared store first, then
        individual player stores (in random order).
        """
        if id in self._storage:
            return self._storage[id]
        for stor in self._player_storage.values():
            if id in stor:
                return stor[id]

    def _del(self, game, *ids):
        """Delete key(s) from shared and all per-player stores. Returns None."""
        for id in ids:
            self._storage.pop(id, None)
            for stor in self._player_storage.values():
                stor.pop(id, None)
        game.notify(
            None,
            Notice(source=self.id,
                   type=NoticeType.STORE_DEL,
                   data=dict(all=ids)))

    def _get_shared(self, game, id, dflt=None):
        """Retrieve an item from shared storage"""
        return self._storage.get(id, dflt)

    def _set_shared(self, game, id, obj):
        """Set or update an item in shared storage"""
        self._storage[id] = obj
        game.notify(
            None,
            Notice(source=self.id,
                   type=NoticeType.STORE_SET,
                   data=dict(shared={id: obj})))
        return id

    def _del_shared(self, game, *ids):
        """Delete key(s) from shared storage. Returns None."""
        for id in ids:
            self._storage.pop(id, None)
        game.notify(
            None,
            Notice(source=self.id,
                   type=NoticeType.STORE_DEL,
                   data=dict(shared=ids)))

    def _list_shared(self, game, filt=FILTER_ALL):
        """Return a list of items in shared storage matching a filter."""
        return [x for x in self._storage.values() if filt.accepts(x)]

    def _get_player(self, game, player_num, id, dflt=None):
        """Retrieve an item from a player storage"""
        if player_num in self._player_storage:
            return self._player_storage[player_num].get(id, dflt)
        return dflt

    def _set_player(self, game, player_num, id, obj):
        """Set or update an item in player storage"""
        self._player_storage[player_num][id] = obj
        game.notify(
            player_num,
            Notice(
                source=self.id,
                type=NoticeType.STORE_SET,
                data=dict(player={player_num: {
                    id: obj
                }}),
            ))
        return id

    def _del_player(self, game, player_num, *ids):
        """Delete key(s) from player storage. Returns None."""
        if player_num in self._player_storage:
            for id in ids:
                self._player_storage[player_num].pop(id, None)
        game.notify(
            player_num,
            Notice(
                source=self.id,
                type=NoticeType.STORE_DEL,
                data=dict(player={player_num: ids}),
            ))

    def _list_player(self, game, player_num, filt=FILTER_ALL):
        """Return a list of items in player storage matching a filter."""
        return [
            x for x in self._player_storage[player_num].values()
            if filt.accepts(x)
        ]

    @event_listener(NoticeType.STORE_SET)
    def on_store_set(self, game, seq, player_num, notice):
        """Process a Notice from our upstream."""
        if notice.source == self.id:
            if 'shared' in notice.data:
                for k, v in notice.data['shared'].items():
                    self._set_shared(game, k, v)
            if 'player' in notice.data:
                for p, data in notice.data['player'].items():
                    for k, v in data.items():
                        self._set_player(game, p, k, v)

    @event_listener(NoticeType.STORE_DEL)
    def on_store_del(self, game, seq, player_num, notice):
        """Process a Notice from our upstream."""
        if notice.source == self.id:
            if 'all' in notice.data:
                self._del(game, *notice.data['all'])
            if 'shared' in notice.data:
                self._del_shared(game, *notice.data['shared'])
            if 'player' in notice.data:
                for p, data in notice.data['player'].items():
                    self._del_player(game, p, *data)

    def get_state(self, player_num):
        state = dict(_storage=copy.deepcopy(self._storage))
        if player_num is GAME_MASTER:
            # save game: append ALL objects
            state['_player_storage'] = copy.deepcopy(self._player_storage)
        elif player_num is NOBODY:
            pass  # kibbitzer, only public knowledge
        elif player_num in self._player_storage:
            # Specific player, hide others' data
            state['_player_storage'] = {
                player_num: copy.deepcopy(self._player_storage[player_num])
            }
        return state
Exemple #22
0
class MyObject(amethyst.core.Object):
    amethyst_register_type = False
    foo = Attr(set_of(int))
    bar = Attr(list_of(float))
    baz = Attr(dict_of('MyObject'))
class ObjectStore(EnginePlugin):
    AMETHYST_PLUGIN_COMPAT = 1.0
    AMETHYST_ENGINE_METHODS = """
    _get
    _get_player _set_player
    _get_shared _set_shared
    _extend_shared
    """.split()
    AMETHYST_ENGINE_DEFAULT_METHOD_PREFIX = "stor_"

    storage = Attr(isa=dict, default=dict)
    player_storage = Attr(isa=dict, default=dict)

    def _get(self, game, id):
        if id in self.storage:
            return self.storage[id]
        for stor in self.player_storage.values():
            if id in stor:
                return stor[id]

    def _get_shared(self, game, id):
        return self.storage.get(id, None)

    def _set_shared(self, game, id, obj):
        if id is None:
            id = self.identify(obj)
        self.storage[id] = obj
        game.notify(
            None,
            Notice(source=self.id,
                   type=NoticeType.STORE_SET,
                   data=dict(shared=dict(id=obj))))
        return id

    def _extend_shared(self, game, items):
        dd = dict()
        for x in items:
            if not isinstance(x, Filterable):
                raise Exception(
                    "May only bulk-insert Filterable objects into storage")
            dd[x.id] = x
        self.storage.update(dd)
        game.notify(
            None,
            Notice(source=self.id,
                   type=NoticeType.STORE_SET,
                   data=dict(shared=dd)))

    @event_listener(NoticeType.STORE_SET)
    def on_store_set(self, game, seq, player_num, notice):
        """Process a Notice FROM the server."""
        if game.is_client() and notice.source == self.id:
            if 'shared' in notice.data:
                self.storage.update(notice.data['shared'])
            if 'player' in notice.data:
                for p, data in notice.data['player'].items():
                    if p not in self.player_storage:
                        self.player_storage[p] = dict()
                    self.player_storage[p].update(data)

    def _get_player(self, game, player_num, id):
        if player_num in self.player_storage:
            return self.player_storage[player_num].get(id, None)
        return None

    def _set_player(self, game, player_num, id, obj):
        if id is None:
            id = self.identify(obj)
        self.player_storage[player_num][id] = obj
        game.notify(
            player_num,
            Notice(
                source=self.id,
                type=NoticeType.STORE_SET,
                data=dict(player={player_num: {
                    id: obj
                }}),
            ))
        return id

    def identify(self, obj):
        if isinstance(obj, Filterable):
            return obj.id
        else:
            return nonce()

    def get_state(self, player_num):
        state = dict(storage=copy.deepcopy(self.storage))
        if player_num is GAME_MASTER:
            # save game: append ALL objects
            state['player_storage'] = copy.deepcopy(self.player_storage)
        elif player_num is NOBODY:
            pass  # kibbitzer, only public knowledge
        elif player_num in self.player_storage:
            # Specific player, hide others' data
            state['player_storage'] = {
                player_num: copy.deepcopy(self.player_storage[player_num])
            }
        return state
 class ObjSub1(Obj):
     foo = Attr(int)
class Obj(Object):
    foo = Attr(int)
    bar = Attr(isa=str).strip()
    baz = Attr(float)
    bip = Attr()
class Notice(Filterable):
    source = Attr(isa=str)
    data = Attr(isa=dict)
 class MyObject(Object):
     amethyst_register_type = False
     foo_int      = Attr(int)
     foo_floatint = Attr(float).int()
     foo_posint   = 0 < Attr(int)
     foo_proofint = (0 <= Attr(int)) <= 200     # Parens are necessary!
 class MyObject(Object):
     amethyst_verifyclass = False
     amethyst_register_type = False
     foo = Attr(int)
     bar = Attr(isa=str).strip()
        class MyObject2(Object):
            amethyst_includeclass  = False
            amethyst_verifyclass   = False

            foo = Attr(int)
            bar = Attr(isa=six.text_type).strip()
 class ObjSer(Object):
     foo = Attr(int)
     bar = Attr(Obj)