/usr/share/pyshared/pybridge/bridge/pbn.py is in pybridge-common 0.3.0-7.2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | # 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 provides two-way conversion between BridgeGame objects and the
PBN 2.0 (Portable Bridge Notation) format.
The PBN 2.0 specification is available from http://www.tistis.nl/pbn/.
"""
import re
import time
from card import Card
from deck import Deck
from call import Bid, Pass, Double, Redouble
from symbols import Player as Position, Level, Strain, Rank, Suit, Vulnerable
class ParseError(Exception):
"""Raised when PBN parser encounters an unexpected input."""
def importPBN(pbn, complete=True):
"""Builds a BridgeGame object from a given PBN "import"-format string.
@param pbn: a string containing PBN markup.
@param complete: if True, expect the game described in pbn to be complete.
Raises a ParseError if BridgeGame cannot be completed.
@return: a BridgeGame object equivalent to the PBN string.
"""
from newnewgame import BridgeGame, GameError
# Mappings from PBN symbols to their PyBridge type equivalents.
BIDLEVEL = dict(zip('1234567', Level))
BIDSTRAIN = dict(zip('CDHS', Strain) + [('NT', Strain.NoTrump)])
CARDRANK = dict(zip('23456789TJQKA', Rank))
CARDSUIT = dict(zip('CDHS', Suit))
POSITION = dict(zip('NESW', Position))
VULN = {'All': Vulnerable.All, 'Both': Vulnerable.All, '-': Vulnerable.None,
'NS': Vulnerable.NorthSouth, 'EW': Vulnerable.EastWest,
'None': Vulnerable.None, 'Love': Vulnerable.None}
game = BridgeGame()
players = dict([(pos, game.addPlayer(pos)) for pos in Position])
tags, sections, notes = parsePBN(pbn)
board = {}
# Load values of non-essential tags.
board['event'] = tags.get('Event')
board['site'] = tags.get('Site')
if tags.get('Date'): # Convert to time tuple.
board['date'] = time.strptime(tags['Date'], "%Y.%m.%d")
board['boardnum'] = int(tags.get('Board', 0))
board['players'] = dict([(p, tags.get(p.key)) for p in Position])
# Load values of essential tags.
for tag in ('Dealer', 'Vulnerable', 'Deal', 'Auction', 'Play'):
if tag not in tags:
raise ParseError, "Required tag '%s' not found" % tag
try:
board['dealer'] = POSITION[tags['Dealer']]
board['vulnerable'] = VULN[tags['Vulnerable']]
# Reconstruct deal.
board['deal'] = {}
first, hands = tags['Deal'].split(":")
firstindex = POSITION[first.strip()].index
order = Position[firstindex:] + Position[:firstindex]
for player, hand in zip(order, hands.strip().split()):
board['deal'][player] = []
for suit, suitcards in zip(reversed(Suit), hand.split('.')):
for rank in suitcards:
card = Card(CARDRANK[rank], suit)
board['deal'][player].append(card)
# Validate deal.
deck = Deck()
if not deck.isValidDeal(board['deal']):
raise ParseError, "Deal does not validate"
game.start(board) # Initialise game with board.
# Process Auction section: build bidding.
# TODO: PBN does not need to provide an auction.
pos = POSITION[tags['Auction']]
for item in sections['Auction'].split():
if item.startswith('='): # A note identifier.
continue
elif item.startswith('-'): # Skip position.
pos = Position[(pos.index + 1) % len(Position)] # Next player.
continue
# Extract call from item.
if item[0] in BIDLEVEL and item[1:] in BIDSTRAIN:
call = Bid(BIDLEVEL[item[0]], BIDSTRAIN[item[1:]])
elif item == 'Pass':
call = Pass()
elif item == 'X':
call = Double()
elif item == 'XX':
call = Redouble()
else:
raise ParseError, "Unrecognised item '%s' in Auction" % item
try: # Make call.
players[pos].makeCall(call)
pos = Position[(pos.index + 1) % len(Position)] # Next player.
except GameError, err:
raise ParseError, "Invalid call %s in Auction: %s" % (call, err)
# Process Play section: build play.
first = POSITION[tags['Play']]
for line in sections['Play'].splitlines():
leader, cards = game.getTurn(), {} # Trick.
# Extract cards from line.
for item in line.split():
print item
if item.startswith('='): # A note identifier.
continue
if item[0] in CARDSUIT and item[1] in CARDRANK:
card = Card(CARDRANK[item[1]], CARDSUIT[item[0]])
cards[Position[(first.index + len(cards)) % len(Position)]] = card
else:
raise ParseError, "Unrecognised item '%s' in Play" % item
try: # Play cards in trick.
for pos in Position[leader.index:] + Position[:leader.index]:
players[pos].playCard(card)
except GameError, err:
raise ParseError, "Invalid card %s in Play: %s" % (card, err)
except KeyError, key:
raise ParseError, "Invalid value %s for attribute" % key
def exportPBN(game):
"""Builds a PBN "export"-format string from a given BridgeGame object.
@param game: a BridgeGame object.
@return: a PBN string equivalent to the BridgeGame object.
"""
# Mappings from PyBridge symbol types to their PBN equivalents.
RANKS = dict(zip(Rank, "23456789TJQKA"))
SUITS = dict(zip(Suit, "CDHS"))
POSITIONS = dict(zip(Player, "NESW"))
'''
def importPBN(self, pbn):
"""Builds a BridgeGame object from a given PBN "import format" string.
@param pbn: a string containing PBN markup.
@return: a BridgeGame object equivalent to the PBN string.
"""
# This lambda reverses the mapping between keys and values of dict d.
# It assumes there are no duplicate values in d.
invert = lambda d: dict([(v, k) for k, v in d.iteritems()])
tagValues, sectionData = self.parse(pbn)
# Get dealer.
dealer = invert(self.SEATS)[tagValues['Dealer']]
# Get deal.
deal = {}
# Determine first hand in Deal string.
first = invert(self.SEATS)[tagValues['Deal'][0]]
seatorder = Seat[first.index:] + Seat[:first.index]
# Split deal into hands, into suits, into cards.
handstrings = tagValues['Deal'][2:].split(' ')
for seat, handstring in zip(seatorder, handstrings):
deal[seat] = []
for suit, rankstring in zip(self.SUITORDER, handstring.split('.')):
for rankchar in rankstring:
card = Card(invert(self.RANKS)[rankchar], suit)
deal[seat].append(card)
# Get vulnerability.
for vulnTuple, vulnTexts in self.VULNERABLE.items():
# Since PBN allows variations on the Vulnerable tag.
if tagValues['Vulnerable'] in vulnTexts:
vulnNS, vulnEW = vulnTuple
break
scoring = scoreDuplicate # For now, just use duplicate scoring method.
game = Game(dealer, deal, scoring, vulnNS, vulnEW)
#
# TODO - determine the calls made, load them in with game.makeCall
# - determine the cards played, load them in with game.playCard
#
# Finally, return the BridgeGame object.
return game
def exportPBN(self, game):
"""Builds a PBN "export format" string from a given BridgeGame object.
@param game: a BridgeGame object.
@return: a PBN string equivalent to the BridgeGame object.
"""
def makeTag(key, value):
"""A convenience function to generate a PBN-style tag."""
return '[%s \"%s\"]\n' % (key, value)
pbn = ''
#
# TODO: fill out all 15 fields, with respect to PBN spec.
#
# (1) Event (the name of the tournament or match)
pbn += makeTag('Event', 'Unknown')
# (2) Site (the location of the event)
pbn += makeTag('Site', 'Unknown')
# (3) Date (the starting date of the game)
# TODO: use the localtime() method of the time module
pbn += makeTag('Date', '%s.%s.%s' % (1,2,3))
# (4) Board (the board number)
pbn += makeTag('Board', '1')
# (5) West (the west player)
# (6) North (the north player)
# (7) East (the east player)
# (8) South (the south player)
# TODO: use 'Unknown' tags
# (9) Dealer (the dealer)
pbn += makeTag('Dealer', self.SEATS[game.bidding.dealer])
# (10) Vulnerable (the situation of vulnerability)
# TODO: put game.vulnNS and game.vulnEW into a tuple, use tuple as index on self.VULNERABLE,
# get first value (the [0]) from list.
# pbn += makeTag('Vulnerable', self.VULNERABLE[( , )][0]
# (11) Deal (the dealt cards)
# (12) Scoring (the scoring method)
# (13) Declarer (the declarer of the contract)
# (14) Contract (the contract)
# (15) Result (the result of the game)
return pbn
'''
def parsePBN(pbn):
"""Parses the given PBN string and extracts:
* for each PBN tag, a dict of associated key/value pairs.
* for each data section, a dict of key/data pairs.
This method does not interpret the PBN string itself.
@param pbn: a string containing PBN markup.
@return: a tuple (tag values, section data, notes).
"""
tagValues, sectionData, notes = {}, {}, {}
for line in pbn.splitlines():
line.strip() # Remove whitespace.
if line.startswith('%'): # A comment.
pass # Skip over comments.
elif line.startswith('['): # A tag.
line = line.strip('[]') # Remove leading [ and trailing ].
# The key is the first word, the value is everything after.
tag, value = line.split(' ', 1)
tag = tag.capitalize()
value = value.strip('\'\"')
if tag == 'Note':
notes.setdefault(tag, [])
notes[tag].append(value)
else:
tagValues[tag] = value
else: # Line follows tag, add line to data buffer for section.
sectionData.setdefault(tag, '')
sectionData[tag] += line + '\n'
return tagValues, sectionData, notes
def testImport():
pbnstr = file("a-pbn-file.pbn").read() # Set path as appropriate.
pbn = PBNHandler() # Create our handler.
g = pbn.importPBN(pbnstr) # Do it!
return g
|