Créez Votre Jeu De Morpion En C++: Tutoriel Complet
Salut les codeurs! Aujourd'hui, on s'attaque à un projet super cool et classique : le jeu de morpion en C++. Que vous soyez débutants ou développeurs expérimentés, ce tutoriel est fait pour vous. On va décortiquer chaque étape, du code de base à l'ajout d'une intelligence artificielle simple pour défier vos amis (ou vous-même!). Accrochez-vous, ça va coder!
Pourquoi coder un morpion en C++?
Coder un jeu de morpion est un excellent moyen de se familiariser avec les fondamentaux de la programmation C++. On va manipuler des tableaux, des boucles, des conditions, et même aborder des notions d'algorithmique si on veut ajouter une IA. C'est un projet parfait pour consolider vos connaissances et apprendre en s'amusant. De plus, c'est un projet que vous pouvez facilement personnaliser et améliorer, en ajoutant par exemple une interface graphique ou en implémentant des stratégies de jeu plus complexes.
Étape 1: La Structure du Jeu
Avant de plonger dans le code, il faut définir la structure de notre jeu. Imaginez un morpion classique : une grille de 3x3 où deux joueurs alternent pour placer leurs symboles (généralement 'X' et 'O'). On va donc avoir besoin d'une grille pour représenter l'état du jeu, et de fonctions pour afficher la grille, demander aux joueurs où ils veulent jouer, et vérifier si la partie est gagnée ou nulle.
La grille
On va utiliser un tableau 2D pour représenter notre grille. En C++, ça se traduit par un vector de vector de caractères :
#include <iostream>
#include <vector>
using namespace std;
vector<vector<char>> creerGrille() {
return vector<vector<char>>(3, vector<char>(3, ' '));
}
Ici, creerGrille() est une fonction qui renvoie une grille 3x3 initialisée avec des espaces vides (' '). Chaque case de la grille pourra contenir soit un 'X', soit un 'O', soit un espace vide.
Affichage de la grille
Pour afficher la grille dans la console, on va écrire une fonction qui parcourt le tableau et affiche chaque case. On peut aussi ajouter des lignes et des colonnes pour rendre la grille plus lisible :
void afficherGrille(const vector<vector<char>>& grille) {
cout << " 0 1 2" << endl;
for (int i = 0; i < 3; ++i) {
cout << i << " ";
for (int j = 0; j < 3; ++j) {
cout << grille[i][j] << " ";
}
cout << endl;
}
}
Cette fonction prend en paramètre une grille (un vector de vector de char) et l'affiche dans la console. On utilise des boucles for pour parcourir les lignes et les colonnes de la grille, et on affiche la valeur de chaque case suivie d'un espace. On ajoute aussi les indices des lignes et des colonnes pour aider les joueurs à se repérer.
Étape 2: Le Tour des Joueurs
Maintenant, il faut gérer le tour des joueurs. On va écrire une fonction qui demande à un joueur où il veut placer son symbole, et qui met à jour la grille en conséquence. Il faudra aussi vérifier que le joueur a bien entré des coordonnées valides (entre 0 et 2), et que la case choisie est bien vide.
Demande de coordonnées
pair<int, int> demanderCoordonnees(char joueur, const vector<vector<char>>& grille) {
int ligne, colonne;
while (true) {
cout << "Joueur " << joueur << ", entrez vos coordonnées (ligne colonne) : ";
cin >> ligne >> colonne;
if (ligne >= 0 && ligne < 3 && colonne >= 0 && colonne < 3 && grille[ligne][colonne] == ' ') {
break;
} else {
cout << "Coup invalide. Réessayez." << endl;
}
}
return make_pair(ligne, colonne);
}
Cette fonction demande au joueur d'entrer les coordonnées de la case où il veut jouer. Elle utilise une boucle while pour s'assurer que le joueur entre des coordonnées valides (entre 0 et 2) et que la case est vide. Si le coup est invalide, elle affiche un message d'erreur et redemande les coordonnées. Sinon, elle renvoie les coordonnées sous forme d'une pair d'entiers.
Mise à jour de la grille
void jouerCoup(vector<vector<char>>& grille, int ligne, int colonne, char joueur) {
grille[ligne][colonne] = joueur;
}
Cette fonction met à jour la grille en plaçant le symbole du joueur à la case spécifiée par les coordonnées ligne et colonne.
Étape 3: Vérification de la Victoire
L'étape cruciale : déterminer si un joueur a gagné, ou si la partie est nulle. On va écrire une fonction qui vérifie toutes les possibilités de victoire (lignes, colonnes, diagonales), et qui renvoie vrai si un joueur a gagné, faux sinon. On écrira aussi une fonction pour vérifier si la partie est nulle (si toutes les cases sont remplies et aucun joueur n'a gagné).
Vérification de la victoire
bool verifierVictoire(const vector<vector<char>>& grille, char joueur) {
// Vérification des lignes et colonnes
for (int i = 0; i < 3; ++i) {
if ((grille[i][0] == joueur && grille[i][1] == joueur && grille[i][2] == joueur) ||
(grille[0][i] == joueur && grille[1][i] == joueur && grille[2][i] == joueur)) {
return true;
}
}
// Vérification des diagonales
if ((grille[0][0] == joueur && grille[1][1] == joueur && grille[2][2] == joueur) ||
(grille[0][2] == joueur && grille[1][1] == joueur && grille[2][0] == joueur)) {
return true;
}
return false;
}
Cette fonction vérifie si le joueur joueur a gagné la partie. Elle commence par vérifier les lignes et les colonnes : pour chaque ligne et chaque colonne, elle vérifie si les trois cases contiennent le symbole du joueur. Ensuite, elle vérifie les diagonales : elle vérifie si les trois cases de la diagonale principale ou de la diagonale secondaire contiennent le symbole du joueur. Si l'une de ces conditions est remplie, la fonction renvoie true, sinon elle renvoie false.
Vérification de la partie nulle
bool verifierNul(const vector<vector<char>>& grille) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (grille[i][j] == ' ') {
return false;
}
}
}
return true;
}
Cette fonction vérifie si la partie est nulle. Elle parcourt toutes les cases de la grille et vérifie si l'une d'entre elles est vide. Si elle trouve une case vide, elle renvoie false, car la partie n'est pas encore terminée. Si toutes les cases sont remplies, elle renvoie true, car la partie est nulle.
Étape 4: La Boucle de Jeu
On a maintenant toutes les pièces du puzzle. Il ne reste plus qu'à assembler le tout dans une boucle de jeu. Cette boucle va gérer l'alternance des joueurs, l'affichage de la grille, la demande de coordonnées, la mise à jour de la grille, et la vérification de la victoire ou de la partie nulle.
int main() {
vector<vector<char>> grille = creerGrille();
char joueurActuel = 'X';
bool partieTerminee = false;
while (!partieTerminee) {
afficherGrille(grille);
pair<int, int> coordonnees = demanderCoordonnees(joueurActuel, grille);
jouerCoup(grille, coordonnees.first, coordonnees.second, joueurActuel);
if (verifierVictoire(grille, joueurActuel)) {
afficherGrille(grille);
cout << "Joueur " << joueurActuel << " a gagné !" << endl;
partieTerminee = true;
} else if (verifierNul(grille)) {
afficherGrille(grille);
cout << "Partie nulle !" << endl;
partieTerminee = true;
} else {
joueurActuel = (joueurActuel == 'X' ? 'O' : 'X');
}
}
return 0;
}
Ce code est le cœur de notre jeu. Il commence par créer une grille vide et initialise le joueur actuel à 'X'. Ensuite, il entre dans une boucle while qui continue tant que la partie n'est pas terminée. À chaque tour, il affiche la grille, demande au joueur actuel d'entrer ses coordonnées, met à jour la grille, et vérifie si le joueur a gagné ou si la partie est nulle. Si c'est le cas, il affiche un message et termine la partie. Sinon, il change de joueur et continue la boucle.
Étape 5: Ajouter une IA (Optionnel)
Pour rendre le jeu plus intéressant, on peut ajouter une intelligence artificielle simple. L'idée est de créer un joueur qui choisit son coup en fonction de l'état du jeu. On peut par exemple implémenter une stratégie simple qui consiste à choisir la première case vide disponible, ou une stratégie plus élaborée qui essaie de gagner ou d'empêcher l'adversaire de gagner.
Stratégie simple: Choisir la première case vide
pair<int, int> choisirCoupIA(const vector<vector<char>>& grille) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (grille[i][j] == ' ') {
return make_pair(i, j);
}
}
}
return make_pair(-1, -1); // Ne devrait jamais arriver si la partie n'est pas nulle
}
Cette fonction implémente une stratégie très simple pour l'IA : elle parcourt la grille et renvoie les coordonnées de la première case vide qu'elle trouve. Si la grille est pleine (ce qui ne devrait jamais arriver si la partie n'est pas nulle), elle renvoie (-1, -1).
Intégration de l'IA dans la boucle de jeu
Pour utiliser l'IA, on va modifier la boucle de jeu pour qu'elle appelle la fonction choisirCoupIA lorsque c'est au tour de l'IA de jouer. On peut par exemple demander au début de la partie si le joueur veut jouer contre l'IA ou contre un autre joueur, et adapter la boucle de jeu en conséquence.
int main() {
vector<vector<char>> grille = creerGrille();
char joueurActuel = 'X';
bool partieTerminee = false;
bool jouerContreIA = false;
cout << "Voulez-vous jouer contre l'IA ? (o/n) : ";
char reponse;
cin >> reponse;
if (reponse == 'o') {
jouerContreIA = true;
}
while (!partieTerminee) {
afficherGrille(grille);
pair<int, int> coordonnees;
if (joueurActuel == 'X' || !jouerContreIA) {
coordonnees = demanderCoordonnees(joueurActuel, grille);
} else {
cout << "L'IA réfléchit..." << endl;
coordonnees = choisirCoupIA(grille);
}
jouerCoup(grille, coordonnees.first, coordonnees.second, joueurActuel);
if (verifierVictoire(grille, joueurActuel)) {
afficherGrille(grille);
cout << "Joueur " << joueurActuel << " a gagné !" << endl;
partieTerminee = true;
} else if (verifierNul(grille)) {
afficherGrille(grille);
cout << "Partie nulle !" << endl;
partieTerminee = true;
} else {
joueurActuel = (joueurActuel == 'X' ? 'O' : 'X');
}
}
return 0;
}
Ici, on a ajouté une variable jouerContreIA qui indique si le joueur joue contre l'IA ou contre un autre joueur. Au début de la partie, on demande au joueur s'il veut jouer contre l'IA. Dans la boucle de jeu, on vérifie si c'est au tour de l'IA de jouer (si joueurActuel est 'O' et jouerContreIA est vrai). Si c'est le cas, on appelle la fonction choisirCoupIA pour que l'IA choisisse son coup. Sinon, on appelle la fonction demanderCoordonnees pour demander au joueur d'entrer ses coordonnées.
Personnalisation et Améliorations
Le code qu'on a écrit est une base solide pour un jeu de morpion. Mais le plus fun, c'est de personnaliser et d'améliorer le jeu! Voici quelques idées :
- Interface graphique: Au lieu d'afficher la grille dans la console, on peut utiliser une bibliothèque comme SFML ou Qt pour créer une interface graphique plus agréable.
- Stratégies d'IA plus complexes: On peut implémenter des algorithmes plus sophistiqués pour l'IA, comme l'algorithme Minimax, pour qu'elle joue de manière plus stratégique.
- Différents modes de jeu: On peut ajouter des modes de jeu différents, comme un mode où la taille de la grille est variable, ou un mode où il faut aligner plus de trois symboles pour gagner.
- Multijoueur en réseau: On peut permettre à deux joueurs de jouer l'un contre l'autre en réseau.
Commentaires d'Expert
"Le morpion est un excellent exercice pour comprendre les bases de la programmation et de l'algorithmique," nous dit Isabelle Martin, experte en développement de jeux. "En C++, il permet de manipuler des structures de données simples comme les tableaux, et d'introduire des concepts comme la logique de jeu et l'intelligence artificielle de base. C'est un projet idéal pour les débutants, mais aussi un bon point de départ pour explorer des concepts plus avancés comme les algorithmes de recherche et la théorie des jeux."
Et voilà! Vous avez maintenant toutes les cartes en main pour coder votre propre jeu de morpion en C++. N'hésitez pas à expérimenter, à modifier le code, et à laisser libre cours à votre créativité. Le plus important, c'est de s'amuser en codant!