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))
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))
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()])