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