def test_shuffle_deck(self): deck = Deck() deck.shuffle() card1 = Card(Rank.ACE, Suit.SPADES) card2 = Card(Rank.TWO, Suit.SPADES) card1_shuffled = deck._cards[0] is not card1 card2_shuffled = deck._cards[1] is not card2 # Extremely unlikely to fail, but can happen self.assertTrue(card1_shuffled or card2_shuffled)
def setup(self) -> None: """Set up the game by dealing the player and the house two cards each. """ self.deck = Deck() self.deck.shuffle() self.user.add_to_hand(self.deck.draw()) self.house.add_to_hand(self.deck.draw()) self.user.add_to_hand(self.deck.draw()) self.house.add_to_hand(self.deck.draw())
def test_new_double_deck(self): deck = Deck(2) self.assertEqual(deck.size(), 2 * _DECK_SZ) as_card = Card(Rank.ACE, Suit.SPADES) as_count = 0 for card in deck._cards: if card == as_card: as_count += 1 self.assertEqual(as_count, 2)
def setup_round(self, num_players: int): """Set up the game by making a deck, shuffling it, dealing cards, making a discard, flipping over the top card, and creating the round points. Args: num_players (int): number of players Returns: CrazyEights: game after the setup is complete """ for player in self.players.values(): player.clear_hand() # empty the players' hands from prev rounds num_decks = 2 if len(self.players) > 5 else 1 self.deck = Deck(num_decks) self.deck.shuffle() num_cards = 5 if len(self.players) > 2 else 7 self.deal(num_cards) self.discard = [] self.discard.append(self.deck.draw()) self.pts = [0] * num_players
class Blackjack: """Represent a game of blackjack, controlling game flow. """ def __init__(self): self.user = Player() self.house = Player() self.game_state = "New Game" self.setup() def setup(self) -> None: """Set up the game by dealing the player and the house two cards each. """ self.deck = Deck() self.deck.shuffle() self.user.add_to_hand(self.deck.draw()) self.house.add_to_hand(self.deck.draw()) self.user.add_to_hand(self.deck.draw()) self.house.add_to_hand(self.deck.draw()) def hit(self, player: Player) -> None: """Deal a card to a player. Args: player (Player): player who gets card """ player.add_to_hand(self.deck.draw()) def reset(self) -> str: self.clear() self.setup() return "Game reset" def clear(self) -> str: """Reset the game by clearing all player hands. """ self.house.clear_hand() self.user.clear_hand() self.game_state = "New Game" return "History cleared" @staticmethod def calculate_current_sum(player: Player) -> int: """Calculate the sum of the card values in a player's hand. """ curr_sum = 0 for card in player.get_cards(): if card.get_rank() == Rank.ACE: curr_sum += 11 elif card.is_face_card(): curr_sum += 10 else: curr_sum += card.get_rank().value return curr_sum def bust(self) -> str: """updates game state and returns loss string """ self.game_state = "Game over." return "BUST" def win(self) -> str: """updates game state and returns win string """ self.game_state = "Game over." return "WIN BABY" def tie(self) -> str: self.game_state = "Game over." return "TIE" # defines win conditions given both user sum and hand sum def win_condition(self) -> str: """Check if the user has won or lost when the game is ending. Returns: str: result of the game """ user_sum = self.calculate_current_sum(self.user) house_sum = self.calculate_current_sum(self.house) if user_sum > 21 and house_sum > 21: return self.tie() if user_sum > 21: return self.bust() if house_sum > 21: return self.win() if user_sum > house_sum: return self.win() else: self.clear() return self.bust() def check_if_bust(self, user_sum: int, house_sum: int) -> str: """ checks if after a turn the user has busted or not. Used when input is hit """ if user_sum > 21: self.clear() return self.bust() elif user_sum == 21: self.clear() return self.win() elif house_sum > 21: self.clear() return self.win() return "" def next_state(self, decision: str) -> str: """Play out one turn of blackjack given the user decision to hit or stand. Args: decision (str): user decision to hit or stand Returns: str: next game state """ win_status = "" house_sum = self.calculate_current_sum(self.house) user_sum = self.calculate_current_sum(self.user) # user hits if decision == "hit": self.hit(self.user) user_sum = self.calculate_current_sum(self.user) win_status = self.check_if_bust(user_sum, house_sum) # user stands elif decision == "stand": # dealer must hit under 17 while house_sum < 17: self.hit(self.house) house_sum = self.calculate_current_sum(self.house) win_status = self.win_condition() elif decision == "help": return "Welcome to blackjack. Please type in your next move." \ " Acceptable commands are hit or stand. To quit type quit. To see this help menu type help." elif decision == "quit": return "QUIT" # return the decision return self.display_state(win_status) def display_state(self, win_status: str) -> str: """[summary] Args: win_status (str): [description] """ user_hand_sum = self.calculate_current_sum(self.user) house_hand_sum = self.calculate_current_sum(self.house) # strings to display user_hand_str = "CURRENT HAND: " + str(user_hand_sum) house_hand_str = "HOUSE HAND: " + str(house_hand_sum) return user_hand_str + "\n" + house_hand_str + "\n" + win_status def start_game(self, decision: str): result = ["BUST", "WIN BABY", "TIE", "QUIT"] if decision not in result: decision = self.next_state(decision) return decision @staticmethod def get_name(): return 'Blackjack' @staticmethod def get_subdir() -> str: return 'blackjack' @staticmethod def get_help(): return "You are originally dealt two cards and one card from the houses hand will be flipped up." \ "You have the choice to either have another card dealt to you (hit) or to stick with " \ "your cards (stand). If your hand's sum is closest to twenty-one then you win, if the sum " \ "is over twenty-one, you lose (bust), or if the sum is exactly twenty-one you win (blackjack)." \ "(User input should be in the form of either: Hit or Stand))"
class CrazyEights: """Represent a crazy eights game. Args: num_players (int): number of players from [2, 7]. """ def __init__(self, num_players: int): # Set up the game. self.setup_game(num_players) self.curr_suit = Suit.SPADES # suit choice after an eight is played # Keep track of the game history. self.game_hist = [] self.game_state = "Round 1" def setup_round(self, num_players: int): """Set up the game by making a deck, shuffling it, dealing cards, making a discard, flipping over the top card, and creating the round points. Args: num_players (int): number of players Returns: CrazyEights: game after the setup is complete """ for player in self.players.values(): player.clear_hand() # empty the players' hands from prev rounds num_decks = 2 if len(self.players) > 5 else 1 self.deck = Deck(num_decks) self.deck.shuffle() num_cards = 5 if len(self.players) > 2 else 7 self.deal(num_cards) self.discard = [] self.discard.append(self.deck.draw()) self.pts = [0] * num_players def setup_game(self, num_players: int) -> CrazyEights: """Set up the game by creating the players, creating the round history, and completing round setup. Args: num_players (int): number of players the game will have Returns: CrazyEights: game after being set up """ # Create the players, numbering them from [1, num_players]. The user # is player 1. self.players = {} for n in range(num_players): # Note that constructing with [] is needed to block pointer aliases self.players[n + 1] = Player([]) # Create the round history. self.round_hist = [] # Set up for the first round. self.setup_round(num_players) return self def deal(self, num_cards: Optional[int] = -1) -> CrazyEights: """Deal the cards in the deck out to the players. Args: num_cards (Optional[int], optional): number of cards to deal each player. If less than zero or greater than the deck size, deal out all the cards in the deck. Defaults to -1 (deal whole deck). Returns: CrazyEights: game after the cards have been dealt """ cards_to_deal = num_cards * len(self.players) if cards_to_deal < 0 or cards_to_deal > self.deck.size(): cards_to_deal = self.deck.size() for card_count in range(cards_to_deal): player_to_deal = (card_count % len(self.players)) + 1 card = self.deck.draw() self.players.get(player_to_deal).add_to_hand(card) return self def show_player_hand(self, player_num: int) -> str: """Show the current hand of a player. Args: player_num (int): number of the player's hand to show Returns: str: hand of the player; empty string if player doesn't exist """ if player_num < 1 or player_num > len(self.players): return '' return self.players.get(player_num).show_hand() def draw(self, player_num: int) -> CrazyEights: """Draw a card off the top of the deck and place it into a player's hand. Args: player_num (int): number of the player who is drawing a card Returns: CrazyEights: game after the player has drawn """ if player_num < 1 or player_num > len(self.players): return self if self.deck.is_empty() and len(self.discard) <= 1: self.reset_round() return self if self.deck.is_empty(): top_card = self.discard.pop() self.deck.add_cards(self.discard) self.discard.clear() self.discard.append(top_card) self.deck.shuffle() card = self.deck.draw() self.players.get(player_num).add_to_hand(card) return self def play(self, player_num: int, card_to_play: Card, set_suit: Optional[Suit] = Suit.SPADES) -> bool: """Play a specific card, if possible. Args: player_num (int): number of the player playing the card. Defaults to player 1, the user. card_to_play (Card): desired card to play set_suit (Optional[Suit], optional): suit to change the play to if the card being played is an eight. Defaults to spades. Returns: bool: True if the card was played, and False otherwise """ player = self.players.get(player_num) if player.has(card_to_play) and self.playable(card_to_play): player.remove_from_hand(card_to_play) self.discard.append(card_to_play) if not player.has_cards(): self.game_state = "Round {}".format(str(len(self.round_hist) + 1)) self.reset_round() if card_to_play.get_rank() == Rank.EIGHT: self.curr_suit = set_suit return True return False def show_top_card(self): return self.discard[-1].__str__() def playable(self, card_to_play: Card) -> bool: """Checks whether a card can be played. Args: card_to_play (Card): card to try to play Returns: bool: whether the card can be played or not """ top_card = self.discard[-1] tc_suit = self.get_top_card_suit() # suit might be set differently # Check the conditions to be met for the card to be playable. return (card_to_play.get_rank() == top_card.get_rank() or card_to_play.get_suit() == tc_suit or card_to_play.get_rank() == Rank.EIGHT) def get_top_card_suit(self) -> Suit: """Get the top card's suit, as it might be set by an eight. Returns: Suit: suit of top card in discard """ tc = self.discard[-1] tc_suit = tc.get_suit() # TODO: add prot for when card flipped to start game is an eight if tc.get_rank() == Rank.EIGHT: tc_suit = self.curr_suit return tc_suit def play_options(self, player_num: int) -> List[Card]: """Find the cards in a player's hand that can be played. Args: player_num (int): player number Returns: List[Card]: cards in the player's hand that are viable """ top_card = self.discard[-1] tc_suit = self.get_top_card_suit() player = self.players.get(player_num) same_rank = player.get_cards(top_card.get_rank()) same_suit = player.get_cards(tc_suit) eights = player.get_cards(Rank.EIGHT) cards = same_rank + same_suit + eights return cards def turn(self, player_num: int) -> Card: """Play out a player's turn using automated choices. Args: player_num (int): number of the player whose turn it is Returns: Card: card that the player plays """ while True: # Try to play the first possible card. card_ops = self.play_options(player_num) if card_ops: self.play(player_num, card_ops[0]) break # Draw a card if none could be played. else: # TODO: check behavior if game ends off of emptying deck self.draw(player_num) def reset_round(self) -> CrazyEights: """Reset the round, storing it into the game's round history. Returns: CrazyEights: game after the round has been reset """ # Store the game state in the round history. self.round_hist.append(self.players) # Count each player's points. for n in self.players: player = self.players.get(n) for card in player.get_cards(): card_pts = card.get_rank().value if card.get_rank() == Rank.EIGHT: card_pts = 50 if card.get_rank() > Rank.TEN: card_pts = 10 self.pts[n - 1] += card_pts # The player(s) with the lowest points won. Add the differences between # their points and each of the other players' points to their scores. min_score = min(self.pts) winners = [] total_pts_diff = 0 for i in range(len(self.players)): if self.pts[i] == min_score: winners.append(i) if self.pts[i] > min_score: total_pts_diff += self.pts[i] - min_score for i in range(len(winners)): winner = self.players.get(i + 1) winner.increase_score(total_pts_diff) # Reset the round by setting up a new round and return. self.setup_round(len(self.players)) return self def reset(self, num_players: Optional[int] = None) -> str: """Reset the game, storing its current state in the game history. Args: num_players (Optional[int], optional): number of players to start the new game with. Defaults to None (use current number of players) Returns: CrazyEights: game after being reset """ self.game_state = "Round 1" self.reset_round() # reset round to store current round into hist self.game_hist.append((self.players, self.round_hist)) if num_players: self.setup_game(num_players) else: self.setup_game(len(self.players)) return "Game reset" def clear(self) -> str: """Reset the current game and clear all game history. Returns: CrazyEights: game after being cleared """ self.reset() self.game_hist.clear() return "History cleared" """Define methods that all games are required to implement. """ @staticmethod def get_name(): return 'Crazy Eights' @staticmethod def get_subdir() -> str: return 'crazy_eights' @staticmethod def get_help(): return " Play a card that matches either the suit or value of the top card. Input is taken as value,suit i.e" \ "seven,hearts to play the seven of hearts." \ " The first one to play all of their cards wins."\ " If you don't have a corresponding card then type draw to draw a card."