À vous de jouer
Vous avez développé un jeu qui fonctionne à merveille ! Il y a de plus en plus de personnes qui y jouent et il passe en tête de tous les stores du monde !
Reprenons les règles de notre jeu
Pour rappel, voici un résumé des règles que vous pouvez retrouver dans le cours (partie 2, chapitre 2).
Il s’agit d’un petit jeu où le héros va combattre une série de monstres. Notre joueur démarre avec un certain nombre de point de vies (15 points de vie).
Un monstre se présente pour un combat : on utilise un dé et deux tirages aléatoires pour connaître le résultat d'un combat.
Si le lancer du dé du joueur est supérieur ou égal au dé du monstre, alors le joueur gagne le combat et est crédité d'un point. Dans le cas contraire, le joueur perd le combat et perd un nombre de points de vie égal à la différence entre les deux dés.
Puis un autre monstre se présente, et ainsi de suite, tant que le joueur a encore des points de vie. Une fois que le joueur n'a plus de points de vie, le jeu s'arrête et on regarde le nombre de points du joueur.
Nous avons ensuite ajouté une fonctionnalité (partie 2, chapitre 5) où la perte de points de vie pour le héros est plus importante en fonction de la météo. On tire la météo aléatoirement à chaque tour de jeu et s'il fait soleil ou pluie, alors cela ne change rien ; par contre, en cas de tempête le joueur perd le double de point de vie.
Une nouvelle fonctionnalité est ajoutée !
Ce jeu fonctionne donc très bien ; l’équipe s’agrandit et votre chef a plein de nouvelles idées. Voici la petite dernière qui est sortie de son chapeau, à réaliser bien sûr pour lundi dernier !
Première chose, le joueur doit pouvoir affronter deux types de monstres différents afin que le jeu soit un peu plus pimenté.
Le premier type de monstre a le même comportement qu’actuellement. C’est-à-dire qu’il meurt dès que notre tirage de dé est supérieur ou égal au sien. Le second par contre est un monstre qui a plusieurs points de vie et on doit potentiellement l’attaquer plusieurs fois pour le vaincre. Si le tirage du joueur est supérieur ou égal à celui du monstre, alors le nombre de dégâts causés est égal à la différence de point de dé plus 1. Par exemple, si nous faisons un lancer de 5 et le monstre un lancer de 4, alors il perd de 2 points de vie.
Le nombre de points de vie du monstre est déterminé aléatoirement à sa création.
Seconde chose, le joueur n’affronte plus un nombre illimité de monstres, mais un nombre fixe. Ce nombre est déterminé aléatoirement à la création du jeu. Ainsi, soit le joueur arrive à vaincre tous les monstres et il a gagné, sinon il a perdu.
Pour déterminer quels sont les monstres qui font partie du combat, on tire à nouveau aléatoirement. Nous avons une chance sur deux d’obtenir un monstre classique, idem pour un monstre “costaud”.
Cette évolution a été confiée à votre collègue fraîchement embauché.e.
Voici ce qu’il/elle a réalisé :
class Program
{
static void Main(string[] args)
{
var ihm = new Ihm(new ConsoleDeSortie(), new De(), new FournisseurMeteo(), new FabriqueDeMonstres());
ihm.Demarre();
}
}
public class Ihm
{
private readonly IConsole _console;
private readonly ILanceurDeDe _lanceurDeDe;
private readonly IFournisseurMeteo _fournisseurMeteo;
private readonly IFabriqueDeMonstres _fabriqueDeMonstres;
public Ihm(IConsole console, ILanceurDeDe lanceurDeDe, IFournisseurMeteo fournisseurMeteo, IFabriqueDeMonstres fabriqueDeMonstres)
{
_console = console;
_lanceurDeDe = lanceurDeDe;
_fournisseurMeteo = fournisseurMeteo;
_fabriqueDeMonstres = fabriqueDeMonstres;
}
public void Demarre()
{
var jeu = new Jeu(_fournisseurMeteo, _fabriqueDeMonstres);
_console.EcrireLigne($"A l'attaque : points/vie {jeu.Heros.Points}/{jeu.Heros.PointDeVies}");
while (!jeu.EstTermine())
{
var resultat = jeu.Tour(_lanceurDeDe.Lance(), _lanceurDeDe.Lance());
switch (resultat)
{
case Resultat.Gagne:
_console.Ecrire($"Monstre battu");
break;
case Resultat.Perdu:
_console.Ecrire($"Combat perdu");
break;
default:
throw new NotImplementedException();
}
_console.EcrireLigne($": points/vie {jeu.Heros.Points}/{jeu.Heros.PointDeVies}");
}
if (jeu.Heros.PointDeVies > 0)
{
_console.EcrireLigne("Le joueur est vainqueur !! Félicitations...");
}
else
{
_console.EcrireLigne("Après un courageux combat, le joueur a malheureusement été vaincu ...");
}
}
}
public interface IMonstre
{
bool EstVivant();
void PerdsUnCombat(int nb);
}
public class Monstre : IMonstre
{
private bool _estVivant;
public Monstre()
{
_estVivant = true;
}
public bool EstVivant()
{
return _estVivant;
}
public void PerdsUnCombat(int nb)
{
_estVivant = false;
}
}
public class MonstreCostaud : IMonstre
{
private int _ptsDeVie;
public MonstreCostaud(int ptsDeVie)
{
_ptsDeVie = ptsDeVie;
}
public bool EstVivant()
{
return _ptsDeVie > 0;
}
public void PerdsUnCombat(int nb)
{
_ptsDeVie -= nb;
}
}
public interface ILanceurDeDe
{
int Lance();
}
public interface IConsole
{
void Ecrire(string message);
void EcrireLigne(string message);
}
public class ConsoleDeSortie : IConsole
{
public void Ecrire(string message)
{
Console.Write(message);
}
public void EcrireLigne(string message)
{
Console.WriteLine(message);
}
}
public class De : ILanceurDeDe
{
private Random random;
public De()
{
random = new Random();
}
public int Lance()
{
return random.Next(1, 7);
}
}
public interface IFournisseurMeteo
{
Meteo QuelTempsFaitIl();
}
public class FournisseurMeteo : IFournisseurMeteo
{
private readonly Random _random;
public FournisseurMeteo()
{
_random = new Random();
}
public Meteo QuelTempsFaitIl()
{
var tirage = _random.Next(0, 21);
if (tirage < 10)
return Meteo.Soleil;
if (tirage < 20)
return Meteo.Pluie;
return Meteo.Tempete;
}
}
public enum Meteo
{
Soleil,
Pluie,
Tempete
}
public interface IFabriqueDeMonstres
{
IEnumerable<IMonstre> GetMonstres();
}
public class FabriqueDeMonstres : IFabriqueDeMonstres
{
private readonly Random _random;
public FabriqueDeMonstres()
{
_random = new Random();
}
public IEnumerable<IMonstre> GetMonstres()
{
int nbMonstres = _random.Next(1, 10);
for (int i = 0; i < nbMonstres; i++)
{
if (_random.Next(0, 2) == 0)
yield return new Monstre();
else
{
int nbPtsDeVie = _random.Next(1, 10);
yield return new MonstreCostaud(nbPtsDeVie);
}
}
}
}
public class Jeu
{
private readonly IFournisseurMeteo _fournisseurMeteo;
private Queue<IMonstre> _monstres;
private IMonstre _monstreCourant;
public Heros Heros { get; }
public Jeu(IFournisseurMeteo fournisseurMeteo, IFabriqueDeMonstres fabriqueDeMonstres)
{
Heros = new Heros(15);
_monstres = new Queue<IMonstre>(fabriqueDeMonstres.GetMonstres());
_fournisseurMeteo = fournisseurMeteo;
_monstreCourant = _monstres.Dequeue();
}
public Resultat Tour(int deHeros, int deMonstre)
{
if (!_monstreCourant.EstVivant())
{
_monstreCourant = _monstres.Dequeue();
}
if (GagneLeCombat(deHeros, deMonstre))
{
Heros.GagneUnCombat();
_monstreCourant.PerdsUnCombat(deHeros - deMonstre);
return Resultat.Gagne;
}
else
{
var temps = _fournisseurMeteo.QuelTempsFaitIl();
if (temps == Meteo.Tempete)
Heros.PerdsUnCombat(2 * (deMonstre - deHeros));
else
Heros.PerdsUnCombat(deMonstre - deHeros);
return Resultat.Perdu;
}
}
private bool GagneLeCombat(int de1, int de2)
{
return de1 >= de2;
}
public bool EstTermine()
{
return Heros.PointDeVies <= 0 || _monstres.Count == 0;
}
}
public class Heros
{
public int PointDeVies { get; private set; }
public int Points { get; private set; }
public Heros(int pointDeVies)
{
PointDeVies = pointDeVies;
}
public void GagneUnCombat()
{
Points++;
}
public void PerdsUnCombat(int nb)
{
PointDeVies -= nb;
}
}
public enum Resultat
{
Gagne,
Perdu
}
Votre collègue vous explique qu’il/elle a créé une classe Monstre et une classe MonstreCostaud
qui implémentent l’interface IMonstre
, en se basant sur le code existant et en l’enrichissant.
Pour déterminer la liste de monstre faisant partie du combat, on utilise une classe FabriqueDeMonstres
, et c’est cette classe qui détermine le nombre de monstres pour le combat, ainsi que les types monstres.
Tout à l’air prêt pour la sortie d’une nouvelle version du jeu, mais vous voulez vous en assurer en créant des tests, car vous avez bien suivi ce cours.
Instructions
Vous devez donc :
Corriger les tests qui existaient avant l'ajout de cette nouvelle fonctionnalité pour qu’ils compilent et passent à nouveau. En effet, l’évolution a perturbé les tests déjà présents que votre collègue n’a pas pris le temps de maintenir.
Vous devez utiliser Moq pour simuler des éventuelles dépendances.
Ajouter un test dans la classe
IhmTests
permettant de valider un scénario fonctionnel pour cette nouvelle version du jeu. Une partie du test a déjà été écrite, notamment la liste des monstres que l’on veut avoir pour le contexte du test, ainsi que la suite de tirage aléatoire du dé. Il y a également l’assertion finale permettant de valider le scénario.
Avec ce jeu de test et ce scénario, vous allez vous rendre compte que votre collègue a laissé traîner 2 bugs dans l’application. Et devinez quoi, vous devez également corriger les problèmes !
Vérifiez votre travail
Vérifiez que vous avez bien les éléments suivants :
Le code et les tests compilent.
Un faux objet a été mis en place pour simuler un comportement avec Moq.
Les tests actuels conservent leur fonctionnement et la même séquence simulée de lancer de dés. Ils testent bien quelque chose.
Les sept tests passent.
Les deux bugs ont été corrigés, avec le minimum d’impact sur le code existant.