def ckan_tiles(self, draw): hand = Tiles() hand.tiles = self.hand.tiles[:] hand.add_tiles(draw) search = [] quad = [tile for tile in hand.tiles if hand.tiles.count(tile) == 4] while len(quad) > 0: first_quad = [tile for tile in quad if tile == quad[0]] search.append(first_quad) for tile in first_quad: quad.remove(tile) pon = [ meld for meld in self.melds.melds if meld.tiles.count(meld.tiles[0]) == 3 ] if pon: for meld in pon: if meld.tiles[0] in hand.tiles: search.append([ meld, [tile for tile in hand.tiles if tile == meld.tiles[0]][0] ]) if search: return search return False
async def riichi(self, draw, game): #this *should* check the hand to make sure its closed opened = [meld for meld in self.melds.melds if meld.opened] if opened: return False #determine what tiles are discardable to be in tenpai shanten = shanten_calculator(str(self.hand) + str(draw)) if (shanten == 0 or shanten == -1) and self.points >= 1000 and not self.in_riichi: riichi_tiles = [] temp_hand = Tiles() temp_hand.tiles = self.hand.tiles[:] temp_hand.add_tiles(draw) for tile in temp_hand.tiles: temp = Tiles() temp.tiles = temp_hand.tiles[:] temp.remove_tiles(tile) if winning_tiles(str(temp)) and tile not in riichi_tiles: riichi_tiles.append(tile) if riichi_tiles: try: reach = await self.user_input('Would you like to riichi?') except asyncio.exceptions.TimeoutError: reach = 'n' if reach != 'y' and reach != 'yes': return False else: return False discard = Tile('8', 'z') while discard not in self.hand.tiles and discard != draw: try: query = "Which tile would you like to riichi on? Type cancel to cancel riichi.\n" + ' '.join( map(str, riichi_tiles)) discard = await self.user_input(query) discard = Tile(discard[0], discard[1]) except asyncio.exceptions.TimeoutError: return False except ValueError: discard = Tile('8', 'z') except IndexError: discard = Tile('8', 'z') #self.discard_tile(choice) if game.tenhou and game.wall.remaining > 65: self.double_riichi = True self.in_riichi = True self.ippatsu = True self.points -= 1000 game.riichi += 1 return discard else: return False '''
def winning_tiles(tiles): if isinstance(tiles, str): tiles = tiles.replace(' ', '') test_tiles = OneOfEach() return [ tile for tile in test_tiles.tiles if shanten_calculator(tiles + str(tile)) == -1 ] elif issubclass(type(tiles), Tiles): return winning_tiles(str(tiles)) elif isinstance(tiles, list): temp = Tiles() temp.add_tiles(tiles) return winning_tiles(str(temp))
def ukeire(tiles): if isinstance(tiles, str): tiles = tiles.replace(' ', '') test_tiles = OneOfEach() shanten = [ shanten_calculator(str(tiles) + str(tile)) for tile in test_tiles.tiles ] return [ tile for index, tile in enumerate(test_tiles.tiles) if shanten[index] == min(shanten) ] elif issubclass(type(tiles), Tiles): return ukeire(str(tiles)) elif isinstance(tiles, list): temp = Tiles() temp.add_tiles(tiles) return ukeire(str(temp))
def efficient_discard(tiles, pool=None): base_shanten = shanten_calculator(tiles) improvements = {} if pool is None: pool = Wall() pool.remove_tiles(tiles) for tile in tiles.tiles: count = 0 temp = Tiles() temp.tiles = tiles.tiles[:] temp.remove_tiles(tile) for sample in pool.tiles: temp.add_tiles(sample) shanten = shanten_calculator(temp) if shanten < base_shanten: count += 1 temp.remove_tiles(sample) if count > 0: improvements[str(tile)] = count return improvements
def efficiency_discard(tiles, pool=None): if isinstance(tiles, str): tiles = tiles.replace(' ', '') temp = Tiles() temp.add_tiles(tiles) return winning_tiles(temp) elif issubclass(type(tiles), Tiles): if pool is None: discards = efficient_discard(tiles) else: discards = efficient_discard(tiles, pool) most_efficient = [ tile for tile in discards.keys() if discards[tile] == max(discards.values()) ] #return the entire list, and a choice can be made later return most_efficient elif isinstance(tiles, list): temp = Tiles() temp.add_tiles(tiles) return winning_tiles(str(temp))
def chii_tiles(self, discard): if discard.suit == 'z': return False else: combinations = [] tiles = [ tile for tile in self.hand.tiles if tile.suit == discard.suit ] prev1 = [ tile for tile in tiles if tile.true_value == discard.true_value - 1 ] next1 = [ tile for tile in tiles if tile.true_value == discard.true_value + 1 ] if prev1 and prev1[0].true_value != 5: prev1 = [prev1[0]] if prev1: prev2 = [ tile for tile in tiles if tile.true_value == discard.true_value - 2 ] if prev2: if prev2[0].true_value != 5: prev2 = [prev2[0]] for tile1 in prev1: for tile2 in prev2: chii = Tiles() chii.add_tiles([tile2, tile1, discard]) combinations.append(chii) if prev1 and next1: for tile1 in prev1: for tile2 in next1: chii = Tiles() chii.add_tiles([tile1, discard, tile2]) combinations.append(chii) if next1 and next1[0].true_value != 5: next1 = [next1[0]] if next1: next2 = [ tile for tile in tiles if tile.true_value == discard.true_value + 2 ] if next2: if next2[0].true_value != 5: next2 = [next2[0]] for tile1 in next1: for tile2 in next2: chii = Tiles() chii.add_tiles([discard, tile1, tile2]) combinations.append(chii) return combinations
def visible_tiles(self, game): visible = Tiles() for player in game.players: for meld in player.melds.melds: visible.add_tiles(meld) visible.add_tiles(player.discards) visible.add_tiles(self.hand) visible.add_tiles(game.dora) return visible
def player_image(player, hidden, tsumogiri, *args): # if not isinstance(player, Player): # return False #if there are melds, do some specific stuff to display the meld accordingly #this includes rotating the tile and placing the rotated tile based on who #the tile was called from if player.melds.melds: image_list = [] #if the hand is another players', hide the hand by displaying a tileback image #distinguish a tsumogiri discard from a tedashi discard by creating a #randomly placed break in the undisplayed hand #to do this, split up the hand into two chunks, each of which is a series of #tileback images if hidden: if tsumogiri: parts = ['x' * len(player.hand) + 'z'] else: location = random.randint(1,len(player.hand)) parts = ['x' * location + 'z'] parts.append('x' * (len(player.hand) - location) + 'z') else: parts = [str(player.hand)] #this creates an array that corresponds with the individual tiles #and determines whether or not to display the tile rotated #0 indicates upright, while 1 indicates rotated #2 is used to indicate an added kan, which is rotated but placed above #a previously rotated tile #if there is a 2 without a corresponding 1 in this array, it'll probably #bug out and have the tiles clip into one another rotated = [] #the tiles in hand are obviously not rotated rotated.append([0 for tile in player.hand.tiles]) #and neither are the arguments (which are basically just the discard, #but this is flexible enough to allow for other displays) for arg in args: if isinstance(arg, Tiles): rotated.append([0 for tile in arg.tiles]) parts.append(str(arg)) elif isinstance(arg, Tile): rotated.append([0]) parts.append(str(arg)) else: parts.append(arg) tiles = Tiles() tiles.add_tiles(arg) rotated.append([0 for tile in tiles.tiles]) #figure out who the tile was called from, for all open melds for meld in player.melds.melds: rotation = {'L': [1, 0, 0], 'M': [0, 1, 0], 'R': [0, 0, 1]} if meld.who: rotate = rotation[meld.who][:] if meld.shominkan: rotate[rotate.index(1):rotate.index(1)+1] = [1,2] elif len(meld) == 4: rotate.insert(2, 0) tiles = sorted(meld.tiles, key=lambda tile:tile.true_value) remove = [index for index, tile in enumerate(tiles) if tile.suit == meld.called.suit and tile.value == meld.called.value] del tiles[remove[0]] string = '' zero_counter = 0 for ref in rotate: if ref == 0: string += tiles[zero_counter].value zero_counter += 1 elif ref == 1: string += meld.called.value elif ref == 2: string += meld.shominkan_tile.value string += meld.called.suit #if its a closed kan, then display the tiles without rotation #but with flipping #the logic exists to display the red 5 in the kan at all times if applicable #but realistically this doesn't need to be present else: rotate = [0 for tile in meld.tiles] string = str(meld) if string[0] == string[3]: string = 'x'.join(string.rsplit(meld.tiles[0].value, 1)) string = string.replace(meld.tiles[0].value, 'x', 1) elif string[0] == string[1]: string = 'x'.join(string.rsplit(meld.tiles[0].value, 2)) else: string = string.replace(meld.tiles[-1].value, 'x', 2) rotated.append(rotate) parts.append(string) #converts the tile string (11123456789999m) into a list of image links #to be pasted together into one long image #any breaks (tedashi discarding hands, the discard, and meld blocks) #are separated by a short upright transparent rectangle image image_list = [] for part in parts: results = re.findall(r'([0-9x]+[mpsz])', part) if part == parts[0]: results = list( reduce(list.__add__, [['{}{}'.format(x, result[-1]) for x in result[:-1]] for result in results])) for result in results: image_list += [ './ui/{}{}.png'.format(x, result[-1]) for x in result[:-1] ] image_list.append('./ui/0.png') refs = [] for block in rotated: refs.extend(block) refs.append(0) del refs[-1] imagefile = [Image.open(x) for x in image_list] dim_x = imagefile[0].size[0] dim_y = imagefile[0].size[1] image_length = refs.count(0) * dim_x + refs.count(1) * dim_y if refs.count(2): image_height = 2 * dim_x else: image_height = dim_y #create a new image, based on how many images need to be displayed #the image generated will always be longer than the space required, due #to the transparent separator image being thinner than a normal tile #but this issue doesn't seem like a big deal target = Image.new('RGBA', (image_length, image_height)) left = 0 for image, ref in zip(imagefile, refs): bottom = 0 + image_height - dim_y if ref: image = image.rotate(90, expand = True) bottom += dim_y - dim_x if ref == 2: bottom -= dim_x left -= dim_y target.paste(image, (left, bottom)) left += image.size[0] # target.save('{}-{}.png'.format(player.disc, player.disc_id), quality=100) image = io.BytesIO() target.save(image, format = 'PNG') image.seek(0) return image else: tiles = str(player.hand) if args: args = [str(arg) for arg in args] return makeImage(tiles, hidden, tsumogiri, args) return makeImage(tiles, hidden, tsumogiri)