{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": "true" }, "source": [ "# Table of Contents\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "# But de ce notebook\n", "\n", "- Je vais expliquer les règles d'un jeu de carte, le \"Jap Jap\", qu'on m'a appris pendant l'été,\n", "- Je veux simuler ce jeu, en Python, afin de calculer quelques statistiques sur le jeu,\n", "- J'aimerai essayer d'écrire une petite intelligence artificielle permettant de jouer contre l'ordinateur,\n", "- Le but est de faire un prototype d'une application web ou mobile qui permettrait de jouer contre son téléphone !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "# Règles du *Jap Jap*\n", "\n", "## But du jeu\n", "- 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).\n", "- 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 !\n", "- On peut rendre le jeu plus long en comptant la première personne à perdre $x \\geq 1$ parties.\n", "\n", "## Début du jeu\n", "- Chaque personne reçoit 5 cartes,\n", "- et on révèle la première carte de la pioche.\n", "\n", "## Tour de jeu\n", "- Chaque personne joue l'une après l'autre, dans le sens horaire (anti trigonométrique),\n", "- 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),\n", "- 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.\n", "\n", "## Fin du jeu\n", "- 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.\n", " + Si elle est la seule personne à avoir une telle main de moins de $5$ points, elle gagne !\n", " + 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.\n", "- La personne qui a gagné ne marque aucun point, et les autres ajoutent à leur total actuel de point \n", "- Si quelqu'un atteint $90$ points, elle perd la partie." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "# Code du jeu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Code pour représenter une carte à jouer" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "coeur = \"♥\"\n", "treffle = \"♣\"\n", "pique = \"♠\"\n", "carreau = \"♦\"\n", "couleurs = [coeur, treffle, pique, carreau]" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Carte():\n", " def __init__(self, valeur, couleur):\n", " assert 1 <= valeur <= 13, \"Erreur : valeur doit etre entre 1 et 13.\"\n", " self.valeur = valeur\n", " assert couleur in couleurs, \"Erreur : couleur doit etre dans la liste {}.\".format(couleurs)\n", " self.couleur = couleur\n", " \n", " def __str__(self):\n", " val = str(self.valeur)\n", " if self.valeur > 10:\n", " val = {11: \"V\" , 12: \"Q\" , 13: \"K\"}[self.valeur]\n", " return \"{:>2}{}\".format(val, self.couleur)\n", " \n", " __repr__ = __str__\n", " \n", " def val(self):\n", " return self.valeur" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def valeur_main(liste_carte):\n", " return sum(carte.val() for carte in liste_carte)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import random\n", "\n", "def nouveau_jeu():\n", " jeu = [\n", " Carte(valeur, couleur)\n", " for valeur in range(1, 13+1)\n", " for couleur in couleurs\n", " ]\n", " random.shuffle(jeu)\n", " return jeu" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "[ 7♣, 7♥, Q♦, 8♣, 1♠]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ "35" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nouveau_jeu()[:5]\n", "valeur_main(_)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fin du jeu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour représenter la fin du jeu :" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class FinDuneManche(Exception):\n", " pass" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "class FinDunePartie(Exception):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Actions\n", "Pour représenter une action choisie par une personne :" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "class action():\n", " def __init__(self, typeAction=\"piocher\", choix=None):\n", " assert typeAction in [\"piocher\", \"choisir\", \"Jap Jap !\"]\n", " self.typeAction = typeAction\n", " assert choix is None or choix in [0, 1, 2, 3, 4]\n", " self.choix = choix\n", " \n", " def __str__(self):\n", " if self.est_piocher(): return \"Piocher\"\n", " elif self.est_japjap(): return \"Jap Jap !\"\n", " elif self.est_choisir(): return \"Choisir #{}\".format(self.choix)\n", "\n", " def est_piocher(self):\n", " return self.typeAction == \"piocher\"\n", "\n", " def est_choisir(self):\n", " return self.typeAction == \"choisir\"\n", "\n", " def est_japjap(self):\n", " return self.typeAction == \"Jap Jap !\"\n", "\n", "action_piocher = action(\"piocher\")\n", "action_japjap = action(\"Jap Jap !\")\n", "action_choisir0 = action(\"choisir\", 0)\n", "action_choisir1 = action(\"choisir\", 1)\n", "action_choisir2 = action(\"choisir\", 2)\n", "action_choisir3 = action(\"choisir\", 3)\n", "action_choisir4 = action(\"choisir\", 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Valider un coup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour savoir si une suite de valeurs est bien continue :" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def suite_valeurs_est_continue(valeurs):\n", " vs = sorted(valeurs)\n", " differences = [ vs[i + 1] - vs[i] for i in range(len(vs) - 1) ]\n", " return all([d == 1 for d in differences])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ "False" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "suite_valeurs_est_continue([5, 6, 7])\n", "suite_valeurs_est_continue([5, 7, 8])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour valider un coup choisie par une personne :" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "def valide_le_coup(jetees):\n", " if jetees is None or not (1 <= len(jetees) <= 5):\n", " return False\n", " # coup valide si une seule carte !\n", " elif len(jetees) == 1:\n", " return True\n", " # si plus d'une carte\n", " elif len(jetees) >= 2:\n", " couleurs_jetees = [carte.couleur for carte in jetees]\n", " valeurs_jetees = sorted([carte.valeur for carte in jetees])\n", " # coup valide si une seule couleur et une suite de valeurs croissantes et continues\n", " if len(set(couleurs_jetees)) == 1:\n", " return suite_valeurs_est_continue(valeurs_jetees)\n", " # coup valide si une seule valeur et différentes couleurs\n", " elif len(set(valeurs_jetees)) == 1:\n", " return len(set(couleurs_jetees)) == len(couleurs_jetees)\n", " return False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exemples de coups valides :" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur)])" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(5, coeur)])" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(5, coeur), Carte(3, coeur)])" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(5, coeur), Carte(3, coeur), Carte(2, coeur), Carte(6, coeur)])" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, carreau)])" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique)])" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique), Carte(4, treffle)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exemples de coups pas valides :" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(9, coeur)])" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, coeur), Carte(3, coeur)])" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(12, carreau)])" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique)])" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valide_le_coup([Carte(4, coeur), Carte(4, carreau), Carte(4, pique), Carte(4, treffle)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Jeu interactif\n", "On va utiliser les widgets ipython pour construire le jeu interactif !" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# Voir https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Asynchronous.html#Waiting-for-user-interaction\n", "%gui asyncio" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "\n", "def wait_for_change(widget, value):\n", " future = asyncio.Future()\n", " def getvalue(change):\n", " # make the new value available\n", " future.set_result(change.new)\n", " widget.unobserve(getvalue, value)\n", " widget.observe(getvalue, value)\n", " return future" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "import ipywidgets as widgets\n", "from IPython.display import display\n", "\n", "style = {\n", " 'description_width': 'initial',\n", "}\n", "style2boutons = {\n", " 'description_width': 'initial',\n", " 'button_width': '50vw',\n", "}\n", "style3boutons = {\n", " 'description_width': 'initial',\n", " 'button_width': '33vw',\n", "}\n", "style4boutons = {\n", " 'description_width': 'initial',\n", " 'button_width': '25vw',\n", "}\n", "style5boutons = {\n", " 'description_width': 'initial',\n", " 'button_width': '20vw',\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour savoir quoi jouer :" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "def piocher_ou_choisir_une_carte_visible():\n", " return widgets.ToggleButtons(\n", " options=[\"Une carte dans la pioche \", \"Une carte du sommet de la défausse \"],\n", " index=0,\n", " tooltips=[\"invisible\", \"visibles\"],\n", " icons=[\"question\", \"list-ol\"],\n", " description=\"Action ?\",\n", " style=style4boutons,\n", " )" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c3bbc981b9ff453588a3ba326b7b9db4", "version_major": 2, "version_minor": 0 }, "text/plain": [ "ToggleButtons(description='Action ?', icons=('question', 'list-ol'), options=('Une carte dans la pioche ', 'Un…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Choix : 0\n" ] } ], "source": [ "bouton = piocher_ou_choisir_une_carte_visible()\n", "display(bouton)\n", "print(\"Choix :\", bouton.index)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour savoir quoi jeter :" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[10♥, V♥, V♠]" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exemple_de_main = [Carte(10, coeur), Carte(11, coeur), Carte(11, pique)]\n", "exemple_de_main" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def faire_japjap(main):\n", " return widgets.ToggleButton(\n", " value=False,\n", " description=\"Jap Jap ? ({})\".format(valeur_main(main)),\n", " button_style=\"success\",\n", " tooltip=\"Votre main vaut moins de 5 points, donc vous pouvez terminer la partie !\",\n", " icon=\"check\",\n", " style=style,\n", " )" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7160585f2792460cbd7d1a8e22bc81e3", "version_major": 2, "version_minor": 0 }, "text/plain": [ "ToggleButton(value=False, button_style='success', description='Jap Jap ? (32)', icon='check', style=Descriptio…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Choix : False\n" ] } ], "source": [ "b = faire_japjap(exemple_de_main)\n", "display(b)\n", "print(\"Choix :\", b.value)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def quoi_jeter(main):\n", " return widgets.SelectMultiple(\n", " options=main,\n", " index=[0],\n", " description=\"Quoi jeter ?\",\n", " style=style,\n", " )" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4dad7172210240be98a08648c5b0d842", "version_major": 2, "version_minor": 0 }, "text/plain": [ "SelectMultiple(description='Quoi jeter ?', index=(0,), options=(10♥, V♥, V♠), style=DescriptionStyle(descrip…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Choix : (0,)\n" ] } ], "source": [ "b = quoi_jeter(exemple_de_main)\n", "display(b)\n", "print(\"Choix :\", b.index)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "from IPython.display import display" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "def valider_action():\n", " return widgets.ToggleButton(description=\"Valider l'action ?\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour savoir quoi piocher :" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[ V♠, 10♣]" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exemple_de_visibles = [Carte(11, pique), Carte(10, treffle)]\n", "exemple_de_visibles" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "def quoi_prendre(visibles):\n", " return widgets.ToggleButtons(\n", " options=visibles,\n", " #index=0,\n", " description=\"Prendre quelle carte du sommet ?\",\n", " style=style,\n", " )" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "scrolled": true }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ac52dce9214b4e8ea5855c91c54b1f0e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "ToggleButtons(description='Prendre quelle carte du sommet ?', options=( V♠, 10♣), style=ToggleButtonsStyle(des…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "quoi_prendre(exemple_de_visibles)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On va tricher et afficher les cartes avec `display(Markdown(...))` plutôt que `print`, pour les avoir en couleurs." ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Alice] Cartes en main : [ 6♦, 5♠, V♣, V♠, 1♣]\n" ] }, { "data": { "text/markdown": [ "[Alice] Cartes en main : [ 6♦, 5♠, V♣, V♠, 1♣]" ], "text/plain": [ "