From b8e92eedcf33aeb41d3cd72bdff874c18d944239 Mon Sep 17 00:00:00 2001 From: Chris Proctor Date: Fri, 19 Dec 2025 15:38:03 -0500 Subject: [PATCH] added demo game in chris_demo You had some really clear planning in your commit message-- In the "chris_demo" folder I implemented a bunch of what you planned. I suggest playing the game I built (go into chris_demo and run python game.py), and then carefully reading all the source code. Then, if you like the patterns I used, copy or move whatever you want to use into your main project folder. Also, if there are parts of my code you don't understand, please let me know (in a commit message or in person next time I'm in class) and I'll be happy to explain it. --- chris_demo/__pycache__/card.cpython-313.pyc | Bin 0 -> 7078 bytes chris_demo/__pycache__/dealer.cpython-313.pyc | Bin 0 -> 5156 bytes chris_demo/__pycache__/deck.cpython-313.pyc | Bin 0 -> 1412 bytes chris_demo/card.py | 146 ++++++++++++++++++ chris_demo/dealer.py | 88 +++++++++++ chris_demo/deck.py | 20 +++ chris_demo/game.py | 11 ++ 7 files changed, 265 insertions(+) create mode 100644 chris_demo/__pycache__/card.cpython-313.pyc create mode 100644 chris_demo/__pycache__/dealer.cpython-313.pyc create mode 100644 chris_demo/__pycache__/deck.cpython-313.pyc create mode 100644 chris_demo/card.py create mode 100644 chris_demo/dealer.py create mode 100644 chris_demo/deck.py create mode 100644 chris_demo/game.py diff --git a/chris_demo/__pycache__/card.cpython-313.pyc b/chris_demo/__pycache__/card.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf11b8672b5b8f94685fcb2b2ba6e8e78d4c52c3 GIT binary patch literal 7078 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 literal 0 HcmV?d00001 diff --git a/chris_demo/__pycache__/dealer.cpython-313.pyc b/chris_demo/__pycache__/dealer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1defc511bc9519b543cd9f5e1937c67b578fee5e GIT binary patch literal 5156 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 AV*mgE literal 0 HcmV?d00001 diff --git a/chris_demo/card.py b/chris_demo/card.py new file mode 100644 index 0000000..bf16a06 --- /dev/null +++ b/chris_demo/card.py @@ -0,0 +1,146 @@ + +suits = ['♠', '♥', '♦', '♣'] +colors = ['black', 'red', 'red', 'black'] +ranks = "A23456789TJQK" + +class Card: + """A card is not an agent itself, but it manages two agents + which are always next to each other, representing + the card as something like '♠A'. + """ + display = False + + def __init__(self, position, index, hidden=False, selected=False): + """When creating a card, you pass an index, which should be a number between + 0 and 51. If you imagine a deck of playing cards in order (all the spades from A-K, + then all the hearts from A-K, etc.), then the index is all we need to identify which + card this is. See the CardSuit and CardRank classes below for the math we can use + to convert an index value to a suit or a rank. + """ + self.index = index + self.name = str(index) + self.agents = [CardSuit(index, hidden, selected), CardRank(index, hidden, selected)] + self.set_position(position) + + def __str__(self): + return ''.join(a.character for a in self.agents) + + def __repr__(self): + return f"" + + def value(self): + """Returns how many points this card is worth. Currently treats aces as worth 1. + Later we might need a way of considering that an ace could be worth 1 or 11 in + blackjack. + """ + rank_index = self.index % 13 + return rank_index + 1 + + def set_position(self, position): + """When the Card's position gets set, it updates + the position of its suit and rank. In this way, we have one + agent (Card) which manages the positions of two agents on the + screen. + """ + x, y = position + suit, rank = self.agents + self.position = position + suit.position = position + rank.position = (x+1, y) + + def set_z(self, z): + suit, rank = self.agents + suit.z = z + rank.z = z + + def hide(self): + for agent in self.agents: + agent.hide() + + def show(self): + for agent in self.agents: + agent.show() + + def select(self): + for agent in self.agents: + agent.select() + + def deselect(self): + for agent in self.agents: + agent.deselect() + +class CardSuit: + def __init__(self, index, hidden=False, selected=False): + self.name = f"{index}_suit" + self.index = index + self.suit_color = colors[index // 13] + self.hidden = hidden + self.selected = selected + self.update_display() + + def select(self): + self.selected = True + self.update_display() + + def deselect(self): + self.selected = False + self.update_display() + + def show(self): + self.hidden = False + self.update_display() + + def hide(self): + self.hidden = True + self.update_display() + + def update_display(self): + if self.hidden: + self.character = "▒" + fg = "blue" + else: + self.character = suits[self.index //13] + fg = self.suit_color + if self.selected: + bg = "yellow" + else: + bg = "gray" + self.color = fg + "_on_" + bg + +class CardRank: + def __init__(self, index, hidden=False, selected=False): + self.name = f"{index}_rank" + self.index = index + self.suit_color = colors[index // 13] + self.hidden = hidden + self.selected = selected + self.update_display() + + def select(self): + self.selected = True + self.update_display() + + def deselect(self): + self.selected = False + self.update_display() + + def show(self): + self.hidden = False + self.update_display() + + def hide(self): + self.hidden = True + self.update_display() + + def update_display(self): + if self.hidden: + self.character = "▒" + fg = "blue" + else: + self.character = ranks[self.index % 13] + fg = self.suit_color + if self.selected: + bg = "yellow" + else: + bg = "gray" + self.color = fg + "_on_" + bg diff --git a/chris_demo/dealer.py b/chris_demo/dealer.py new file mode 100644 index 0000000..88cbb78 --- /dev/null +++ b/chris_demo/dealer.py @@ -0,0 +1,88 @@ +from deck import Deck + +class Dealer: + """The Dealer runs the game, following these rules: + + I have a card class that returns a card value + I have a dealer file that has a function that deals the first two cards + I'm still a little lost on how this works into the Game class + and how to organize the play. + I want to: + + 1. deal two cards to each dealer and player + 2. hide one of the dealers cards + 3. present the user with their cards and let them + choose * hit me OR * stay. + 4. If they hit, deal another card to them and calculate if they + went over 21. If they went over 21 end the game with BUST Dealer WINS!. + If they didn't go over 21, calculate their score and allow the dealer + their turn. + 5. If they stay, calculate their score and let the dealer have their turn. + 6. Dealer - checks their cards and compares them to the player. Then + chooses to hit or stay. Print choice to screen. If they hit, show the card + to the player. + """ + + display = False + playing = False + dealer_origin = (1, 3) + player_origin = (1, 5) + + def __init__(self, position): + self.position = position + self.dealer_cards = [] + self.player_cards = [] + + def play_turn(self, game): + if not self.playing: + self.set_up_new_round(game) + self.playing = True + + def handle_keystroke(self, keystroke, game): + game.log(keystroke) + if keystroke == "h": + self.deal_card_to_player() + game.state["score"] = self.get_player_score() + + def set_up_new_round(self, game): + self.deck = Deck(self.position) + for agent in self.deck.get_agents(): + game.add_agent(agent) + self.deal_card_to_dealer(hidden=True) + self.deal_card_to_dealer(hidden=False) + self.deal_card_to_player() + self.deal_card_to_player() + game.state["score"] = self.get_player_score() + + def deal_card_to_dealer(self, hidden=True): + card = self.deck.draw() + if hidden: + card.hide() + else: + card.show() + card.set_position(self.get_position_of_next_dealer_card()) + self.dealer_cards.append(card) + + def deal_card_to_player(self, hidden=False): + card = self.deck.draw() + if hidden: + card.hide() + else: + card.show() + card.set_position(self.get_position_of_next_player_card()) + self.player_cards.append(card) + + def get_position_of_next_player_card(self): + x, y = self.player_origin + return (x + 4 * len(self.player_cards), y) + + def get_position_of_next_dealer_card(self): + x, y = self.dealer_origin + return (x + 4 * len(self.dealer_cards), y) + + def get_player_score(self): + return sum([card.value() for card in self.player_cards]) + + def end_round(self, game): + for agent in self.deck.get_agents(): + game.remove_agent(agent) diff --git a/chris_demo/deck.py b/chris_demo/deck.py new file mode 100644 index 0000000..b9a7e84 --- /dev/null +++ b/chris_demo/deck.py @@ -0,0 +1,20 @@ +from card import Card +from random import shuffle + +class Deck: + def __init__(self, position, hidden=True, shuffled=True): + self.position = position + self.cards = [Card(position, i, hidden=hidden) for i in range(52)] + if shuffled: + shuffle(self.cards) + + def draw(self): + return self.cards.pop() + + def get_agents(self): + agents = [] + for card in self.cards: + agents.append(card) + agents += card.agents + return agents + diff --git a/chris_demo/game.py b/chris_demo/game.py new file mode 100644 index 0000000..4580853 --- /dev/null +++ b/chris_demo/game.py @@ -0,0 +1,11 @@ +from retro.game import Game +from dealer import Dealer + +dealer = Dealer((1, 1)) +state = { + "score": 0, + "options": "h to hit or s to stay", +} +game = Game([dealer], state) +game.play() +