Exemple #1
0
def unitest():
    htapi = Htapi()
    allcards = htapi.get52cards()
    random.shuffle(allcards)
    print (format(allcards))
    print (format(htapi.get_cards_by_suit(allcards, 'S')))
    print (format(htapi.find_card(allcards, Card('2H'))))
class HacBot(PokerBot, Htapi):
    """
    Hac's policy-based bot.
    
    This is the most stupid version which cannot shoot moon, but this is smart enough to avoid taking tricks.
    The grades of 50 games:
    ...Player: hac, score: -4, score_accl: -3835, shoot_moon_accl: 1
    ...Player: bota, score: 0, score_accl: -12501, shoot_moon_accl: 2
    ...Player: botb, score: -33, score_accl: -11415, shoot_moon_accl: 2
    ...Player: botc, score: -2, score_accl: -11538, shoot_moon_accl: 3
    """
    def __init__(self, name, is_debug=False):
        super(HacBot, self).__init__(name)

        self.htapi = Htapi(is_debug=is_debug)

        self.players = {}
        self.stat = {}
        self.stat['roundCard'] = []
        self.stat['usedCard'] = []
        self.stat['nextPlayers'] = []

        self.big_rank_cards = [
            Card('AS'),
            Card('KS'),
            Card('JS'),
            Card('TS'),
            Card('AH'),
            Card('KH'),
            Card('QH'),
            Card('JH'),
            Card('TH'),
            Card('AD'),
            Card('KD'),
            Card('QD'),
            Card('JD'),
            Card('TD'),
            Card('AC'),
            Card('KC'),
            Card('QC'),
            Card('JC'),
            Card('TC'),
        ]

        self.score_cards = [
            Card("QS"),
            Card("TC"),
            Card("2H"),
            Card("3H"),
            Card("4H"),
            Card("5H"),
            Card("6H"),
            Card("7H"),
            Card("8H"),
            Card("9H"),
            Card("TH"),
            Card("JH"),
            Card("QH"),
            Card("KH"),
            Card("AH")
        ]

        self.plz_rebuild_players = True

    def _rebuild_players(self, data):
        """
        Configure player table by input data.
        """
        self.htapi.dbg("Rebuild players: ", format(data))

        data_players = data['players']

        if len(data_players) != 4:
            self.errmsg("Invalid player number " + str(len(data_players)) +
                        " in server msg")

        self.players = {}

        for dp in data_players:
            self.players[dp['playerName']] = {
                'playerName': dp['playerName'],
                'score_accl': 0,
                'score': 0,
                'shoot_moon': 0,
                'expose': False,
                'shoot': [],
                'suit_leak': [],
                'pick': [],
            }

    def new_game(self, data):
        """
        Event: The start of a new game.
        """
        if self.plz_rebuild_players == True:
            self._rebuild_players(data)
            self.plz_rebuild_players = False

    def receive_cards(self, data):
        """
        Event: Receive my 13 cards.
        """
        self.stat['hand'] = self.get_cards(data)

    def _select_card2pass(self, my_hand_cards):

        card = self.htapi.remove_card(my_hand_cards, Card('KS'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('AS'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('AC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('KC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('QC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('JC'))
        if card != None:
            return card

        # Found the suit in shortage
        suit_list = ['S', 'H', 'D', 'C']
        pick_suit = None
        pick_suit_num = 0
        for suit in suit_list:
            this_suit_num = self.htapi.calc_card_num_by_suit(
                my_hand_cards, suit)

            if this_suit_num == 0:
                continue

            if pick_suit_num == 0:
                pick_suit_num = this_suit_num
                pick_suit = suit
            elif this_suit_num < pick_suit_num:
                pick_suit_num = this_suit_num
                pick_suit = suit

        # Remove the most large card in the target suit
        candidate_list = self.htapi.get_cards_by_suit(my_hand_cards, pick_suit)
        candidate_list = self.htapi.arrange_cards(candidate_list)
        card = candidate_list.pop()

        return self.htapi.remove_card(my_hand_cards, card)

    def pass_cards(self, data):
        """
        Event: Pick 3 cards to pass to others
        """
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        my_hand_cards = self.stat['hand']

        output = []
        for i in range(3):
            card = self._select_card2pass(my_hand_cards)
            if card == None:
                self.htapi.errmsg("Cannot pick a card to pass")
            output.append(card.toString())

        self.htapi.dbg(self.get_name() + " Pass 3 cards: " + format(output))
        return output

    def receive_opponent_cards(self, data):
        """
        Event: Recv 3 cards from one opponent
        """
        pass

    def expose_my_cards(self, data):
        """
        Event: The server asks me to expose AH...
        """
        my_hand_cards = self.stat['hand']

        output = []
        candidates = data['cards']
        if candidates == None or len(candidates) == 0:
            return output

        candidates = [Card(x) for x in candidates]

        if self.htapi.find_card(candidates, Card('AH')) == None:
            return output

        if my_hand_cards == None or len(my_hand_cards) == 0:
            return output

        # Calculate the rate that I will get penalty...
        # The simplest calculation is to check if I have too many big-rank cards
        big_card_num = self.htapi.find_cards(my_hand_cards,
                                             self.big_rank_cards)
        if big_card_num > (len(self.big_rank_cards) / 3):
            # Too many big-rank cards...Give up expose.
            return output

        output = ['AH']

        return output

    def expose_cards_end(self, data):
        """
        Event: Check if somebody expose AH. Damn. 
        """
        data_players = data['players']

        for dp in data_players:
            local_player = self.players[dp['playerName']]

            if len(dp['exposedCards']) > 0:
                # This guy exposed AH.
                local_player['expose'] = True
            else:
                local_player['expose'] = False

    def _monte_carlo_predict(self, card2shoot, opponent_cards, round_cards=[]):
        """
        Will I take the trick if I select the card2shoot!?
        """

        # TODO: Improve prediction by the players!
        #         next_players = self.stat['nextPlayers']
        #         next_player_num = len(next_players)

        # Calculate the number of cards will... make me eat the trick.
        opponent_same_suit_cards = self.htapi.get_cards_by_suit(
            opponent_cards, card2shoot.get_suit())
        if len(opponent_same_suit_cards) == 0:
            return 100.0

        danger_level = 0
        for ocard in opponent_same_suit_cards:
            if ocard.get_rank_num() < card2shoot.get_rank_num():
                # Oops...
                danger_level += 1

        max = float(len(opponent_same_suit_cards))
        percentage = danger_level / max
        return percentage

    def __leadplay(self, data):
        """
        I am the round leader. I want to avoid tacking the trick, so choose the best!
        """

        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        opponent_cards = self._get_unused_cards_by_suits(my_hand_cards)

        selected = []
        peak_percentage = 0
        for c in my_avail_cards:
            # If I pick this card, will I take the trick?
            percentage = self._monte_carlo_predict(c,
                                                   opponent_cards,
                                                   round_cards=[])

            if len(selected) == 0:
                peak_percentage = percentage
                selected = [c]
            elif percentage < peak_percentage:
                peak_percentage = percentage
                selected = [c]
            elif percentage == peak_percentage:
                selected.append(c)

        # Prefer a lower number suit
        all_suit = []
        for c in selected:
            card_suit = c.get_suit()
            if all_suit.count(card_suit) == 0:
                all_suit.append(card_suit)

        self.htapi.dbg("Selected candidates: " + format(selected) +
                       ", the suits: " + format(all_suit) + ", all cards: " +
                       format(my_hand_cards))

        prefer_suit = None
        min_suit_num = 0
        for suit in all_suit:
            same_suit_num = self.htapi.get_cards_by_suit(my_hand_cards, suit)

            if prefer_suit == None:
                prefer_suit = suit
                min_suit_num = same_suit_num
            elif same_suit_num < min_suit_num:
                prefer_suit = suit
                min_suit_num = same_suit_num

        prefer_cards = self.htapi.arrange_cards(
            self.htapi.get_cards_by_suit(selected, prefer_suit))
        self.htapi.dbg("Selected candidates: " + format(prefer_cards))
        card2shoot = prefer_cards.pop()
        self.htapi.dbg("Select card" + format(card2shoot))

        return card2shoot

    def __midplay(self, data):
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        round_cards = self.stat['roundCard']
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        lead_card = round_cards[0]
        my_lead_suit_num = self.htapi.calc_card_num_by_suit(
            my_hand_cards, lead_card.get_suit())

        if my_lead_suit_num > 0:
            """
            Have the same suit. Choose a smaller card to avoid the trick.
            """
            filtered_round_cards = self.htapi.get_cards_by_suit(
                round_cards, lead_card.get_suit())
            return self.htapi.pick_smaller_card(my_avail_cards,
                                                filtered_round_cards,
                                                auto_choose_big=True)
        else:
            """
            I don't have the same suit. Git rid of 'QS'. 
            """
            cardqs = self.htapi.find_card(my_avail_cards, Card('QS'))
            if cardqs != None:
                return cardqs
            """
            I don't have the same suit. Throw dangerous high rank cards. 
            """
            high_card = self.htapi.pick_big_card(my_avail_cards)
            if high_card.get_rank_num() >= Card('JS').get_rank_num():
                return high_card
            """
            I don't have the same suit. My chance to throw out point cards!
            """
            candidate_list = []

            candidate_list += self.htapi.find_cards(my_avail_cards,
                                                    [Card('TC')])
            candidate_list += self.htapi.get_cards_by_suit(my_avail_cards, 'H')

            if len(candidate_list) > 0:
                return candidate_list.pop(0)
            """
            Otherwise, pick the highest rank card. 
            """
            return high_card

    def __lastplay(self, data):
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        round_cards = self.stat['roundCard']
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        lead_card = round_cards[0]
        my_lead_suit_num = self.htapi.calc_card_num_by_suit(
            my_hand_cards, lead_card.get_suit())

        score_card_num = self.htapi.find_cards(round_cards, self.score_cards)

        if my_lead_suit_num > 0:
            """
            Have the same suit. If there's no point on board, shoot the biggest card.
            If there's a point, try to avoid.
            """
            if score_card_num == 0:
                return self.htapi.pick_big_card(my_avail_cards)
            else:
                return self.htapi.pick_smaller_card(my_avail_cards,
                                                    round_cards,
                                                    auto_choose_big=True)
        else:
            """
            Don't have the same suit. Play the trick.
            """
            return self.__midplay(data)

    def pick_card(self, data):
        """
        Event: My turn to shoot a card.
        """
        # roundPlayers is in the correct order of shooting cards.
        round_players = data['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        # Get players in next turn.
        self.stat['nextPlayers'] = data['roundPlayers'][(my_pos + 1):]

        if my_pos == 0:
            card = self.__leadplay(data)
        elif my_pos == 3:
            card = self.__lastplay(data)
        else:
            card = self.__midplay(data)

        self.htapi.dbg(self.get_name() + " shoot card: " + format(card) +
                       ", from: " + format(data['self']['cards']) +
                       ", next players: " + format(self.stat['nextPlayers']))
        return card.toString()

    def _detect_card_shortage(self, local_player, turn_card):
        #
        # If the player is not lead player and he shoots a card not the same as lead card,
        # mark the player as card shortage.
        #
        # If I know the shortage status of players, can have advantage to predict.
        #

        round_cards = self.stat['roundCard']

        if len(round_cards) <= 1:
            # The turn player is leader. Ignore.
            return False

        lead_card = round_cards[0]

        if lead_card.get_suit_num() != turn_card.get_suit_num():
            if local_player['suit_leak'].count(lead_card.get_suit()):
                local_player['suit_leak'].append(lead_card.get_suit())
                self.htapi.dbg("Player: " + local_player['playerName'] +
                               " card leakage: " + lead_card.get_suit())

    def _get_unused_cards_by_suits(self, my_cards, suits=[]):
        """
        Get unused cards of other opponents so that I can know how many cards are left..
        """

        all52cards = self.htapi.get52cards()
        output = all52cards

        self.htapi.remove_cards(output, self.stat['usedCard'])
        self.htapi.remove_cards(output, my_cards)

        if len(suits) == 0:
            # Do not need to pick a specific suit
            pass
        else:
            # Pick unused cards of target suits
            output = self.htapi.get_cards_by_suits(output, suits)

        return output

    def turn_end(self, data):
        """
        Event: turn end
        """
        data_turn_player = data['turnPlayer']

        data_turn_card = data['turnCard']
        data_turn_card = Card(data_turn_card)

        if data_turn_player != self.get_name():
            self.htapi.dbg(data_turn_player + " shoot card: " +
                           format(data_turn_card))

        local_player = self.players[data_turn_player]
        local_player['shoot'].append(data_turn_card)

        self.stat['roundCard'].append(data_turn_card)
        self.stat['usedCard'].append(data_turn_card)

        self._detect_card_shortage(local_player, data_turn_card)

    def pick_history(self, data, is_timeout, pick_his):
        """
        Event: turn end
        """
        self.turn_end(data)

    def round_end(self, data):
        """
        Event: round end
        """
        round_player_name = data['roundPlayer']
        round_cards = self.stat['roundCard']

        local_player = self.players[round_player_name]
        local_player['pick'] += round_cards

        self.htapi.dbg(
            "Player: " + round_player_name + " picked 4 cards: " +
            format(round_cards),
            " overall pick: " + format(local_player['pick']))

        #
        # Reset round data
        #
        self.stat['roundCard'] = []

    def deal_end(self, data):
        """
        Event: deal end
        """
        data_players = data['players']

        self.htapi.dbg(format(data))

        for player in data_players:
            local_player = self.players[player['playerName']]
            local_player['score_accl'] = player['gameScore']
            if player['shootingTheMoon'] == True:
                local_player['shoot_moon'] += 1

        for key in self.players.keys():
            p = self.players[key]
            # Reset deal-specific data.
            p['score'] = 0
            p['shoot'] = []
            p['expose'] = False
            p['pick'] = []

        self.stat['usedCard'] = []

    def game_over(self, data):
        """
        Event: game end
        """
        self.htapi.dbg(format(data))
Exemple #3
0
class HacBotII(PokerBot, Htapi):
    """
    Hac's policy-based bot.
    
    This is a bot able to shoot moon. When I get a lot of big-rank cards. DO IT!
    Don't want to avoid opponents shooting the moon, because I don't loss any money :-) 
    """

    SM_THOLD_PASS3 = 0.6
    SM_THOLD_PICK = 0.1

    def __init__(self, name, is_debug=False):
        super(HacBotII, self).__init__(name)

        self.htapi = Htapi(is_debug=is_debug)

        self.players = {}
        self.stat = {}
        self.stat['roundCard'] = []
        self.stat['usedCard'] = []
        self.stat['nextPlayers'] = []

        self.big_rank_cards = [
            Card('AS'),
            Card('KS'),
            Card('JS'),
            Card('TS'),
            Card('AH'),
            Card('KH'),
            Card('QH'),
            Card('JH'),
            Card('TH'),
            Card('AD'),
            Card('KD'),
            Card('QD'),
            Card('JD'),
            Card('TD'),
            Card('AC'),
            Card('KC'),
            Card('QC'),
            Card('JC'),
            Card('TC'),
        ]

        self.score_cards = [
            Card("QS"),
            Card("TC"),
            Card("2H"),
            Card("3H"),
            Card("4H"),
            Card("5H"),
            Card("6H"),
            Card("7H"),
            Card("8H"),
            Card("9H"),
            Card("TH"),
            Card("JH"),
            Card("QH"),
            Card("KH"),
            Card("AH")
        ]

        self.plz_rebuild_players = True

        self.stat['expose_ah_mode'] = False
        self.stat['shoot_moon_mode'] = True

    def _rebuild_players(self, data):
        """
        Configure player table by input data.
        """
        self.htapi.dbg("Rebuild players: ", format(data))

        data_players = data['players']

        if len(data_players) != 4:
            self.errmsg("Invalid player number " + str(len(data_players)) +
                        " in server msg")

        self.players = {}

        for dp in data_players:
            self.players[dp['playerName']] = {
                'playerName': dp['playerName'],
                'score_accl': 0,
                'score': 0,
                'shoot_moon': 0,
                'expose': False,
                'shoot': [],
                'suit_leak': [],
                'pick': [],
            }

    def new_game(self, data):
        """
        Event: The start of a new game.
        """
        if self.plz_rebuild_players == True:
            self._rebuild_players(data)
            self.plz_rebuild_players = False

    def receive_cards(self, data):
        """
        Event: Receive my 13 cards.
        """
        self.stat['hand'] = self.get_cards(data)

    def _select_card2pass_shoot_moon_mode(self, my_hand_cards):
        """
        Pop out 1 card to pass
        
        The rule here is to pick the small cards in shortage
        """

        #
        # Find the non-empty suit but the card num is low.
        #
        # TODO: Try to remove non-heart suit can help shoot-moon ability.
        #
        card_num_stat = {'S': 0, 'H': 0, 'D': 0, 'C': 0}
        for c in my_hand_cards:
            card_num_stat[c.get_suit()] += 1

        card_num_stat_sorted = sorted(card_num_stat.iteritems(),
                                      key=lambda (k, v): (v, k))

        #
        # Try to remove a suit
        #
        for di in card_num_stat_sorted:
            k, v = di
            if v != 0:
                pick_suit, v = di

                tgt_cards = self.htapi.get_cards_by_suit(
                    my_hand_cards, pick_suit)
                tgt_cards = self.htapi.arrange_cards(tgt_cards)

                smaller_card = self.htapi.pick_smaller_card(
                    tgt_cards, [Card('9S')])
                if smaller_card != None:
                    return self.htapi.remove_card(my_hand_cards, smaller_card)

        #
        # In case...
        #
        for di in card_num_stat_sorted:
            k, v = di
            if v != 0:
                pick_suit, v = di

                tgt_cards = self.htapi.get_cards_by_suit(
                    my_hand_cards, pick_suit)
                tgt_cards = self.htapi.arrange_cards(tgt_cards)

                card = tgt_cards[0]
                return self.htapi.remove_card(my_hand_cards, card)

        self.htapi.errmsg("BUG")

        card = my_hand_cards[0]
        return self.htapi.remove_card(my_hand_cards, card)

    def _select_card2pass(self, my_hand_cards):
        """
        Pop out 1 card to pass
        """

        card = self.htapi.remove_card(my_hand_cards, Card('KS'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('AS'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('AC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('KC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('QC'))
        if card != None:
            return card

        card = self.htapi.remove_card(my_hand_cards, Card('JC'))
        if card != None:
            return card

        # Found the suit in shortage
        suit_list = ['S', 'H', 'D', 'C']
        pick_suit = None
        pick_suit_num = 0
        for suit in suit_list:
            this_suit_num = self.htapi.calc_card_num_by_suit(
                my_hand_cards, suit)

            if this_suit_num == 0:
                continue

            if pick_suit_num == 0:
                pick_suit_num = this_suit_num
                pick_suit = suit
            elif this_suit_num < pick_suit_num:
                pick_suit_num = this_suit_num
                pick_suit = suit

        # Remove the most large card in the target suit
        candidate_list = self.htapi.get_cards_by_suit(my_hand_cards, pick_suit)
        candidate_list = self.htapi.arrange_cards(candidate_list)
        card = candidate_list.pop()

        return self.htapi.remove_card(my_hand_cards, card)

    def _pass_cards_anti_score_mode(self, data):
        """
        Pick 3 cards which can avoid taking score
        """
        my_hand_cards = self.stat['hand']

        output = []
        for i in range(3):
            card = self._select_card2pass(my_hand_cards)
            if card == None:
                self.htapi.errmsg("Cannot pick a card to pass")
            output.append(card.toString())

        self.htapi.dbg(self.get_name() + " pass 3 cards: " + format(output))
        return output

    def _pass_cards_shoot_moon_mode(self, data):
        """
        Pick 3 cards which can help myself to shoot moon.
        """
        my_hand_cards = self.stat['hand']

        output = []
        for i in range(3):
            card = self._select_card2pass_shoot_moon_mode(my_hand_cards)
            if card == None:
                self.htapi.errmsg("Cannot pick a card to pass")
            output.append(card.toString())

        self.htapi.dbg(self.get_name() + " pass 3 cards: " + format(output))
        return output

    def _do_i_have_too_many_low_rank_cards(self):
        my_hand_cards = self.stat['hand']

        low_rank_card_num = 0
        for c in my_hand_cards:
            if c.get_rank_num() < Card("TS").get_rank_num:
                low_rank_card_num += 1

        # TBD
        if low_rank_card_num >= 9:
            return True
        else:
            return False

    def pass_cards(self, data):
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        self.stat['hand'] = self.htapi.arrange_cards(self.stat['hand'])

        self.htapi.dbg("Select 3 cards from: " + format(self.stat['hand']))

        sm_ability = self._calc_shoot_moon_ability(data)
        if sm_ability > self.SM_THOLD_PASS3:
            self.htapi.dbg("shoot moon mode pass3: " + str(sm_ability))
            return self._pass_cards_shoot_moon_mode(data)
        else:
            self.htapi.dbg("anti score mode pass3")
            return self._pass_cards_anti_score_mode(data)

    def receive_opponent_cards(self, data):
        """
        Event: Recv 3 cards from one opponent
        """
        pass

    def expose_my_cards(self, data):
        """
        Event: The server asks me to expose AH...
        """
        my_hand_cards = self.stat['hand']

        output = []
        candidates = data['cards']
        if candidates == None or len(candidates) == 0:
            return output

        candidates = [Card(x) for x in candidates]

        if self.htapi.find_card(candidates, Card('AH')) == None:
            return output

        if my_hand_cards == None or len(my_hand_cards) == 0:
            return output

        #
        # TODO: Be smarter... calculate shoot-moon ability, or anti-score ability
        # If I know I won't take any score, expose AH!
        # If I want to shoot the moon, expose AH!
        #

        # Calculate the rate that I will get penalty...
        # The simplest calculation is to check if I have too many big-rank cards
        my_big_cards = self.htapi.find_cards(my_hand_cards,
                                             self.big_rank_cards)
        my_big_card_num = len(my_big_cards)
        if my_big_card_num > 2 and my_big_card_num < 5:
            """
            Have some but not many high-rank.
            """
            return output

        #
        # Expose AH!
        #
        output = ['AH']
        return output

    def expose_cards_end(self, data):
        """
        Event: Check if somebody expose AH. Damn. 
        """
        data_players = data['players']

        for dp in data_players:
            local_player = self.players[dp['playerName']]

            if len(dp['exposedCards']) > 0:
                # This guy exposed AH.
                local_player['expose'] = True
                self.stat['expose_ah_mode'] = True
            else:
                local_player['expose'] = False

    def _lead_play_monte_carlo_predict_penalty(self,
                                               card2shoot,
                                               opponent_cards,
                                               round_cards=[]):
        """
        Will I take the trick if I select the card2shoot!?
        
        Output: The possibility to get score if I shoot the card: 0 ~ 100.0
        """

        # TODO: Improve prediction by the players!
        #         next_players = self.stat['nextPlayers']
        #         next_player_num = len(next_players)

        # Calculate the number of cards will... make me eat the trick.
        opponent_same_suit_cards = self.htapi.get_cards_by_suit(
            opponent_cards, card2shoot.get_suit())
        if len(opponent_same_suit_cards) == 0:
            opponent_score_cards = self.htapi.find_score_cards(opponent_cards)
            if len(opponent_score_cards) > 0:
                return 100.0
            else:
                # There's no penalty card now. Won't eat
                return 0.0

        # Danger level means the chance I will eat a trick.
        # If the card2shoot is high-rank, the chance is higher, of course.
        danger_level = 0
        for ocard in opponent_same_suit_cards:
            if ocard.get_rank_num() < card2shoot.get_rank_num():
                # Oops...
                danger_level += 1

        percentage_max = float(len(opponent_same_suit_cards))
        percentage = danger_level / percentage_max
        return percentage

    def _get_unused_cards_by_suits(self, my_cards, suits=[]):
        """
        Get unused cards of other opponents so that I can know how many cards are left..
        """

        all52cards = self.htapi.get52cards()
        output = all52cards

        self.htapi.remove_cards(output, self.stat['usedCard'])
        self.htapi.remove_cards(output, my_cards)

        if len(suits) == 0:
            # Do not need to pick a specific suit
            pass
        else:
            # Pick unused cards of target suits
            output = self.htapi.get_cards_by_suits(output, suits)

        return output

    def __leadplay(self, data):
        """
        I am the round leader. I want to avoid tacking the trick, so choose the best!
        """

        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        opponent_cards = self._get_unused_cards_by_suits(my_hand_cards)

        selected = []
        peak_percentage = 0
        for c in my_avail_cards:
            # If I pick this card, will I take the trick?
            percentage = self._lead_play_monte_carlo_predict_penalty(
                c, opponent_cards, round_cards=[])

            if len(selected) == 0:
                peak_percentage = percentage
                selected = [c]
            elif percentage < peak_percentage:
                peak_percentage = percentage
                selected = [c]
            elif percentage == peak_percentage:
                selected.append(c)

        # Prefer a lower number suit
        all_suit = []
        for c in selected:
            card_suit = c.get_suit()
            if all_suit.count(card_suit) == 0:
                all_suit.append(card_suit)

        self.htapi.dbg("Selected candidates: " + format(selected) +
                       ", the suits: " + format(all_suit) + ", all cards: " +
                       format(my_hand_cards))

        prefer_suit = None
        min_suit_num = 0
        for suit in all_suit:
            same_suit_num = self.htapi.get_cards_by_suit(my_hand_cards, suit)

            if prefer_suit == None:
                prefer_suit = suit
                min_suit_num = same_suit_num
            elif same_suit_num < min_suit_num:
                prefer_suit = suit
                min_suit_num = same_suit_num

        prefer_cards = self.htapi.arrange_cards(
            self.htapi.get_cards_by_suit(selected, prefer_suit))
        self.htapi.dbg("Selected candidates: " + format(prefer_cards))
        card2shoot = prefer_cards.pop(0)
        self.htapi.dbg("Select card" + format(card2shoot))

        return card2shoot

    def __midplay(self, data):
        round_cards = self.stat['roundCard']
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        lead_card = round_cards[0]
        my_lead_suit_num = self.htapi.calc_card_num_by_suit(
            my_hand_cards, lead_card.get_suit())

        if my_lead_suit_num > 0:
            """
            Have the same suit. Choose a smaller card to avoid the trick.
            """
            filtered_round_cards = self.htapi.get_cards_by_suit(
                round_cards, lead_card.get_suit())
            return self.htapi.pick_smaller_card(my_avail_cards,
                                                filtered_round_cards,
                                                auto_choose_big=True)
        else:
            """
            I don't have the same suit. Git rid of 'QS'. 
            """
            cardqs = self.htapi.find_card(my_avail_cards, Card('QS'))
            if cardqs != None:
                return cardqs
            """
            I don't have the same suit. Throw dangerous high rank cards. 
            """
            high_card = self.htapi.pick_big_card(my_avail_cards)
            if high_card.get_rank_num() >= Card('JS').get_rank_num():
                return high_card
            """
            I don't have the same suit. My chance to throw out point cards!
            """
            candidate_list = []

            candidate_list += self.htapi.find_cards(my_avail_cards,
                                                    [Card('TC')])
            candidate_list += self.htapi.get_cards_by_suit(my_avail_cards, 'H')

            if len(candidate_list) > 0:
                return candidate_list.pop(0)
            """
            Otherwise, pick the highest rank card. 
            """
            return high_card

    def __lastplay(self, data):
        round_cards = self.stat['roundCard']
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        lead_card = round_cards[0]
        my_lead_suit_num = self.htapi.calc_card_num_by_suit(
            my_hand_cards, lead_card.get_suit())

        score_card_num = self.htapi.find_cards(round_cards, self.score_cards)

        if my_lead_suit_num > 0:
            """
            Have the same suit. If there's no point on board, shoot the biggest card.
            If there's a point, try to avoid.
            """
            if score_card_num == 0:
                return self.htapi.pick_big_card(my_avail_cards)
            else:
                return self.htapi.pick_smaller_card(my_avail_cards,
                                                    round_cards,
                                                    auto_choose_big=True)
        else:
            """
            Don't have the same suit. Play the trick.
            """
            return self.__midplay(data)

    def pick_card_anti_score_mode(self, data):
        # roundPlayers is in the correct order of shooting cards.
        round_players = data['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        # Get players in next turn.
        self.stat['nextPlayers'] = data['roundPlayers'][(my_pos + 1):]

        if my_pos == 0:
            card = self.__leadplay(data)
        elif my_pos == 3:
            card = self.__lastplay(data)
        else:
            card = self.__midplay(data)

        return card

    def _calc_shoot_moon_ability(self, data):
        """
        Detect if I can shoot the moon.
        """
        if self.stat['shoot_moon_mode'] == False:
            # I know it's unlikely to shoot the moon... Give up
            return 0.0

        # Detect factors: all played turn cards, hand_card
        my_hand_cards = self.stat['hand']
        all_used_cards = self.stat['usedCard']

        opponent_unused_cards = self._get_unused_cards_by_suits(my_hand_cards)
        opponent_unused_penalty_cards = self.htapi.find_penalty_cards(
            opponent_unused_cards)

        left_turn_num = len(my_hand_cards)
        """
        The prediction is simple, calculate how many turns I can win.
        """
        win_count = 0
        for c in my_hand_cards:
            unused_same_suit_cards = self.htapi.get_cards_by_suit(
                opponent_unused_cards, c.get_suit())
            if len(unused_same_suit_cards) > 0:
                unused_same_suit_cards = self.htapi.arrange_cards(
                    unused_same_suit_cards)

                if c.get_rank_num() > unused_same_suit_cards[-1].get_rank_num(
                ):
                    win_count += 1

        self.htapi.dbg("shoot moon ability: " + str(win_count) + " / " +
                       str(left_turn_num))
        return win_count / float(left_turn_num)

    def __leadplay_shoot_moon_mode(self, data):
        """
        I am the lead player. Try to eat heart and QS!
        
        Make sure I have the a card that can win a score. 
        If I don't have, I will fail to shoot. Try Monte Carlo simulation.
        """
        my_hand_cards = self.stat['hand']

        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]
        all_used_cards = self.stat['usedCard']
        opponent_cards = self._get_unused_cards_by_suits(my_hand_cards)

        #
        # Shoot the card that nobody has the same suit.
        #
        for c in my_avail_cards:
            my_same_suit_card_num = self.htapi.get_cards_by_suit(
                my_avail_cards, c.get_suit())
            used_same_suit_card_num = self.htapi.get_cards_by_suit(
                all_used_cards, c.get_suit())

            if my_same_suit_card_num + used_same_suit_card_num == 13:
                self.htapi.dbg("Select nobody have suit to play the trick.")
                return c

        #
        # Heart eater.
        #
        my_heart_cards = self.htapi.get_cards_by_suit(my_avail_cards, 'H')
        if len(my_heart_cards) > 0:
            unused_heart_cards = self.htapi.get_cards_by_suit(
                opponent_cards, 'H')
            if len(unused_heart_cards) > 0:
                unused_heart_cards = self.htapi.arrange_cards(
                    unused_heart_cards)

                for c in my_heart_cards:
                    if c.get_rank_num() > unused_heart_cards[-1].get_rank_num(
                    ):
                        return c

        #
        # Shoot the card that I will win the round.
        #
        for c in my_avail_cards:
            unused_same_suit_cards = self.htapi.get_cards_by_suit(
                opponent_cards, c.get_suit())
            if len(unused_same_suit_cards) > 0:
                unused_same_suit_cards = self.htapi.arrange_cards(
                    unused_same_suit_cards)

                if c.get_rank_num() > unused_same_suit_cards[-1].get_rank_num(
                ):
                    return c

        #
        # If I don't have cards can win, ...shoot no score suit left a lot.
        #
        pick_suit = None
        pick_suit_num = 0
        for c in my_avail_cards:
            unused_same_suit_cards = self.htapi.get_cards_by_suit(
                opponent_cards, c.get_suit())

            if pick_suit_num == 0:
                pick_suit_num = len(unused_same_suit_cards)
                pick_suit = c.get_suit()
            elif len(unused_same_suit_cards) > pick_suit_num:
                pick_suit_num = len(unused_same_suit_cards)
                pick_suit = c.get_suit()

        candidates = self.htapi.get_cards_by_suit(my_avail_cards, pick_suit)
        candidates = self.htapi.arrange_cards(candidates)

        return candidates[-1]

    def __midplay_shoot_moon_mode(self, data):
        """
        Check if the next players are in shortage of lead card suit.
        If they are in shortage, shoot the biggest card I have to win the leader ship.
        
        TBD: Use Monte Carlo simulation here?
        """
        return self.__leadplay_shoot_moon_mode(data)

    def __lastplay_shoot_moon_mode(self, data):
        """
        I am the last player: See if there's a score card!
        """

        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        round_cards = self.stat['roundCard']
        lead_card = round_cards[0]
        if self.htapi.calc_card_num_by_suit(my_avail_cards,
                                            lead_card.get_suit()) == 0:
            """
            I don't have the same suit. Pick a no-score small card :-)
            """
            candidates = self.htapi.find_no_penalty_cards(my_avail_cards)
            if len(candidates) == 0:
                # Sorry, don't have no penalty card. The shoot-moon action will fail.
                return self.pick_card_anti_score_mode(data)

            #
            # Detect the suit in shortage
            #
            card_num_stat = {'S': 0, 'H': 0, 'D': 0, 'C': 0}
            for c in candidates:
                card_num_stat[c.get_suit()] += 1

            # Sort the card_num_stat
            card_num_stat_sorted = sorted(card_num_stat.iteritems(),
                                          key=lambda (k, v): (v, k))

            # Fetch the suit in shortage, but ignore zero number suit.
            for di in card_num_stat_sorted:
                k, v = di
                if v != 0:
                    pick_suit, v = di
                    break

            candidates = self.htapi.get_cards_by_suit(candidates, pick_suit)
            candidates = self.htapi.arrange_cards(candidates)
            no_score_small_card = candidates.pop(0)

            return no_score_small_card

        elif self.htapi.calc_score(round_cards) > 0:
            """
            I have the same suit. Try to win the score card.
            """
            bigger_card = self.htapi.pick_bigger_card(my_avail_cards,
                                                      round_cards)
            if bigger_card == None:
                # Too bad, I can't win... switch to anti-score mode.
                return self.pick_card_anti_score_mode(data)
            else:
                return bigger_card
        else:
            """
            I have the same suit. No score on board... should I take the leadership? Yes?! TBD?
            """
            bigger_card = self.htapi.pick_bigger_card(my_avail_cards,
                                                      round_cards)
            if bigger_card == None:
                # Too bad, I can't win...
                return self.htapi.pick_small_card(my_avail_cards)
            else:
                return bigger_card

    def pick_card_shoot_moon_mode(self, data):
        # roundPlayers is in the correct order of shooting cards.
        round_players = data['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        # Get players in next turn.
        self.stat['nextPlayers'] = data['roundPlayers'][(my_pos + 1):]

        if my_pos == 0:
            self.htapi.dbg("sm lead play")
            card = self.__leadplay_shoot_moon_mode(data)
        elif my_pos == 3:
            self.htapi.dbg("sm last play")
            card = self.__lastplay_shoot_moon_mode(data)
        else:
            self.htapi.dbg("sm mid play")
            card = self.__midplay_shoot_moon_mode(data)

        return card

    def pick_card(self, data):
        """
        Event: My turn to shoot a card.
        """
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]

        if self._calc_shoot_moon_ability(data) >= self.SM_THOLD_PICK:
            self.htapi.dbg("shoot moon mode")
            card = self.pick_card_shoot_moon_mode(data)
        else:
            self.htapi.dbg("aniti-score mode")
            card = self.pick_card_anti_score_mode(data)

        # NOTE: Do not remove the card in hand card, do it ad turn end event.
        self.htapi.dbg(self.get_name() + " shoot card: " + format(card) +
                       ", from: " + format(data['self']['cards']) +
                       ", next players: " + format(self.stat['nextPlayers']))
        return card.toString()

    def _detect_card_shortage(self, local_player, turn_card):
        #
        # If the player is not lead player and he shoots a card not the same as lead card,
        # mark the player as card shortage.
        #
        # If I know the shortage status of players, can have advantage to predict.
        #

        round_cards = self.stat['roundCard']

        if len(round_cards) <= 1:
            # The turn player is leader. Ignore.
            return False

        lead_card = round_cards[0]

        if lead_card.get_suit_num() != turn_card.get_suit_num():
            if local_player['suit_leak'].count(lead_card.get_suit()):
                local_player['suit_leak'].append(lead_card.get_suit())
                self.htapi.dbg("Player: " + local_player['playerName'] +
                               " card leakage: " + lead_card.get_suit())

    def turn_end(self, data):
        """
        Event: turn end
        """
        data_turn_player = data['turnPlayer']

        data_turn_card = data['turnCard']
        data_turn_card = Card(data_turn_card)

        if data_turn_player != self.get_name():
            self.htapi.dbg(data_turn_player + " shoot card: " +
                           format(data_turn_card))

        local_player = self.players[data_turn_player]
        local_player['shoot'].append(data_turn_card)

        self.stat['roundCard'].append(data_turn_card)
        self.stat['usedCard'].append(data_turn_card)

        self._detect_card_shortage(local_player, data_turn_card)

    def pick_history(self, data, is_timeout, pick_his):
        """
        Event: turn end
        """
        self.turn_end(data)

    def _round_end_detect_shoot_moon_ability(self):
        #
        # Check if somebody get any score and give up shoot-moon mode.
        #

        if self.stat['shoot_moon_mode'] == False:
            # Already disable shoot moon mode.
            return

        for key in self.players:
            lp = self.players[key]
            score = self.htapi.calc_score(
                lp['pick'], is_expose_ah=self.stat['expose_ah_mode'])
            if score != 0 and lp['playerName'] != self.get_name():
                self.htapi.dbg("Give up shoot-moon mode...So sad.")
                self.stat['shoot_moon_mode'] = False

    def round_end(self, data):
        """
        Event: round end
        """
        round_player_name = data['roundPlayer']
        round_cards = self.stat['roundCard']

        local_player = self.players[round_player_name]
        local_player['pick'] += round_cards

        self.htapi.dbg(
            "Player: " + round_player_name + " picked 4 cards: " +
            format(round_cards),
            " overall pick: " + format(local_player['pick']))

        # Disable shoot moon mode if somebody rx a score.
        self._round_end_detect_shoot_moon_ability()

        #
        # Reset round data
        #
        self.stat['roundCard'] = []

    def deal_end(self, data):
        """
        Event: deal end
        """
        data_players = data['players']

        self.htapi.dbg(format(data))

        for player in data_players:
            local_player = self.players[player['playerName']]
            local_player['score_accl'] = player['gameScore']
            if player['shootingTheMoon'] == True:
                local_player['shoot_moon'] += 1

        for key in self.players.keys():
            p = self.players[key]
            # Reset deal-specific data.
            p['score'] = 0
            p['shoot'] = []
            p['expose'] = False
            p['pick'] = []

        self.stat['usedCard'] = []
        self.stat['shoot_moon_mode'] = True
        self.stat['expose_ah_mode'] = False

    def game_over(self, data):
        """
        Event: game end
        """
        self.htapi.dbg(format(data))
Exemple #4
0
class PseudoHeart(Htapi):
    """
    Create a virtual game which can play faster!
    """
    game_score_cards = [Card("QS"), Card("TC"),
                        Card("2H"), Card("3H"), Card("4H"), Card("5H"), Card("6H"), 
                        Card("7H"), Card("8H"), Card("9H"), Card("TH"), Card("JH"), 
                        Card("QH"), Card("KH"), Card("AH")]
    
    # If have all penalty cards, the player shoots the moon and win a lot.
    game_penalty_cards = [Card("QS"),
                        Card("2H"), Card("3H"), Card("4H"), Card("5H"), Card("6H"), 
                        Card("7H"), Card("8H"), Card("9H"), Card("TH"), Card("JH"), 
                        Card("QH"), Card("KH"), Card("AH")]
    
    def __init__(self, player_bots):
        if len(player_bots) != 4:
            print ("Invalid player num != 4")
            sys.exit()
    
        self.db = {}  
        self.htapi = Htapi(is_debug=True)
        self.game_heart_cards = self.htapi.get_cards_by_suit(self.htapi.get52cards(), 'H')
        
        # Save bot object into self.player_bots 
        self.player_tups = []
        id = 0
        for p in player_bots:
            player_tup = {
                # Constant data
                'bot': p, 'name': p.get_name(), 'id': id,
                # Deal data
                'hand_origin': [], 'recv3': [], 'pass3': [],
                'hand': [], 'pick': [], 'round_pick': [], 'shoot': [], 'expose': False, 'score': 0, 'shoot_moon': False,
                # Game data 
                'score_game': 0,
                # MISC data 
                'score_accl': 0, 'shoot_moon_accl': 0, 'score_negative_accl': 0,
                'winner': [0, 0, 0, 0],
                            }
            self.player_tups.append(player_tup)
            self.htapi.msg("Add new player: " + player_tup['name'])
            id += 1
        
        self.db['dealNumber'] = 0
        self.db['gameNumber'] = 0
        
    def player_tups_rotate(self, shift=1):
        """
        Shift order of the players.
        """
        for i in range(shift):
            p = self.player_tups.pop(0)
            self.player_tups.append(p)
        
    def game_next_deal(self):
        self.db['dealNumber'] += 1
        self.db['heartBreak'] = False
        self.db['expose'] = False
        self.db['unusedCards'] = self.htapi.get52cards()
        self.db['usedCards'] = []
        
        self.db['roundNumber'] = 0
        self.db['is_first_play'] = True
        
        for ptup in self.player_tups:
            ptup['pick'] = []
            
            ptup['recv3'] = []
            ptup['pass3'] = []
            ptup['hand_origin'] = []
            ptup['hand'] = []
            
            ptup['shoot'] = []
            ptup['score'] = 0
            ptup['shoot_moon'] = False
            
        # Re-arrange position to default layout
        for i in range(0, 4):
            ctup = self.player_tups[0]
            if ctup['id'] == 0:
                break
            else:
                self.player_tups_rotate(1)
        
    def game_next_round(self):
        self.db['roundNumber'] += 1
        self.db['roundCards'] = []
        
        for ptup in self.player_tups:
            ptup['round_pick'] = []
            
    def _ev_new_game(self, ptup):
        """
        Event: new game
        """
        players = []
        for ptup_this in self.player_tups:
            pdata = {
                'playerName': ptup_this['name'],
                'playerNumber': ptup_this['id'],
                'status': 0 
                     }
            players.append(pdata)            
        
        data = {
            'players': players
            }
        
        pbot = ptup['bot']
        pbot.new_game(data)

    def _ev_receive_cards(self, ptup):
            """
            Event: receive_cards
            
            data = {"players": [{"playerName": "hac", "cards": ["3S", "8S", "9S", "TS", "JS", "4H", "6H", "9H", "3D", "7D", "9D", "JD", "8C"], "dealNumber": 1, "gameNumber": 1}]}
            """
            data = {
                    'players': [
                        {
                            'playerName': ptup['name'],
                            'cards': self.htapi.clone_cards([x.toString() for x in ptup['hand']]),
                            
                            'gameNumber': self.db['gameNumber'],
                            'dealNumber': self.db['dealNumber'],
                        },
                        {
                            'playerName': 'self',
                            'cards': self.htapi.clone_cards([x.toString() for x in ptup['hand']]),
                            
                            'gameNumber': self.db['gameNumber'],
                            'dealNumber': self.db['dealNumber'],
                        },
                        # TBD: Add other players' data? Not necessary, I guess.
                    ]
                }

            # Call player method.            
            pbot = ptup['bot']
            pbot.receive_cards(data)
    
    def _ev_deal_end(self, ptup):
        """
        End of a deal
        """
        data = {}
        data['dealNumber'] = self.db['dealNumber']
        data['roundNumber'] = self.db['roundNumber']
        data['gameNumber'] = self.db['gameNumber']
        
        data['players'] = []
        data_players = data['players']
        
        for ptup_this in self.player_tups:
            # Setup this player data. 
            pld = {}
            
            pld['playerNumber'] = ptup_this['id']
            pld['playerName'] = ptup_this['name']
            pld['dealScore'] = ptup_this['score']
            pld['gameScore'] = ptup_this['score_game']
            
            pld['scoreCards'] = self.htapi.clone_cards([x.toString() for x in ptup_this['pick']])
            pld['initialCards'] = self.htapi.clone_cards([x.toString() for x in ptup_this['hand_origin']]) 
            pld['receivedCards'] = self.htapi.clone_cards([x.toString() for x in ptup_this['recv3']])
            pld['pickedCards'] = self.htapi.clone_cards([x.toString() for x in ptup_this['pass3']])
            pld['shootingTheMoon'] = ptup_this['shoot_moon']
            
            # Add data into list
            data_players.append(pld)
        
        pbot = ptup['bot']
        pbot.deal_end(data)

    def _ev_pass3cards(self, ptup):
        """
        Output: picked 3 cards
        """
        data = {}
        data['self'] = {
            'cards': self.htapi.clone_cards([x.toString() for x in ptup['hand']])
            }
        
        pbot = ptup['bot']
        picked = pbot.pass_cards(data)
        
        if picked == None or len(picked) != 3:
            self.htapi.errmsg("Invalid picked num of bot: " + ptup['name'])
        
        # Convert ['XX', 'OO', 'AA'] into Card list
        picked = [Card(c) for c in picked]
        
        # Verify if there's any duplicated picked card to avoid buggy bot.
        for c in picked:
            if picked.count(c) != 1:
                self.errmsg("Player: " + ptup['name'] + " tries to pick invalid cards: " + format(picked))
            
        # Check picked cards are really in list to keep away from stupid bot.
        found_cards = self.htapi.find_cards(ptup['hand'], picked)
        if found_cards == None or len(found_cards) != 3:
            self.htapi.errmsg("Player: " + ptup['name'] + " attemps to pick invalid cards")
        
        return picked

    def _ev_receive_opponent_cards(self, ptup, picked, received):
        """
        Pass 3 cards to the player
        """
        data = {
                'players': [
                    {
                        'playerName': ptup['name'],
                        'cards': self.htapi.clone_cards([x.toString() for x in ptup['hand']]),
                        'pickedCards': self.htapi.clone_cards([x.toString() for x in picked]),
                        'receivedCards': self.htapi.clone_cards([x.toString() for x in received]),
                        
                        'gameNumber': self.db['gameNumber'],
                        'dealNumber': self.db['dealNumber'],
                    },
                    {
                        'playerName': 'self',
                        'cards': self.htapi.clone_cards([x.toString() for x in ptup['hand']]),
                        'pickedCards': self.htapi.clone_cards([x.toString() for x in picked]),
                        'receivedCards': self.htapi.clone_cards([x.toString() for x in received]),
                        
                        'gameNumber': self.db['gameNumber'],
                        'dealNumber': self.db['dealNumber'],
                    },
                    # TBD: Add other players' data? Not necessary, I guess.
                ]
            }
        
        pbot = ptup['bot']
        pbot.receive_opponent_cards(data)
        
    def _ev_expose_ah(self, ptup):
        """
        Ask the player if he wants to expose AH.
        """
        data = {
            'dealNumber': self.db['dealNumber'],
            'cards': ['AH'] 
            }
                                      
        pbot = ptup['bot']
        card = pbot.expose_my_cards(data)
        if card == None:
            # The player rejects to expose AH
            return False
        
        return True
    
    def _ev_turn_end(self, ptup, turn_ptup, card2shoot):
        data_players = []
        
        data_player = {}
        data_player['playerName'] = turn_ptup['name']
        data_player['cards'] = self.htapi.clone_cards([card2shoot.toString()]) 
        
        data_players.append(data_player)

        data = {
            'turnCard': card2shoot.toString(),
            'turnPlayer': turn_ptup['name'],
            'players': data_players,
            'serverRandom': str(False)
            }
        
        pbot = ptup['bot']
        pbot.turn_end(data)

    def _ev_round_end(self, ptup, next_lead_ptup):
        """
        Inform the user the end of a round
        """
        data = {'roundPlayers': [], 'roundPlayer': next_lead_ptup['name'], 'players': []}
        
        data_round_players = data['roundPlayers']
        for ptup_this in self.player_tups:
            data_round_players.append(ptup_this['name'])
        
        data_players = data['players']    
        for ptup_this in self.player_tups:
            dp = {}
            
            dp['playerNumber'] = ptup_this['id']
            dp['playerName'] = ptup_this['name']
            dp['gameScore'] = ptup_this['score_game']
            dp['dealScore'] = ptup_this['score']
            dp['shootingTheMoon'] = ptup_this['shoot_moon']
            dp['roundCard'] = ptup_this['shoot'][-1].toString()
            
            data_players.append(dp)
        
        pbot = ptup['bot']
        pbot.round_end(data)  
        
    def _ev_expose_ah_end(self, ptup):
        """
        Inform the player the expose result.
        """
        data = {'players': []}
        data_players = data['players']
        
        for ptup_this in self.player_tups:
            data_this = {}
            
            data_this['playerNumber'] = ptup_this['id']
            data_this['playerName'] = ptup_this['name']
            
            if ptup_this['expose'] == True:
                data_this['exposedCards'] = [Card('AH').toString()]
            else:
                data_this['exposedCards'] = []                
                
            data_players.append(data_this)
            
            if ptup_this['name'] == ptup['name']:
                # Add a 'self'!
                another_data_this = dict(data_this)
                another_data_this['playerName'] = 'self'
                data['self'] = another_data_this

        pbot = ptup['bot']
        pbot.expose_cards_end(data)
        
    def _ev_game_end(self, ptup):
        """
        Inform the player the game result.
        """
        data = {'players': []}
        data_players = data['players']
        
        for ptup_this in self.player_tups:
            data_this = {}
            
            data_this['playerName'] = ptup_this['name']
            data_this['gameScore'] = ptup_this['score_accl']
                
            data_players.append(data_this)
            
        pbot = ptup['bot']
        pbot.game_over(data)
        
    def game_new_deal_manual_deliver(self):
        """
        Deliever fixed 13 cards to each player.
        
        ['7C', '2C', '2D', 'JS', 'QD', '4C', 'QC', 'QS', '3C', 'TD', '2H', '7D', 'KS', 
        '8C', 'JD', '6D', '3D', 'KC', 'AS', '9D', '8S', 'AD', 'TS', '7H', '5D', '4H', 
        '8H', '3H', 'AC', '8D', 'KH', 'KD', '4S', '9C', '2S', '4D', 'JH', '5C', 'JC', 
        'AH', '9H', 'TH', '9S', '6S', '6H', '7S', 'QH', 'TC', '3S', '6C', '5H', '5S']
        """
        card2deliver = [
            ['8C', 'JD', '6D', '3D', 'KC', 'AS', '9D', '8S', 'AH', 'TS', 'JH', '5D', 'QH'],
            ['7C', '2C', '2D', 'JS', 'QD', '4C', 'QC', 'QS', '3C', 'TD', '2H', '7D', 'KS'],
            ['8H', '3H', 'AC', '8D', 'AD', 'KD', '4S', '9C', '2S', '4D', '7H', '5C', 'JC'],
            ['KH', '9H', 'TH', '9S', '6S', '6H', '7S', '4H', 'TC', '3S', '6C', '5H', '5S']            
            ]
        
        # Avoid stupid assignment error. Check if there're 52 different cards
        tgt = []
        card52 = self.htapi.get52cards()
        for card13 in card2deliver:
            card13 = [Card(x) for x in card13]
            tgt += card13
        
        if len(self.htapi.find_cards(tgt, card52)) != 52:
            self.htapi.errmsg("Invalid card2deliver table.")
                
        for ptup in self.player_tups:
            if ptup['id'] >= 4: 
                self.htapi.errmsg("Cannot allow player id >= 4")
                
            # Assume player id is from 0 ~ 3, so assign the cards directly.
            ptup['hand'] = [Card(x) for x in card2deliver[ptup['id']]]
            ptup['hand_origin'] = [Card(x) for x in card2deliver[ptup['id']]]
                        
    def game_new_deal_random_deliver(self):   
        unused_card = self.htapi.get52cards()
        unused_card = self.htapi.shuffle_cards(unused_card)
        
        if len(self.player_tups) != 4:
            self.htapi.errmsg("Invalid player bot num")
        
        # Save 13 cards
        for ptup in self.player_tups:
            picked = []
            for i in range(13):
                picked.append(unused_card.pop())
            
            # The player get 13 cards. Generate event: 'receive_cards'
            picked = self.htapi.arrange_cards(picked)
            ptup['hand'] = picked
            ptup['hand_origin'] = self.htapi.clone_cards(picked)

    def game_new_deal(self):
        """
        Deliver 13 cards to each player
        """             
        manual_deliver = False
        if manual_deliver == True:
            self.htapi.msg(" *** WARNING: You are running in manual-deliver mode")
            self.game_new_deal_manual_deliver()
        else:
            self.game_new_deal_random_deliver()

        # Inform every player            
        for ptup in self.player_tups:
            self._ev_receive_cards(ptup)
        
    def game_pass3cards(self):
        """
        Each player has to pick 3 cards and pass to one opponent.
        """
        
        picked = {}
        for ptup in self.player_tups:
            picked[ptup['name']] = self._ev_pass3cards(ptup)
            
            # Remove the picked 3 cards!
            removed = self.htapi.remove_cards(ptup['hand'], picked[ptup['name']])
            if len(removed) != 3:
                self.htapi.errmsg("Cannot pop picked 3 cards of user: "******"""
        Get position of a player. (position=1 ~ 4)
        
        position = 1 means he is the lead playerr.
        """
        position = 1
        for p in self.player_tups:
            if p['name'] == ptup['name']:
                return position
            
            position += 1
            
        self.errmsg("Cannot find player position")
        return None
            
    def _get_candidates(self, ptup):
        """
        Help players to find candidate cards.
        """
        
        if self.db['is_first_play'] == True:
            self.db['is_first_play'] = False
            candidates = [Card('2C')]
            return candidates
        
        player_pos = self._get_player_pos(ptup)

        if player_pos == 1:           
            if self.db['heartBreak'] == True:
                # Can select any card after heart break.
                candidates = ptup['hand']
                return candidates       
            else:
                # Not heart break. Cannot pick heart unless there's no other suit.
                candidates = self.htapi.get_cards_by_suits(ptup['hand'], ['S', 'D', 'C'])
                if len(candidates) == 0:
                    # Only heart suit left. Allow heart break, of course.
                    candidates = ptup['hand']
                
                return candidates
        else:
            # Follow the leading card unless there's no the same suit.
            round_cards = self.db['roundCards']
            lead_card = round_cards[0]
            
            candidates = self.htapi.get_cards_by_suit(ptup['hand'], lead_card.get_suit())
            if len(candidates) > 0:
                return candidates
            else:
                # No card in the same suit. Can pick any card.
                candidates = ptup['hand']
                return candidates
            
        self.errmsg("Cannot get candidate cards")
        return None
    
    def _ev_pick_card(self, ptup):
        """
        Event: pick_card - Ask the player to shoot a card.
        """
        # round cards
        round_cards = self.db['roundCards']
        
        # Candidate cards
        candidates = self._get_candidates(ptup)
        candidates = self.htapi.arrange_cards(candidates)
        candidates = self.htapi.clone_cards(candidates)
        
        # Round players
        round_players = []
        for ptup_this in self.player_tups:
            rp = {}
            rp = ptup_this['name']
            round_players.append(rp)
        
        data = {'self': {
                        'cards': [x.toString() for x in ptup['hand']],
                        'candidateCards': [x.toString() for x in candidates],
                        
                        'gameNumber': self.db['gameNumber'],
                        'dealNumber': self.db['dealNumber'],
                        'roundCard': ''
                    },
            'roundPlayers': round_players
        }
        
        pbot = ptup['bot']
        card2shoot = pbot.pick_card(data)
        card2shoot = Card(card2shoot)
        
        return card2shoot    
        
    def game_shoot1card(self, ptup):
        """
        Make the player shoot 1 card!
        """

        card2shoot = self._ev_pick_card(ptup)
        if self.htapi.find_card(ptup['hand'], card2shoot) == None:
            self.errmsg("Cannot shoot un-existed card at player: " + ptup['name'])
            
        # Shoot the card.
        removed = self.htapi.remove_card(ptup['hand'], card2shoot)
        if removed == None:
            self.errmsg("(BUG) Cannot remove player card: " + card2shoot.toString() + ", " + ptup['name']) 
        
        ptup['shoot'].append(card2shoot)
           
        self.db['roundCards'].append(card2shoot)
        self.db['usedCards'].append(card2shoot)
        self.db['unusedCards'].remove(card2shoot)

        if card2shoot.get_suit() == 'H':
            self.db['heartBreak'] = True
            
        for ptup_this in self.player_tups:
            self._ev_turn_end(ptup_this, ptup, card2shoot)
            
    def _recalculate_round_score(self, ptup):
        """
        Calculate player's score in current round.
        """
        score = 0
        picked_cards = ptup['pick']
 
        my_score_cards = self.htapi.find_cards(picked_cards, self.game_score_cards)
        my_heart_cards = self.htapi.find_cards(picked_cards, self.game_heart_cards)
        my_penalty_cards = self.htapi.find_cards(picked_cards, self.game_penalty_cards)
        
        if self.db['expose'] == True:
            score = len(my_heart_cards) * 2 * (-1)
        else:
            score = len(my_heart_cards) * (-1)
        
        if self.htapi.find_card(my_score_cards, Card('QS')) != None:
            score += -13
            
        if self.htapi.find_card(my_score_cards, Card('TC')) != None:
            score *= 2
            
        if len(self.htapi.find_cards(my_score_cards, my_penalty_cards)) == len(self.game_penalty_cards):
            # Shoot the moon. Score becomes postive! Score x 4! 
            score *= -1
            score *= 4
            ptup['shoot_moon'] = True
                
        ptup['score'] = score
        
    def game_round_end(self, round_num):
        """
        Decide round player, round player = the player "win" the round.
        """
        round_cards = self.db['roundCards']
        if len(round_cards) != 4:
            self.htapi.errmsg("Invalid cards of this round.")

        lead_card = round_cards[0]
        lead_ptup = self.player_tups[0]
        
        lead_card_suit = lead_card.get_suit_num()
        lead_card_rank = lead_card.get_rank_num()

        idx = 0
        rotate = 0
        next_lead_ptup = lead_ptup
        highest_rank = lead_card_rank
        for ptup in self.player_tups:
            if idx == 0:
                # This is lead player. Skip
                idx += 1
                continue         
            
            shoot_card = round_cards[idx]
            shoot_card_suit = shoot_card.get_suit_num()
            shoot_card_rank = shoot_card.get_rank_num()
            
            if shoot_card_suit == lead_card_suit:
                if shoot_card_rank > highest_rank:
                    next_lead_ptup = ptup
                    rotate = idx
                    highest_rank = shoot_card_rank
                    
            idx += 1
        
        # The cards belong to the next lead player. Give him the cards.
        next_lead_ptup['pick'] += round_cards
        next_lead_ptup['round_pick'] = round_cards
        
        """
        Calculate scores of this round and store the result.
        """
        for ptup in self.player_tups:
            self._recalculate_round_score(ptup)
             
        
        """
        Inform the user an event
        """
        for ptup in self.player_tups:
            self._ev_round_end(ptup, next_lead_ptup)
        
        """
        Rotate ptup position for next round.
        """
        if rotate > 0:
            self.player_tups_rotate(rotate)
            
    def show_score(self):
        for ptup in self.player_tups:
            self.htapi.dbg(
                "Player: " + ptup['name'] + 
                ", score: " + str(ptup['score']) + 
                ", score_accl: " + str(ptup['score_accl']) +
                ", score_negative_accl:" + str(ptup['score_negative_accl']) + 
                ", shoot_moon_accl: " + str(ptup['shoot_moon_accl']) +
                ", winner: " + str(ptup['winner'])
                )
       
    def game_round(self, round_num):
        """
        Play 1 round = 4 turn
        """
        self.htapi.msg("Round: " + str(self.db['roundNumber']) + 
                       ", Deal: " + str(self.db['dealNumber']) + 
                       ", Game: " + str(self.db['gameNumber']))
        
        for ptup in self.player_tups:
            self.game_shoot1card(ptup)
            
        self.game_round_end(round_num)

    def game_finish_deal(self):
        """
        The end of a single deal.
        """
        score_list = []
        for ptup in self.player_tups:
             
            if ptup['score'] < 0:
                ptup['score_negative_accl'] += ptup['score']
                
            ptup['score_accl'] += ptup['score']
            ptup['score_game'] += ptup['score']
            if ptup['shoot_moon'] == True:
                ptup['shoot_moon_accl'] += 1
            
            score_list.append({'name': ptup['name'], 'score': ptup['score'], 'ptup': ptup})
        
        score_list_sorted = sorted(score_list, key=lambda v: v['score'])

        winner = score_list_sorted[3]['ptup']
        winner['winner'][0] += 1 # winner
        winner = score_list_sorted[2]['ptup']
        winner['winner'][1] += 1 # 2nd winner
        winner = score_list_sorted[1]['ptup']
        winner['winner'][2] += 1 # 3rd...
        winner = score_list_sorted[0]['ptup']
        winner['winner'][3] += 1 # loser...
            
        # Inform players
        for ptup in self.player_tups:
            self._ev_deal_end(ptup)
    
    def game_expose_ah(self):
        for ptup in self.player_tups:
            hand_cards = ptup['hand']
            
            card_ah = self.htapi.find_card(hand_cards, Card('AH'))
            if card_ah != None:
                if self._ev_expose_ah(ptup):
                    # The player decides to expose, all heart score will double 
                    ptup['expose'] = True
                    self.db['expose'] = True   
                break
        
        # Inform players expose end.
        for ptup in self.player_tups:
            self._ev_expose_ah_end(ptup)
    
    def game_decide_lead_player(self):
        """
        Decide who must start the deal, i.e. the player has 2C.
        """        
        for i in range(0, 4):
            ptup = self.player_tups[0]
            
            card2c = self.htapi.find_card(ptup['hand'], Card('2C'))
            if card2c != None:
                break
            else:
                self.player_tups_rotate(1)
    
    def game_play1deal(self):
        """
        event: new_deal
        """
        self.game_new_deal()
        
        """
        event: receive_opponent_cards & pass_cards
        """
        self.game_pass3cards()
        
        self.game_decide_lead_player()
        
        """
        event: Ask if the player wants to expose AH
        """
        self.game_expose_ah()
        
        """
        event: your_turn & turn_end & expose_cards & expose_cards_end && round_end
        """
        for round in range(1, 14):
            # Round 1 to 13
            self.game_next_round()
            self.game_round(round)
            
        self.game_finish_deal()
        
    def game_next_game(self):
        self.db['gameNumber'] += 1
        self.db['dealNumber'] = 0
                
        for ptup in self.player_tups:
            # Reset game-specific data
            ptup['score_game'] = 0
            
    def game_over(self):
        """
        playerName, gameScore
        """
        for ptup in self.player_tups:
            self._ev_game_end(ptup)
            
    def game_single(self):
        """
        There're 16 deals in a game
        """
        self.game_next_game()
        
        # Inform the players there's a new game to start.
        for ptup in self.player_tups:
            self._ev_new_game(ptup)
        
        DEAL_PER_GAME = 16
        for deal in range(1, DEAL_PER_GAME + 1):
            self.game_next_deal()
            self.game_play1deal()
            
        self.game_over()
        
        self.show_score()

        random.shuffle(self.player_tups)

    def game_loop(self, loop_max=1):
        for loop in range(1, loop_max + 1):
            self.game_single()
class HacBotV(PokerBot, Htapi):
    """
    Anti-Score Mode (AS)
    Shoot-Moon Mode (SM)
    ...Player: HacBotV, score: 0, score_accl: 24516, score_negative_accl:-56136, shoot_moon_accl: 273, winner: [2234, 1790, 1280, 1096]
    """
    SM_THOLD_PASS3 = 8.0
    SM_THOLD_PICK = 9.5
    AS_THOLD_PASS3 = 7.0

    def __init__(self, name, is_debug=False):
        super(HacBotV, self).__init__(name)

        self.htapi = Htapi(is_debug=is_debug)

        self.players = {}
        self.stat = {}
        self.stat['roundCard'] = []
        self.stat['usedCard'] = []
        self.stat['nextPlayers'] = []
        self.stat['roundPlayers'] = []

        self.big_rank_cards = [
            Card('AS'),
            Card('KS'),
            Card('JS'),
            Card('TS'),
            Card('AH'),
            Card('KH'),
            Card('QH'),
            Card('JH'),
            Card('TH'),
            Card('AD'),
            Card('KD'),
            Card('QD'),
            Card('JD'),
            Card('TD'),
            Card('AC'),
            Card('KC'),
            Card('QC'),
            Card('JC'),
            Card('TC'),
        ]

        self.score_cards = [
            Card("QS"),
            Card("TC"),
            Card("2H"),
            Card("3H"),
            Card("4H"),
            Card("5H"),
            Card("6H"),
            Card("7H"),
            Card("8H"),
            Card("9H"),
            Card("TH"),
            Card("JH"),
            Card("QH"),
            Card("KH"),
            Card("AH")
        ]

        self.plz_rebuild_players = True

        self.stat['expose_ah_mode'] = False
        self.stat['sm_mode'] = True
        self.stat['sm_mode_started'] = False

        self.stat['pass3_as_ability'] = 0
        self.stat['pass3_as_ability_history'] = []
        self.stat['pass3_sm_ability'] = 0
        self.stat['pass3_sm_ability_history'] = []

    def _rebuild_players(self, data):
        """
        Configure player table by input data.
        """
        self.htapi.dbg("Rebuild players: ", format(data))

        data_players = data['players']

        if len(data_players) != 4:
            self.errmsg("Invalid player number " + str(len(data_players)) +
                        " in server msg")

        self.players = {}

        for dp in data_players:
            self.players[dp['playerName']] = {
                'playerName': dp['playerName'],
                'score_accl': 0,
                'score': 0,
                'sm': 0,
                'expose': False,
                'shoot': [],
                'suit_leak': [],
                'pick': [],
            }

    def new_game(self, data):
        """
        Event: The start of a new game.
        """
        if self.plz_rebuild_players == True:
            self._rebuild_players(data)
            self.plz_rebuild_players = False

    def receive_cards(self, data):
        """
        Event: Receive my 13 cards.
        """
        self.stat['hand'] = self.get_cards(data)
        self.stat['hand'] = self.htapi.clone_cards(self.stat['hand'])

    def _calc_hand_cards_num(self, my_hand_cards, reverse=False):
        card_num_stat = {'S': 0, 'H': 0, 'D': 0, 'C': 0}
        for c in my_hand_cards:
            card_num_stat[c.get_suit()] += 1

        card_num_stat_sorted = sorted(card_num_stat.iteritems(),
                                      key=lambda (k, v): (v, k),
                                      reverse=reverse)
        return card_num_stat_sorted

    def _select_card2pass_sm_mode(self, my_hand_cards):
        """
        Pop out 1 card to pass
        
        Reserve cards in long suit.
        Remove small cards in shortage suit.
        """
        card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

        # Remove small cards in shortage suit.
        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            if suit == 'H':
                continue

            same_suit_cards = self.htapi.get_cards_by_suit(my_hand_cards, suit)
            smaller_card = self.htapi.pick_smaller_card(same_suit_cards,
                                                        [Card('9S')],
                                                        auto_choose_big=False)

            if smaller_card != None:
                return self.htapi.remove_card(my_hand_cards, smaller_card)

        # Remove suit
        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            if suit == 'H':
                continue

            same_suit_cards = self.htapi.get_cards_by_suit(my_hand_cards, suit)
            small_card = self.htapi.pick_small_card(same_suit_cards)

            return self.htapi.remove_card(my_hand_cards, small_card)

        # Remove suit, in case I have 13 hearts... A.A
        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            same_suit_cards = self.htapi.get_cards_by_suit(my_hand_cards, suit)
            small_card = self.htapi.pick_small_card(same_suit_cards)

            return self.htapi.remove_card(my_hand_cards, small_card)

        self.htapi.errmsg("BUG")

    def _select_card2pass(self, my_hand_cards):
        """
        Pop out 1 card to pass. Anti-score mode.
        
        - Remove big rank card from shortage suit
        - Remove big rank card in long suit
        """

        card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)
        #         card_num_stat_sorted_dict = dict(card_num_stat_sorted)

        if self.htapi.find_card(my_hand_cards, Card('QS')) == None:
            card = self.htapi.remove_card(my_hand_cards, Card('AS'))
            if card != None:
                return card

            # Spade in shortage. KS and AS will be dangerous.
            card = self.htapi.remove_card(my_hand_cards, Card('KS'))
            if card != None:
                return card

        # Remove big rank card from shortage suit
        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            same_suit_cards = self.htapi.get_cards_by_suit(my_hand_cards, suit)
            big_card = self.htapi.pick_big_card(same_suit_cards)

            if big_card.get_rank_num() > Card('8S').get_rank_num():
                return self.htapi.remove_card(my_hand_cards, big_card)

        # Remove suit
        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            same_suit_cards = self.htapi.get_cards_by_suit(my_hand_cards, suit)
            big_card = self.htapi.pick_big_card(same_suit_cards)

            return self.htapi.remove_card(my_hand_cards, big_card)

        self.htapi.errmsg("BUG")

    def _get_used_cards(self):
        return self.stat['usedCard']

    def _get_used_cards_by_suits(self, suits=[]):
        used_cards = self.stat['usedCard']
        output = self.htapi.get_cards_by_suits(used_cards, suits)
        return output

    def _get_unused_cards_by_suits(self, my_hand_cards, suits=[]):
        """
        Get unused cards of other opponents so that I can know how many cards are left..
        """

        all52cards = self.htapi.get52cards()
        output = all52cards

        self.htapi.remove_cards(output, self.stat['usedCard'])
        self.htapi.remove_cards(output, my_hand_cards)

        if len(suits) == 0:
            # Do not need to pick a specific suit
            pass
        else:
            # Pick unused cards of target suits
            output = self.htapi.get_cards_by_suits(output, suits)

        return output

    def _get_unused_cards(self, my_hand_cards):
        return self._get_unused_cards_by_suits(my_hand_cards, suits=[])

    def _get_round_cards(self):
        round_cards = self.stat['roundCard']
        return round_cards

    def _get_avail_cards(self):
        my_avail_cards = self.stat['avail']
        return my_avail_cards

    def _get_hand_cards(self):
        my_hand_cards = self.stat['hand']
        return my_hand_cards

    def _calc_as_point(self, card, oppo_unused_cards):
        """
        Calculate the power of this card to avoid taking grades.
        
        Output: 1.0 - This card won't take the trick. (Won't be next leader)
        Output: 0.0 - This card will take the trick. Possibly a big card.
        """

        oppo_unused_same_suit_cards = self.htapi.get_cards_by_suit(
            oppo_unused_cards, card.get_suit())
        if len(oppo_unused_same_suit_cards) == 0:
            # Opponent does not have same suit...
            #             oppo_no_score_cards = self.htapi.find_no_score_cards(oppo_unused_cards)
            #             return len(oppo_no_score_cards) / float(len(oppo_unused_cards))
            return 0.0

        lose_cnt = 0
        for c in oppo_unused_same_suit_cards:
            if card.get_rank_num() < c.get_rank_num():
                lose_cnt += 1

        point = lose_cnt / float(len(oppo_unused_same_suit_cards))
        return point

    def _calc_sm_point(self, card, oppo_unused_cards):
        """
        Calculate the power of this card to win others.
        """
        oppo_unused_same_suit_cards = self.htapi.get_cards_by_suit(
            oppo_unused_cards, card.get_suit())
        if len(oppo_unused_same_suit_cards) == 0:
            # No same suit at opponent.
            return 1.0

        win_cnt = 0
        for c in oppo_unused_same_suit_cards:
            if card.get_rank_num() > c.get_rank_num():
                win_cnt += 1

        return win_cnt / float(len(oppo_unused_same_suit_cards))

    def _pass_cards_calc_sm_ability(self, data=None):
        """
        Detect if I can shoot the moon.
        """
        my_hand_cards = self._get_hand_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        if len(oppo_unused_cards) != (52 - 13) or len(my_hand_cards) != 13:
            self.htapi.errmsg("BUG")

        return self._pick_card_calc_sm_ability(data=None)

    def _pass_cards_calc_as_ability(self, data=None):
        """
        Detect if I can anti-score
        """
        return self._pick_card_calc_as_ability(data=None)

    def _pass_cards_sm_mode(self, data):
        """
        Pick 3 cards which can help myself to shoot moon.
        """
        my_hand_cards = self._get_hand_cards()

        output = []
        for i in range(3):
            card = self._select_card2pass_sm_mode(my_hand_cards)
            if card == None:
                self.htapi.errmsg("Cannot pick a card to pass")
            output.append(card.toString())

        self.htapi.dbg(self.get_name() + " pass 3 cards: " + format(output))
        return output

    def _pass_cards_as_mode(self, data):
        """
        Pick 3 cards which can avoid taking score
        """
        my_hand_cards = self._get_hand_cards()

        output = []
        for i in range(3):
            card = self._select_card2pass(my_hand_cards)
            if card == None:
                self.htapi.errmsg("Cannot pick a card to pass")
            output.append(card.toString())

        self.htapi.dbg(self.get_name() + " pass 3 cards: " + format(output))
        return output

    def pass_cards(self, data):
        """
        Event: Pick 3 cards to pass to others
        """
        self.stat['hand'] = self.htapi.clone_cards(
            [Card(x) for x in data['self']['cards']])
        self.stat['hand'] = self.htapi.arrange_cards(self.stat['hand'])

        self.htapi.dbg("Select 3 cards from: " + format(self.stat['hand']))

        sm_ability = self._pass_cards_calc_sm_ability()
        as_ability = self._pass_cards_calc_as_ability()
        self.stat['pass3_sm_ability'] = sm_ability
        self.stat['pass3_as_ability'] = as_ability
        self.htapi.dbg("Ability stat. sm: ", format(sm_ability), ", as: ",
                       format(self.stat['pass3_as_ability']))
        if sm_ability > self.SM_THOLD_PASS3:
            self.htapi.dbg("shoot moon mode pass3: " + str(sm_ability))
            return self._pass_cards_sm_mode(data)
        else:
            self.htapi.dbg("anti score mode pass3")
            return self._pass_cards_as_mode(data)

    def receive_opponent_cards(self, data):
        """
        Event: Recv 3 cards from one opponent
        """
        pass

    def ______pass3(self):
        pass

    def expose_my_cards(self, data):
        """
        Event: The server asks me to expose AH...
        """
        output = []
        candidates = data['cards']
        if candidates == None or len(candidates) == 0:
            return output

        candidates = [Card(x) for x in candidates]

        if self.htapi.find_card(candidates, Card('AH')) == None:
            return output

        if self.stat['pass3_sm_ability'] > self.SM_THOLD_PASS3:
            output = ['AH']
            return output

        if self.stat['pass3_as_ability'] > self.AS_THOLD_PASS3:
            # I have confidence not taking score...
            output = ['AH']
            return output

        return output

    def expose_cards_end(self, data):
        """
        Event: Check if somebody expose AH. Damn. 
        """
        data_players = data['players']

        for dp in data_players:
            local_player = self.players[dp['playerName']]

            if len(dp['exposedCards']) > 0:
                # This guy exposed AH.
                local_player['expose'] = True
                self.stat['expose_ah_mode'] = True
            else:
                local_player['expose'] = False

    def ______expose_card(self):
        pass

    def _do_i_have_lead_suit(self):
        """
        Cannot be called at lead play!!
        """
        round_cards = self._get_round_cards()
        if len(round_cards) == 0:
            self.errmsg("BUG")

        lead_card = round_cards[0]

        my_avail_cards = self._get_avail_cards()

        if len(
                self.htapi.get_cards_by_suit(my_avail_cards,
                                             lead_card.get_suit())) == 0:
            return False

        return True

    def _pick_card_calc_sm_ability(self, data=None):
        """
        Detect if I can shoot the moon.
        """
        my_hand_cards = self._get_hand_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        my_sm_point = 0.0
        for c in my_hand_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)
            if this_sm_point >= 1.0:
                # Power card
                my_sm_point += this_sm_point
            else:
                my_sm_point += this_sm_point

        my_sm_point *= (13 / float(len(my_hand_cards)))
        my_sm_point = round(my_sm_point, 3)
        print("my_hand_cards: ", my_hand_cards,
              ", sm ability -> " + format(my_sm_point))
        return my_sm_point

    def _pick_card_calc_as_ability(self, data=None):
        """
        Detect if I can anti-score
        """
        my_hand_cards = self._get_hand_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        my_as_point = 0.0
        for c in my_hand_cards:
            this_as_point = self._calc_as_point(c, oppo_unused_cards)
            if this_as_point >= 1.0:
                my_as_point += this_as_point
            else:
                my_as_point += this_as_point

        my_as_point *= (13 / float(len(my_hand_cards)))
        my_as_point = round(my_as_point, 3)
        print("my_hand_cards: ", my_hand_cards,
              ", as ability -> " + format(my_as_point))
        return my_as_point

    def _pick_card_should_i_sm(self, data):
        """
        Predict my ability to shoot moon.
        """
        sm_ability = self._pick_card_calc_sm_ability(data=None)
        as_ability = self._pick_card_calc_as_ability(data=None)

        if self.stat['sm_mode'] == False:
            return False

        if self.stat['sm_mode_started'] == True:
            # Already starting to sm. Don't stop.
            return True

        my_hand_cards = self._get_hand_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)
        my_heart_cards = self.htapi.get_cards_by_suit(my_hand_cards, 'H')

        power_heart_num = 0
        for c in my_hand_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

            if this_sm_point >= 1.0 and c.get_suit() == 'H':
                power_heart_num += 1

        if power_heart_num == 0 and len(my_heart_cards) > 0:
            sm_ability -= 1.0

        if sm_ability > self.SM_THOLD_PICK:
            self.stat['sm_mode_started'] = True
            return True

        return False

    def _pick_card_sm_mode_leadplay(self, data):
        """
        I am the leader. If I have sm ability. Eat! Eat! Eat you to the hole!
        """
        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        for c in my_avail_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

            if this_sm_point >= 1.0 and c.get_suit() == 'S':
                self.htapi.dbg("This card will win..." + format(c))
                return c

        for c in my_avail_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

            if this_sm_point >= 1.0:
                self.htapi.dbg("This card will win..." + format(c))
                return c

        for c in my_avail_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

            if this_sm_point >= 1.0 and c.get_suit() == 'H':
                self.htapi.dbg("This card will win..." + format(c))
                return c

        # Pick short suit candidates
        self.htapi.dbg("Low strength stage... I am weak :-(.")

        max_sm_point = 0
        candidates = []
        for c in my_avail_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

            if len(candidates) == 0:
                candidates = [c]
                max_sm_point = this_sm_point
            elif this_sm_point > max_sm_point:
                candidates = [c]
                max_sm_point = this_sm_point
            elif this_sm_point < max_sm_point:
                pass
            else:
                candidates.append(c)

        card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            prefer_candidates = self.htapi.get_cards_by_suit(
                my_avail_cards, suit)
            if len(prefer_candidates) > 0:
                prefer_candidates = self.htapi.arrange_cards(prefer_candidates)
                return prefer_candidates[-1]

        self.htapi.errmsg("BUG")

    def _pick_card_sm_mode_freeplay(self, data):
        """
        Do not have the same suit to follow. So I am free to shoot.
        """
        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        my_noscore_cards = self.htapi.find_no_score_cards(my_avail_cards)

        if len(my_noscore_cards) == 0:
            # Too bad... I have to give up sm. Turn to as mode.
            return self._pick_card_as_mode(data)

        min_sm_cards = []
        min_sm_point = None
        for c in my_noscore_cards:
            this_sm_point = self._calc_sm_point(c, oppo_unused_cards)
            if min_sm_point == None:
                min_sm_point = this_sm_point
                min_sm_cards = [c]
            elif this_sm_point < min_sm_point:
                min_sm_point = this_sm_point
                min_sm_cards = [c]
            elif this_sm_point > min_sm_point:
                pass
            else:
                min_sm_cards.append(c)

        card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            prefer_candidates = self.htapi.get_cards_by_suit(
                min_sm_cards, suit)
            if len(prefer_candidates) > 0:
                prefer_candidates = self.htapi.arrange_cards(prefer_candidates)
                return prefer_candidates[-1]  # TBD: Use the big rank one!?

        self.htapi.errmsg("BUG")

    def _pick_card_sm_mode_midplay(self, data):
        """
        I am the mid player. 
        
        Try to win...
        """
        round_cards = self._get_round_cards()
        lead_card = round_cards[0]
        filtered_round_cards = self.htapi.get_cards_by_suit(
            round_cards, lead_card.get_suit())
        round_card_score = self.htapi.calc_score(round_cards)

        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        if self._do_i_have_lead_suit() == True:
            # Try to win this round.
            if self.htapi.pick_bigger_card(my_avail_cards,
                                           filtered_round_cards) == None:
                # ...I don't have bigger card to win.
                self.htapi.dbg("Give up sm... Oops")
                return self._pick_card_as_mode(data)
            else:
                # I have chance to win.
                king_cards = []
                max_sm_point = 0
                my_avail_cards = self.htapi.arrange_cards(my_avail_cards)
                for c in my_avail_cards:
                    this_sm_point = self._calc_sm_point(c, oppo_unused_cards)

                    if this_sm_point > max_sm_point:
                        max_sm_point = this_sm_point
                        king_cards = [c]
                    elif this_sm_point < max_sm_point:
                        pass
                    else:
                        king_cards.append(c)

                print(" + king cards to win next players are: ",
                      format(king_cards))

                if len(king_cards) == 0:
                    self.htapi.errmsg("BUG")

                return king_cards[0]
        else:
            if round_card_score > 0:
                # Cannot win this round. Have to give up shoot moon.
                self.htapi.dbg("Give up sm... Oops")
                return self._pick_card_as_mode(data)
            else:
                return self._pick_card_sm_mode_freeplay(data)

    def _pick_card_sm_mode_lastplay(self, data):
        """
        I am the last player. I can decide to win or not to win.
        
        Try to win...maybe.
        """
        round_cards = self._get_round_cards()
        lead_card = round_cards[0]
        filtered_round_cards = self.htapi.get_cards_by_suit(
            round_cards, lead_card.get_suit())
        round_card_score = self.htapi.calc_score(round_cards)

        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        if self._do_i_have_lead_suit() == True:
            if round_card_score > 0:
                #
                # Have score on this round. I have to decide to take or not to take...
                #

                # Try to win...
                bigger_card = self.htapi.pick_bigger_card(
                    my_avail_cards, filtered_round_cards)
                if bigger_card != None:
                    return bigger_card
                else:
                    # Cannot win this round. Have to give up shoot moon.
                    self.htapi.dbg("Give up sm... Oops")
                    return self._pick_card_as_mode(data)
            else:
                #
                # Have no score, I can give up to win if I have a too weak point.
                #
                same_suit_card_num = self.htapi.calc_card_num_by_suit(
                    my_hand_cards + oppo_unused_cards, lead_card.get_suit())

                min_sm_card = None
                min_sm_point = None
                for c in my_avail_cards:
                    this_sm_point = self._calc_sm_point(c, oppo_unused_cards)
                    if min_sm_point == None:
                        min_sm_point = this_sm_point
                        min_sm_card = c
                    elif this_sm_point < min_sm_point:
                        min_sm_point = this_sm_point
                        min_sm_card = c

                if same_suit_card_num > 9:
                    return min_sm_card
                else:
                    # Try to win this round...
                    bigger_card = self.htapi.pick_bigger_card(
                        my_avail_cards, filtered_round_cards)
                    if bigger_card != None:
                        return bigger_card
                    else:
                        # Cannot win this round.
                        return min_sm_card

        else:
            if round_card_score > 0:
                # Cannot win this round. Have to give up shoot moon.
                self.htapi.dbg("Give up sm... Oops")
                return self._pick_card_as_mode(data)
            else:
                return self._pick_card_sm_mode_freeplay(data)

    def _pick_card_sm_mode(self, data):
        """
        Pick a card!
        """
        # roundPlayers is in the correct order of shooting cards.
        round_players = self.stat['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        if my_pos == 0:
            card = self._pick_card_sm_mode_leadplay(data)
        elif my_pos == 3:
            card = self._pick_card_sm_mode_midplay(data)
        else:
            card = self._pick_card_sm_mode_lastplay(data)

        return card

    def _pick_card_as_mode_leadplay(self, data):
        """
        I don't want to take a trick...
        """
        my_avail_cards = self._get_avail_cards()

        my_hand_cards = self._get_hand_cards()
        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        candidates = []
        current_max_point = None
        for c in my_avail_cards:
            as_point = self._calc_as_point(c, oppo_unused_cards)

            if current_max_point == None:
                current_max_point = as_point
                candidates = [c]
            elif as_point > current_max_point:
                candidates = [c]
                current_max_point = as_point
            elif as_point < current_max_point:
                pass
            else:
                candidates.append(c)

        if len(candidates) == 0:
            self.htapi.errmsg("BUG")

        #
        # Try to make others eat 'QS'
        #
        spade_candidates = self.htapi.get_cards_by_suit(candidates, 'S')
        if len(spade_candidates) > 0:
            used_spade_cards = self._get_used_cards_by_suits(['S'])
            if self.htapi.find_card(used_spade_cards, Card('QS')) != None:
                for c in spade_candidates:
                    if self._calc_as_point(c, oppo_unused_cards) == 1.0:
                        return c

        #
        # Pick strong small heart cards first, so that I won't eat more later.
        #
        heart_candidates = self.htapi.get_cards_by_suit(candidates, 'H')
        if len(heart_candidates) > 0:
            for c in heart_candidates:
                if self._calc_as_point(c, oppo_unused_cards) == 1.0:
                    return c

        #
        # Pick small rank cards from shortage suit first
        #
        card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

        for di in card_num_stat_sorted:
            suit, num = di
            if num == 0:
                continue

            prefer_candidates = self.htapi.get_cards_by_suit(candidates, suit)
            if len(prefer_candidates) > 0:
                return prefer_candidates[0]

        self.htapi.errmsg("BUG")

    def _get_current_winner(self):

        round_players = self.stat['roundPlayers']

        round_cards = self._get_round_cards()
        if len(round_cards) == 0:
            self.errmsg("BUG")

        lead_card = round_cards[0]

        idx = 0
        winner_idx = 0
        for c in round_cards:
            if c.get_suit() == lead_card.get_suit(
            ) and c.get_rank_num() > lead_card.get_rank_num():
                winner_idx = idx
            idx += 1

        winner = round_players[winner_idx]

        return winner

    def _is_sm_possible(self):
        #
        # Check if there're 2 player having score. Then it's impossible to shoot moon.
        #
        have_score_player = 0
        for key in self.players:
            lp = self.players[key]
            score = self.htapi.calc_score(
                lp['pick'], is_expose_ah=self.stat['expose_ah_mode'])
            if score != 0:
                have_score_player += 1

        if have_score_player >= 2:
            return False  # Not possible to shoot moon.

        return True

    def _check_current_winner_sm(self):
        """
        Detect the round winner is now shooting moon.
        """
        if self._is_sm_possible() == False:
            return False

        winner = self._get_current_winner()
        pl = self.players[winner]

        winner_picked_cards = pl['pick']

        winner_picked_score_cards = self.htapi.find_score_cards(
            winner_picked_cards)

        if len(winner_picked_score_cards) > 7:
            return True  # Possibly want to shoot moon.

        return False

    def _pick_card_as_mode_freeplay(self, data):
        """
        I am in midplay or last play. I don't have lead suit, so I can shoot anything.
        """
        my_avail_cards = self.htapi.arrange_cards(self._get_avail_cards())
        my_hand_cards = self._get_hand_cards()

        round_cards = self._get_round_cards()
        if len(round_cards) == 0:
            self.errmsg("BUG")

        oppo_unused_cards = self._get_unused_cards(my_hand_cards)

        if self._check_current_winner_sm() == True:
            # Current winner is a sm player. Prefer no score card here.
            self.htapi.dbg("Current winner player is a suspicous pig.")

            if self.htapi.find_card(oppo_unused_cards, Card('QS')) != None:
                # I don't want to eat QS
                card = self.htapi.find_card(my_avail_cards, Card('AS'))
                if card != None:
                    return card

                card = self.htapi.find_card(my_avail_cards, Card('KS'))
                if card != None:
                    return card

#             # Shoot heart card but reserve the biggest rank
#             my_heart_cards = self.htapi.get_cards_by_suit(my_avail_cards, 'H')
#             if len(my_heart_cards) >= 2:
#                 my_heart_cards = self.htapi.arrange_cards(my_heart_cards)
#
#                 my_2nd_heart_card = my_heart_cards[-2]
#                 score = self._calc_as_point(my_2nd_heart_card, oppo_unused_cards)
#
#                 if score < 0.5:
#                     # Remove the 2nd dangerous heart.
#                     return my_2nd_heart_card

# Shoot no-score cards
            candidates = []
            as_point_min = None
            for c in my_avail_cards:
                this_as_point = self._calc_as_point(c, oppo_unused_cards)

                if self.htapi.calc_card_num_by_suit(oppo_unused_cards,
                                                    c.get_suit()) == 0:
                    # Don't worry. The oppo won't send the suit again... so I won't eat the trick.
                    # But if I am the lead... I will eat 100%.
                    continue

                if len(self.htapi.find_score_cards([c])) > 0:
                    # Avoid giving out score cards for sm player
                    continue

                if as_point_min == None:
                    as_point_min = this_as_point
                    candidates = [c]
                elif this_as_point < as_point_min:
                    as_point_min = this_as_point
                    candidates = [c]
                elif this_as_point > as_point_min:
                    pass
                else:
                    candidates.append(c)

            if len(candidates) > 0:
                card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

                # Remove small cards in shortage suit.
                for di in card_num_stat_sorted:
                    suit, num = di
                    if num == 0:
                        continue

                    prefer_candidates = self.htapi.get_cards_by_suit(
                        candidates, suit)
                    if len(prefer_candidates) > 0:
                        prefer_candidates = self.htapi.arrange_cards(
                            prefer_candidates)
                        return prefer_candidates[-1]
        #
        # Shoot QS out if I have chance.
        #
        card = self.htapi.find_card(my_avail_cards, Card('QS'))
        if card != None:
            return card

        #
        # Prefer AS, KS if the oppo has QS. Otherwise, KS, AS are not so dangerous...
        #
        if self.htapi.find_card(oppo_unused_cards, Card('QS')) != None:
            card = self.htapi.find_card(my_avail_cards, Card('AS'))
            if card != None:
                return card

            card = self.htapi.find_card(my_avail_cards, Card('KS'))
            if card != None:
                return card

        #
        # Shoot dangerous heart while opponents still have hearts.
        #
        oppo_heart_cards = self.htapi.get_cards_by_suit(oppo_unused_cards, 'H')
        if len(oppo_heart_cards) > 0:
            my_heart_cards = self.htapi.get_cards_by_suit(my_avail_cards, 'H')
            my_heart_cards = self.htapi.arrange_cards(my_heart_cards)
            for c in reversed(my_heart_cards):
                point = self._calc_as_point(c, oppo_unused_cards)

                if point < 0.5:
                    return c

        #
        # Shoot 'TC'
        #
        card = self.htapi.find_card(my_avail_cards, Card('TC'))
        if card != None:
            return card

        #
        # Avoid eating 'TC'
        #
        if self.htapi.find_card(oppo_unused_cards, Card('TC')) != None:
            my_club_cards = self.htapi.get_cards_by_suit(my_hand_cards, 'C')

            if len(my_club_cards) > 0:
                my_small_club_card = self.htapi.pick_small_card(my_club_cards)
                if my_small_club_card.get_rank_num() < Card('TC'):
                    # I still have small club. Won't eat 'TC'.
                    pass
                else:
                    card = self.htapi.find_card(my_avail_cards, Card('AC'))
                    if card != None:
                        return card

                    card = self.htapi.find_card(my_avail_cards, Card('KC'))
                    if card != None:
                        return card

                    card = self.htapi.find_card(my_avail_cards, Card('QC'))
                    if card != None:
                        return card

                    card = self.htapi.find_card(my_avail_cards, Card('JC'))
                    if card != None:
                        return card

        #
        # Choose the most dangerous cards
        #
        candidates = []
        as_point_min = None
        for c in my_avail_cards:
            this_as_point = self._calc_as_point(c, oppo_unused_cards)

            if self.htapi.calc_card_num_by_suit(oppo_unused_cards,
                                                c.get_suit()) == 0:
                # Don't worry. The oppo won't send the suit again... so I won't eat the trick.
                # But if I am the lead... I will eat 100%.
                continue

            if as_point_min == None:
                as_point_min = this_as_point
                candidates = [c]
            elif this_as_point < as_point_min:
                as_point_min = this_as_point
                candidates = [c]
            elif this_as_point > as_point_min:
                pass
            else:
                candidates.append(c)

        if len(candidates) > 0:
            card_num_stat_sorted = self._calc_hand_cards_num(my_hand_cards)

            # Remove small cards in shortage suit.
            for di in card_num_stat_sorted:
                suit, num = di
                if num == 0:
                    continue

                prefer_candidates = self.htapi.get_cards_by_suit(
                    candidates, suit)
                if len(prefer_candidates) > 0:
                    prefer_candidates = self.htapi.arrange_cards(
                        prefer_candidates)
                    return prefer_candidates[-1]

        #
        # Shoot the min as point card
        #
        candidates = []
        as_point_min = None
        for c in my_avail_cards:
            this_as_point = self._calc_as_point(c, oppo_unused_cards)

            if as_point_min == None:
                as_point_min = this_as_point
                candidates = [c]
            elif this_as_point < as_point_min:
                as_point_min = this_as_point
                candidates = [c]
            elif this_as_point > as_point_min:
                pass
            else:
                candidates.append(c)

        if len(candidates) == 0:
            self.htapi.errmsg("BUG")

        candidates = self.htapi.arrange_cards(candidates)
        return candidates[-1]

    def _pick_card_as_mode_midplay(self, data):
        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()

        round_cards = self._get_round_cards()
        lead_card = round_cards[0]
        filtered_round_cards = self.htapi.get_cards_by_suit(
            round_cards, lead_card.get_suit())
        filtered_round_cards_sorted = self.htapi.arrange_cards(
            filtered_round_cards)

        round_score_cards = self.htapi.find_score_cards(round_cards)

        oppo_same_suit_cards = self._get_unused_cards_by_suits(
            my_hand_cards, [lead_card.get_suit()])

        if self._do_i_have_lead_suit() == True:
            #
            # I have the lead suit... Don't have many choice...
            #
            filtered_round_cards = self.htapi.get_cards_by_suit(
                round_cards, lead_card.get_suit())
            card2shoot = self.htapi.pick_smaller_card(my_avail_cards,
                                                      filtered_round_cards,
                                                      auto_choose_big=False)
            if card2shoot != None:
                return card2shoot
            else:
                # I don't have smaller cards, so I am possible to eat the trick. Avoid picking QS here, or I will eat it myself.
                my_no_score_cards = self.htapi.find_no_score_cards(
                    my_avail_cards)
                if len(my_no_score_cards) > 0:
                    # Prefer no score card to decrease the score I will eat.

                    if len(oppo_same_suit_cards) > 9 and len(
                            round_score_cards) == 0:
                        # Gamble! Pick a big card and guess the next player have the same suit.
                        # TODO: Improve...
                        return self.htapi.pick_big_card(my_no_score_cards)
                    else:
                        return self.htapi.pick_small_card(my_no_score_cards)
                else:
                    # All cards left are score cards... what can I do...
                    return self.htapi.pick_small_card(my_avail_cards)
        else:
            #
            # I don't have the same suit. Can do anything.
            #
            return self._pick_card_as_mode_freeplay(data)

        self.htapi.errmsg("BUG")

    def _pick_card_as_mode_lastplay(self, data):
        my_hand_cards = self._get_hand_cards()
        my_avail_cards = self._get_avail_cards()

        round_cards = self._get_round_cards()
        lead_card = round_cards[0]
        filtered_round_cards = self.htapi.get_cards_by_suit(
            round_cards, lead_card.get_suit())
        filtered_round_cards_sorted = self.htapi.arrange_cards(
            filtered_round_cards)

        oppo_same_suit_cards = self._get_unused_cards_by_suits(
            my_hand_cards, [lead_card.get_suit()])

        round_score_cards = self.htapi.find_score_cards(round_cards)

        if self._do_i_have_lead_suit() == True:
            #
            # I have the lead suit... Don't have many choice...
            #
            if len(round_score_cards) > 0:
                card2shoot = self.htapi.pick_smaller_card(
                    my_avail_cards,
                    filtered_round_cards,
                    auto_choose_big=False)
                if card2shoot != None:
                    return card2shoot
                else:
                    if self.stat['sm_mode'] == False:
                        return self.htapi.pick_big_card(my_avail_cards)
                    else:
                        return self.htapi.pick_bigger_card(
                            my_avail_cards, filtered_round_cards)
            else:
                # No score card on table. Choose any no score card first.
                # Pick the score card if I have no choice.
                current_winner_card = filtered_round_cards_sorted[-1]

                candidates = []
                for c in my_avail_cards:
                    if len(self.htapi.find_score_cards(
                        [c])) > 0 and c.get_rank_num(
                        ) > current_winner_card.get_rank_num():
                        # This is a score card and will eat the trick. Don't shoot it.
                        pass
                    else:
                        candidates.append(c)

                if len(candidates) > 0:
                    candidates = self.htapi.arrange_cards(candidates)
                    score_candidates = self.htapi.find_score_cards(candidates)
                    if len(score_candidates) > 0:
                        return self.htapi.pick_big_card(score_candidates)
                    else:
                        return self.htapi.pick_big_card(candidates)
                else:
                    # I don't have good candidates. Usually means I will eat the trick myself.
                    return self.htapi.pick_big_card(my_avail_cards)
        else:
            return self._pick_card_as_mode_freeplay(data)

    def _pick_card_as_mode(self, data):
        """
        Pick a card!
        """
        # roundPlayers is in the correct order of shooting cards.
        round_players = self.stat['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        if my_pos == 0:
            card = self._pick_card_as_mode_leadplay(data)
        elif my_pos == 3:
            card = self._pick_card_as_mode_lastplay(data)
        else:
            card = self._pick_card_as_mode_midplay(data)

        return card

    def pick_card(self, data):
        """
        Event: My turn to shoot a card.
        """
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        self.stat['hand'] = self.htapi.arrange_cards(self.stat['hand'])

        self.stat['avail'] = self.htapi.arrange_cards(
            [Card(x) for x in data['self']['candidateCards']])

        self.stat['roundPlayers'] = data['roundPlayers'][:]

        # Get players in next turn.
        round_players = self.stat['roundPlayers']
        my_pos = round_players.index(self.get_name())
        self.stat['nextPlayers'] = data['roundPlayers'][(my_pos + 1):]

        if self._pick_card_should_i_sm(data) == True:
            self.htapi.dbg("sm mode")
            card2shoot = self._pick_card_sm_mode(data)
        else:
            self.htapi.dbg("as mode")
            card2shoot = self._pick_card_as_mode(data)

        self.htapi.dbg(self.get_name() + " shoot card: " + format(card2shoot) +
                       ", from: " + format(data['self']['cards']) +
                       ", next players: " + format(self.stat['nextPlayers']))
        return card2shoot.toString()

    def _detect_card_shortage(self, local_player, turn_card):
        #
        # If the player is not lead player and he shoots a card not the same as lead card,
        # mark the player as card shortage.
        #
        # If I know the shortage status of players, can have advantage to predict.
        #

        round_cards = self._get_round_cards()

        if len(round_cards) <= 1:
            # The turn player is leader. Ignore.
            return False

        lead_card = round_cards[0]

        if lead_card.get_suit_num() != turn_card.get_suit_num():
            if local_player['suit_leak'].count(lead_card.get_suit()):
                local_player['suit_leak'].append(lead_card.get_suit())
                self.htapi.dbg("Player: " + local_player['playerName'] +
                               " card leakage: " + lead_card.get_suit())

    def turn_end(self, data):
        """
        Event: turn end
        """
        data_turn_player = data['turnPlayer']

        data_turn_card = data['turnCard']
        data_turn_card = Card(data_turn_card)

        if data_turn_player != self.get_name():
            self.htapi.dbg(data_turn_player + " shoot card: " +
                           format(data_turn_card))

        local_player = self.players[data_turn_player]
        local_player['shoot'].append(data_turn_card)

        self.stat['roundCard'].append(data_turn_card)
        self.stat['usedCard'].append(data_turn_card)

        self._detect_card_shortage(local_player, data_turn_card)

    def pick_history(self, data, is_timeout, pick_his):
        """
        Event: turn end
        """
        self.turn_end(data)

    def ______pick(self):
        pass

    def _round_end_detect_sm_ability(self):
        #
        # Check if somebody get any score and give up shoot-moon mode.
        #

        if self.stat['sm_mode'] == False:
            # Already disable shoot moon mode.
            return

        for key in self.players:
            lp = self.players[key]
            score = self.htapi.calc_score(
                lp['pick'], is_expose_ah=self.stat['expose_ah_mode'])
            if score != 0 and lp['playerName'] != self.get_name():
                self.htapi.dbg("Give up shoot-moon mode...So sad.")
                self.stat['sm_mode'] = False

    def round_end(self, data):
        """
        Event: round end
        """
        round_player_name = data['roundPlayer']
        round_cards = self._get_round_cards()

        local_player = self.players[round_player_name]
        local_player['pick'] += round_cards

        self.htapi.dbg(
            "Player: " + round_player_name + " picked 4 cards: " +
            format(round_cards),
            " overall pick: " + format(local_player['pick']))

        # Disable shoot moon mode if somebody rx a score.
        self._round_end_detect_sm_ability()

        #
        # Reset round data
        #
        self.stat['roundCard'] = []
        self.stat['nextPlayers'] = []
        self.stat['roundPlayers'] = []

    def deal_end(self, data):
        """
        Event: deal end
        """
        data_players = data['players']

        self.htapi.dbg(format(data))

        for player in data_players:
            local_player = self.players[player['playerName']]
            local_player['score_accl'] = player['gameScore']
            if player['shootingTheMoon'] == True:
                local_player['sm'] += 1
                if player['playerName'] == self.get_name():
                    self.stat['pass3_sm_ability_history'].append(
                        self.stat['pass3_sm_ability'])

            if player['gameScore'] == 0 and self.get_name(
            ) == player['playerName']:
                self.stat['pass3_as_ability_history'].append(
                    self.stat['pass3_as_ability'])

        for key in self.players.keys():
            p = self.players[key]
            # Reset deal-specific data.
            p['score'] = 0
            p['shoot'] = []
            p['expose'] = False
            p['pick'] = []

        self.stat['usedCard'] = []
        self.stat['sm_mode'] = True
        self.stat['sm_mode_started'] = False
        self.stat['expose_ah_mode'] = False
        self.stat['pass3_sm_ability'] = 0.0
        self.stat['pass3_as_ability'] = 0.0

    def game_over(self, data):
        """
        Event: game end
        """
        self.htapi.dbg(format(data))

        print(self.stat)
        print(self.players[self.get_name()])
Exemple #6
0
class RandomBot(PokerBot, Htapi):
    """
    Random bot. Life is luck. Life is random. Life is life.
    """
    def __init__(self, name, is_debug=False):
        super(RandomBot, self).__init__(name)

        self.htapi = Htapi(is_debug=is_debug)

        self.stat = {}

    def new_game(self, data):
        """
        Event: The start of a new game.
        """
        pass

    def receive_cards(self, data):
        """
        Event: Receive my 13 cards.
        """
        self.stat['hand'] = self.get_cards(data)

    def pass_cards(self, data):
        """
        Event: Pick 3 cards to pass to others
        """
        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        my_hand_cards = self.stat['hand']

        output = []
        my_hand_cards = self.htapi.shuffle_cards(my_hand_cards)
        output.append(my_hand_cards.pop(0))
        output.append(my_hand_cards.pop(0))
        output.append(my_hand_cards.pop(0))

        output = [x.toString() for x in output]

        self.htapi.dbg(self.get_name() + " Pass 3 cards: " + format(output))
        return output

    def receive_opponent_cards(self, data):
        """
        Event: Recv 3 cards from one opponent
        """
        pass

    def expose_my_cards(self, data):
        """
        Event: The server asks me to expose AH...
        """
        output = []
        candidates = data['cards']
        if candidates == None or len(candidates) == 0:
            return output

        candidates = [Card(x) for x in candidates]

        if self.htapi.find_card(candidates, Card('AH')) == None:
            return output

        output = ['AH']
        return output

    def expose_cards_end(self, data):
        """
        Event: Check if somebody expose AH. Damn. 
        """
        pass

    def pick_card(self, data):
        """
        Event: My turn to shoot a card.
        """
        # roundPlayers is in the correct order of shooting cards.
        round_players = data['roundPlayers']

        # Identify my position in this round
        my_pos = round_players.index(self.get_name())

        # Get players in next turn.
        self.stat['nextPlayers'] = data['roundPlayers'][(my_pos + 1):]

        self.stat['hand'] = [Card(x) for x in data['self']['cards']]
        my_hand_cards = self.stat['hand']
        my_avail_cards = [Card(x) for x in data['self']['candidateCards']]

        my_avail_cards = self.htapi.shuffle_cards(my_avail_cards)

        card2shoot = my_avail_cards[0]

        return card2shoot.toString()

    def turn_end(self, data):
        """
        Event: turn end
        """
        pass

    def pick_history(self, data, is_timeout, pick_his):
        """
        Event: turn end
        """
        self.turn_end(data)

    def round_end(self, data):
        """
        Event: round end
        """
        pass

    def deal_end(self, data):
        """
        Event: deal end
        """
        pass

    def game_over(self, data):
        """
        Event: game end
        """
        pass