Jeu de dé, rotation sur un circuit (classes)

Ce notebook met en classe les fonctions développées dans le notebook Jeu de dé, rotation sur un circuit.

Le notebook utilise les annotations pour indiquer les types de variables et résultats la fonction ou méthode manipulent. L’interpréteur n’utilise pas ces informations. Elles peuvent être néanmoins utilisées par des outils comme mypy pour vérifier s’il n’existe pas des incohérences. D’autres outils comme black ou encore black-nb permettent de formatter le code. Le module ruff permet quant à lui de vérifier le code sans l’exécuter.

De

La classe minimaliste comprend un unique constructeur. Il initialise la classe et en particulier ces attributes.

[1]:
class De:
    def __init__(self, faces: list[int] | None = None):
        self.de = [2, 6, 5, 1, 4, 3] if faces is None else faces


d = De()
print(d)
<__main__.De object at 0x7fec374f5ed0>

L’affichage de la classe n’est pas très utile car python ne sait pas convertir une classe en une chaîne de caractères autrement qu’en retournant le type de la classe. Mais on peut définir cette conversion de sorte que str(d) retourne une chaîne qu’on aura construite.

[2]:
class De:
    def __init__(self, faces: list[int] | None = None):
        self.de = [2, 6, 5, 1, 4, 3] if faces is None else faces

    def __str__(self) -> str:
        return "De en chaîne"


de = De()
print(de)
De en chaîne

Il ne reste plus qu’à retourner une chaîne de caractères plus utile :

[3]:
class De:
    def __init__(self, faces: list[int] | None = None):
        self.de = [2, 6, 5, 1, 4, 3] if faces is None else faces

    def __str__(self) -> str:
        return f"""
           |   {self.de[4]}     |
        De | {self.de[0]} {self.de[1]} {self.de[2]} {self.de[3]} |
           |   {self.de[5]}     |
        """


de = De()
print(de)

           |   4     |
        De | 2 6 5 1 |
           |   3     |

Un autre example :

[ ]:
de_autre = De([6, 5, 4, 3, 2, 1])
print(de_autre)

On ajoute une méthode vérifiant la validité du dé, à savoir des faces toutes distinctes et des faces opposées dont la somme fait 7.

[4]:
class De:
    def __init__(self, faces: list[int] | None = None):
        self.de = [2, 6, 5, 1, 4, 3] if faces is None else faces

    def __str__(self) -> str:
        return f"""
           |   {self.de[4]}     |
        De | {self.de[0]} {self.de[1]} {self.de[2]} {self.de[3]} |
           |   {self.de[5]}     |
        """

    def valid(self) -> bool:
        sommes = [
            self.de[0] + self.de[2],
            self.de[1] + self.de[3],
            self.de[4] + self.de[5],
        ]
        return min(sommes) == max(sommes) == 7 and set(self.de) == set(range(1, 7))


d = De()
print(d.valid())
True

Permutation

Faire tourner un dé revient à appliquer une permutation sur l’ensemble des faces. On construit pour ce faire une classe permutation. Permutation(5) est la permutation identité (qui ne change rien) sur un ensemble de 5 éléments. On ajoute à cette classe une méthode applique qui permute les éléments d’un ensemble quelconque.

[5]:
class Permutation:
    def __init__(self, sigma_or_n: int | list[int]):
        if isinstance(sigma_or_n, int):
            self.sigma = list(range(sigma_or_n))
        elif isinstance(sigma_or_n, list):
            self.sigma = sigma_or_n
        else:
            # Ce code produit une erreur dès que le type du paramètre d'entrée
            # n'est ni un entier ni une liste.
            raise TypeError(f"unexpected type {type(sigma_or_n)}")

    def __str__(self) -> str:
        return " ".join([str(i) for i in self.sigma])

    def applique(self, ensemble: list) -> list:
        nouvel_ensemble = [None for i in ensemble]
        for position, s in enumerate(self.sigma):
            nouvel_ensemble[position] = ensemble[s]
        return nouvel_ensemble


p = Permutation([1, 2, 3, 0])
p.applique(["A", "B", "C", "D"])
[5]:
['B', 'C', 'D', 'A']

On vérifie que l’expression Permutation(5.5) produit une erreur en l’attrapant.

[9]:
try:
    p = Permutation(5.5)
except Exception as e:
    print(type(e), e)
<class 'TypeError'> unexpected type <class 'float'>

Une utilise maintenant cette classe pour réécrire les fonctions rotation1 et rotation2.

[10]:
def rotation(de: De, p: Permutation) -> De:
    return De(p.applique(de.de))


def rotation1(de: De) -> De:
    """
    [2 6 5 1 4 3] -> [1 2 6 5 4 3]
    """
    return rotation(de, Permutation([3, 0, 1, 2, 4, 5]))


def test_rotation1():
    de0 = de = De([2, 6, 5, 1, 4, 3])
    for i in range(4):
        de = rotation1(de)
        assert set(de.de) == set(de0.de)
    assert de.de == de0.de


test_rotation1()

Cette fonction applique une permutation à un dé. On pourrait en faire une méthode de la classe De.

[18]:
class De:
    def __init__(self, faces: list[int] | None = None):
        self.de = [2, 6, 5, 1, 4, 3] if faces is None else faces

    def __str__(self):
        return f"""
           |   {self.de[4]}     |
        De | {self.de[0]} {self.de[1]} {self.de[2]} {self.de[3]} |
           |   {self.de[5]}     |
        """

    def valid(self):
        sommes = [
            self.de[0] + self.de[2],
            self.de[1] + self.de[3],
            self.de[4] + self.de[5],
        ]
        return min(sommes) == max(sommes) == 7 and set(self.de) == set(range(1, 7))

    # L'annotation du résultat est "De" plutôt que De car le type doit exister
    # au moment où l'interpréteur lit la ligne. Or ce type De est en train d'être défini.
    def rotation(self, p: Permutation) -> "De":
        return De(p.applique(self.de))

    def rotation1(self) -> "De":
        """
        [2 6 5 1 4 3] -> [1 2 6 5 4 3]
        """
        return self.rotation(Permutation([3, 0, 1, 2, 4, 5]))

    def rotation2(self) -> "De":
        """
        [2 6 5 1 4 3] -> [4 6 3 1 5 2]
        """
        return self.rotation(Permutation([4, 1, 5, 3, 2, 0]))


def test_rotation1():
    r = De([2, 6, 5, 1, 4, 3]).rotation1().de
    expected = [1, 2, 6, 5, 4, 3]
    if r != expected:
        raise AssertionError(f"{r} != {expected}")


def test_rotation2():
    r = De([2, 6, 5, 1, 4, 3]).rotation2().de
    expected = [4, 6, 3, 1, 5, 2]
    if r != expected:
        raise AssertionError(f"{r} != {expected}")


test_rotation1()
test_rotation2()

Notebook on github