File indexing completed on 2024-04-21 04:01:52

0001 # -*- coding: utf-8 -*-
0002 
0003 """Copyright (C) 2013-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0004 
0005 SPDX-License-Identifier: GPL-2.0
0006 
0007 Read the user manual for a description of the interface to this scoring engine
0008 """
0009 
0010 import itertools
0011 
0012 from tile import Tile
0013 from meld import Meld, MeldList
0014 
0015 
0016 class Permutations:
0017 
0018     """creates permutations for building melds out of single tiles.
0019     NEVER returns Kongs!"""
0020     cache = {}
0021     permuteCache = {}
0022 
0023     def __new__(cls, tiles):
0024         cacheKey = tuple(x.key for x in tiles)
0025         if cacheKey in cls.cache:
0026             return cls.cache[cacheKey]
0027         result = object.__new__(cls)
0028         cls.cache[cacheKey] = result
0029         return result
0030 
0031     def __init__(self, tiles):
0032         self.tiles = tiles
0033         if not hasattr(self, 'variants'):
0034             self.variants = self._variants()
0035 
0036     def _variants(self):
0037         """full Meld lists"""
0038         honors = []
0039         for tile in sorted(set(self.tiles)):
0040             if tile.isHonor:
0041                 count = self.tiles.count(tile)
0042                 if count == 4:
0043                     honors.append(tile.single)
0044                     count -= 1
0045                 honors.append(tile.meld(count))
0046         boni = [x.single for x in self.tiles if x.isBonus]
0047         variants = []
0048         for group in Tile.colors.upper():
0049             gTiles = [x for x in self.tiles if x.group == group]
0050             groupVariants = self.__colorVariants(group, [x.value for x in gTiles])
0051             if groupVariants:
0052                 variants.append(groupVariants)
0053         result = []
0054         for variant in (sum(x, []) for x in itertools.product(*variants)):
0055             if variant not in result:
0056                 result.append(variant)
0057         result = sorted(MeldList(honors + x + boni) for x in result)
0058         return result
0059 
0060     @classmethod
0061     def permute(cls, valuesTuple):
0062         """return all groupings into melds.
0063         values is a tuple of int, range 1..9"""
0064         assert isinstance(valuesTuple, tuple)
0065         if valuesTuple in cls.permuteCache:
0066             return cls.permuteCache[valuesTuple]
0067         values = list(valuesTuple)
0068         result = list()
0069         possibleMelds = []
0070         valueSet = set(values)
0071         for value in sorted(valueSet):
0072             if values.count(value) == 2:
0073                 possibleMelds.append(tuple([value] * 2))
0074             if values.count(value) >= 3:
0075                 possibleMelds.append(tuple([value] * 3))
0076             if values.count(value + 1) and values.count(value + 2):
0077                 possibleMelds.append(tuple([value, value + 1, value + 2]))
0078         if possibleMelds:
0079             for meld in possibleMelds:
0080                 appendValue = list([meld])
0081                 rest = values[:]
0082                 for tile in meld:
0083                     rest.remove(tile)
0084                 if rest:
0085                     permuteRest = cls.permute(tuple(rest))
0086                     for combi in permuteRest:
0087                         result.append(tuple(list(appendValue) + list(combi)))
0088                 else:
0089                     result.append(appendValue)
0090         else:
0091             result = list([list([tuple([x]) for x in values])])  # pylint: disable=consider-using-generator
0092             # the solution proposed by pylint creates something unusable
0093         tupleResult = tuple(sorted(set(tuple(tuple(sorted(x)) for x in result))))
0094         cls.permuteCache[valuesTuple] = tupleResult
0095         return tupleResult
0096 
0097     colorPermCache = {}
0098 
0099     @classmethod
0100     def usefulPermutations(cls, values):
0101         """return all variants usable for standard MJ formt (4 melds plus 1 pair),
0102         and also the variant with the most pungs. At least one will be returned.
0103         This is meant for the standard MJ format (4 pungs/kongs/chows plus 1 pair)"""
0104         values = tuple(values)
0105         if values not in cls.colorPermCache:
0106             variants = cls.permute(values)
0107             result = []
0108             maxPungs = -1
0109             maxPungVariant = minMeldVariant = None
0110             minMelds = 99
0111             for variant in variants:
0112                 if all(len(meld) > 1 for meld in variant):
0113                     # no singles: usable for MJ
0114                     result.append(variant)
0115                 if len(variant) < minMelds:
0116                     minMelds = len(variant)
0117                     minMeldVariant = variant
0118                 pungCount = sum(
0119                     len(meld) == 3 and len(set(meld)) == 1 for meld in variant)
0120                 if pungCount > maxPungs:
0121                     maxPungs = pungCount
0122                     maxPungVariant = variant
0123             if maxPungs > 0 and maxPungVariant not in result:
0124                 result.append(maxPungVariant)
0125             result.append(minMeldVariant)
0126             if not result:
0127                 # if nothing seems useful, return all possible permutations
0128                 result.extend(variants)
0129             cls.colorPermCache[values] = tuple(result)
0130         return cls.colorPermCache[values]
0131 
0132     @classmethod
0133     def __colorVariants(cls, color, values):
0134         """generates all possible meld variants out of original
0135         where values is a string like '113445'.
0136         Returns lists of Meld"""
0137         allValues = sorted(values)
0138         vSet = set(allValues)
0139         groups = []
0140         for border in sorted(x + 1 for x in sorted(vSet) if x + 1 not in vSet):
0141             content = [x for x in allValues if x < border]
0142             if content:
0143                 groups.append(content)
0144                 allValues = [x for x in allValues if x > border]
0145         combinations = [cls.usefulPermutations(x) for x in groups]
0146         result = []
0147         for variant in itertools.product(*combinations):
0148             melds = []
0149             for block in variant:
0150                 for meld in block:
0151                     melds.append(Meld(Tile(color, x) for x in meld))
0152             if melds:
0153                 result.append(melds)
0154         return result