def declaredMahJongg(self, concealed, withDiscard, lastTile, lastMeld): """player declared mah jongg. Determine last meld, show concealed tiles grouped to melds""" assert not isinstance(lastTile, Tile) lastMeld = Meld(lastMeld) # do not change the original! self.game.winner = self if withDiscard: self.lastTile = withDiscard self.lastMeld = lastMeld assert withDiscard == self.game.lastDiscard.element, 'withDiscard: %s lastDiscard: %s' % ( withDiscard, self.game.lastDiscard.element) self.addConcealedTiles(self.game.lastDiscard) melds = [Meld(x) for x in concealed.split()] if self.lastSource != 'k': # robbed the kong self.lastSource = 'd' # the last claimed meld is exposed assert lastMeld in melds, '%s: concealed=%s melds=%s lastMeld=%s lastTile=%s withDiscard=%s' % ( self.__concealedTileNames, concealed, meldsContent(melds), ''.join(lastMeld.pairs), lastTile, withDiscard) melds.remove(lastMeld) self.lastTile = self.lastTile.lower() lastMeld.pairs.toLower() self._exposedMelds.append(lastMeld) for tileName in lastMeld.pairs: self.visibleTiles[tileName] += 1 else: melds = [Meld(x) for x in concealed.split()] self.lastTile = lastTile self.lastMeld = lastMeld self._concealedMelds = melds self.__concealedTileNames = [] self.__hand = None self.syncHandBoard()
def receive(self, tile=None, meld=None): """receive a tile or meld and return the meld this tile becomes part of""" if tile: if tile.isBonus(): if tile.board == self: return meld = Meld(tile) else: meld = self.__chooseDestinationMeld(tile, meld) # from selector board. # if the source is a Handboard, we got a Meld, not a Tile if not meld: # user pressed ESCAPE return None assert not tile.element.istitle() or meld.pairs[0] != 'Xy', tile senderBoard = meld[0].board senderBoard.removing(meld=meld) if senderBoard == self: self.player.moveMeld(meld) self.sync() else: self.player.addMeld(meld) self.sync(adding=meld.tiles) senderBoard.remove(meld=meld) meld.tiles = sorted(meld.tiles, key=lambda x: x.xoffset) if any(x.focusable for x in meld.tiles): for idx, tile in enumerate(meld.tiles): tile.focusable = idx == 0 return meld
def newTilePositions(self): """returns list(TileAttr) for all tiles except bonus tiles. The tiles are not associated to any board.""" result = list() isScoringGame = self.player.game.isScoringGame() newUpperMelds = list(self.player.exposedMelds) if isScoringGame: newLowerMelds = list(self.player.concealedMelds) else: if self.player.concealedMelds: newLowerMelds = sorted(self.player.concealedMelds, key=meldKey) else: tileStr = 'R' + ''.join(self.player.concealedTileNames) handStr = ' '.join([tileStr, self.player.mjString()]) content = Hand.cached(self.player, handStr) newLowerMelds = list(Meld(x) for x in content.sortedMeldsContent.split()) if newLowerMelds: if self.rearrangeMelds: if newLowerMelds[0].pairs[0] == 'Xy': newLowerMelds = sorted(newLowerMelds, key=len, reverse=True) else: # generate one meld with all sorted tiles newLowerMelds = [Meld(sorted(sum((x.pairs for x in newLowerMelds), []), key=elementKey))] for yPos, melds in ((0, newUpperMelds), (self.lowerY, newLowerMelds)): meldDistance = self.concealedMeldDistance if yPos else self.exposedMeldDistance meldX = 0 for meld in melds: for idx in range(len(meld)): result.append(TileAttr(self, meld, idx, meldX, yPos)) meldX += 1 meldX += meldDistance return sorted(result, key=lambda x: x.yoffset * 100 + x.xoffset)
def computeLastMeld(self): """compile hand info into a string as needed by the scoring engine""" if self.scoringDialog: cbLastMeld = self.scoringDialog.cbLastMeld idx = cbLastMeld.currentIndex() if idx >= 0: return Meld(str(cbLastMeld.itemData(idx).toString())) return Meld()
def __parseString(self, inString): """parse the string passed to Hand()""" # pylint: disable=too-many-branches tileStrings = [] for part in inString.split(): partId = part[0] if partId == 'm': if len(part) > 1: try: self.__lastSource = TileSource.byChar[part[1]] except KeyError: raise Exception('{} has unknown lastTile {}'.format(inString, part[1])) if len(part) > 2: self.__announcements = set(part[2]) elif partId == 'L': if len(part[1:]) > 8: raise Exception( 'last tile cannot complete a kang:' + inString) if len(part) > 3: self.__lastMeld = Meld(part[3:]) self.__lastTile = Tile(part[1:3]) else: if part != 'R': tileStrings.append(part) self.bonusMelds, tileStrings = self.__separateBonusMelds(tileStrings) tileString = ' '.join(tileStrings) self.tiles = TileList(tileString.replace(' ', '').replace('R', '')) self.tiles.sort() for part in tileStrings[:]: if part[:1] != 'R': self.melds.append(Meld(part)) tileStrings.remove(part) self.values = tuple(x.value for x in self.tiles) self.suits = set(x.lowerGroup for x in self.tiles) self.declaredMelds = MeldList(x for x in self.melds if x.isDeclared) declaredTiles = list(sum((x for x in self.declaredMelds), [])) self.tilesInHand = TileList(x for x in self.tiles if x not in declaredTiles) self.lenOffset = (len(self.tiles) - 13 - sum(x.isKong for x in self.melds)) assert len(tileStrings) < 2, tileStrings self.__rest = TileList() if len(tileStrings): self.__rest.extend(TileList(tileStrings[0][1:])) last = self.__lastTile if last and not last.isBonus: assert last in self.tiles, \ 'lastTile %s is not in hand %s' % (last, str(self)) if self.__lastSource is TileSource.RobbedKong: assert self.tiles.count(last.exposed) + \ self.tiles.count(last.concealed) == 1, ( 'Robbing kong: I cannot have ' 'lastTile %s more than once in %s' % ( last, ' '.join(self.tiles)))
def computeLastMeld(self): """compile hand info into a string as needed by the scoring engine""" if self.scoringDialog: # is None while ScoringGame is created cbLastMeld = self.scoringDialog.cbLastMeld idx = cbLastMeld.currentIndex() if idx >= 0: return Meld(cbLastMeld.itemData(idx)) return Meld()
def rearrange(dummyHand, pairs): result = [] for tileName in pairs[:]: if pairs.count(tileName) >= 2: result.append(Meld([tileName, tileName])) pairs.remove(tileName) pairs.remove(tileName) elif pairs.count(tileName) == 1: result.append(Meld([tileName])) pairs.remove(tileName) return result, pairs
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.running: return lastDiscard = self.game.lastDiscard # if we rob a tile, self.game.lastDiscard has already been set to the # robbed tile hasTiles = Meld(meldTiles[:]) discardingPlayer = self.game.activePlayer hasTiles = hasTiles.without(lastDiscard) meld = Meld(meldTiles) if len(meld) != 4 and not (meld.isPair or meld.isPungKong or meld.isChow): msg = i18nE('%1 wrongly said %2 for meld %3') self.abort(msg, player.name, claim.name, str(meld)) return if not player.hasConcealedTiles(hasTiles): msg = i18nE( '%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.concealedTiles)) return # update our internal state before we listen to the clients again self.game.discardedTiles[lastDiscard.exposed] -= 1 self.game.activePlayer = player if lastDiscard: player.lastTile = lastDiscard.exposed player.lastSource = TileSource.LivingWallDiscard player.exposeMeld(hasTiles, lastDiscard) 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, meld=meld) if claim == Message.Kong: block.callback(self.pickKongReplacement) else: block.callback(self.moved)
def clientAction(self, client, move): """mirror the action locally""" prevMove = client.game.lastMoves(only=[Message.DeclaredKong]).next() prevKong = Meld(prevMove.source) prevMove.player.robTile(prevKong.pairs[0].capitalize()) move.player.lastSource = 'k' client.game.addCsvTag('robbedKong', forAllPlayers=True)
def splitRegex(self, rest): """split rest into melds as good as possible""" rest = ''.join(rest) melds = [] for rule in self.ruleset.splitRules: splits = rule.apply(rest) while len(splits) > 1: for split in splits[:-1]: melds.append(Meld(split)) rest = splits[-1] splits = rule.apply(rest) if len(splits) == 0: break if len(splits) == 1: assert Meld(splits[0]).isValid() # or the splitRules are wrong return melds
def toolTip(self, button, dummyTile): """decorate the action button which will send this message""" maySay = button.client.sayable[self] if not maySay: return '', False, '' myself = button.client.game.myself txt = [] warn = False if myself.originalCall and myself.mayWin: warn = True txt.append(m18n('saying %1 violates Original Call', self.i18nName)) dangerousMelds = button.client.maybeDangerous(self) if dangerousMelds: lastDiscardName = Meld.tileName(button.client.game.lastDiscard.element) warn = True if Debug.dangerousGame and len(dangerousMelds) != len(maySay): button.client.game.debug('only some claimable melds are dangerous: %s' % dangerousMelds) if len(dangerousMelds) == 1: txt.append(m18n( 'claiming %1 is dangerous because you will have to discard a dangerous tile', lastDiscardName)) else: for meld in dangerousMelds: txt.append(m18n( 'claiming %1 for %2 is dangerous because you will have to discard a dangerous tile', lastDiscardName, str(meld))) if not txt: txt = [m18n('You may say %1', self.i18nName)] return '<br><br>'.join(txt), warn, ''
def toolTip(self, button, dummyTile): """decorate the action button which will send this message""" maySay = button.client.sayable[self] if not maySay: return '', False, '' myself = button.client.game.myself txt = [] warn = False if myself.originalCall and myself.mayWin: warn = True txt.append(m18n('saying Kong for %1 violates Original Call', Meld.tileName(maySay[0]))) if not txt: txt = [m18n('You may say Kong for %1', Meld.tileName(maySay[0][0]))] return '<br><br>'.join(txt), warn, ''
def __setLastTile(self): """sets lastTile, lastSource, announcements""" self.__announcements = '' self.__lastTile = None self.__lastSource = None parts = self.mjStr.split() for part in parts: if part[0] == 'L': part = part[1:] if len(part) > 2: self.__lastMeld = Meld(part[2:]) self.__lastTile = part[:2] elif part[0] == 'M': if len(part) > 3: self.__lastSource = part[3] if len(part) > 4: self.__announcements = part[4:] if self.__lastTile: assert self.__lastTile in self.tileNames, 'lastTile %s is not in tiles %s, mjStr=%s' % ( self.__lastTile, ' '.join(self.tileNames), self.mjStr) if self.__lastSource == 'k': assert self.tileNames.count(self.__lastTile.lower()) + \ self.tileNames.count(self.__lastTile.capitalize()) == 1, \ 'Robbing kong: I cannot have lastTile %s more than once in %s' % ( self.__lastTile, ' '.join(self.tileNames))
def __init__(self, player, command, kwargs): if isinstance(command, Message): self.message = command else: self.message = Message.defined[command] self.table = None self.notifying = False self._player = weakref.ref(player) if player else None self.token = kwargs['token'] self.kwargs = kwargs.copy() del self.kwargs['token'] self.score = None self.lastMeld = None for key, value in kwargs.items(): assert not isinstance(value, bytes), 'value is bytes:{}'.format( repr(value)) if value is None: self.__setattr__(key, None) else: if key.lower().endswith('tile'): self.__setattr__(key, Tile(value)) elif key.lower().endswith('tiles'): self.__setattr__(key, TileList(value)) elif key.lower().endswith('meld'): self.__setattr__(key, Meld(value)) elif key.lower().endswith('melds'): self.__setattr__(key, MeldList(value)) elif key == 'playerNames': if Internal.isServer: self.__setattr__(key, value) else: self.__setattr__(key, self.__convertWinds(value)) else: self.__setattr__(key, value)
def computeLastMelds(cls, hand): """returns all possible last melds""" if not hand.lastTile: return [] couples, rest = cls.findCouples(hand) assert not rest, '%s: couples=%s rest=%s' % (hand.string, couples, rest) return [Meld(x) for x in couples if hand.lastTile in x]
def computeLastMelds(self, hand): """returns all possible last melds""" if not hand.lastTile: return triples, rest = self.findTriples(hand) assert len(rest) == 2 triples.append(rest) # just a list of tuples return [Meld(x) for x in triples if hand.lastTile in x]
def rearrange(self, hand, pairs): melds = [] for triple in self.findTriples(hand)[0]: melds.append(Meld(triple)) pairs.remove(triple[0]) pairs.remove(triple[1]) pairs.remove(triple[2]) while len(pairs) >= 2: for value in set(x[1] for x in pairs): suits = set(x[0] for x in pairs if x[1] == value) if len(suits) < 2: return melds, pairs pair = (suits.pop() + value, suits.pop() + value) melds.append(Meld(sorted(pair, key=elementKey))) pairs.remove(pair[0]) pairs.remove(pair[1]) return melds, pairs
def rearrange(dummyHand, pairs): melds = [] for pair in set(pairs) & elements.mAJORS: while pairs.count(pair) >= 2: melds.append(Meld(pair * 2)) pairs.remove(pair) pairs.remove(pair) return melds, pairs
def computeLastMelds(cls, hand): """returns all possible last melds""" if not hand.lastTile: return triples, rest = cls.findTriples(hand) assert len(rest) == 2 triples = list(triples) triples.append(rest) return [Meld(x) for x in triples if hand.lastTile in x]
def __separateBonusMelds(tileString): """keep them separate. One meld per bonus tile. Others depend on that.""" result = [] if 'f' in tileString or 'y' in tileString: for pair in Pairs(tileString.replace(' ', '').replace('R', '')): if pair[0] in 'fy': result.append(Meld(pair)) tileString = tileString.replace(pair, '', 1) return result, tileString
def toolTip(self, button, tile): """decorate the action button which will send this message""" game = button.client.game myself = game.myself txt = [] warn = False if myself.violatesOriginalCall(tile): txt.append(m18n('discarding %1 violates Original Call', Meld.tileName(tile.element))) warn = True if game.dangerousFor(myself, tile): txt.append(m18n('discarding %1 is Dangerous Game', Meld.tileName(tile.element))) warn = True if not txt: txt = [m18n('discard the least useful tile')] txt = '<br><br>'.join(txt) return txt, warn, txt
def rearrange(self, hand, pairs): melds = [] for couple in self.findCouples(hand)[0]: if couple[0].islower(): # this is the mj pair, lower after claiming continue melds.append(Meld(couple)) pairs.remove(couple[0]) pairs.remove(couple[1]) return melds, pairs
def __computeDragonWindMelds(tileString): """returns lists with melds containing all (even single) dragons respective winds""" dragonMelds = [] windMelds = [] for split in tileString.split(): if split[0] == 'R': pairs = Pairs(split[1:]) for lst, tiles in ((windMelds, elements.wINDS), (dragonMelds, elements.dRAGONS)): for tile in tiles: count = pairs.count(tile) if count: lst.append(Meld([tile] * count)) elif split[0] in 'dD': dragonMelds.append(Meld(split)) elif split[0] in 'wW': windMelds.append(Meld(split)) return dragonMelds, windMelds
def __computeLenOffset(self, tileString): """lenOffset is <0 for short hand, 0 for correct calling hand, >0 for long hand. Of course ignoring bonus tiles. if there are no kongs, 13 tiles will return 0""" result = len(self.tileNames) - 13 for split in tileString.split(): if split[0] != 'R': if Meld(split).isKong(): result -= 1 return result
def rearrange(cls, hand, rest): melds = [] for couple in cls.findCouples(hand, rest)[0]: if couple[0].islower(): # this is the mj pair, lower after claiming continue melds.append(Meld(couple)) rest.remove(couple[0]) rest.remove(couple[1]) yield tuple(melds), tuple(rest)
def exposeMeld(self, meldTiles, calledTile=None): """exposes a meld with meldTiles: removes them from concealedTileNames, adds the meld to exposedMelds and returns it calledTile: we got the last tile for the meld from discarded, otherwise from the wall""" game = self.game game.activePlayer = self allMeldTiles = meldTiles[:] if calledTile: allMeldTiles.append(calledTile.element if isinstance(calledTile, Tile) else calledTile) if len(allMeldTiles) == 4 and allMeldTiles[0].islower(): tile0 = allMeldTiles[0].lower() # we are adding a 4th tile to an exposed pung self._exposedMelds = [meld for meld in self._exposedMelds if meld.pairs != [tile0] * 3] meld = Meld(tile0 * 4) self.__concealedTileNames.remove(allMeldTiles[3]) self.visibleTiles[tile0] += 1 else: allMeldTiles = sorted(allMeldTiles) # needed for Chow meld = Meld(allMeldTiles) for meldTile in meldTiles: self.__concealedTileNames.remove(meldTile) for meldTile in allMeldTiles: self.visibleTiles[meldTile.lower()] += 1 meld.expose(bool(calledTile)) self._exposedMelds.append(meld) self.__hand = None game.computeDangerous(self) adding = [calledTile] if calledTile else None self.syncHandBoard(adding=adding) return meld
def speak(what): """this is what the user of this module will call.""" if not Sound.enabled: return game = Internal.field.game reactor = Internal.reactor if game and not game.autoPlay and Sound.playProcesses: # in normal play, wait a moment between two speaks. Otherwise # sometimes too many simultaneous speaks make them ununderstandable lastSpeakStart = max(x.startTime for x in Sound.playProcesses) if datetime.datetime.now() - lastSpeakStart < datetime.timedelta(seconds=0.3): reactor.callLater(1, Sound.speak, what) return if os.path.exists(what): if Sound.findOgg(): if os.name == 'nt': name, ext = os.path.splitext(what) assert ext == '.ogg' wavName = name + '.wav' if not os.path.exists(wavName): args = [r'c:\vorbis\oggdec', '--quiet', what] process = subprocess.Popen(args) os.waitpid(process.pid, 0) winsound.PlaySound(wavName, winsound.SND_FILENAME) else: args = ['ogg123', '-q', what] if Debug.sound: game.debug(' '.join(args)) process = subprocess.Popen(args) process.startTime = datetime.datetime.now() process.name = what Sound.playProcesses.append(process) reactor.callLater(3, Sound.cleanProcesses) reactor.callLater(6, Sound.cleanProcesses) elif False: text = os.path.basename(what) text = os.path.splitext(text)[0] # If this ever works, we need to translate all texts # we need package jovie and mbrola voices # KSpeech setLanguage de # KSpeech.showManagerDialog lets me define voices but # how do I use them? it is always the same voice, # setDefaultTalker "namefrommanager" does not change anything # although defaultTalker returns what we just set even if no talker # with that name exists # getTalkerCodes returns nothing # this all feels immature if len(text) == 2 and text[0] in 'sdbcw': text = Meld.tileName(text) args = ['qdbus', 'org.kde.jovie', '/KSpeech', 'say', text, '1'] subprocess.Popen(args)
def speak(what): """this is what the user of this module will call.""" if not Sound.enabled: return game = Internal.field.game reactor = Internal.reactor if game and not game.autoPlay and Sound.playProcesses: # in normal play, wait a moment between two speaks. Otherwise # sometimes too many simultaneous speaks make them ununderstandable lastSpeakStart = max(x.startTime for x in Sound.playProcesses) if datetime.datetime.now() - lastSpeakStart < datetime.timedelta( seconds=0.3): reactor.callLater(1, Sound.speak, what) return if os.path.exists(what): if Sound.findOgg(): if os.name == 'nt': name, ext = os.path.splitext(what) assert ext == '.ogg' wavName = name + '.wav' if not os.path.exists(wavName): args = [r'c:\vorbis\oggdec', '--quiet', what] process = subprocess.Popen(args) os.waitpid(process.pid, 0) winsound.PlaySound(wavName, winsound.SND_FILENAME) else: args = ['ogg123', '-q', what] if Debug.sound: game.debug(' '.join(args)) process = subprocess.Popen(args) process.startTime = datetime.datetime.now() process.name = what Sound.playProcesses.append(process) reactor.callLater(3, Sound.cleanProcesses) reactor.callLater(6, Sound.cleanProcesses) elif False: text = os.path.basename(what) text = os.path.splitext(text)[0] # If this ever works, we need to translate all texts # we need package jovie and mbrola voices # KSpeech setLanguage de # KSpeech.showManagerDialog lets me define voices but # how do I use them? it is always the same voice, # setDefaultTalker "namefrommanager" does not change anything # although defaultTalker returns what we just set even if no talker # with that name exists # getTalkerCodes returns nothing # this all feels immature if len(text) == 2 and text[0] in 'sdbcw': text = Meld.tileName(text) args = ['qdbus', 'org.kde.jovie', '/KSpeech', 'say', text, '1'] subprocess.Popen(args)
def getMonth(month): folder = os.path.expanduser( '~/Documents/MJLogPython/log/{}/'.format(month)) if not os.path.exists(folder): os.mkdir(folder) if month > '200801': ykmlist = os.path.expanduser( '~/Documents/MJLogPython/ykmjs/New/{}.txt'.format(month)) else: #ykmlist = os.path.expanduser('~/Documents/MJLogPython/ykmjs/New/{}.txt'.format(month)) raise ValueError("{} yakuman list is in an old format".format(month)) if not os.path.isfile(ykmlist): raise ValueError("{} yakuman list was not prepared.".format(month)) CharHaiDisp = [ "<1m>", "<2m>", "<3m>", "<4m>", "<5m>", "<6m>", "<7m>", "<8m>", "<9m>", "<1p>", "<2p>", "<3p>", "<4p>", "<5p>", "<6p>", "<7p>", "<8p>", "<9p>", "<1s>", "<2s>", "<3s>", "<4s>", "<5s>", "<6s>", "<7s>", "<8s>", "<9s>", "<東>", "<南>", "<西>", "<北>", "<白>", "<發>", "<中>" ] #cnt, cnt1 = 0, 0 with open(ykmlist, 'rb') as file_: next(file_) next(file_) # ' time ',' name ','[table,[ hand ],[chiiponkon],last]' ,[yakuman(s)],'link info' ykmline = re.finditer( r'\'.{11}\',\'[^\']+\',\'\[\d+,\[([\d,]+)\],\[([\d,]*)\],\d+\]\',\[([\d,]*)\],\'([^\']+)\'', next(file_).decode('utf-8')) for ykm in ykmline: #cnt += 1 hand, furu, ykmstr, info = ykm.groups() # print(ykm.groups()) try: ykmtypes = {int(_) for _ in ykmstr.split(',')} except: # 数え役満 s = ''.join(CharHaiDisp[int(tile) >> 2] for tile in hand.split(',')) if len(furu) > 0: s += ', ' + ', '.join( Meld(_).getInfo() for _ in furu.split(',')) print(info) print(s) #if (37 in ykmtypes) or (38 in ykmtypes): # 天和,地和 # #cnt1 += 1 # a = PaifuParseTool(info) # a.printHaiPai() #print(cnt, cnt1) return None
def toolTip(self, button, tile): """decorate the action button which will send this message""" myself = button.client.game.myself isCalling = bool((myself.hand - tile.element).callingHands()) if not isCalling: txt = m18n('discarding %1 and declaring Original Call makes this hand unwinnable', Meld.tileName(tile.element)) return txt, True, txt else: return (m18n( 'Discard a tile, declaring Original Call meaning you need only one ' 'tile to complete the hand and will not alter the hand in any way (except bonus tiles)'), False, '')
def claimMahJongg(self, msg): """a player claims mah jongg. Check this and if correct, tell all. Otherwise abort game, kajongg client is faulty""" if not self.running: return player = msg.player concealedMelds = MeldList(msg.args[0]) withDiscard = Tile(msg.args[1]) if msg.args[1] else None lastMeld = Meld(msg.args[2]) if self.game.ruleset.mustDeclareCallingHand: assert player.isCalling, '%s %s %s says MJ but never claimed: concmelds:%s withdiscard:%s lastmeld:%s' % ( self.game.handId, player.hand, player, concealedMelds, withDiscard, lastMeld) discardingPlayer = self.game.activePlayer lastMove = next(self.game.lastMoves(withoutNotifications=True)) robbedTheKong = lastMove.message == Message.DeclaredKong if robbedTheKong: player.robsTile() withDiscard = lastMove.meld[0].concealed lastMove.player.robTileFrom(withDiscard) msgArgs = player.showConcealedMelds(concealedMelds, withDiscard) if msgArgs: self.abort(*msgArgs) return player.declaredMahJongg(concealedMelds, withDiscard, player.lastTile, lastMeld) if not player.hand.won: msg = i18nE('%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 is TileSource.LivingWallDiscard 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, melds=concealedMelds, lastTile=player.lastTile, lastMeld=lastMeld, withDiscardTile=withDiscard) block.callback(self.endHand)
def __separateMelds(self, tileString): """build a meld list from the hand string""" # no matter how the tiles are grouped make a single # meld for every bonus tile # we need to remove spaces from the hand string first # for building only pairs with length 2 splits = tileString.split() rest = '' for split in splits: if split[0] == 'R': rest = split[1:] else: meld = Meld(split) self.melds.append(meld) self.declaredMelds.append(meld) if rest: rest = sorted([rest[x:x+2] for x in range(0, len(rest), 2)]) self.__split(rest) self.melds = sorted(self.melds, key=meldKey) for meld in self.melds: if not meld.isValid(): raise Exception('%s has an invalid meld: %s' % (self.string, meld.joined)) self.__categorizeMelds()