1. Presentation
Wish Solitaire is a simple solo card game that requires a 32-card deck to play.
To begin, shuffle the cards and deal the entire deck into 8 piles of 4 cards each. Arrange the piles in a column.
Flip over the top card of each pile so they are face-up. Then, remove any pairs of cards that have the same rank, regardless of suit — for example, two 10s, two Kings, etc.
Once you remove the top card of a pile, flip the next card in the pile so it becomes face-up.
The goal of the game is to remove all the piles by matching pairs of cards.
We will use classes and objects in Python to implement this game.
You learned how to use Git during the last lab session; from now on, make it a habit to use it consistently.
2. Card
2.1. Suit of a card
There are only four card suits: spades, hearts, diamonds, and clubs. This kind of data (finite set of named values) is called an enumeration, and programming languages generally provide specific support for these types.
In Python, you need to define a class that inherits from the Enum class (see the documentation).
from enum import Enum
class Suit(Enum):
SPADE = 1
HEART = 2
DIAMOND = 3
CLUB = 4
By convention, the names of the members in the set are upper-cased.
As you can see, it is mandatory to assign a value (not necessarily an integer) to the members of the enumeration. If you want this value to be a string (the characters “♠”, “♥”, “♦”, and “♣” exist in Unicode), you can use the variant StrEnum, which results, when using the functional form, in:
from enum import StrEnum
Suit = StrEnum('Suit', [('SPADE', '♠'), ('HEART', '♥'), ('DIAMOND', '♦'), ('CLUB', '♣')])
- Test this type by trying out some instructions. For example, you can print a suit value, or compare two suit values:
print(Suit.SPADE) print(Suit.SPADE == Suit.HEART)
2.2. Definition of a card
We will use a Card class to represent a playing card. An object of this class will have three attributes: _suit (of type Suit), _order (an integer between 1 and 13), and _hidden (a boolean). The attributes _suit and _order uniquely define the card, they are not modifiable after initialization, but will be accessible as a property (decorator @property on the accessor). The _hidden attribute indicates whether the card is face-up (False) or face-down (True). Initially, the card will be visible; two methods, reveal(self) and hide(self) will update its value.
- Define this
Cardclass with its constructor and specified methods.
2.3. Representation of a card as string
- Add an
__str__(self)method to convert a card into a string. This method is implicitly called bystr(). If the card is hidden, it will be represented as[██]. If the card is face-up, the representation should be something like[9♠],[K♥],[A♦], or[10♣], depending on the rank and suit. - Create and display some cards to check your code.
Cards are also available as Unicode characters (Unicode block U+1F0A0 à U+1F0FF): 🂩 🂾 🃁 🃚, but when they are displayed in a terminal with the standard size (🂩 🂾 🃁 🃚), they can be hard to recognize.
2.4. Comparison of cards
- Create two identical cards and compare them using
==. Add a__eq__(self, other)method so that the result is the one expected.
Even though only the value of the cards is taken into account in this game, the comparison method will consider the color (but not whether it is visible) so that this Card class can be used in other games.
3. Deck
We need a class representing a deck of 32 cards (from 7 to Ace). This class will have a single attribute: a list of Card objects.
- Define a
Deckclass with a constructor that initializes the deck by creating all 32 cards, then shuffling them.
You can use Python’s random.shuffle() function to shuffle the elements in a mutable sequence (such as a list).
- Add an
__str__(self)method so that a deck can be displayed. - Add a
pop(self)method which remove and return the top card from the deck (Noneif the deck is empty).
4. Stack
We need a class representing a stack of cards. This class will have a single attribute: a list of Card objects.
- Define a
Stackclass with a constructor that initializes the stack by drawing four cards from the deck received as argument. Only the top card is revealed. - Add an
__str__(self)method so that a stack can be displayed. - Add a
__len__(self)method which returns the number of cards in the stack. - Add a
pop(self)method which remove and return the top card from the stack (Noneif the stack is empty). The next card, if available, is revealed.
5. Game
The Game class maintains the game state using 8 stacks.
- Define this class with a constructor and a display method that follows the example below:
0: [██][A♥]1: [██][██][██][Q♠]2: [██][██][██][K♥]3: [██][██][A♦]4: 5: [██][██][██][Q♥]6: [██][██][A♣]7: [██][██][10♣]
- The
is_clearable(self, i, j)method checks if the top cards of stacksiandjhave the same value. The method returnsFalseif at least one of the stacks is empty or if the top cards have not the same value. - The
clear(self, i, j)method removes the top cards from stacksiandjif the cards have the same value (checked usingis_clearable(self, i, j)). It returnTrueif the cards have been removed,Falseotherwise. - The
is_over(self)method returnsFalseif there are at least two stacks with clearable top cards, andTrueotherwise. This method checks all possible pairs of stacks. - The
is_cleared(self)method returnsTrueif all stacks have been cleared, meaning they contain no cards.
6. Main
If you correctly implements the methods, the following main function should allow a human player to play Wish Solitaire.
def main():
g = Game()
while not g.is_over():
print(g)
try:
i, j = map(int, input().split())
if 0 <= i < 8 and 0 <= j < 8:
if not g.clear(i, j):
print(f"Cannot removed cards from stacks {i} and {j}")
else:
print("Invalid stack number")
except ValueError:
print("Input two indexes")
print(g)
if g.is_cleared():
print("You win!")
else:
print("You lose!")
main()

