def addUser(self, user): """add user to this table""" if user.name in list(x.name for x in self.users): raise srvError(pb.Error, m18nE('You already joined this table')) if len(self.users) == self.maxSeats(): raise srvError(pb.Error, m18nE('All seats are already taken')) self.users.append(user) self.sendChatMessage(ChatMessage(self.tableid, user.name, m18nE('takes a seat'), isStatusMessage=True))
def __failed(self, result, request): """a user did not or not correctly answer""" if request in self.requests: self.removeRequest(request) if result.type in [pb.PBConnectionLost]: msg = m18nE('The game server lost connection to player %1') self.table.abort(msg, request.user.name) else: msg = m18nE('Error for player %1: %2\n%3') try: traceBack = result.getTraceback() except BaseException: # may happen with twisted 12.3.0 traceBack = 'twisted cannot give us a traceback' self.table.abort(msg, request.user.name, result.getErrorMessage(), traceBack)
def requestAvatarId(self, cred): # pylint: disable=R0201 """get user id from database""" args = cred.username.split(SERVERMARK) if len(args) > 1: if args[0] == 'adduser': cred.username = args[1] password = args[2] with Transaction(): query = Query('insert into player(name,password) values(?,?)', list([cred.username.decode('utf-8'), password.decode('utf-8')])) if not query.success: if query.msg.startswith('ERROR: constraint failed') \ or 'not unique' in query.msg: template = m18nE('User %1 already exists') logInfo(m18n(template, cred.username)) query.msg = srvMessage(template, cred.username) else: logInfo(query.msg) return fail(credError.UnauthorizedLogin(query.msg)) elif args[1] == 'deluser': pass query = Query('select id, password from player where name=?', list([cred.username.decode('utf-8')])) if not len(query.records): template = 'Wrong username: %1' logInfo(m18n(template, cred.username)) return fail(credError.UnauthorizedLogin(srvMessage(template, cred.username))) userid, password = query.records[0] # checkPassword uses md5 which cannot handle unicode strings (python 2.7) defer1 = maybeDeferred(cred.checkPassword, password.encode('utf-8')) defer1.addCallback(DBPasswordChecker._checkedPassword, userid) return defer1
def logout(self, user): """remove user from all tables""" if user in self.users and user.mind: self.callRemote(user,'serverDisconnects') user.mind = None for block in DeferredBlock.blocks: for request in block.requests: if request.player.remote == user: block.removeRequest(request) if user in self.users: # avoid recursion : a disconnect error calls logout for table in self.tables.values(): if user in table.users: if table.game: self.closeTable(table, 'abort', m18nE('Player %1 has logged out'), user.name) else: self.leaveTable(user, table.tableid) self.users.remove(user) if InternalParameters.socket and not InternalParameters.continueServer \ and not self.users and reactor.running: # do not stop right now, the client might reconnect right away # this happens if the wanted human player name did not yet exist # in the data base - in that case login fails. Next the client # might tell us to add that user to the data base. So let's wait # to see for 5 seconds if he does reactor.callLater(5, self.stopNowAfterLastDisconnect)
def showConcealedMelds(self, concealedMelds, ignoreDiscard=None): """the server tells how the winner shows and melds his concealed tiles. In case of error, return message and arguments""" for part in concealedMelds.split(): meld = Meld(part) for pair in meld.pairs: if pair == ignoreDiscard: ignoreDiscard = None else: if not pair in self.__concealedTileNames: msg = m18nE('%1 claiming MahJongg: She does not really have tile %2') return msg, self.name, pair self.__concealedTileNames.remove(pair) self.addMeld(meld) if self.__concealedTileNames: msg = m18nE('%1 claiming MahJongg: She did not pass all concealed tiles to the server') return msg, self.name self.__hand = None
def delUser(self, user): """remove user from this table""" if user in self.users: self.game = None self.users.remove(user) self.sendChatMessage(ChatMessage(self.tableid, user.name, m18nE('leaves the table'), isStatusMessage=True)) if user is self.owner: # silently pass ownership if self.users: self.owner = self.users[0]
def claimTile(self, player, claim, meldTiles, nextMessage): """a player claims a tile for pung, kong or chow. meldTiles contains the claimed tile, concealed""" if not self.game: return lastDiscard = self.game.lastDiscard claimedTile = lastDiscard.element hasTiles = meldTiles[:] discardingPlayer = self.game.activePlayer hasTiles.remove(claimedTile) meld = Meld(meldTiles) if len(meldTiles) != 4 and meld.meldType not in [PAIR, PUNG, KONG, CHOW]: msg = m18nE('%1 wrongly said %2 for meld %3') + 'x:' + str(meld.meldType) + meld.joined self.abort(msg, player.name, claim.name, str(meld)) return if not player.hasConcealedTiles(hasTiles): msg = m18nE('%1 wrongly said %2: claims to have concealed tiles %3 but only has %4') self.abort(msg, player.name, claim.name, ' '.join(hasTiles), ''.join(player.concealedTileNames)) return # update our internal state before we listen to the clients again self.game.discardedTiles[claimedTile.lower()] -= 1 self.game.activePlayer = player if claimedTile: player.lastTile = claimedTile.lower() player.lastSource = 'd' player.exposeMeld(hasTiles, claimedTile) self.game.lastDiscard = None block = DeferredBlock(self) if (nextMessage != Message.Kong and self.game.dangerousFor(discardingPlayer, lastDiscard) and discardingPlayer.playedDangerous): player.usedDangerousFrom = discardingPlayer if Debug.dangerousGame: logDebug('%s claims dangerous tile %s discarded by %s' % \ (player, lastDiscard, discardingPlayer)) block.tellAll(player, Message.UsedDangerousFrom, source=discardingPlayer.name) block.tellAll(player, nextMessage, source=meldTiles) if claim == Message.Kong: block.callback(self.pickKongReplacement) else: block.callback(self.moved)
def perspective_setClientProperties(self, dbIdent, voiceId, maxGameId, clientVersion=None): """perspective_* methods are to be called remotely""" self.dbIdent = dbIdent self.voiceId = voiceId self.maxGameId = maxGameId serverVersion = InternalParameters.version if clientVersion != serverVersion: # we assume that versions x.y.* are compatible if clientVersion is None: # client passed no version info return fail(srvError(pb.Error, m18nE('Your client has a version older than 4.9.0 but you need %1 for this server'), serverVersion)) else: commonDigits = len([x for x in zip( clientVersion.split('.'), serverVersion.split('.')) if x[0] == x[1]]) if commonDigits < 2: return fail(srvError(pb.Error, m18nE('Your client has version %1 but you need %2 for this server'), clientVersion or '<4.9.0', '.'.join(serverVersion.split('.')[:2]) + '.*'))
def declareKong(self, player, meldTiles): """player declares a Kong, meldTiles is a list""" if not player.hasConcealedTiles(meldTiles) and not player.hasExposedPungOf(meldTiles[0]): # pylint: disable=W0142 msg = m18nE('declareKong:%1 wrongly said Kong for meld %2') args = (player.name, ''.join(meldTiles)) logError(m18n(msg, *args)) logError('declareKong:concealedTileNames:%s' % ''.join(player.concealedTileNames)) logError('declareKong:concealedMelds:%s' % \ ' '.join(x.joined for x in player.concealedMelds)) logError('declareKong:exposedMelds:%s' % \ ' '.join(x.joined for x in player.exposedMelds)) self.abort(msg, *args) return player.exposeMeld(meldTiles) self.tellAll(player, Message.DeclaredKong, self.pickKongReplacement, source=meldTiles)
def readyForGameStart(self, user): """the table initiator told us he wants to start the game""" # pylint: disable=R0912 # pylint too many branches if len(self.users) < self.maxSeats() and self.owner != user: raise srvError(pb.Error, m18nE('Only the initiator %1 can start this game, you are %2'), self.owner.name, user.name) if not self.suspended: self.preparedGame = self.prepareNewGame() game = self.preparedGame self.connectPlayers(game) self.__checkDbIdents(game) if self.suspended: self.initGame() else: self.proposeGameId(self.calcGameId())
def nextHand(self, dummyResults): """next hand: maybe rotate""" if not self.game: return DeferredBlock.garbageCollection() for block in DeferredBlock.blocks: if block.table == self: logError('request left from previous hand: %s' % block.outstandingStr()) token = self.game.handId() # we need to send the old token until the # clients started the new hand rotateWinds = self.game.maybeRotateWinds() if self.game.finished(): self.close('gameOver', m18nE('The game is over!')) return self.game.sortPlayers() playerNames = list((x.wind, x.name) for x in self.game.players) self.tellAll(None, Message.ReadyForHandStart, self.startHand, source=playerNames, rotateWinds=rotateWinds, token=token)
def chat(self): """chat. Only generate ChatWindow after the message has successfully been sent to the server. Because the server might have gone away.""" def initChat(_): """now that we were able to send the message to the server instantiate the chat window""" table.chatWindow = ChatWindow(table) table.chatWindow.receiveLine(msg) table = self.selectedTable() if not table.chatWindow: line = m18nE('opens a chat window') msg = ChatMessage(table.tableid, table.client.name, line, isStatusMessage=True) table.client.sendChat(msg).addCallback(initChat).addErrback(self.client.tableError) elif table.chatWindow.isVisible(): table.chatWindow.hide() else: table.chatWindow.show()
def claimMahJongg(self, msg): """a player claims mah jongg. Check this and if correct, tell all.""" # pylint: disable=R0912 # too many branches if not self.game: return player = msg.player concealedMelds, withDiscard, lastMeld = msg.args if self.game.ruleset.mustDeclareCallingHand: assert player.isCalling, '%s: concmelds:%s withdiscard:%s lastmeld:%s' % ( player, concealedMelds, withDiscard, lastMeld) discardingPlayer = self.game.activePlayer # pylint: disable=E1103 # (pylint ticket 8774) lastMove = self.game.lastMoves(without=[Message.PopupMsg]).next() robbedTheKong = lastMove.message == Message.DeclaredKong if robbedTheKong: player.lastSource = 'k' withDiscard = lastMove.source[0].capitalize() lastMove.player.robTile(withDiscard) lastMeld = Meld(lastMeld) msgArgs = player.showConcealedMelds(concealedMelds, withDiscard) if msgArgs: self.abort(*msgArgs) # pylint: disable=W0142 player.declaredMahJongg(concealedMelds, withDiscard, player.lastTile, lastMeld) if not player.hand.won: msg = m18nE('%1 claiming MahJongg: This is not a winning hand: %2') self.abort(msg, player.name, player.hand.string) return block = DeferredBlock(self) if robbedTheKong: block.tellAll(player, Message.RobbedTheKong, tile=withDiscard) if (player.lastSource == 'd' and self.game.dangerousFor(discardingPlayer, player.lastTile) and discardingPlayer.playedDangerous): player.usedDangerousFrom = discardingPlayer if Debug.dangerousGame: logDebug('%s wins with dangerous tile %s from %s' % \ (player, self.game.lastDiscard, discardingPlayer)) block.tellAll(player, Message.UsedDangerousFrom, source=discardingPlayer.name) block.tellAll(player, Message.MahJongg, source=concealedMelds, lastTile=player.lastTile, lastMeld=list(lastMeld.pairs), withDiscard=withDiscard) block.callback(self.endHand)
def tell(self, about, receivers, command, **kwargs): """send info about player 'about' to users 'receivers'""" if about.__class__.__name__ == 'User': about = self.playerForUser(about) if not isinstance(receivers, list): receivers = list([receivers]) assert receivers, 'DeferredBlock.tell(%s) has no receiver' % command self.__enrichMessage(self.table.game, about, command, kwargs) aboutName = about.name if about else None if self.table.running and len(receivers) in [1, 4]: # messages are either identical for all 4 players # or identical for 3 players and different for 1 player. And # we want to capture each message exactly once. self.table.game.appendMove(about, command, kwargs) localDeferreds = [] for rec in self.__convertReceivers(receivers): isClient = rec.__class__.__name__.endswith('Client') if Debug.traffic and not isClient: message = '-> {receiver:<15} about {about} {command}{kwargs}'.format( receiver=rec.name[:15], about=about, command=command, kwargs=Move.prettyKwargs(kwargs)) logDebug(message) if isClient: defer = Deferred() defer.addCallback(rec.remote_move, command, **kwargs) else: defer = self.table.server.callRemote(rec, 'move', aboutName, command.name, **kwargs) if defer: defer.command = command.name defer.notifying = 'notifying' in kwargs self.__addRequest(defer, rec, about) else: msg = m18nE('The game server lost connection to player %1') self.table.abort(msg, rec.name) if isClient: localDeferreds.append(defer) for defer in localDeferreds: defer.callback(aboutName) # callback needs an argument !
def joinTable(self, user, tableid): """user joins table""" if tableid in self.tables: table = self._lookupTable(tableid) table.addUser(user) for user in self.users: self.callRemote(user, 'replaceTable', table.msg(user)) return True else: # might be a suspended table: for suspTable in self.suspendedTables.values(): assert isinstance(suspTable.preparedGame, RemoteGame), suspTable.preparedGame if suspTable.tableid == tableid: self.setTableId(suspTable) # puts suspTable into self.tables del self.suspendedTables[suspTable.preparedGame.gameid] suspTable.addUser(user) for user in self.users: self.callRemote(user, 'replaceTable', suspTable.msg(user)) if len(suspTable.users) == suspTable.maxSeats(): suspTable.readyForGameStart(suspTable.owner) return True raise srvError(pb.Error, m18nE('table with id <numid>%1</numid> not found'), tableid)
def chat(self): """chat. Only generate ChatWindow after the message has successfully been sent to the server. Because the server might have gone away.""" def initChat(_): """now that we were able to send the message to the server instantiate the chat window""" table.chatWindow = ChatWindow(table) table.chatWindow.receiveLine(msg) table = self.selectedTable() if not table.chatWindow: line = m18nE('opens a chat window') msg = ChatMessage(table.tableid, table.client.name, line, isStatusMessage=True) table.client.sendChat(msg).addCallback(initChat).addErrback( self.client.tableError) elif table.chatWindow.isVisible(): table.chatWindow.hide() else: table.chatWindow.show()
def loadRules(self): # TODO: we need a separate part for any number of announcements. Both r for robbing kong and a for # Original Call can be possible together. ClassicalChinese.loadRules(self) del self.winnerRules['Zero Point Hand'] originalCall = self.winnerRules.pop('Mah Jongg with Original Call') originalCall.name = m18nE('Original Call') self.handRules.add(originalCall) del self.mjRules['Nine Gates'] self.mjRules.add(Rule('Gates of Heaven', 'FGatesOfHeaven||Opair28', limits=1, description=m18n('All tiles concealed of same color: Values 1-1-1-2-3-4-5-6-7-8-9-9-9 and ' 'another tile 2..8 of the same color'))) self.mjRules.add(Rule('Wriggling Snake', 'FWrigglingSnake', limits=1, description=m18n('Pair of 1s and a run from 2 to 9 in the same suit with each of the winds'))) self.mjRules.add(Rule('Triple Knitting', 'FTripleKnitting', limits=0.5, description=m18n('Four sets of three tiles in the different suits and a pair: No Winds or Dragons'))) self.mjRules.add(Rule('Knitting', 'FKnitting', limits=0.5, description=m18n('7 pairs of tiles in any 2 out of 3 suits; no Winds or Dragons'))) self.mjRules.add(Rule('All pair honors', 'FAllPairHonors', limits=0.5, description=m18n('7 pairs of 1s/9s/Winds/Dragons'))) del self.handRules['Own Flower and Own Season'] del self.handRules['Three Concealed Pongs'] self.handRules.add(Rule('Own Flower', 'FOwnFlower', doubles=1)) self.handRules.add(Rule('Own Season', 'FOwnSeason', doubles=1)) del self.winnerRules['Last Tile Taken from Dead Wall'] del self.winnerRules['Hidden Treasure'] del self.winnerRules['False Color Game'] del self.winnerRules['Concealed True Color Game'] del self.winnerRules['East won nine times in a row'] del self.winnerRules['Last Tile Completes Pair of 2..8'] del self.winnerRules['Last Tile Completes Pair of Terminals or Honors'] del self.winnerRules['Last Tile is Only Possible Tile'] self.winnerRules.add(Rule('Buried Treasure', 'FBuriedTreasure', limits=1, description=m18n('Concealed pungs of one suit with winds/dragons and a pair'))) del self.winnerRules['True Color Game'] self.winnerRules.add(Rule('Purity', 'FPurity', doubles=3, description=m18n('Only same-colored tiles (no chows, dragons or winds)'))) self.winnerRules['All Greens'].name = m18nE('Imperial Jade') self.mjRules['Thirteen Orphans'].name = m18nE('The 13 Unique Wonders') del self.winnerRules['Three Great Scholars'] self.winnerRules.add(Rule('Three Great Scholars', 'FThreeGreatScholars||Onochow', limits=1, description=m18n('3 Pungs or Kongs of dragons plus any pung/kong and a pair'))) self.handRules['All Flowers'].score.doubles = 2 self.handRules['All Seasons'].score.doubles = 2 self.penaltyRules.add(Rule('False Naming of Discard, Claimed for Chow/Pung/Kong', points = -50)) self.penaltyRules.add(Rule('False Declaration of Mah Jongg by One Player', 'Oabsolute payees=3', limits = -0.5)) self.winnerRules.add(Rule('False Naming of Discard, Claimed for Mah Jongg', 'FFalseDiscardForMJ||Opayforall')) self.loserRules.add(Rule('Calling for Only Honors', 'FCallingHand||Ohand=OnlyHonors', limits=0.4)) self.loserRules.add(Rule('Calling for Wriggling Snake', 'FCallingHand||Ohand=WrigglingSnake', limits=0.4)) self.loserRules.add(Rule('Calling for Triple Knitting', 'FCallingHand||Ohand=TripleKnitting', limits=0.2)) self.loserRules.add(Rule('Calling for Gates of Heaven', 'FCallingHand||Ohand=GatesOfHeaven||Opair28', limits=0.4)) self.loserRules.add(Rule('Calling for Knitting', 'FCallingHand||Ohand=Knitting', limits=0.2)) self.loserRules.add(Rule('Calling for Imperial Jade', 'FCallingHand||Ohand=AllGreen', limits=0.4)) self.loserRules.add(Rule('Calling for 13 Unique Wonders', 'FCallingHand||Ohand=ThirteenOrphans', limits=0.4)) self.loserRules.add(Rule('Calling for Three Great Scholars', 'FCallingHand||Ohand=ThreeGreatScholars', limits=0.4)) self.loserRules.add(Rule('Calling for All pair honors', 'FCallingHand||Ohand=AllPairHonors', limits=0.2)) self.loserRules.add(Rule('Calling for Heads and Tails', 'FCallingHand||Ohand=AllTerminals', limits=0.4)) self.loserRules.add(Rule('Calling for Four Blessings Hovering over the Door', 'FCallingHand||Ohand=FourBlessingsHoveringOverTheDoor', limits=0.4)) self.loserRules.add(Rule('Calling for Buried Treasure', 'FCallingHand||Ohand=BuriedTreasure', limits=0.4)) self.loserRules.add(Rule('Calling for Fourfold Plenty', 'FCallingHand||Ohand=FourfoldPlenty', limits=0.4)) self.loserRules.add(Rule('Calling for Purity', 'FCallingHand||Ohand=Purity', doubles=3))
class Score(object): """holds all parts contributing to a score. It has two use cases: 1. for defining what a rules does: either points or doubles or limits, holding never more than one unit 2. for summing up the scores of all rules: Now more than one of the units can be in use. If a rule should want to set more than one unit, split it into two rules. For the first use case only we have the attributes value and unit""" __hash__ = None def __init__(self, points=0, doubles=0, limits=0, ruleset=None): self.points = 0 # define the types for those values self.doubles = 0 self.limits = 0.0 self.ruleset = ruleset self.points = type(self.points)(points) self.doubles = type(self.doubles)(doubles) self.limits = type(self.limits)(limits) unitNames = { m18nE('points'): 0, m18nE('doubles'): 50, m18nE('limits'): 9999 } def clear(self): """set all to 0""" self.points = self.doubles = self.limits = 0 def change(self, unitName, value): """sets value for unitName. If changed, return True""" oldValue = self.__getattribute__(unitName) if isinstance(value, QVariant): value = value.toString() newValue = type(oldValue)(value) if newValue == oldValue: return False, None if newValue: if unitName == 'points': if self.doubles: return False, 'Cannot have points and doubles' if unitName == 'doubles': if self.points: return False, 'Cannot have points and doubles' self.__setattr__(unitName, newValue) return True, None def __str__(self): """make score printable""" parts = [] if self.points: parts.append('points=%d' % self.points) if self.doubles: parts.append('doubles=%d' % self.doubles) if self.limits: parts.append('limits=%f' % self.limits) return ' '.join(parts) def __repr__(self): return 'Score(%s)' % str(self) def contentStr(self): """make score readable for humans, i18n""" parts = [] if self.points: parts.append(m18nc('Kajongg', '%1 points', self.points)) if self.doubles: parts.append(m18nc('Kajongg', '%1 doubles', self.doubles)) if self.limits: parts.append(m18nc('Kajongg', '%1 limits', self.limits)) return ' '.join(parts) def __eq__(self, other): """ == comparison """ assert isinstance(other, Score) return self.points == other.points and self.doubles == other.doubles and self.limits == other.limits def __ne__(self, other): """ != comparison """ return self.points != other.points or self.doubles != other.doubles or self.limits != other.limits def __lt__(self, other): return self.total() < other.total() def __le__(self, other): return self.total() <= other.total() def __gt__(self, other): return self.total() > other.total() def __ge__(self, other): return self.total() >= other.total() def __add__(self, other): """implement adding Score""" return Score(self.points + other.points, self.doubles + other.doubles, max(self.limits, other.limits), self.ruleset or other.ruleset) def total(self): """the total score""" if self.ruleset is None: raise Exception('Score.total: ruleset unknown for %s' % self) score = int(self.points * (2**self.doubles)) if self.limits: if self.limits >= 1: self.points = self.doubles = 0 elif self.limits * self.ruleset.limit >= score: self.points = self.doubles = 0 else: self.limits = 0 if self.limits: return int(round(self.limits * self.ruleset.limit)) if not self.ruleset.roofOff: score = min(score, self.ruleset.limit) return score def __int__(self): """the total score""" return self.total() def __nonzero__(self): """for bool() conversion""" return self.points != 0 or self.doubles != 0 or self.limits != 0
def __init__(self, name=None): PredefinedRuleset.__init__(self, name or m18nE('Classical Chinese standard'))
def __init__(self, name=None): ClassicalChinese.__init__(self, name or m18nE('Classical Chinese BMJA'))
def loadRules(self): # TODO: we need a separate part for any number of announcements. Both r for robbing kong and a for # Original Call can be possible together. ClassicalChinese.loadRules(self) del self.winnerRules['Zero Point Hand'] originalCall = self.winnerRules.pop('Mah Jongg with Original Call') originalCall.name = m18nE('Original Call') self.handRules.add(originalCall) del self.mjRules['Nine Gates'] self.mjRules.add( Rule( 'Gates of Heaven', 'FGatesOfHeaven||Opair28', limits=1, description=m18n( 'All tiles concealed of same color: Values 1-1-1-2-3-4-5-6-7-8-9-9-9 and ' 'another tile 2..8 of the same color'))) self.mjRules.add( Rule( 'Wriggling Snake', 'FWrigglingSnake', limits=1, description=m18n( 'Pair of 1s and a run from 2 to 9 in the same suit with each of the winds' ))) self.mjRules.add( Rule( 'Triple Knitting', 'FTripleKnitting', limits=0.5, description=m18n( 'Four sets of three tiles in the different suits and a pair: No Winds or Dragons' ))) self.mjRules.add( Rule( 'Knitting', 'FKnitting', limits=0.5, description=m18n( '7 pairs of tiles in any 2 out of 3 suits; no Winds or Dragons' ))) self.mjRules.add( Rule('All pair honors', 'FAllPairHonors', limits=0.5, description=m18n('7 pairs of 1s/9s/Winds/Dragons'))) del self.handRules['Own Flower and Own Season'] del self.handRules['Three Concealed Pongs'] self.handRules.add(Rule('Own Flower', 'FOwnFlower', doubles=1)) self.handRules.add(Rule('Own Season', 'FOwnSeason', doubles=1)) del self.winnerRules['Last Tile Taken from Dead Wall'] del self.winnerRules['Hidden Treasure'] del self.winnerRules['False Color Game'] del self.winnerRules['Concealed True Color Game'] del self.winnerRules['East won nine times in a row'] del self.winnerRules['Last Tile Completes Pair of 2..8'] del self.winnerRules['Last Tile Completes Pair of Terminals or Honors'] del self.winnerRules['Last Tile is Only Possible Tile'] self.winnerRules.add( Rule( 'Buried Treasure', 'FBuriedTreasure', limits=1, description=m18n( 'Concealed pungs of one suit with winds/dragons and a pair' ))) del self.winnerRules['True Color Game'] self.winnerRules.add( Rule('Purity', 'FPurity', doubles=3, description=m18n( 'Only same-colored tiles (no chows, dragons or winds)'))) self.winnerRules['All Greens'].name = m18nE('Imperial Jade') self.mjRules['Thirteen Orphans'].name = m18nE('The 13 Unique Wonders') del self.winnerRules['Three Great Scholars'] self.winnerRules.add( Rule( 'Three Great Scholars', 'FThreeGreatScholars||Onochow', limits=1, description=m18n( '3 Pungs or Kongs of dragons plus any pung/kong and a pair' ))) self.handRules['All Flowers'].score.doubles = 2 self.handRules['All Seasons'].score.doubles = 2 self.penaltyRules.add( Rule('False Naming of Discard, Claimed for Chow/Pung/Kong', points=-50)) self.penaltyRules.add( Rule('False Declaration of Mah Jongg by One Player', 'Oabsolute payees=3', limits=-0.5)) self.winnerRules.add( Rule('False Naming of Discard, Claimed for Mah Jongg', 'FFalseDiscardForMJ||Opayforall')) self.loserRules.add( Rule('Calling for Only Honors', 'FCallingHand||Ohand=OnlyHonors', limits=0.4)) self.loserRules.add( Rule('Calling for Wriggling Snake', 'FCallingHand||Ohand=WrigglingSnake', limits=0.4)) self.loserRules.add( Rule('Calling for Triple Knitting', 'FCallingHand||Ohand=TripleKnitting', limits=0.2)) self.loserRules.add( Rule('Calling for Gates of Heaven', 'FCallingHand||Ohand=GatesOfHeaven||Opair28', limits=0.4)) self.loserRules.add( Rule('Calling for Knitting', 'FCallingHand||Ohand=Knitting', limits=0.2)) self.loserRules.add( Rule('Calling for Imperial Jade', 'FCallingHand||Ohand=AllGreen', limits=0.4)) self.loserRules.add( Rule('Calling for 13 Unique Wonders', 'FCallingHand||Ohand=ThirteenOrphans', limits=0.4)) self.loserRules.add( Rule('Calling for Three Great Scholars', 'FCallingHand||Ohand=ThreeGreatScholars', limits=0.4)) self.loserRules.add( Rule('Calling for All pair honors', 'FCallingHand||Ohand=AllPairHonors', limits=0.2)) self.loserRules.add( Rule('Calling for Heads and Tails', 'FCallingHand||Ohand=AllTerminals', limits=0.4)) self.loserRules.add( Rule('Calling for Four Blessings Hovering over the Door', 'FCallingHand||Ohand=FourBlessingsHoveringOverTheDoor', limits=0.4)) self.loserRules.add( Rule('Calling for Buried Treasure', 'FCallingHand||Ohand=BuriedTreasure', limits=0.4)) self.loserRules.add( Rule('Calling for Fourfold Plenty', 'FCallingHand||Ohand=FourfoldPlenty', limits=0.4)) self.loserRules.add( Rule('Calling for Purity', 'FCallingHand||Ohand=Purity', doubles=3))
def _lookupTable(self, tableid): """return table by id or raise exception""" if tableid not in self.tables: raise srvError(pb.Error, m18nE('table with id <numid>%1</numid> not found'), tableid) return self.tables[tableid]
def _checkedPassword(matched, userid): """after the password has been checked""" if not matched: return fail(credError.UnauthorizedLogin(srvMessage(m18nE('Wrong password')))) return userid