Pour réaliser ces dessins, on considère qu'un labyrinthe est formé d'un ensemble de cellules carrées séparées par des murs qui peuvent être ouverts ou fermés. C'est-à-dire que l'on considère que deux cellules voisines sont toujours séparées par un mur, mais que le passage de l'une à l'autre n'est possible que si ce mur est ouvert (au lieu de murs ouverts ou fermés on pourrait aussi parler de portes fermées à clé ou non).
Labyrinthe
qui posséderont des murs pouvant être ouverts ou
fermés ainsi qu'une méthode permettant de les dessiner. Le choix des
murs à ouvrir pour obtenir un vrai labyrinthe fait l'objet des
étapes 2 et 3./home/cremet/projet
. Pour les copier sur votre
compte, exécutez la commande suivante :cp -r /home/cremet/projet .Cela va créer dans votre répertoire courant un sous-répertoire nommé
projet
contenant tous les fichiers nécessaires pour le projet.
Pour compiler le projet, déplacez-vous dans ce répertoire
(en tapant cd projet
), puis exécutez la commande
gmake
.Vecteur
, Segment
et Dessin
(voir annexe). À la fin de cette étape on n'aura
cependant pas encore décidé quels murs seront ouverts ou fermés, ce
qui est le thème des étapes 2 et 3. Par conséquent, ils seront par
convention tous uniformément fermés (graphiquement on obtiendra un
simple quadrillage).Mur
représentant un mur du labyrinthe, et une classe
Labyrinthe
représentant le labyrinthe proprement dit.Mur
possèderait les attributs suivants :
cellule_1
et un champ cellule_2
, de type
entier, contenant les indices des cellules séparées par le mur,
ouvert
, de type booléen, qui est vrai si le mur
est effectivement ouvert,
segment
de type Segment
, qui représente
le dessin du mur, et enfin,
dessiner
qui prend en argument un objet de
type Dessin
et qui, si le mur est fermé, ajoute son segment
sur le dessin.
Labyrinthe
possèderait alors les attributs suivants :
largeur
et un champ hauteur
pour les
dimensions du labyrinthe,
nbre_murs
égal au nombre de murs du
labyrinthe,
murs
représentant la liste des murs du
labyrinthe, celle-ci pouvant être implantée par un tableau, et
enfin,
dessiner
qui prend en argument un objet de
type Dessin
, puis qui y dessine l'enceinte du labyrinthe,
ainsi que chaque mur fermé.
Labyrinthe
. Etant données une largeur
et une hauteur données en argument, il doit en effet construire tous
les murs en donnant comme argument au constructeur de la classe
Mur
les informations nécessaires. C'est-à-dire les indices des
cellules séparées par le mur et les coordonneés des deux extrémités du
mur sur le dessin.X
et Y
qui vont balayer ligne par ligne, de gauche à droite et de bas en
haut, le labyrinthe. On procède de façon analogue pour les murs
horizontaux.Mur
et Labyrinthe
. Prenez soin de
bien séparer l'interface de ces classes (dans un fichier .H
) de
leur implantation (dans un fichier .C
). Ecrivez enfin un petit
programme de test qui imprime dans un fichier un labyrinthe dont tous
les murs sont fermés.Initialement, en (a), les 4 éléments appartiennent tous à des ensembles différents. Chaque élément est donc le représentant de son ensemble. En (b), les ensembles des éléments 1 et 2 sont mis en commun, et l'élément 2 est choisi comme nouveau représentant. En (c), ce sont cette fois les ensembles des éléments 3 et 4 qui sont mis en commun, et 3 est choisi comme nouveau représentant. En (d), les deux ensembles sont mis en commun, et 3 est à nouveau choisi comme représentant. Le contenu des ensembles reste le même en (e), par contre une optimisation d'aplatissement est appliquée afin que l'élément 1 ait le représentant de son ensemble, à savoir 3, comme parent direct. Cela élimine une indirection lors des recherches subséquentes du représentant de l'élément 1.
Noeud
car les éléments liés entre
eux via le lien parent
font penser à un graphe.parent
qui contient
l'adresse du noeud parent, ou NULL
si le noeud est le
représentant de son ensemble. Il contient de plus deux méthodes
publiques : meme_ensemble
permet de tester si le noeud
appartient au même ensemble qu'un autre tandis que connecte
met
en commun l'ensemble du noeud avec l'ensemble d'un autre noeud.parent
. S'il est nul, alors le noeud est son propre
représentant. Sinon, on répète l'opération en recherchant le
représentant du noeud désigné par le champ parent
. Étant
donné que le nombre de noeuds est borné et qu'il n'y a pas de
cycles, on finira forcément par aboutir au représentant du noeud.parent
une sorte d'arbre dont la racine est le noeud qui représente
l'ensemble. Plus un noeud est éloigné de la racine et plus
l'opération de recherche de son représentant est longue, car il faut
parcourir un plus grand nombre de noeuds avant d'arriver à la
racine.parent
pour qu'il contienne l'adresse de leur
représentant. Ainsi, la prochaine fois que l'on recherchera le
représentant de l'un de ces noeuds on l'obtiendra immédiatement
sans devoir parcourir d'autre noeuds. Cette optimisation est
présentée dans la figure 3 lors du passage de (d) à (e).Noeud
, commencez par écrire une
méthode auxiliaire obtient_representant
qui retourne le
représentant de l'élément. Pour cela, appliquez l'algorithme de
recherche ainsi que l'optimisation décrits ci-dessus.meme_ensemble
est immédiate : il suffit de déterminer les
représentants des deux noeuds et de les comparer. La méthode
connecte
n'est pas beaucoup plus compliquée : on commence aussi
par déterminer les représentants des deux noeuds puis, s'ils sont
différents, on fait pointer l'un des deux sur l'autre en modifiant son
champ parent
.Noeud.H
pour voir à quoi
ressemble la classe. Ensuite, écrivez le fichier Noeud.C
dans
son ensemble, en utilisant les algorithmes décrits ci-dessus ; au
besoin, complétez le fichier Noeud.H
.rand
qui ne prend aucun argument et qui retourne un
nombre aléatoire compris entre 0 et le plus grand entier possible.
Pour ramener cette valeur à un intervalle donné, on peut utiliser une
judicieuse combinaison d'addition et de calcul de reste de division.rand
, pensez à inclure le fichier
stdlib.h
.main.C
en écrivant le
corps de la fonction ouvre_murs
, selon la technique présentée
plus haut.Vecteur
, Segment
et Dessin
. Ces trois
classes sont complètes. A aucun moment vous n'aurez besoin de les
modifier. De plus, on vous demande juste de les utiliser. On ne vous
demande pas d'en comprendre toute l'implantation. Ne passez donc pas
trop de temps sur ces classes maintenant. Sachez simplement qu'elles
existent et revenez-y lorsque vous en aurez besoin. Et, dans tous les
cas, évitez de perdre du temps maintenant ou plus tard en voulant à
tout prix comprendre les moindres détails de leur implantation.Vecteur
(fichier
Vecteur.H
) décrit un vecteur du
plan. Elle est notamment utilisée pour repérer la position des
cellules et le sommet des murs. Sa définition surcharge plusieurs
opérateurs et il est ainsi possible d'additionner et de soustraire
deux vecteurs avec les opérateurs +
et -
ou de
multiplier et diviser un vecteur par un scalaire avec les opérateurs
*
et /
. Cette classe peut donc aussi être utilisée pour
faire un peu de calcul vectoriel.Segment
(fichier
Segment.H
) représente un segment du
plan. Elle est utilisée pour repérer la position des murs du
labyrinthe.Dessin
(fichiers
Dessin.H
et Dessin.C
) permet de
dessiner un ensemble de segments et d'écrire le résultat dans un
fichier. La classe translate et redimensionne toujours l'ensemble des
segments afin que le dessin final soit centré et occupe la place au
maximum. Cela signifie que seule la position relative des segments les
uns par rapport aux autres importe. Leur position absolue dans le plan
ainsi que la taille du dessin n'ont pas d'importance..ps
). Vous pouvez donc directement imprimer le résultat avec
la commande lp
. Toutefois, pour vos tests, il sera infiniment
plus simple, rapide et écologique d'utiliser la commande gv
qui permet d'afficher à l'écran le contenu d'un fichier PostScript.
Pour cela, exécutez la ligne de commande suivante:gv <nom_du_fichier_a_visualiser>
This document was translated from LATEX by HEVEA.