• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 10/03/2017

TP : Créez un petit jeu orienté objet

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce TP, nous allons essayer de mettre en pratique ce que nous avons appris en programmation orientée objet avec le C#.

Avec ce TP, vous aurez l'occasion de vous entraîner à créer des classes, à manipuler l’héritage, à créer des propriétés, à encapsuler, à substituer des méthodes, à jouer avec le polymorphisme et vous pourrez vous confronter à des situations un peu différentes de la théorie !

Le but du TP est de créer un mini jeu où nous allons pouvoir utiliser un dé pour tuer des monstres, super original ! Bon, tout va être automatique et aléatoire, on est pas là pour faire un jeu complet avec des interactions avec l'utilisateur ; on est là pour s'entrainer à faire de l'OO ;) .
Quoique ... si vous avez envie d'aller plus loin, libre à vous :).

Instructions pour réaliser le TP

Alors voici l'énoncé du TP. Vous allez devoir créer 4 classes :

  • Une classe Joueur, représentant notre héros.

  • Une classe MonstreFacile.

  • Une classe MonstreDifficile, qui bien sûr va hériter des fonctionnalités d'un monstre facile et qui en ajoutera.

  • Une classe De pour gérer notre tirage aléatoire, comme un dé classique.

Les règles de mon cru qui suivent peuvent paraître compliquées mais en fait elles sont assez facile à retranscrire en code, donc ne vous découragez pas à la lecture de celles-ci et revenez-y une à une.

Le principe est de voir combien notre héros va pouvoir tuer de monstres faciles et de monstres difficiles avant de mourir, en ayant perdu tous ses points de vie (mon héros démarre avec 150 points de vie). Chaque monstre facile tué rapporte 1 point, chaque monstre difficile tué en rapporte 2.
Un monstre aléatoire arrive, le héros attaque le monstre ; puis si le monstre a survécu il attaque à son tour le héros et ceci jusqu'à ce que mort s'en suive.

Une attaque du héros sur un monstre consiste en un jet de dé des deux protagonistes. Si le dé du héros est supérieur ou égal au dé du monstre, alors celui-ci est vaincu. Sinon, rien ne se passe et c'est au tour du monstre d'attaquer.
L'attaque du monstre facile sur le héros est similaire, mais à la différence que le jet du monstre doit être strictement supérieur au jet du héros. Eh oui, il faut bien avantager un peu notre héros :).

A noter que lorsque le héros subit des dégâts, son bouclier se déclenche avec un nouveau jet de dé. Si ce dernier est inférieur ou égal à 2 (donc 2 chances sur 6), alors le héros ne perçoit pas de dégâts. Le cas contraire, ses points de vie sont diminués d'une valeur forfaitaire, disons 10 points de vie.

L'attaque du monstre difficile est la même que celle du monstre facile, sauf qu'il enchaine avec un sort magique. Un jet de dé est réalisé et si ce jet est différent de 6 alors le héros perçoit des dommages équivalent à la valeur du dé multiplié par une valeur forfaitaire, disons 5.

N'oubliez pas, pour générer un nombre aléatoire, il faut instancier un objet Random et appeler la méthode Next sur l'objet. On lui passera en paramètre les bornes du tirage aléatoire. Par exemple pour avoir un nombre aléatoire entre 1 et 6 (inclus), il faut faire :

Random random = new Random();
int tirage = random.Next(1, 7);

N'hésitez pas à conserver l'objet random comme membre des classes, pour éviter d'avoir à le réinstancier à chaque tirage.

Au niveau des détails techniques des classes :

La classe Joueur  :

  • possède une propriété en lecture seule qui contient les points de vies ; ceux-ci sont initialisés dans le constructeur.

  • possède une propriété en lecture seule permettant de savoir si le joueur est vivant, et encapsule le nombre de points de vie.

  • possède une méthode Attaque, prenant en paramètre un monstre

  • possède une méthode SubitDegats qui prend en paramètre un entier avec la valeur des dégâts subits

 

La classe MonstreFacile  :

  • possède également une méthode Attaque   

  • possède une propriété en lecture seule qui encapsule le fait de savoir si le monstre est vivant

La classe MonstreDifficile  :

  • modifie le comportement de la méthode Attaque 

La classe De  :

  • possède une méthode LanceLeDe qui renvoie un entier

Et voilà, vous savez tout. Bon, ça a l'air long comme ça, mais en fait ce n’est pas si compliqué, sauf si bien sûr vous n’avez pas lu ou pas compris les chapitres précédents. Dans ce cas, n’hésitez pas à y rejeter un coup d’œil.

En résultat de mon côté, j'ai obtenu :

Snif, vous êtes mort...
Bravo !!! Vous avez tué 8 monstres faciles et 10 monstres difficiles. Vous avez 28 points.

Bien sûr, vous pouvez vous rajouter des méthodes privées, des variables membres (comme un objet Random), et ce que vous voulez d'autre. :)

Correction

Ne regardez pas tout de suite la correction. Faites le TP. Il est important de manipuler les classes. Vous allez faire ça très régulièrement. Cela doit devenir un réflexe et pour que cela le devienne, il faut pratiquer !

Toujours est-il que voici ma correction. Peut-être que le plus dur est de modéliser correctement l’application ?
Il y a plusieurs solutions bien sûr pour créer ce petit programme, voici celle que je propose.

Tout d'abord, nous allons commencer par la classe De , c'est la plus simple :

public class De
{
    private Random random;

	public De()
	{
		random = new Random();
	}

	public int LanceLeDe()
	{
		return random.Next(1, 7);
	}
}

l s'agit d'utiliser la classe Random du framework .NET en l'encapsulant dans une classe à nous. C'est vraiment typique de la POO, la classe Random fait plein de choses ; nous n'avons besoin que d'un lancer de dé, donc un nombre aléatoire entre 1 et 6, nous créons une méthode qui facilite cette utilisation. Un nom clair, des intentions bien définies et la classe De  est prête à être utilisée.

Passons à la classe MonstreFacile :

public class MonstreFacile
{
    private const int degats = 10;
	protected De de;
	public bool EstVivant { get; private set; }

	public MonstreFacile()
	{
		de = new De();
		EstVivant = true;
	}

	public virtual void Attaque(Joueur joueur)
	{
		int lanceMonstre = LanceLeDe();
		int lanceJoueur = joueur.LanceLeDe();
		if (lanceMonstre > lanceJoueur)
			joueur.SubitDegats(degats);
	}

	public void SubitDegats()
	{
		EstVivant = false;
	}
	
	public int LanceLeDe()
	{
		return de.LanceLeDe();
	}
}

Il n'y a rien de trop particulier. On a tout d'abord une constante pour représenter les dégâts occasionnés par le monstre.
Ensuite, on a une variable membre De , qui a une visibilité à protected  car je veux pouvoir la réutiliser dans la classe MonstreDifficile  qui héritera de MonstreFacile .
Puis on a une propriété en lecture seule EstVivant qui permet de savoir si le monstre… est vivant ! (N'oubliez pas de toujours donner des noms explicites à vos variables, à vos méthodes et à vos classes).
Tout ça est initialisé dans le constructeur.

Vient ensuite la méthode d'attaque. Elle est marquée comme virtual car je veux offrir la possibilité de la substituer dans la classe MonstreDifficile. Son contenu parle de lui-même ; on lance les dés et si c'est supérieur, le joueur  subit des dégâts.
La méthode SubitDegats ici est simple, elle tue tout bonnement le monstre en changeant la valeur du booléen EstVivant. Et puis on crée une méthode pour lancer le dé. On aurait simplement pu appeler de.LanceLeDe(), mais cela permet de déléguer la façon de lancer le dé au monstre (et au joueur également). Imaginons que plus tard le monstre ait le droit de lancer plusieurs fois le dé et de choisir le meilleur de ses jets, il suffit ici de changer le contenu de sa méthode sans venir perturber la méthode Attaque .

Voilà pour le monstre facile. Passons maintenant au MonstreDifficile :

public class MonstreDifficile : MonstreFacile
{
    private const int degatsSort = 5;

	public override void Attaque(Joueur joueur)
	{
		base.Attaque(joueur);
		joueur.SubitDegats(SortMagique());
	}
	
	private int SortMagique()
	{
		int valeur = de.LanceLeDe();
		if (valeur == 6)
			return 0;
		return degatsSort * valeur;
	}
}

Il suffit de dériver de la classe MonstreFacile et nous récupérons tous les comportements public   ou protected (le dé !) de la classe mère.
Et nous substituons la méthode Attaque. Elle doit faire la même chose, donc on appelle la méthode de la classe mère avec base.Attaque(...) puis on ajoute un comportement qui consiste à faire subir des dégâts au joueur en fonction d'un sort magique.

Ce sort magique est matérialisé par la méthode privée SortMagique() dont le fonctionnement parle de lui-même.

Et enfin, parlons du joueur :

public class Joueur
{
    private De de;
	public int PtsDeVies { get; private set; }
	public bool EstVivant
	{
		get { return PtsDeVies > 0; }
	}

	public Joueur(int points)
	{
		PtsDeVies = points;
		de = new De();
	}

	public void Attaque(MonstreFacile monstre)
	{
		int lanceJoueur = LanceLeDe();
		int lanceMonstre = monstre.LanceLeDe();
		if (lanceJoueur >= lanceMonstre)
			monstre.SubitDegats();
	}

	public int LanceLeDe()
	{
		return de.LanceLeDe();
	}

	public void SubitDegats(int degats)
	{
		if (!BouclierFonctionne())
			PtsDeVies -= degats;
	}

	private bool BouclierFonctionne()
	{
		return de.LanceLeDe() <= 2;
	}
}

Le principe est grosso modo le même que pour les monstres. On encapsule le dé, sauf que là, la variable reste privée car il n'y a pas besoin de l'exposer à une classe fille. La méthode LanceLeDe() fonctionne de la même façon.
Puis on a une propriété PtsDeVies en lecture seule pour stocker les points de vie. On peut voir ensuite encore une des merveilles de l'encapsulation où on expose une propriété EstVivant avec son fonctionnement basé sur les points de vie. Le joueur est bien sûr vivant si le nombre de points de vie est supérieur à 0.
Ensuite, il y a le constructeur qui initialise tout ça.
La méthode Attaque est quasiment identique à celle du monstre.
Puis la méthode SubitDegats qui enlève un nombre de points de vie au joueur, sauf si le bouclier a fonctionné.

Voilà, je vous avais dit que c'était simple, nonobstant l'abondance de règles. :)

Il ne reste plus que le corps du programme :

class Program
{
    private static Random random = new Random();

	static void Main(string[] args)
	{
		Jeu1();
	}

	private static void Jeu1()
	{
		Joueur nicolas = new Joueur(150);
		int cptFacile = 0;
		int cptDifficile = 0;
		while (nicolas.EstVivant)
		{
			MonstreFacile monstre = FabriqueDeMonstre();
			while (monstre.EstVivant && nicolas.EstVivant)
			{
				nicolas.Attaque(monstre);
				if (monstre.EstVivant)
					monstre.Attaque(nicolas);
			}

			if (nicolas.EstVivant)
			{
				if (monstre is MonstreDifficile)
					cptDifficile++;
				else
					cptFacile++;
			}
			else
			{
				Console.WriteLine("Snif, vous êtes mort...");
				break;
			}
		}
		Console.WriteLine("Bravo !!! Vous avez tué {0} monstres faciles et {1} monstres difficiles. Vous avez {2} points.", cptFacile, cptDifficile, cptFacile + cptDifficile * 2);
	}

	private static MonstreFacile FabriqueDeMonstre()
	{
		if (random.Next(2) == 0)
			return new MonstreFacile();
		else
			return new MonstreDifficile();
	}
}

Il n'y a rien de spécial. Dans les choses notables on peut remarquer la méthode FabriqueDeMonstre qui renvoie un monstre facile ou un monstre difficile en fonction d'un tirage aléatoire, 50% de chances d'avoir l'un ou l'autre. La méthode renvoi la classe de base, car c'est elle que l'on manipulera ensuite mais vous voyez dans le cors de la méthode que l'objet concrètement instancié est soir un MonstreFacile, soit un MonstreDifficile.
Puis, l'algorithme parle de lui-même. Notez juste l'utilisation du mot clé is pour vérifier de quel type est un monstre afin de compter le nombre de monstres différents tués.

Et voilà. :)

J’espère que vous aurez réussi avec brio toutes les créations de classes et que vous ne vous êtes pas mélangés dans les mots-clés. Vous verrez que vous aurez très souvent besoin d’écrire des classes dans ce genre afin de créer une application. C’est un élément indispensable du C# qu’il est primordial de maîtriser.

Aller plus loin

Je profite que ce TP soit tout frais dans votre esprit pour aller un peu plus loin et réaliser quelques améliorations de ce jeu afin de vous familiariser avec d’autres éléments de la POO, comme le polymorphisme, et les classes statiques. Je vous propose donc d’ajouter un nouveau mode de jeu, où le joueur peut attaquer un boss de fin selon les règles suivantes :

  • Le boss de fin possède comme le joueur des points de vie. Au démarrage, donnez 250 points de vie au boss de fin, et 150 points au joueur (eh oui, il faut bien un peu de challenge !).

  • Quand le joueur attaque, il lance un dé 25 et il inflige au boss de fin autant de points de vie en moins que le score du dé.

  • De même, lorsque le boss de fin attaque le joueur, il lance un dé 25 et inflige les dégâts de la même façon.

  • Le joueur peut éviter l’attaque du boss de fin grâce à son bouclier magique (que l’on a déjà créé dans le TP !).

Pour créer ce mode de jeu, il va falloir prendre en compte les nouvelles classes et méthodes nécessaires :

  • pour que le boss de fin et le joueur lancent le dé lors de leur affrontement ;

  • pour que le boss de fin puisse attaquer un joueur et perdre des points quand il est attaqué en retour ;

  • pour que le joueur puisse attaquer le boss de fin.

Prêts pour le combat ? C’est parti !

Le lancement de dés

Pourquoi serions-nous obligés d'instancier une classe De pour effectuer un tirage ? Autant rendre la classe De statique ! Et pour que le joueur et le boss de fin puissent faire un lancer 25, nous allons ajouter une nouvelle méthode statique LanceLeDe, qui prendra un entier en paramètre.

Voici donc notre nouvelle classe De  :

public static class De
{
	private static Random random = new Random();

	public static int LanceLeDe()
	{
		return random.Next(1, 7);
	}

	public static int LanceLeDe(int valeur)
	{
		return random.Next(1, valeur);
	}
}

Comme vous le voyez dans ce code, on utilise le même nom de méthode LanceLeDe pour plusieurs types d’objets : nous sommes en pleine pratique de polymorphisme !

Le Boss de fin

Pour ce nouveau mode de jeu, nous avons besoin de créer une nouvelle classe BossDeFin qui fonctionne quasiment comme la classe Joueur :

  • Le boss de fin doit pouvoir faire un lancer 25 lors du combat, via la méthode LanceLeDe de la classe statique De.

  • Il a des points de vie que l’on va placer dans le constructeur, et qui devront pouvoir être décrémentés lorsque le joueur l’attaque via une méthode SubitDegats.

  • Il doit pouvoir attaquer le joueur via la méthode Attaque qui lance le dé et fait subir des dégâts au personnage passé en paramètre.

  • Enfin, il n’est pas invincible, il doit pouvoir mourir ;). On va donc créer une propriété EstVivant en lecture seule qui retournera vrai si le nombre de points de vie est supérieur à 0.

 

Facile, non ? ;) Voyez à quoi ressemble cette nouvelle classe BossDeFin avec toutes ces fonctionnalités :

public class BossDeFin
{
	public int PtsDeVies { get; private set; }
	public bool EstVivant
	{
		get { return PtsDeVies > 0; }
	}

	public BossDeFin(int points)
	{
		PtsDeVies = points;
	}

	public void Attaque(Joueur personnage)
	{
		int nbPoints = LanceLeDe(26);
		personnage.SubitDegats(nbPoints);
	}

	public void SubitDegats(int valeur)
	{
		PtsDeVies -= valeur;
	}

	private int LanceLeDe(int valeur)
	{
		return De.LanceLeDe(valeur);
	}
}

Le joueur

Alors, c'est très bien ce boss de fin mais il faut que notre joueur puisse l'attaquer. Nous avons besoin d'une méthode Attaque qui prend un BossDeFin en paramètre. Encore une illustration de la magie du polymorphisme... Il faut également une méthode LanceLeDe qui prend un entier en paramètre.
Au final nous avons :

public class Joueur
{
	public int PtsDeVies { get; private set; }
	public bool EstVivant
	{
		get { return PtsDeVies > 0; }
	}

	public Joueur(int points)
	{
		PtsDeVies = points;
	}

	public void Attaque(MonstreFacile monstre)
	{
		int lanceJoueur = LanceLeDe();
		int lanceMonstre = monstre.LanceLeDe();
		if (lanceJoueur >= lanceMonstre)
			monstre.SubitDegats();
	}

	public void Attaque(BossDeFin boss)
	{
		int nbPoints = LanceLeDe(26);
		boss.SubitDegats(nbPoints);
	}

	public int LanceLeDe()
	{
		return De.LanceLeDe();
	}

	public int LanceLeDe(int valeur)
	{
		return De.LanceLeDe(valeur);
	}

	public void SubitDegats(int degats)
	{
		if (!BouclierFonctionne())
			PtsDeVies -= degats;
	}

	private bool BouclierFonctionne()
	{
		return De.LanceLeDe() <= 2;
	}
}

Vous remarquerez au passage voir qu’on utilise à nouveau la classe statique De.
Voilà, il ne nous reste plus qu’à créer le nouveau mode de jeu qui utilisera toutes ces classes et méthodes !

Le nouveau mode de jeu

Nous allons donc créer une méthode Jeu2()  (n’hésitez pas à lui donner un nom plus original si vous êtes inspirés !) pour ce nouveau mode de jeu que nous lancerons dans la méthode Main.

Dans la méthode Jeu2(), il va falloir mettre en place les fonctionnalités suivantes :

  • au démarrage, donner 250 points de vie au boss de fin pour pimenter un peu le combat ;

  • à la fin… et bien arrêter le jeu si lorsque l’un des deux meurt ! Pour cela, on va utiliser une boucle while.

Voici à quoi ressemble notre nouveau mode de jeu : 

static void Main(string[] args)
{
	Jeu2();
}

private static void Jeu2()
{
	Joueur nicolas = new Joueur(150);
	BossDeFin boss = new BossDeFin(250);
	while (nicolas.EstVivant && boss.EstVivant)
	{
		nicolas.Attaque(boss);
		if (boss.EstVivant)
			boss.Attaque(nicolas);
	}
	if (nicolas.EstVivant)
		Console.WriteLine("Bravo, vous avez sauvé la princesse (ou le prince !)");
	else
		Console.WriteLine("Game over...");
}

Et au passage, j'affiche les points de vie à chaque tour, comme ça on pourra suivre le combat en direct, bien confortablement installés sur nos chaises.

Et voilà un exemple d'exécution :

Le combat contre le boss de fin
Le combat contre le boss de fin

Sympa non ?

Le jeu complet

Maintenant que nous avons ajouté ces nouvelles fonctionnalités pour le mode de jeu 2, nous allons pouvoir finaliser le code complet de notre jeu en proposant à l’utilisateur de choisir quel mode de jeu il veut démarrer :

class Program
{
	private static Random random = new Random();
	
	static void Main(string[] args)
	{
		AfficheMenu();
		ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(true);
		while (consoleKeyInfo.Key != ConsoleKey.D1 && consoleKeyInfo.Key != ConsoleKey.D2 && consoleKeyInfo.Key != ConsoleKey.NumPad1 && consoleKeyInfo.Key != ConsoleKey.NumPad2)
		{
			AfficheMenu();
			consoleKeyInfo = Console.ReadKey(true);
		}
		if (consoleKeyInfo.Key == ConsoleKey.D1 || consoleKeyInfo.Key == ConsoleKey.NumPad1)
			Jeu1();
		else
			Jeu2();
	}

	private static void AfficheMenu()
	{
		Console.Clear();
		Console.WriteLine("Veuillez choisir votre mode de jeu :");
		Console.WriteLine("\t1 : Contre les monstres");
		Console.WriteLine("\t2 : Contre le boss de fin");
	}
	private static void Jeu1()
	{
		Joueur nicolas = new Joueur(150);
		int cptFacile = 0;
		int cptDifficile = 0;
		while (nicolas.EstVivant)
		{
			MonstreFacile monstre = FabriqueDeMonstre();
			while (monstre.EstVivant && nicolas.EstVivant)
			{
				nicolas.Attaque(monstre);
				if (monstre.EstVivant)
					monstre.Attaque(nicolas);
			}

			if (nicolas.EstVivant)
			{
				if (monstre is MonstreDifficile)
					cptDifficile++;
				else
					cptFacile++;
			}
			else
			{
				Console.WriteLine("Snif, vous êtes mort...");
				break;
			}
		}
		Console.WriteLine("Bravo !!! Vous avez tué {0} monstres faciles et {1} monstres difficiles. Vous avez {2} points.", cptFacile, cptDifficile, cptFacile + cptDifficile * 2);
	}

	private static MonstreFacile FabriqueDeMonstre()
	{
		if (random.Next(2) == 0)
			return new MonstreFacile();
		else
			return new MonstreDifficile();
	}


	private static void Jeu2()
	{
		Joueur nicolas = new Joueur(150);
		BossDeFin boss = new BossDeFin(250);
		while (nicolas.EstVivant && boss.EstVivant)
		{
			nicolas.Attaque(boss);
			if (boss.EstVivant)
				boss.Attaque(nicolas);
		}
		if (nicolas.EstVivant)
			Console.WriteLine("Bravo, vous avez sauvé la princesse (ou le prince !)");
		else
			Console.WriteLine("Game over...");
	}
}

public static class De
{
	private static Random random = new Random();

	public static int LanceLeDe()
	{
		return random.Next(1, 7);
	}

	public static int LanceLeDe(int valeur)
	{
		return random.Next(1, valeur);
	}
}

public class MonstreFacile
{
	private const int degats = 10;
	public bool EstVivant { get; private set; }

	public MonstreFacile()
	{
		EstVivant = true;
	}

	public virtual void Attaque(Joueur joueur)
	{
		int lanceMonstre = LanceLeDe();
		int lanceJoueur = joueur.LanceLeDe();
		if (lanceMonstre > lanceJoueur)
			joueur.SubitDegats(degats);
	}

	public void SubitDegats()
	{
		EstVivant = false;
	}

	public int LanceLeDe()
	{
		return De.LanceLeDe();
	}
}

public class MonstreDifficile : MonstreFacile
{
	private const int degatsSort = 5;

	public override void Attaque(Joueur joueur)
	{
		base.Attaque(joueur);
		joueur.SubitDegats(SortMagique());
	}

	private int SortMagique()
	{
		int valeur = De.LanceLeDe();
		if (valeur == 6)
			return 0;
		return degatsSort * valeur;
	}
}

public class Joueur
{
	public int PtsDeVies { get; private set; }
	public bool EstVivant
	{
		get { return PtsDeVies > 0; }
	}

	public Joueur(int points)
	{
		PtsDeVies = points;
	}

	public void Attaque(MonstreFacile monstre)
	{
		int lanceJoueur = LanceLeDe();
		int lanceMonstre = monstre.LanceLeDe();
		if (lanceJoueur >= lanceMonstre)
			monstre.SubitDegats();
	}

	public void Attaque(BossDeFin boss)
	{
		int nbPoints = LanceLeDe(26);
		boss.SubitDegats(nbPoints);
	}

	public int LanceLeDe()
	{
		return De.LanceLeDe();
	}

	public int LanceLeDe(int valeur)
	{
		return De.LanceLeDe(valeur);
	}

	public void SubitDegats(int degats)
	{
		if (!BouclierFonctionne())
			PtsDeVies -= degats;
	}

	private bool BouclierFonctionne()
	{
		return De.LanceLeDe() <= 2;
	}
}

public class BossDeFin
{
	public int PtsDeVies { get; private set; }
	public bool EstVivant
	{
		get { return PtsDeVies > 0; }
	}

	public BossDeFin(int points)
	{
		PtsDeVies = points;
	}

	public void Attaque(Joueur personnage)
	{
		int nbPoints = LanceLeDe(26);
		personnage.SubitDegats(nbPoints);
	}

	public void SubitDegats(int valeur)
	{
		PtsDeVies -= valeur;
	}

	private int LanceLeDe(int valeur)
	{
		return De.LanceLeDe(valeur);
	}
}

C'en est fini pour ce petit TP, mais rassurez-vous, vous allez bientôt avoir l'occasion de l’améliorer davantage pour vous continuer de vous entraîner à l'orienté objet !

Exemple de certificat de réussite
Exemple de certificat de réussite