From 74bd1c4415bf18a42e9035132e3b7d9b7970b7e6 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Fri, 11 Mar 2022 20:16:04 +0000 Subject: [PATCH] Implement playing cards --- blackjack.py | 322 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 236 insertions(+), 86 deletions(-) diff --git a/blackjack.py b/blackjack.py index 53e8d38..e846bae 100755 --- a/blackjack.py +++ b/blackjack.py @@ -4,6 +4,12 @@ import random class NoMoreCards(Exception): pass +class InvalidCard(Exception): + pass + +class InvalidSuit(Exception): + pass + class Game(object): def __init__(self, deck, rules): self.players = [] @@ -13,50 +19,86 @@ class Game(object): # Which index player's turn is it? self.player_index = 0 - print("Created game.") + self.last_player = None + + @property + def current_player(self): + return self.players[self.player_index] def next_turn(self): - current_player = self.players[self.current_player] + current_player = self.players[self.player_index] print(f"Current player is {current_player}") if self.player_index == len(self.players): - print(f"Reached the end of players: {self.players}, {self.current_player}") + print(f"Reached the end of players: {self.players}, {current_player}") self.current_player = 0 else: self.current_player += 1 - print(f"Incrementing current_player: {self.players}, {self.current_player}") + print(f"Incrementing current_player: {self.players}, {current_player}") + + def determine_start(self, first_card): + """Determine who starts the game.""" + card_map = {} + for player in self.players: + player_min = player.hand.min_of_suit_or_none(first_card.suit) + if player_min is None: + card_map[None] = player + else: + card_map[str(player_min)] = player + if len(card_map) == 1 and None in card_map: + return card_map[None] + else: + without_none = {k:v for k,v in card_map.items() if k} + min_card = min(without_none.keys()) + min_player = without_none[min_card] + return min_player def add_players(self, *players): for player in players: print(f"Adding player: {player}") self.players.append(player) + def flipcard(self): + if not len(self.playdeck.cards) == 0: + return False + first_card = self.drawdeck.draw() + print(f"First card is: {first_card}") + self.playdeck.put(first_card) + starter = self.determine_start(first_card) + print(f"{starter} to start!") + def deal(self): for player in self.players: num_cards = self.rules.initial_cards cards = list(self.drawdeck.draw_num(num_cards)) - print("Cards", cards) - player.deck.put_cards(*cards) + player.hand.put_cards(*cards) if self.rules.flipcard: - first_card = self.drawdeck.draw() - print(f"First card is: {first_card}") - self.playdeck.put(first_card) + self.flipcard() - def play_card(self, player, card): + def play_card(self, player, card, special=True): print(f"Player {player} attempting to play {card}") last_card = self.playdeck.last_card print(f"Last card {last_card}") - is_playable = self.rules.is_playable_on(last_card, card) + if player == self.last_player: + special = False + is_playable = self.rules.is_playable_on(last_card, card, special=special) print("Card is playable", is_playable) - #current_card = self.d + if is_playable: + actual_card = player.hand.draw_card(card) + self.playdeck.put(actual_card) + self.last_player = player + return is_playable + + #def play_combination(self, player, *cards): + # print(f"Player {player} attempting to play combination {cards}") + class Player(object): def __init__(self, name): self.name = name - self.deck = Hand() - print(f"Created player {name}") + self.hand = Hand() def __str__(self): - return self.name + return f"@{self.name}" def __repr__(self): return self.__str__() @@ -66,11 +108,9 @@ class SetOfCards(object): self.cards = [] if cards: self.cards = [*cards] - print(f"Created deck with cards: {self.cards}") def draw(self): card = self.cards.pop() - print(f"Drawing {card} from deck") return card def draw_num(self, num): @@ -78,17 +118,28 @@ class SetOfCards(object): yield self.cards.pop() def put(self, card): - print("Putting card on deck:", card) self.cards.append(card) def put_cards(self, *cards): for card in cards: self.put(card) + def draw_card(self, card): + for mycard in self.cards: + if card == mycard: + self.cards.remove(mycard) + print(f"Returned {mycard} and amended cards: {self.cards}") + return mycard + + def __str__(self): + return str(self.cards) + + def __repr__(self): + return self.__str__() + class Deck(SetOfCards): def shuffle(self): random.shuffle(self.cards) - print(f"Shuffled deck: {self.cards}") @property def last_card(self): @@ -97,44 +148,70 @@ class Deck(SetOfCards): else: raise NoMoreCards + @property + def last_cards(self): + if self.cards: + return self.cards[:5] + + def fill(self): + for suit_str in "SHCD": + values_str_10 = [str(x) for x in range(2, 11)] + for val_str in [*values_str_10, "J", "Q", "K", "A"]: + card_str = f"{suit_str}{val_str}" + print("card_str", card_str) + card = Card(card_str) + self.cards.append(card) + print("cards after fill", self.cards) + print("len after fill", len(self.cards)) + class Hand(SetOfCards): - pass + def have_suit(self, suit): + """Have we got this suit?""" + for card in self.cards: + if card.suit == suit: + return True + return False + + def min_of_suit_or_none(self, suit): + if not self.have_suit(suit): + return None + suit_cards = [] + for card in self.cards: + if card.suit == suit: + suit_cards.append(card) + return min(suit_cards) class Rules(object): def __init__(self): - self.initial_cards = 2 # Number of cards to start with + self.initial_cards = 7 # Number of cards to start with self.flipcard = True # Initially flip a card self.jacks_on_twos = False # Can we play Jacks on 2s? self.jacks_on_eights = False # Can we play Jacks on 8s? self.set_power_cards() - print("Created rules.") - def set_power_cards(self): # Next player has to pick up two cards if they don't have a # card of the same value - self.pick_up_two = Value(2) + self.pick_up_two = Value("2") # Next player has to skip a go if they don't have another - self.skip_a_turn = Value(8) + self.skip_a_turn = Value("8") # How many cards to pick up self.multiple_number = 6 - pick_up_multiple_suits = [Suit("Spades"), Suit("Clubs")] + pick_up_multiple_suits = [Suit("S"), Suit("C")] # Create a BlackJack filter - self.pick_up_multiple = Filter(pick_up_multiple_suits, Value("Jack")) - self.change_suit = Value("Ace") + self.pick_up_multiple = Filter(pick_up_multiple_suits, Value("J")) + self.change_suit = Value("A") self.specials_all = [self.pick_up_two, self.skip_a_turn, self.pick_up_multiple, self.change_suit] self.specials_power = self.specials_all[:-1] - print("Specials all", self.specials_all) - print("Power specials", self.specials_power) - def can_play_on_special(bottom_card, card): + def can_play_on_special_power(self, bottom_card, card): # If the bottom card is a two if bottom_card == self.pick_up_two: # If the proposed card is not also the same @@ -162,41 +239,67 @@ class Rules(object): if card == self.pick_up_multiple: return True return False + print("Reached final return for can play on special") + return False + + def can_play_special_on(self, bottom_card, card): + print("CARD IS ", card) + print("CHANGE SUIT IS", self.change_suit) + if card == self.change_suit: + print("Card is change suit") + if bottom_card not in self.specials_power: + return True return False - - def is_playable_on(bottom_card, card): - if bottom_card in self.rules.specials_all: - return self.can_play_on_special(bottom_card, card) + def is_playable_on(self, bottom_card, card, special): + print(f"Is playable on {bottom_card} {card}") + if special: + if bottom_card in self.specials_power: + print("Bottom card in specials all") + return self.can_play_on_special_power(bottom_card, card) if card.suit == bottom_card.suit: + print("Suit is bottom card suit") return True - if card.val == buttom_card.val: + if card.val == bottom_card.val: + print("Value is bottom card value") return True + if special: + if card in self.specials_all: + print("Card is in specials_all") + return self.can_play_special_on(bottom_card, card) class Suit(object): def __init__(self, suit): + if suit not in "SHDC": + raise InvalidSuit self.suit = suit def __str__(self): - return f"#{self.suit}" + return f"{self.suit}" def __repr__(self): return self.__str__() def __eq__(self, other): - return other.suit == self.suit + if isinstance(other, Suit): + return self.suit == other.suit + elif isinstance(other, Card): + return self.suit == other.suit.suit class Value(object): def __init__(self, val): + self.hash_map = {"J": 11, "Q": 12, "K": 13, "A": 14} + string_values = [str(x) for x in range(2, 11)] + if val not in self.hash_map.keys() and val not in string_values: + raise InvalidCard self.val = val self.numeric_value = self.get_numeric_value(val) def get_numeric_value(self, val): - hash_map = {"Jack": 11, "Queen": 12, "King": 13, "Ace": 14} - if val in hash_map: - return hash_map[val] + if val in self.hash_map: + return self.hash_map[val] else: - return val + return int(val) def __gt__(self, other): return self.numeric_value > other.numeric_value @@ -207,9 +310,14 @@ class Value(object): def __eq__(self, other): if isinstance(other, Value): return self.numeric_value == other.numeric_value + elif isinstance(other, Card): + return self.numeric_value == other.val.numeric_value + else: + print(f"ERROR! Cannot compare {self} and {other}") + return False def __str__(self): - return f".{str(self.val)}" + return f"{str(self.val)}" def __repr__(self): return self.__str__() @@ -225,37 +333,54 @@ class Filter(object): self.val = val def __str__(self): - return f"|{self.val} of {self.suits}" + return f"|{self.val} of {self.suits}|" def __repr__(self): return self.__str__() def __eq__(self, other): - print("Filter EQ called") - print("other suit", other.suit) - print("our suits", self.suits) - print("other val", other.val) - print("our val", self.val) if other.suit in self.suits and other.val == self.val: return True else: return False class Card(object): - def __init__(self, suit=None, val=None): - self.suit = Suit(suit) - self.val = Value(val) - print(f"Created card with suit {suit} and value {self.val}") + def __init__(self, identifier): + self.suit, self.val = Card.identifier_to_card(identifier) def playable_on(self, card): pass def __str__(self): - return f"+({self.val} of {self.suit})" + return f"{self.suit}{self.val}" def __repr__(self): return self.__str__() + def __gt__(self, other): + return self.val.numeric_value > other.val.numeric_value + + def __lt__(self, other): + return self.val.numeric_value < other.val.numeric_value + + def __eq__(self, other): + if isinstance(other, Card): + if self.val.numeric_value == other.val.numeric_value: + if self.suit.suit == other.suit.suit: + return True + elif isinstance(other, Value): + return self.val.numeric_value == other.numeric_value + elif isinstance(other, Suit): + return self.suit.suit == other.suit + return False + + @staticmethod + def identifier_to_card(identifier): + suit_str = identifier[0] + val_str = identifier[1:] + suit = Suit(suit_str) + val = Value(val_str) + return suit, val @staticmethod def multiple_suit(val, suits): @@ -265,18 +390,12 @@ class Card(object): card.val = val yield card - mark = Player("Mark") john = Player("John") -card1 = Card("Spades", "Ace") -card2 = Card("Diamonds", "Jack") -card4 = Card("Clubs", 3) -card5 = Card("Diamonds", 3) -card6 = Card("Hearts", 4) - -deck = Deck(card1, card2, card4, card5, card6) -deck.shuffle() +deck = Deck() +deck.fill() +#deck.shuffle() rules = Rules() @@ -285,26 +404,57 @@ game.add_players(mark, john) game.deal() -print("Mark's cards:", mark.deck.cards) - -print("John's cards:", john.deck.cards) - -val1 = Value("Queen") -val2 = Value(2) -val3 = Value("Queen") -assert val1 > val2 -assert val2 < val1 -assert val3 == val1 - -filter_blackjack = Filter([Suit("Spades"), Suit("Clubs")], Value("Jack")) -blackjack1 = Card("Spades", "Jack") -blackjack2 = Card("Clubs", "Jack") - -redjack1 = Card("Diamonds", "Jack") -redjack2 = Card("Hearts", "Jack") - -assert blackjack1 == filter_blackjack -assert blackjack2 == filter_blackjack - -assert redjack1 != filter_blackjack -assert redjack2 != filter_blackjack +print("Mark's cards:", mark.hand.cards) +print("John's cards:", john.hand.cards) + +print("--- Beginning game ---") +while 1: + print("=====") + current_player = game.current_player + print(f"Player {current_player}'s go!") + player_cards = current_player.hand.cards + last_player = game.last_player + if last_player: + print(f"Last player: {last_player}") + print(f"Last card is: {game.playdeck.last_card}") + print(f"Last 5 cards: {game.playdeck.last_cards}") + print(f"Your cards: {player_cards}") + text = input("~> ") + parsed_cards = text.split(",") + print("Parsed", parsed_cards) + cards_to_play = [] + for card in parsed_cards: + if len(card) == 0: + print("Invalid card") + continue + print("Parsed card iter", card) + try: + card_to_play = Card(card) + print("Card to play", card_to_play) + except InvalidCard: + print("Invalid card:", card) + continue + if card_to_play not in current_player.hand.cards: + print(f"Card is not in your hand: {card_to_play}") + continue + print(f"Attempting to play {card_to_play}") + played = game.play_card(current_player, card_to_play) + if not played: + print("Illegal move!") + continue + if card_to_play == game.rules.change_suit: + suit = None + while not suit: + new_suit = input("Enter a new suit: #> ") + try: + suit = Suit(new_suit) + except InvalidSuit: + print(f"Suit {new_suit} is not valid, please select S, H, D or C") + continue + print(f"Changing last card of {game.playdeck.cards[-1]} to {suit}") + game.playdeck.cards[-1].suit = suit + + if len(current_player.hand.cards) == 0: + print(f"Player {current_player} wins!") + print("Thanks for playing!") + exit()