1. Présentation
Wish Solitaire est un jeu de cartes simple, destiné à un seul joueur, qui nécessite un jeu de 32 cartes.
Pour commencer, mélangez les cartes et répartissez l'ensemble du jeu en 8 piles de 4 cartes chacune. Disposez les piles en colonne.
Retournez la carte du dessus de chaque pile afin qu'elles soient face visible. Ensuite, retirez toutes les paires de cartes de même valeur, quelle que soit leur couleur — par exemple, deux 10, deux rois, etc.
Une fois que vous avez retiré la carte du dessus d'une pile, retournez la carte suivante de la pile pour qu'elle soit face visible.
Le but du jeu est de faire disparaître toutes les piles en formant des paires de cartes.
Nous allons utiliser des classes et des objets en Python pour implémenter ce jeu.
2. Carte
2.1. Couleur d'une carte
Il n’existe que 4 couleurs de cartes : pique, cœur, carreau, trèfle. Une telle sorte de données (ensemble fini de valeurs nommées) est appelé énumération, et les langages de programmation ont en général un support spécifique pour ces types.
En Python, il faut définir une classe qui hérite de la classe Enum (voir la documentation).
from enum import Enum
class Suit(Enum):
SPADE = 1
HEART = 2
DIAMOND = 3
CLUB = 4
Par convention, les noms des membres de l'énumération sont en majuscule.
On voit qu'il est obligatoire de donner une valeur (pas obligatoirement un entier) aux membres de l'énumération. Quand on souhaite que cette valeur soit une chaine de caractères (les caractères “♠”, “♥”, “♦”, “♣” existent en Unicode), on peut utiliser la variante StrEnum , ce qui donne en utilisant la forme fonctionnelle :
from enum import StrEnum
Suit = StrEnum('Suit', [('SPADE', '♠'), ('HEART', '♥'), ('DIAMOND', '♦'), ('CLUB', '♣')])
Tester ce type avec quelques instructions. Par exemple, vous pouvez afficher une couleur ou comparer deux couleurs :
print(Suit.SPADE) print(Suit.SPADE == Suit.HEART)
2.2. Définition d'une carte
Nous utiliserons une classe Card pour représenter une carte à jouer. Un objet de cette classe possédera trois attributs : _suit (de type Suit), _order (un entier compris entre 1 et 13) et _hidden (un booléen). Les attributs _suit et _order définissent la carte de manière unique ; ils ne sont pas modifiables après l’initialisation, mais seront accessibles en tant que propriété (décorateur @property sur l’accesseur). L’attribut _hidden indique si la carte est face visible (False) ou face cachée (True). Au départ, la carte est visible ; deux méthodes, reveal(self) et hide(self), permettent de mettre à jour sa valeur.
Définir cette classe Card avec son constructeur et les méthodes indiquées.
2.3. Représentation d'une carte en chaîne de caractères
Ajouter une méthode __str__(self) pour convertir une carte en chaîne de caractères. Cette méthode est implicitement appelée par str(). Si la carte est cachée, elle sera représentée sous la forme [██]. Si la carte est face visible, la représentation doit ressembler à [9♠], [K♥], [A♦] ou [10♣], selon la valeur et la couleur.
Créer et afficher quelques cartes pour vérifier votre code.
Les cartes sont aussi disponibles comme caractères Unicode (bloc Unicode U+1F0A0 à U+1F0FF) : 🂩 🂾 🃁 🃚, mais quand elles sont affichées dans un terminal en taille standard (🂩 🂾 🃁 🃚), il peut être difficile de les reconnaitre.
2.4. Comparaison de cartes
Créer deux cartes identiques, et comparer-les avec ==. Ajouter une méthode __eq__(self, other) pour obtenir le résultat attendu.
Même si dans ce jeu, seule la valeur des cartes est prise en compte, la méthode de comparaison tiendra compte de la couleur (mais pas de sa visibilité) dans l'optique de pouvoir utiliser cette classe Card dans d'autres jeux.
3. Jeu
Nous avons besoin d'une classe représentant un jeu de 32 cartes (du 7 à l'as). Cette classe possédera un seul attribut : une liste de cartes.
Définir une classe Deck avec un constructeur qui initialise le jeu en créant les 32 cartes, puis les mélange.
Vous pouvez utiliser la fonction random.shuffle() de Python pour mélanger les éléments d'une séquence modifiable (telle qu'une liste).
Ajouter une méthode __str__() permettant d'afficher un jeu de cartes.
Ajouter une méthode pop() qui retire la carte du dessus du jeu et la retourne (None s'il n'y a plus de carte dans le jeu).
4. Pile
Nous avons besoin d'une classe représentant une pile de cartes. Cette classe possédera un seul attribut : une liste de cartes.
Définir une classe Stack avec un constructeur qui initialise la pile en piochant quatre cartes dans le jeu reçu en argument. Seule la carte du dessus est visible.
Ajouter une méthode __str__(self) afin de pouvoir afficher le contenu de la pile.
Ajouter une méthode __len__(self) qui retourne le nombre de cartes dans la pile.
Ajouter une méthode pop(self) qui retire et retourne la carte du dessus de la pile (None s'il n'y a plus de carte dans la pile). La carte suivante, s'il y en a une, est rendue visible.
5. Jeu
La classe Game maintient l'état du jeu sous la forme de 8 piles. Définir cette classe avec son constructeur et une méthode d'affichage conforme à l'exemple suivant :
0: [██][A♥]1: [██][██][██][Q♠]2: [██][██][██][K♥]3: [██][██][A♦]4: 5: [██][██][██][Q♥]6: [██][██][A♣]7: [██][██][10♣]
La méthode is_clearable(self, i, j) vérifie si les cartes du dessus des piles i et j ont la même valeur. La méthode renvoie False si au moins l'une des piles est vide ou si les cartes du dessus n'ont pas la même valeur.
La méthode clear(self, i, j) retire les cartes du dessus des piles i et j si ces cartes ont la meme valeur (en utilisant is_clearable(self, i, j)). Elle retourne True si les cartes ont été retirées, False sinon.
La méthode is_over(self) renvoie False s’il existe au moins deux piles dont les cartes du dessus peuvent être retirées, et True dans le cas contraire. Cette méthode vérifie toutes les paires de piles possibles.
La méthode is_cleared(self) renvoie True si toutes les piles ont été vidées, c'est-à-dire si elles ne contiennent plus aucune carte.
6. Programme principal
Si vous avez respecté les consignes, le programme suivant devrait permettre à un humain de jouer à 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()

