From: <umg...@us...> - 2007-08-03 18:42:16
|
Revision: 499 http://pybridge.svn.sourceforge.net/pybridge/?rev=499&view=rev Author: umgangee Date: 2007-08-03 11:42:14 -0700 (Fri, 03 Aug 2007) Log Message: ----------- New Rubber class (for rubber bridge), move some logic from Board to BridgeGame. I think this implements rubber bridge scoring/vulnerability rules accurately, but some testing is still required. Modified Paths: -------------- trunk/pybridge/pybridge/games/bridge/board.py trunk/pybridge/pybridge/games/bridge/game.py trunk/pybridge/pybridge/games/bridge/result.py Modified: trunk/pybridge/pybridge/games/bridge/board.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/board.py 2007-07-27 19:05:07 UTC (rev 498) +++ trunk/pybridge/pybridge/games/bridge/board.py 2007-08-03 18:42:14 UTC (rev 499) @@ -48,7 +48,7 @@ @classmethod def first(cls, deal=None): - """Build an initial board. + """Builds an initial board. @deal: if provided, the deal to be wrapped by board. Otherwise, a randomly-generated deal is wrapped. @@ -58,27 +58,22 @@ board['num'] = 1 board['time'] = tuple(time.localtime()) - board['dealer'] = Direction.North # Arbitary. + # Convention for duplicate bridge. + board['dealer'] = Direction.North board['vuln'] = Vulnerable.None return board - def next(self, results, deal=None): - """Given the results for this board (and all previous boards), - builds the next board. + def next(self, deal=None): + """Builds and returns a successor board to this board. - The dealer and vulnerability of the next board are calculated - from the results provided. + The dealer and vulnerability of the successor board are determined from + the board number, according to the rotation scheme for duplicate bridge. - @param result: a list of all previous results, ordered from earliest - to most recent, ie. this board's result is last in list. @param deal: if provided, the deal to be wrapped by next board. Otherwise, a randomly-generated deal is wrapped. """ - boardresult = results[-1] - assert boardresult.board == self - board = Board(self.copy()) # copy() returns a dict. board['deal'] = deal or Deal.fromRandom() board['num'] = board.get('num', 0) + 1 @@ -87,33 +82,10 @@ # Dealer rotates clockwise. board['dealer'] = Direction[(board['dealer'].index + 1) % 4] - if isinstance(boardresult, DuplicateResult): - # See http://www.d21acbl.com/References/Laws/node5.html#law2 - i = (board['num'] - 1) % 16 - # Map from duplicate board index range 1..16 to vulnerability. - board['vuln'] = Vulnerable[(i%4 + i/4)%4] + # Map from duplicate board index range 1..16 to vulnerability. + # See http://www.d21acbl.com/References/Laws/node5.html#law2 + i = (board['num'] - 1) % 16 + board['vuln'] = Vulnerable[(i%4 + i/4)%4] - elif isinstance(boardresult, RubberResult): - belowNS, belowEW = 0, 0 # Running totals of below-the-line scores. - board['vuln'] = Vulnerable.None - # Only consider rounds which score below-the-line. - for result in (r for r in results if r.score.below > 0): - if result.contract.declarer in (Direction.North, Direction.South): - belowNS += result.score.below - pair = Vulnerable.NorthSouth - else: - belowEW += result.score.below - pair = Vulnerable.EastWest - # If either score exceeds 100, pair has made game. - if belowNS >= 100 or belowEW >= 100: - belowNS, belowEW = 0, 0 # Reset totals for next game. - # Vulnerability transitions. - if board['vuln'] == Vulnerable.None: - board['vuln'] = pair - elif board['vuln'] in (pair, Vulnerable.All): - board['vuln'] = Vulnerable.None - else: # Pair was not vulnerable, but other pair was. - board['vuln'] = Vulnerable.All - return board Modified: trunk/pybridge/pybridge/games/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/game.py 2007-07-27 19:05:07 UTC (rev 498) +++ trunk/pybridge/pybridge/games/bridge/game.py 2007-08-03 18:42:14 UTC (rev 499) @@ -25,8 +25,8 @@ from auction import Auction from board import Board -from play import Trick, TrickPlay -from result import DuplicateResult, RubberResult +from play import TrickPlay +from result import DuplicateResult, Rubber, RubberResult from call import Bid, Pass, Double, Redouble from card import Card @@ -56,7 +56,7 @@ Strain.NoTrump: None} - def __init__(self): + def __init__(self, **options): self.listeners = [] self.board = None @@ -68,6 +68,11 @@ self.visibleHands = {} # A subset of deal, containing revealed hands. self.players = {} # One-to-one mapping from BridgePlayer to Direction. + self.options = options + if self.options.get('RubberScoring'): # Use rubber scoring? + self.rubbers = [] # Group results into Rubber objects. + + contract = property(lambda self: self.auction and self.auction.contract or None) trumpSuit = property(lambda self: self.play and self.play.trumpSuit or None) result = property(lambda self: not self.inProgress() and self.results[-1]) @@ -83,10 +88,27 @@ if board: # Use specified board. self.board = board elif self.board: # Advance to next round. - self.board = self.board.next(self.results) - else: # Create a board. + self.board = self.board.next() + else: # Create an initial board. self.board = Board.first() + if self.options.get('RubberScoring'): + # Vulnerability determined by number of games won by each pair. + if len(self.rubbers) == 0 or self.rubbers[-1].winner: + self.board['vuln'] = Vulnerable.None # First round, new rubber. + else: + games = self.rubbers[-1].games + if len(games[(Direction.North, Direction.South)]) > 0: + if len(games[(Direction.East, Direction.West)]) > 0: + self.board['vuln'] = Vulnerable.All + else: + self.board['vuln'] = Vulnerable.NorthSouth + else: + if len(games[(Direction.East, Direction.West)]) > 0: + self.board['vuln'] = Vulnerable.EastWest + else: + self.board['vuln'] = Vulnerable.None + self.auction = Auction(self.board['dealer']) # Start auction. self.play = None self.visibleHands.clear() @@ -246,7 +268,7 @@ # If bidding is passed out, game is complete. if not self.inProgress() and self.board['deal']: - self.results.append(DuplicateResult(self.board, contract=None)) + self._addResult(self.board, contract=None) self.notify('makeCall', call=call, position=position) @@ -318,8 +340,7 @@ # If play is complete, game is complete. if not self.inProgress() and self.board['deal']: tricksMade, _ = self.play.wonTrickCount() - result = DuplicateResult(self.board, self.contract, tricksMade) - self.results.append(result) + self._addResult(self.board, self.contract, tricksMade) self.notify('playCard', card=card, position=position) @@ -329,9 +350,22 @@ hand = self.board['deal'].get(position) if hand and position not in self.visibleHands: self.revealHand(hand, position) - + def _addResult(self, board, contract=None, tricksMade=None): + if self.options.get('RubberScoring'): + result = RubberResult(board, contract, tricksMade) + if len(self.rubbers) > 0 and self.rubbers[-1].winner is None: + rubber = self.rubbers[-1] + else: # Instantiate new rubber. + rubber = Rubber() + self.rubbers.append(rubber) + rubber.append(result) + else: + result = DuplicateResult(board, contract, tricksMade) + self.results.append(result) + + def revealHand(self, hand, position): """Reveal hand to all observers. Modified: trunk/pybridge/pybridge/games/bridge/result.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/result.py 2007-07-27 19:05:07 UTC (rev 498) +++ trunk/pybridge/pybridge/games/bridge/result.py 2007-08-03 18:42:14 UTC (rev 499) @@ -24,10 +24,10 @@ _getScore = NotImplemented # Expected to be implemented by subclasses. - __vulnMapping = {Vulnerable.None: (), - Vulnerable.NorthSouth: (Direction.North, Direction.South), - Vulnerable.EastWest: (Direction.East, Direction.West), - Vulnerable.All: tuple(Direction)} + __vulnMap = {Vulnerable.None: (), + Vulnerable.NorthSouth: (Direction.North, Direction.South), + Vulnerable.EastWest: (Direction.East, Direction.West), + Vulnerable.All: tuple(Direction)} def __init__(self, board, contract, tricksMade=None): @@ -42,7 +42,7 @@ if self.contract: vuln = self.board.get('vuln', Vulnerable.None) - self.isVulnerable = self.contract.declarer in self.__vulnMapping[vuln] + self.isVulnerable = self.contract.declarer in self.__vulnMap[vuln] self.score = self._getScore() @@ -218,3 +218,55 @@ below += value return above, below + + + +class Rubber(list): + """A rubber set, in which pairs compete to make two consecutive games. + + A game is made by accumulation of 100+ points from below-the-line scores + without interruption from an opponent's game. + """ + + games = property(lambda self: self._getGames()) + winner = property(lambda self: self._getWinner()) + + + def _getGames(self): + """Returns, for each pair, a list of completed 'games' won by the pair + in this rubber. + + A game is represented as the list of consecutive results in this rubber, + with below-the-line scores that count towards the game. + """ + gamesNS, gamesEW = [], [] + + game = [] + belowNS, belowEW = 0, 0 # Cumulative totals for results. + for result in self: + game.append(result) + + if result.contract.declarer in (Direction.North, Direction.South): + belowNS += result.score[1] + if belowNS >= 100: + gamesNS.append(game) + else: + belowEW += result.score[1] + if belowEW >= 100: + gamesEW.append(game) + + # If either accumulated total exceeds 100, proceed to next game. + if belowNS >= 100 or belowEW >= 100: + game = [] + belowNS, belowEW = 0, 0 + + return {(Direction.North, Direction.South): gamesNS, + (Direction.East, Direction.West): gamesEW} + + + def _getWinner(self): + """The rubber is won by the pair which have completed two games.""" + for pair, games in self.games.items(): + if len(games) >= 2: + return pair + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |