Projet composition et agrégation d'objets : dés

Dans les deux précédents projets dés, vous avez codé des classes toutes seules. Dans cette partie on va coder plusieurs classes enchevêtrées.

Pour les besoins de ce projet, nous allons présupposer que vous avez une classe qui fonctionne. La version minimale que nous allons utiliser ici est disponible ci-après. Mais ne vous sentez pas obliger de l'utiliser.

une implémentation de la classe

fichier dé.py :

import random

class Dé:
    MIN_VALEUR = 1
    MAX_VALEUR = 6

    def __init__(self, valeur=1):
        self.valeur = valeur

    def lancer(self):
        self.valeur = random.randrange(self.MIN_VALEUR, self.MAX_VALEUR + 1)

        return self

    def __str__(self):
        if self.valeur == 1:
            return "⚀"
        elif self.valeur == 2:
            return "⚁"
        elif self.valeur == 3:
            return "⚂"
        elif self.valeur == 4:
            return "⚃"
        elif self.valeur == 5:
            return "⚄"
        else:
            return "⚅"

fichier test_dé.py :

fromimportdef test_init():
    assert isinstance((),)


def test_valeur():
    assert().valeur == 1
    assert(valeur=4).valeur == 4


def test_lancer():=().lancer()
    assert.MIN_VALEUR <=.valeur <=.MAX_VALEUR


def test_str():=()
    assert str() == "⚀".valeur = 4
    assert str() == "⚃"

Il va nous falloir manipuler 5 dés ensemble pour atteindre le but de notre projet :

But du projet

Jouer au poker d'as.

Nous n'atteindrons pas ce but à la fin du projet, mais libre à vous de le continuer et de le finir.

5 dés

Méthode naïve pour manipuler 5 dés : la liste de dés.

Pour illustrer cette étape et progresser dans notre projet de jeu, faisons une petite user story :

User Story

  • Nom : "jets de dés"
  • Utilisateur : un joueur compulsif
  • Story : On veut pouvoir lancer des dés et voir le résultat
  • Actions :
    1. créer une liste
    2. créer 5 dés que l'on ajoute un à un à la liste
    3. lancer les 5 dés
    4. afficher chaque dés de la liste

Créez la user story dans le fichier story_jets.py

Composition

L'utilisation d'une liste permet de grouper les 5 dés, mais il faut toujours les lancer individuellement. Cela pourrait être pratique de lancer automatiquement tous les dés.

Classe TapisVert

On aimerait avoir une structure, nommée TapisVert, qui :

  1. Proposez une modélisation UML de cette classe, montrez la relation qu'elle entretient avec la classe .
  2. modifier la user story "jets de dés" pour qu'elle utilise cette classe

corrigé

tapis vert

story_jets.py :

fromimport TapisVert

tapis_vert = TapisVert()

tapis_vert.lancer()

forin tapis_vert.dés:
    print()

Comme les classes TapisVert et sont différentes elles peuvent toutes les deux implémenter une méthode lancer() :

Codez la classe TapisVert dans le fichier dé.py.

corrigé

Fichier dé.py :


# ...

class TapisVert:
    def __init__(self):
        self.dés = []
        for i in range(5):
            self.dés.append(())

    def lancer(self):
        forin self.dés:.lancer()

# ...

Et on ajoute ses tests :

Ajoutez les tests de cette nouvelle classe au fichier test_dé.py. Vous pourrez par exemple tester :

  • qu'après la création d'un objet TapisVert on dispose bien de 5 dés de valeur 1.
  • qu'après avoir lancé les dés, leurs valeurs sont toujours cohérentes avec le nombre de faces.

corrigé

Fichier test_dé.py :


# ...

def test_tapis_vert_creation():
    tapis_vert = TapisVert()

    for d in tapis_vert.dés:
        assert d.valeur == 1

def test_tapis_vert_lancer():
    tapis_vert = TapisVert()
    tapis_vert.lancer()

    for d in tapis_vert.dés:
        assert 1 <= d.valeur <= 6


# ...

Affichage

On peut utiliser Dé.__str__() pour que TapisVert.__str__() soit facile à coder :

Créez une méthode TapisVert.__str__() qui permette d'écrire :

>>> fromimport TapisVert
>>> tapis_vert = TapisVert()
>>> print(tapis_vert)---->>> tapis_vert.lancer()
>>> print(tapis_vert)---->>> 

corrigé

On peut utiliser deux astuces python. La première est de construire une liste avec les représentations sous la forme d'une chaîne de caractères des dés. Par exemple :

>>> fromimport TapisVert
>>> tapis_vert = TapisVert()
>>>[str(x) for x in tapis_vert.dés]
['⚀', '⚀', '⚀', '⚀', '⚀']

Puis utiliser la méthode str.join de python qui est super utile pour concaténer des listes de chaînes de caractères :

>>> l = ["coucou", "les", "amis"]
>>> "*".join(l)
'coucou*les*amis'

Ces deux astuces nous permettent d'écrire le code :

class TapisVert:
    # ...

    def __str__(self):
        return " - ".join([str(x) for x in self.dés])

    # ...

Reconnaissance

Pour jouer au poker d'as, il nous faudra reconnaître des formes de jets de dés (comme les paires, ou encore les full). Créons une user story pour coder cette fonctionnalité :

User Story

  • Nom : "formes de jets"
  • Utilisateur : un joueur compulsif
  • Story : On veut pouvoir savoir quelles formes de dés sont présentes
  • Actions :
    1. jeter 5 dés
    2. vérifier s'il y a une paire, un brelan ou un carré
    3. recommencer en 1

Créez la story dans le fichier story_formes_dés.py.

Pour cela, l'utilisateur pourra appuyer sur la touche entrée pour lancer les dés d'un objet de type TapisVert, afficher les 5 dés et indiquer s'il y a une paire, un brelan ou un carré avec des méthodes TapisVert.possède_paire(), TapisVert.possède_brelan(), et TapisVert.possède_carré() qui rendent des booléens.

corrigé

fromimport TapisVert

tapis_vert = TapisVert()

while True:

    tapis_vert.lancer()
    print(tapis_vert)
    if tapis_vert.possède_paire():
        print("  A une paire")
    if tapis_vert.possède_brelan():
        print("  A un brelan")
    if tapis_vert.possède_carré():
        print("  A un carré")

    entrée = input("Appuyez sur la touche <entrée> pour recommencer")

Et maintenant le code des différentes méthodes à implémenter :

Ajoutez dans TapisVert les méthodes nécessaires et testez-les en simulant des lancers ayant une forme particulière.

Pour coder cela de façon simple, vous pourrez coder deux méthodes supports :

  • une méthode qui rend une liste $L$ de taille 7 telle que $L[i]$ donne le nombre de dés ayant la valeur $i$ ($1 \leq i \leq 6$)
  • une méthode qui prend en paramètre un nombre $n$ et qui rend True s'il existe au moins $n$ dés ayant la même valeur. Ceci permettra de coder de la même manière les différentes fonctions demandées.

corrigé

class TapisVert:

    # ...

    def _nombre_valeurs(self):
        count = [0] * 7
        forin self.dés:
            count[.valeur] += 1
        return count

    def nb_dés_valeurs_identiques(self, nb):
        comptes = self._nombre_valeurs()

        for i in range(len(comptes)):
            if comptes[i] >= nb:
                return True
        return False

    def possède_paire(self):
        return self.nb_dés_valeurs_identiques(2)

    def possède_brelan(self):
        return self.nb_dés_valeurs_identiques(3)

    def possède_carré(self):
        return self.nb_dés_valeurs_identiques(4)

Agrégation : Memento

Nous Allons créer un nouvel objet appelé permettant de sauver la valeur d'un dé puis de le restaurer si besoin. On va utiliser pour cela un patron de conception nommé Memento

Classe MementoDé

Le diagramme UML d'un Memento pour notre dé est le suivant :

memento dé

Lors de sa création il sauve la valeur du dé et la restore lors de l'appel de sa méthode MementoDé.restore().

Créez la classe MementoDé dans le fichier dés.py.

corrigé

class MementoDé:
    def __init__(self,):
        self.= dé
        self.valeur_sauvée =.valeur

    def restore(self):
        self..valeur = self.valeur_sauvée

Créez un test possible pour la classe MementoDé.

corrigé

fromimport, TapisVert, MementoDé

# ...

def test_mementoDé():=().valeur = 5
    memento = MementoDé().valeur = 1
    memento.restore()
    assert.valeur == 5

Le Memento est un outil formidable pour créer des undo/redo !

Classe MementoTapisVert

Finissons par mettre en oeuvre un Memento pour les TapisVert :

Créez la classe MementoTapisVert dans le fichier dés.py et son test dans le fichier test_dés.py

corrigé

Fichier dés.py :

# ...

class MementoTapisVert:
    def __init__(self, tapis_vert):
        self.tapis_vert = tapis_vert
        self.valeur_sauvée = [.valeur forin tapis_vert.dés]

    def restore(self):
        for, valeur_sauvée in zip(self.tapis_vert.dés, self.valeur_sauvée):.valeur = valeur_sauvée

Fichier test_dés.py :

fromimport, TapisVert, MementoDé, MementoTapisVert

# ...

def test_mementoTapisVert():
    tapis_vert = TapisVert()
    forin tapis_vert.dés:.valeur = 5
    memento = MementoTapisVert(tapis_vert)
    forin tapis_vert.dés:.valeur = 1
    memento.restore()
    forin tapis_vert.dés:
        assert.valeur == 5

Code final