TP Le jeu du morpions dans le langage de programmation Go

Dans ce chapitre vous allez commencer votre premier TP sur le langage de programmation GO en créant un jeu de morpions en GoLang.

Présentation du TP

Il est temps de pratiquer un peu !

Je vous ai préparé un TP qui reprend la base de tout ce qu'on a pu étudier jusqu’ici.

Nous allons créer un jeu de morpion appelé aussi le tic tac toe avec le langage de programmation Go.

jeu du morpion

Image du jeu de morpion

Avant de commencer la création du jeu voici déjà quelques règles à respecter :

  • Le jeu se déroulera sur un damier de 3 cases par 3 cases
  • Le jeu se joue à deux. Un premier joueur dessine son symbole sur une case. Puis c'est au tour de l'autre joueur de dessiner son symbole sur une case libre.
  • Pour gagner il faut aligner 3 symboles identiques horizontalement, verticalement ou en diagonale avant le tour de l'adversaire
  • Quand toutes les cases sont remplîtes alors il y a match nul.
  • Vérifier toujours si l'utilisateur a rentré les valeurs attendues "Never trust user input" !
  • Le jeu va avoir lieu sur l'invite de commande.

Libre à vous de choisir le design que vous souhaitez, en ce qui me concerne voici à quoi ressemble mon jeu :

1  2  3
4  5  6
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 1
X  2  3
4  5  6
7  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 2
X  O  3
4  5  6
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 5
X  O  3
4  X  6
7  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 6
X  O  3
4  X  O
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 9
Joueur 1  vous avez gagné !

Information

Ici les chiffres correspondent à des cases libres. Ça permet à l'utilisateur de savoir si sa case est libre. Libre à vous bien sûr de mettre autre chose à la place.

Gestion des différents cas

Voici la sortie standard des différents cas d'usage :

- Si le joueur rentre autre chose que ce que je souhaite :

1  2  3
4  5  6
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 15
Votre nombre doit être compris entre 0 à 9 !
Joueur 1 entrez un nombre compris entre 1 à 9 : dsdsds
Entrez un nombre et non autre chose !
Joueur 1 entrez un nombre compris entre 1 à 9 : 1
X  2  3
4  5  6
7  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 1
Cette case est déjà prise !

- Si il y a match nul :

1  2  3
4  5  6
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 1
X  2  3
4  5  6
7  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 4
X  2  3
O  5  6
7  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 7
X  2  3
O  5  6
X  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 5
X  2  3
O  O  6
X  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 2
X  X  3
O  O  6
X  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 3
X  X  O
O  O  6
X  8  9
Joueur 1 entrez un nombre compris entre 1 à 9 : 6
X  X  O
O  O  X
X  8  9
Joueur2 entrez un nombre compris entre 1 à 9 : 9
X  X  O
O  O  X
X  8  O
Joueur 1 entrez un nombre compris entre 1 à 9 : 8
X  X  O
O  O  X
X  X  O
Partie nulle !

Sachez que vous possédez les connaissances nécessaires vues dans les chapitres précédents, et donc vous êtes tout à fait capables de réaliser ce jeu.

Good Luck ! et à vous de jouer !

Solution

J'espère que vous avez réussi à réaliser ce tp ! Même si vous n'avez pas forcément réussi à tout faire. L'essentiel c'est qu'au moins quelques fonctionnalités du jeu fonctionnent, ça restera toujours mieux que de voir la solution directement sans même essayer.

Je vous présente ici ma solution avec beaucoup de commentaires. J'ai essayé de reprendre tout ce qu'on a pu découvrir jusqu'ici. Je ne détiens pas la meilleure solution donc n'hésitez pas à améliorer le code en rajoutant ou modifiant les fonctionnalités déjà présentes et de partager votre code dans les commentaires avec un lien de votre repository github ou autres !

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

// constantes globales
const (
    tailleDamier   = 9
    symboleJoueur1 = "X"
    symboleJoueur2 = "O"
)

// Variables globales
var (
    tableauMorpion = [tailleDamier]string{ // création d'un damier sans aucune cases remplies
        "1", "2", "3",
        "4", "5", "6",
        "7", "8", "9"}
    joueur1 = true // c'est le joueur 1 qui commence en 1er
)

func main() {
    jouer() // Lancement du jeu
}

/**
* description de la fonction : Permet de lancer le jeu
*
* @return rien
*/
func jouer() {

    var numeroCase int
    for true {
        affichage()
        numeroCase = gestionEntreeUtilisateur() // Récupération de l'entrée utilisateur
        remplirCase(numeroCase)
        if gagner() { // Vérifier si le joueur a gagné
            affichage()
            fmt.Println(nomJoueur(), "vous avez gagné !")
            os.Exit(0) // on quitte la partie
        } else if partieNulle() { // Vérifier si match nul
            affichage()
            fmt.Println("Partie nulle !")
            os.Exit(0) // on quitte la partie
        }
        joueur1 = !joueur1 // on change de joueur
    }
}

/**
* description de la fonction : Permet d'afficher le damier en prenant en compte les cases remplies
*
* @return rien
*/
func affichage() {
    for i := 0; i < len(tableauMorpion); i++ {
        fmt.Print(" ", tableauMorpion[i], " ")
        if (i+1)%3 == 0 { // retour à la ligne après avoir affiché 3 éléments
            fmt.Println()
        }
    }
}

/**
* description de la fonction : Retourne le nom des joueurs
*
* @return string
*/
func nomJoueur() string {
    if joueur1 {
        return "Joueur 1 "
    } else {
        return "Joueur2 "
    }
}

/**
* description de la fonction : Permet de vérifier l'entrée utilisateur
*
* @return int : retourne l'entrée utilisateur
*/
func gestionEntreeUtilisateur() int {

    var (
        bonneEntree = false // variable qui permet de vérifier si l'utilisateur a rentré la valeur qu'on attend de lui
        numeroCase  = 0
        err         error
        scanner     = bufio.NewScanner(os.Stdin)
    )

    for bonneEntree == false {
        fmt.Print(nomJoueur(), "entrez un nombre compris entre 1 à ", tailleDamier, " : ")
        scanner.Scan()
        numeroCase, err = strconv.Atoi(scanner.Text())
        if err != nil { //vérifier si l'utilisateur a rentré un nombre
            fmt.Println("Entrez un nombre et non autre chose !")
        } else if numeroCase < 1 || numeroCase > tailleDamier {
            fmt.Println("Votre nombre doit être compris entre 0 à", tailleDamier, "!")
        } else if tableauMorpion[numeroCase-1] == symboleJoueur1 || tableauMorpion[numeroCase-1] == symboleJoueur2 { // vérifier si la case est libre
            fmt.Println("Cette case est déjà prise !")
        } else {
            bonneEntree = true
        }
    }
    return numeroCase - 1 // n'oubliez pas que la taille d'un tableau commence toujours par 0 ;)
}

/**
* description de la fonction : Permet de remplir la case choisie par le joueur
*
* @param numeroCase
* @return rien
*/
func remplirCase(numeroCase int) {
    if joueur1 {
        tableauMorpion[numeroCase] = symboleJoueur1
    } else {
        tableauMorpion[numeroCase] = symboleJoueur2
    }
}

/**
* description de la fonction : Permet de savoir si le joueur a gagné
*
* @return bool
*/
func gagner() bool {

    /*
        tableauxdeGain est un tableau à double dimensions où j'ai rajouté
        les différents cas d'utilisation où il est possible de gagner.
    */
    tableauxdeGain := [][tailleDamier]bool{
    {
        true, true, true,
        false, false, false,
        false, false, false},

    {
        false, false, true,
        false, false, true,
        false, false, true},
    {
        false, false, false,
        false, false, false,
        true, true, true},
    {
        true, false, false,
        true, false, false,
        true, false, false},
    {
        true, false, false,
        false, true, false,
        false, false, true},
    {
        false, false, true,
        false, true, false,
        true, false, false},
    {
        false, true, false,
        false, true, false,
        false, true, false}}

    // création d'un damier temporaire
    var tableauMorpionBool [tailleDamier]bool

    for index, valeur := range tableauMorpion {
        if joueur1 && valeur == symboleJoueur1 { // si c'est le tour du joueur 1 et que la case possède le bon symbole du joueur 1
            tableauMorpionBool[index] = true
        } else if !joueur1 && valeur == symboleJoueur2 {
            tableauMorpionBool[index] = true
        }
    }

    ressemblance := 0 // Nombre de true qui sont sur les mêmes cases dans le tableau tableauMorpionBool et dans le tableau tableauxdeGain
    for _, tableauGain := range tableauxdeGain {
        for i := 0; i < len(tableauMorpionBool); i++ {
            if tableauMorpionBool[i] == true && tableauMorpionBool[i] == tableauGain[i] { // si c'est à true dans le même index de tableauMorpionBool et tableauGain alors on incrémente la ressemblance
                ressemblance++
                if ressemblance == 3 { // si les cases du tableau tableauMorpionBool sont 3 fois les mêmes que sur l'un des tableaux tableauxdeGain alors ça veut dire que le joueur a gagné
                    return true
                }
            }
        }
        ressemblance = 0 // On remet le compteur à 0 pour vérifier un autre tableau du tableauxdeGain
    }
    return false
}

/*
* description de la fonction : Permet de vérifier si la partie est nulle
*
* @return bool
*/
func partieNulle() bool {
    occurence := 0

    for _, valeur := range tableauMorpion {
        if valeur == symboleJoueur1 || valeur == symboleJoueur2 {
            occurence++ // incrémenter de 1 si une case est remplite par un symbole
        }
    }

    /*
        si toutes les cases sont remplies de symboles
        et que le joueur n'a pas encore gagné alors la partie est nulle
    */
    return (occurence == len(tableauMorpion))
}

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

27 commentaires

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Pensez bien à tester vos cas limites avec des entrées non numériques. C'est là qu'on voit si votre gestionEntreeUtilisateur tient la route en production.

01/04/2019 à 05:12
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

T'as bien mis joueur1 = !joueur1 à la fin de la boucle jouer() ? Si tu le mets dans un if, ça ne tournera jamais.

01/04/2019 à 01:01
thibaut-ruiz
Membre Actif
Avatar de thibaut-ruiz
thibaut-ruiz
Membre Actif

J'ai un bug étrange, le joueur 2 ne peut pas jouer après le joueur 1. Le tour ne change pas.

31/03/2019 à 20:35
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Bonne idée, Go avec WASM c'est puissant. Fais juste gaffe au DOM, ça demande un peu plus de taf que le simple fmt.Println ici.

31/03/2019 à 14:03
adele-levy
Membre Actif
Avatar de adele-levy
adele-levy
Membre Actif

Merci pour le tuto. Je vais essayer de porter ça sur une interface web avec WebAssembly plus tard.

31/03/2019 à 08:56
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Ta conversion strconv.Atoi devrait échouer si le nombre est trop grand ou hors plage. Vérifie ta logique de validation dans le else if.

31/03/2019 à 01:39
weiss-gabriel
Membre Actif
Avatar de weiss-gabriel
weiss-gabriel
Membre Actif

Comment je peux limiter l'entrée utilisateur à un seul caractère ? Actuellement, si je tape '12', il prend le 1.

30/03/2019 à 21:12
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est un TP, pas une archi micro-services. C'est efficace pour apprendre. Après, en vrai, tu gères des retours d'erreurs ou des contextes.

30/03/2019 à 16:48
francoise98
Membre Actif
Avatar de francoise98
francoise98
Membre Actif

Le code tourne, mais je trouve que le for true avec os.Exit est un peu violent. C'est propre de faire ça ?

30/03/2019 à 11:10
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Ton check doit être strict. Si tu saisis 0, le numeroCase - 1 devient -1. Ajoute une condition :

if numeroCase < 1 || numeroCase > 9 {
    fmt.Println("Nombre invalide")
    continue
}
30/03/2019 à 03:25
xmasse
Membre Actif
Avatar de xmasse
xmasse
Membre Actif

J'ai une erreur index out of range quand je saisis 0. Le check dans gestionEntreeUtilisateur ne suffit pas ?

29/03/2019 à 20:02
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie tes indices. Voici la config pour les diagonales :

{true, false, false, false, true, false, false, false, true}, // Diagonale 1
{false, false, true, false, true, false, true, false, false}  // Diagonale 2
29/03/2019 à 14:49
theodore26
Membre
Avatar de theodore26
theodore26
Membre

Mon code ne détecte pas la victoire en diagonale. J'ai dû me planter dans les indices de tableauxdeGain.

29/03/2019 à 09:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Utilise les codes d'échappement ANSI. C'est simple, tu entoures ta chaîne par \033[31m pour le rouge par exemple.

29/03/2019 à 01:45
timothee74
Membre Actif
Avatar de timothee74
timothee74
Membre Actif

Salut, j'ai essayé de compiler pour Linux avec go build, ça marche nickel. Par contre, comment je peux colorer le X et le O ?

28/03/2019 à 19:41
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Utilise strings.TrimSpace(scanner.Text()) avant la conversion, ça nettoiera les espaces blancs. C'est basique.

28/03/2019 à 15:35
pineau-luc
Membre Actif
Avatar de pineau-luc
pineau-luc
Membre Actif

Erreur étrange lors du scan : bufio.Scanner plante si j'ajoute des espaces. Une idée ?

28/03/2019 à 09:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Pour un morpion, la taille est fixe (3x3). Un tableau est plus performant ici, pas besoin de dynamicité. Ne complexifie pas pour rien.

28/03/2019 à 01:55
alain-alexandre
Membre Actif
Avatar de alain-alexandre
alain-alexandre
Membre Actif

Le code est lisible, mais pourquoi utiliser un tableau [9]string plutôt qu'un slice ?

27/03/2019 à 21:47
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est normal si tu ne vides pas ton buffer ou si tu ne gères pas l'état du scanner. Montre ton code pour gestionEntreeUtilisateur, je te dirai où ça coince.

27/03/2019 à 17:21
eroy
Membre Actif
Avatar de eroy
eroy
Membre Actif

J'ai un souci avec strconv.Atoi quand je tape une lettre. Ça me boucle à l'infini sur le message d'erreur. C'est normal ?

27/03/2019 à 13:05
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Faut sortir de la boucle for true proprement au lieu de tuer le process. Remplace os.Exit(0) par un break et encapsule tout ça dans une autre boucle for pour le replay.

27/03/2019 à 07:39
marie-regnier
Membre Actif
Avatar de marie-regnier
marie-regnier
Membre Actif

Comment je fais pour éviter de quitter le programme avec os.Exit(0) ? J'aimerais bien relancer une partie sans tout redémarrer.

27/03/2019 à 00:48
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est sans doute un problème de scope si tu as séparé ton code dans plusieurs fichiers. N'oublie pas que tes constantes doivent être accessibles dans le package main. Vérifie bien que tout est dans le même dossier.

26/03/2019 à 19:46
philippine-pons
Membre Actif
Avatar de philippine-pons
philippine-pons
Membre Actif

Sympa le TP. Par contre, j'ai une erreur au build : undefined: tailleDamier alors que je l'ai bien mis en constante. J'ai raté un truc ?

26/03/2019 à 12:31

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire