TP 3 : Réalisation d'un "Space invader" et Projet associé

Mettre de la couleur dans le terminal sous GNU / Linux

Puisque les cours d'IHM (Interface Homme Machine) ne commencent qu'au second semestre, nous allons voir comment nous pouvons spécifier, dans un programme, la couleur des caractères ainsi que celle du fond d'une console, mais aussi effacer son contenu et positionner le curseur sur une ligne et une colonne de l'écran.
Puisque la console est une émulation d'un terminal VT100, toutes les instructions que nous allons lui donner commencent par \033[. Ensuite, une autre séquence d'instruction, concaténée à la première, provoque l'effacement de l'écran, le changement de la couleur de la police ou du fond, le placement du curseur en un point de l'écran.

Remarque : les informations évoquées dans cette partie fonctionnent aussi sous Mac OS-X, mais pas sous Windows.

Effacer l'écran

Afin d'effacer l'écran, la seconde séquence est : H\033[2J. En conséquence, la fonction suivante permet d'effacer l'écran :

void ClearScreen ()
{
    cout << "\033[H\033[2J";
}

Copiez la, et mettez la dans votre espace de nom anonyme.

Changer la couleur

Afin de changer la couleur, la seconde séquence est : XXm, où m est le code de la couleur souhaitée. Ce code est constitué de 2 chiffres comme le montre le tableau suivant :

Couleur Code associé
0 réinitialise le système de couleur à la valeur par défaut du shell
30 Noir
31 Rouge
32 Vert
33 Jaune
34 Noir
35 Magenta
36 Cyan

Copiez le code suivant, et mettez le dans votre espace de nom anonyme :
const string KReset   ("0");
const string KNoir    ("30");
const string KRouge   ("31");
const string KVert    ("32");
const string KJaune   ("33");
const string KBleu    ("34");
const string KMAgenta ("35");
const string KCyan    ("36");
	
void Couleur (const string & coul)
{
    cout << "\033[" << coul <<"m";
}	
Couleur (KRouge);
cout << "Rouge" << endl;
Couleur (KVert);
cout << "Vert" << endl;
Couleur (KReset);
cout << "Retour à la normale" << endl;

provoquera l'affichage :

Rouge
Vert
Retour à la normale

Changer le fond

Pour changer le fond, c'est exactement la même séquence d'instructions que pour changer la couleur. La seule différence provient du fait que les couleurs associées voient leur code respectif augmenté de 10.

Pour des informations complémentaires quant à la gestion du terminal (sous GNU / Linux), vous pouvez visiter cette page.

Se positionner en un point de l'écran

Afin de se positionner à l'écran au point de coordonnées X et Y, la seconde séquence est : H\033[X;YH. En conséquence, la fonction suivante permet d'effacer l'écran :

void GotoXY (unsigned X, unsigned Y)
{
    cout << "\033[" << Y << ';' << X << "H";
    
} // gotoxy()                 

Copiez la, et mettez la dans votre espace de nom anonyme.

Gestion de la grille

Le but du projet est de réaliser une version simplifiée du célèbre jeu SpaceInvaders).

Les règles du jeu sont les suivantes :

  1. Un unique joueur joue contre un unique envahisseur. Le joueur est toujours sur la ligne du bas de l'écran (dernière ligne de la grille (une matrice de caractères) représentant l'écran), tandis que l'envahisseur est placé, au début de la partie, en haut de l'écran (la première ligne de la grille (la matrice peut être un vector de strings de même taille)).

  2. À chaque fois que c'est son tour de jouer, l'envahisseur avance d'une colonne vers la droite. Lorsqu'il est aux limites de la grille, il descend d'une ligne et change de sens (la prochine fois, il se déplacera vers la gauche), et ainsi de suite. Une fois sur 2, il envoie un missile vers le bas (le missile part de la colonne où se trouve l'envahisseur et de la ligne suivante). Si le missile touche le joueur, ou si l'envahisseur est arrivé en bas de l'écran, le joueur a perdu.

  3. À chaque fois que c'est son tour de jouer, le joueur a 3 possibilités : se déplacer vers la droite (il saisit la touche 6), se déplacer vers la gauche (il saisit la touche 4) ou envoyer une torpille (il saisit la touche 5). Si une torpille atteint l'envahisseur, le joueur a gagné.

  4. Le joueur joue plusieurs fois pendant que l'envahisseur ne joue qu'une fois.

La grille est représentée par une matrice de taille KSizeSpace x KSizeLine. En conséquence, nous pouvons définir les constantes et les alias suivants :
    const char KEmpty               = ' ';  // case vide de l'écran
    const char KRight               = '6';  // déplacement vers la droite
    const char KLeft                = '4';  // Déplacement vers la gauche
    const char KShoot               = '5';  // Lancé de torpille

    //  Constantes liées à l'envahisseur
    
    const string KInvadersColor (KYellow);  // Couleur de l'envahisseur
    const char KInsideInvader       = 'W';  // caractèrere formant l'envahisseur
    const char KMissile             = 'T';  // missile
    const unsigned KInvadersSize    =  4;   // nombre de caractères qui forment l'envahisseur
    const unsigned KInvadersMiddle  = KInvadersSize / 2;
    const string KInvadersForm (KInvadersSize, KInsideInvader);  // forme de l'envahisseur
    
    // Constantes liées au joueur
    
    const string KMyColor (KGreen);
    const char KInsideMe            = 'A';
    const char KTorpedo             = '|';  
    const unsigned KMySize          = 1;
    const unsigned KMyMiddle        = KMySize / 2;
    const string KMyForm (KMySize, KInsideMe);
    
    // Constantes liées à l'eapace (l'écran)
    
    const unsigned KSizeLine   = 10;   // Nombre de lignes de l'écran (de l'espace)
    const unsigned KSizeSpace  = 10;   // Nombre de colonnes de l'écran (de l'espace)
    
    const unsigned KBegInvader = 0;    // Numéro de colonne où commence l'envahisseur
    const unsigned KBegMe      = KSizeLine / 2;  // Numéro de colonne où commence le joueur
    
    typedef vector <string> CVString;    // c'est le type de l'écran (l'espace, la matrice)
    
    const string KEmptyLine (KSizeLine, KEmpty);  // Une ligne vide de la matrice 
    
    const unsigned KRatioMeInvaders = 4;    // Nombre de fois où c'est le tour du joueur pour un tour de l'envahisseur
    

Affichage de la grille

Travail à effectuer : écrire le corps de la fonction DisplaySpace () de profil :

void  DisplaySpace (const CVString & Space)

Cette fonction doit :

  1. faire appel à la fonction ClearScreen ();

  2. afficher case/case le contenu de l'espace (l'écran, la grille). Si la case n'est pas occupée, on affiche une case vide, sinon, si c'est une case où se trouve le joueur ou une de ses torpilles, on l'affiche avec la couleur du joueur, sinon on l'affiche avec la couleur de l'envahisseur.

Le jeu

Initialisation de la grille

Ecrire la fonction InitSpace () de profil :

void InitSpace (CVString & Space, unsigned Size);

Cette fonction a pour but d'initialiser toutes les cases de la grille avec le caractère KEmpty ainsi que de placer, sur la première ligne, l'envahisseur (KInvadersForm) à sa position initiale (KBegInvader) et, sur la dernière ligne, le joueur (KMyForm) à sa position initiale (KBegMe).

Faire descendre l'envahisseur d'une ligne

Pour gérer le jeu de l'envahisseur et du joueur, il est nécessaire d'écrire quelques outils.

Écrire la fonction DownShift() de profil

void DownShift (CVString & Space, unsigned CurrentLine);

qui fait descendre toutes les lignes de la grile d'une ligne (sauf la derenière) et remplace la première ligne par une ligne vide.

Un joueur est-il mort ?

Écrire le prédicat IsDead() de profil

bool IsDead (const CVString & Space, unsigned Line, unsigned Column, char Who);

qui renvoie vrai si la case de la ligne Line, et de la colonne Column de Space, est une case de l'envahisseur ou du joueur, mais pas Who et faux sinon.

Tir d'un joueur

Écrire le prédicat Shoot() de profil

bool Shoot (CVString & Space, unsigned Line, unsigned Middle, char Projectile, char Who);

qui renvoie vrai si le tir projeté en ligne Line et en colonne Middle touche directement l'adversaire (utiliser IsDead()), sinon, place Projectile sur la case Line, Middle et renvoie faux.

Suppression d'un des 2 joueurs

Écrire la fonction Remove() de profil

void Remove (CVString & Space, unsigned Line, unsigned Column);

qui remplace tous les caractères identique à Space [Line][Column], contigüs, à gauche et à droite de cette case, par des KEmpty.

Recalculer la grille

Lorsque l'envahisseur, d'une part, et le jouieur, d'autre part, ont lancé des missiles et des torpilles, après chaque action d'un des deux joueurs, il faut faire progresser les missiles (qui descendent d'une ligne) et les torpilles (qui montent d'une ligne) sur la grille.

Écrire la procédure RecomputeSpace() de profil

    void RecomputeSpace (CVString & Space, bool & Win, bool & Lost);

qui déplace Torpilles et Missiles d'une ligne sur la grille. Cette procédure rend vrai Win si une torpille doit toucher l'envahisseur. Elle rend également vrai Lost si un missile doit toucher le joueur. Elle supprime (Remove) le joueur ou l'envahisseur de sa ligne s'il a perdu.

Pour vous faciliter le traitement, vous pouvez décomposer en deux procédure, une qui parcours la grille en descendant pour faire progresser les torpilles (bien qu'elles aillent vers le haut), elle modifie Win et une qui parcours la grille dans l'autre sens pour faire progresser les missiles.

Jeu de l'envahisseur

Écrire le prédicat ManageInvader() de profil

bool ManageInvader (int & Increment, unsigned & CurrentLine,   
                    unsigned & Beg, bool & ToShoot, 
                    bool & Win, CVString & Space);

qui simule je jeu de l'envahisseur comme décrit dans B, renvoie vrai lorsque l'envahisseur a gagné et faux sinon.

Jeu du joueur

Écrire le prédicat ManageMe() de profil

bool ManageMe (CVString & Space, unsigned & Pos, bool & Lost);

qui simule je jeu du joueur comme décrit dans B, renvoie vrai lorsque le joueur a gagné et faux sinon. C'est ici que le joueur va choisir ce qu'il fait. Renvoie vrai si le joueur a gagné, faux sinon.

L'algorithme principal

Écrire la procédure SpaceInvaders() de profil

void SpaceInvaders (void);

qui :

Lecture d'un fichier de configuration

Puisque le jeu (tel qu'il est proposé) n'est pas très malléable, on se propose de lire un fichier de configuration qui doit contenir la dé finition des constantes qui vous sont proposées (elles ne seront donc plus constantes puisqu'initialisées à l'exécution). Bien entendu, les constantes de la partie A n'ont pas à être configurées.

La documentation

Doxygen est un puissant outil permettant de générer automatiquement une partie de la documentation de votre projet de C++. Malheureusement, cet outil n'est pas complètement disponible sur la version de Debian du département (ma faute). En revanche, il est relativement complet sous Windows.
Une fois que vous êtes sûr que votre code est "fonctionnel", retourner sous Windows, ajouter les balises nécessaires en suivant cette aide. Veillez à bien activer les références croisées entre le code source et la sortie.
Faites des tests jusqu'à que ce votre fichier de sortie ressemble à celui fourni dans la correction. Ce fichier est le fichier généré par la solution (partielle) du projet.

Le projet

Le fichier de solution est une ébauche de solution. Vous disposez d'un peu plus de 1 mois pour y ajouter toutes les fonctionnalités que vous souhaitez (sans pour autant changer de langage de programmation).

Le rendu de projet

Vous trouverez ici une ébauche de ce qui est attendu. Dans cette ébauche :
  1. Nous avons ajouté la lecture du fichier de configuration au format YAML;
  2. Nous avons repercuté la prise en compte de ces paramètres dans certaines fonctions (mais pas dans toutes);
  3. Nous avons généré une partie de la documentation en conséquence;
  4. Il manque cependant la documentation (fichier pdf) expliquant les modifiactions apportées.
Remarque : Ne perdez pas de temps à faire une IHM, ce n'est pas l'objectif.

Soutenance orale

Lors de votre soutenance, vous aurez 15 minutes (maximum) pour effectuer une présentation via diaporama afin d'expliquer quels sont les apports de votre projet par rapport à la version initialement proposée. S'en suivra une discussion de 15 mn.

Pénalités

La liste de pénalités suivante n'est pas exhaustive :

  1. Projet rendu en retard : -1 pt par heure d'envoi de l'email (les emails sont horodatés);
  2. Copie sur un autre groupe ( on ne savait pas comment implémenter telle ou telle fonctionnalité dont on avait besoin pour aller plus loin, on l'a donc copiée sur un autre groupe), plusieurs cas se distinguent :
    • si le groupe est clairement nommé, cette fonctionnalité ne sera pas prise en compte dans la notation;
    • si le groupe est clairement nommé, et qu'il y a une amélioration de la fonctionnalité, (note pour la fonctionnalité / 2 - seulement la moitié du travail a été réalisée);
    • 0 aux deux groupes sinon.
    Deux groupes peuvent développer la même fonctionnalité, mais ils ne doivent pas avoir la même implémentation, les mêmes jeux d'essais.