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