{"cells": [{"cell_type": "markdown", "id": "cf349c14", "metadata": {}, "source": ["# 2048 et les classes\n", "\n", "Le jeu [2048](https://play2048.co/) est assez addictif mais peut-on imaginer une strat\u00e9gie qui joue \u00e0 notre place est maximise le gain... Le jeu se joue sur une matrice *4x4*."]}, {"cell_type": "code", "execution_count": 1, "id": "ef14b2b0", "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "id": "e0e341cf", "metadata": {}, "source": ["## D\u00e9composition du probl\u00e8me\n", "\n", "0. Cr\u00e9ation de la matrice de jeu\n", "1. Ajout d'un nombre al\u00e9atoire dans `{2,4}` \u00e0 une position al\u00e9atoire pourvu qu'elle soit libre\n", "2. D\u00e9termination de toutes les cases libres\n", "3. A-t-on perdu ?\n", "4. Joue un coup sachant une direction donn\u00e9e\n", "5. Aggr\u00e8ge les nombres dans un tableau que ce soit une ligne ou une colonne\n", "6. Score...\n", "7. Choisit le coup suivant (un coup au hasard selon deux directions possibles)\n", "8. Joue une partie en appelant toutes les fonctions pr\u00e9c\u00e9dentes."]}, {"cell_type": "markdown", "id": "6d47e109", "metadata": {}, "source": ["### 0 - Cr\u00e9ation de la matrice de jeu"]}, {"cell_type": "code", "execution_count": 2, "id": "bc5cda97", "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [0, 0, 0, 0]])"]}, "execution_count": 3, "metadata": {}, "output_type": "execute_result"}], "source": ["import numpy\n", "\n", "def creer_jeu(dim):\n", " return numpy.zeros((dim, dim), dtype=int)\n", "\n", "jeu = creer_jeu(4)\n", "jeu"]}, {"cell_type": "markdown", "id": "a58daf2e", "metadata": {}, "source": ["### 2 - D\u00e9termination de toutes les cases libres"]}, {"cell_type": "code", "execution_count": 3, "id": "7b111cfd", "metadata": {}, "outputs": [{"data": {"text/plain": ["[(0, 0),\n", " (0, 1),\n", " (0, 2),\n", " (0, 3),\n", " (1, 0),\n", " (1, 1),\n", " (1, 2),\n", " (1, 3),\n", " (2, 0),\n", " (2, 1),\n", " (2, 2),\n", " (2, 3),\n", " (3, 0),\n", " (3, 1),\n", " (3, 2),\n", " (3, 3)]"]}, "execution_count": 4, "metadata": {}, "output_type": "execute_result"}], "source": ["def position_libre(jeu):\n", " pos = []\n", " for i in range(jeu.shape[0]):\n", " for j in range(jeu.shape[1]):\n", " if jeu[i, j] == 0:\n", " pos.append((i, j))\n", " return pos\n", "\n", "\n", "position_libre(jeu)"]}, {"cell_type": "markdown", "id": "cb56dec6", "metadata": {}, "source": ["### 1 - Ajout d'un nombre al\u00e9atoire dans {2,4} \u00e0 une position al\u00e9atoire pourvu qu'elle soit libre"]}, {"cell_type": "code", "execution_count": 4, "id": "8c66d10e", "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [0, 4, 0, 0]])"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["def nombre_aleatoire(jeu):\n", " pos = position_libre(jeu)\n", " nb = numpy.random.randint(0, 2) * 2 + 2\n", " i = numpy.random.randint(0, len(pos))\n", " p = pos[i]\n", " jeu[p] = nb\n", " \n", "nombre_aleatoire(jeu)\n", "jeu"]}, {"cell_type": "markdown", "id": "312ea544", "metadata": {}, "source": ["### 3 - A-t-on perdu ?"]}, {"cell_type": "code", "execution_count": 5, "id": "33564881", "metadata": {}, "outputs": [{"data": {"text/plain": ["False"]}, "execution_count": 6, "metadata": {}, "output_type": "execute_result"}], "source": ["def perdu(jeu):\n", " pos = position_libre(jeu)\n", " return len(pos) == 0\n", "\n", "perdu(jeu)"]}, {"cell_type": "markdown", "id": "b8086d68", "metadata": {}, "source": ["### 5 - Aggr\u00e8ge les nombres dans un tableau que ce soit une ligne ou une colonne"]}, {"cell_type": "code", "execution_count": 6, "id": "c0fce40f", "metadata": {}, "outputs": [{"data": {"text/plain": ["array([2, 4, 4, 0])"]}, "execution_count": 7, "metadata": {}, "output_type": "execute_result"}], "source": ["def joue_ligne_colonne(lc):\n", " # on enl\u00e8ve les 0\n", " non_null = [a for a in lc if a != 0]\n", " # on additionne les nombres identiques cons\u00e9cutifs\n", " i = len(non_null) - 1\n", " while i > 0:\n", " if non_null[i] != 0 and non_null[i] == non_null[i-1]:\n", " non_null[i-1] *= 2\n", " non_null[i] = 0\n", " i -= 2\n", " else:\n", " i -= 1\n", " # on enl\u00e8ve \u00e0 nouveau les z\u00e9ros\n", " non_null2 = [a for a in non_null if a != 0]\n", " final = numpy.zeros(len(lc), dtype=int)\n", " final[:len(non_null2)] = non_null2\n", " return final\n", "\n", "joue_ligne_colonne(numpy.array([2, 4, 2, 2]))"]}, {"cell_type": "code", "execution_count": 7, "id": "b0f2d2fe", "metadata": {}, "outputs": [], "source": ["assert joue_ligne_colonne(numpy.array([0, 2, 0, 2])).tolist() == [4, 0, 0, 0]\n", "assert joue_ligne_colonne(numpy.array([2, 2, 2, 2])).tolist() == [4, 4, 0, 0]\n", "assert joue_ligne_colonne(numpy.array([2, 4, 2, 2])).tolist() == [2, 4, 4, 0]"]}, {"cell_type": "markdown", "id": "143d5c47", "metadata": {}, "source": ["### 4 - Joue un coup sachant une direction donn\u00e9e"]}, {"cell_type": "code", "execution_count": 8, "id": "f2933759", "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [0, 0, 0, 0],\n", " [4, 0, 0, 0]])"]}, "execution_count": 9, "metadata": {}, "output_type": "execute_result"}], "source": ["def joue_coup(jeu, direction):\n", " if direction == 0: # gauche\n", " for i in range(jeu.shape[0]):\n", " jeu[i, :] = joue_ligne_colonne(jeu[i, :])\n", " elif direction == 1: # droite\n", " for i in range(jeu.shape[0]):\n", " jeu[i, ::-1] = joue_ligne_colonne(jeu[i, ::-1])\n", " elif direction == 2: # haut\n", " for i in range(jeu.shape[0]):\n", " jeu[:, i] = joue_ligne_colonne(jeu[:, i])\n", " elif direction == 3: # bas\n", " for i in range(jeu.shape[0]):\n", " jeu[::-1, i] = joue_ligne_colonne(jeu[::-1, i])\n", "\n", "joue_coup(jeu, 0)\n", "jeu"]}, {"cell_type": "markdown", "id": "a1894143", "metadata": {}, "source": ["### 6 - score"]}, {"cell_type": "code", "execution_count": 9, "id": "7d6a5697", "metadata": {}, "outputs": [{"data": {"text/plain": ["4"]}, "execution_count": 10, "metadata": {}, "output_type": "execute_result"}], "source": ["def score(jeu):\n", " return jeu.max()\n", " # \u00e0 ne pas confondre avec max(jeu)\n", " # max(jeu) appelle la fonction max de python (et non celle du numpy),\n", " # elle cherche le maximum sur toutes les lignes\n", " # et comparer deux lignes est ambig\u00fc, comparaison terme \u00e0 terme ? ce n'est pas un ordre total\n", " \n", "score(jeu)"]}, {"cell_type": "markdown", "id": "3bb9d978", "metadata": {}, "source": ["Voir [ordre total](https://fr.wikipedia.org/wiki/Ordre_total)."]}, {"cell_type": "markdown", "id": "13358dcb", "metadata": {}, "source": ["### 7 - coup suivant"]}, {"cell_type": "code", "execution_count": 10, "id": "102da10a", "metadata": {}, "outputs": [{"data": {"text/plain": ["2"]}, "execution_count": 11, "metadata": {}, "output_type": "execute_result"}], "source": ["def coup_suivant(jeu):\n", " # une direction al\u00e9atoire parmi 0 ou 4\n", " h = numpy.random.randint(0, 2)\n", " return h * 2\n", "\n", "\n", "coup_suivant(jeu)"]}, {"cell_type": "markdown", "id": "756afd5c", "metadata": {}, "source": ["### 8 - la fonction faisant tout"]}, {"cell_type": "code", "execution_count": 11, "id": "73ead8f4", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["nombre de coups: 68, score=64 jeu:\n", "[[64 32 16 4]\n", " [32 16 8 2]\n", " [ 2 8 4 2]\n", " [ 8 2 4 2]]\n"]}], "source": ["def partie(dim):\n", " jeu = creer_jeu(dim)\n", " coup = 0\n", " while not perdu(jeu):\n", " nombre_aleatoire(jeu)\n", " d = coup_suivant(jeu)\n", " joue_coup(jeu, d)\n", " coup += 1\n", " return coup, jeu\n", "\n", "coup, jeu = partie(4)\n", "print(\"nombre de coups: %d, score=%d jeu:\" % (coup, score(jeu)))\n", "print(jeu)"]}, {"cell_type": "markdown", "id": "0ba27ead", "metadata": {}, "source": ["## Classes"]}, {"cell_type": "code", "execution_count": 12, "id": "170bd17b", "metadata": {}, "outputs": [{"data": {"text/plain": ["coup=0 score=0, jeu=\n", "[[0 0 0 0]\n", " [0 0 0 0]\n", " [0 0 0 0]\n", " [0 0 0 0]]"]}, "execution_count": 13, "metadata": {}, "output_type": "execute_result"}], "source": ["class c2048:\n", " \n", " def __init__(self, dim=4):\n", " self.jeu = self.creer_jeu(dim)\n", " self.coup = 0\n", " self.score = 0\n", " \n", " def creer_jeu(self, dim):\n", " return creer_jeu(dim)\n", "\n", " def __repr__(self):\n", " return \"coup=%d score=%d, jeu=\\n%s\" % (self.coup, self.score, self.jeu)\n", " \n", "J = c2048()\n", "J"]}, {"cell_type": "code", "execution_count": 13, "id": "b0e51772", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["coup=0 score=0, jeu=\n", "[[0 0 0 0]\n", " [0 0 0 0]\n", " [0 0 0 0]\n", " [0 0 0 0]]\n"]}], "source": ["print(J) # l'interpr\u00e9teur python ex\u00e9cute implicitement : print(J.__repr__())"]}, {"cell_type": "markdown", "id": "56d57876", "metadata": {}, "source": ["## Classe compl\u00e8te"]}, {"cell_type": "code", "execution_count": 14, "id": "4e29bfa7", "metadata": {}, "outputs": [{"data": {"text/plain": ["coup=40 score=16, jeu=\n", "[[16 2 16 8]\n", " [ 2 16 4 2]\n", " [ 8 4 16 2]\n", " [ 4 2 8 4]]"]}, "execution_count": 15, "metadata": {}, "output_type": "execute_result"}], "source": ["class c2048:\n", " \n", " def __init__(self, dim=4):\n", " self.jeu = self.creer_jeu(dim)\n", " self.coup = 0\n", " self.score = 0\n", " \n", " def creer_jeu(self, dim):\n", " return creer_jeu(dim)\n", "\n", " def __repr__(self):\n", " return \"coup=%d score=%d, jeu=\\n%s\" % (self.coup, self.score, self.jeu)\n", " \n", " def position_libre(self):\n", " pos = []\n", " for i in range(self.jeu.shape[0]):\n", " for j in range(self.jeu.shape[1]):\n", " if self.jeu[i, j] == 0:\n", " pos.append((i, j))\n", " return pos\n", "\n", " def calcule_score(self):\n", " return self.jeu.max()\n", "\n", " def joue_ligne_colonne(self, lc):\n", " # on enl\u00e8ve les 0\n", " non_null = [a for a in lc if a != 0]\n", " # on additionne les nombres identiques cons\u00e9cutifs\n", " i = len(non_null) - 1\n", " while i > 0:\n", " if non_null[i] != 0 and non_null[i] == non_null[i-1]:\n", " non_null[i-1] *= 2\n", " non_null[i] = 0\n", " i -= 2\n", " else:\n", " i -= 1\n", " # on enl\u00e8ve \u00e0 nouveau les z\u00e9ros\n", " non_null2 = [a for a in non_null if a != 0]\n", " final = numpy.zeros(len(lc), dtype=int)\n", " final[:len(non_null2)] = non_null2\n", " return final\n", "\n", " def joue_coup(self, direction):\n", " if direction == 0: # gauche\n", " for i in range(self.jeu.shape[0]):\n", " self.jeu[i, :] = joue_ligne_colonne(self.jeu[i, :])\n", " elif direction == 1: # droite\n", " for i in range(self.jeu.shape[0]):\n", " self.jeu[i, ::-1] = joue_ligne_colonne(self.jeu[i, ::-1])\n", " # identique \u00e0\n", " # self.jeu[i, :] = joue_ligne_colonne(self.jeu[i, ::-1])[::-1]\n", " elif direction == 2: # haut\n", " for i in range(self.jeu.shape[0]):\n", " self.jeu[:, i] = joue_ligne_colonne(self.jeu[:, i])\n", " elif direction == 3: # bas\n", " for i in range(self.jeu.shape[0]):\n", " self.jeu[::-1, i] = joue_ligne_colonne(self.jeu[::-1, i])\n", " \n", " def nombre_aleatoire(self):\n", " pos = self.position_libre()\n", " nb = numpy.random.randint(0, 2) * 2 + 2\n", " i = numpy.random.randint(0, len(pos))\n", " p = pos[i]\n", " self.jeu[p] = nb\n", "\n", " def perdu(self):\n", " pos = self.position_libre()\n", " return len(pos) == 0\n", "\n", " def coup_suivant(self):\n", " # une direction al\u00e9atoire parmi 0 ou 4\n", " h = numpy.random.randint(0, 2)\n", " return h * 2\n", " \n", " def partie(self):\n", " self.coup = 0\n", " while not self.perdu():\n", " self.nombre_aleatoire()\n", " d = self.coup_suivant()\n", " self.joue_coup(d)\n", " self.coup += 1\n", " self.score = self.calcule_score()\n", " \n", "J = c2048()\n", "J.partie()\n", "J"]}, {"cell_type": "markdown", "id": "c0baddc8", "metadata": {}, "source": ["## Un dernier graphe pour finir\n", "\n", "On r\u00e9alise plusieurs parties, on trace un graphe avec le nombre de coups en abscisse et le score en ordonn\u00e9e."]}, {"cell_type": "code", "execution_count": 15, "id": "96828bc8", "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 500/500 [00:01<00:00, 282.10it/s]\n"]}, {"data": {"text/html": ["
"]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["df.plot(x='coup', y='score', kind='scatter', title=\"Score / Coup\");"]}, {"cell_type": "markdown", "id": "c646aa6a", "metadata": {}, "source": ["## Une autre strat\u00e9gie pour illustrer l'h\u00e9ritage\n", "\n", "On veut essayer une autre strat\u00e9gie et la comparer avec la pr\u00e9c\u00e9dente. Pour cela, on cr\u00e9e une seconde classe dans laquelle on remplace la m\u00e9thode `coup_suivant`. On pourrait tout copier coller mais c'est tr\u00e8s souvent la plus mauvaise option. Et pour \u00e9viter cela, on cr\u00e9e une seconde classe qui [h\u00e9rite](http://www.xavierdupre.fr/app/teachpyx/helpsphinx/c_classes/classes.html?highlight=h%C3%A9ritage#heritage) de la pr\u00e9c\u00e9dente, puis on remplace la m\u00e9thode souhait\u00e9e."]}, {"cell_type": "code", "execution_count": 17, "id": "57d15200", "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 500/500 [00:01<00:00, 299.41it/s]\n"]}, {"data": {"text/html": ["