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
Beispiel #11
0
    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
Beispiel #22
0
 def __init__(self, player_id):
     super().__init__()
     check_param(player_id, int)
     self._id = player_id
Beispiel #23
0
 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
Beispiel #25
0
 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))