def __init__(self, player_from, player_to, trick): check_param(player_to in range(4) and ((player_from+1)% 4 == player_to or (player_from-1)% 4 == player_to), param=(player_from, player_to)) check_isinstance(trick, Trick) check_param(Card.DRAGON is trick.last_combination.card) super().__init__(player_pos=player_from) self._trick = trick self._to = player_to
def pairsteps(self, ignore_phoenix=False, length=None, contains_value=None): check_param(length is None or length > 0, length) sorted_pairs = sorted(self.pairs(ignore_phoenix=ignore_phoenix)) next_pair_no_ph = defaultdict(lambda: []) next_pair_with_ph = defaultdict(lambda: []) for p in sorted_pairs: if p.contains_phoenix(): next_pair_with_ph[p.height - 1].append(p) else: next_pair_no_ph[p.height - 1].append(p) def gen_from(pair, remlength, ph_used): if remlength <= 1: yield [pair] # continue without phoenix: try: for ps in gen_from(next_pair_no_ph[pair.height][0], remlength - 1, ph_used=ph_used): yield [pair] + ps except (StopIteration, IndexError): pass # continue with phoenix: if not ph_used: try: for ps in gen_from(next_pair_with_ph[pair.height][0], remlength - 1, ph_used=True): yield [pair] + ps except (StopIteration, IndexError): pass def gen_all_pairsteps(): """ Take all possible starting pairs and generate pairsteps from them """ max_height = CardValue.A.value # there is no possible pairstep starting from As (must have length 2) if isinstance(contains_value, CardValue): max_height = min( max_height, contains_value.value ) # straight starting from a higher value than contains_val, can not contain that val for pair in sorted_pairs: if pair.height <= max_height: yield from gen_from(pair, 2, ph_used=pair.contains_phoenix() ) # all steps starting with the pair # make and yield the pairsteps: gen = (PairSteps(pairs) for pairs in gen_all_pairsteps()) if isinstance(contains_value, CardValue): yield from (ps for ps in gen if ps.contains_cardval(contains_value)) else: yield from gen
def from_cards(cls, cards): check_param(len(cards) >= 4 and len(cards) % 2 == 0) check_param(Card.PHOENIX not in cards, "can't make pairstep from cards when Phoenix is present") pairs = [] for cs in ImmutableCards(cards).value_dict().values(): if len(cs) == 2: pairs.append(Pair(*cs)) check_true(len(cs) == 0, ex=ValueError, msg="Not a pairstep") return cls(pairs)
def __init__(self, team1, team2, winner_team, points, target_points, rounds): check_isinstance(team1, Team) check_isinstance(team2, Team) check_isinstance(winner_team, Team) check_isinstance(points, tuple) check_param(len(points) == 2) check_isinstance(target_points, int) check_all_isinstance(rounds, RoundHistory) check_param(len(rounds) > 0) super().__init__()
def set_phoenix_height(self, newheight): """ Set the height of tis single to the given height ONLY IF the Phoenix is the card of this single. Otherwise the call is ignored and a warning is printed. :param newheight: :return: the height of the single after this call """ check_isinstance(newheight, (int, float)) check_param( newheight == Card.PHOENIX.card_height or 2 <= newheight < 15, param=newheight) # newheight must be between 2 and 14 (TWO and As) if self._card is Card.PHOENIX: self._height = newheight # else: # warnings.warn("Tried to set the height of a non-phoenix single. The height was not set.") return self.height
def make(cards): """ makes a combiantion out of the given cards :param cards: the cards :return: the Combination :raise ValueError: if cards don't make a valid combination """ nbr_cards = len(cards) err = None try: check_param(0 < nbr_cards <= 15, nbr_cards) if nbr_cards == 1: return Single(*cards) if nbr_cards == 2: return Pair(*cards) if nbr_cards == 3: return Trio(*cards) if nbr_cards % 2 == 0: with ignored(Exception): ps = PairSteps.from_cards(cards) return ps if nbr_cards == 4: return SquareBomb(*cards) if nbr_cards == 5: with ignored(Exception): fh = FullHouse.from_cards(cards) return fh if nbr_cards >= 5: st, sb = None, None with ignored(Exception): st = Straight(cards) sb = StraightBomb(st) if sb: return sb if st: return st except Exception as e: err = e raise ValueError("Is no combination: {}\ncards: {}".format( err, str(cards)))
def from_cards(cls, cards): check_param(len(set(cards)) == 5) # 5 different cards check_param(Card.PHOENIX not in cards, "can't make from cards when Phoenix is present") pair = None trio = None for cs in ImmutableCards(cards).value_dict().values(): if len(cs) == 2: pair = Pair(*cs) elif len(cs) == 3: trio = Trio(*cs) else: check_true( len(cs) == 0, ex=ValueError, msg="there is no fullhouse in the cards (cards: {})". format(cards) ) # if this fails, then there is no fullhouse in the cards return cls(pair, trio)
def __init__(self, initial_points): check_param(len(initial_points) == 2) check_isinstance(initial_points, tuple) self._initial_points = initial_points self._points = initial_points empty_hcs = HandCardSnapshot(ImmutableCards([]), ImmutableCards([]), ImmutableCards([]), ImmutableCards([])) self._grand_tichu_hands = empty_hcs # A HandCardSnapshot (initially all hands are empty) self._before_swap_hands = empty_hcs # A HandCardSnapshot (initially all hands are empty) self._swap_actions = set() self._complete_hands = empty_hcs # A HandCardSnapshot (initially all hands are empty) self._announced_grand_tichus = set() self._announced_tichus = set() self._tricks = list() self._current_trick = UnfinishedTrick() self._handcards = list() self._ranking = list() self._events = list()
def pairsteps_old(self, ignore_phoenix=False, length=None, contains_value=None): check_param(length is None or length > 0, length) pairs_s = sorted(self.pairs(ignore_phoenix=ignore_phoenix)) if len(pairs_s) < 2: return def ps_len2(): # find all pairsteps of length 2 for i1, p1 in enumerate(pairs_s): for i2 in range(i1 + 1, len(pairs_s)): p2 = pairs_s[i2] try: yield PairSteps([p1, p2]) except Exception: pass ps_length2 = list(ps_len2()) print("ps2:", ps_length2) def ps_len_le_than(l): if l <= 2: yield from ps_length2 else: for ps in ps_len_le_than(l - 1): for p in pairs_s: try: yield ps.extend(p) except Exception: pass gen = (ps for ps in ps_len_le_than(length) if len(ps) == length) if length is not None else ps_len_le_than( len(pairs_s)) if isinstance(contains_value, CardValue): yield from (ps for ps in gen if ps.contains_cardval(contains_value)) else: yield from gen
def __init__(self, cards, phoenix_as=None): check_param(len(cards) >= 5) if Card.PHOENIX in cards: check_isinstance(phoenix_as, Card) check_param(phoenix_as not in cards, param=(phoenix_as, cards)) check_param(phoenix_as.suit is not CardSuit.SPECIAL, param=phoenix_as) cards_phoenix_replaced = [c for c in cards if c is not Card.PHOENIX ] + [phoenix_as] if phoenix_as else cards check_param( len({c.card_value for c in cards_phoenix_replaced }) == len(cards_phoenix_replaced)) # different card values cardheights = [c.card_height for c in cards_phoenix_replaced] check_param( max(cardheights) - min(cardheights) + 1 == len(cards_phoenix_replaced)) # cards are consecutive super().__init__(cards) self._height = max(cardheights) self._ph_as = phoenix_as
def search(self, root_state: TichuState, observer_id: int, iterations: int, cheat: bool = False, clear_graph_on_new_root=True) -> TichuAction: logging.debug( f"started {self.__class__.__name__} with observer {observer_id}, for {iterations} iterations and cheat={cheat}" ) check_param(observer_id in range(4)) self.observer_id = observer_id root_nid = self._graph_node_id(root_state) if root_nid not in self.graph and clear_graph_on_new_root: _ = self.graph.clear() else: logging.debug("Could keep the graph :)") self.add_root(root_state) iteration = 0 while iteration < iterations: iteration += 1 self._init_iteration() # logging.debug("iteration "+str(iteration)) state = root_state.determinization(observer_id=self.observer_id, cheat=cheat) # logging.debug("Tree policy") leaf_state = self.tree_policy(state) # logging.debug("rollout") rollout_result = self.rollout_policy(leaf_state) # logging.debug("backpropagation") assert len(rollout_result) == 4 self.backpropagation(reward_vector=rollout_result) action = self.best_action(root_state) logging.debug(f"size of graph after search: {len(self.graph)}") # self._draw_graph('./graphs/graph_{}.pdf'.format(time())) return action
def __init__(self, pairs): check_param(len(pairs) >= 2) check_all_isinstance(pairs, Pair) pairheights = {p.height for p in pairs} check_param( len(pairheights) == len(pairs)) # all pairs have different height check_param(max(pairheights) - min(pairheights) + 1 == len(pairs)) # pairs are consecutive cards = set(itertools.chain(*[p.cards for p in pairs])) check_param( len(cards) == 2 * len(pairs), param=pairs ) # no duplicated card (takes care of multiple phoenix use) super().__init__(cards) self._height = max(pairheights) self._lowest_pair_height = min(pairheights) self._pairs = pairs
def __init__(self, pair, trio): check_isinstance(pair, Pair) check_param(trio, Trio) check_param( not (pair.contains_phoenix() and trio.contains_phoenix())) # phoenix can only be used once cards = set(pair.cards + trio.cards) check_param(len(cards) == 5, param=(pair, trio)) super().__init__(cards) self._height = trio.height self._pair = pair self._trio = trio
def __init__(self, card1, card2): check_param(card1 is not card2, param=(card1, card2)) # different cards super().__init__((card1, card2)) if Card.PHOENIX in self._cards: if card1 is Card.PHOENIX: card1, card2 = card2, card1 # make sure card1 is not Phoenix check_param(card1.suit is not CardSuit.SPECIAL, card1) else: check_param(card1.card_value is card2.card_value, (card1, card2)) # same value self._height = card1.card_height self._card_value = card1.card_value
def all_combinations(self, played_on=None, ignore_phoenix=False, contains_value=None): check_param( contains_value is None or isinstance(contains_value, CardValue), contains_value) if played_on is None: yield from itertools.chain( self.singles(contains_value=contains_value), self.all_bombs(contains_value=contains_value), self.pairs(ignore_phoenix=ignore_phoenix, contains_value=contains_value), self.trios(ignore_phoenix=ignore_phoenix, contains_value=contains_value), self.straights(ignore_phoenix=ignore_phoenix, contains_value=contains_value), self.fullhouses(ignore_phoenix=ignore_phoenix, contains_value=contains_value), self.pairsteps(ignore_phoenix=ignore_phoenix, contains_value=contains_value)) elif isinstance(played_on, Combination): if Card.DOG in played_on: assert len(played_on) == 1 return # it is not possible to play on the dog if isinstance(played_on, Bomb): yield from ( b for b in self.all_bombs(contains_value=contains_value) if b.can_be_played_on(played_on)) # only higher bombs else: yield from self.all_bombs( contains_value=contains_value) # all bombs if Card.DRAGON in played_on: assert len(played_on) == 1 return # only bombs can beat the Dragon elif isinstance(played_on, Single): # all single cards higher than the played_on yield from (single for single in self.singles( contains_value=contains_value) if single.can_be_played_on(played_on)) elif isinstance(played_on, Pair): # all pairs higher than the played_on.any_card yield from (pair for pair in self.pairs( contains_value=contains_value) if pair.can_be_played_on(played_on)) elif isinstance(played_on, Trio): # all trios higher than the played_on.any_card yield from (trio for trio in self.trios( contains_value=contains_value) if trio.can_be_played_on(played_on)) elif isinstance(played_on, PairSteps): # all higher pairsteps yield from (ps for ps in self.pairsteps( length=len(played_on), contains_value=contains_value) if ps.can_be_played_on(played_on)) elif isinstance(played_on, Straight): # all higher straights yield from (st for st in self.straights( length=len(played_on), contains_value=contains_value) if st.can_be_played_on(played_on)) else: raise ValueError( "Wrong arguments! (played_on was {})".format(played_on))
def announced_grand_tichu(self, val): check_param(v in range(4) for v in val) self._announced_grand_tichu = set(val)
def add_grand_tichu(self, pos): check_param(pos in range(4)) self._announced_grand_tichu.add(pos)
def ranking(self, val): check_param(v in range(4) for v in val) self._ranking = val
def nbr_passed(self, val): check_param(val in range(3)) self._nbr_passed = val
def __init__(self, initial_points, final_points, points, grand_tichu_hands, before_swap_hands, card_swaps, complete_hands, announced_grand_tichus, announced_tichus, tricks, handcards, ranking, events): check_isinstance(initial_points, tuple) check_isinstance(final_points, tuple) check_isinstance(points, tuple) check_param( len(initial_points) == len(final_points) == len(points) == 2) check_all_isinstance( [grand_tichu_hands, before_swap_hands, complete_hands], HandCardSnapshot) if card_swaps != frozenset(): check_isinstance(card_swaps, frozenset) check_all_isinstance(card_swaps, SwapCardAction) check_param(len(card_swaps) == 12, param=card_swaps) check_param(len({sca.player_pos for sca in card_swaps}) == 4) check_param(len({sca.to for sca in card_swaps}) == 4) check_isinstance(announced_grand_tichus, frozenset) check_isinstance(announced_tichus, frozenset) check_all_isinstance(tricks, Trick) check_all_isinstance(handcards, HandCardSnapshot) check_param(len(tricks) == len(handcards)) check_isinstance(ranking, tuple) check_param(len(ranking) <= 4) check_isinstance(events, tuple) check_all_isinstance(events, GameEvent) super().__init__()
def current_pos(self, val): check_param(val in range(4)) self._current_pos = val
def __init__(self, player_id): super().__init__() check_param(player_id, int) self._id = player_id
def maxdepth(self, new_maxdepth): check_param(new_maxdepth > 0) self._maxdepth = new_maxdepth
def __init__(self, card1, card2, card3, card4): super().__init__((card1, card2, card3, card4)) check_param(len(set(self.cards)) == 4) # all cards are different # all cards have same card_value (takes also care of the phoenix) check_param(len({c.card_value for c in self.cards}) == 1) self._height = card1.card_height + 500 # 500 to make sure it is higher than any other non bomb combination
def __init__(self, nbr_simualtions): check_param(nbr_simualtions > 0) super().__init__() self._nbr_simulations = nbr_simualtions self._pool = Pool(3)
def straights_old(self, length=None, ignore_phoenix=False, contains_value=None): check_param(length is None or length >= 5, length) can_use_phoenix = not ignore_phoenix and Card.PHOENIX in self._cards if len(self._cards) < (5 if length is None else length): # if not enough cards are available -> return. return elif isinstance( contains_value, CardValue) and contains_value not in (c.card_value for c in self._cards): # does not contain the 'contains_value' card -> return return else: sorted_cards = sorted([ c for c in self._cards if c is not Card.PHOENIX and c is not Card.DOG and c is not Card.DRAGON ], key=lambda c: c.card_value) next_c = defaultdict(lambda: [ ]) # card val height -> list of cards with height 1 higher for c in sorted_cards: next_c[c.card_height - 1].append(c) def gen_from(card, remlength, ph): if remlength <= 1: yield [card] # finish a straight with this card # a straight for one possible continuation next_cards = next_c[card.card_height] if len(next_cards) > 0: for st in gen_from(next_cards[0], remlength - 1, ph=ph): yield [card] + st # Phoenix: if ph is None and can_use_phoenix: if remlength <= 2 and card.card_value is not CardValue.A: phoenix_as = ImmutableCards._card_val_to_sword_card[ card.card_height + 1] yield [card, (Card.PHOENIX, phoenix_as) ] # finish the straight with the Phoenix # take phoenix instead of card if card is not Card.MAHJONG: if len(next_cards) > 0: for st in gen_from(next_cards[0], remlength - 1, ph=card): yield [(Card.PHOENIX, card)] + st # take phoenix to jump a value if card.card_value < CardValue.K: # can not jump the As after_next_cards = next_c[card.card_height + 1] if len(after_next_cards) > 0: phoenix_as = ImmutableCards._card_val_to_sword_card[ card.card_height + 1] for st in gen_from(after_next_cards[0], remlength - 2, ph=phoenix_as): yield [card, (Card.PHOENIX, phoenix_as)] + st def gen_all_straights(): """ Take all possible starting cards and generate straights from them """ must_contain_val = isinstance(contains_value, CardValue) max_card_val = CardValue.TEN # there is no possible straight starting from J (must have length 5) if must_contain_val: max_card_val = min( max_card_val, contains_value ) # straight starting from a higher value than contains_val, can not contain that val for c in sorted_cards: if c.card_value <= max_card_val: yield from gen_from( c, 5, ph=None) # all straights starting with normal card if can_use_phoenix and c.card_value > CardValue.TWO: # all straights starting with the Phoenix phoenix = ImmutableCards._card_val_to_sword_card[ c.card_height - 1] for st in gen_from(c, 4, ph=phoenix): yield [(Card.PHOENIX, phoenix)] + st # make the Straights must_contain_val = isinstance(contains_value, CardValue) for st in gen_all_straights(): # TODO speed, make more efficient straight = None try: # raises Stop Iteration when phoenix is not in the straight (phoenix, phoenix_as) = next(elem for elem in st if isinstance(elem, tuple)) st.remove( (phoenix, phoenix_as) ) # TODO switch to dictionaries {card->card, phoenix->card ...} st.append(phoenix) straight = Straight(st, phoenix_as=phoenix_as) except StopIteration: straight = Straight(st) if not must_contain_val or ( must_contain_val and straight.contains_cardval(contains_value)): yield straight
def straights(self, length=None, ignore_phoenix=False, contains_value=None): check_param(length is None or length >= 5, length) can_use_phoenix = not ignore_phoenix and Card.PHOENIX in self._cards if len(self._cards) < (5 if length is None else length): # if not enough cards are available -> return. return elif isinstance( contains_value, CardValue) and contains_value not in (c.card_value for c in self._cards): # does not contain the 'contains_value' card -> return return else: sorted_cards = sorted([ c for c in self._cards if c is not Card.PHOENIX and c is not Card.DOG and c is not Card.DRAGON ], key=lambda c: c.card_value) next_card = defaultdict(lambda: [ ]) # card val height -> list of cards with height 1 higher for c in sorted_cards: next_card[c.card_height - 1].append(c) def gen_from(card, remlength, ph): if remlength <= 1: yield {card: card} # finish a straight with this card # a straight for one possible continuation next_cards = next_card[card.card_height] if len(next_cards) > 0: for st in gen_from(next_cards[0], remlength - 1, ph=ph): yield {card: card, **st} # Phoenix: if ph is None and can_use_phoenix: # finish the straight with the Phoenix: if remlength <= 2 and card.card_value is not CardValue.A: phoenix_as = ImmutableCards._card_val_to_sword_card[ card.card_height + 1] yield {card: card, Card.PHOENIX: phoenix_as} # take phoenix instead of card if card is not Card.MAHJONG: if len(next_cards) > 0: for st in gen_from(next_cards[0], remlength - 1, ph=card): yield {Card.PHOENIX: card, **st} # take phoenix to jump a value if card.card_value < CardValue.K and len( next_card[card.card_height] ) == 0: # can not jump the As, and only jump if there is no next card after_next_cards = next_card[card.card_height + 1] if len(after_next_cards ) > 0: # there is a card to 'land' phoenix_as = ImmutableCards._card_val_to_sword_card[ card.card_height + 1] for st in gen_from(after_next_cards[0], remlength - 2, ph=phoenix_as): yield { card: card, Card.PHOENIX: phoenix_as, **st } def gen_all_straights(): """ Take all possible starting cards and generate straights from them """ must_contain_val = isinstance(contains_value, CardValue) max_card_val = CardValue.TEN # there is no possible straight starting from J (must have length 5) if must_contain_val: max_card_val = min( max_card_val, contains_value ) # straight starting from a higher value than contains_val, can not contain that val for c in sorted_cards: if c.card_value <= max_card_val: yield from gen_from( c, 5, ph=None) # all straights starting with normal card # all straights starting with the Phoenix: if can_use_phoenix and c.card_value > CardValue.TWO: phoenix = ImmutableCards._card_val_to_sword_card[ c.card_height - 1] for st in gen_from(c, 4, ph=phoenix): yield {Card.PHOENIX: phoenix, **st} # make and yield the Straights: gen = (Straight( set(st.keys()), phoenix_as=st[Card.PHOENIX] if Card.PHOENIX in st else None) for st in gen_all_straights()) if isinstance(contains_value, CardValue): yield from (st for st in gen if st.contains_cardval(contains_value)) else: yield from gen
def points(self, points): check_isinstance(points, tuple) check_param(len(points) == 2) check_all_isinstance(points, int) self._points = points
def _ranking_append_player(self, player_pos): check_param(player_pos in range(4) and player_pos not in self._ranking) self._ranking.append(player_pos)
def __init__(self, cards): check_param(len(cards) > 0, cards) check_all_isinstance(cards, Card) self._cards = ImmutableCards(cards) check_true(len(self._cards) == len(cards))