Aller au contenu

Un jeu de carte

Description du projet

Ce projet de difficulté intermédiaire se donne pour but de modéliser un jeu de carte à l'aide de Python. Dans un premier temps, on construit deux classes, Carte et Deck afin de représenter un jeu classique de 52 cartes. Puis, on implémente deux classes, Joueur et Bataille. Celles-ci nous permettent alors de faire jouer l'ordinateur à la bataille contre lui-même ou bien contre un joueur humain. N'oubliez pas d'importer la fonction randint.

1
from random import randint

La classe Carte

Une carte d'un jeu de 52 cartes possède une valeur qui est un nombre entier compris entre \(2\) et \(14\), et une couleur parmi "pique", "carreau", "cœur", "trèfle". Par convention, la carte dont la valeur est la plus élevée est l'as, associé à la valeur 14. On prendra dans ce projet la convention que la figure la plus forte est la Dame (associée à la valeur 13), puis le Roi (associé à la valeur 12), puis le valet (associé à la valeur 11). On pourra au choix prendre la convention du Roi maître à la couleur, mais ceci n'est pas obligatoire.

Étant donné une carte, on souhaite pouvoir l'afficher (par exemple "Dame de cœur"), et pouvoir la comparer à une autre carte (par exemple, "Reine de cœur bat roi de cœur", mais il y a égalité entre "Reine de cœur" et "Reine de pique").

  1. Quelles sont les données décrivant un objet de type Carte ?
  2. Pour chacunes des deux données précédentes, préciser quel type python permet de les représenter au mieux.
  3. Déduire des questions précédentes le code python d'une classe Carte. On n'indiquera pour le moment que la méthode __init__ de cette classe.
  4. D'après l'énoncé, quels sont les actions que l'on souhaite effectuer sur des cartes ?
1
2
class Carte:
    """Représente une carte d'un jeu"""

Méthodes de la classe

Méthode __str__

Écrire la méthode spéciale __str__ de la classe carte. Celle-ci s'applique à un objet de type Carte et en renvoie une représentation textuelle. Par exemple, si la variable c représente la carte "Dame de cœur", print(c) devra afficher "Dame de cœur". On pourra au choix tester l'attribut valeur d'une carte et renvoyer le nom de la figure correspondante ("Roi" pour 12 par exemple) à l'aide branchements conditionnels if/elif/else. Pour simplifire le code, on pourra utiliser de manière astucieuse un dictionnaire, dont les clés sont seront les valeurs possibles pour l'attribut valeur et les valeurs le texte correspondant.

1
2
3
4
def __str__(self):
    """ Carte -> str
    Méthode spéciale. Permet d'afficher la carte. """
    pass

Méthode bat

Écrire une méthode bat de la classe Carte qui étant donné deux cartes self et other, renvoie True si self bat other, et False sinon. Si jamais les deux cartes ont la même valeur, alors bat renverra la valeur spéciale None.

1
2
3
4
def bat(self, other):
    """ Carte, Carte -> bool | NoneType
    Compare deux cartes. Renvoie True si self bat other, False sinon. """
    pass

La classe Deck

Dans ce projet, un paquet de carte est constitué des 52 différentes cartes possibles. Ainsi, une variable de type Deck n'aura qu'un seul attribut, contenu. Celui-ci sera une liste de cartes. Lors de l'initialisation, on ajoutera automatiquement les 52 cartes au deck, dans l'ordre de votre choix.

Lorsque l'on manipule un deck, les actions principales à supporter sont de le mélanger, et de tirer une carte du deck. On peut éventuellement vouloir compter les cartes du deck, afin de savoir s'il est encore possible d'en tirer.

1
2
class Deck:
    """Représente un paquet de 52 cartes"""

Les méthodes de la classe Deck

Méthode tirer

Écrire une méthode tirer de la classe Deck qui renvoie la première carte du paquet. On pourra éventuellement utiliser un argument optionnel pour choisir l'indice de la carte tirée dans le deck. S'il n'est pas possible de tirer une carte du deck, alors on renverra la valeur spéciale None. Attention, après l'exécution de d.tirer(), l'attribut contenu du deck d doit avoir été modifié de telle sorte que la carte tirée ne s'y trouve plus.

1
2
3
4
def tirer(self):
    """ Deck -> Carte
    Tire une carte du deck """
    pass

Méthode melanger

Écrire une méthode melanger de la classe Deck. Étant donné un deck self, celle-ci permute aléatoirement les éléments de la liste self.contenu. Plusieurs algorithmes sont pour cela possibles :

  • si n = len(self.contenu), alors on peut tirer successivement n cartes du deck (en choisissant leurs indices aléatoirement) et les placer dans une liste de cartes temporaire (à l'aide de la méthode append). Puis, on mettra a jour l'attribut contenu de self.
  • il est possible de mélanger directement une liste à l'aide du module random.
1
2
3
4
def melanger(self):
    """ Deck -> NoneType
    Mélange le deck. """
    pass

Méthode __len__

Écrire une méthode spéciale __len__ de la classe Deck. Celle-ci renverra la taille de l'attribut contenu de l'objet self. Une fois cette méthode implémentée, l'intruction len(d) est équivalente à d.__len__().

1
2
3
4
def __len__(self):
    """ Deck -> int
    Renvoie la taille du deck """
    pass

Méthode __str__

Écrire une méthode __str__ de la classe Deck qui permet d'afficher toutes les cartes d'un deck. On concatènera le résultat de carte.__str__() pour toutes les carte du deck.

Remarque. On pourra utiliser la chaine de caractère "\n" pour représenter les retours à la ligne entre l'affichage de deux cartes.

1
2
3
4
def __str__(self):
    """ Deck -> str
    Renvoie une chaine de caractère décrivant le deck """
    pass

La classe Joueur

Les joueurs ont tous un nom, et possèdent dans leur main un tas de carte (une liste de Carte). Initiallement, un joueur ne possède aucune carte dans sa main. Chaque joueur doit pouvoir piocher une carte du deck central, joueur une carte sur le tapis, et ramasser un tas de carte qui se trouve sur le tapis (il peut y en avoir plus de deux en cas de bataille, par exemple).

Remarque. Il est possible d'utiliser un objet de type Deck pour l'attribut main, et d'implémenter directement certaines fonctionnalités dans la classe Deck. Cependant, inutile ici de faire très compliqué, une simple liste suffit.

1
2
class Joueur:
    """Représente un joueur"""

Méthodes de la classe Joueur

Méthode piocher_carte

Écrire une méthode piocher_carte de la classe Joueur qui étant donné un joueur self et un deck, simule la pioche d'une carte du deck par le joueur. Pour cela, on tire une carte du deck, puis on l'ajoute à la main. Les cartes piochées sont empilées les unes sur les autres, par convention la dernière carte piochée correspondra donc à l'indice le plus élevé dans main. En python, le dernier élément d'un tableau non vide a pour indice -1.

1
2
3
4
def piocher_carte(self, deck):
    """ Joueur, Deck -> NoneType
    Le joueur tire une carte du deck et l'ajoute à sa main. """
    pass

Méthode jouer_carte

Écrire une méthode jouer_carte de la classe Joueur qui simule l'action de déposer une carte au centre de la table. Le joueur self retire la première carte de son paquet et la renvoie. À l'issue de l'exécution de cette méthode, il y a une carte de moins dans self.main.

1
2
3
4
def jouer_carte(self):
    """ Joueur -> Carte
    Joue une carte de la main (celle au sommet). """
    pass

Méthode ramasser

Écrire une méthode ramasser de la classe Joueur qui étant donné un joueur self et une pile de cartes place toutes les cartes de la pile en dessous de la main du joueur. À l'issue de l'exécution de la méthode ramasser, la pile sera vide et la main du joueur aura augmenté d'autant de cartes. On pourra utiliser la méthode insert du type python list (il y a d'autres méthodes, avec la concaténation de listes par exemple).

1
2
3
4
def ramasser(self, carte1, carte2):
    """ Joueur, Carte, Carte -> Nonetype
    Ajoute les deux cartes en dessous de la main. """
    pass

Une partie ? La classe Bataille

Le jeu de la bataille fait s'affronter deux joueurs à l'aide d'un deck (mélangé, de préférence) : un joueur1 et un joueur2. Après la phase de distribution des cartes du deck, les joueurs jouent alternativement leurs cartes sur une pile centrale, initialement vide.

On pourra au choix implémenter une ou plusieurs des méthodes suivantes de la classe Bataille. Vous écrirez pour chacunes des méthodes des tests qui permettent de s'assurer de leur bon fonctionnement. Si vous ne parvenez pas au comportement souhaité, vous devez expliquer votre démarche et indiquer votre code défaillant.

  • une méthode distribuer : distribue le deck aux deux joueurs, en alternant une carte à l'un, une carte à l'autre.
  • une méthode jouer_tour : simule un tour de bataille. Les deux joueurs jouent une carte sur la pile centrale, puis, la plus forte des cartes l'emporte. Si jamais les deux cartes sont de même valeur, alors il y a bataille ! Chaque joueur dépose une carte sur la pile centrale, puis on recommence un tour. Il sera possible d'utiliser un appel récursif à la méthode jouer_tour.
  • une méthode fin_partie : on vérifie si la bataille est terminée ou non. Plusieurs cas peuvent survenir :
    • soit un des deux joueurs n'a plus de cartes dans sa main, dans ce cas c'est l'autre joueur qui est déclaré gagnant ;
    • soit un joueur ne peut pas déposer une carte alors qu'il doit en déposer une (cela peut arriver lors d'une bataille, par exemple). Dans ce cas c'est l'autre joueur qui est déclaré gagnant.
  • une méthode run : on joue les tours les uns après les autres tant que la partie n'est pas terminée. Une fois que la partie est terminée, on renvoie le joueur gagnant. On pourra limiter le nombre maximum de coups à 1000 pour éviter les batailles trop longues.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Bataille:
    """Simule le jeu de la bataille entre deux joueurs"""
    def __init__(self, j1, j2):
        """Bataille, Joueur, Joueur -> NoneType"""
        pass

    def distribuer(self):
        """ Bataille -> NoneType
        Distribue le deck aux joueurs """
        pass

    def jouer_tour(self):
        """ Bataille -> NoneType
        Simule un tour de bataille """
        pass

    def fin_partie(self, joueur):
        """ Bataille, Joueur -> Joueur
        Vérifie si la partie est terminée. Si c'est le cas, renvoie le joueur gagnant. """
        pass

    def bataille(self):
        """ Bataille -> NoneType
        Déclenche une bataille """
        pass

    def run(self):
        """ Bataille -> Joueur
        Simule une bataille entre les deux joueurs """
        pass