Et voici une nouvelle démo de samedi, qui est doublement spéciale ! Non seulement elle est la première démo PC, mais en plus elle nous a été concoctée par Doud. A la base, il nous avait proposé son Bomberman pour le pimp my code. Mais comme son code n'avait pas de "gros" défaut général - ce dont on a besoin pour le pimp - et bien nous vous le proposons comme démo du samedi.

En fait, ce Bomberman est le projet de fin d'année de l'ami Doud. Et qui dit projet de fin d'année, dit rapport à rendre aux profs ... Son rapport explique pas à pas toutes les étapes de la conception de son jeu, et explique tous les algos, les choix faits ou à faire, etc. Et oui les profs ne demandent qu'à apprendre, pauvres ignorants qu'ils sont ...

Voilà, je vous laisse lire tout ça !

Présentation du projet :Lors du second semestre de l’année spéciale, 2 projets de programmation de C++ nous on été proposés, le Bomberman ou le Casse brique. Nous avons choisi de traiter le Bomberman.
Dans ce type de jeu, le joueur incarne un poseur de bombes, le but étant de faire exploser les ennemis, ainsi que certains éléments du décor afin de pouvoir se déplacer plus facilement sur le plateau de jeu.
Analyse des besoins :Avant de commencer à coder le programme, nous nous sommes mis d’accord sur certaines règles à adopter pour le jeu :
- Le joueur peut poser une bombe et détruire uniquement des caisses.
- Le joueur ne peut poser qu’une bombe à la fois.
- Les ennemis ne posent pas de bombes et sont « invincibles »
- Si le joueur touche un ennemi, il « meurt » et revient au point de départ.
Ensuite, nous avons listé un certain nombre de points à aborder ou à laisser de côté du fait de leur complexité ou du temps de réalisation.
| Points à traiter | Points à ne pas traiter |
- Charger un niveau depuis un fichier - Gérer les déplacements du personnage (et l’interaction avec le décor) - Possibilité de poser des bombes et donc de détruire des morceaux de décor - IA simpliste - Buffering
| - Les graphismes - Les bonus - IA complexe
|
Une fois cette liste obtenue, nous avons listé toutes les données et fonctions nécessaire au bon fonctionnement du programme. Ce qui nous a permis de réaliser un diagramme de classe UML, mettant en évidence l’héritage, les données membres et les méthodes de chaque classe.
Nom de | la classe |
| Liste des données membres | Liste des méthodes |
Définition des classes :Classe Carte :Cette classe sera utilisée pour gérer le niveau et contiendra les informations de la carte (position des caisses, mur, joueur). Un niveau est composé d’un tableau de 11*11.
On y trouve en donnée membre un pointeur sur un tableau à 2 dimensions **tab. Les méthodes de cette classe sont :
- Un constructeur.
- Un constructeur par recopie.
- Un destructeur.
- Une surdéfinition d’opérateur.
- Une fonction membre d’initialisation.
- Une fonction membre d’affichage.
- Une fonction membre permettant de modifier une case du tableau.
Remarque : Les cases du tableau peuvent prendre 4 valeurs : 0 pour une case libre, 1 pour un mur (incassable, infranchissable), 2 pour une caisse (cassable), et 3 pour une bombe (infranchissable).
Le constructeur se charge de réserver dynamiquement l’espace mémoire nécessaire au stockage des informations du plateau de jeu. De plus il initialise l’ensemble des cases du tableau à 0.
(algorithme):
DEBUT
Tab = allouer_mémoire int*[11]
Pour i variant de 0 à 10 faire
Tab[i] = allouer_mémoire int[11]
Pour i variant de 0 à 10 faire
tab[i][j] <- 0
Fin Pour
Fin Pour
FIN
Le constructeur de recopie prend en charge la recopie parfaite de l’objet avec réallocation de mémoire pour ce nouvel objet. Il prend en paramètre un objet de type carte.
(algorithme):
DEBUT
Tab = allouer mémoire int*[11]
Pour i variant de 0 à 10 faire
Tab[i] = allouer mémoire int[11]
Pour i variant de 0 à 10 faire
tab[i][j] <- carte_a_copier.tab[i][j]
Fin Pour
Fin Pour
FIN
Le destructeur quand a lui libère en fin de programme l’espace mémoire alloué par le constructeur.
(algorithme):
DEBUT
Pour i variant de 0 à 10 faire
Libérez tab[i]
Tab[i] <- NULL
Fin pour
Libérez Tab
FIN
La sur définition de l’opérateur ( ) permet d’accéder à une case du tableau. Elle prend en paramètre 2 entiers X et Y et retourne un entier correspondant à la valeur de la case [X][Y] du tableau.
La fonction d’initialisation permet de remplir le tableau à partir d’un fichier texte. Elle prend en paramètre un pointeur sur fichier.
(algorithme):
DEBUT
Si (ouverture(niveau.txt) == NULL)
Afficher (« Impossible d’ouvrir le fichier du niveau »)
Sinon
Pour i variant de 0 à 10 faire
Pour j variant de 0 à 10 faire
Tab[i][j] <- Lecture(1 entier)
Fin Pour
Fin Pour
Fin Si
FIN
La fonction d’affichage est plutôt simpliste, elle prend en paramètre un pointeur d’image pour y afficher un quadrillage qui représente le plateau de jeu. Chaque case fait 40*40 pixels. Les autres éléments (personnage, mur, caisse) seront affichés dans d’autres fonctions appropriées.
(algorithme):
DEBUT
Pour i variant de 0 à 11 faire
Ligne_verticale (buffer, (10+40*i), 10, 450, blanc))
Ligne_horizontale (buffer, 10, (10+40*i), 450, blanc))
Fin Pour
FIN
Enfin la fonction « change » permet de modifier une case du plateau de jeu (pose d’une bombe, suppression d’une caisse…). Elle prend 3 entiers en paramètre, les 2 premiers pour situer la case en X et Y, et le troisième pour la nouvelle valeur.
Classe Point :Cette classe est la classe de base du projet. Elle permet de référencer les coordonnées de tous les objets.
Elle possède 2 entiers en données membre X et Y. Les méthodes de cette classe sont :
- Deux constructeurs.
- Un destructeur.
- Une fonction membre d’initialisation.
- Deux fonctions membre permettant d’accéder en lecture aux données protégées.
Le constructeur ne prenant pas de paramètre initialise les 2 données membre à 0. Celui avec 2 arguments initialise X et Y respectivement aux paramètres passés.
La fonction d’initialisation est semblable à celle du constructeur avec 2 arguments. Elle affecte à X et Y les données passés dans la fonction.
La fonction Get_X() renvoie le contenu de la variable X et Get_Y() celui de Y.
Classe Bombe :Cette classe hérite de la classe point. Nous avons ajouté 2 données membre supplémentaires :
- Time (qui sert de timer à la bombe avant qu’elle n’explose).
- Use (un booléen permettant de savoir si la bombe est posée). En effet, nous n’avons géré qu’une seule bombe dans ce projet.
Les méthodes de cette classe sont :
- Un constructeur.
- Un destructeur.
- Une fonction membre d’affichage.
- Une fonction membre timer.
- Une fonction membre pose.
- Deux fonctions membre permettant d’accéder aux 2 nouvelles données de cette classe.
Le constructeur initialise X, Y et time à 0, ainsi que le booléen à faux.
Le destructeur reste vide, n’ayant pas de libération de mémoire à effectuer.
La fonction d’affichage prend en paramètre un pointeur d’image. Elle affiche un triangle grâce aux coordonnées des trois sommets et du X Y de la bombe.
La fonction Timer prend 3 arguments : un pointeur sur image, une référence sur une carte, et une référence sur les caisses. Cette fonction attend un laps de temps défini, puis effectue des tests autour de la bombe. Si une caisse est présente au dessus, en dessous, à gauche ou à droite de la bombe, elle sera supprimée.
(algorithme):
DEBUT
Haut, bas, gauche, droite de type entier.
Si (timer++ >= 150) // Si le temps de bombe est dépassé, elle explose.
Si (y==0) alors haut <- 0 // Si la bombe est sur la première ligne
Sinon haut <- y-1
Fin Si
Si (y==10) alors bas <- 10 // Si la bombe est sur la dernière ligne
Sinon bas <- y+1
Fin Si
Si (x==0) alors gauche <- 0 // Si la bombe est sur la première colonne
Sinon gauche <- x-1
Fin Si
Si (x==10) alors droite <- 10 // Si la bombe est sur la dernière colonne
Sinon droite <- x+1
Fin Si
Si map(x, haut) == 2 alors // S’il y a une caisse au dessus de la bombe
Map.change(x, haut, 0) // On change la valeur dans le tableau
Box.suppression(x, haut) // On supprime la caisse
Fin Si
// De même pour (x, bas) (gauche, y) et (droite, y)
Time <- 0
Use <- FAUX
Map.change(x, y, 0) // On remet à 0 la case [x][y]
Fin Si
FIN
La fonction pose prend 3 paramètres : 2 entiers et la référence vers la carte. On place la bombe en X, Y (ou se trouve le joueur), use passe à VRAI et on met la case [x][y] de la carte à 3.
La fonction Get_use() renvoi Vrai si la bombe est posée, et donc qu’il n’est pas possible d’en poser une autre. La fonction Get_time() retourne le timer de la bombe.
Classe Bomberman :Cette classe hérite aussi de la classe point. Nous n’avons pas jugé nécessaire d’ajouter des données membre, la position en X et en Y du bomberman est définie par les données de la classe point.
En revanche, les méthodes ajoutées sont :
- Deux constructeurs.
- Un destructeur.
- Une fonction membre affiche.
- Une fonction membre déplace.
Le constructeur sans paramètre initialise la position du bomberman en X et Y à 0. L’autre constructeur prend 2 entiers paramètres, permettant d’initialiser la position du bomberman ou l’on veut sur le plateau de jeu.
La fonction affiche dessine un cercle de rayon 15 pixels à la position du bomberman, en rouge.
La fonction déplace prend en paramètre la référence vers la carte, ainsi que le sens de déplacement. 1 pour haut, 2 pour droite, 3 pour bas, 4 pour gauche.
En fonction du sens, on vérifie si on ne se trouve pas sur un bord, et s’il n’y a pas d’obstacle pour se déplacer.
(algorithme):
DEBUT
Cas (sens) parmi
1 : Si ((y !=0) && (map(x, y-1) == 0)) Alors
Y <- y–1
Fin Si
2 : Si ((x !=10) && (map(x+1, y) == 0)) Alors
X <- x+1
Fin Si
3 : Si ((y !=10) && (map(x, y+1) == 0)) Alors
Y <- y+1
Fin Si
4 : Si ((x !=0) && (map(x-1, y) == 0)) Alors
X <- x–1
Fin Si
Fin Cas Parmi
FIN
Classe IA :Cette classe hérite de la classe bomberman. Nous avons souhaité intégrer une sorte d’intelligence artificielle. Pour ce faire, nous avons ajouter en donnée un compteur (qui fera le déplacement tout les X temps). Puis dans une fonction, on choisi un sens au hasard, et si possible on se déplace.
De plus, les méthodes ajoutées sont les suivantes : - Un constructeur.
- Un destructeur.
- Une fonction membre moveIA.
- Une fonction membre affiche.
- Et une fonction membre checkcollision.
Le constructeur prend 2 paramètres, pour initialiser la position de l’IA en X et Y sur le tableau, et initialise time à 0.
La fonction moveIA prend la référence de la carte en paramètre. Au bout d’un temps donné, elle choisi un sens de déplacement aléatoirement et fait se déplacer l’IA.
(algorithme):
DEBUT
Si ((time <- time + 1) > 30) alors
Sens = Random (4)
Déplace(map, sens)
Time <- 0
Fin Si
FIN
La fonction affiche prend en argument un pointeur d’image, ainsi qu’un entier (pour afficher une couleur différente pour chaque ennemi). Elle affiche dans le buffer un cercle de couleur à la position [X][Y] de l’ennemi.
La fonction booléenne checkcollision prend en paramètre un objet de type bomberman. Elle vérifie si le bomberman est à la même place que l’IA, si c’est le cas, affichage d’un message et elle renvoie VRAI, sinon FAUX est retourné en résultat
Classe mur :Cette classe hérite aussi de la classe point. La gestion des murs est réalisée à l’aide d’une liste chaînée et d’une structure wall. Cette structure est composée de 2 entiers (placement en X et Y) et d’un pointeur de structure wall.
En ce qui concerne la classe mur, elle est composée de 3 pointeurs de type wall, un pour la tête de liste, un pour le pointeur courant, et un pour le pointeur précourant. On y trouve aussi un entier permettant de recenser le nombre d’éléments.
Les méthodes de cette classe sont : - Un constructeur.
- Un constructeur par recopie.
- Un destructeur.
- Une fonction membre affiche.
- Une fonction membre init.
- Et une fonction membre ajoute.
Le constructeur ne prend pas de paramètre, et crée une liste vide. On initialise les 3 pointeurs à NULL, et le nombre d’élément à 0.
Le constructeur par recopie a été réalisé de cette façon :
- Création de 3 pointeurs de type wall. (1 pour la création de liste, 2 pour se déplacer sur la liste créée et la liste « d’origine » (ici L.A.C : liste à copier))
- Le nombre d’éléments prend pour valeur celui de la L.A.C.
- Avec une première boucle « pour » à chaque itération correspondra :
• Création d’un nouveau mur.
• Le suivant pointe sur la tête.
• Le X et le Y du nouveau mur prennent la valeur du mur à copier
• Initialisation de la tête à ce nouveau mur.
Pour le destructeur :
- Création d’un pointeur « tmp » de type wall.
- A l’aide d’une boucle « tant que » la liste n’est pas vide :
• Tmp prend pour valeur la tête.
• La tête devient le suivant.
• Libération de l’espace occupé par tmp.
La fonction membre affiche prend en paramètre un pointeur d’image et un entier (pour la couleur du mur, soit un mur blanc, soit une caisse bleue.
- Création d’un pointeur « tmp » de type wall.
- Définition de la couleur.
- A l’aide d’une boucle « tant que » la liste ne pointe pas sur NULL, affichage de la caisse ou du mur.
(algorithme):
DEBUT
Wall *tmp = tête
Si (couleur == 1) Alors
Couleur <- blanc
Sinon
Couleur <- bleu.
Fin Si
Tant que (tmp != NULL)
Affichage_carré (buffer, x1, y1, x2, y2, couleur)
Tmp <- tmp.suivant
Fin Tant que
FIN
La fonction init prend en paramètre la référence sur la map, et un entier prenant pour valeur 1 ou 2 (si il s’agit d’un mur ou d’une caisse).
(algorithme):
DEBUT
Courant <- tête
Precourant <- courant
Pour i variant de 0 à 10 faire
Pour j variant de 0 à 10 faire
Si (map(i,j)==nb) alors
Ajoute(i,j)
Fin Si
Fin Pour
Fin Pour
FIN
Enfin la fonction ajoute, qui comme son nom l’indique ajoute un élément à la liste chaînée. Elle prend en paramètre 2 entiers, pour repérer encore une fois la position sur la carte.
(algorithme):
DEBUT
Wall *new_wall
Nbrelmt <- nbrelmt + 1
new_wall <- New Wall
new_wall.x <- x
new_wall.y <- y
new_wall.suivant <- tête
tête <- new_wall
FIN
Classe caisse :Cette classe hérite de la classe mur. Le seul ajout à cette classe est la fonction membre suppression d’un élément de la liste chaînée. En effet, si une caisse se trouve directement à gauche, droite, en haut ou en bas d’une bombe qui explose, la caisse se brise. Il faut donc prévoir la libération de mémoire occupée par un élément de la liste.
Cette fonction prend en paramètre 2 entiers (pour la position en X, Y de la caisse)
(algorithme):
DEBUT
Courant <- tête
Precourant <- courant
Bool continuer <- VRAI
Tant que (continuer) faire
Si ((courant.x == x) && (courant.y == y)) alors // Si les coordonnées
Si (precourant != courant) alors // correspondent
Precourant.suivant <- courant.suivant
Sinon
Tete <- courant.suivant
Fin Si
Libérer (courant)
Nbrelmt <- nbrelmt – 1
Continuer <- FAUX
Sinon // si pas les bonnes coordonnées, on avance les pointeurs
Precourant <- courant
Courant <- courant.suivant
Fin Si
Fin Tant que
FIN
Le programme principal :Le projet a été réalisé avec la librairie graphique Allégro. Nous allons étudier les étapes aborder pour le bon déroulement du programme.
Tout d’abord les variables. On utilise un entier pour stocker la touche pressée par l’utilisateur. Ensuite nous avons besoin d’un pointeur sur image pour le buffer. Un pointeur sur fichier pour permettre la lecture du niveau. A cela s’ajoute une instance de carte, de bomberman, de bombe, de mur et de caisse. Enfin les 3 IA.
Les variables étant déclarées, il faut initialiser la librairie, le clavier, la couleur et le système de random. Ensuite on installe le mode graphique et on crée un buffer de mêmes dimensions que la fenêtre principale.
Cela étant fait, on commence par initialiser le niveau. On envoi donc le pointeur sur fichier dans la fonction init de la classe carte. Ensuite on crée les listes chaînées des murs et des caisses en envoyant la carte dans les fonctions d’initialisation.
Arrive la boucle principale du programme. Tant que l’utilisateur n’appuie pas sur la touche echap, on va répéter ce bloc d’instruction :
Tout d’abord on regarde si le joueur presse une touche, et si c’est le cas, on effectue en action en conséquence. Si la touche est HAUT, BAS, GAUCHE, ou DROITE on demande au bomberman de se déplacer, on envoie donc dans la fonction déplace la carte et le sens, et la position du joueur sera modifiée s’il y a possibilité de déplacement.
Si la touche est ESPACE, le joueur veut poser une bombe, on vérifie donc si la bombe n’est pas déjà posée grâce à la fonction Get_Use, et si elle est disponible, on pose la bombe à la position X, Y sur la carte. Les autres touches sont ignorées.
Ensuite, on fait se déplacer les IA avec la fonction moveIA.
On va ensuite effectuer les différents affichages. Pour commencer, on efface le buffer, si une bombe est posée, on augmenter le timer, et on affiche la bombe. Ensuite, il y aura affichage successif des éléments suivant : la carte, les murs, les caisses, le bomberman, et les 3 adversaires. Tous ces éléments présents dans le buffer, il ne reste qu’à l’afficher à l’écran.
Pour terminer la boucle principale, on vérifie si le bomberman n’est pas touché par un adversaire, et si c’est le cas, affichage d’un message « perdu », et replacement du joueur en position initiale.
A la sortie de la boucle par demande de l’utilisateur, on libère l’espace mémoire utilisé par le buffer, et on retourne un succès de sortie.
Voilà j'espère que ça vous a plu.
Un grand merci à Doud pour sa participation !
Les sources
La version PDF