Premiere NSI / Projets / Projet 1

Jeu du morpion

Projet n°1 : JEU DU MORPION

illustration

Principe de l’algorithme

La plupart des jeux de “plateau” à deux joueurs suivent le même principe :

Ce processus se répète jusqu’à ce qu’un des joueurs remporte la partie, ou que la partie s’achève sur une égalité.

Comme on répète tour à tour le même schéma avec chaque joueur, il est intéressant de programmer ce principe par une boucle. Et comme on ne sait pas à l’avance en combien de tours la partie se termine, on utilise une boucle Tant que.

Dans le cas d’un jeu numérique, il faut également afficher l’état du plateau de jeu à chaque tour. Cet état peut être mémorisé dans un tableau à deux dimensions (un tableau de tableaux).

Dans le cas du jeu du Morpion, on peut mémoriser l’état initial du plateau du jeu dans le tableau suivant.

jeu = [['.', '.', '.'],
       ['.', '.', '.'],
       ['.', '.', '.']]

Analyse plus fine et décomposition du problème

Dans un projet, la première phase consiste toujours à décomposer le problème en différentes tâches simples.

Pour cela, il est nécessaire de réfléchir de manière plus fine au problème en détaillant davantage le principe de l’algorithme. C’est cette analyse qui va permettre d’identifier les différentes tâches attendues dans notre algorithme/programme.

Principe de l’algorithme

Voici le principe de notre algorithme :

On affiche le plateau
On définit qui joue en premier
Tant que la partie n'est pas gagnée :
    Le joueur joue
    On affiche le plateau après son coup
    On vérifie s'il a gagné
        Si oui, la partie est gagnée et le jeu s'arrête ; 
        Sinon, c'est au tour de l'autre joueur. 

Décomposition en tâches

On peut désormais identifier les tâches principales à accomplir pour programmer notre jeu. Chacune de ces tâches sera programmée par une fonction. Voici les fonctions principales nécessaires :

On utilisera les trois variables suivantes :

Entrées et sorties des différentes fonctions

Les fonctions principales étant identifiées, il faut maintenant réfléchir aux entrées et aux sorties de chacune d’elle : c’est-à-dire aux paramètres de la fonction (de quoi a-t-elle besoin pour travailler ?) et aux valeurs renvoyées par la fonction (que doit-elle renvoyer ?)

La fonction afficher_plateau

La fonction afficher_plateau a besoin de connaître le contenu du tableau jeu pour afficher le plateau de jeu à l’écran. Et c’est tout ! Ce sera donc son seul paramètre et cette fonction ne renvoie rien (elle affiche quelque chose uniquement).

def afficher_plateau(jeu):
    """Affiche le contenu du tableau jeu."""
    # sera à compléter !

Exemple :

>>> jeu = [['X', 'O', '.'], ['.', '.', '.'], ['.', '.', 'X']]
>>> afficher_plateau(jeu)
['X', 'O', '.']
['.', '.', '.']
['.', '.', 'X']

Exemple (amélioration possible de l’affichage) :

>>> jeu = [['X', 'O', '.'], ['.', '.', '.'], ['.', '.', 'X']]
>>> afficher_plateau(jeu)
X | O |  
--+---+--
  |   |  
--+---+--
  |   | X
Voir l'aide n°1/2

Cherchez dans le notebook d’exercices sur les tableaux (Thème 2, Chapitre 2).

Voir l'aide n°2/2

Voici le début d’une fonction envisageable

def afficher_plateau(jeu):
    print(jeu[0][0], "|", jeu[0][1], "|", jeu[0][2])
    print("--+---+--")
    # à compléter

La fonction jouer

La fonction jouer a pour rôle de demander à l’utilisateur dans quelle ligne et quelle colonne il veut jouer, puis elle doit actualiser et donc modifier l’état du tableau jeu en conséquence. Cette fonction a donc a besoin de ce tableau en paramètre. De plus, cette fonction a besoin de connaître le contenu de la variable joueur (de type str) pour écrire soit 'X' soit 'O' dans le tableau jeu. Elle possède donc deux paramètres (jeu et joueur) et ne renvoie rien.

def jouer(jeu, joueur):
    """Demande à l'utilisateur la case à jouer et met à jour le tableau jeu."""
    # sera à compléter !

Exemple :

>>> jeu = [['X', 'O', '.'], ['.', '.', '.'], ['.', '.', 'X']] 
>>> jouer(jeu, 'O')  # on suppose que le joueur choisit de jouer dans la case centrale
Quelle colonne veux-tu jouer ? (0, 1 ou 2) : 1
Quelle ligne veux-tu jouer ? (0, 1 ou 2) : 1
>>> jeu
[['X', 'O', '.'], ['.', 'O', '.'], ['.', '.', 'X']]
Voir l'aide n°1/2

Pour demander à l’utilisateur de choisir une case à cocher, on peut lui demander de saisir un numéro de colonne et un numéro de ligne. Par exemple, pour lui demander un numéro de colonne, on peut utiliser l’instruction suivante (cette instruction est la première ligne de la fonction attendue).

choix_colonne = int(input("Quelle colonne veux-tu jouer ? (0, 1 ou 2) : "))
Voir l'aide n°2/2

Voici une fonction incomplète (et pas forcément satisfaisante).

def jouer(joueur, jeu):
    choix_colonne = int(input("Quelle colonne veux-tu jouer ? (0, 1 ou 2) : "))
    ... 
    if joueur == 'X': 
        jeu[...][...] = 'X'
    else:
        ...

La fonction verifier_victoire

La fonction verifier_victoire a besoin de connaître l’état du plateau donc le tableau jeu pour déterminer si la partie est gagnée. Et c’est tout, ce sera son seul paramètre. Cette fonction doit indiquer si la partie est gagnée ou non, elle renverra donc un booléen (VRAI si la partie est gagnée et FAUX sinon).

def verifier_victoire(jeu):
    """Renvoie True si la partie est gagnée et False sinon."""
    # sera à compléter !

Exemples :

>>> jeu = [['X', 'X', 'X'], ['O', '.', '.'], ['O', '.', '.']]
>>> verifier_victoire(jeu)
True
>>> jeu = [['O', '.', '.'], ['.', 'X', '.'], ['X', '.', '.']]
>>> verifier_victoire(jeu)
False
Voir l'aide n°1/3

La vérification de victoire peut se faire en trois étapes :

Voir l'aide n°2/3

On peut écrire une fonction verifier_victoire_horizontale(jeu) qui renvoie True s’il y a victoire horizontale et False sinon.

Voici une ébauche de cette fonction. Celle-ci est incomplète et pas forcément satisfaisante en l’état (à vous de trouver pourquoi et de l’améliorer).

def verifier_victoire_horizontale(jeu):
    if (jeu[0][0] == jeu[0][1] == jeu[0][2]) or ... or ... : 
        return ...
    else: 
        return ...
Voir l'aide n°3/3

Il faut procéder de même en écrivant deux autres fonctions pour tester les victoires verticales et diagonales. Ensuite, on peut écrire la fonction verifier_victoire(jeu) en utilisant les trois fonctions de vérifications (horizontale, verticale, diagonale).

def verifier_victoire(jeu): 
    return ...

La fonction changer_joueur

La fonction changer_joueur a besoin de connaître qui vient de jouer pour passer à l’autre joueur. Et c’est tout, donc elle n’a besoin que du paramètre joueur. Cette fonction doit aussi modifier le contenu de la variable joueur donc elle doit renvoyer une valeur : la chaîne de caractères 'X' ou la chaîne de caractères 'O'.

def changer_joueur(joueur):
    """Renvoie la chaîne 'X' si joueur vaut 'O' et la chaîne 'O' si joueur vaut 'X'."""
    # sera à compléter !
Voir l'aide n°1/1

Relisez bien la spécification de cette fonction. Il y a deux cas à distinguer…

Écriture de l’algorithme

L’algorithme final du jeu peut alors s’écrire de la façon suivante, dans lequel on utilise les variables jeu, joueur, gagne, ainsi que les fonctions afficher_plateau(), jouer(), verifier_victoire(), changer_joueur() définies précédemment.

afficher_plateau(jeu)
joueur ← 'X'                           # le joueur 'X' commence (arbitraire)
gagne ← FAUX                           # il n'y a pas de gagnant au départ
Tant que non gagne faire               # tant qu'il n'y a pas de gagnant
    Afficher "Au tour de", joueur
    jouer(jeu, joueur)                 # Le joueur propose sa case et maj du plateau
    afficher_plateau(jeu)
    si verifier_victoire(jeu)           # Si verifie_victoire(jeu) renvoie VRAI
    alors
        gagne ← VRAI                   # le booleen gagne prend la valeur VRAI (ce qui stoppera la boucle while)
        Afficher "Le joueur", joueur, "a gagné !"
    sinon
        joueur ← changer_joueur(joueur) # sinon on passe au joueur suivant

À vous de jouer !

Démarche attendue

La démarche de projet est toujours sensiblement la même, quel que soit le projet :

Étape 1 : Répartition des tâches principales = qui fait quoi ?

Répartissez-vous les différentes fonctions afficher_plateau(), jouer(), verifier_victoire(), changer_joueur()

Étape 2 : Travail individuel pour implémenter (= programmer) la (ou les) fonction(s) qui vous est (sont) attribuée(s). Voici les étapes à suivre pour implémenter une fonction :
- Commencez par bien comprendre ce qu’elle doit faire, en donnant différents exemples d’appels et ce qu’ils doivent produire/renvoyer
- Programmez la fonction (1ère tentative)
- Testez la fonction en faisant des appels qui couvrent les différents cas de figure
- si les tests sont concluants, passer à la suite
- sinon, analysez les erreurs/problèmes et revenez au deuxième point pour procéder aux corrections

⚙️ Cette phase de tests/corrections est tout à fait normale et importante ! Il faudra la présenter dans le compte-rendu (quel(s) étai(en)t le(s) problème(s) ? quelle(s) solution(s) avez-vous trouvée(s) ? …) --> donc il faut garder une trace de tout cela !

🆘 N'hésitez pas à regarder les aides pour chaque fonction, mais seulement après avoir réfléchi suffisamment de temps et testez plusieurs choses.

Étape 3 : Écriture du programme principal : traduire l’algorithme du jeu dans le langage précédent

Étape 4 : Tests du programme principal

Étape 5 : Correction et amélioration du programme principal (et éventuellement des fonctions principales) pour qu’il n’y ait plus de problèmes.


Germain BECKER & Sébastien POINT, Lycée Mounier, ANGERS Licence Creative Commons