Projet Labyrinthe

Transcription

Projet Labyrinthe
Projet Labyrinthe
L2 info – Algo Avancée/C++
Thème : À travers la réalisation d'un jeu d'aventure simple, on utilisera les
algorithmes essentiels de la théorie des graphes, tout en mettant en oeuvre
les bases de C++ et de la programmation-objet.
Étape 1 : création d'un labyrinthe.
On veut créer un labyrinthe 2D.
• On utilisera un tableau de cellules qui contient la version "linéaire" (1D) du labyrinthe. Pour
plus de commodité, ce tableau sera contenu dans un type Labyrinthe qui contiendra aussi les
dimensions dimX, dimY du labyrinthe, et les coordonnées de son point d'entrée.
• On créera des fonctions permettant de traduire 2 coordonnées 2D (x ,y) en une coordonnée
linéaire 1D, ainsi que l'inverse ; et d'accéder directement à une case (x,y) du labyrinthe en
retournant une référence (voir cours).
• Les dimensions du labyrinthe seront passées sur la ligne de commande (ex. ./laby 12 8).
• Chaque cellule sera un entier non signé (unsigned) et on codera les murs de la cellule par les
4 bits de poids faibles, respectivement les murs Nord, Est, Sud, Ouest par les bits 0 à 3.
◦ Un bit à 1 indique qu'un mur est présent.
◦ On accédera à chaque bit par les opérateurs "binaires" (bitwise) "et" (&), "ou" ( | ), "ou
exclusif" (^), et complément binaire (~) (voir cours).
◦ Les cellules seront initialisées avec 4 murs (masque binaire 0xF, décimal 15), et
l'algorithme de création du labyrinthe supprimera les murs qui doivent l'être.
• Pour la création du labyrinthe, on utilisera l'algorithme de Prim, qui calcule un arbre
couvrant de poids minimal ou maximal dans un graphe.
◦ On considére que chaque cellule du labyrinthe est un sommet d'un graphe, connecté à
ses 4 voisins immédiats (Nord, Est, ...), avec l'exception des bords, évidemment.
◦ On va associer un poids aléatoire à chaque connection : l'algo de Prim nous construira
donc un arbre aléatoire qui constitue un labyrinthe.
◦ Pour la détermination des poids aléatoire, on construira un tableau "P" d'entiers aléatoire
de même dimension que le labyrinthe. Une connexion entre les cellules (i, j) du
labyrinthe aura pour poids P[i] + P[j].
Notes étape 1 :
•
•
•
utilisez le type unsigned quand vous le pouvez (les indices de tableau par exemple)
utilisez les vecteurs de C++ au lieu des tableaux quand c'est plus pratique.
Vous pourrez utilisez le code "affichage labyrinthe" sur ma page web pour réaliser un
affichage sommaire des chemins (et non des murs) du labyrinthe, à des fins de mise au
point.
Étape 2 : affichage plan et 3D
Comme il n'y a pas de contraintes fortes de temps d'affichage, et que le cours n'est pas dédié au
graphisme, nous utiliserons une solution graphique simple fournie par Tcl/Tk sous la forme du
"shell" graphique de nom wish (voir cours). Le démarrage de la fenêtre graphique de wish, sa
connexion via des pointeurs de fichiers et la création des objets graphiques de base (widgets) est
donnée sur ma page web : connection wish. Pour la partie 3D, il est évident qu'un jeu réel utiliserait
un moteur graphique déjà développé, l'objectif ici est plus modeste : on se contentera d'esquisser les
problèmes algorithmiques liés notamment à "l'algorithme du peintre".
La vue en mode plan (2D) consiste à dessiner le labyrinthe vu par dessus ("vu d'avion"). Il
suffit donc de tracer les murs entourant chaque cellule. On recommande de créer une
constante ou variable qui stocke un facteur d'échelle, et de multiplier toutes les coordonnées
par ce facteur par la suite. Une cellule de coordonnées x,y aura par exemple son coin en haut
à gauche en coordonnées x * echelle, y *echelle. On procède de même pour obtenir le coin
sup./gauche de toutes les cellules et on en déduit les coordonnées des lignes à tracer pour
représenter les murs.
• Pour la vue en 3D, on utilisera "l'algorithme du peintre", qui consiste à dessiner en premier
les cellules les plus éloignées. Ainsi on masquera automatiquement les parties cachées par
les murs des cellules plus proches.
• L'avatar ne pourra être orienté que le long des axes X et Y du labyrinthe (les 4 directions
N,E,S,O), avec un angle de vision de 45° de part et d'autre. On peut esquisser l'ordre de
dessin des cellules vue à une profondeur 2 par le schéma suivant (l'avatar est la flêche) :
1
6 3
­> 8 5
7 4 2 • On utilise une perspective à un point de fuite, au milieu de la scène représentée : un mur
face à l'observateur, et situé devant lui, sera dessiné comme un carré, l'oeil pointant au centre
du carré.
◦ là encore on peut considérer un carré de côté unitaire (longueur 1) que l'on multiplie par
un facteur d'échelle.
◦ dans ce type,de vue en perspective, seule la distance en profondeur est prise en compte,
pas la distance sur les côtés. Pour le calcul de la dimension du carré, donc des
coordonnées de ses sommets, en fonction de la profondeur on utilisera le théorème de
Thalès.
◦ les murs vus de côté seront représentés comme des trapèzes : il est plus simple de
calculer les coordonnées de leur sommets en considérant que ces mêmes sommets sont
ceux de murs vus de face aux profondeur N et N+1, selon le calcul fait précédemment.
•
Notes étape 2 :
•
•
faites des schémas sur une feuille quadrillée !
Voir page suivante pour un exemple de réalisation (plan en haut (l'avatar est la flèche tout en
haut vers la droite du labyrinthe), vue 3D en bas) :
Étape 3 : étape intermédiaire
•
Placer une boucle "d'attente active" dans l'application, qui va lire avec des fscanf(...) les
sorties de Wish (commandes 'puts' associés aux boutons) pour savoir sur quels boutons
l'utilisateur à cliqué (valeurs 2, 4 et 8 dans le code exemple 'connexion wish', que vous
pouvez changer évidemment). Note : fscanf(...) retourne aussi le nombre de variables lues, et
-1 si la lecture n'est pas possible, par exemple lorsque l'on quitte Wish en cliquant sur le
bouton 'quit'.
•
En fonction de ce qui est lu dans la boucle d'attente, on déplacera l'avatar sur le plan du
labyrinthe lorsque le mouvement est possible (on ne peut pas traverser les murs) et on mettra
à jour la vue 3D.
•
Deux exemples de code possibles pour réaliser les questions précédentes sont disponibles
(voir le code de jeu.cc dans "Mini-Projet étape intermédiaire" sur ma page web), afin que
tout le monde puisse aborder les questions suivantes. Vous pouvez évidemment garder votre
code s'il fonctionne.
Étape 4 : parcours du labyrinthe
•
En utilisant les algorithmes de parcours de graphe vus en cours, trouver la séquence de
mouvement minimale qui permet depuis la position courante de l'avatar de retourner à
l'entrée du labyrinthe.
•
Créer une fonction fuite qui déplace l'avatar à une distance aléatoire (non nulle) depuis son
point courant sur le chemin le plus court menant à l'entrée.
•
Créer les structures de données permettant de modéliser un groupe de 4 "aventuriers", qui
sont des personnages munis des caractéristiques suivantes :
◦ un nom (une chaîne de caractères) ;
◦ 3 caractéristiques de combat: initiative, attaque, défense, qui sont des entiers positifs,
qui seront chacune initialisée à (1 + jet d'un dé à 6 faces) ;
◦ un nombre de "points de vie" : pv qui peut devenir négatif, initialisé à (10 + 2 dés à 6
faces) ;
◦ au groupe est associé un unique sac contenant le nombre de pièces d'or récupérées par
les aventuriers, initialisé à 0 ;
•
Créer une liste de 8 "monstres", où chaque monstre est un personnage particulier :
◦ il est associé à une cellule aléatoire fixée du labyrinthe (une cellule ne peut contenir
qu'un monstre, un monstre ne peut pas être sur l'entrée du labyrinthe) ;
◦ ses 3 caractéristiques de combat initiative, attaque, défense, sont initialisées à (2 dés à
4 faces) ;
◦ ses pv sont initialisées à (8 + 3 dés à 6 faces) ;
◦ il garde un trésor initialisé à (5 + 3 dés à 6 faces) pièces d'or ;
◦ NOTE : la liste peut être implantée comme un vecteur (pas de bonus) ou comme une
liste chainée (qui sera récompensée par un bonus sur la note du projet).
•
Lorsque l'avatar, représentant le groupe d'aventurier, arrive à 3 cases ou moins d'un monstre
visible (non séparé par un mur) la case du monstre est affichée dans une couleur différente
sur le plan et le reste tant qu'il est vivant (on sait où il est). Lorsque le groupe arrive sur la
case du monstre, les caractéristiques de ce dernier sont affichées. Le groupe peut alors soit
fuir (on utilise la fonction créée plus haut pour l'éloigner du monstre) soit combattre (voir
section suivante).
Étape 5 : combats
•
Lorsque le groupe combat un monstre, chacun (personnages/monstre) tente une attaque dans
l'ordre décroissant des (initiative + un dé à 6 faces). En cas d'égalité avec le monstre, il
frappe en premier. En cas d'égalité entre joueur, l'ordre n'a pas d'importance. Les joueurs
attaquent évidemment le monstre, le monstre attaque un joueur pris au hasard à chaque
attaque.
•
Attaque : on calcule (1 dé à 6 face + attaque de l'attaquant – défense du défenseur). Si le
résultat est nul ou négatif, rien ne se passe, si le résultat est positif sa valeur est retirée des
pv du défenseur. Si les pv du défenseur tombent à 0 ou moins il est considéré comme mort
et ne peut pas participer dans la suite des attaques.
•
Si le monstre meurt, les personnages récupèrent son trésor qui vient grossir leur sac. Le
monstre est retiré de la liste.
•
Si tous les monstres sont tués, la partie est gagnée, si tous les aventuriers sont morts, elle est
perdue.
__ FIN __

Documents pareils