30. Wish Solitaire#

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.

30.1. Card#

30.1.1. Suit#

We start by creating a Suit class to represent the suit of a card. This class inherits from Python’s Enum class. You can copy and paste the following code to create it. Once this class is defined, it will allow us to manipulate suits as values.

Test the class by trying out some instructions. For example, you can compare two suit values: print(Suit.SPADE == Suit.HEART).

Next, complete the class by adding a __str__ method, which will convert a Suit object into a str. Use the following symbols, which are valid ASCII characters: “♠”, “♥”, “◆”, “♣”.

from enum import Enum
import random

class Suit(Enum):
    SPADE = 1
    HEART = 2
    DIAMOND = 3
    CLUB = 4

    def __str__(self):
        pass

30.1.2. Class Card#

Now, create a Card class to represent a playing card. This class will have an __init__(self, suit, order, hidden) method, which initializes three attributes: suit (of class Suit), order (an integer between [1, 13]), and hidden (a boolean). The attributes suit and order uniquely define the card, while the hidden attribute indicates whether the card is face-up (False) or face-down (True).

class Card(object):
    def __init__(self, suit, order, hidden):
        pass

30.1.3. Compare method#

Add the __int__ method to the class to allow cards to be compared converted into an int. The method return the order of a card. The method __int__ is implicitly called by the function int() when converting an object into an int.

Add the __eq__ method to the class to allow cards to be compared using the == operator. The comparison will be based on the order attribute, which can be easily checked by converting it to an int using int() function.

    def __int__(self):
        pass

    def __eq__(self, other):
        pass

Test this method with the following examples:

print(Card(Suit.SPADE, 12, False) == Card(Suit.HEART, 12, False)) # True
print(Card(Suit.SPADE, 6, False) == Card(Suit.SPADE, 12, False)) # False

30.1.4. Representation as string#

Add a __str__ method to convert a card into a string. This method is implicitly called by str(). If the card is hidden (attribute hidden is True), 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.

The symbols for the blank character and suits are part of the ASCII set. You can copy and paste them directly into your string representation.

    def __str__(self):
        pass

30.1.5. Reveal and Hide#

Lastly, add two methods to reveal and hide the card by modifying its hidden attribute.

    def reveal(self):
        pass

    def hide(self):
        pass

30.2. Deck#

30.2.1. Creating the Class#

We want to create a class representing a deck of 32 cards (from 7 to Ace). This class will have a single attribute: a list of Card objects.

Write an __init__ method that initializes the deck by creating all 32 cards, then shuffling them.

Hint: You can use Python’s random.shuffle function to shuffle the elements in a mutable sequence (such as a list).

30.2.2. Drawing a Card#

Next, add a pop method to the Deck class. This method should remove and return the top card from the deck. Since the list of cards acts as the deck, the top card will always be the last element of the list. Use the pop method from Python’s list to remove and return this card.

30.3. Stack#

We need to represent a stack of cards. A stack has one attribute, a list of Card objects, and four methods:

  • __init__(self, deck): Initializes a new stack using a deck of cards.

  • pop(self): Removes and returns the top card from the stack. If a card remains in the stack after removing the top card, the next card is revealed.

  • __len__(self): Returns the number of cards in the stack.

  • __str__(self): Returns a string representation of the stack, showing the hidden and revealed cards.

30.3.1. Initialization (__init__)#

The __init__(self, deck) method initializes a stack by drawing four cards from the deck. These cards are added to the stack, and by default, only the top card is revealed while the others remain hidden.

30.3.2. String Representation (__str__)#

The __str__(self) method converts the stack into a string that represents the cards in the stack. Cards that are hidden appear as [██], while the top (revealed) card is displayed with its rank and suit.

For example, a stack with three hidden cards and one revealed card (a King of Hearts) will be displayed as: [██][██][██][ K♥].

30.3.3. Pop Method (pop)#

The pop(self) method removes and returns the top card of the stack. After removing the top card, if there are still cards left in the stack, the new top card is revealed.

30.3.4. Length (__len__)#

The __len__(self) method simply returns the number of cards remaining in the stack.

30.4. The Game#

To implement the game, we create a Game class that encapsulates all the necessary functionality. This makes the code more readable and modular.

30.4.1. Initializing the Game (__init__)#

The __init__(self) method creates a Game object with a single attribute, stacks, which is a list of 8 Stack objects. These stacks are initialized using a new Deck object that contains 32 shuffled cards.

30.4.2. Representing the Game (__str__)#

The __str__(self) method represents the game as a string. It shows each stack with its index, followed by the cards in that stack. Each stack is displayed on a new line.

Example Output:

0: [██][ A♥]
1: [██][██][██][ Q♠]
2: [██][██][██][ K♥]
3: [██][██][ A◆]
4: [██][██][ 9♣]
5: [██][██][██][ Q♥]
6: [██][██][ A♣]
7: [██][██][10♣]

30.4.3. Checking if Cards are Clearable (is_clearable)#

The is_clearable(self, i, j) method checks if the top cards of stacks i and j are equal. The method returns False if at least one of the stacks is empty or if the top cards are not equal.

30.4.4. Clearing Cards (clear)#

The clear(self, i, j) method removes the top cards from stacks i and j if the cards are equal (checked using is_clearable). After removing the cards, it reveals the next top cards (if any) in each stack.

30.4.5. Checking if the Game is Over (is_over)#

The is_over(self) method returns True if there are at least two stacks with clearable top cards, and False otherwise. This method checks all possible pairs of stacks.

30.4.6. Checking if the Game is Cleared (is_cleared)#

The is_cleared(self) method returns True if all stacks have been cleared, meaning they contain no cards. It checks if the length of each stack is 0.

30.5. Main#

If you correctly implements the methods, the following mainfunction 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())
            g.clear(i, j)
        except ValueError:
            print("Input two indexes")
    print(g)
    if g.is_cleared():
        print("You win!")
    else:
        print("You lose!")

#main()