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