solitaire_11.py Diff

solitaire_11.py
--- /home/docs/checkouts/readthedocs.org/user_builds/arcade-library/checkouts/latest/doc/tutorials/card_game/solitaire_10.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/arcade-library/checkouts/latest/doc/tutorials/card_game/solitaire_11.py
@@ -1,7 +1,9 @@
 """
 Solitaire clone.
 """
+
 import random
+
 import arcade
 
 # Screen title and size
@@ -69,10 +71,10 @@
 
 
 class Card(arcade.Sprite):
-    """ Card sprite """
+    """Card sprite"""
 
     def __init__(self, suit, value, scale=1):
-        """ Card constructor """
+        """Card constructor"""
 
         # Attributes for suit and value
         self.suit = suit
@@ -84,29 +86,29 @@
         super().__init__(FACE_DOWN_IMAGE, scale, hit_box_algorithm="None")
 
     def face_down(self):
-        """ Turn card face-down """
+        """Turn card face-down"""
         self.texture = arcade.load_texture(FACE_DOWN_IMAGE)
         self.is_face_up = False
 
     def face_up(self):
-        """ Turn card face-up """
+        """Turn card face-up"""
         self.texture = arcade.load_texture(self.image_file_name)
         self.is_face_up = True
 
     @property
     def is_face_down(self):
-        """ Is this card face down? """
+        """Is this card face down?"""
         return not self.is_face_up
 
 
 class MyGame(arcade.Window):
-    """ Main application class. """
+    """Main application class."""
 
     def __init__(self):
         super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 
         # Sprite list with all the cards, no matter what pile they are in.
-        self.card_list = None
+        self.card_list: arcade.SpriteList | None = None
 
         self.background_color = arcade.color.AMAZON
 
@@ -124,7 +126,7 @@
         self.piles = None
 
     def setup(self):
-        """ Set up the game here. Call this function to restart the game. """
+        """Set up the game here. Call this function to restart the game."""
 
         # List of cards we are dragging with the mouse
         self.held_cards = []
@@ -202,7 +204,7 @@
             self.piles[i][-1].face_up()
 
     def on_draw(self):
-        """ Render the screen. """
+        """Render the screen."""
         # Clear the screen
         self.clear()
 
@@ -213,21 +215,26 @@
         self.card_list.draw()
 
     def pull_to_top(self, card: arcade.Sprite):
-        """ Pull card to top of rendering order (last to render, looks on-top) """
+        """Pull card to top of rendering order (last to render, looks on-top)"""
 
         # Remove, and append to the end
         self.card_list.remove(card)
         self.card_list.append(card)
 
+    def on_key_press(self, symbol: int, modifiers: int):
+        """User presses key"""
+        if symbol == arcade.key.R:
+            # Restart
+            self.setup()
+
     def on_mouse_press(self, x, y, button, key_modifiers):
-        """ Called when the user presses a mouse button. """
+        """Called when the user presses a mouse button."""
 
         # Get list of cards we've clicked on
         cards = arcade.get_sprites_at_point((x, y), self.card_list)
 
         # Have we clicked on a card?
         if len(cards) > 0:
-
             # Might be a stack of cards, get the top one
             primary_card = cards[-1]
             assert isinstance(primary_card, Card)
@@ -235,7 +242,27 @@
             # Figure out what pile the card is in
             pile_index = self.get_pile_for_card(primary_card)
 
-            if primary_card.is_face_down:
+            # Are we clicking on the bottom deck, to flip three cards?
+            if pile_index == BOTTOM_FACE_DOWN_PILE:
+                # Flip three cards
+                for i in range(3):
+                    # If we ran out of cards, stop
+                    if len(self.piles[BOTTOM_FACE_DOWN_PILE]) == 0:
+                        break
+                    # Get top card
+                    card = self.piles[BOTTOM_FACE_DOWN_PILE][-1]
+                    # Flip face up
+                    card.face_up()
+                    # Move card position to bottom-right face up pile
+                    card.position = self.pile_mat_list[BOTTOM_FACE_UP_PILE].position
+                    # Remove card from face down pile
+                    self.piles[BOTTOM_FACE_DOWN_PILE].remove(card)
+                    # Move card to face up list
+                    self.piles[BOTTOM_FACE_UP_PILE].append(card)
+                    # Put on top draw-order wise
+                    self.pull_to_top(card)
+
+            elif primary_card.is_face_down:
                 # Is the card face down? In one of those middle 7 piles? Then flip up
                 primary_card.face_up()
             else:
@@ -254,27 +281,47 @@
                     self.held_cards_original_position.append(card.position)
                     self.pull_to_top(card)
 
+        else:
+            # Click on a mat instead of a card?
+            mats = arcade.get_sprites_at_point((x, y), self.pile_mat_list)
+
+            if len(mats) > 0:
+                mat = mats[0]
+                mat_index = self.pile_mat_list.index(mat)
+
+                # Is it our turned over flip mat? and no cards on it?
+                if (
+                    mat_index == BOTTOM_FACE_DOWN_PILE
+                    and len(self.piles[BOTTOM_FACE_DOWN_PILE]) == 0
+                ):
+                    # Flip the deck back over so we can restart
+                    temp_list = self.piles[BOTTOM_FACE_UP_PILE].copy()
+                    for card in reversed(temp_list):
+                        card.face_down()
+                        self.piles[BOTTOM_FACE_UP_PILE].remove(card)
+                        self.piles[BOTTOM_FACE_DOWN_PILE].append(card)
+                        card.position = self.pile_mat_list[BOTTOM_FACE_DOWN_PILE].position
+
     def remove_card_from_pile(self, card):
-        """ Remove card from whatever pile it was in. """
+        """Remove card from whatever pile it was in."""
         for pile in self.piles:
             if card in pile:
                 pile.remove(card)
                 break
 
     def get_pile_for_card(self, card):
-        """ What pile is this card in? """
+        """What pile is this card in?"""
         for index, pile in enumerate(self.piles):
             if card in pile:
                 return index
 
     def move_card_to_new_pile(self, card, pile_index):
-        """ Move the card to a new pile """
+        """Move the card to a new pile"""
         self.remove_card_from_pile(card)
         self.piles[pile_index].append(card)
 
-    def on_mouse_release(self, x: float, y: float, button: int,
-                         modifiers: int):
-        """ Called when the user presses a mouse button. """
+    def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
+        """Called when the user presses a mouse button."""
 
         # If we don't have any cards, who cares
         if len(self.held_cards) == 0:
@@ -286,7 +333,6 @@
 
         # See if we are in contact with the closest pile
         if arcade.check_for_collision(self.held_cards[0], pile):
-
             # What pile is it?
             pile_index = self.pile_mat_list.index(pile)
 
@@ -302,14 +348,18 @@
                     # Move cards to proper position
                     top_card = self.piles[pile_index][-1]
                     for i, dropped_card in enumerate(self.held_cards):
-                        dropped_card.position = top_card.center_x, \
-                                                top_card.center_y - CARD_VERTICAL_OFFSET * (i + 1)
+                        dropped_card.position = (
+                            top_card.center_x,
+                            top_card.center_y - CARD_VERTICAL_OFFSET * (i + 1),
+                        )
                 else:
                     # Are there no cards in the middle play pile?
                     for i, dropped_card in enumerate(self.held_cards):
                         # Move cards to proper position
-                        dropped_card.position = pile.center_x, \
-                                                pile.center_y - CARD_VERTICAL_OFFSET * i
+                        dropped_card.position = (
+                            pile.center_x,
+                            pile.center_y - CARD_VERTICAL_OFFSET * i,
+                        )
 
                 for card in self.held_cards:
                     # Cards are in the right position, but we need to move them to the right list
@@ -338,7 +388,7 @@
         self.held_cards = []
 
     def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
-        """ User moves mouse """
+        """User moves mouse"""
 
         # If we are holding cards, move them with the mouse
         for card in self.held_cards:
@@ -347,7 +397,7 @@
 
 
 def main():
-    """ Main function """
+    """Main function"""
     window = MyGame()
     window.setup()
     arcade.run()