import cards import pprint import pygame from game_consts import GameState, PlayerRole, STARTING_HAND, DOUBLE_CLICK_EVENT, DOUBLE_CLICK_TIMING, CALL_EVENT class Player(cards.Deck): """ A player is essentially a Deck with decision making function or AI component if it is a bot that returns a valid action for the Table/Board. The player has the knowledge of Table status in the form of a dictionary (as it is mutable, thus passed by ref) so all validation is done by the player Possible decisions, each decision has to be enum maybe: - Query the board status (i.e. current round, player status), AI most likely need a lot more - Query the last round - Attempt to play a card - Play the validate move The player also implements method to play from the terminal if it is not a bot. """ def __init__(self, *args, ai_component=None, **kwargs): super().__init__(*args, **kwargs) self.role = PlayerRole.UNKNOWN self.AI = ai_component self._table_status = None # This is found in Table and updated through Table self.score = 0 def connect_to_table(self, table): self._table_status = table def add_ai(self, ai_comp): self.AI = ai_comp ai_comp.connect_to_player(self) self.selectable = False def make_decision(self, game_state, sub_state, game_events=None): """ The player will need to make a decision depending on the game state and sub-state :param game_state: Current game state :param sub_state: Sub-state which affects the output for the current game state :param game_events: Pygame events :return: For Bidding: Either a bid or a partner call, int For Playing: A Card For Reshuffle: bool, True to reshuffle, False otherwise """ if game_state == GameState.POINT_CHECK: if self.AI: return self.AI.request_reshuffle() return self.request_reshuffle(game_events=game_events) if game_state == GameState.BIDDING: if sub_state == 0: if self.AI: return self.AI.make_a_bid() return self.make_a_bid(game_events=game_events) else: if self.AI: return self.AI.call_partner() return self.call_partner(game_events=game_events) if game_state == GameState.PLAYING: if self.AI: play = self.AI.make_a_play(sub_state) [_, pos] = self.check_card_in(play) return self.remove_card(pos) return self.make_a_play(sub_state, game_events=game_events) def make_a_bid(self, game_events=None): """ The procedure to make a bid :return: A valid bid number """ msg = '' while True: bid = input("Please input a bid in the format 'number' + 'suit' \n" "To pass, enter nothing. \n" "e.g 4d is 4 Diamond, 6n is 6 No Trump \n") if not bid: return 0, msg bid = cards.convert_bid_string(bid) if bid < 0: print("Error in processing bid") continue if self._table_status["bid"] < bid: return bid, msg else: if bid > 75: print("You cannot bid beyond 7 No Trump") else: print("You might need to bid higher") def call_partner(self, game_events=None): """ The procedure to call a partner :return: A valid card value """ current_card_values = self.get_deck_values() msg = '' while True: partner = input("Please call your partner card. Enter card number + suit number \n" "e.g. qs is Queen Spade, 8c is 8 Clubs, ah is Ace Hearts\n") partner = cards.convert_input_string(partner) if partner in current_card_values: print("Please call a card outside of your hand") elif cards.card_check(partner): return partner, msg else: print("Invalid card call") def make_a_play(self, substate, game_events=None): """ The procedure to make a play in a round :return: A valid Card """ msg = '' while True: play = input("Please play a card.Enter card number + suit number \n" "e.g. qs is Queen Spade, 8c is 8 Clubs, ah is Ace Hearts\n") #if play == "v": # pprint.pprint(self._table_status) #else: play = cards.convert_input_string(play) if play > 0: valid = self.check_for_valid_plays(play, substate == 0) if valid: [_, pos] = self.check_card_in(play) return self.remove_card(pos), msg print("Invalid play") def view_last_round(self): pass def check_for_valid_plays(self, card, leading): """ Check if the card played is valid :param card: int :param leading: bool :return: """ if not self.check_card_in(card): return False card_suit = cards.get_card_suit(card) if leading: if not self._table_status['trump broken'] and \ card_suit == self._table_status['trump suit']: if any([not cards.get_card_suit(crd) == self._table_status['trump suit'] for crd in self.get_deck_values()]): return False else: leading_card_suit = self._table_status['played cards'][self._table_status["leading player"]].suit() if not card_suit == leading_card_suit and \ any([cards.get_card_suit(crd) == leading_card_suit for crd in self.get_deck_values()]): return False return True def get_card_points(self): suit_points = 0 card_points = [] current_suit = 1 card_position = 0 for (i, card) in enumerate(self.cards): if card.suit() != current_suit: suit_points += (i-card_position) // 5 card_position = i current_suit = card.suit() card_points.append(max(0, card.number() - 10)) suit_points += (STARTING_HAND-card_position) // 5 return suit_points + sum(card_points) def request_reshuffle(self, game_events=None): # Players can choose NOT to reshuffle return input("Reshuffle? (y/n)").lower() == 'y' class MainPlayer(Player): def __init__(self, *args, ai_component=None, **kwargs): super().__init__(*args, **kwargs) self.AI = ai_component self.table_status = None # This is found in Table and updated through Table self.selectable = True self.left_mouse_down = False self.double_clicking = False def make_a_bid(self, game_events=None): """ The procedure to make a bid :return: A valid bid number """ if game_events: for event in game_events: if event.type == CALL_EVENT: bid = event.call if not bid: return 0, '' bid = cards.convert_bid_string(bid) if bid < 0: return -1, "Invalid bid" if self._table_status["bid"] >= bid: if bid > 75: return -1, "You cannot bid beyond 7 No Trump" else: return -1, "You might need to bid higher" return bid, '' return -1, '' def call_partner(self, game_events=None): if game_events: for event in game_events: if event.type == CALL_EVENT: current_card_values = self.get_deck_values() partner = event.call partner = cards.convert_input_string(partner) if partner in current_card_values: return False, "Please call a card outside of your hand" elif cards.card_check(partner): return partner, "" else: return False, "Invalid card call" return False, '' def make_a_play(self, substate, game_events=None): card = None msg = None if game_events: for event in game_events: if event.type == pygame.MOUSEBUTTONUP and event.button == 1: mouse_pos = pygame.mouse.get_pos() if self.rect.collidepoint(mouse_pos): reselect = self.get_selected_card(mouse_pos, double_clicking=self.double_clicking) if not reselect: card = 1 if self.double_clicking: pygame.time.set_timer(DOUBLE_CLICK_EVENT, 0) if reselect: card_value = self.cards[self.selected_card].value if self.check_for_valid_plays(card_value, substate == 0): card = self.remove_selected_card() else: card = 1 msg = "Invalid card play" self.double_clicking = False else: self.double_clicking = True pygame.time.set_timer(DOUBLE_CLICK_EVENT, DOUBLE_CLICK_TIMING) #if reselect: # self.deselect_card() # card = 1 return card, msg if event.type == DOUBLE_CLICK_EVENT: pygame.time.set_timer(DOUBLE_CLICK_EVENT, 0) self.double_clicking = False return card, msg def request_reshuffle(self, game_events=None): # Players can choose NOT to reshuffle for event in game_events: if event.type == CALL_EVENT: return event.call return None