Пример #1
0
 def __sub__(self, tiles):
     """returns a copy of self minus tiles. Case of tiles (hidden
     or exposed) is ignored. If the tile is not hidden
     but found in an exposed meld, this meld will be hidden with
     the tile removed from it. Exposed melds of length<3 will also
     be hidden."""
     # pylint: disable=R0912
     # pylint says too many branches
     if not isinstance(tiles, list):
         tiles = list([tiles])
     hidden = 'R' + ''.join(self.tileNamesInHand)
     # exposed is a deep copy of declaredMelds. If lastMeld is given, it
     # must be first in the list.
     exposed = (Meld(x) for x in self.declaredMelds)
     if self.lastMeld:
         exposed = sorted(exposed, key=lambda x: (x.pairs != self.lastMeld.pairs, meldKey(x)))
     else:
         exposed = sorted(exposed, key=meldKey)
     bonus = sorted(Meld(x) for x in self.bonusMelds)
     for tile in tiles:
         assert isinstance(tile, str) and len(tile) == 2, 'Hand.__sub__:%s' % tiles
         if tile.capitalize() in hidden:
             hidden = hidden.replace(tile.capitalize(), '', 1)
         elif tile[0] in 'fy': # bonus tile
             for idx, meld in enumerate(bonus):
                 if tile == meld.pairs[0]:
                     del bonus[idx]
                     break
         else:
             for idx, meld in enumerate(exposed):
                 if tile.lower() in meld.pairs:
                     del meld.pairs[meld.pairs.index(tile.lower())]
                     del exposed[idx]
                     meld.conceal()
                     hidden += meld.joined
                     break
     for idx, meld in enumerate(exposed):
         if len(meld.pairs) < 3:
             del exposed[idx]
             meld.conceal()
             hidden += meld.joined
     mjStr = self.mjStr
     if self.lastTile in tiles:
         parts = mjStr.split()
         newParts = []
         for idx, part in enumerate(parts):
             if part[0] == 'M':
                 part = 'm' + part[1:]
                 if len(part) > 3 and part[3] == 'k':
                     part = part[:3]
             elif part[0] == 'L':
                 continue
             newParts.append(part)
         mjStr = ' '.join(newParts)
     newString = ' '.join([hidden, meldsContent(exposed), meldsContent(bonus), mjStr])
     return Hand.cached(self, newString, self.computedRules)
Пример #2
0
 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()
Пример #3
0
 def __maySayMahjongg(self, move):
     """returns answer arguments for the server if calling or declaring Mah Jongg is possible"""
     game = self.game
     myself = game.myself
     robbableTile = withDiscard = None
     if move.message == Message.DeclaredKong:
         withDiscard = move.source[0].capitalize()
         if move.player != myself:
             robbableTile = move.exposedMeld.pairs[
                 1]  # we want it capitalized for a hidden Kong
     elif move.message == Message.AskForClaims:
         withDiscard = game.lastDiscard.element
     hand = myself.computeHand(withTile=withDiscard,
                               robbedTile=robbableTile,
                               asWinner=True)
     if hand.won:
         if Debug.robbingKong:
             if move.message == Message.DeclaredKong:
                 game.debug('%s may rob the kong from %s/%s' % \
                    (myself, move.player, move.exposedMeld.joined))
         if Debug.mahJongg:
             game.debug(
                 '%s may say MJ:%s, active=%s' %
                 (myself, list(x for x in game.players), game.activePlayer))
         return (meldsContent(hand.hiddenMelds), withDiscard,
                 list(hand.lastMeld.pairs))
Пример #4
0
 def __split(self, rest):
     """work hard to always return the variant with the highest Mah Jongg value.
     Adds melds to self.melds.
     only one special mjRule may try to rearrange melds.
     A rest will be rearranged by standard rules."""
     if 'Xy' in rest:
         # hidden tiles of other players:
         self.melds.extend(self.splitRegex(rest))
         return
     arrangements = []
     for mjRule in self.ruleset.mjRules:
         func = mjRule.function
         if func.__class__.__name__ == 'StandardMahJongg':
             stdMJ = func
     if self.mjRule:
         rules = [self.mjRule]
     else:
         rules = self.ruleset.mjRules
     for mjRule in rules:
         func = mjRule.function
         if func != stdMJ and hasattr(func, 'rearrange'):
             if ((self.lenOffset == 1 and func.appliesToHand(self))
                     or (self.lenOffset < 1 and func.shouldTry(self))):
                 melds, pairs = func.rearrange(self, rest[:])
                 if melds:
                     arrangements.append((mjRule, melds, pairs))
     if arrangements:
         # TODO: we should know for each arrangement how many tiles for MJ are still needed.
         # If len(pairs) == 4, one or up to three might be needed. That would allow for better AI.
         # TODO: if hand just completed and we did not win, only try stdmj
         arrangement = sorted(arrangements, key=lambda x: len(x[2]))[0]
         self.melds.extend(arrangement[1])
         self.melds.extend([Meld(x) for x in arrangement[2]])
         assert len(''.join(x.joined for x in self.melds)) == len(
             self.tileNames) * 2, '%s != %s' % (meldsContent(
                 self.melds), self.tileNames)
     else:
         # stdMJ is special because it might build more than one pair
         # the other special hands would put that into the rest
         # if the above TODO is done, stdMJ does not have to be special anymore
         melds, _ = stdMJ.rearrange(self, rest[:])
         self.melds.extend(melds)
         assert len(''.join(x.joined for x in self.melds)) == len(
             self.tileNames) * 2, '%s != %s' % (meldsContent(
                 self.melds), self.tileNames)
Пример #5
0
    def __split(self, rest):
        """work hard to always return the variant with the highest Mah Jongg value.
        Adds melds to self.melds.
        only one special mjRule may try to rearrange melds.
        A rest will be rearranged by standard rules."""
        if 'Xy' in rest:
            # hidden tiles of other players:
            self.melds.extend(self.splitRegex(rest))
            return
        arrangements = []
        for mjRule in self.ruleset.mjRules:
            func = mjRule.function
            if func.__class__.__name__ == 'StandardMahJongg':
                stdMJ = func
        if self.mjRule:
            rules = [self.mjRule]
        else:
            rules = self.ruleset.mjRules
        for mjRule in rules:
            func = mjRule.function
            if func != stdMJ and hasattr(func, 'rearrange'):
                if ((self.lenOffset == 1 and func.appliesToHand(self))
                        or (self.lenOffset < 1 and func.shouldTry(self))):
                    melds, pairs = func.rearrange(self, rest[:])
                    if melds:
                        arrangements.append((mjRule, melds, pairs))
        if arrangements:
# TODO: we should know for each arrangement how many tiles for MJ are still needed.
# If len(pairs) == 4, one or up to three might be needed. That would allow for better AI.
# TODO: if hand just completed and we did not win, only try stdmj
            arrangement = sorted(arrangements, key=lambda x: len(x[2]))[0]
            self.melds.extend(arrangement[1])
            self.melds.extend([Meld(x) for x in arrangement[2]])
            assert len(''.join(x.joined for x in self.melds)) == len(self.tileNames) * 2, '%s != %s' % (
                meldsContent(self.melds), self.tileNames)
        else:
            # stdMJ is special because it might build more than one pair
            # the other special hands would put that into the rest
            # if the above TODO is done, stdMJ does not have to be special anymore
            melds, _ = stdMJ.rearrange(self, rest[:])
            self.melds.extend(melds)
            assert len(''.join(x.joined for x in self.melds)) == len(self.tileNames) * 2, '%s != %s' % (
                meldsContent(self.melds), self.tileNames)
Пример #6
0
    def __init__(self, ruleset, string, computedRules=None, robbedTile=None):
        """evaluate string using ruleset. rules are to be applied in any case.
        ruleset can be Hand, Game or Ruleset."""
        # silence pylint. This method is time critical, so do not split it into smaller methods
        # pylint: disable=R0902,R0914,R0912,R0915
        if isinstance(ruleset, Hand):
            self.ruleset = ruleset.ruleset
            self.player = ruleset.player
            self.computedRules = ruleset.computedRules
        elif isinstance(ruleset, Ruleset):
            self.ruleset = ruleset
            self.player = None
        else:
            self.player = ruleset
            self.ruleset = self.player.game.ruleset
        self.string = string
        self.robbedTile = robbedTile
        if computedRules is not None and not isinstance(computedRules, list):
            computedRules = list([computedRules])
        self.computedRules = computedRules or []
        self.__won = False
        self.mjStr = ''
        self.mjRule = None
        self.ownWind = None
        self.roundWind = None
        tileStrings = []
        mjStrings = []
        haveM = False
        splits = self.string.split()
        for part in splits:
            partId = part[0]
            if partId in 'Mmx':
                haveM = True
                self.ownWind = part[1]
                self.roundWind = part[2]
                mjStrings.append(part)
                self.__won = partId == 'M'
            elif partId == 'L':
                if len(part[1:]) > 8:
                    raise Exception('last tile cannot complete a kang:' + self.string)
                mjStrings.append(part)
            else:
                tileStrings.append(part)

        if not haveM:
            raise Exception('Hand got string without mMx: %s', self.string)
        self.mjStr = ' '.join(mjStrings)
        self.__lastTile = self.__lastSource = self.__announcements = ''
        self.__lastMeld = 0
        self.__lastMelds = []
        self.hiddenMelds = []
        self.declaredMelds = []
        self.melds = []
        tileString = ' '.join(tileStrings)
        self.bonusMelds, tileString = self.__separateBonusMelds(tileString)
        self.tileNames = Pairs(tileString.replace(' ','').replace('R', ''))
        self.tileNames.sort()
        self.values = ''.join(x[1] for x in self.tileNames)
        self.suits = set(x[0].lower() for x in self.tileNames)
        self.lenOffset = self.__computeLenOffset(tileString)
        self.dragonMelds, self.windMelds = self.__computeDragonWindMelds(tileString)
        self.__separateMelds(tileString)
        self.hiddenMelds = sorted(self.hiddenMelds, key=meldKey)
        self.tileNamesInHand = sum((x.pairs for x in self.hiddenMelds), [])
        self.sortedMeldsContent = meldsContent(self.melds)
        if self.bonusMelds:
            self.sortedMeldsContent += ' ' + meldsContent(self.bonusMelds)

        self.usedRules = []
        self.score = None
        oldWon = self.won
        self.__applyRules()
        if len(self.lastMelds) > 1:
            self.__applyBestLastMeld()
        if self.won != oldWon:
            # if not won after all, this might be a long hand.
            # So we might even have to unapply meld rules and
            # bonus points. Instead just recompute all again.
            # This should only happen with scoring manual games
            # and with scoringtest - normally kajongg would not
            # let you declare an invalid mah jongg
            self.__applyRules()
Пример #7
0
    def __init__(self, ruleset, string, computedRules=None, robbedTile=None):
        """evaluate string using ruleset. rules are to be applied in any case.
        ruleset can be Hand, Game or Ruleset."""
        # silence pylint. This method is time critical, so do not split it into smaller methods
        # pylint: disable=R0902,R0914,R0912,R0915
        if isinstance(ruleset, Hand):
            self.ruleset = ruleset.ruleset
            self.player = ruleset.player
            self.computedRules = ruleset.computedRules
        elif isinstance(ruleset, Ruleset):
            self.ruleset = ruleset
            self.player = None
        else:
            self.player = ruleset
            self.ruleset = self.player.game.ruleset
        self.string = string
        self.robbedTile = robbedTile
        if computedRules is not None and not isinstance(computedRules, list):
            computedRules = list([computedRules])
        self.computedRules = computedRules or []
        self.__won = False
        self.mjStr = ''
        self.mjRule = None
        self.ownWind = None
        self.roundWind = None
        tileStrings = []
        mjStrings = []
        haveM = False
        splits = self.string.split()
        for part in splits:
            partId = part[0]
            if partId in 'Mmx':
                haveM = True
                self.ownWind = part[1]
                self.roundWind = part[2]
                mjStrings.append(part)
                self.__won = partId == 'M'
            elif partId == 'L':
                if len(part[1:]) > 8:
                    raise Exception('last tile cannot complete a kang:' +
                                    self.string)
                mjStrings.append(part)
            else:
                tileStrings.append(part)

        if not haveM:
            raise Exception('Hand got string without mMx: %s', self.string)
        self.mjStr = ' '.join(mjStrings)
        self.__lastTile = self.__lastSource = self.__announcements = ''
        self.__lastMeld = 0
        self.__lastMelds = []
        self.hiddenMelds = []
        self.declaredMelds = []
        self.melds = []
        tileString = ' '.join(tileStrings)
        self.bonusMelds, tileString = self.__separateBonusMelds(tileString)
        self.tileNames = Pairs(tileString.replace(' ', '').replace('R', ''))
        self.tileNames.sort()
        self.values = ''.join(x[1] for x in self.tileNames)
        self.suits = set(x[0].lower() for x in self.tileNames)
        self.lenOffset = self.__computeLenOffset(tileString)
        self.dragonMelds, self.windMelds = self.__computeDragonWindMelds(
            tileString)
        self.__separateMelds(tileString)
        self.hiddenMelds = sorted(self.hiddenMelds, key=meldKey)
        self.tileNamesInHand = sum((x.pairs for x in self.hiddenMelds), [])
        self.sortedMeldsContent = meldsContent(self.melds)
        if self.bonusMelds:
            self.sortedMeldsContent += ' ' + meldsContent(self.bonusMelds)

        self.usedRules = []
        self.score = None
        oldWon = self.won
        self.__applyRules()
        if len(self.lastMelds) > 1:
            self.__applyBestLastMeld()
        if self.won != oldWon:
            # if not won after all, this might be a long hand.
            # So we might even have to unapply meld rules and
            # bonus points. Instead just recompute all again.
            # This should only happen with scoring manual games
            # and with scoringtest - normally kajongg would not
            # let you declare an invalid mah jongg
            self.__applyRules()
Пример #8
0
 def __sub__(self, tiles):
     """returns a copy of self minus tiles. Case of tiles (hidden
     or exposed) is ignored. If the tile is not hidden
     but found in an exposed meld, this meld will be hidden with
     the tile removed from it. Exposed melds of length<3 will also
     be hidden."""
     # pylint: disable=R0912
     # pylint says too many branches
     if not isinstance(tiles, list):
         tiles = list([tiles])
     hidden = 'R' + ''.join(self.tileNamesInHand)
     # exposed is a deep copy of declaredMelds. If lastMeld is given, it
     # must be first in the list.
     exposed = (Meld(x) for x in self.declaredMelds)
     if self.lastMeld:
         exposed = sorted(exposed,
                          key=lambda x:
                          (x.pairs != self.lastMeld.pairs, meldKey(x)))
     else:
         exposed = sorted(exposed, key=meldKey)
     bonus = sorted(Meld(x) for x in self.bonusMelds)
     for tile in tiles:
         assert isinstance(
             tile, str) and len(tile) == 2, 'Hand.__sub__:%s' % tiles
         if tile.capitalize() in hidden:
             hidden = hidden.replace(tile.capitalize(), '', 1)
         elif tile[0] in 'fy':  # bonus tile
             for idx, meld in enumerate(bonus):
                 if tile == meld.pairs[0]:
                     del bonus[idx]
                     break
         else:
             for idx, meld in enumerate(exposed):
                 if tile.lower() in meld.pairs:
                     del meld.pairs[meld.pairs.index(tile.lower())]
                     del exposed[idx]
                     meld.conceal()
                     hidden += meld.joined
                     break
     for idx, meld in enumerate(exposed):
         if len(meld.pairs) < 3:
             del exposed[idx]
             meld.conceal()
             hidden += meld.joined
     mjStr = self.mjStr
     if self.lastTile in tiles:
         parts = mjStr.split()
         newParts = []
         for idx, part in enumerate(parts):
             if part[0] == 'M':
                 part = 'm' + part[1:]
                 if len(part) > 3 and part[3] == 'k':
                     part = part[:3]
             elif part[0] == 'L':
                 continue
             newParts.append(part)
         mjStr = ' '.join(newParts)
     newString = ' '.join(
         [hidden, meldsContent(exposed),
          meldsContent(bonus), mjStr])
     return Hand.cached(self, newString, self.computedRules)