From c6e189d392d27735dfba92062ad3b7df6a85d608 Mon Sep 17 00:00:00 2001 From: Donald Calloway Date: Thu, 18 Sep 2025 11:54:21 -0700 Subject: [PATCH] Final commit --- 5-Card_Stud_Poker.py | 220 +++++++++++++++++++++++++ __pycache__/hand_utils.cpython-313.pyc | Bin 0 -> 6669 bytes hand_utils.py | 78 +++++++++ poker_class_version.py | 2 +- 4 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 5-Card_Stud_Poker.py create mode 100644 __pycache__/hand_utils.cpython-313.pyc create mode 100644 hand_utils.py diff --git a/5-Card_Stud_Poker.py b/5-Card_Stud_Poker.py new file mode 100644 index 0000000..c7ae13c --- /dev/null +++ b/5-Card_Stud_Poker.py @@ -0,0 +1,220 @@ +# 5-Card_Stud_Poker.py + +import random +from collections import Counter +from hand_utils import evaluate_hand, Tuple, List, Card, SUITS, RANKS + +rank_counts = Counter() + +@staticmethod +def setup_players() -> List["Player"]: + num_players = int(input("Enter number of players (2–5): ")) + if not 2 <= num_players <= 5: + raise ValueError("Must be between 2 and 5 players.") + return [Player(f"Player {i+1}") for i in range(num_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): + best_score = (-1, -1) + winner = None + + for player in players: + if player.folded: + 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: + print(f"\nšŸ† {winner.name} wins with: {winner.reveal_full_hand()}") + +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 play(): + deck = Deck() + players = setup_players() + deal_initial_cards(deck, players) + + for round_num in range(2, 5): # 3 more face-up cards + # deal_round(deck, players, round_num) + 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()) + + print("\n--- Showdown ---") + for player in players: + print(player.reveal_full_hand()) + +def betting_round(players: list, current_bet: int, pot: int) -> tuple[int, int]: + print(f"\nCurrent bet: {current_bet}") + for player in players: + if player.folded: + continue + + print(f"\n{player.name}'s turn") + print(f"Visible cards: {' '.join(str(card) for card in player.face_up_cards)}") + decision = input("Choose action (call, raise, fold): ").strip().lower() + + if decision == 'fold': + player.folded = True + print(f"{player.name} folds.") + elif decision == 'call': + pot += current_bet + print(f"{player.name} calls {current_bet}.") + elif decision == 'raise': + try: + raise_amount = int(input("Enter raise amount: ")) + current_bet += raise_amount + pot += current_bet + print(f"{player.name} raises to {current_bet}.") + except ValueError: + print("Invalid raise amount. Treating as call.") + pot += current_bet + else: + print("Invalid input. Treating as fold.") + player.folded = True + + return current_bet, pot + + +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): + self.name = name + self.hold_card: Card = None + self.face_up_cards: List[Card] = [] + self.hand: List[Card] = [] + self.folded = False + + 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 'call' + else: + return 'fold' + +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") + +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!") + deck = Deck() + players = setup_players() + deal_initial_cards(deck, players) + + current_bet = 10 + pot = 0 + + for round_num in range(2, 5): + 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) + print(f"\nTotal pot: {pot}") + +if __name__ == "__main__": + main() + + + + \ No newline at end of file diff --git a/__pycache__/hand_utils.cpython-313.pyc b/__pycache__/hand_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f35f579e66358f9ad2133d1d9f9be22bf7c5bfb9 GIT binary patch literal 6669 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 literal 0 HcmV?d00001 diff --git a/hand_utils.py b/hand_utils.py new file mode 100644 index 0000000..d524aec --- /dev/null +++ b/hand_utils.py @@ -0,0 +1,78 @@ +# 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 + +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, ranks) # Royal Flush + if flush and straight: + return (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, [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, [three]*3 + [pair]*2) # Full House + if flush: + return (5, ranks) # Flush + if straight: + return (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, [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, 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, [pair]*2 + kickers) # One Pair + return (0, ranks) # High Card + diff --git a/poker_class_version.py b/poker_class_version.py index 6c4d43b..3f8378d 100644 --- a/poker_class_version.py +++ b/poker_class_version.py @@ -1,7 +1,7 @@ import random from typing import List, Tuple -SUITS = ['Hearts', 'Diamonds', 'Clubs', 'Spades'] +SUITS = ['Hearts', 'Diamonds', 'Clubs', 'Spades'] #'\u2665', '\u2663', '\u2660', '\u2666' 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)}