From 21fd570196702c4ae753d628b7c7bc046a4f3910 Mon Sep 17 00:00:00 2001 From: Dan Calloway Date: Sat, 20 Sep 2025 21:01:39 -0400 Subject: [PATCH] Committing new files --- 5-Card_Stud_Poker_Game (Final).py | 312 +++++++++++++++++++++++++ __pycache__/hand_utils.cpython-312.pyc | Bin 0 -> 7379 bytes hand_utils.py | 94 ++++++++ 3 files changed, 406 insertions(+) create mode 100644 5-Card_Stud_Poker_Game (Final).py create mode 100644 __pycache__/hand_utils.cpython-312.pyc create mode 100644 hand_utils.py diff --git a/5-Card_Stud_Poker_Game (Final).py b/5-Card_Stud_Poker_Game (Final).py new file mode 100644 index 0000000..d98e539 --- /dev/null +++ b/5-Card_Stud_Poker_Game (Final).py @@ -0,0 +1,312 @@ +# 5-Card_Stud_Poker_Game.py + +import random +from collections import Counter +from hand_utils import evaluate_hand, Tuple, List, Card, SUITS, RANKS + +starting_chips = 0 # Default starting chips for each player +rank_counts = Counter() + +@staticmethod +def setup_players(): + num_players = int(input("Enter number of players (2–5): ")) + while True: + if 2 <= num_players <= 5: + break + else: + print("Invalid number of players. Please enter a number between 2 and 5.") + num_players = int(input("Enter number of players (2–5): ")) + + players = [] + for i in range(num_players): + name = input(f"Enter name for Player {i+1} (or press Enter for default): ").strip() + if not name: + name = f"Player {i+1}" + while True: + try: + starting_chips = int(input(f"Enter starting chips for {name}: ")) + if starting_chips <= 0: + print("Starting chips must be a positive number.") + continue + break + except ValueError: + print("Please enter a valid integer.") + player = Player(name=name, starting_chips=starting_chips) + player.active = True # Ensure player is active at start + players.append(player) + return players + +@staticmethod +def deal_round(deck, players, round_num): + print(f"\n--- Round {round_num} ---") + for player in players: + if not player.folded: + player.receive_card(deck.deal_card()) + print(player.show_hand()) + +@staticmethod +def determine_winner(players, pot): + best_score = (-1, -1) + + winner = None + pot = pot + + for player in players: + if player.folded: + player.active = False + continue + full_hand = [player.hole_card] + player.face_up_cards + score = evaluate_hand(full_hand) + print(f"{player.name} hand score: {score}") + if score > best_score: + best_score = score + winner = player + + if winner: + winner.chips += pot + print(f"\nšŸ† {winner.name} wins the pot with: {winner.reveal_full_hand()} and {winner.chips} chips") + +def deal_initial_cards(deck, players): + for player in players: + player.receive_hole_card(deck.deal_card()) # face-down + player.receive_face_up_card(deck.deal_card()) # first face-up + +@staticmethod +def betting_round(players, current_bet, pot): + print("\n--- Betting Round ---") + for player in players: + if player.folded: + continue + print(f"\n{player.name}'s turn. Current bet: {current_bet}. Chips: {player.chips}") + while True: + action = input(f"{player.name}, do you want to call, raise or fold 'c', 'r', or 'f'? ").strip().lower() + if action == 'f': + player.folded = True + print(f"{player.name} folds.") + # Check if only one player remains + active_players = [p for p in players if not p.folded and p.active and p.chips > 0] + if len(active_players) == 1: + winner = active_players[0] + winner.chips += pot + print(f"{winner.name} wins the pot of {pot} chips by default!") + show_final_stacks(players, pot) + continue_prompt(players) + break + else: + players.remove(player) # Remove folded player from active players + break + elif action == 'c': + try: + bet = player.place_bet(current_bet) + pot += bet + print(f"{player.name} calls and bets {bet}.") + break + except ValueError as e: + print(e) + elif action == 'r': + try: + raise_amount = int(input("Enter raise amount: ")) + total_bet = current_bet + raise_amount + bet = player.place_bet(total_bet) + current_bet = total_bet + pot += bet + print(f"{player.name} raises to {total_bet}.") + break + except ValueError as e: + print(e) + else: + print("Invalid action. Please enter 'call', 'raise', or 'fold', 'c', 'r' or 'f'.") + return current_bet, pot + +@staticmethod +def play(players): + deck = Deck() + deal_initial_cards(deck, players) + + current_bet = 10 + pot = 0 + + # Run betting round, determine winner, update chips + for round_num in range(1, 4): + print(f"\n--- Round {round_num} ---") + for player in players: + if not player.folded: + player.receive_face_up_card(deck.deal_card()) + print(player.show_hand()) + + current_bet, pot = betting_round(players=players, current_bet=current_bet, pot=pot) + + print("\n--- Showdown ---") + for player in players: + if not player.folded: + print(player.reveal_full_hand()) + + print(f"\nTotal pot: {pot}") + + # Simulate betting round + current_bet, pot = betting_round(players, current_bet, pot) + pot += current_bet * sum(1 for p in players if not p.folded) + + # Showdown + print("\n--- Showdown ---") + for player in players: + if not player.folded: + print(player.reveal_full_hand()) + + determine_winner(players, pot) + show_final_stacks(players, pot) + +def show_final_stacks(players, pot): + print(f"\nTotal pot: {pot}") + print("\n--- Final Chip Stacks ---") + for player in players: + print(f"{player.name}: {player.chips} chips") + continue_prompt(players) + +def continue_prompt(players): + response = input("Play another round? (y/n): ").lower() + if response == 'y': + add_new_players(players) + reset_round(players) + play(players) + return True + else: + print("Game over. Thanks for playing!") + exit() + return False + +def add_new_players(players): + while True: + name = input("Enter new player name (or press Enter to skip): ") + if not name: + reset_round(players) + play(players) + break + while True: + try: + starting_chips = int(input(f"Enter starting chips for {name}: ")) + if starting_chips <= 0: + print("Starting chips must be a positive number.") + continue + break + except ValueError: + print("Please enter a valid integer.") + continue + player = Player(name=name, starting_chips=starting_chips) + player.active = True # Ensure new player is active at start + print(f"{player.name} enters the game with {player.chips} chips.") + players.append(player) # Default starting chips for new players + reset_round(players) + play(players) + return players + +def reset_round(players): + for p in players: + if p.active and p.chips <= 0: + print(f"{p.name} is out of chips and removed from the game.") + players.remove(p) + continue + elif not p.active and p.chips > 0: + p.active = True + print(f"{p.name} re-enters the game with {p.chips} chips.") + # Discard active player's previous hands, reset round-specific flags + p.folded = False + p.active = True + p.hand = [] + p.face_up_cards = [] + p.hole_card = None + elif p.active and p.chips > 0: + p.folded = False + p.hand = [] + p.face_up_cards = [] + p.hole_card = None + +class Deck: + def __init__(self): + self.cards: List[Card] = [Card(rank, suit) for suit in SUITS for rank in RANKS] + random.shuffle(self.cards) + + def deal_card(self) -> Card: + return self.cards.pop(0) + + def deal_hand(self, num: int = 5) -> List[Card]: + hand = self.cards[:num] + del self.cards[:num] + return hand + +class Player: + def __init__(self, name: str, starting_chips: int): + self.name = name + self.hold_card: Card = None + self.face_up_cards: List[Card] = [] + self.hand: List[Card] = [] + self.folded = False + self.chips = starting_chips + self.active = False + + def place_bet(self, amount): + if amount > self.chips: + raise ValueError(f"{self.name} doesn't have enough chips to bet {amount}.") + self.chips -= amount + return amount + + def receive_hole_card(self, card): + self.hole_card = card + + def receive_face_up_card(self, card): + self.face_up_cards.append(card) + + + def show_hand(self): + return f"{self.name}: | {' | '.join(str(card) for card in self.face_up_cards)} |" + + def reveal_full_hand(self): + return f"{self.name}: | {str(self.hole_card)} | {' | '.join(str(card) for card in self.face_up_cards)} |" + + def hand_rank(self) -> Tuple[int, List[int]]: + return evaluate_hand(self.hand) + + def make_decision(self): + high_ranks = {'J', 'Q', 'K', 'A'} + visible_ranks = [card.rank for card in self.face_up_cards] + if any(rank in high_ranks for rank in visible_ranks): + return 'c' + else: + return 'f' + + +class Game: + def __init__(self): + self.deck = Deck() + self.players = setup_players() + for player in self.players: + player.hand = self.deck.deal_hand() + + def deal_initial_cards(deck, players): + for player in players: + player.receive_card(deck.deal_card()) # hole card + player.receive_card(deck.deal_card()) # first face-up card + + def deal_round(deck, players, round_num): + print(f"\n--- Round {round_num} ---") + for player in players: + if not player.folded: + player.receive_card(deck.deal_card()) + print(player.show_hand()) + + +def main(): + print("Welcome to 5-Card Stud Poker!") + players = setup_players() + while True: + play(players) # Pass persistent player list + if not continue_prompt(players): + break + + +if __name__ == "__main__": + main() + + + + \ No newline at end of file diff --git a/__pycache__/hand_utils.cpython-312.pyc b/__pycache__/hand_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37a56e818e20b8df559b8255652b109afe879411 GIT binary patch literal 7379 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 literal 0 HcmV?d00001 diff --git a/hand_utils.py b/hand_utils.py new file mode 100644 index 0000000..833ce66 --- /dev/null +++ b/hand_utils.py @@ -0,0 +1,94 @@ +# hand_utils.py + +from typing import List, Tuple +from collections import Counter + +SUITS = ['\u2665', '\u2663', '\u2660', '\u2666'] # Hearts, Clubs, Spades, Diamonds +RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] +RANK_VALUES = {rank: i for i, rank in enumerate(RANKS, 2)} + +class Card: + def __init__(self, rank: str, suit: str): + self.rank = rank.strip().upper() + self.suit = suit.strip().capitalize() + + def __repr__(self): + return f"{self.rank} of {self.suit}" + + def __eq__(self, other): + return isinstance(other, Card) and self.rank == other.rank and self.suit == other.suit + # return str(self) == str(other) # Alternative comparison method + +# Other utility functions for hand evaluation +def hand_rank_name(rank: int) -> str: + names = { + 9: "Royal Flush", + 8: "Straight Flush", + 7: "Four of a Kind", + 6: "Full House", + 5: "Flush", + 4: "Straight", + 3: "Three of a Kind", + 2: "Two Pair", + 1: "One Pair", + 0: "High Card" + } + return names.get(rank, "Unknown Hand") + +def get_rank_counts(ranks: List[int]) -> Counter: + return Counter(ranks) + +def is_flush(suits: List[str]) -> bool: + return len(set(suits)) == 1 + +def is_straight(ranks: List[int]) -> bool: + sorted_ranks = sorted(ranks, reverse=True) + return all(sorted_ranks[i] - 1 == sorted_ranks[i + 1] for i in range(len(sorted_ranks) - 1)) + +def evaluate_hand(hand: List[Card]) -> Tuple[int, List[int]]: + if len(hand) != 5: + raise ValueError("Hand must contain exactly 5 cards.") + + ranks = sorted([RANK_VALUES[card.rank] for card in hand], reverse=True) + suits = [card.suit for card in hand] + rank_counts = get_rank_counts(ranks) + + # Handle Ace-low straight + if ranks == [14, 5, 4, 3, 2]: + ranks = [5, 4, 3, 2, 1] + straight = True + else: + straight = is_straight(ranks) + + flush = is_flush(suits) + + if flush and ranks == [14, 13, 12, 11, 10]: + return (9, hand_rank_name(9), ranks) # Royal Flush + if flush and straight: + return (8, hand_rank_name(8), ranks) # Straight Flush + if 4 in rank_counts.values(): + four = max(rank for rank, count in rank_counts.items() if count == 4) + kicker = max(rank for rank in ranks if rank != four) + return (7, hand_rank_name(7), [four]*4 + [kicker]) # Four of a Kind + if 3 in rank_counts.values() and 2 in rank_counts.values(): + three = max(rank for rank, count in rank_counts.items() if count == 3) + pair = max(rank for rank, count in rank_counts.items() if count == 2) + return (6, hand_rank_name(6), [three]*3 + [pair]*2) # Full House + if flush: + return (5, hand_rank_name(5), ranks) # Flush + if straight: + return (4, hand_rank_name(4), ranks) # Straight + if 3 in rank_counts.values(): + three = max(rank for rank, count in rank_counts.items() if count == 3) + kickers = sorted([rank for rank in ranks if rank != three], reverse=True) + return (3, hand_rank_name(3), [three]*3 + kickers) # Three of a Kind + if list(rank_counts.values()).count(2) == 2: + pairs = sorted([rank for rank, count in rank_counts.items() if count == 2], reverse=True) + kicker = max(rank for rank in ranks if rank not in pairs) + return (2, hand_rank_name(2), pairs*2 + [kicker]) # Two Pair + if 2 in rank_counts.values(): + pair = max(rank for rank, count in rank_counts.items() if count == 2) + kickers = sorted([rank for rank in ranks if rank != pair], reverse=True) + return (1, hand_rank_name(1), [pair]*2 + kickers) # One Pair + return (0, hand_rank_name(0), ranks) # High Card +