Événements liés
  • Démo technique du samedi #7: 14 Juillet 2007
Pages: [1]   Bas de page
Imprimer
Auteur Fil de discussion: [Demo] Le bomberman à la Doud, ou comment organiser son projet  (Lu 6474 fois)
0 Membres et 1 Invité sur ce fil de discussion.
Pitt Hors ligne
Administrateur
*****
Messages: 575


Voir le profil WWW
« 14 Juillet 2007, 16:52:54 »

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. Azn

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 ... Tongue
Voilà, je vous laisse lire tout ça ! Azn




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 à traiterPoints à 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 membresListe des méthodes

http://www.gp2xfr.info/uploads/images/bomberman_uml.png

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.

Code
(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.

Code
(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.

Code
(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.

Code
(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.

Code
(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.

Code
(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.

Code
(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.

Code
(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.

Code
(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).

Code
(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.

Code
(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)

Code
(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 ! Wink

Les sources
La version PDF

Journalisée
Mollusk Hors ligne
PAlib Guru et
Administrateur
*****
Messages: 3480


Voir le profil WWW
Ne vous posez pas de questions, codez !

« Réponse #1 : 14 Juillet 2007, 16:53:38 »

Waouh !
Journalisée

Nameless Hors ligne
Full Member
***
Messages: 153


Voir le profil WWW
« Réponse #2 : 14 Juillet 2007, 17:14:04 »

Bon, ben je vais me coucher moi....


--------> []
Journalisée

Arcadia Hors ligne
Newbie
*
Messages: 32


Voir le profil WWW
« Réponse #3 : 15 Juillet 2007, 09:19:53 »

Waouh !

+1  Smiley

Absolument bien fait le tuto  Wink !!!
Journalisée

Arcadia
---
Ouiiiiiinnnn, ouiiiiiinnnn ! (Arcadia, 1971)
omg Hors ligne
Full Member
***
Messages: 125


Voir le profil WWW
Codeur au Percil

« Réponse #4 : 18 Juillet 2007, 06:36:39 »

 Azn Sympa en effet!
Journalisée

selkis Hors ligne
Jr. Member
**
Messages: 78


Voir le profil WWW
On s'en pose pas mollusk, on code! ;)

« Réponse #5 : 18 Décembre 2007, 01:16:07 »

simple et fonctionnel que demander de plus
bravo pour ce projet
Journalisée
Pages: [1]   Haut de page
Imprimer

Aller à: