|
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.
|