<jupyter_start><jupyter_text>Table of Contents 1&nbsp;&nbsp;But de ce notebook2&nbsp;&nbsp;Règles du Jap Jap2.1&nbsp;&nbsp;But du jeu2.2&nbsp;&nbsp;Début du jeu2.3&nbsp;&nbsp;Tour de jeu2.4&nbsp;&nbsp;Fin du jeu3&nbsp;&nbsp;Code du jeu3.1&nbsp;&nbsp;Code pour représenter une carte à jouer3.2&nbsp;&nbsp;Fin du jeu3.3&nbsp;&nbsp;Actions3.4&nbsp;&nbsp;Valider un coup3.5&nbsp;&nbsp;Jeu interactif3.6&nbsp;&nbsp;Etat du jeu3.7&nbsp;&nbsp;Lancement du jeu intéractif4&nbsp;&nbsp;Conclusion ---- But de ce notebook- Je vais expliquer les règles d'un jeu de carte, le "Jap Jap", qu'on m'a appris pendant l'été,- Je veux simuler ce jeu, en Python, afin de calculer quelques statistiques sur le jeu,- J'aimerai essayer d'écrire une petite intelligence artificielle permettant de jouer contre l'ordinateur,- Le but est de faire un prototype d'une application web ou mobile qui permettrait de jouer contre son téléphone ! ---- Règles du *Jap Jap* But du jeu- Le *Jap Jap* se joue à $n \geq 2$ joueur-euse-s (désignées par le mot neutre "personne"), avec un jeu de $52$ cartes classiques (4 couleurs, 1 à 10 + vallet/dame/roi).- Chaque partie du *Jap Jap* jeu se joue en plusieurs manches. A la fin de chaque manche, une personne gagne et les autres marquent des points. Le but est d'avoir le moins de point possible, et la première personne a atteindre $90$ points a perdu !- On peut rendre le jeu plus long en comptant la première personne à perdre $x \geq 1$ parties. Début du jeu- Chaque personne reçoit 5 cartes,- et on révèle la première carte de la pioche. Tour de jeu- Chaque personne joue l'une après l'autre, dans le sens horaire (anti trigonométrique),- A son tour, la personne a le choix entre jouer normalement, ou déclencher la fin de jeu si elle possède une main valant $v \leq 5$ points (voir "Fin du jeu" plus bas),- Jouer normalement consiste à jeter *une ou plusieurs* ($x \in \{1,\dots,5\}$) cartes de sa main dans la défausse, et prendre *une* carte et la remettre dans sa main. Elle peut choisir la carte du sommet de la pioche (qui est face cachée), ou *une* des $x' \in \{1,\dots,5\}$ cartes ayant été jetées par la personne précédente, ou bien la première carte de la défausse si c'est le début de la partie. Fin du jeu- Dès qu'une personne possède une main valant $v \leq 5$ points, elle peut dire *Jap Jap !* au lieu de jouer à son tour.  + Si elle est la seule personne à avoir une telle main de moins de $5$ points, elle gagne !  + Si une autre personne a une main de moins de $5$ points, elle peut dire *Contre Jap Jap !*, à condition d'avoir *strictement* moins de points que le *Jap Jap !* ou le *Contre Jap Jap !* précédent. La personne qui remporte la manche est celle qui a eu le *Contre Jap Jap !* de plus petite valeur.- La personne qui a gagné ne marque aucun point, et les autres ajoutent à leur total actuel de point - Si quelqu'un atteint $90$ points, elle perd la partie. ---- Code du jeu  Code pour représenter une carte à jouer<jupyter_code>coeur   = "♥"
treffle = "♣"
pique   = "♠"
carreau = "♦"
couleurs = [coeur, treffle, pique, carreau]
class Carte():
    def __init__(self, valeur, couleur):
        assert 1 <= valeur <= 13, "Erreur : valeur doit etre entre 1 et 13."
        self.valeur = valeur
        assert couleur in couleurs, "Erreur : couleur doit etre dans la liste {}.".format(couleurs)
        self.couleur = couleur
        
    def __str__(self):
        val = str(self.valeur)
        if self.valeur > 10:
            val = {11: "V" , 12: "Q" , 13: "K"}[self.valeur]
        return "{:>2}{}".format(val, self.couleur)
    
    __repr__ = __str__
    
    def val(self):
        return self.valeur
def valeur_main(liste_carte):
    return sum(carte.val() for carte in liste_carte)
import random

def nouveau_jeu():
    jeu = [
        Carte(valeur, couleur)
        for valeur in range(1, 13+1)
        for couleur in couleurs
    ]
    random.shuffle(jeu)
    return jeu
nouveau_jeu()[:5]
valeur_main(_)<jupyter_output><empty_output><jupyter_text>Fin du jeu Pour représenter la fin du jeu :<jupyter_code>class FinDuneManche(Exception):
    pass
class FinDunePartie(Exception):
    pass<jupyter_output><empty_output><jupyter_text>ActionsPour représenter une action choisie par une personne :<jupyter_code>class action():
    def __init__(self, typeAction="piocher", choix=None):
        assert typeAction in ["piocher", "choisir", "Jap Jap !"]
        self.typeAction = typeAction
        assert choix is None or choix in [0, 1, 2, 3, 4]
        self.choix = choix
    
    def __str__(self):
        if self.est_piocher(): return "Piocher"
        elif self.est_japjap(): return "Jap Jap !"
        elif self.est_choisir(): return "Choisir #{}".format(self.choix)

    def est_piocher(self):
        return self.typeAction == "piocher"

    def est_choisir(self):
        return self.typeAction == "choisir"

    def est_japjap(self):
        return self.typeAction == "Jap Jap !"

action_piocher = action("piocher")
action_japjap = action("Jap Jap !")
action_choisir0 = action("choisir", 0)
action_choisir1 = action("choisir", 1)
action_choisir2 = action("choisir", 2)
action_choisir3 = action("choisir", 3)
action_choisir4 = action("choisir", 4)<jupyter_output><empty_output><jupyter_text>Valider un coup Pour savoir si une suite de valeurs est bien continue :<jupyter_code>def suite_valeurs_est_continue(valeurs):
    vs = sorted(valeurs)
    differences = [ vs[i + 1] - vs[i] for i in range(len(vs) - 1) ]
    return all([d == 1 for d in differences])
suite_valeurs_est_continue([5, 6, 7])
suite_valeurs_est_continue([5, 7, 8])<jupyter_output><empty_output><jupyter_text>Pour valider un coup choisie par une personne :<jupyter_code>def valide_le_coup(jetees):
    if jetees is None or not (1 <= len(jetees) <= 5):
        return False
    # coup valide si une seule carte !
    elif len(jetees) == 1:
        return True
    # si plus d'une carte
    elif len(jetees) >= 2:
        couleurs_jetees = [carte.couleur for carte in jetees]
        valeurs_jetees  = sorted([carte.valeur for carte in jetees])
        # coup valide si une seule couleur et une suite de valeurs croissantes et continues
        if len(set(couleurs_jetees)) == 1:
            return suite_valeurs_est_continue(valeurs_jetees)
        # coup valide si une seule valeur et différentes couleurs
        elif len(set(valeurs_jetees)) == 1:
            return len(set(couleurs_jetees)) == len(couleurs_jetees)
        return False<jupyter_output><empty_output><jupyter_text>Exemples de coups valides :<jupyter_code>valide_le_coup([Carte(4, coeur)])
valide_le_coup([Carte(4, coeur), Carte(5, coeur)])
valide_le_coup([Carte(4, coeur), Carte(5, coeur), Carte(3, coeur)])
valide_le_coup([Carte(4, coeur), Carte(5, coeur), Carte(3, coeur), Carte(2, coeur), Carte(6, coeur)])
valide_le_coup([Carte(4, coeur), Carte(4, carreau)])
valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique)])
valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique), Carte(4, treffle)])<jupyter_output><empty_output><jupyter_text>Exemples de coups pas valides :<jupyter_code>valide_le_coup([Carte(4, coeur), Carte(9, coeur)])
valide_le_coup([Carte(4, coeur), Carte(4, coeur), Carte(3, coeur)])
valide_le_coup([Carte(4, coeur), Carte(12, carreau)])
valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique)])
valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique), Carte(4, treffle)])<jupyter_output><empty_output><jupyter_text>Jeu interactifOn va utiliser les widgets ipython pour construire le jeu interactif !<jupyter_code># Voir https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Asynchronous.html#Waiting-for-user-interaction
%gui asyncio
import asyncio

def wait_for_change(widget, value):
    future = asyncio.Future()
    def getvalue(change):
        # make the new value available
        future.set_result(change.new)
        widget.unobserve(getvalue, value)
    widget.observe(getvalue, value)
    return future
import ipywidgets as widgets
from IPython.display import display

style = {
    'description_width': 'initial',
}
style2boutons = {
    'description_width': 'initial',
    'button_width': '50vw',
}
style3boutons = {
    'description_width': 'initial',
    'button_width': '33vw',
}
style4boutons = {
    'description_width': 'initial',
    'button_width': '25vw',
}
style5boutons = {
    'description_width': 'initial',
    'button_width': '20vw',
}<jupyter_output><empty_output><jupyter_text>Pour savoir quoi jouer :<jupyter_code>def piocher_ou_choisir_une_carte_visible():
    return widgets.ToggleButtons(
        options=["Une carte dans la pioche ", "Une carte du sommet de la défausse "],
        index=0,
        tooltips=["invisible", "visibles"],
        icons=["question", "list-ol"],
        description="Action ?",
        style=style4boutons,
    )
bouton = piocher_ou_choisir_une_carte_visible()
display(bouton)
print("Choix :", bouton.index)<jupyter_output><empty_output><jupyter_text>Pour savoir quoi jeter :<jupyter_code>exemple_de_main = [Carte(10, coeur), Carte(11, coeur), Carte(11, pique)]
exemple_de_main
def faire_japjap(main):
    return widgets.ToggleButton(
        value=False,
        description="Jap Jap ? ({})".format(valeur_main(main)),
        button_style="success",
        tooltip="Votre main vaut moins de 5 points, donc vous pouvez terminer la partie !",
        icon="check",
        style=style,
    )
b = faire_japjap(exemple_de_main)
display(b)
print("Choix :", b.value)
def quoi_jeter(main):
    return widgets.SelectMultiple(
        options=main,
        index=[0],
        description="Quoi jeter ?",
        style=style,
    )
b = quoi_jeter(exemple_de_main)
display(b)
print("Choix :", b.index)
from IPython.display import display
def valider_action():
    return widgets.ToggleButton(description="Valider l'action ?")<jupyter_output><empty_output><jupyter_text>Pour savoir quoi piocher :<jupyter_code>exemple_de_visibles = [Carte(11, pique), Carte(10, treffle)]
exemple_de_visibles
def quoi_prendre(visibles):
    return widgets.ToggleButtons(
        options=visibles,
        #index=0,
        description="Prendre quelle carte du sommet ?",
        style=style,
    )
quoi_prendre(exemple_de_visibles)<jupyter_output><empty_output><jupyter_text>On va tricher et afficher les cartes avec `display(Markdown(...))` plutôt que `print`, pour les avoir en couleurs.<jupyter_code>from IPython.display import Markdown
print("[Alice] Cartes en main : [ 6♦,  5♠,  V♣,  V♠,  1♣]")
# avec de la couleurs
display(Markdown("[Alice] Cartes en main : [ 6♦,  5♠,  V♣,  V♠,  1♣]"))<jupyter_output>[Alice] Cartes en main : [ 6♦,  5♠,  V♣,  V♠,  1♣]<jupyter_text>Maintenant on peut tout combiner.<jupyter_code>async def demander_action(visibles=None, main=None, stockResultat=None):
    display(Markdown("- Main : {} (valeur = {})".format(main, valeur_main(main))))
    display(Markdown("- Sommet de la défausse  {}".format(visibles)))
    fait_japjap = False
    # 1.a. si on peut faire jap jap, demander si on le fait ?
    if valeur_main(main) <= 5:
        print("Vous pouvez faire Jap Jap !")
        bouton3 = faire_japjap(main)
        validation = valider_action()
        display(widgets.VBox([bouton3, validation]))
        await wait_for_change(validation, 'value')
        # print("  ==> Choix :", bouton3.value)
        if bouton3.value:
            fait_japjap = True
            typeAction = "Jap Jap !"
            jetees = None
    # 1.b. quoi jouer
    if not fait_japjap:
        bouton1 = piocher_ou_choisir_une_carte_visible()
        validation = valider_action()
        display(widgets.VBox([bouton1, validation]))
        await wait_for_change(validation, 'value')
        piocher = bouton1.index == 0
        # print("  ==> Choix :", bouton1.value)
        # 2.a. si piocher, rien à faire pour savoir quoi piocher
        if piocher:
            print("Okay, vous piochez.")
            typeAction = "piocher"
            choix = None
        # 2.b. si choisir carte
        else:
            typeAction = "choisir"
            print("Okay, vous choisissez dans le sommet de la défausse.")
            if len(visibles) > 1:
                bouton2 = quoi_prendre(visibles)
                validation = valider_action()
                display(widgets.VBox([bouton2, validation]))
                await wait_for_change(validation, 'value')
                # print("  ==> Choix :", bouton2.index)
                choix = bouton2.index
            else:
                choix = 0
    # 3. choisir quoi jeter
    if typeAction != "Jap Jap !":
        if len(main) > 1:
            pas_encore_de_coup = True
            jetees = None
            bouton4 = quoi_jeter(main)
            validation = valider_action()
            display(widgets.VBox([bouton4, validation]))
            while pas_encore_de_coup or not valide_le_coup(jetees):
                await wait_for_change(validation, 'value')
                # print("  ==> Choix :", bouton4.index)
                jetees = bouton4.index
                pas_encore_de_coup = False
                if not valide_le_coup(jetees):
                    print("ERREUR ce coup n'est pas valide, on ne peut pas se débarasser de cet ensemble de cartes {}.".format(jetees))
        else:
            jetees = 0
    action_choisie = action(typeAction=typeAction, choix=choix)
    if stockResultat is not None:
        stockResultat["action_choisie"] = action_choisie
        stockResultat["jetees"] = jetees
    return action_choisie, jetees
if False:
    stockResultat = {"action_choisie": None, "jetees": None}

    asyncio.ensure_future(
        demander_action(
            visibles=exemple_de_visibles,
            main=exemple_de_main,
            stockResultat=stockResultat,
        )
    )

    stockResultat
def demander_action_et_donne_resultat(visibles=None, main=None):
    stockResultat = {"action_choisie": None, "jetees": None}

    asyncio.ensure_future(
        demander_action(
            visibles=visibles,
            main=main,
            stockResultat=stockResultat,
        )
    )

    return stockResultat["action_choisie"], stockResultat["jetees"]<jupyter_output><empty_output><jupyter_text>Etat du jeuMaintenant on peut représenter un état du jeu.<jupyter_code># on peut changer ici pour jouer moins longtemps !
scoreMax = 90
scoreMax = 10
class EtatJeu():
    def __init__(self, nbPersonnes=2, nomsPersonnes=None,
                 scoreMax=scoreMax, malusContreJapJap=25, nbCartesMax=5):
        assert 2 <= nbPersonnes <= 5, "Le nombre de personnes pouvant jouer doit etre entre 2 et 5."
        self.nbPersonnes = nbPersonnes
        self.nomsPersonnes = nomsPersonnes
        self.scoreMax = scoreMax
        self.malusContreJapJap = malusContreJapJap
        self.nbCartesMax = nbCartesMax
        # on initialise le stockage interne
        self.personnes = [personne for personne in range(nbPersonnes)]
        self.scores = [
            0 for personne in self.personnes
        ]
        self.mains = [
            [ ] for personne in self.personnes
        ]
        self.visibles = []
        self.jeu = nouveau_jeu()
    
    def montrer_information_visibles(self):
        print("- Nombre de carte dans la pioche :", len(self.jeu))
        print("- Cartes visibles au sommet de la défausse :", len(self.visibles))
        for personne in self.personnes:
            nom = self.nomsPersonnes[personne] if self.nomsPersonnes is not None else personne
            main = self.mains[personne]
            score = self.scores[personne]
            print("    + Personne {} a {} carte{} en main, et un score de {}.".format(
                nom, len(main), "s" if len(main) > 1 else "", score)
            )

    def montrer_information_privee(self, personne=0):
        main = self.mains[personne]
        nom = self.nomsPersonnes[personne] if self.nomsPersonnes is not None else personne
        display(Markdown("[{}] Carte{} en main : {}".format(nom, "s" if len(main) > 1 else "", main)))
    
    # --- Mécanique de pioche et distribution initiale
    
    def prendre_une_carte_pioche(self):
        if len(self.jeu) <= 0:
            raise FinDuneManche
        premiere_carte = self.jeu.pop(0)
        return premiere_carte
    
    def debut_jeu(self):
        self.distribuer_mains()
        premiere_carte = self.prendre_une_carte_pioche()
        self.visibles = [premiere_carte]
    
    def donner_une_carte(self, personne=0):
        premiere_carte = self.prendre_une_carte_pioche()
        self.mains[personne].append(premiere_carte)
        
    def distribuer_mains(self):
        self.mains = [
            [ ] for personne in self.personnes
        ]
        premiere_personne = random.choice(self.personnes)
        self.personnes = self.personnes[premiere_personne:] + self.personnes[:premiere_personne]
        for nb_carte in range(self.nbCartesMax):
            for personne in self.personnes:
                self.donner_une_carte(personne)
    
    # --- Fin d'une manche
    
    def fin_dune_manche(self):
        self.jeu = nouveau_jeu()
        self.debut_jeu()
    
    # --- Enchainer les tours de jeux
    
    async def enchainer_les_tours(self):
        try:
            indice_actuel = 0
            while len(self.jeu) > 0:
                # dans la même manche, on joue chaque tour, pour la personne actuelle
                personne_actuelle = self.personnes[indice_actuel]
                
                # 1. on affiche ce qui est public, et privé
                self.montrer_information_visibles()
                self.montrer_information_privee(personne_actuelle)
                
                # 2. on demande l'action choisie par la personne
                # action_choisie, jetees = demander_action_et_donne_resultat(
                action_choisie, jetees = await demander_action(
                    visibles = self.visibles,
                    main = self.mains[personne_actuelle],
                )

                # 3. on joue l'action
                self.jouer(
                    personne = personne_actuelle,
                    action = action_choisie,
                    indices = jetees,
                )
                
                # personne suivante
                indice_actuel = (indice_actuel + 1) % self.nbPersonnes
            if len(self.jeu) <= 0:
                print("\nIl n'y a plus de cartes dans la pioche, fin de la manche sans personne qui gagne.")
                raise FinDuneManche
        except FinDuneManche:
            print("\nFin d'une manche.")
            fin_dune_manche()
        except FinDunePartie:
            print("\n\nFin d'une partie.")
    
    # --- Un tour de jeu
    
    def jouer(self, personne=0, action=action_piocher, indices=None):
        print("  ? Personne {} joue l'action {} avec les indices {} ...".format(personne, action, indices))  # DEBUG
        if indices is not None:
            jetees = [ self.mains[personne][indice] for indice in indices ]
            assert valide_le_coup(jetees)
        # et on en prend une nouvelle
        if action.est_piocher():
            # soit celle face cachée en sommet de pioche
            premiere_carte = self.prendre_une_carte_pioche()
            display(Markdown("=> Vous piochez la carte {}.".format(premiere_carte)))
            self.mains[personne].append(premiere_carte)
        if action.est_choisir():
            # soit une des cartes précedemment visibles
            choix = action.choix
            carte_choisie = self.visibles.pop(choix)
            display(Markdown("=> Vous récupérez la carte {}.".format(carte_choisie)))
            self.mains[personne].append(carte_choisie)
        if action.est_japjap():
            # on vérifie que cette personne a bien un Jap Jap !
            valeur_du_premier_japjap = valeur_main(self.mains[personne])
            assert 1 <= valeur_du_premier_japjap <= 5
            gagnante = personne
            display(Markdown("=> Vous faites un Jap Jap, valant {} point{}.".format(valeur_du_premier_japjap, "s" if valeur_du_premier_japjap > 1 else "")))
            contre_JapJap = False

            # on vérifie les valeurs des mains des autres personnes
            valeurs_mains = [valeur_main(main) for main in self.mains]
            plus_petite_valeur = min([valeurs_mains[autre_personne] for autre_personne in [ p for p in personnes if p != gagnante ]])
            if plus_petite_valeur < valeur_du_premier_japjap:
                print("CONTRE JAP JAP !")
                # si une personne a un jap jap plus petit, la personne ne gagne pas
                contre_JapJap = True
                # la personne gagnante est la première (ordre du jeu) à obtenir le jap jap
                # de valeur minimale, et en cas d'égalité c'est la personne obtenant
                # cette valeur en le nombre minimal de cartes !
                gagnantes = [ p for p in personnes if valeurs_mains[p] == plus_petite_valeur ]
                print("Les autres personnes ayant un Jap Jap de plus petite valeur sont {} !".format(gagnantes))
                nombre_min_carte = min([len(self.mains[p]) for p in gagnantes])
                gagnante = min([p for p in gagnantes if len(self.mains[p]) == nombre_min_carte])
                print("La personne gagnant la manche est {}.".format(gagnante))

            # on marque les scores
            print("\nOn marque les scores !")
            print("==> La personne {} a gagné ! Avec un Jap Jap de valeur {} !".format(gagnante, plus_petite_valeur))
            for autre_personne in [ p for p in personnes if p != gagnante ]:
                marque_point = valeur_main(self.mains[autre_personne])
                print("- La personne {} n'a pas gagné, et marque {} points".format(autre_personne, marque_point))
                self.scores[autre_personne] += marque_point
            # si la personne s'est prise un contre jap jap, elle marque +25 et pas son total de cartes en main
            print("- La personne {} n'a pas gagné et a subi un CONTRE JAP JAP ! Elle marque +25 points.")
            if contre_JapJap:
                self.scores[personne] -= valeur_main(self.mains[personne])
                self.scores[personne] += self.malusContreJapJap
            
            print("\nA la fin de cette manche :")
            for personne in self.personnes:
                nom = self.nomsPersonnes[personne] if self.nomsPersonnes is not None else personne
                score = self.scores[personne]
                print("    + Personne {} a un score de {}.".format(nom, score))
                
            # si un score est >= 90
            if max(self.scores) >= self.scoreMax:
                # quelqu'un a perdu cette partie !
                for personne in personnes:
                    score = self.scores[personne]
                    if score == max(self.scores):
                        nom = self.nomsPersonnes[personne] if self.nomsPersonnes is not None else personne
                        print("\n==> La personne {} a perdu, avec un score de {}.".format(nom, score))
                raise FinDunePartie
            raise FinDuneManche
        # on pose les cartes jetées
        self.visibles = jetees
        # et on enlève les cartes jetées de sa main
        nouvelle_main = self.mains[personne]
        for carte_jetee in jetees:
            nouvelle_main.remove(carte_jetee)
        # et ça continue<jupyter_output><empty_output><jupyter_text>Lancement du jeu intéractif<jupyter_code>jeu = EtatJeu(nomsPersonnes=["Alice", "Bob"])
jeu.debut_jeu()
import asyncio
asyncio.ensure_future(jeu.enchainer_les_tours())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10283178360101437, 'fraction_numerical': 0.009847844463229078, 'mean_word_length': 2.7610872675250357, 'pattern_counts': {'":': 5, '<': 72, '<?xml version=': 0, '>': 80, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '285942', 'license': 'MIT', 'n_tokens_mistral': 8166, 'n_tokens_neox': 8025, 'n_words': 2116, 'path': 'Simulations du jeu de carte Jap-Jap.ipynb', 'repo_name': 'doc22940/notebooks-2', 'size': '23660'}