You can subscribe to this list here.
2006 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
(8) |
Sep
(3) |
Oct
(5) |
Nov
|
Dec
(1) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2007 |
Jan
|
Feb
(7) |
Mar
(17) |
Apr
(37) |
May
|
Jun
(46) |
Jul
(40) |
Aug
(2) |
Sep
(4) |
Oct
(2) |
Nov
|
Dec
|
2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <umg...@us...> - 2007-04-01 17:41:28
|
Revision: 382 http://svn.sourceforge.net/pybridge/?rev=382&view=rev Author: umgangee Date: 2007-04-01 10:41:14 -0700 (Sun, 01 Apr 2007) Log Message: ----------- SimpleEventHandler implementation of IListener: redirects update() calls to methods of its target object. Modified Paths: -------------- trunk/pybridge/pybridge/ui/eventhandler.py Modified: trunk/pybridge/pybridge/ui/eventhandler.py =================================================================== --- trunk/pybridge/pybridge/ui/eventhandler.py 2007-04-01 17:39:19 UTC (rev 381) +++ trunk/pybridge/pybridge/ui/eventhandler.py 2007-04-01 17:41:14 UTC (rev 382) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -18,15 +18,33 @@ from zope.interface import implements -from pybridge.interfaces.table import ITableEvents -from pybridge.interfaces.bridgetable import IBridgeTableEvents -from pybridge.interfaces.serverstate import IServerEvents +from pybridge.interfaces.observer import IListener +class SimpleEventHandler: + """An implementation of IListener which redirects updates to its target.""" + + implements(IListener) + + + def __init__(self, target, prefix='event_'): + self.__target = target + self.__prefix = prefix + + + def update(self, event, *args, **kwargs): + """Redirects named event to target's handler method, if present.""" + method = getattr(self.__target, "%s%s" % (self.__prefix, event), None) + if method: + method(*args, **kwargs) + + + + class EventHandler: """An implementation of ITableEvents.""" - implements(IServerEvents, ITableEvents, IBridgeTableEvents) + #implements(IServerEvents, ITableEvents, IBridgeTableEvents) def __init__(self): @@ -37,7 +55,7 @@ def connectionLost(self, connector, reason): - print "lost connection -", reason.getErrorMessage() + print "Lost connection:", reason.getErrorMessage() self.runCallbacks('connectionLost', connector, reason) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:39:18
|
Revision: 381 http://svn.sourceforge.net/pybridge/?rev=381&view=rev Author: umgangee Date: 2007-04-01 10:39:19 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Change Player references to Direction type. Modified Paths: -------------- trunk/pybridge/pybridge/ui/cardarea.py Modified: trunk/pybridge/pybridge/ui/cardarea.py =================================================================== --- trunk/pybridge/pybridge/ui/cardarea.py 2007-04-01 17:37:24 UTC (rev 380) +++ trunk/pybridge/pybridge/ui/cardarea.py 2007-04-01 17:39:19 UTC (rev 381) @@ -25,7 +25,7 @@ from canvas import CairoCanvas from pybridge.bridge.card import Card -from pybridge.bridge.symbols import Player, Rank, Suit +from pybridge.bridge.symbols import Direction, Rank, Suit # The order in which card graphics are expected in card mask. CARD_MASK_RANKS = [Rank.Ace, Rank.Two, Rank.Three, Rank.Four, Rank.Five, @@ -64,7 +64,7 @@ self.hands = {} self.trick = None - self.set_player_mapping(Player.South) + self.set_player_mapping(Direction.South) self.connect('button_press_event', self.button_press) self.add_events(gtk.gdk.BUTTON_PRESS_MASK) @@ -98,7 +98,7 @@ infrequently and multiple calls to draw_card() are expensive. @param hand: a list of Card objects. - @param player: a member of Player. + @param player: a member of Direction. @param facedown: if True, cards are drawn face-down. @param omit: a list of elements of hand not to draw. """ @@ -218,13 +218,13 @@ self.add_item(id, surface, xy[player], 2) - def set_player_mapping(self, focus=Player.South): + def set_player_mapping(self, focus=Direction.South): """Sets the mapping between players at table and positions of hands. - @param focus: the Player to be drawn "closest" to the observer. + @param focus: the Direction to be drawn "closest" to the observer. """ - # Assumes Player elements are ordered clockwise from North. - order = Player[focus.index:] + Player[:focus.index] + # Assumes Direction elements are ordered clockwise from North. + order = Direction[focus.index:] + Direction[:focus.index] for player, attr in zip(order, ('BOTTOM', 'LEFT', 'TOP', 'RIGHT')): setattr(self, attr, player) # TODO: set player labels. @@ -240,9 +240,9 @@ self.LEFT : (0.425, 0.5), self.RIGHT : (0.575, 0.5), } if trick: - # The order of play is the leader, then clockwise around Player. + # The order of play is the leader, then clockwise around Direction. leader = trick[0] - order = Player[leader.index:] + Player[:leader.index] + order = Direction[leader.index:] + Direction[:leader.index] for i, player in enumerate(order): id = 'trick-%s' % player old_card = self.trick and self.trick[1].get(player) or None @@ -272,14 +272,15 @@ The hand of the player on turn is drawn opaque; the other hands are drawn translucent. - @param turn: a member of Player, or None. + @param turn: the position of the turn indicator. + @type turn: Direction or None """ if turn is None: return - for player in Player: - opacity = (player is turn) and 1 or 0.5 - self.update_item('hand-%s' % player, opacity=opacity) + for position in Direction: + opacity = (position is turn) and 1 or 0.5 + self.update_item('hand-%s' % position, opacity=opacity) def button_press(self, widget, event): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:37:23
|
Revision: 380 http://svn.sourceforge.net/pybridge/?rev=380&view=rev Author: umgangee Date: 2007-04-01 10:37:24 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Code cleanup; replace "assert <condition>" statements with "if not <condition>: raise TypeError" statements. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/bidding.py Modified: trunk/pybridge/pybridge/bridge/bidding.py =================================================================== --- trunk/pybridge/pybridge/bridge/bidding.py 2007-04-01 17:35:52 UTC (rev 379) +++ trunk/pybridge/pybridge/bridge/bidding.py 2007-04-01 17:37:24 UTC (rev 380) @@ -16,7 +16,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from call import Call, Bid, Pass, Double, Redouble +from pybridge.network.error import GameError + +from call import Bid, Pass, Double, Redouble from symbols import Direction, Level, Strain @@ -39,17 +41,18 @@ and the last 3 calls are Pass calls. @return: True if bidding is complete, False if not. + @rtype: bool """ passes = len([c for c in self.calls[-3:] if isinstance(c, Pass)]) return len(self.calls) >= 4 and passes == 3 def isPassedOut(self): - """Bidding is passed out if each player has called Pass on their - first turn. This implies no contract has been established. - Note that this is a special case of isComplete(). - + """Bidding is passed out if each player has passed on their first turn. + In this case, the bidding is complete, but no contract is established. + @return: True if bidding is passed out, False if not. + @rtype: bool """ passes = len([call for call in self.calls if isinstance(call, Pass)]) return len(self.calls) == 4 and passes == 4 @@ -58,22 +61,24 @@ def getContract(self): """When the bidding is complete, the contract is the last and highest bid, which may be doubled or redoubled. - + Hence, the contract represents the "final state" of the bidding. - @return['bid']: the last and highest bid. - @return['declarer']: the partner who first called the contract strain. - @return['doubleBy']: the opponent who doubled the contract, or None. - @return['redoubleBy']: the partner who redoubled an opponent's double - on the contract, or None. + @return: a dict containing the keywords: + @keyword bid: the last and highest bid. + @keyword declarer: the partner who first bid the contract strain. + @keyword doubleBy: the opponent who doubled the contract, or None. + @keyword redoubleBy: the partner who redoubled an opponent's double + on the contract, or None. """ - bid = self.getCurrentCall(Bid) - if bid and self.isComplete() and not self.isPassedOut(): + if self.isComplete() and not self.isPassedOut(): + bid = self.getCurrentCall(Bid) double = self.getCurrentCall(Double) redouble = self.getCurrentCall(Redouble) + # Determine partnership. + caller = self.whoCalled(bid) + partnership = (caller, Direction[(caller.index + 2) % 4]) # Determine declarer. - partnership = (self.whoCalled(bid), \ - Direction[(self.whoCalled(bid).index + 2) % 4]) for call in self.calls: if isinstance(call, Bid) and call.strain == bid.strain \ and self.whoCalled(call) in partnership: @@ -87,15 +92,17 @@ return None # Bidding passed out or not complete, no contract. - def getCurrentCall(self, type): + def getCurrentCall(self, calltype): """Returns most recent current call of specified type, or None. - @param type: call type, in (Bid, Pass, Double, Redouble). + @param calltype: call type, in (Bid, Pass, Double, Redouble). @return: most recent call matching type, or None. """ - assert issubclass(type, Call) + if calltype not in (Bid, Pass, Double, Redouble): + raise GameError, "Expected call type, got %s" % type(calltype) + for call in self.calls[::-1]: - if isinstance(call, type): + if isinstance(call, calltype): return call elif isinstance(call, Bid): break @@ -108,13 +115,14 @@ @param call: the Call object representing player's call. @param player: the player making call, or None. """ - assert isinstance(call, Call) - valid = self.isValidCall(call, player) - assert valid - if valid: # In case assert is disabled. - self.calls.append(call) + if not isinstance(call, (Bid, Pass, Double, Redouble)): + raise GameError, "Expected call type, got %s" % type(call) + if not self.isValidCall(call, player): + raise GameError, "Invalid call" + self.calls.append(call) + def isValidCall(self, call, player=None): """Check that specified call is available to player, with respect to current state of bidding. If specified, player's turn will be checked. @@ -123,7 +131,8 @@ @param player: the player attempting to call, or None. @return: True if call is available, False if not. """ - assert isinstance(call, Call) + if not isinstance(call, (Bid, Pass, Double, Redouble)): + raise GameError, "Expected call type, got %s" % type(call) assert player in Direction or player is None # The bidding must not be complete. @@ -172,7 +181,9 @@ @param call: a Call. @return: the player who made call, or False. """ - assert isinstance(call, Call) + if not isinstance(call, (Bid, Pass, Double, Redouble)): + raise GameError, "Expected call type, got %s" % type(call) + if call in self.calls: return Direction[(self.calls.index(call) + self.dealer.index) % 4] return False # Call not made by any player. @@ -185,6 +196,6 @@ @rtype: Direction """ if self.isComplete(): - return None + raise GameError, "Bidding complete" return Direction[(len(self.calls) + self.dealer.index) % 4] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:35:51
|
Revision: 379 http://svn.sourceforge.net/pybridge/?rev=379&view=rev Author: umgangee Date: 2007-04-01 10:35:52 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Implement robust revealHand (to all observers) and getHand (for a player). Modified Paths: -------------- trunk/pybridge/pybridge/bridge/game.py Modified: trunk/pybridge/pybridge/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/bridge/game.py 2007-04-01 17:33:51 UTC (rev 378) +++ trunk/pybridge/pybridge/bridge/game.py 2007-04-01 17:35:52 UTC (rev 379) @@ -27,7 +27,9 @@ from board import Board from playing import Playing -from symbols import Direction, Suit, Strain +from call import Bid, Pass, Double, Redouble +from card import Card +from symbols import Direction, Suit, Strain, Vulnerable class BridgeGame(object): @@ -62,6 +64,7 @@ self.play = None self.boardQueue = [] # Boards for successive games. + self.visibleHands = {} # A subset of deal, containing revealed hands. self.players = {} # One-to-one mapping from Direction to BridgePlayer. @@ -79,10 +82,12 @@ self.bidding = Bidding(self.board['dealer']) # Start bidding. self.contract = None self.play = None + self.visibleHands.clear() - # Remove deal from board, so it does not appear to players. + # Remove deal from board, so it does not appear to clients. visibleBoard = self.board.copy() - del visibleBoard['deal'] + visibleBoard['deal'] = self.visibleHands + self.notify('start', board=visibleBoard) @@ -96,13 +101,12 @@ def getState(self): - # TODO: all flag? state = {} if self.inProgress(): - # Remove deal from board, so it does not appear to players. + # Remove hidden hands from deal. visibleBoard = self.board.copy() - del visibleBoard['deal'] + visibleBoard['deal'] = self.visibleHands state['board'] = visibleBoard if self.bidding: @@ -123,33 +127,23 @@ if state.get('board'): self.start(state['board']) - # Comprehensive error checking is suppressed. for call in state.get('calls', []): turn = self.getTurn() - self.makeCall(call, self.players[turn]) -# self.bidding.makeCall(call, player=turn) + self.makeCall(call, turn) - # If a contract has been reached, start the play. -# contract = self.bidding.getContract() -# if contract: -# self.contract = contract -# trumpSuit = self.trumpMap[self.contract['bid'].strain] -# self.play = Playing(contract['declarer'], trumpSuit) - - # Comprehensive error checking is suppressed. for card in state.get('played', []): turn = self.getTurn() - self.playCard(card, self.players[turn]) - #self.play.playCard(card, player=turn) + # TODO: remove this hack + if turn == self.play.dummy: + turn = self.play.declarer + self.playCard(card, turn) def updateState(self, event, *args, **kwargs): - allowed = ['start', 'makeCall', 'playCard'] + allowed = ['start', 'makeCall', 'playCard', 'revealHand'] if event in allowed: handler = getattr(self, event) handler(*args, **kwargs) -# else: -# print "updateState unknown attempted", event def addPlayer(self, position): @@ -158,7 +152,7 @@ if position in self.players: raise GameError, "Position %s is taken" % position - player = BridgePlayer(self) + player = BridgePlayer(self, position) self.players[position] = player self.notify('addPlayer', position=position) @@ -195,107 +189,128 @@ # Bridge-specific methods. - def makeCall(self, call, player): + def makeCall(self, call, position): """Make a call in the current bidding session. - @param call: the call. + @param call: a Call object. @type call: Bid or Pass or Double or Redouble - @param player: a player identifier. - @type player: BridgePlayer + @param position: the position of the player making the call. + @type position: Direction """ - position = self.getPositionOfPlayer(player) - if position is None: - raise GameError, "Invalid player reference" + if not isinstance(call, (Bid, Pass, Double, Redouble)): + raise TypeError, "Expected Call, got %s" % type(call) + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) # Validate call according to game state. if not self.bidding or self.bidding.isComplete(): raise GameError, "Game not running or bidding complete" - if self.getTurn() is not position: + if self.getTurn() != position: raise GameError, "Call made out of turn" if not self.bidding.isValidCall(call, position): raise GameError, "Call cannot be made" self.bidding.makeCall(call, position) + + if self.bidding.isComplete() and not self.bidding.isPassedOut(): + self.contract = self.bidding.getContract() # TODO: make a property + trumpSuit = self.trumpMap[self.contract['bid'].strain] + self.play = Playing(self.contract['declarer'], trumpSuit) + self.notify('makeCall', call=call, position=position) - if self.bidding.isComplete(): - # If a contract has been reached, start the play. - contract = self.bidding.getContract() - if contract: - self.contract = contract - trumpSuit = self.trumpMap[self.contract['bid'].strain] - self.play = Playing(contract['declarer'], trumpSuit) - elif self.bidding.isPassedOut(): - self.notify('gameOver') + if self.bidding.isPassedOut(): + self.notify('gameOver') # TODO: reveal all hands - def signalAlert(self, alert, player): + def signalAlert(self, alert, position): pass # TODO - def playCard(self, card, player): + def playCard(self, card, position): """Play a card in the current play session. + The position specified is that of the player of the card: in particular, + declarer plays cards from dummy's hand when it is dummy's turn. + @param card: a Card object. - @param player: a BridgePlayer object. + @type card: Card + @param position: the position of the player playing the card. + @type position: Direction """ - position = self.getPositionOfPlayer(player) - if position is None: - raise GameError, "Invalid player reference" + if not isinstance(card, Card): + raise TypeError, "Expected Card, got %s" % type(card) + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + if not self.play or self.play.isComplete(): raise GameError, "Game not running or play complete" - if self.getTurn() is self.play.dummy: # Dummy's turn. - if position is self.play.declarer: - position = self.play.dummy # Declarer can play dummy's cards. - elif position is self.play.dummy: - raise GameError, "Dummy cannot play cards" - elif self.getTurn() is not position: + playfrom = position + + # Declarer controls dummy's turn. + if self.getTurn() == self.play.dummy: + if position == self.play.declarer: + playfrom = self.play.dummy # Declarer can play from dummy. + elif position == self.play.dummy: + raise GameError, "Dummy cannot play hand" + + if self.getTurn() != playfrom: raise GameError, "Card played out of turn" - hand = self.board['deal'][position] # May be empty, if hand unknown. - if not self.play.isValidPlay(card, position, hand): + hand = self.board['deal'].get(playfrom, []) # Empty if hand unknown. + if not self.play.isValidPlay(card, playfrom, hand): raise GameError, "Card cannot be played from hand" self.play.playCard(card) self.notify('playCard', card=card, position=position) + # Dummy's hand is revealed when the first card of first trick is played. + if len(self.play.getTrick(0)[1]) == 1: + dummyhand = self.board['deal'].get(self.play.dummy) + if dummyhand: # Reveal hand only if known. + self.revealHand(dummyhand, self.play.dummy) + # TODO: if game over, reveal all hands + - def getHand(self, position, player): + def revealHand(self, hand, position): + """Reveal hand to all observers. + + @param hand: a hand of Card objects. + @type hand: list + @param position: the position of the player with hand. + @type position: Direction + """ + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + + self.visibleHands[position] = hand + # Add hand to board only if it was previously unknown. + if not self.board['deal'].get(position): + self.board['deal'][position] = hand + + self.notify('revealHand', hand=hand, position=position) + + + def getHand(self, position): """If specified hand is visible, returns the list of cards in hand. - A hand is visible if one of the following conditions is met: - - 1. The hand is the player's own hand. - 2. The game is finished. - 3. The bidding is complete and the hand is dummy's, and first card of - first trick has been played. - - @param position: the hand identifier. + @param position: the position of the requested hand. @type position: Direction - @param player: a player identifier. - @type player: BridgePlayer + @return: the hand of player at position. """ - viewer = self.getPositionOfPlayer(player) - if viewer is None: - raise GameError, "Invalid player reference" - if not self.inProgress() and self.bidding is None: - raise GameError, "No game in progress" + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) - if player == viewer or not self.inProgress(): - return self.board.deal[position] - if self.bidding.isComplete() and position == self.play.dummy: - leader, cards = self.play.getTrick(0) - if len(cards) >= 1: - return self.board.deal[position] + if self.board and self.board['deal'].get(position): + return self.board['deal'][position] + else: + raise GameError, "Hand unknown" - # At this point, checks have been exhausted. - raise GameError, "Hand is not visible" - def getTurn(self): if self.inProgress(): - if self.play: # Currently in the play. + if self.bidding.isComplete(): # In trick play. return self.play.whoseTurn() else: # Currently in the bidding. return self.bidding.whoseTurn() @@ -355,10 +370,13 @@ contract = self.bidding.getContract() declarer = contract['declarer'] - dummy = Seat[(declarer.index + 2) % 4] - vulnerable = (self.vulnNS and declarer in (Seat.North, Seat.South)) + \ - (self.vulnEW and declarer in (Seat.West, Seat.East)) + dummy = Direction[(declarer.index + 2) % 4] + if declarer in (Direction.North, Direction.South): + vulnerable = (self.board['vuln'] in (Vulnerable.NorthSouth, Vulnerable.All)) + else: # East or West + vulnerable = (self.board['vuln'] in (Vulnerable.EastWest, Vulnerable.All)) + tricksMade = 0 # Count of tricks won by declarer or dummy. for index in range(len(self.play.winners)): trick = self.play.getTrick(index) @@ -371,36 +389,28 @@ return self.scoring(result) - def getPositionOfPlayer(self, player): - """If player is playing, returns position of player, otherwise None. - - @param player: a BridgePlayer object. - @type player: BridgePlayer - @return: the position of player. - @rtype: Direction or None - """ - for position, p in self.players.items(): - if p == player: - return position - return None - - class BridgePlayer(pb.Referenceable): """Actor representing a player's view of a BridgeGame object.""" - def __init__(self, game): + def __init__(self, game, position): self.__game = game # Provide access to game only through this object. + self.__position = position + def getHand(self): + return self.__game.getHand(self.__position) + + def makeCall(self, call): - self.__game.makeCall(call, player=self) + return self.__game.makeCall(call, self.__position) def playCard(self, card): - self.__game.playCard(card, player=self) + # TODO: need try/except block on each. + return self.__game.playCard(card, self.__position) def nextGame(self): @@ -409,11 +419,7 @@ # Aliases for remote-callable methods. + remote_getHand = getHand + remote_makeCall = makeCall + remote_playCard = playCard - def remote_makeCall(self, call): - self.makeCall(call) - - - def remote_playCard(self, card): - self.playCard(card) - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:33:50
|
Revision: 378 http://svn.sourceforge.net/pybridge/?rev=378&view=rev Author: umgangee Date: 2007-04-01 10:33:51 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Use (some) magic to determine game type; remove unnecessary conversion of Direction type. Modified Paths: -------------- trunk/pybridge/pybridge/network/remotetable.py Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-04-01 17:32:25 UTC (rev 377) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-04-01 17:33:51 UTC (rev 378) @@ -24,6 +24,11 @@ from pybridge.network.error import DeniedRequest, IllegalRequest +# TODO: move to somewhere more appropriate. +from pybridge.bridge.game import BridgeGame +GAMETYPES = {'BridgeGame' : BridgeGame} + + class RemoteTable(pb.RemoteCache): """A client-side implementation of ITable providing a "front-end" to a remote server-side LocalTable. @@ -42,24 +47,22 @@ self.id = None self.game = None + self.gametype = None self.observers = [] # Observers of master table. self.players = {} # Positions mapped to player identifiers. def setCopyableState(self, state): self.id = state['id'] - self.observers = state['observers'] - self.players = state['players'] - - # TODO: do this by magic. - if state['gametype'] in ['BridgeGame']: - from pybridge.bridge.game import BridgeGame - self.gametype = BridgeGame + if state['gametype'] in GAMETYPES: + self.gametype = GAMETYPES[state['gametype']] + self.game = self.gametype() + self.game.setState(state['gamestate']) else: raise NameError, "Unknown game type %s" % state['gametype'] - self.game = self.gametype() - self.game.setState(state['gamestate']) + self.observers = state['observers'] + self.players = state['players'] # Implementation of ITable. @@ -70,12 +73,12 @@ def joinGame(self, position, user=None): - d = self.master.callRemote('joinGame', position.key) + d = self.master.callRemote('joinGame', position) return d def leaveGame(self, position, user=None): - d = self.master.callRemote('leaveGame', position.key) + d = self.master.callRemote('leaveGame', position) return d @@ -114,13 +117,11 @@ def observe_joinGame(self, player, position): - position = getattr(self.game.positions, position) self.players[position] = player self.notify('joinGame', player, position) def observe_leaveGame(self, player, position): - position = getattr(self.game.positions, position) del self.players[position] self.notify('leaveGame', player, position) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:32:29
|
Revision: 377 http://svn.sourceforge.net/pybridge/?rev=377&view=rev Author: umgangee Date: 2007-04-01 10:32:25 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Fix error dump when a listener abruptly stops observing; accept config dict at initialisation. Modified Paths: -------------- trunk/pybridge/pybridge/network/localtable.py Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-04-01 17:28:46 UTC (rev 376) +++ trunk/pybridge/pybridge/network/localtable.py 2007-04-01 17:32:25 UTC (rev 377) @@ -36,7 +36,7 @@ implements(ITable, ISubject, IListener) - def __init__(self, id, gametype): + def __init__(self, id, gametype, config={}): self.listeners = [] self.id = id @@ -44,14 +44,16 @@ self.game = gametype() # Initialise game. self.game.attach(self) # Listen for game events. - self.config = {} self.observers = {} # For each user perspective, a remote ITableEvents. self.players = {} # Positions mapped to perspectives of game players. self.view = LocalTableViewable(self) # For remote clients. - + # Configuration variables. - self.config['closeWhenEmpty'] = True - self.config['timeCreated'] = time.localtime() + self.config = {} + self.config['CloseWhenEmpty'] = True + self.config['MultiplePlayersPerUser'] = False + self.config['TimeCreated'] = tuple(time.localtime()) + self.config.update(config) def getStateToCacheAndObserveFor(self, perspective, observer): @@ -62,26 +64,26 @@ # Build a dict of public information about the table. state = {} state['id'] = self.id + state['gametype'] = self.gametype.__name__ + state['gamestate'] = self.game.getState() state['observers'] = [p.name for p in self.observers.keys()] state['players'] = dict([(pos, p.name) for pos, p in self.players.items()]) - state['gametype'] = self.gametype.__name__ - state['gamestate'] = self.game.getState() return state # To observer. def stoppedObserving(self, perspective, observer): + del self.observers[perspective] + # If user was playing, then remove their player(s) from game. for position, user in self.players.items(): if perspective == user: self.leaveGame(perspective, position) - - del self.observers[perspective] self.notify('removeObserver', observer=perspective.name) # If there are no remaining observers, close table. - if self.config.get('closeWhenEmpty') and not self.observers: + if self.config.get('CloseWhenEmpty') and not self.observers: self.server.tables.closeTable(self) @@ -133,7 +135,7 @@ if position not in self.game.positions: raise IllegalRequest, "Invalid position type" # Check that user is not already playing at table. - if not self.config.get('allowUserMultiplePlayers'): + if not self.config.get('MultiplePlayersPerUser'): if user in self.players.values(): raise DeniedRequest, "Already playing in game" @@ -195,6 +197,7 @@ def view_joinGame(self, user, position): + # TODO: return a deferred? return self.table.joinGame(user, position) @@ -203,5 +206,6 @@ def view_sendMessage(self, user, message, sender=None, recipients=[]): - return self.table.sendMessage(message, sender=user, recipients=recipients) + return self.table.sendMessage(message, sender=user, + recipients=recipients) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-04-01 17:28:50
|
Revision: 376 http://svn.sourceforge.net/pybridge/?rev=376&view=rev Author: umgangee Date: 2007-04-01 10:28:46 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Provide extra information in interface docstrings, remove ITableEvents. Modified Paths: -------------- trunk/pybridge/pybridge/interfaces/game.py trunk/pybridge/pybridge/interfaces/table.py Modified: trunk/pybridge/pybridge/interfaces/game.py =================================================================== --- trunk/pybridge/pybridge/interfaces/game.py 2007-03-29 20:11:12 UTC (rev 375) +++ trunk/pybridge/pybridge/interfaces/game.py 2007-04-01 17:28:46 UTC (rev 376) @@ -38,7 +38,7 @@ """Returns an object representing the current state of the game. This may be used to export a game to be saved or transmitted. - @return: a state object. + @return: a state object, consumable by setState(). """ @@ -46,7 +46,7 @@ """Overwrites the current game state with the specified state. This may be used to import a saved or transmitted game. - @param state: a state object. + @param state: a state object, as generated by getState(). """ Modified: trunk/pybridge/pybridge/interfaces/table.py =================================================================== --- trunk/pybridge/pybridge/interfaces/table.py 2007-03-29 20:11:12 UTC (rev 375) +++ trunk/pybridge/pybridge/interfaces/table.py 2007-04-01 17:28:46 UTC (rev 376) @@ -20,13 +20,11 @@ class ITable(Interface): - """ITable defines methods which are common to all table implementations. + """ITable defines methods which are common to all table implementations, + which are expected to provide the following services: - A table is the abstraction of "the place where a game is played". - Implementations of ITable are expected to provide the following services: - - Synchronisation of game state between the server and connected clients. - This should be transparent to external code. + This should be transparent to game code. - Functionality shared by all games, such as: @@ -34,6 +32,8 @@ table when all observers have left. - Communication between users. + + A table is the abstraction of "the place where a game is played". """ @@ -65,59 +65,7 @@ or to all observers. @param message: message text string. - @param sender: identifier of sender. - @param recipients: identifiers of recipient observers. + @param sender: user identifier of sender. + @param recipients: user identifiers of recipient observers. """ - - - -class ITableEvents(Interface): - """ITableEvents defines the events which may be generated by ITable. - - """ - - - def observerAdded(self, table, observer): - """Called when an observer is added to the table. - - @param table: reference to table. - @param observer: observer identifier. - """ - - - def observerRemoved(self, table, observer): - """Called when an observer is removed from the table. - - @param table: reference to table. - @param observer: observer identifier. - """ - - - def playerJoined(self, table, player, position): - """Called when a player takes the position. - - @param table: reference to table. - @param player: player identifier. - @param position: position which player takes. - """ - - - def playerLeft(self, table, player, position): - """Called when a player is removed from their position. - - @param table: reference to table. - @param player: player identifier. - @param position: position which player leaves. - """ - - - def messageReceived(self, table, message, sender, recipients): - """Called when a message from sender is received. - - @param table: reference to table. - @param message: message text string. - @param sender: identifier of sender. - @param recipients: identifiers of recipient observers. - """ - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-29 20:11:11
|
Revision: 375 http://svn.sourceforge.net/pybridge/?rev=375&view=rev Author: umgangee Date: 2007-03-29 13:11:12 -0700 (Thu, 29 Mar 2007) Log Message: ----------- Remove string conversions on serialize/unserialize, since enum values are now copyable over the network. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/call.py trunk/pybridge/pybridge/bridge/card.py Modified: trunk/pybridge/pybridge/bridge/call.py =================================================================== --- trunk/pybridge/pybridge/bridge/call.py 2007-03-29 19:57:32 UTC (rev 374) +++ trunk/pybridge/pybridge/bridge/call.py 2007-03-29 20:11:12 UTC (rev 375) @@ -26,20 +26,32 @@ class Bid(Call): - """A Bid represents a statement of a level and a strain.""" + """A Bid represents a statement of a level and a strain. + + @param level: the level of the bid. + @type level: L{Level} + @param strain: the strain (denomination) of the bid. + @type strain: L{Strain} + """ + level = property(lambda self: self.__level) + strain = property(lambda self: self.__strain) + + def __init__(self, level, strain): - assert level in Level - assert strain in Strain - - self.level = level - self.strain = strain + if level not in Level: + raise TypeError, "Expected Level, got %s" % type(level) + if strain not in Strain: + raise TypeError, "Expected Strain, got %s" % type(strain) + self.__level = level + self.__strain = strain + def __cmp__(self, other): if not issubclass(other.__class__, Call): raise TypeError, "Expected Call, got %s" % type(other) - + if isinstance(other, Bid): # Compare two bids. selfIndex = self.level.index*len(Strain) + self.strain.index otherIndex = other.level.index*len(Strain) + other.strain.index @@ -53,17 +65,16 @@ def getStateToCopy(self): - state = {} - state['level'] = self.level.key - state['strain'] = self.strain.key - return state + return self.level, self.strain def setCopyableState(self, state): - self.level = getattr(Level, state['level']) - self.strain = getattr(Strain, state['strain']) + self.__level, self.__strain = state +pb.setUnjellyableForClass(Bid, Bid) + + class Pass(Call): """A Pass represents an abstention from the bidding.""" @@ -71,6 +82,9 @@ return "Pass" +pb.setUnjellyableForClass(Pass, Pass) + + class Double(Call): """A Double over an opponent's current bid.""" @@ -78,9 +92,15 @@ return "Double" +pb.setUnjellyableForClass(Double, Double) + + class Redouble(Call): """A Redouble over an opponent's double of partnership's current bid.""" def __str__(self): return "Redouble" + +pb.setUnjellyableForClass(Redouble, Redouble) + Modified: trunk/pybridge/pybridge/bridge/card.py =================================================================== --- trunk/pybridge/pybridge/bridge/card.py 2007-03-29 19:57:32 UTC (rev 374) +++ trunk/pybridge/pybridge/bridge/card.py 2007-03-29 20:11:12 UTC (rev 375) @@ -69,10 +69,12 @@ def getStateToCopy(self): - return {'rank' : self.rank.key, 'suit' : self.suit.key} + return self.rank, self.suit def setCopyableState(self, state): - self.__rank = getattr(Rank, state['rank']) - self.__suit = getattr(Suit, state['suit']) + self.__rank, self.__suit = state + +pb.setUnjellyableForClass(Card, Card) + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-29 19:57:31
|
Revision: 374 http://svn.sourceforge.net/pybridge/?rev=374&view=rev Author: umgangee Date: 2007-03-29 12:57:32 -0700 (Thu, 29 Mar 2007) Log Message: ----------- Introduce WeakEnumValue (a PB-friendly version of EnumValue) for all Enum types. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/symbols.py Modified: trunk/pybridge/pybridge/bridge/symbols.py =================================================================== --- trunk/pybridge/pybridge/bridge/symbols.py 2007-03-29 19:53:46 UTC (rev 373) +++ trunk/pybridge/pybridge/bridge/symbols.py 2007-03-29 19:57:32 UTC (rev 374) @@ -24,31 +24,74 @@ """ -from pybridge.enum import Enum +from twisted.spread import pb +from pybridge.enum import Enum, EnumValue + +class WeakEnumValue(EnumValue, pb.Copyable, pb.RemoteCopy): + """A variant of EnumValue which may be copied across the network. + + Since the enumtype reference (an Enum object) cannot be maintained when this + object is copied, it is discarded. An undesirable side-effect is that + comparisons between WeakEnumValue objects with identical indexes and keys + (but belonging to different Enum types) will result in True. + """ + + enumtype = property(lambda self: None) + + + def __repr__(self): + return "WeakEnumValue(%s, %s)" % (self.index, self.key) + + + def __cmp__(self, other): + try: + assert self.key == other.key + result = cmp(self.index, other.index) + except (AssertionError, AttributeError): + result = NotImplemented + return result + + + def getStateToCopy(self): + return (self.index, self.key) + + + def setCopyableState(self, (index, key)): + # self = WeakEnumValue(None, index, key) + self.__init__(None, index, key) + + +pb.setUnjellyableForClass(WeakEnumValue, WeakEnumValue) + + + + # Bid levels and strains (denominations). -Level = Enum('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven') +Level = Enum('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', + value_type=WeakEnumValue) -Strain = Enum('Club', 'Diamond', 'Heart', 'Spade', 'NoTrump') +Strain = Enum('Club', 'Diamond', 'Heart', 'Spade', 'NoTrump', + value_type=WeakEnumValue) # Card ranks and suits. Rank = Enum('Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', - 'Ten', 'Jack', 'Queen', 'King', 'Ace') + 'Ten', 'Jack', 'Queen', 'King', 'Ace', value_type=WeakEnumValue) -Suit = Enum('Club', 'Diamond', 'Heart', 'Spade') +Suit = Enum('Club', 'Diamond', 'Heart', 'Spade', value_type=WeakEnumValue) -# Player compass positions. +# Player compass positions, in clockwise order. -Direction = Enum('North', 'East', 'South', 'West') # Clockwise order. -Player = Direction # TODO: remove +Direction = Enum('North', 'East', 'South', 'West', value_type=WeakEnumValue) # Vulnerability indicators. -Vulnerable = Enum('None', 'NorthSouth', 'EastWest', 'All') +Vulnerable = Enum('None', 'NorthSouth', 'EastWest', 'All', + value_type=WeakEnumValue) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-29 19:53:45
|
Revision: 373 http://svn.sourceforge.net/pybridge/?rev=373&view=rev Author: umgangee Date: 2007-03-29 12:53:46 -0700 (Thu, 29 Mar 2007) Log Message: ----------- Rename Player to Direction, tidy up some code. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/bidding.py trunk/pybridge/pybridge/bridge/deck.py trunk/pybridge/pybridge/bridge/playing.py Modified: trunk/pybridge/pybridge/bridge/bidding.py =================================================================== --- trunk/pybridge/pybridge/bridge/bidding.py 2007-03-29 19:42:57 UTC (rev 372) +++ trunk/pybridge/pybridge/bridge/bidding.py 2007-03-29 19:53:46 UTC (rev 373) @@ -17,10 +17,10 @@ from call import Call, Bid, Pass, Double, Redouble -from symbols import Level, Player, Strain +from symbols import Direction, Level, Strain -class Bidding: +class Bidding(object): """This class models the bidding (auction) phase of a game of bridge. A bidding session is a list of Call objects and the dealer. @@ -28,7 +28,8 @@ def __init__(self, dealer): - assert dealer in Player + if dealer not in Direction: + raise TypeError, "Expected Direction, got %s" % type(dealer) self.calls = [] self.dealer = dealer @@ -72,7 +73,7 @@ redouble = self.getCurrentCall(Redouble) # Determine declarer. partnership = (self.whoCalled(bid), \ - Player[(self.whoCalled(bid).index + 2) % 4]) + Direction[(self.whoCalled(bid).index + 2) % 4]) for call in self.calls: if isinstance(call, Bid) and call.strain == bid.strain \ and self.whoCalled(call) in partnership: @@ -123,7 +124,7 @@ @return: True if call is available, False if not. """ assert isinstance(call, Call) - assert player in Player or player is None + assert player in Direction or player is None # The bidding must not be complete. if self.isComplete(): @@ -150,15 +151,15 @@ # A double must be made on the current bid from opponents, # with has not been already doubled by partnership. if isinstance(call, Double): - opposition = (Player[(self.whoseTurn().index + 1) % 4], - Player[(self.whoseTurn().index + 3) % 4]) + opposition = (Direction[(self.whoseTurn().index + 1) % 4], + Direction[(self.whoseTurn().index + 3) % 4]) return bidder in opposition and not self.getCurrentCall(Double) # A redouble must be made on the current bid from partnership, # which has been doubled by an opponent. elif isinstance(call, Redouble): partnership = (self.whoseTurn(), - Player[(self.whoseTurn().index + 2) % 4]) + Direction[(self.whoseTurn().index + 2) % 4]) return bidder in partnership and self.getCurrentCall(Double) \ and not self.getCurrentCall(Redouble) @@ -173,15 +174,17 @@ """ assert isinstance(call, Call) if call in self.calls: - return Player[(self.calls.index(call) + self.dealer.index) % 4] + return Direction[(self.calls.index(call) + self.dealer.index) % 4] return False # Call not made by any player. def whoseTurn(self): - """If bidding is not complete, returns the player who is next to call. + """Returns position of player who is next to make a call. - @return: the player next to call. + @return: the current turn. + @rtype: Direction """ - player = Player[(len(self.calls) + self.dealer.index) % 4] - return not self.isComplete() and player + if self.isComplete(): + return None + return Direction[(len(self.calls) + self.dealer.index) % 4] Modified: trunk/pybridge/pybridge/bridge/deck.py =================================================================== --- trunk/pybridge/pybridge/bridge/deck.py 2007-03-29 19:42:57 UTC (rev 372) +++ trunk/pybridge/pybridge/bridge/deck.py 2007-03-29 19:53:46 UTC (rev 373) @@ -21,7 +21,7 @@ from random import shuffle from card import Card -from symbols import Player, Rank, Suit +from symbols import Direction, Rank, Suit # See http://mail.python.org/pipermail/edu-sig/2001-May/001288.html for details. @@ -30,17 +30,17 @@ # TODO: consider making Hand a subclass of List, with additional constraints. -class Deck: +class Deck(object): """A Deck object provides operations for dealing Card objects. A hand is a collection of 13 cards from the deck. A deal is a distribution of all 52 cards to four hands. - A deal is represented as a dictionary, mapping Player labels to lists (hands) - of Card objects. + A deal is represented as a dictionary, mapping Direction labels to + lists (hands) of Card objects. There are exactly 52! / (13!)**4 (comb(52,13) * comb(39,13) * comb(26,13)) - distinct deals of 13 cards to 4 players from a standard 52-card deck. + distinct deals of 13 cards to 4 positions from a standard 52-card deck. """ cards = [Card(r, s) for r in Rank for s in Suit] @@ -56,7 +56,7 @@ def isValidDeal(self, deal): """Checks that structure of deal conforms to requirements: - * 4-element dict, mapping Player objects to hand lists. + * 4-element dict, mapping Direction objects to hand lists. * Hand lists contain exactly 13 Card objects. * No card may be repeated in the same hand, or between hands. * The cards in hands may be in any order. @@ -64,7 +64,7 @@ @param deal: a deal dict. @return: True if deal is valid, False otherwise. """ - return True # TODO + return True # TODO - if invalid, perhaps give reason def randomDeal(self): @@ -74,10 +74,10 @@ """ shuffle(self.cards) hands = {} - for player in Player: - hands[player] = [] + for position in Direction: + hands[position] = [] for index, card in enumerate(self.cards): - hands[Player[index % len(Player)]].append(card) + hands[Direction[index % len(Direction)]].append(card) for hand in hands.values(): hand.sort() return hands @@ -98,19 +98,19 @@ indexes = {} # For each hand, compute indexes of cards in cardSeq. - for player in (Player.North, Player.East, Player.South): - indexes[player] = 0 - deal[player].sort(reverse=False) + for position in (Direction.North, Direction.East, Direction.South): + indexes[position] = 0 + deal[position].sort(reverse=False) # It is desirable to remove cards from cardSeq when adding their # indexes, instead of doing so in an extra step. # Removing cards backwards preserves the indexes of later cards. - for i, card in enumerate(deal[player]): - indexes[player] += comb(cardSeq.index(card), 13-i) + for i, card in enumerate(deal[position]): + indexes[position] += comb(cardSeq.index(card), 13-i) cardSeq.remove(card) # Deal index = (Nindex * Emax * Smax) + (Eindex * Smax) + Sindex - indexes[Player.North] *= self.Emax * self.Smax - indexes[Player.East] *= self.Smax + indexes[Direction.North] *= self.Emax * self.Smax + indexes[Direction.East] *= self.Smax return long(sum(indexes.values())) @@ -130,28 +130,28 @@ deal = {} # Split index into hand indexes. - indexes = {Player.North : (num / self.Smax) / self.Emax, - Player.East : (num / self.Smax) % self.Emax, - Player.South : (num % self.Smax) } + indexes = {Direction.North : (num / self.Smax) / self.Emax, + Direction.East : (num / self.Smax) % self.Emax, + Direction.South : (num % self.Smax) } - for player in (Player.North, Player.East, Player.South): - deal[player] = [] + for position in (Direction.North, Direction.East, Direction.South): + deal[position] = [] for k in range(13, 0, -1): - # Find the largest n such that comb(n, k) <= indexes[player]. + # Find the largest n such that comb(n, k) <= indexes[position]. n = k-1 # n < k implies comb(n, k) = 0 # comb(n+1, k) = # n-k = -1 => comb(n, k) * (n+1) # otherwise => (comb(n, k) * (n+1)) / (n+1 - k) - while comb(n+1, k) <= indexes[player]: + while comb(n+1, k) <= indexes[position]: n += 1 # Remove card index from indices, add card to hand. - indexes[player] -= comb(n, k) + indexes[position] -= comb(n, k) card = cardSeq[n] - deal[player].append(card) + deal[position].append(card) cardSeq.remove(card) - deal[Player.West] = cardSeq # West has the remaining cards. + deal[Direction.West] = cardSeq # West has the remaining cards. return deal Modified: trunk/pybridge/pybridge/bridge/playing.py =================================================================== --- trunk/pybridge/pybridge/bridge/playing.py 2007-03-29 19:42:57 UTC (rev 372) +++ trunk/pybridge/pybridge/bridge/playing.py 2007-03-29 19:53:46 UTC (rev 373) @@ -157,7 +157,7 @@ return False elif hand and card not in hand: return False # Playing a card not in hand. - elif player and player is not self.whoseTurn(): + elif player and player != self.whoseTurn(): return False # Playing out of turn. elif self.whoPlayed(card): return False # Card played previously. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-29 19:43:00
|
Revision: 372 http://svn.sourceforge.net/pybridge/?rev=372&view=rev Author: umgangee Date: 2007-03-29 12:42:57 -0700 (Thu, 29 Mar 2007) Log Message: ----------- Comment out unused database tables, for now. Modified Paths: -------------- trunk/pybridge/pybridge/server/database.py Modified: trunk/pybridge/pybridge/server/database.py =================================================================== --- trunk/pybridge/pybridge/server/database.py 2007-03-29 19:26:41 UTC (rev 371) +++ trunk/pybridge/pybridge/server/database.py 2007-03-29 19:42:57 UTC (rev 372) @@ -22,7 +22,6 @@ from sqlobject.inheritance import InheritableSQLObject import pybridge.environment as env -from pybridge.bridge.symbols import Player backend = "sqlite" @@ -56,7 +55,7 @@ if not isinstance(value, str) or not(1 <= len(value) <= 20): raise ValueError, "Invalid specification of username" if re.search("[^A-z0-9_]", value): - raise ValueError, "Username can only contain alphanumeric characters" + raise ValueError, "Username may only be alphanumeric characters" self._SO_set_username(value) def _set_password(self, value): @@ -79,6 +78,8 @@ # They will be enhanced and used in future releases. +''' + class UserFriend(SQLObject): """Models the social interconnections that exist between users. @@ -144,6 +145,8 @@ """ deal = IntCol() - dealer = EnumCol(enumValues=list(Player)) - vuln = EnumCol(enumValues=['none', 'ns', 'ew', 'all']) + dealer = EnumCol(enumValues=list(Direction)) + vuln = EnumCol(enumValues=list(Vulnerable)) +''' + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-29 19:26:45
|
Revision: 371 http://svn.sourceforge.net/pybridge/?rev=371&view=rev Author: umgangee Date: 2007-03-29 12:26:41 -0700 (Thu, 29 Mar 2007) Log Message: ----------- Fix a bug which allowed Redouble on top of another Redouble. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/bidding.py Modified: trunk/pybridge/pybridge/bridge/bidding.py =================================================================== --- trunk/pybridge/pybridge/bridge/bidding.py 2007-03-27 15:39:04 UTC (rev 370) +++ trunk/pybridge/pybridge/bridge/bidding.py 2007-03-29 19:26:41 UTC (rev 371) @@ -152,14 +152,15 @@ if isinstance(call, Double): opposition = (Player[(self.whoseTurn().index + 1) % 4], Player[(self.whoseTurn().index + 3) % 4]) - return not self.getCurrentCall(Double) and bidder in opposition + return bidder in opposition and not self.getCurrentCall(Double) # A redouble must be made on the current bid from partnership, # which has been doubled by an opponent. elif isinstance(call, Redouble): partnership = (self.whoseTurn(), Player[(self.whoseTurn().index + 2) % 4]) - return self.getCurrentCall(Double) and bidder in partnership + return bidder in partnership and self.getCurrentCall(Double) \ + and not self.getCurrentCall(Redouble) return False # Otherwise unavailable. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 15:40:52
|
Revision: 370 http://svn.sourceforge.net/pybridge/?rev=370&view=rev Author: umgangee Date: 2007-03-27 08:39:04 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Refactor BridgeGame class to support new LocalTable and RemoteTable. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/game.py trunk/pybridge/pybridge/network/error.py Modified: trunk/pybridge/pybridge/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/bridge/game.py 2007-03-27 15:33:14 UTC (rev 369) +++ trunk/pybridge/pybridge/bridge/game.py 2007-03-27 15:39:04 UTC (rev 370) @@ -16,107 +16,301 @@ # Foundation Inc. 51 Franklin Street Fifth Floor Boston MA 02110-1301 USA. +from twisted.spread import pb +from zope.interface import implements + +from pybridge.interfaces.game import ICardGame +from pybridge.interfaces.observer import ISubject +from pybridge.network.error import GameError + from bidding import Bidding +from board import Board from playing import Playing -from symbols import Player, Suit +from symbols import Direction, Suit, Strain -class GameError(Exception): - pass +class BridgeGame(object): + """A bridge game models the bidding and play sequence. + + The methods of this class comprise the interface of a state machine. + Clients should only use the class methods to interact with the game state. + Modifications to the state are typically made through BridgePlayer objects. + + Methods which change the game state (makeCall, playCard) require a player + argument as "authentication". + """ -class Game: - """A bridge game models the bidding, playing, scoring sequence.""" + implements(ICardGame, ISubject) - def __init__(self, dealer, deal, scoring, vulnNS, vulnEW): - """Initialises game. - - scoring: instance of scoring class. - """ - self.vulnNS, self.vulnEW = vulnNS, vulnEW - self.deal = deal - self.playing = None - self.scoring = scoring - - # Start bidding. - self.bidding = Bidding(dealer) + # Valid positions. + positions = Direction + # Mapping from Strain symbols (in bidding) to Suit symbols (in play). + trumpMap = {Strain.Club: Suit.Club, Strain.Diamond: Suit.Diamond, + Strain.Heart: Suit.Heart, Strain.Spade: Suit.Spade, + Strain.NoTrump: None} - def isComplete(self): - """Returns True if game is complete, False otherwise.""" - if self.playing: - return self.playing.isComplete() + + def __init__(self): + self.listeners = [] + + self.board = None + self.bidding = None + self.contract = None + self.play = None + + self.boardQueue = [] # Boards for successive games. + self.players = {} # One-to-one mapping from Direction to BridgePlayer. + + +# Implementation of ICardGame. + + + def start(self, board=None): + if board: # Use specified board. + self.board = board + elif self.board: # Advance to next deal. + self.board.nextDeal(result=999) + else: # Create a board. + self.board = Board() + self.board.nextDeal() + self.bidding = Bidding(self.board['dealer']) # Start bidding. + self.contract = None + self.play = None + + # Remove deal from board, so it does not appear to players. + visibleBoard = self.board.copy() + del visibleBoard['deal'] + self.notify('start', board=visibleBoard) + + + def inProgress(self): + if self.play: + return not self.play.isComplete() + elif self.bidding: + return not self.bidding.isPassedOut() else: - return self.bidding.isPassedOut() + return False - def isHandVisible(self, player, viewer): - """A hand is visible if one of the following conditions is met: + def getState(self): + # TODO: all flag? + state = {} + + if self.inProgress(): + # Remove deal from board, so it does not appear to players. + visibleBoard = self.board.copy() + del visibleBoard['deal'] + state['board'] = visibleBoard + + if self.bidding: + state['calls'] = self.bidding.calls + if self.play: + state['played'] = [] + trickcount = max([len(s) for s in self.play.played.values()]) + for index in range(trickcount): + leader, cards = self.play.getTrick(index) + for pos in Direction[leader.index:] + Direction[:leader.index]: + if pos in cards: + state['played'].append(cards[pos]) + + return state + + + def setState(self, state): + if state.get('board'): + self.start(state['board']) + + # Comprehensive error checking is suppressed. + for call in state.get('calls', []): + turn = self.getTurn() + self.makeCall(call, self.players[turn]) +# self.bidding.makeCall(call, player=turn) + + # If a contract has been reached, start the play. +# contract = self.bidding.getContract() +# if contract: +# self.contract = contract +# trumpSuit = self.trumpMap[self.contract['bid'].strain] +# self.play = Playing(contract['declarer'], trumpSuit) + + # Comprehensive error checking is suppressed. + for card in state.get('played', []): + turn = self.getTurn() + self.playCard(card, self.players[turn]) + #self.play.playCard(card, player=turn) + + + def updateState(self, event, *args, **kwargs): + allowed = ['start', 'makeCall', 'playCard'] + if event in allowed: + handler = getattr(self, event) + handler(*args, **kwargs) +# else: +# print "updateState unknown attempted", event + + + def addPlayer(self, position): + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + if position in self.players: + raise GameError, "Position %s is taken" % position + + player = BridgePlayer(self) + self.players[position] = player + self.notify('addPlayer', position=position) + + return player + + + def removePlayer(self, position): + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + if position not in self.players: + raise GameError, "Position %s is vacant" % position + + player = self.players[position] + del self.players[position] + self.notify('removePlayer', position=position) + + +# Implementation of ISubject. + + + def attach(self, listener): + self.listeners.append(listener) + + + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + + +# Bridge-specific methods. + + + def makeCall(self, call, player): + """Make a call in the current bidding session. - 1. The hand is the viewer's own hand. - 2. Game is complete. - 3. Bidding complete and hand is dummy's, and first card of - first trick has been played. + @param call: the call. + @type call: Bid or Pass or Double or Redouble + @param player: a player identifier. + @type player: BridgePlayer """ - return player == viewer \ - or self.isComplete() \ - or (self.bidding.isComplete() and player == self.playing.dummy and \ - len(self.playing.getTrick(0)[1]) >= 1) + position = self.getPositionOfPlayer(player) + if position is None: + raise GameError, "Invalid player reference" + # Validate call according to game state. + if not self.bidding or self.bidding.isComplete(): + raise GameError, "Game not running or bidding complete" + if self.getTurn() is not position: + raise GameError, "Call made out of turn" + if not self.bidding.isValidCall(call, position): + raise GameError, "Call cannot be made" - def makeCall(self, player, call): - """Makes call from player.""" + self.bidding.makeCall(call, position) + self.notify('makeCall', call=call, position=position) + if self.bidding.isComplete(): - raise GameError('not in bidding') - - if self.bidding.whoseTurn() is not player: - raise GameError('out of turn') - elif not self.bidding.isValidCall(call, player): - raise GameError('invalid call') - - self.bidding.makeCall(call, player) - - # If bidding is complete, start playing. - if self.bidding.isComplete() and not self.bidding.isPassedOut(): + # If a contract has been reached, start the play. contract = self.bidding.getContract() - # Convert from bidding's Strain type to playing's Suit type. - # Note that No Trumps implies a None trump suit. - trumpSuit = getattr(Suit, str(contract['bid'].strain), None) - self.playing = Playing(contract['declarer'], trumpSuit) + if contract: + self.contract = contract + trumpSuit = self.trumpMap[self.contract['bid'].strain] + self.play = Playing(contract['declarer'], trumpSuit) + elif self.bidding.isPassedOut(): + self.notify('gameOver') - def playCard(self, player, card): - """Plays card from player.""" - if not self.bidding.isComplete() or self.bidding.isPassedOut(): - raise GameError('not in play') - elif self.playing.isComplete(): - raise GameError('not in play') + def signalAlert(self, alert, player): + pass # TODO + + + def playCard(self, card, player): + """Play a card in the current play session. - hand = self.deal[player] - if self.playing.whoseTurn() is not player: - raise GameError('out of turn') - elif not self.playing.isValidPlay(card, player, hand): - raise GameError('invalid card') - - self.playing.playCard(card) + @param card: a Card object. + @param player: a BridgePlayer object. + """ + position = self.getPositionOfPlayer(player) + if position is None: + raise GameError, "Invalid player reference" + if not self.play or self.play.isComplete(): + raise GameError, "Game not running or play complete" + if self.getTurn() is self.play.dummy: # Dummy's turn. + if position is self.play.declarer: + position = self.play.dummy # Declarer can play dummy's cards. + elif position is self.play.dummy: + raise GameError, "Dummy cannot play cards" + elif self.getTurn() is not position: + raise GameError, "Card played out of turn" - def getHand(self, player, viewer=None): - """Returns the hand of specified player. + hand = self.board['deal'][position] # May be empty, if hand unknown. + if not self.play.isValidPlay(card, position, hand): + raise GameError, "Card cannot be played from hand" + + self.play.playCard(card) + self.notify('playCard', card=card, position=position) + + + def getHand(self, position, player): + """If specified hand is visible, returns the list of cards in hand. - If viewer player is specified, then the ability of viewer - to "see" the hand will be examined. + A hand is visible if one of the following conditions is met: + + 1. The hand is the player's own hand. + 2. The game is finished. + 3. The bidding is complete and the hand is dummy's, and first card of + first trick has been played. + + @param position: the hand identifier. + @type position: Direction + @param player: a player identifier. + @type player: BridgePlayer """ - if viewer and not self.isHandVisible(player, viewer): - raise GameError('hand not visible') - return self.deal[player] + viewer = self.getPositionOfPlayer(player) + if viewer is None: + raise GameError, "Invalid player reference" + if not self.inProgress() and self.bidding is None: + raise GameError, "No game in progress" + if player == viewer or not self.inProgress(): + return self.board.deal[position] + if self.bidding.isComplete() and position == self.play.dummy: + leader, cards = self.play.getTrick(0) + if len(cards) >= 1: + return self.board.deal[position] + # At this point, checks have been exhausted. + raise GameError, "Hand is not visible" + + + def getTurn(self): + if self.inProgress(): + if self.play: # Currently in the play. + return self.play.whoseTurn() + else: # Currently in the bidding. + return self.bidding.whoseTurn() + else: # Not in game. + raise GameError, "No game in progress" + + def getTrickCount(self): - """ + """Returns various - @return['declarerWon']: number of tricks won by declarer/dummy. + @return: a dictionary of result information. + @rtype: dict + + + ['declarerWon']: number of tricks won by declarer/dummy. @return['defenceWon']: number of tricks won by defenders. @return['declarerNeeds']: number of extra tricks required by declarer to make contract. @@ -125,16 +319,16 @@ @return['required']: number of tricks required from contract level. """ - if self.playing is None: - raise GameError('not in play') + if self.play is None: + raise GameError, "Not in play" count = dict.fromkeys(('declarerWon', 'declarerNeeds', 'defenceWon', 'defenceNeeds'), 0) - for index in range(len(self.playing.winners)): - trick = self.playing.getTrick(index) - winner = self.playing.whoPlayed(self.playing.winningCard(trick)) - if winner in (self.playing.declarer, self.playing.dummy): + for index in range(len(self.play.winners)): + trick = self.play.getTrick(index) + winner = self.play.whoPlayed(self.play.winningCard(trick)) + if winner in (self.play.declarer, self.play.dummy): count['declarerWon'] += 1 else: # Trick won by defenders. count ['defenceWon'] += 1 @@ -148,43 +342,78 @@ return count - def score(self): + def getScore(self): """Returns the integer score value for declarer/dummy if: - bidding stage has been passed out, with no bids made. - play stage is complete. """ - if not self.isComplete(): - raise GameError('game not complete') - elif self.bidding.isPassedOut(): + if self.inProgress() or self.bidding is None: + raise GameError, "Game not complete" + if self.bidding.isPassedOut(): return 0 # A passed out deal does not score. - else: - contract = self.bidding.getContract() - declarer = contract['declarer'] - dummy = Player[(declarer.index + 2) % 4] - vulnerable = (self.vulnNS and declarer in (Player.North, Player.South)) + \ - (self.vulnEW and declarer in (Player.West, Player.East)) - - tricksMade = 0 # Count of tricks won by declarer or dummy. - for index in range(len(self.playing.winners)): - trick = self.playing.getTrick(index) - winningCard = self.playing.winningCard(trick) - winner = self.playing.whoPlayed(winningCard) - tricksMade += winner in (declarer, dummy) - - result = {'contract' : contract, - 'tricksMade' : tricksMade, - 'vulnerable' : vulnerable, } - return self.scoring(result) + contract = self.bidding.getContract() + declarer = contract['declarer'] + dummy = Seat[(declarer.index + 2) % 4] + vulnerable = (self.vulnNS and declarer in (Seat.North, Seat.South)) + \ + (self.vulnEW and declarer in (Seat.West, Seat.East)) - def whoseTurn(self): - """Returns the player that is next to call or play card.""" - if not self.isComplete(): - if self.bidding.isComplete(): - return self.playing.whoseTurn() - else: - return self.bidding.whoseTurn() - else: - raise GameError('game complete') + tricksMade = 0 # Count of tricks won by declarer or dummy. + for index in range(len(self.play.winners)): + trick = self.play.getTrick(index) + winningCard = self.play.winningCard(trick) + winner = self.play.whoPlayed(winningCard) + tricksMade += winner in (declarer, dummy) + result = {'contract' : contract, + 'tricksMade' : tricksMade, + 'vulnerable' : vulnerable, } + return self.scoring(result) + + def getPositionOfPlayer(self, player): + """If player is playing, returns position of player, otherwise None. + + @param player: a BridgePlayer object. + @type player: BridgePlayer + @return: the position of player. + @rtype: Direction or None + """ + for position, p in self.players.items(): + if p == player: + return position + return None + + + + +class BridgePlayer(pb.Referenceable): + """Actor representing a player's view of a BridgeGame object.""" + + + def __init__(self, game): + self.__game = game # Provide access to game only through this object. + + + def makeCall(self, call): + self.__game.makeCall(call, player=self) + + + def playCard(self, card): + self.__game.playCard(card, player=self) + + + def nextGame(self): + pass + + +# Aliases for remote-callable methods. + + + def remote_makeCall(self, call): + self.makeCall(call) + + + def remote_playCard(self, card): + self.playCard(card) + Modified: trunk/pybridge/pybridge/network/error.py =================================================================== --- trunk/pybridge/pybridge/network/error.py 2007-03-27 15:33:14 UTC (rev 369) +++ trunk/pybridge/pybridge/network/error.py 2007-03-27 15:39:04 UTC (rev 370) @@ -33,3 +33,7 @@ Please report any bugs which you discover in PyBridge! """ + +class GameError(pb.Error): + """Raised by game in response to an unsatisfiable or erroneous request.""" + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 15:33:14
|
Revision: 369 http://svn.sourceforge.net/pybridge/?rev=369&view=rev Author: umgangee Date: 2007-03-27 08:33:14 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Relocate getTrickCount() method from BridgeGame to this class. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/playing.py Modified: trunk/pybridge/pybridge/bridge/playing.py =================================================================== --- trunk/pybridge/pybridge/bridge/playing.py 2007-03-27 15:11:40 UTC (rev 368) +++ trunk/pybridge/pybridge/bridge/playing.py 2007-03-27 15:33:14 UTC (rev 369) @@ -17,31 +17,41 @@ from card import Card -from symbols import Player, Suit +from symbols import Direction, Suit -class Playing: +class Playing(object): """This class models the trick-taking phase of a game of bridge. This code is generalised, and could easily be adapted to support a variety of trick-taking card games. """ + # TODO: tricks, leader, winner properties? - def __init__(self, declarer, trumps): - assert declarer in Player - assert trumps in Suit or trumps is None # (None = No Trumps) - self.trumps = trumps + + def __init__(self, declarer, trumpSuit): + """ + @param declarer: the declarer from the auction. + @type declarer: Direction + @param trumpSuit: the trump suit from the auction. + @type trumpSuit: Suit or None + """ + if declarer not in Direction: + raise TypeError, "Expected Direction, got %s" % type(declarer) + if trumpSuit not in Suit and trumpSuit is not None: # None => No Trumps + raise TypeError, "Expected Suit, got %s" % type(suit) + self.trumps = trumpSuit self.declarer = declarer - self.dummy = Player[(declarer.index + 2) % 4] - self.lho = Player[(declarer.index + 1) % 4] - self.rho = Player[(declarer.index + 3) % 4] + self.dummy = Direction[(declarer.index + 2) % 4] + self.lho = Direction[(declarer.index + 1) % 4] + self.rho = Direction[(declarer.index + 3) % 4] # Each trick corresponds to a cross-section of lists. self.played = {} - for player in Player: - self.played[player] = [] + for position in Direction: + self.played[position] = [] self.winners = [] # Winning player of each trick. @@ -67,10 +77,10 @@ else: # Leader is winner of previous trick. leader = self.winners[index - 1] cards = {} - for player in Player: + for position in Direction: # If length of list exceeds index value, player's card in trick. - if len(self.played[player]) > index: - cards[player] = self.played[player][index] + if len(self.played[position]) > index: + cards[position] = self.played[position][index] return leader, cards @@ -84,6 +94,25 @@ return self.getTrick(index) + def getTrickCount(self): + """Returns the number of tricks won by declarer/dummy and by defenders. + + @return: the declarer trick count, the defender trick count. + @rtype: tuple + """ + declarerCount, defenderCount = 0, 0 + + for i in range(len(self.winners)): + trick = self.getTrick(i) + winner = self.whoPlayed(self.winningCard(trick)) + if winner in (self.declarer, self.dummy): + declarerCount += 1 + else: # Trick won by defenders. + defenderCount += 1 + + return declarerCount, defenderCount + + def playCard(self, card, player=None, hand=[]): """Plays card to current trick. Card validity should be checked with isValidPlay() beforehand. @@ -113,7 +142,7 @@ """Card is playable if and only if: - Play session is not complete. - - Player is on turn to play. + - Direction is on turn to play. - Card exists in hand. - Card has not been previously played. @@ -171,7 +200,7 @@ if len(cards) == 4: # If trick is complete, trick winner's turn. return self.whoPlayed(self.winningCard(trick)) else: # Otherwise, turn is next (clockwise) player in trick. - return Player[(leader.index + len(cards)) % 4] + return Direction[(leader.index + len(cards)) % 4] return False This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 15:11:41
|
Revision: 368 http://svn.sourceforge.net/pybridge/?rev=368&view=rev Author: umgangee Date: 2007-03-27 08:11:40 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Move game-centric code out of Table classes and into Game classes. Modified Paths: -------------- trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/server/server.py Removed Paths: ------------- trunk/pybridge/pybridge/network/localbridge.py trunk/pybridge/pybridge/network/remotebridge.py Deleted: trunk/pybridge/pybridge/network/localbridge.py =================================================================== --- trunk/pybridge/pybridge/network/localbridge.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/localbridge.py 2007-03-27 15:11:40 UTC (rev 368) @@ -1,220 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -from twisted.internet import reactor -from twisted.spread import pb -from zope.interface import implements - -from pybridge.interfaces.bridgetable import IBridgeTable -from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.network.localtable import LocalTable, LocalTableViewable - -# Set up reconstruction of objects from clients. -from pybridge.bridge.call import Bid, Pass, Double, Redouble -from pybridge.bridge.card import Card -pb.setUnjellyableForClass(Bid, Bid) -pb.setUnjellyableForClass(Pass, Pass) -pb.setUnjellyableForClass(Double, Double) -pb.setUnjellyableForClass(Redouble, Redouble) -pb.setUnjellyableForClass(Card, Card) - -# Bridge game. -from pybridge.bridge.deck import Deck -from pybridge.bridge.game import Game, GameError -from pybridge.bridge.scoring import scoreDuplicate -from pybridge.bridge.symbols import Player - - -class LocalBridgeTable(LocalTable): - """A bridge table, implementing the IBridgeTable interface. - - """ - - implements(IBridgeTable) # Also ITable, from parent Table. - - - def __init__(self, id): - LocalTable.__init__(self, id) - self.view = LocalBridgeTableViewable(self) # For clients. - - self.dealer = None - self.deck = Deck() - self.game = None - self.players = dict.fromkeys(Player, None) - self.scoring = scoreDuplicate - - self.handsSeen = {} - for player in Player: - self.handsSeen[player] = [] - - self.pendingDeals = [] # Queue of deals for successive games. - - # A time delay between a finished game and starting the next game. - self.config['gameWaitInterval'] = 5 - - - def getState(self): - state = LocalTable.getState(self) - - if self.game: - state['game'] = {} - state['game']['dealer'] = self.dealer.key - state['game']['vulnNS'] = self.game.vulnNS - state['game']['vulnEW'] = self.game.vulnEW - if self.game.bidding: - state['game']['calls'] = self.game.bidding.calls - if self.game.playing: - state['game']['declarer'] = self.game.playing.declarer.key - state['game']['played'] = {} - for player, played in self.game.playing.played.items(): - state['game']['played'][player.key] = played - # Add visible hands. - state['game']['deal'] = {} - for player, hand in self.game.deal.items(): - if self.game.isHandVisible(player, viewer=None): - state['game']['deal'][player.key] = hand - else: - state['game'] = None - - return state - - - def addPlayer(self, position, player): - # Overrides LocalTable, to provision revealHands() and testStartGame(). - LocalTable.addPlayer(self, position, player) - - self.handsSeen[position] = [] # Clear list of hands seen by player. - if self.game: # If game in progress... - self.revealHands() # ... provide player with visible hands. - # Test if game is ready to start. - self.testStartGame() - - - -# Methods implementing IBridgeTable. - - - def gameMakeCall(self, call, position): - if self.game is None or self.game.isComplete(): - raise DeniedRequest, 'Game not running' - elif position is None: - raise DeniedRequest, 'Not a player' - - try: - self.game.makeCall(call=call, player=position) - except GameError, error: - raise DeniedRequest, error - - self.updateObservers('gameCallMade', call=call, position=position.key) - self.testEndGame() - - - def gamePlayCard(self, card, position): - if self.game is None or self.game.isComplete(): - raise DeniedRequest, 'Game not running' - elif position is None: - raise DeniedRequest, 'Not a player' - elif self.game.playing is None: - raise DeniedRequest, 'Play not running' - - # Declarer can play dummy's cards, dummy cannot play own cards. - if self.game.whoseTurn() == self.game.playing.dummy: - if position == self.game.playing.declarer: # Declarer commands dummy. - position = self.game.playing.dummy - elif position == self.game.playing.dummy: - raise DeniedRequest, 'Dummy cannot play cards' - - try: - self.game.playCard(card=card, player=position) - except GameError, error: - raise DeniedRequest, error - - self.updateObservers('gameCardPlayed', card=card, position=position.key) - self.revealHands() - self.testEndGame() - - - def requestNextGame(self, player, ready=True): - self.observers[player]['ready'] = (ready == True) - self.testStartGame() # Check to start game. - - -# Utility methods. - - - def testStartGame(self, dealer=None, deal=None): - """If no game is active and all players are ready, start a game.""" - if (self.game is None or self.game.isComplete()) \ - and len([p for p in self.players.values() if p is None]) == 0: - - deal = deal or self.deck.randomDeal() - vulnNS, vulnEW = False, False - self.dealer = dealer or (self.dealer and Player[(self.dealer.index + 1) % 4]) or Player.North - self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) - self.updateObservers('gameStarted', dealer=self.dealer.key, vulnNS=vulnNS, vulnEW=vulnEW) - - for position in self.handsSeen: - self.handsSeen[position] = [] # Clear lists of hands seen. - self.revealHands() # Provide players with their hands. - return True - return False - - - def testEndGame(self): - """If game is finished, end game.""" - if self.game and self.game.isComplete(): - self.updateObservers('gameFinished') - self.revealHands() # Make all hands visible. - - # Set up time delay before next game starts. - wait = self.config.get('gameWaitInterval', 0) - reactor.callLater(wait, self.testStartGame) - - return True - return False - - - def revealHands(self): - """ - - Each hand is transmitted only once to each player. - """ - # TODO: what about observers? - for viewer, player in self.players.items(): - for seat in Player: - if seat not in self.handsSeen[viewer] and self.game.isHandVisible(seat, viewer): - self.handsSeen[viewer].append(seat) - hand = self.game.deal[seat] - self.informObserver(self.observers[player], 'gameHandRevealed', - hand=hand, position=seat.key) - - - - -class LocalBridgeTableViewable(LocalTableViewable): - - - def view_gameMakeCall(self, user, call, position=None): - position = self.table.getPositionOfPlayer(user) - self.table.gameMakeCall(call, position) - - - def view_gamePlayCard(self, user, card, position=None): - position = self.table.getPositionOfPlayer(user) - self.table.gamePlayCard(card, position) - Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/localtable.py 2007-03-27 15:11:40 UTC (rev 368) @@ -21,22 +21,33 @@ from twisted.spread import pb from zope.interface import implements +from pybridge.interfaces.observer import ISubject, IListener from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. class LocalTable(pb.Cacheable): - """""" + """An implementation of ITable suitable for server-side table instances. + + A LocalTable maintains the "master" game object and provides synchronisation + services for remote tables to mirror the game state. + """ - implements(ITable) + implements(ITable, ISubject, IListener) - def __init__(self, id): + def __init__(self, id, gametype): + self.listeners = [] + self.id = id + self.gametype = gametype + self.game = gametype() # Initialise game. + self.game.attach(self) # Listen for game events. + self.config = {} - self.observers = {} # For each perspective, a remote ITableEvents observer. - self.players = {} # For each position, the player's perspective. + self.observers = {} # For each user perspective, a remote ITableEvents. + self.players = {} # Positions mapped to perspectives of game players. + self.view = LocalTableViewable(self) # For remote clients. # Configuration variables. self.config['closeWhenEmpty'] = True @@ -44,65 +55,112 @@ def getStateToCacheAndObserveFor(self, perspective, observer): - self.updateObservers('observerAdded', observer=perspective.name) + # Inform existing observers that a new user has joined. + self.notify('addObserver', observer=perspective.name) self.observers[perspective] = observer - - return self.getState() # For observer. - - def getState(self): - """Build a dict of public information about the table.""" + # Build a dict of public information about the table. state = {} - state['id'] = self.id state['observers'] = [p.name for p in self.observers.keys()] - state['players'] = {} - for position, perspective in self.players.items(): - state['players'][position.key] = getattr(perspective, 'name', None) -# state['timeCreated'] = self.config['timeCreated'] - - return state + state['players'] = dict([(pos, p.name) + for pos, p in self.players.items()]) + state['gametype'] = self.gametype.__name__ + state['gamestate'] = self.game.getState() + return state # To observer. + def stoppedObserving(self, perspective, observer): + # If user was playing, then remove their player(s) from game. + for position, user in self.players.items(): + if perspective == user: + self.leaveGame(perspective, position) + del self.observers[perspective] - - # If user was a player, then remove player. - if self.getPositionOfPlayer(perspective): - self.removePlayer(perspective) - - self.updateObservers('observerRemoved', observer=perspective.name) - + self.notify('removeObserver', observer=perspective.name) + # If there are no remaining observers, close table. - if self.config.get('closeWhenEmpty') and len(self.observers) == 0: + if self.config.get('closeWhenEmpty') and not self.observers: self.server.tables.closeTable(self) -# Methods implementing ITable. +# Implementation of ISubject. - def addPlayer(self, position, player): - # Check that player is not already playing at table. - if self.getPositionOfPlayer(player): - raise DeniedRequest('already playing at table') - # Check that position is not occupied by another player. - if self.players.get(position) is not None: - raise DeniedRequest('position occupied by another player') - - self.players[position] = player - self.updateObservers('playerAdded', player=player.name, position=position.key) + def attach(self, listener): + self.listeners.append(listener) - def removePlayer(self, player): - position = self.getPositionOfPlayer(player) - # Check that player is playing at table: - if not position: - raise DeniedRequest('not playing at table') + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + # For all observers, calls event handler with provided arguments. + for observer in self.observers.values(): + self.notifyObserver(observer, event, *args, **kwargs) + + + def notifyObserver(self, obs, event, *args, **kwargs): + """Calls observer's event handler with provided arguments. - self.players[position] = None - self.updateObservers('playerRemoved', player=player.name, position=position.key) + @param obs: an observer object. + @type obs: RemoteCacheObserver + @param event: the name of the event. + @type event: str + """ + # Event handlers are called on the next iteration of the reactor, + # to allow the caller of this method to return a result. + reactor.callLater(0, obs.callRemote, event, *args, **kwargs) +# Implementation of IListener. + + + def update(self, event, *args, **kwargs): + # Expected to be called only by methods of self.game. + for observer in self.observers.values(): + self.notifyObserver(observer, 'gameUpdate', event, *args, **kwargs) + + +# Implementation of ITable. + + + def joinGame(self, user, position): + if position not in self.game.positions: + raise IllegalRequest, "Invalid position type" + # Check that user is not already playing at table. + if not self.config.get('allowUserMultiplePlayers'): + if user in self.players.values(): + raise DeniedRequest, "Already playing in game" + + player = self.game.addPlayer(position) # May raise GameError. + self.players[position] = user + self.notify('joinGame', player=user.name, position=position) + + # If no game is active and all players are ready, start game. + if not self.game.inProgress(): + if len(self.players) == len(self.game.positions): + self.game.start() + + return player + + + def leaveGame(self, user, position): + if position not in self.game.positions: + raise IllegalRequest, "Invalid position type" + # Ensure that user is playing at specified position. + if self.players.get(position) != user: + raise DeniedRequest, "Not playing at position" + + self.game.removePlayer(position) # May raise GameError. + del self.players[position] + self.notify('leaveGame', player=user.name, position=position) + + def sendMessage(self, message, sender, recipients): names = [perspective.name for perspective in self.observers.keys()] if recipients: # Translate user names to their observer objects. @@ -112,49 +170,19 @@ else: # Broadcast message to all observers. recipients = names sendTo = self.observers.values() - + for observer in sendTo: - observer.callRemote('messageReceived', message, sender.name, recipients) + self.notifyObserver(observer, 'sendMessage', message=message, + sender=sender.name, recipients=recipients) -# Utility methods. - def getPositionOfPlayer(self, user): - """If observer is playing, returns position of player. - Otherwise, returns False. - - @param user: observer identifier. - @return: position of player, or False. - """ - for position, player in self.players.items(): - if player == user: - return position - return False - - - def informObserver(self, obs, event, *args, **kwargs): - """Calls observer's event handler with provided args and kwargs. - - Event handlers are called on the next iteration of the reactor, - to allow the caller of this method to return a result. - """ - reactor.callLater(0, obs.callRemote, event, *args, **kwargs) - - - def updateObservers(self, event, *args, **kwargs): - """For each observer, calls event handler with provided kwargs.""" - for observer in self.observers.values(): - self.informObserver(observer, event, *args, **kwargs) - - - - class LocalTableViewable(pb.Viewable): - """ + """Provides a public front-end to an instantiated LocalTable. Serialization flavors are mutually exclusive and cannot be mixed, - so this class provides a pb.Viewable front-end to LocalTable. + so this class is a subclass of pb.Viewable. """ @@ -166,15 +194,14 @@ self.table = table - def view_addPlayer(self, user, position, player=None): - position = getattr(Player, position) # XX - self.table.addPlayer(position, user) + def view_joinGame(self, user, position): + return self.table.joinGame(user, position) - def view_removePlayer(self, user, player=None): - self.table.removePlayer(user) + def view_leaveGame(self, user, position): + return self.table.leaveGame(user, position) def view_sendMessage(self, user, message, sender=None, recipients=[]): - self.table.sendMessage(message, user, recipients) + return self.table.sendMessage(message, sender=user, recipients=recipients) Deleted: trunk/pybridge/pybridge/network/remotebridge.py =================================================================== --- trunk/pybridge/pybridge/network/remotebridge.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/remotebridge.py 2007-03-27 15:11:40 UTC (rev 368) @@ -1,140 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -from twisted.spread import pb -from zope.interface import implements - -from pybridge.interfaces.bridgetable import IBridgeTable -from pybridge.network.remotetable import RemoteTable -from pybridge.network.error import DeniedRequest, IllegalRequest - -# Set up reconstruction of objects from server. -from pybridge.bridge.call import Bid, Pass, Double, Redouble -from pybridge.bridge.card import Card -pb.setUnjellyableForClass(Bid, Bid) -pb.setUnjellyableForClass(Pass, Pass) -pb.setUnjellyableForClass(Double, Double) -pb.setUnjellyableForClass(Redouble, Redouble) -pb.setUnjellyableForClass(Card, Card) - -# Bridge game. -from pybridge.bridge.game import Game, GameError -from pybridge.bridge.scoring import scoreDuplicate -from pybridge.bridge.symbols import Player - - -class RemoteBridgeTable(RemoteTable): - """A bridge table, implementing the IBridgeTable interface. - - This is a cache of a remote MasterBridgeTable. - """ - - implements(IBridgeTable) - - - def __init__(self): - RemoteTable.__init__(self) - - self.dealer = None - self.game = None - self.players = dict.fromkeys(Player, None) - self.scoring = scoreDuplicate - - - def setCopyableState(self, state): - RemoteTable.setCopyableState(self, state) - - # Convert seat strings to Player enumeration values. - players = {} - for seat, player in self.players.items(): - players[getattr(Player, seat)] = player - self.players = players - - if state.get('game'): - self.dealer = getattr(Player, state['game']['dealer']) # XX - deal = {} - for player in Player: - deal[player] = state['game']['deal'].get(player, []) - vulnNS, vulnEW = state['game']['vulnNS'], state['game']['vulnEW'] - self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) - if state['game'].get('calls'): - for call in state['game']['calls']: - player = self.game.whoseTurn() - self.game.makeCall(call=call, player=player) - if state['game'].get('played'): - played = state['game']['played'] - while sum([len(cards) for cards in played.values()]) > 0: - player = self.game.whoseTurn() - card = played[player.key].pop(0) - self.game.playCard(card=card, player=player) - - - def gameMakeCall(self, call, position=None): - d = self.master.callRemote('gameMakeCall', call) - return d - -# # Check that game is running and we are playing. -# if self.game and self.game.whoseTurn() == self.position \ -# and self.game.bidding.validCall(call): -# # TODO: bidding.isValidCall() should check turn to bid. -# d = self.master.callRemote('gameMakeCall', call) -# return d - - - def gamePlayCard(self, card, position): - d = self.master.callRemote('gamePlayCard', card) - return d - - - def requestNextGame(self, ready=True, player=None): - d = self.master.callRemote('requestNextGame', ready) - return d - - -# Remote update methods. - - - def observe_gameStarted(self, dealer, vulnNS, vulnEW): - dealer = getattr(Player, dealer) # XX - self.dealer = dealer - deal = dict.fromkeys(Player, []) # Unknown hands. - self.game = Game(dealer, deal, self.scoring, vulnNS, vulnEW) - self.eventHandler.gameStarted(self, dealer, vulnNS, vulnEW) - - - def observe_gameFinished(self): - self.eventHandler.gameFinished(self) - - - def observe_gameCallMade(self, call, position): - position = getattr(Player, position) # XX - self.game.makeCall(call=call, player=position) - self.eventHandler.gameCallMade(self, call, position) - - - def observe_gameCardPlayed(self, card, position): - position = getattr(Player, position) # XX - self.game.playCard(card=card, player=position) - self.eventHandler.gameCardPlayed(self, card, position) - - - def observe_gameHandRevealed(self, hand, position): - position = getattr(Player, position) # XX - self.game.deal[position] = hand - self.eventHandler.gameHandRevealed(self, hand, position) - Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-03-27 15:11:40 UTC (rev 368) @@ -19,27 +19,31 @@ from twisted.spread import pb from zope.interface import implements +from pybridge.interfaces.observer import ISubject from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. class RemoteTable(pb.RemoteCache): - """ + """A client-side implementation of ITable providing a "front-end" to a + remote server-side LocalTable. + RemoteTable mirrors the state of LocalTable as a local cache. External code + may, therefore, read the table state without network communication. Actions + which change the table state are forwarded to the LocalTable. """ - implements(ITable) + implements(ITable, ISubject) def __init__(self): self.master = None # Server-side ITable object. - self.eventHandler = None # Expected to be set. - + self.listeners = [] + self.id = None - self.observers = [] - self.players = {} - self.seated = None # If user is playing at table. + self.game = None + self.observers = [] # Observers of master table. + self.players = {} # Positions mapped to player identifiers. def setCopyableState(self, state): @@ -47,23 +51,31 @@ self.observers = state['observers'] self.players = state['players'] + # TODO: do this by magic. + if state['gametype'] in ['BridgeGame']: + from pybridge.bridge.game import BridgeGame + self.gametype = BridgeGame + else: + raise NameError, "Unknown game type %s" % state['gametype'] - def setEventHandler(self, handler): - self.eventHandler = handler + self.game = self.gametype() + self.game.setState(state['gamestate']) -# Methods implementing ITable. +# Implementation of ITable. - def addPlayer(self, position, player=None): - d = self.master.callRemote('addPlayer', position.key) - d.addCallback(lambda _: setattr(self, 'seated', position)) + def setEventHandler(self, e): + print "called event handler - remove this!" + + + def joinGame(self, position, user=None): + d = self.master.callRemote('joinGame', position.key) return d - def removePlayer(self, player=None): - d = self.master.callRemote('removePlayer') - d.addCallback(lambda _: setattr(self, 'seated', None)) + def leaveGame(self, position, user=None): + d = self.master.callRemote('leaveGame', position.key) return d @@ -72,46 +84,52 @@ return d +# Implementation of ISubject. + + + def attach(self, listener): + self.listeners.append(listener) + + + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + + # Remote update methods. - def observe_observerAdded(self, observer): + def observe_addObserver(self, observer): self.observers.append(observer) - self.eventHandler.observerAdded(self, observer) + self.notify('addObserver', observer) - def observe_observerRemoved(self, observer): + def observe_removeObserver(self, observer): self.observers.remove(observer) - self.eventHandler.observerRemoved(self, observer) + self.notify('removeObserver', observer) - def observe_playerAdded(self, player, position): - position = getattr(Player, position) # XX + def observe_joinGame(self, player, position): + position = getattr(self.game.positions, position) self.players[position] = player - self.eventHandler.playerAdded(self, player, position) + self.notify('joinGame', player, position) - def observe_playerRemoved(self, player, position): - position = getattr(Player, position) # XX - self.players[position] = None - self.eventHandler.playerRemoved(self, player, position) + def observe_leaveGame(self, player, position): + position = getattr(self.game.positions, position) + del self.players[position] + self.notify('leaveGame', player, position) - def observe_messageReceived(self, message, sender, recipients): - self.eventHandler.messageReceived(self, message, sender, recipients) + def observe_sendMessage(self, message, sender, recipients): + # TODO: add to message log? + self.notify('sendMessage', message, sender, recipients) -# Utility methods. + def observe_gameUpdate(self, event, *args, **kwargs): + self.game.updateState(event, *args, **kwargs) - - def getPositionOfPlayer(self, user=None): - """If user is playing, returns position of player, otherwise False. - - @param user: observer identifier. - @return: position of player, or False. - """ - for position, player in self.players.items(): - if player == user: - return position - return False - Modified: trunk/pybridge/pybridge/server/server.py =================================================================== --- trunk/pybridge/pybridge/server/server.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/server/server.py 2007-03-27 15:11:40 UTC (rev 368) @@ -26,7 +26,8 @@ from pybridge.network.tablemanager import LocalTableManager from pybridge.network.usermanager import LocalUserManager -from pybridge.network.localbridge import LocalBridgeTable +from pybridge.network.localtable import LocalTable +from pybridge.bridge.game import BridgeGame class Server: @@ -80,7 +81,7 @@ def createTable(self, tableid, tabletype): # Ignore specified tabletype, for now. if tableid not in self.tables: - table = LocalBridgeTable(tableid) + table = LocalTable(tableid, BridgeGame) table.id = tableid table.server = self self.tables.openTable(table) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 14:55:29
|
Revision: 367 http://svn.sourceforge.net/pybridge/?rev=367&view=rev Author: umgangee Date: 2007-03-27 07:55:26 -0700 (Tue, 27 Mar 2007) Log Message: ----------- An encapsulation of bridge board information. Added Paths: ----------- trunk/pybridge/pybridge/bridge/board.py Added: trunk/pybridge/pybridge/bridge/board.py =================================================================== --- trunk/pybridge/pybridge/bridge/board.py (rev 0) +++ trunk/pybridge/pybridge/bridge/board.py 2007-03-27 14:55:26 UTC (rev 367) @@ -0,0 +1,69 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not write to the Free Software +# Foundation Inc. 51 Franklin Street Fifth Floor Boston MA 02110-1301 USA. + + +import random +import time +from deck import Deck +from symbols import Direction, Vulnerable + + +class Board(dict): + """An encapsulation of board information. + + @keyword deal: the cards in each hand. + @type deal: Deal + @keyword dealer: the position of the dealer. + @type dealer: Direction + @keyword event: the name of the event where the board was played. + @type event: str + @keyword num: the board number. + @type num: int + @keyword players: a mapping from positions to player names. + @type players: dict + @keyword site: the location (of the event) where the board was played. + @type site: str + @keyword time: the date/time when the board was generated. + @type time: time.struct_time + @keyword vuln: the board vulnerability. + @type vuln: Vulnerable + """ + + + def nextDeal(self, result=None): + """Generates and stores a random deal for the board. + + If result of a previous game is provided, the dealer and vulnerability + are rotated according to the rules of bridge. + + @param result: + @type result: + """ + deck = Deck() + self['deal'] = deck.randomDeal() + + self['num'] = self.get('num', 0) + 1 + self['time'] = time.localtime() + + if self.get('dealer'): # Rotate dealer. + self['dealer'] = Direction((self['dealer'].index+1) % len(Direction)) + else: # Select any player as the dealer. + self['dealer'] = random.choice(Direction) + + if result: + self['vuln'] = Vulnerable.All # TODO + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 14:45:59
|
Revision: 366 http://svn.sourceforge.net/pybridge/?rev=366&view=rev Author: umgangee Date: 2007-03-27 07:45:39 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Introduction of ISubject and IListener interfaces, modelling the Observer pattern. Added Paths: ----------- trunk/pybridge/pybridge/interfaces/observer.py Added: trunk/pybridge/pybridge/interfaces/observer.py =================================================================== --- trunk/pybridge/pybridge/interfaces/observer.py (rev 0) +++ trunk/pybridge/pybridge/interfaces/observer.py 2007-03-27 14:45:39 UTC (rev 366) @@ -0,0 +1,64 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +Provides interfaces for the Observer Pattern. + +For more information, see Gamma et al., "Design Patterns: Elements of Reusable +Object-Oriented Software", ISBN 0-201-63361-2. +""" + + +from zope.interface import Interface + + +class ISubject(Interface): + """ISubject defines methods required for observation of an object.""" + + + def attach(self, observer): + """Add observer to list of observers. + + @param observer: object implementing IListener. + """ + + + def detach(self, observer): + """Remove observer from list of observers. + + @param observer: object implementing IListener. + """ + + + def notify(self, event, *args, **kwargs): + """Inform all observers that state has been changed by event. + + @param event: the name of the event. + @type event: str + """ + + + + +class IListener(Interface): + """IListener defines methods required by observers of an ISubject.""" + + + def update(self, event, *args, **kwargs): + """Called by ISubject being observed.""" + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 14:44:53
|
Revision: 365 http://svn.sourceforge.net/pybridge/?rev=365&view=rev Author: umgangee Date: 2007-03-27 07:44:45 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Some modifications to ITable, for compatibility with new LocalTable / RemoteTable. Modified Paths: -------------- trunk/pybridge/pybridge/interfaces/table.py Modified: trunk/pybridge/pybridge/interfaces/table.py =================================================================== --- trunk/pybridge/pybridge/interfaces/table.py 2007-03-27 14:42:37 UTC (rev 364) +++ trunk/pybridge/pybridge/interfaces/table.py 2007-03-27 14:44:45 UTC (rev 365) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -20,33 +20,49 @@ class ITable(Interface): - """ITable defines generic methods for use with any table. + """ITable defines methods which are common to all table implementations. - This interface makes no assumptions about the game to be played, - besides that it has players. + A table is the abstraction of "the place where a game is played". + Implementations of ITable are expected to provide the following services: + + - Synchronisation of game state between the server and connected clients. + This should be transparent to external code. + + - Functionality shared by all games, such as: + + - Game management: players joining and leaving the game, closure of + table when all observers have left. + + - Communication between users. """ - def addPlayer(self, position, player): - """Registers an observer as an active player, provided: + def joinGame(self, user, position): + """Registers a user as an active player, provided that the specified + position is vacant. - - observer is not already playing at this table. - - the specified position is vacant. + The interface supports a single user playing at multiple positions. + Implementations may disable this feature. - @param player: player identifier. + @param user: user identifier. @param position: position which player takes. """ - def removePlayer(self, player): + def leaveGame(self, user, position): """Removes player from their position. - @param player: player identifier. + Specification of position is required, if the user is playing at + multiple positions. + + @param user: user identifier. + @param position: position which player takes. """ def sendMessage(self, message, sender, recipients): - """Issues message from sender to all named recipients, or to all observers. + """Issues message from sender to all named recipients, + or to all observers. @param message: message text string. @param sender: identifier of sender. @@ -78,7 +94,7 @@ """ - def playerAdded(self, table, player, position): + def playerJoined(self, table, player, position): """Called when a player takes the position. @param table: reference to table. @@ -87,7 +103,7 @@ """ - def playerRemoved(self, table, player, position): + def playerLeft(self, table, player, position): """Called when a player is removed from their position. @param table: reference to table. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 14:42:45
|
Revision: 364 http://svn.sourceforge.net/pybridge/?rev=364&view=rev Author: umgangee Date: 2007-03-27 07:42:37 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Introduction of IGame and ICardGame interfaces, deprecating IBridgeGame. Added Paths: ----------- trunk/pybridge/pybridge/interfaces/game.py Added: trunk/pybridge/pybridge/interfaces/game.py =================================================================== --- trunk/pybridge/pybridge/interfaces/game.py (rev 0) +++ trunk/pybridge/pybridge/interfaces/game.py 2007-03-27 14:42:37 UTC (rev 364) @@ -0,0 +1,108 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +from zope.interface import Interface + + +class IGame(Interface): + """IGame defines methods common to all games. + + This interface makes no assumptions about the game to be played, besides + that it has players. + """ + + + def start(self, initial): + """Called to initialise game state. This resets any previous state. + + @param initial: the initial state of the game. + """ + + + def getState(self): + """Returns an object representing the current state of the game. + This may be used to export a game to be saved or transmitted. + + @return: a state object. + """ + + + def setState(self, state): + """Overwrites the current game state with the specified state. + This may be used to import a saved or transmitted game. + + @param state: a state object. + """ + + + def updateState(self, event, *args, **kwargs): + """Updates game state in response to event. + + @param event: the name of the event. + """ + + + def addPlayer(self, position): + """Provide caller with a Player object bound to position. + + The specified position must be vacant. + + @param position: position to add player. + @return: a Player object. + """ + + + def removePlayer(self, position): + """Removes player from specified position. + + @param position: position from which to remove player. + """ + + +# Methods to query game state. + + + def inProgress(self): + """Indicates whether the game is currently being played or has finished. + + @return: True if game is running, False otherwise. + """ + + +class ICardGame(IGame): + """ICardGame defines methods specific to card games. + + ICardGame inherits all methods in IGame. + """ + + + def getHand(self, position): + """Returns a list of the known cards in hand. + For each unknown card, None is used as its placeholder. + + @player: a player identifier. + @return: the hand of the player. + """ + + + def getTurn(self): + """If game is in progress, returns the player who is next to play. + + @return: a player identifier, or None. + """ + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-13 12:32:20
|
Revision: 363 http://svn.sourceforge.net/pybridge/?rev=363&view=rev Author: umgangee Date: 2007-03-13 05:32:21 -0700 (Tue, 13 Mar 2007) Log Message: ----------- Rename Player symbol to Direction. New Vulnerable symbol. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/symbols.py Modified: trunk/pybridge/pybridge/bridge/symbols.py =================================================================== --- trunk/pybridge/pybridge/bridge/symbols.py 2007-03-13 12:31:04 UTC (rev 362) +++ trunk/pybridge/pybridge/bridge/symbols.py 2007-03-13 12:32:21 UTC (rev 363) @@ -20,9 +20,10 @@ This module contains enumeration types used for the implementation of bridge. The particular ordering of the values in each enumeration is assumed throughout -Pybridge, so it is vital that the order is not changed. +PyBridge, so it is vital that the order is not changed. """ + from pybridge.enum import Enum @@ -43,5 +44,11 @@ # Player compass positions. -Player = Enum('North', 'East', 'South', 'West') # Clockwise order. +Direction = Enum('North', 'East', 'South', 'West') # Clockwise order. +Player = Direction # TODO: remove + +# Vulnerability indicators. + +Vulnerable = Enum('None', 'NorthSouth', 'EastWest', 'All') + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-13 12:31:13
|
Revision: 362 http://svn.sourceforge.net/pybridge/?rev=362&view=rev Author: umgangee Date: 2007-03-13 05:31:04 -0700 (Tue, 13 Mar 2007) Log Message: ----------- Make Card a new-style class, subclassing object. Make rank and suit read-only properties. Enhanced type checking. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/card.py Modified: trunk/pybridge/pybridge/bridge/card.py =================================================================== --- trunk/pybridge/pybridge/bridge/card.py 2007-03-13 12:27:29 UTC (rev 361) +++ trunk/pybridge/pybridge/bridge/card.py 2007-03-13 12:31:04 UTC (rev 362) @@ -21,16 +21,29 @@ from symbols import Rank, Suit -class Card(pb.Copyable, pb.RemoteCopy): - """A card has a rank and a suit.""" +class Card(object, pb.Copyable, pb.RemoteCopy): + """A card has a rank and a suit. + + @param rank: the rank of the card. + @type rank: L{Rank} + @param suit: the suit of the card. + @type suit: L{Suit} + """ + rank = property(lambda self: self.__rank) + suit = property(lambda self: self.__suit) + def __init__(self, rank, suit): - assert rank in Rank and suit in Suit - self.rank = rank - self.suit = suit + if rank not in Rank: + raise TypeError, "Expected Rank, got %s" % type(rank) + if suit not in Suit: + raise TypeError, "Expected Suit, got %s" % type(suit) + self.__rank = rank + self.__suit = suit + def __eq__(self, other): """Two cards are equivalent if their ranks and suits match.""" if isinstance(other, Card): @@ -56,13 +69,10 @@ def getStateToCopy(self): - state = {} - state['rank'] = self.rank.key - state['suit'] = self.suit.key - return state + return {'rank' : self.rank.key, 'suit' : self.suit.key} def setCopyableState(self, state): - self.rank = getattr(Rank, state['rank']) - self.suit = getattr(Suit, state['suit']) + self.__rank = getattr(Rank, state['rank']) + self.__suit = getattr(Suit, state['suit']) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-13 12:27:32
|
Revision: 361 http://svn.sourceforge.net/pybridge/?rev=361&view=rev Author: umgangee Date: 2007-03-13 05:27:29 -0700 (Tue, 13 Mar 2007) Log Message: ----------- Added checking of user account enabled flag. Modified Paths: -------------- trunk/pybridge/pybridge/server/checker.py Modified: trunk/pybridge/pybridge/server/checker.py =================================================================== --- trunk/pybridge/pybridge/server/checker.py 2007-03-01 17:29:38 UTC (rev 360) +++ trunk/pybridge/pybridge/server/checker.py 2007-03-13 12:27:29 UTC (rev 361) @@ -25,7 +25,7 @@ class Checker: - """""" + """A database-driven implementation of ICredentialsChecker.""" implements(checkers.ICredentialsChecker) @@ -45,11 +45,7 @@ def passwordMatch(matched): if matched: - if credentials.username in self.users: - # TODO: delete old session and use this one instead? - return unauthorized("User is already logged in") - else: - return credentials.username + return credentials.username else: return unauthorized("Incorrect password for user") @@ -59,6 +55,11 @@ users = db.UserAccount.selectBy(username=credentials.username) if users.count() is 0: return unauthorized("User not known on server") + elif users[0].allowLogin is False: + return unauthorized("User account is disabled") + elif credentials.username in self.users: + # TODO: delete old session and use this one instead? + return unauthorized("User is already logged in") d = defer.maybeDeferred(credentials.checkPassword, users[0].password) d.addCallback(passwordMatch) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-01 17:29:43
|
Revision: 360 http://svn.sourceforge.net/pybridge/?rev=360&view=rev Author: umgangee Date: 2007-03-01 09:29:38 -0800 (Thu, 01 Mar 2007) Log Message: ----------- Relocate symbol types to pybridge.bridge.symbols. Renamed "Seat" type to the more intuitive "Player". Modified Paths: -------------- trunk/pybridge/pybridge/bridge/bidding.py trunk/pybridge/pybridge/bridge/call.py trunk/pybridge/pybridge/bridge/card.py trunk/pybridge/pybridge/bridge/deck.py trunk/pybridge/pybridge/bridge/game.py trunk/pybridge/pybridge/bridge/playing.py trunk/pybridge/pybridge/bridge/scoring.py trunk/pybridge/pybridge/network/localbridge.py trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotebridge.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/server/database.py trunk/pybridge/pybridge/ui/cardarea.py trunk/pybridge/pybridge/ui/window_bridgetable.py trunk/pybridge/tests/gumby.py trunk/pybridge/tests/test_bidding.py trunk/pybridge/tests/test_playing.py Modified: trunk/pybridge/pybridge/bridge/bidding.py =================================================================== --- trunk/pybridge/pybridge/bridge/bidding.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/bidding.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,19 +10,16 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from call import Call, Bid, Pass, Double, Redouble +from symbols import Level, Player, Strain -from call import Level, Strain -from deck import Seat - - class Bidding: """This class models the bidding (auction) phase of a game of bridge. @@ -31,7 +28,7 @@ def __init__(self, dealer): - assert dealer in Seat + assert dealer in Player self.calls = [] self.dealer = dealer @@ -75,7 +72,7 @@ redouble = self.getCurrentCall(Redouble) # Determine declarer. partnership = (self.whoCalled(bid), \ - Seat[(self.whoCalled(bid).index + 2) % 4]) + Player[(self.whoCalled(bid).index + 2) % 4]) for call in self.calls: if isinstance(call, Bid) and call.strain == bid.strain \ and self.whoCalled(call) in partnership: @@ -125,8 +122,8 @@ @param player: the player attempting to call, or None. @return: True if call is available, False if not. """ - assert(isinstance(call, Call)) - assert(player in Seat or player is None) + assert isinstance(call, Call) + assert player in Player or player is None # The bidding must not be complete. if self.isComplete(): @@ -153,15 +150,15 @@ # A double must be made on the current bid from opponents, # with has not been already doubled by partnership. if isinstance(call, Double): - opposition = (Seat[(self.whoseTurn().index + 1) % 4], - Seat[(self.whoseTurn().index + 3) % 4]) + opposition = (Player[(self.whoseTurn().index + 1) % 4], + Player[(self.whoseTurn().index + 3) % 4]) return not self.getCurrentCall(Double) and bidder in opposition # A redouble must be made on the current bid from partnership, # which has been doubled by an opponent. elif isinstance(call, Redouble): partnership = (self.whoseTurn(), - Seat[(self.whoseTurn().index + 2) % 4]) + Player[(self.whoseTurn().index + 2) % 4]) return self.getCurrentCall(Double) and bidder in partnership return False # Otherwise unavailable. @@ -175,7 +172,7 @@ """ assert isinstance(call, Call) if call in self.calls: - return Seat[(self.calls.index(call) + self.dealer.index) % 4] + return Player[(self.calls.index(call) + self.dealer.index) % 4] return False # Call not made by any player. @@ -184,6 +181,6 @@ @return: the player next to call. """ - player = Seat[(len(self.calls) + self.dealer.index) % 4] - return not(self.isComplete()) and player + player = Player[(len(self.calls) + self.dealer.index) % 4] + return not self.isComplete() and player Modified: trunk/pybridge/pybridge/bridge/call.py =================================================================== --- trunk/pybridge/pybridge/bridge/call.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/call.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -18,18 +18,11 @@ from twisted.spread import pb -from pybridge.enum import Enum +from symbols import Level, Strain -# Bid levels. -Level = Enum('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven') - -# Bid strains, or denominations. -Strain = Enum('Club', 'Diamond', 'Heart', 'Spade', 'NoTrump') - - class Call(pb.Copyable, pb.RemoteCopy): - """Superclass for bids, passes, doubles and redoubles.""" + """Abstract class, inherited by Bid, Pass, Double and Redouble.""" class Bid(Call): @@ -44,12 +37,13 @@ def __cmp__(self, other): - assert issubclass(other.__class__, Call) + if not issubclass(other.__class__, Call): + raise TypeError, "Expected Call, got %s" % type(other) if isinstance(other, Bid): # Compare two bids. - selfVal = self.level.index*len(Strain) + self.strain.index - otherVal = other.level.index*len(Strain) + other.strain.index - return cmp(selfVal, otherVal) + selfIndex = self.level.index*len(Strain) + self.strain.index + otherIndex = other.level.index*len(Strain) + other.strain.index + return cmp(selfIndex, otherIndex) else: # Comparing non-bid calls returns true. return 1 Modified: trunk/pybridge/pybridge/bridge/card.py =================================================================== --- trunk/pybridge/pybridge/bridge/card.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/card.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -18,23 +18,15 @@ from twisted.spread import pb -from pybridge.enum import Enum +from symbols import Rank, Suit -# Card ranks. -Rank = Enum('Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', - 'Ten', 'Jack', 'Queen', 'King', 'Ace') - -# Card suits. -Suit = Enum('Club', 'Diamond', 'Heart', 'Spade') - - class Card(pb.Copyable, pb.RemoteCopy): """A card has a rank and a suit.""" + def __init__(self, rank, suit): - assert(rank in Rank) - assert(suit in Suit) + assert rank in Rank and suit in Suit self.rank = rank self.suit = suit @@ -51,7 +43,9 @@ Care must be taken when comparing cards of different suits. """ - assert isinstance(other, Card) + if not isinstance(other, Card): + raise TypeError, "Expected Card, got %s" % type(other) + selfIndex = self.suit.index*13 + self.rank.index otherIndex = other.suit.index*13 + other.rank.index return cmp(selfIndex, otherIndex) Modified: trunk/pybridge/pybridge/bridge/deck.py =================================================================== --- trunk/pybridge/pybridge/bridge/deck.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/deck.py 2007-03-01 17:29:38 UTC (rev 360) @@ -20,13 +20,10 @@ from operator import mul from random import shuffle -from card import Card, Rank, Suit +from card import Card +from symbols import Player, Rank, Suit -from pybridge.enum import Enum -Seat = Enum('North', 'East', 'South', 'West') # Clockwise. - - # See http://mail.python.org/pipermail/edu-sig/2001-May/001288.html for details. comb = lambda n, k: reduce(mul, range(n, n-k, -1)) / reduce(mul, range(1, k+1)) @@ -39,7 +36,7 @@ A hand is a collection of 13 cards from the deck. A deal is a distribution of all 52 cards to four hands. - A deal is represented as a dictionary, mapping Seat labels to lists (hands) + A deal is represented as a dictionary, mapping Player labels to lists (hands) of Card objects. There are exactly 52! / (13!)**4 (comb(52,13) * comb(39,13) * comb(26,13)) @@ -59,7 +56,7 @@ def isValidDeal(self, deal): """Checks that structure of deal conforms to requirements: - * 4-element dict, mapping Seat objects to hand lists. + * 4-element dict, mapping Player objects to hand lists. * Hand lists contain exactly 13 Card objects. * No card may be repeated in the same hand, or between hands. * The cards in hands may be in any order. @@ -70,17 +67,17 @@ return True # TODO - def dealRandom(self): # randomDeal + def randomDeal(self): """Shuffles the deck and generates a random deal of hands. @return: a deal dictionary. """ shuffle(self.cards) hands = {} - for seat in Seat: - hands[seat] = [] + for player in Player: + hands[player] = [] for index, card in enumerate(self.cards): - hands[Seat[index % len(Seat)]].append(card) + hands[Player[index % len(Player)]].append(card) for hand in hands.values(): hand.sort() return hands @@ -101,20 +98,20 @@ indexes = {} # For each hand, compute indexes of cards in cardSeq. - for seat in (Seat.North, Seat.East, Seat.South): - indexes[seat] = 0 - deal[seat].sort(reverse=False) + for player in (Player.North, Player.East, Player.South): + indexes[player] = 0 + deal[player].sort(reverse=False) # It is desirable to remove cards from cardSeq when adding their # indexes, instead of doing so in an extra step. # Removing cards backwards preserves the indexes of later cards. - for i, card in enumerate(deal[seat]): - indexes[seat] += comb(cardSeq.index(card), 13-i) + for i, card in enumerate(deal[player]): + indexes[player] += comb(cardSeq.index(card), 13-i) cardSeq.remove(card) # Deal index = (Nindex * Emax * Smax) + (Eindex * Smax) + Sindex - indexes[Seat.North] *= self.Emax * self.Smax - indexes[Seat.East] *= self.Smax - return sum(indexes.values()) + indexes[Player.North] *= self.Emax * self.Smax + indexes[Player.East] *= self.Smax + return long(sum(indexes.values())) def indexToDeal(self, num): @@ -133,28 +130,28 @@ deal = {} # Split index into hand indexes. - indexes = {Seat.North : (num / self.Smax) / self.Emax, - Seat.East : (num / self.Smax) % self.Emax, - Seat.South : (num % self.Smax) } + indexes = {Player.North : (num / self.Smax) / self.Emax, + Player.East : (num / self.Smax) % self.Emax, + Player.South : (num % self.Smax) } - for seat in (Seat.North, Seat.East, Seat.South): - deal[seat] = [] + for player in (Player.North, Player.East, Player.South): + deal[player] = [] for k in range(13, 0, -1): - # Find the largest n such that comb(n, k) <= indexes[seat]. + # Find the largest n such that comb(n, k) <= indexes[player]. n = k-1 # n < k implies comb(n, k) = 0 # comb(n+1, k) = # n-k = -1 => comb(n, k) * (n+1) # otherwise => (comb(n, k) * (n+1)) / (n+1 - k) - while comb(n+1, k) <= indexes[seat]: + while comb(n+1, k) <= indexes[player]: n += 1 # Remove card index from indices, add card to hand. - indexes[seat] -= comb(n, k) + indexes[player] -= comb(n, k) card = cardSeq[n] - deal[seat].append(card) + deal[player].append(card) cardSeq.remove(card) - - deal[Seat.West] = cardSeq # South has the remaining cards. + + deal[Player.West] = cardSeq # West has the remaining cards. return deal Modified: trunk/pybridge/pybridge/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/bridge/game.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/game.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not write to the Free Software # Foundation Inc. 51 Franklin Street Fifth Floor Boston MA 02110-1301 USA. @@ -18,19 +18,15 @@ from bidding import Bidding from playing import Playing +from symbols import Player, Suit -# Enumerations. -from card import Suit -from deck import Seat +class GameError(Exception): + pass -class GameError(Exception): pass - class Game: - """A bridge game models the bidding, playing, scoring sequence. - - """ + """A bridge game models the bidding, playing, scoring sequence.""" def __init__(self, dealer, deal, scoring, vulnNS, vulnEW): @@ -55,7 +51,7 @@ return self.bidding.isPassedOut() - def isHandVisible(self, seat, viewer): + def isHandVisible(self, player, viewer): """A hand is visible if one of the following conditions is met: 1. The hand is the viewer's own hand. @@ -63,23 +59,23 @@ 3. Bidding complete and hand is dummy's, and first card of first trick has been played. """ - return seat == viewer \ + return player == viewer \ or self.isComplete() \ - or (self.bidding.isComplete() and seat == self.playing.dummy and \ + or (self.bidding.isComplete() and player == self.playing.dummy and \ len(self.playing.getTrick(0)[1]) >= 1) - def makeCall(self, seat, call): - """Makes call from seat.""" + def makeCall(self, player, call): + """Makes call from player.""" if self.bidding.isComplete(): raise GameError('not in bidding') - if self.bidding.whoseTurn() is not seat: + if self.bidding.whoseTurn() is not player: raise GameError('out of turn') - elif not self.bidding.isValidCall(call, seat): + elif not self.bidding.isValidCall(call, player): raise GameError('invalid call') - self.bidding.makeCall(call, seat) + self.bidding.makeCall(call, player) # If bidding is complete, start playing. if self.bidding.isComplete() and not self.bidding.isPassedOut(): @@ -90,31 +86,31 @@ self.playing = Playing(contract['declarer'], trumpSuit) - def playCard(self, seat, card): - """Plays card from seat.""" + def playCard(self, player, card): + """Plays card from player.""" if not self.bidding.isComplete() or self.bidding.isPassedOut(): raise GameError('not in play') elif self.playing.isComplete(): raise GameError('not in play') - hand = self.deal[seat] - if self.playing.whoseTurn() is not seat: + hand = self.deal[player] + if self.playing.whoseTurn() is not player: raise GameError('out of turn') - elif not self.playing.isValidPlay(card, seat, hand): + elif not self.playing.isValidPlay(card, player, hand): raise GameError('invalid card') self.playing.playCard(card) - def getHand(self, seat, viewer=None): - """Returns the hand of player specified by seat. + def getHand(self, player, viewer=None): + """Returns the hand of specified player. If viewer player is specified, then the ability of viewer to "see" the hand will be examined. """ - if viewer and not self.isHandVisible(seat, viewer): + if viewer and not self.isHandVisible(player, viewer): raise GameError('hand not visible') - return self.deal[seat] + return self.deal[player] def getTrickCount(self): @@ -165,9 +161,9 @@ else: contract = self.bidding.getContract() declarer = contract['declarer'] - dummy = Seat[(declarer.index + 2) % 4] - vulnerable = (self.vulnNS and declarer in (Seat.North, Seat.South)) + \ - (self.vulnEW and declarer in (Seat.West, Seat.East)) + dummy = Player[(declarer.index + 2) % 4] + vulnerable = (self.vulnNS and declarer in (Player.North, Player.South)) + \ + (self.vulnEW and declarer in (Player.West, Player.East)) tricksMade = 0 # Count of tricks won by declarer or dummy. for index in range(len(self.playing.winners)): @@ -183,7 +179,7 @@ def whoseTurn(self): - """Returns the seat that is next to call or play card.""" + """Returns the player that is next to call or play card.""" if not self.isComplete(): if self.bidding.isComplete(): return self.playing.whoseTurn() Modified: trunk/pybridge/pybridge/bridge/playing.py =================================================================== --- trunk/pybridge/pybridge/bridge/playing.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/playing.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,19 +10,16 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from card import Card +from symbols import Player, Suit -# Enumerations. -from card import Suit -from deck import Seat - class Playing: """This class models the trick-taking phase of a game of bridge. @@ -32,19 +29,19 @@ def __init__(self, declarer, trumps): - assert declarer in Seat + assert declarer in Player assert trumps in Suit or trumps is None # (None = No Trumps) self.trumps = trumps self.declarer = declarer - self.dummy = Seat[(declarer.index + 2) % 4] - self.lho = Seat[(declarer.index + 1) % 4] - self.rho = Seat[(declarer.index + 3) % 4] + self.dummy = Player[(declarer.index + 2) % 4] + self.lho = Player[(declarer.index + 1) % 4] + self.rho = Player[(declarer.index + 3) % 4] # Each trick corresponds to a cross-section of lists. self.played = {} - for seat in Seat: - self.played[seat] = [] + for player in Player: + self.played[player] = [] self.winners = [] # Winning player of each trick. @@ -63,17 +60,17 @@ @param: trick index, in range 0 to 12. @return: a (leader, cards) trick tuple. """ - assert(index in range(13)) + assert 0 <= index < 13 if index == 0: # First trick. leader = self.lho # Leader is declarer's left-hand opponent. else: # Leader is winner of previous trick. leader = self.winners[index - 1] cards = {} - for seat in Seat: + for player in Player: # If length of list exceeds index value, player's card in trick. - if len(self.played[seat]) > index: - cards[seat] = self.played[seat][index] + if len(self.played[player]) > index: + cards[player] = self.played[player][index] return leader, cards @@ -116,7 +113,7 @@ """Card is playable if and only if: - Play session is not complete. - - Seat is on turn to play. + - Player is on turn to play. - Card exists in hand. - Card has not been previously played. @@ -174,7 +171,7 @@ if len(cards) == 4: # If trick is complete, trick winner's turn. return self.whoPlayed(self.winningCard(trick)) else: # Otherwise, turn is next (clockwise) player in trick. - return Seat[(leader.index + len(cards)) % 4] + return Player[(leader.index + len(cards)) % 4] return False Modified: trunk/pybridge/pybridge/bridge/scoring.py =================================================================== --- trunk/pybridge/pybridge/bridge/scoring.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/bridge/scoring.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,13 +10,13 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from call import Strain +from symbols import Strain # There are undoubtedly many minor variations of the score values. @@ -24,7 +24,11 @@ def scoreDuplicate(result): - """Scoring algorithm for duplicate bridge.""" + """Scoring algorithm for duplicate bridge. + + This code includes the scoring values from: + http://www.ebu.co.uk/lawsandethics/the_laws/chapter8.asp + """ score = 0 isDoubled = result['contract']['doubleBy'] Modified: trunk/pybridge/pybridge/network/localbridge.py =================================================================== --- trunk/pybridge/pybridge/network/localbridge.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/network/localbridge.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -37,11 +37,9 @@ from pybridge.bridge.deck import Deck from pybridge.bridge.game import Game, GameError from pybridge.bridge.scoring import scoreDuplicate +from pybridge.bridge.symbols import Player -# Enumerations. -from pybridge.bridge.deck import Seat - class LocalBridgeTable(LocalTable): """A bridge table, implementing the IBridgeTable interface. @@ -57,12 +55,12 @@ self.dealer = None self.deck = Deck() self.game = None - self.players = dict.fromkeys(Seat, None) + self.players = dict.fromkeys(Player, None) self.scoring = scoreDuplicate self.handsSeen = {} - for seat in Seat: - self.handsSeen[seat] = [] + for player in Player: + self.handsSeen[player] = [] self.pendingDeals = [] # Queue of deals for successive games. @@ -83,13 +81,13 @@ if self.game.playing: state['game']['declarer'] = self.game.playing.declarer.key state['game']['played'] = {} - for seat, played in self.game.playing.played.items(): - state['game']['played'][seat.key] = played + for player, played in self.game.playing.played.items(): + state['game']['played'][player.key] = played # Add visible hands. state['game']['deal'] = {} - for seat, hand in self.game.deal.items(): - if self.game.isHandVisible(seat, viewer=None): - state['game']['deal'][seat.key] = hand + for player, hand in self.game.deal.items(): + if self.game.isHandVisible(player, viewer=None): + state['game']['deal'][player.key] = hand else: state['game'] = None @@ -118,7 +116,7 @@ raise DeniedRequest, 'Not a player' try: - self.game.makeCall(call=call, seat=position) + self.game.makeCall(call=call, player=position) except GameError, error: raise DeniedRequest, error @@ -142,7 +140,7 @@ raise DeniedRequest, 'Dummy cannot play cards' try: - self.game.playCard(card=card, seat=position) + self.game.playCard(card=card, player=position) except GameError, error: raise DeniedRequest, error @@ -164,9 +162,9 @@ if (self.game is None or self.game.isComplete()) \ and len([p for p in self.players.values() if p is None]) == 0: - deal = deal or self.deck.dealRandom() + deal = deal or self.deck.randomDeal() vulnNS, vulnEW = False, False - self.dealer = dealer or (self.dealer and Seat[(self.dealer.index + 1) % 4]) or Seat.North + self.dealer = dealer or (self.dealer and Player[(self.dealer.index + 1) % 4]) or Player.North self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) self.updateObservers('gameStarted', dealer=self.dealer.key, vulnNS=vulnNS, vulnEW=vulnEW) @@ -198,7 +196,7 @@ """ # TODO: what about observers? for viewer, player in self.players.items(): - for seat in Seat: + for seat in Player: if seat not in self.handsSeen[viewer] and self.game.isHandVisible(seat, viewer): self.handsSeen[viewer].append(seat) hand = self.game.deal[seat] Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/network/localtable.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -23,10 +23,9 @@ from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest +from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. -from pybridge.bridge.deck import Seat # XX TODO: Try to avoid this. - class LocalTable(pb.Cacheable): """""" @@ -168,7 +167,7 @@ def view_addPlayer(self, user, position, player=None): - position = getattr(Seat, position) # XX + position = getattr(Player, position) # XX self.table.addPlayer(position, user) Modified: trunk/pybridge/pybridge/network/remotebridge.py =================================================================== --- trunk/pybridge/pybridge/network/remotebridge.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/network/remotebridge.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -35,11 +35,9 @@ # Bridge game. from pybridge.bridge.game import Game, GameError from pybridge.bridge.scoring import scoreDuplicate +from pybridge.bridge.symbols import Player -# Enumerations. -from pybridge.bridge.deck import Seat - class RemoteBridgeTable(RemoteTable): """A bridge table, implementing the IBridgeTable interface. @@ -54,36 +52,36 @@ self.dealer = None self.game = None - self.players = dict.fromkeys(Seat, None) + self.players = dict.fromkeys(Player, None) self.scoring = scoreDuplicate def setCopyableState(self, state): RemoteTable.setCopyableState(self, state) - # Convert seat strings to Seat enumeration values. + # Convert seat strings to Player enumeration values. players = {} for seat, player in self.players.items(): - players[getattr(Seat, seat)] = player + players[getattr(Player, seat)] = player self.players = players if state.get('game'): - self.dealer = getattr(Seat, state['game']['dealer']) # XX + self.dealer = getattr(Player, state['game']['dealer']) # XX deal = {} - for seat in Seat: - deal[seat] = state['game']['deal'].get(seat, []) + for player in Player: + deal[player] = state['game']['deal'].get(player, []) vulnNS, vulnEW = state['game']['vulnNS'], state['game']['vulnEW'] self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) if state['game'].get('calls'): for call in state['game']['calls']: - seat = self.game.whoseTurn() - self.game.makeCall(call=call, seat=seat) + player = self.game.whoseTurn() + self.game.makeCall(call=call, player=player) if state['game'].get('played'): played = state['game']['played'] while sum([len(cards) for cards in played.values()]) > 0: - seat = self.game.whoseTurn() - card = played[seat.key].pop(0) - self.game.playCard(card=card, seat=seat) + player = self.game.whoseTurn() + card = played[player.key].pop(0) + self.game.playCard(card=card, player=player) def gameMakeCall(self, call, position=None): @@ -101,22 +99,6 @@ def gamePlayCard(self, card, position): d = self.master.callRemote('gamePlayCard', card) return d -# # Check that game is running, we are playing, -# # the position specified is on turn to play, -# # and the card specified is in player's hand. -# if self.game and self.position \ -# and self.game.whoseTurn() == position \ -# and self.game.playing.isValidPlay(card, position, self.game.deal[position]): -# d = self.master.callRemote('gamePlayCard', card) -# return d -# declarer = self.game.playing.declarer -# dummy = self.game.playing.dummy -# # Can play card from own hand, or from dummy's hand as declarer. -# seat = -# if self.game.whoseTurn() == self.position \ -# or (self.game.whoseTurn() == dummy and self.position == declarer): -# d = self.master.callRemote('gamePlayCard', card) -# return d def requestNextGame(self, ready=True, player=None): @@ -128,9 +110,9 @@ def observe_gameStarted(self, dealer, vulnNS, vulnEW): - dealer = getattr(Seat, dealer) # XX + dealer = getattr(Player, dealer) # XX self.dealer = dealer - deal = dict.fromkeys(Seat, []) # Unknown hands. + deal = dict.fromkeys(Player, []) # Unknown hands. self.game = Game(dealer, deal, self.scoring, vulnNS, vulnEW) self.eventHandler.gameStarted(self, dealer, vulnNS, vulnEW) @@ -140,19 +122,19 @@ def observe_gameCallMade(self, call, position): - position = getattr(Seat, position) # XX - self.game.makeCall(call=call, seat=position) + position = getattr(Player, position) # XX + self.game.makeCall(call=call, player=position) self.eventHandler.gameCallMade(self, call, position) def observe_gameCardPlayed(self, card, position): - position = getattr(Seat, position) # XX - self.game.playCard(card=card, seat=position) + position = getattr(Player, position) # XX + self.game.playCard(card=card, player=position) self.eventHandler.gameCardPlayed(self, card, position) def observe_gameHandRevealed(self, hand, position): - position = getattr(Seat, position) # XX + position = getattr(Player, position) # XX self.game.deal[position] = hand self.eventHandler.gameHandRevealed(self, hand, position) Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,10 +21,9 @@ from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest +from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. -from pybridge.bridge.deck import Seat # XX TODO: Try to avoid this. - class RemoteTable(pb.RemoteCache): """ @@ -87,13 +86,13 @@ def observe_playerAdded(self, player, position): - position = getattr(Seat, position) # XX + position = getattr(Player, position) # XX self.players[position] = player self.eventHandler.playerAdded(self, player, position) def observe_playerRemoved(self, player, position): - position = getattr(Seat, position) # XX + position = getattr(Player, position) # XX self.players[position] = None self.eventHandler.playerRemoved(self, player, position) Modified: trunk/pybridge/pybridge/server/database.py =================================================================== --- trunk/pybridge/pybridge/server/database.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/server/database.py 2007-03-01 17:29:38 UTC (rev 360) @@ -22,7 +22,7 @@ from sqlobject.inheritance import InheritableSQLObject import pybridge.environment as env -from pybridge.bridge.deck import Seat +from pybridge.bridge.symbols import Player backend = "sqlite" @@ -119,10 +119,9 @@ """Captures game attributes specific to bridge games. """ - board = ForeignKey('BridgeBoard') - declarer = EnumCol(enumValues=list(Seat)) + declarer = EnumCol(enumValues=list(Player)) # contract = trickCount = IntCol() # Number of tricks won by score = IntCol() @@ -145,6 +144,6 @@ """ deal = IntCol() - dealer = EnumCol(enumValues=list(Seat)) + dealer = EnumCol(enumValues=list(Player)) vuln = EnumCol(enumValues=['none', 'ns', 'ew', 'all']) Modified: trunk/pybridge/pybridge/ui/cardarea.py =================================================================== --- trunk/pybridge/pybridge/ui/cardarea.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/ui/cardarea.py 2007-03-01 17:29:38 UTC (rev 360) @@ -24,8 +24,8 @@ import pybridge.environment as env from canvas import CairoCanvas -from pybridge.bridge.card import Card, Rank, Suit -from pybridge.bridge.deck import Seat +from pybridge.bridge.card import Card +from pybridge.bridge.symbols import Player, Rank, Suit # The order in which card graphics are expected in card mask. CARD_MASK_RANKS = [Rank.Ace, Rank.Two, Rank.Three, Rank.Four, Rank.Five, @@ -60,11 +60,11 @@ super(CardArea, self).__init__() # Initialise parent. # To receive card clicked events, override this with external method. - self.on_card_clicked = lambda card, seat: True + self.on_card_clicked = lambda card, player: True self.hands = {} self.trick = None - self.set_seat_mapping(Seat.South) + self.set_player_mapping(Player.South) self.connect('button_press_event', self.button_press) self.add_events(gtk.gdk.BUTTON_PRESS_MASK) @@ -91,15 +91,14 @@ context.reset_clip() - def set_hand(self, hand, seat, facedown=False, omit=[]): - """Sets the hand of player at seat. - Draws representation of cards in hand to context. + def set_hand(self, hand, player, facedown=False, omit=[]): + """Sets the hand of player. Draws cards in hand to context. The hand is buffered into an ImageSurface, since hands change infrequently and multiple calls to draw_card() are expensive. @param hand: a list of Card objects. - @param seat: a member of Seat. + @param player: a member of Player. @param facedown: if True, cards are drawn face-down. @param omit: a list of elements of hand not to draw. """ @@ -107,7 +106,7 @@ # TODO: coords should be dict (card : (pos_x, pos_y)), but this breaks when hashing. def get_coords_for_hand(): coords = [] - if seat in (self.TOP, self.BOTTOM): + if player in (self.TOP, self.BOTTOM): pos_y = 0 if facedown is True: # Draw cards in one continuous row. for index, card in enumerate(hand): @@ -124,14 +123,14 @@ else: # LEFT or RIGHT. if facedown is True: # Wrap cards to a 4x4 grid. for index, card in enumerate(hand): - adjust = seat is self.RIGHT and index == 12 and 3 + adjust = player is self.RIGHT and index == 12 and 3 pos_x = ((index % 4) + adjust) * self.spacing_x pos_y = (index / 4) * self.spacing_y coords.append((card, pos_x, pos_y)) else: longest = max([len(cards) for cards in suits.values()]) for index, card in enumerate(hand): - adjust = seat is self.RIGHT and longest - len(suits[card.suit]) + adjust = player is self.RIGHT and longest - len(suits[card.suit]) pos_x = (suits[card.suit].index(card) + adjust) * self.spacing_x pos_y = RED_BLACK.index(card.suit) * self.spacing_y coords.append((card, pos_x, pos_y)) @@ -150,7 +149,7 @@ for suit in RED_BLACK: hand.extend(suits[suit]) - saved = self.hands.get(seat) + saved = self.hands.get(player) if saved and saved['hand'] == hand: # If hand has been set previously, do not recalculate coords. coords = saved['coords'] @@ -169,24 +168,24 @@ self.draw_card(context, pos_x, pos_y, card) # Save - self.hands[seat] = {'hand' : hand, 'visible' : visible, - 'surface' : surface, 'coords' : coords, } + self.hands[player] = {'hand' : hand, 'visible' : visible, + 'surface' : surface, 'coords' : coords, } - id = 'hand-%s' % seat # Identifier for this item. + id = 'hand-%s' % player # Identifier for this item. if id in self.items: self.update_item(id, source=surface) else: xy = {self.TOP : (0.5, 0.15), self.BOTTOM : (0.5, 0.85), self.LEFT : (0.15, 0.5), self.RIGHT : (0.85, 0.5), } - self.add_item(id, surface, xy[seat], 0) + self.add_item(id, surface, xy[player], 0) - def set_player_name(self, seat, name=None): + def set_player_name(self, player, name=None): """ @param name: the name of the player, or None. """ - id = 'player-%s' % seat + id = 'player-%s' % player if name is None or id in self.items: self.remove_item(id) return @@ -216,19 +215,19 @@ else: xy = {self.TOP : (0.5, 0.15), self.BOTTOM : (0.5, 0.85), self.LEFT : (0.15, 0.6), self.RIGHT : (0.85, 0.6), } - self.add_item(id, surface, xy[seat], 2) + self.add_item(id, surface, xy[player], 2) - def set_seat_mapping(self, focus=Seat.South): - """Sets the mapping between seats at table and positions of hands. + def set_player_mapping(self, focus=Player.South): + """Sets the mapping between players at table and positions of hands. - @param focus: the Seat to be drawn "closest" to the observer. + @param focus: the Player to be drawn "closest" to the observer. """ - # Assumes Seat elements are ordered clockwise from North. - order = Seat[focus.index:] + Seat[:focus.index] - for seat, attr in zip(order, ('BOTTOM', 'LEFT', 'TOP', 'RIGHT')): - setattr(self, attr, seat) - # TODO: set seat labels. + # Assumes Player elements are ordered clockwise from North. + order = Player[focus.index:] + Player[:focus.index] + for player, attr in zip(order, ('BOTTOM', 'LEFT', 'TOP', 'RIGHT')): + setattr(self, attr, player) + # TODO: set player labels. def set_trick(self, trick): @@ -241,18 +240,18 @@ self.LEFT : (0.425, 0.5), self.RIGHT : (0.575, 0.5), } if trick: - # The order of play is the leader, then clockwise around Seat. + # The order of play is the leader, then clockwise around Player. leader = trick[0] - order = Seat[leader.index:] + Seat[:leader.index] - for i, seat in enumerate(order): - id = 'trick-%s' % seat - old_card = self.trick and self.trick[1].get(seat) or None - new_card = trick[1].get(seat) + order = Player[leader.index:] + Player[:leader.index] + for i, player in enumerate(order): + id = 'trick-%s' % player + old_card = self.trick and self.trick[1].get(player) or None + new_card = trick[1].get(player) # If old card matches new card, take no action. if old_card is None and new_card is not None: surface, context = self.new_surface(self.card_width, self.card_height) self.draw_card(context, 0, 0, new_card) - self.add_item(id, surface, xy[seat], z_index=i+1) + self.add_item(id, surface, xy[player], z_index=i+1) elif new_card is None and old_card is not None: self.remove_item(id) elif old_card != new_card: @@ -261,8 +260,8 @@ self.update_item(id, surface, z_index=i+1) elif self.trick: # Remove all cards from previous trick. - for seat in self.trick[1]: - self.remove_item('trick-%s' % seat) + for player in self.trick[1]: + self.remove_item('trick-%s' % player) self.trick = trick # Save trick and return. @@ -273,14 +272,14 @@ The hand of the player on turn is drawn opaque; the other hands are drawn translucent. - @param turn: a member of Seat, or None. + @param turn: a member of Player, or None. """ if turn is None: return - for seat in Seat: - opacity = (seat is turn) and 1 or 0.5 - self.update_item('hand-%s' % seat, opacity=opacity) + for player in Player: + opacity = (player is turn) and 1 or 0.5 + self.update_item('hand-%s' % player, opacity=opacity) def button_press(self, widget, event): @@ -289,10 +288,10 @@ found_hand = False # Determine the hand which was clicked. - for seat in self.hands: - card_coords = self.hands[seat]['coords'] - surface = self.hands[seat]['surface'] - hand_x, hand_y = self.items['hand-%s' % seat]['area'][0:2] + for player in self.hands: + card_coords = self.hands[player]['coords'] + surface = self.hands[player]['surface'] + hand_x, hand_y = self.items['hand-%s' % player]['area'][0:2] if (hand_x <= event.x <= hand_x + surface.get_width()) and \ (hand_y <= event.y <= hand_y + surface.get_height()): found_hand = True @@ -302,11 +301,11 @@ # Determine the card in hand which was clicked. pos_x, pos_y = event.x - hand_x, event.y - hand_y # Iterate through visible cards backwards. - for i, card in self.hands[seat]['visible'][::-1]: + for i, card in self.hands[player]['visible'][::-1]: x, y = card_coords[i][1:] if (x <= pos_x <= x + self.card_width) and \ (y <= pos_y <= y + self.card_height): - self.on_card_clicked(card, seat) + self.on_card_clicked(card, player) break return True # Expected to return True. Modified: trunk/pybridge/pybridge/ui/window_bridgetable.py =================================================================== --- trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -24,13 +24,9 @@ import utils from pybridge.bridge.call import Bid, Pass, Double, Redouble +from pybridge.bridge.symbols import Level, Strain, Player, Rank -# Enumerations. -from pybridge.bridge.call import Level, Strain -from pybridge.bridge.card import Rank -from pybridge.bridge.deck import Seat - # Translatable symbols for elements of bridge. CALLTYPE_SYMBOLS = {Pass : _('pass'), Double : _('dbl'), Redouble : _('rdbl') } @@ -51,8 +47,8 @@ Strain.Spade : u'\N{BLACK SPADE SUIT}', Strain.NoTrump : u'NT', } -SEAT_SYMBOLS = {Seat.North : _('North'), Seat.East : _('East'), - Seat.South : _('South'), Seat.West : _('West') } +SEAT_SYMBOLS = {Player.North : _('North'), Player.East : _('East'), + Player.South : _('South'), Player.West : _('West') } class WindowBridgetable(GladeWrapper): @@ -72,11 +68,11 @@ # Set up "Take Seat" menu. items = {} menu = gtk.Menu() - for seat in Seat: - items[seat] = gtk.MenuItem(SEAT_SYMBOLS[seat], True) - items[seat].connect('activate', self.on_seat_activated, seat) - items[seat].show() - menu.append(items[seat]) + for player in Player: + items[player] = gtk.MenuItem(SEAT_SYMBOLS[player], True) + items[player].connect('activate', self.on_seat_activated, player) + items[player].show() + menu.append(items[player]) self.takeseat.set_menu(menu) self.takeseat_items = items @@ -92,8 +88,8 @@ # Set up bidding history and column display. self.call_store = gtk.ListStore(str, str, str, str) self.biddingview.set_model(self.call_store) - for index, seat in enumerate(Seat): - title = SEAT_SYMBOLS[seat] + for index, player in enumerate(Player): + title = SEAT_SYMBOLS[player] column = gtk.TreeViewColumn(str(title), renderer, text=index) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(50) @@ -102,8 +98,8 @@ # Set up trick history and column display. self.trick_store = gtk.ListStore(str, str, str, str) self.trickview.set_model(self.trick_store) - for index, seat in enumerate(Seat): - title = SEAT_SYMBOLS[seat] + for index, player in enumerate(Player): + title = SEAT_SYMBOLS[player] column = gtk.TreeViewColumn(str(title), renderer, text=index) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(50) @@ -152,8 +148,8 @@ self.setTurnIndicator() for call in table.game.bidding.calls: - seat = table.game.bidding.whoCalled(call) - self.addCall(call, seat) + player = table.game.bidding.whoCalled(call) + self.addCall(call, player) self.setDealer(table.dealer) self.setVuln(table.game.vulnNS, table.game.vulnEW) @@ -165,9 +161,9 @@ # If playing, set trick counts. if table.game.playing: - for seat, cards in table.game.playing.played.items(): + for player, cards in table.game.playing.played.items(): for card in cards: - self.addCard(card, seat) + self.addCard(card, player) self.setTrickCount(table.game.getTrickCount()) # If user is a player and bidding in progress, open bidding box. @@ -180,7 +176,7 @@ self.takeseat_items[seat].set_property('sensitive', available) if player: self.event_playerAdded(table, player, seat) - else: # Seat vacant. + else: # Player vacant. self.event_playerRemoved(table, None, seat) # Initialise observer listing. @@ -238,8 +234,8 @@ def addScore(self, contract, made, score): textContract = self.getContractFormat(contract) textMade = '%s' % made - if contract['declarer'] in (Seat.North, Seat.South) and score > 0 \ - or contract['declarer'] in (Seat.East, Seat.West) and score < 0: + if contract['declarer'] in (Player.North, Player.South) and score > 0 \ + or contract['declarer'] in (Player.East, Player.West) and score < 0: textNS, textEW = '%s' % abs(score), '' else: textNS, textEW = '', '%s' % abs(score) @@ -374,7 +370,7 @@ widget.set_property('sensitive', True) # Reset player label. label = getattr(self, 'label_%s' % position.key.lower()) - label.set_markup('<i>%s</i>' % _('Seat vacant')) + label.set_markup('<i>%s</i>' % _('Player vacant')) # If we are not seated, ensure Take Seat is enabled. if not table.seated: Modified: trunk/pybridge/tests/gumby.py =================================================================== --- trunk/pybridge/tests/gumby.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/tests/gumby.py 2007-03-01 17:29:38 UTC (rev 360) @@ -1,7 +1,7 @@ #!/usr/bin/env python # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -12,7 +12,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -47,8 +47,7 @@ from pybridge.network.client import client from pybridge.bridge.call import Bid, Double, Redouble, Pass -from pybridge.bridge.call import Level, Strain -from pybridge.bridge.deck import Seat +from pybridge.bridge.symbols import Level, Strain # Give the bot some "personality". Modified: trunk/pybridge/tests/test_bidding.py =================================================================== --- trunk/pybridge/tests/test_bidding.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/tests/test_bidding.py 2007-03-01 17:29:38 UTC (rev 360) @@ -3,12 +3,9 @@ from pybridge.bridge.bidding import Bidding from pybridge.bridge.call import Bid, Pass, Double, Redouble +from pybridge.bridge.symbols import Level, Player, Strain -# Enumerations. -from pybridge.bridge.call import Level, Strain -from pybridge.bridge.deck import Seat - class TestBidding(unittest.TestCase): @@ -16,9 +13,8 @@ bids = [Bid(l, s) for l in Level for s in Strain] - def setUp(self): - dealer = random.choice(Seat) + dealer = random.choice(Player) self.bidding = Bidding(dealer) @@ -30,13 +26,13 @@ """getCurrentCall""" for calltype in [Bid, Pass, Double, Redouble]: self.assertEqual(self.bidding.getCurrentCall(calltype), None) - + for call, calltype in [(Pass(), Pass), (Bid(Level.One, Strain.Club), Bid), (Double(), Double), (Redouble(), Redouble)]: self.assertEqual(self.bidding.getCurrentCall(calltype), None) self.bidding.makeCall(call) self.assertEqual(self.bidding.getCurrentCall(calltype), call) - + # A bid should clear types Pass, Double, Redouble. bid = Bid(Level.One, Strain.Diamond) self.bidding.makeCall(bid) @@ -48,11 +44,11 @@ def testWhoseTurn(self): """whoseTurn""" # Tests whoseTurn() before and after making calls. - turn = Seat[self.bidding.dealer.index] + turn = Player[self.bidding.dealer.index] for call in self.bids: self.assertEqual(self.bidding.whoseTurn(), turn) self.bidding.makeCall(call) - turn = Seat[(turn.index + 1) % 4] + turn = Player[(turn.index + 1) % 4] self.assertEqual(self.bidding.whoseTurn(), turn) @@ -61,8 +57,6 @@ pass - - def main(): suite = unittest.makeSuite(TestBidding) unittest.TextTestRunner(verbosity=2).run(suite) Modified: trunk/pybridge/tests/test_playing.py =================================================================== --- trunk/pybridge/tests/test_playing.py 2007-03-01 17:22:17 UTC (rev 359) +++ trunk/pybridge/tests/test_playing.py 2007-03-01 17:29:38 UTC (rev 360) @@ -4,19 +4,16 @@ from pybridge.bridge.card import Card from pybridge.bridge.deck import Deck from pybridge.bridge.playing import Playing +from pybridge.bridge.symbols import Player, Rank, Suit -# Enumerations. -from pybridge.bridge.card import Suit -from pybridge.bridge.deck import Seat - class TestPlaying(unittest.TestCase): def setUp(self): - decl... [truncated message content] |
From: <umg...@us...> - 2007-03-01 17:22:23
|
Revision: 359 http://svn.sourceforge.net/pybridge/?rev=359&view=rev Author: umgangee Date: 2007-03-01 09:22:17 -0800 (Thu, 01 Mar 2007) Log Message: ----------- Relocated symbol enumeration types from pybridge.bridge.* modules to symbols.py. Added Paths: ----------- trunk/pybridge/pybridge/bridge/symbols.py Added: trunk/pybridge/pybridge/bridge/symbols.py =================================================================== --- trunk/pybridge/pybridge/bridge/symbols.py (rev 0) +++ trunk/pybridge/pybridge/bridge/symbols.py 2007-03-01 17:22:17 UTC (rev 359) @@ -0,0 +1,47 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +This module contains enumeration types used for the implementation of bridge. + +The particular ordering of the values in each enumeration is assumed throughout +Pybridge, so it is vital that the order is not changed. +""" + +from pybridge.enum import Enum + + +# Bid levels and strains (denominations). + +Level = Enum('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven') + +Strain = Enum('Club', 'Diamond', 'Heart', 'Spade', 'NoTrump') + + +# Card ranks and suits. + +Rank = Enum('Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', + 'Ten', 'Jack', 'Queen', 'King', 'Ace') + +Suit = Enum('Club', 'Diamond', 'Heart', 'Spade') + + +# Player compass positions. + +Player = Enum('North', 'East', 'South', 'West') # Clockwise order. + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-02-28 21:37:21
|
Revision: 358 http://svn.sourceforge.net/pybridge/?rev=358&view=rev Author: umgangee Date: 2007-02-28 13:37:21 -0800 (Wed, 28 Feb 2007) Log Message: ----------- Move Settings class out of UI, to enable reuse. Modified Paths: -------------- trunk/pybridge/pybridge/ui/utils.py Added Paths: ----------- trunk/pybridge/pybridge/settings.py Added: trunk/pybridge/pybridge/settings.py =================================================================== --- trunk/pybridge/pybridge/settings.py (rev 0) +++ trunk/pybridge/pybridge/settings.py 2007-02-28 21:37:21 UTC (rev 358) @@ -0,0 +1,53 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import ConfigParser + + +class Settings: + """A wrapper for ConfigParser.""" + + + def __init__(self, path, sections): + """Make section key/value pairs into attributes of this object. + + @param path: the location of configuration file to load. + @param sections: a list of section names to be made available. + """ + self._path = path + self._config = ConfigParser.SafeConfigParser() + self._config.read(path) + + for section in sections: + # Create sections if they do not exist. + if not self._config.has_section(section): + self._config.add_section(section) + # Make items in section available as + items = {} + for key, value in self._config.items(section): + items[key] = value + setattr(self, section.lower(), items) # self.<section> = items + + + def save(self): + """Writes contents of section/item dicts back to file.""" + for section in self._config.sections(): + for key, value in getattr(self, section.lower()).items(): + self._config.set(section, key, value) + self._config.write(file(self._path, 'w')) + Modified: trunk/pybridge/pybridge/ui/utils.py =================================================================== --- trunk/pybridge/pybridge/ui/utils.py 2007-02-28 18:33:16 UTC (rev 357) +++ trunk/pybridge/pybridge/ui/utils.py 2007-02-28 21:37:21 UTC (rev 358) @@ -16,8 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import pybridge.environment as env - # Set up client with UI event handler. from pybridge.network.client import client from eventhandler import eventhandler @@ -26,54 +24,13 @@ PORT = 5040 # Default port for PyBridge. +import pybridge.environment as env +from pybridge.settings import Settings +file = env.find_config_client('client.cfg') +settings = Settings(file, ['Connection', 'General']) -import ConfigParser - -class Settings: - """""" - - connection = {} - general = {} - - - def __init__(self, filename): - self.config = ConfigParser.SafeConfigParser() - self.filename = filename - self.read() - - - def read(self): - """""" - self.config.read(self.filename) - - # Create sections if they do not exist. - for section in ('Connection', 'General'): - if not self.config.has_section(section): - self.config.add_section(section) - self.write() - - for key, value in self.config.items('Connection'): - self.connection[key] = value - for key, value in self.config.items('General'): - self.general[key] = value - - - def write(self): - """""" - for key, value in self.connection.items(): - self.config.set('Connection', key, value) - for key, value in self.general.items(): - self.config.set('General', key, value) - self.config.write(file(self.filename, 'w')) - - -settings = Settings(env.find_config_client('client.cfg')) - - - - import imp from UserDict import UserDict @@ -123,7 +80,7 @@ def quit(): """Shutdown gracefully.""" client.disconnect() - settings.write() # Save settings. + settings.save() # Save settings. reactor.stop() gtk.main_quit() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |