Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice pratique : le ROGUE LIKE - partie 1

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 28 février 2013

Bonjour,

Nous allons commencer une série d'exercices en plusieurs parties dans le but de faire des jeux un peu plus évoluée que les précédents, cet exercice va donc s'étaler sur plusieurs articles. Plus évolués ne veut pas forcément dire plus difficiles, mais plus long à écrire et souvent plus techniques, c'est pourquoi il me semble nécessaire de fragmenter, ce qui vous permettra en outre d'isoler la partie qui vous intéresse.

Voici ce qu'on doit obtenir au final :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Utilisez les trois boutons (L C D) pour générer un Labyrinthe, une Caverne ou un Donjon.

Cette fois nous allons nous attaquer au JDR, autrement dit, le Jeu De Rôle… et plus particulièrement ceux que l'on classe dans la catégorie Rogue-Like (voir : http://fr.wikipedia.org/wiki/Rogue-like). Les Rogues-Likes sont des jeux (souvent des JDR) dont une des caractéristiques du gameplay repose sur le fait que les niveaux et leur contenus sont générés aléatoirement. Si cela permet de s'affranchir de tout un pan de conception inhérent aux jeux de ce type, cela demande aussi de créer des environnements, certes aléatoires, mais aussi cohérents. Mais vous verrez bien plus tard dans cet exercice, qu'on peut tout à fait mixer les genres et passer de la génération aléatoire à une zone prédéfinie, voire enregistrée en cours de progression car le héros devra y revenir,… Bref, je m'emballe, commençons par le début, et posons-nous la question de comment générer aléatoirement les trois environnements les plus rencontrés dans ces jeux ?

Le labyrinthe, la caverne et le donjon.

On va donc s'attaquer aux structures, c'est à dire poser les murs, on verra le remplissage et la déco un peu plus tard.

Des générateurs de donjon vous pouvez en trouver plein sur Internet, en voici deux beaux exemples :

http://www.dizzydragon.net/adventuregenerator/start
http://donjon.bin.sh/

Vous pouvez aussi vous attaquer à des jeux dont le code source à été libéré comme Angband (voir : http://fr.wikipedia.org/wiki/Angband), vous pouvez télécharger la source ici : http://rephial.org/

Il vous suffit de trouver le fichier “generate.c” et de l'éditer, le générateur du jeu s'y trouve sous licence GNU et “Angband”. A vous les joies du dépiautage pour obtenir votre générateur parfait, nous on va s'essayer à des trucs bien plus basiques pour le moment. J'ai donc essayé de trouver des solutions simples, les plus simples possibles à vrai dire, pour aborder le Rogue-Like sans y passer non plus trop de temps.

Les pré-requis

Je vous recommande fortement d'avoir au moins lu les exercices suivants : DEMINEUR, PONG, SNAKE, TAQUIN
Les exercices sont courts mais de nombreuses astuces y sont proposées, je ne les expliquerai pas à chaque nouvel exercice.

Pour ce programme vous devez connaître :

Si vous souhaitez plus de précisions sur ces points, je vous encourage à parcourir le Wiki de Mediabox où vous trouverez de nombreux tutoriaux détaillés.

Le labyrinthe

Tout d'abord le résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Cliquez pour générer une nouvelle map.

Les sources sont disponibles en fin d'exercice

Etude préliminaire

Cette fois je m'épargne le descriptif, allez vous renseigner sur Wikipedia pour savoir ce qu'est un labyrinthe : http://fr.wikipedia.org/wiki/Labyrinthe

La seule chose que nous avons besoin de retenir (outre la définition même d'un labyrinthe) c'est que tout point du labyrinthe peut être atteint depuis n'importe quel autre point. Autrement dit, il n'existe pas de salle ou de couloirs fermés, tous sont ouverts et tous peuvent être atteints. L'objectif pour le joueur est bien évidemment de retrouver la sortie.

Tout ceci est bien joli, mais construire un labyrinthe à la main est un vrai casse tête, alors que paradoxalement le générer aléatoirement est d'une simplicité enfantine. Tout repose sur une méthode simple appelée BackTracking (ou “retour sur trace”), elle est expliquée en détail ici : http://fr.wikipedia.org/wiki/Backtracking

Voyons comment cela se met en place.

Le code

Voyons d'abord tout le code d'un coup.

const C:uint = 48;
const L:uint = 48;
const T:uint = 10;
const tailleGrille:uint = C*L;
 
var i:int;
var j:int;
var X:int;
var Y:int;
var grille:Array;
var mouvements:Array;
var directions:Array;
var entree:Point;
var sortie:Point;
var rendu:Sprite;
var pas:int;
var retour:int;
 
init();
 
function init():void{
	entree = new Point(1,1);
	sortie = new Point(L-2,C-2);
	rendu =  new Sprite();
	stage.addEventListener(MouseEvent.CLICK, genere);
	genere();
}
 
function genere(e:MouseEvent=null):void{
	while(rendu.numChildren>0) rendu.removeChildAt(0);
	grille = [];
	for (i=0;i<C*L;i++) grille.push(1);
	creeLabyrinthe();
	render();
}
 
function creeLabyrinthe():void{
 
	X = 		entree.x;
	Y =		entree.y;
	pas = 		3;
	mouvements = 	[X+Y*C];
	grille[X+Y*C] = 0;
 
	while(mouvements.length){
 
		directions = [];
 
		if (X+pas<L && grille[X+pas+Y*C]) 	directions.push("W");
		if (Y+pas<C && grille[X+(Y+pas)*C]) 	directions.push("N");
		if (X-pas>0 && grille[X-pas+Y*C]) 	directions.push("E");
		if (Y-pas>0 && grille[X+(Y-pas)*C]) 	directions.push("S");
 
		if(directions.length)	{
			switch (directions[int(Math.random()*directions.length)]){
				case "E" :
					for (i=0; i<=pas; i++) grille[X-i+Y*C] = 0;
					X -=  pas;
					break;
				case "W" :
					for (i=0; i<=pas; i++) grille[X+i+Y*C] = 0;
					X +=  pas;
					break;
				case "S" :
					for (i=0; i<=pas; i++) grille[X+(Y-i)*C] = 0;
					Y -=  pas;
					break;
				case "N" :
					for (i=0; i<=pas; i++) grille[X+(Y+i)*C] = 0;
					Y +=  pas;
					break;
			}
			mouvements.push(Y+X*C);
		} else {
			retour = mouvements.pop();
			X = retour/C;
			Y = retour%C;
		}
	}
}
 
function render():void{
	for (i=0; i<tailleGrille; i++){
		renduTuile(int(i/C),int(i%C),grille[i]+1);
	}
	renduTuile(entree.x,entree.y,3);
	renduTuile(sortie.x,sortie.y,4);	
	addChild(rendu);
}
 
function renduTuile(X,Y,F):void{
	var t:Tuiles = new Tuiles();
	t.x = X*T;
	t.y = Y*T;
	t.gotoAndStop(F);
	rendu.addChild(t);
}

Etude du programme

Ce programme n'utilise pratiquement pas les graphismes, nous avons simplement besoin de représenter les deux types de structures que nous allons trouver à savoir le sol et les murs. J'utilise la bibliothèque de Flash pour me simplifier la vie, donc un Clip exporté pour AS contenant deux frames, une qui contient la représentation du sol (gris) et l'autre celle des murs (noir). J'y ajoute deux frames supplémentaires pour l'entrée (vert) et la sortie (rouge).

Allez c'est parti pour l'étude pas à pas :

const C:uint = 48;
const L:uint = 48;
const T:uint = 10;
const tailleGrille:uint = C*L;
 
var i:int;
var j:int;
var X:int;
var Y:int;
 
var grille:Array;
var mouvements:Array;
var directions:Array;
var entree:Point;
var sortie:Point;
var rendu:Sprite;
var pas:int;
var retour:int;

Tient du nouveau par rapport aux exercices précédent, les constantes “const”. Il s'agit en fait d'une simple variable verrouillée, ce qui est antinomique j'en ai conscience, par définition une “variable” peur être modifiée (d'où son nom), alors qu'une constante ne le peut pas (d'où son nom aussi). Tout au long du programme je peux donc faire appel à mes constantes, mais je ne peux pas les modifier.

C et L représentent le nombre de Colonnes et de Lignes de la grille que je vais construire.
T représente la taille d'une tuile.
“i”, “j”, “X” et “Y” sont des variable généralistes utilisées dans les boucles ou au cours du programme.

Le reste est assez explicite, nous y reviendrons en détail au cours de l'exercice.

init();
 
function init():void{
	entree = new Point(1,1);
	sortie = new Point(L-2,C-2);
	rendu =  new Sprite();
	stage.addEventListener(MouseEvent.CLICK, genere);
	genere();
}

Rien de bien compliqué, on initialise le programme avec une entrée, une sortie, un Sprite qui servira pour le rendu graphique de la grille et un petit écouteur qui permet de générer une nouvelle grille au clic, puis on génère la grille.

function genere(e:MouseEvent=null):void{
	while(rendu.numChildren>0) rendu.removeChildAt(0);
	grille = [];
	for (i=0;i<C*L;i++) grille.push(1);
	creeLabyrinthe();
	render();
}

Je commence par vider tout ce que contient le clip de rendu graphique, puis je vide la grille et la rempli avec des 1 (des murs). Puis je crée le labyrinthe et lance le rendu.

function creeLabyrinthe():void{
 
	X = 		entree.x;
	Y =		entree.y;
	pas = 		3;
	mouvements = 	[X+Y*C];
	grille[X+Y*C] = 0;
 
	while(mouvements.length){
 
		directions = [];
 
		if (X+pas<L && grille[X+pas+Y*C]) 	directions.push("W");
		if (Y+pas<C && grille[X+(Y+pas)*C]) 	directions.push("N");
		if (X-pas>0 && grille[X-pas+Y*C]) 	directions.push("E");
		if (Y-pas>0 && grille[X+(Y-pas)*C]) 	directions.push("S");
 
		if(directions.length)	{
			switch (directions[int(Math.random()*directions.length)]){
				case "E" :
					for (i=0; i<=pas; i++) grille[X-i+Y*C] = 0;
					X -=  pas;
					break;
				case "W" :
					for (i=0; i<=pas; i++) grille[X+i+Y*C] = 0;
					X +=  pas;
					break;
				case "S" :
					for (i=0; i<=pas; i++) grille[X+(Y-i)*C] = 0;
					Y -=  pas;
					break;
				case "N" :
					for (i=0; i<=pas; i++) grille[X+(Y+i)*C] = 0;
					Y +=  pas;
					break;
			}
			mouvements.push(Y+X*C);
		} else {
			retour = mouvements.pop();
			X = retour/C;
			Y = retour%C;
		}
	}
}

Ok nous voici au coeur de la chose.

X et Y sont notre point de départ dans la grille, le “pas” représente le nombre de case à franchir à chaque test, les “mouvements” sont les différentes cases enregistrée lors d'un déplacement, la première étant bien entendu le point de départ, qui dans la grille est considéré comme un sol (0).

Pour faire très simple, le principe est le suivant : on part d'un point de départ, on avance de X cases, on teste toutes les directions possibles, si il y en a au moins une on choisi aléatoirement la prochaine direction à suivre parmi les solutions disponibles et on continue avec le prochain pas qu'on fait, sinon on reviens une position en arrière et on recommence les tests, et ainsi de suite jusqu'à ce qu'il n'y ait plus de directions possibles.

Maintenant voyons ce que ça donne au niveau du petit programme que je viens d'écrire.

Tant qu'il y a des mouvements possibles, pour chaque mouvement je vide le tableau où on stocke les directions possibles à partir de la case où on se trouve. Puis je teste les directions à partir de cette case, pour cela j'avance du “pas” dans une direction (Nord, Sud, Est, Ouest) en m'assurant que la case testée ne se trouve pas hors de la grille. Une case est considérée comme disponible si c'est un mur (pour rappel, ce qu'on souhaite faire c'est creuser des galeries) et qu'elle ne se trouve pas hors de la grille. Lorsqu'une direction est validée je l'enregistre dans le tableau.

Tant que le tableau n'est pas vide, je choisi une direction aléatoirement, je transforme toutes les tuiles se trouvant entre la case de départ et la case d'arrivée en sol (0) et je fais avancer mon testeur dans cette direction. Enfin, j'ajoute cette nouvelle position dans le tableau des “mouvements”. Si le tableau des directions est vide c'est que je ne peux plus avancer dans aucune direction, je me retrouve donc dans une impasse, c'est là que j'utilise le BackTracking, je reviens tout simplement à la dernière position connue et la supprime du tableau des “mouvements”, je vais repartir de cette position pour refaire mes tests.

Voilà c'est tout, vous voyez que ce n'est pas la mer à boire ;-)

function render():void{
	for (i=0; i<tailleGrille; i++){
		renduTuile(int(i/C),int(i%C),grille[i]+1);
	}
	renduTuile(entree.x,entree.y,3);
	renduTuile(sortie.x,sortie.y,4);		
	addChild(rendu);
}

La fonction “render” sert simplement à tracer la grille définitive obtenue après la construction du labyrinthe, elle est simple à comprendre, pour chaque case de la grille on crée une tuile correspondante, et on ajoute deux tuiles supplémentaires pour l'entrée et la sortie.

function renduTuile(X,Y,F):void{
	var t:Tuiles = new Tuiles();
	t.x = X*T;
	t.y = Y*T;
	t.gotoAndStop(F);
	rendu.addChild(t);
}

Ce petit bout de code crée simplement une nouvelle tuile à chaque fois qu'on le lui demande, il la place, affiche la bonne image et l'ajoute au rendu.

C'est terminé pour le Labyrinthe, c'était le plus facile de tous, passons à présent à la caverne.

La Caverne

Tout d'abord le résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Cliquez pour générer une nouvelle map.

Les sources sont disponibles en fin d'exercice

Etude préliminaire

Non, je ne vais pas vous expliquer ce qu'est une caverne ;-)
Ce qui nous intéresse ici c'est de savoir comment générer ce type de terrain pour qu'il ne ressemble pas à un assemblage de trous irréguliers percés aléatoirement dans une grille.

Pour cela je vais utiliser la méthode dite des Automates Cellulaires : http://fr.wikipedia.org/wiki/Automate_cellulaire

Le but est de générer une grille dont le contenu est aléatoire, puis effectuer plusieurs passage sur cette grille, à chaque passage les cellules qui la composent vont regarder leur environnement et modifier leur état en conséquence. Plus les passage sont nombreux et vos cellules fines et nombreuses, plus votre caverne aura une définition précise.

Voyons comment cela se met en place.

Le code

Voyons d'abord tout le code d'un coup.

var grille:Array;
var grille2:Array; 
 
var C:int = 	48;
var L:int = 	48;
var T:int = 	10;
var i:int;
var j:int;
var X:int;
var Y:int;
var r:int;
 
init();
 
function init(e:MouseEvent=null):void {
	while(numChildren>0) removeChildAt(0);
	grille  = [];
	grille2 = [];
	for(i=0; i<L*C; i++){
		grille.push(choisirTuile());
		grille2.push(1);
		if(bords(int(i%C),int(i/C))) grille[i]=1;
	}
	r = 10;
 	while(r--) generation();
 	render();
	stage.addEventListener(MouseEvent.CLICK, init);
}
 
function generation():void{
 
	var x:int; 
	var y:int; 
	var A:int;
	var B:int;
 
	for(i=0; i<L*C; i++){
		X = i%C;
		Y = i/C;
		if(!bords(X,Y)) {
			A = 0;
			B = 0;
			for(x=-1; x<=1; x++){
				for(y=-1; y<=1; y++){
					if(grille[x+X+(y+Y)*C] != 0) A++; 
				}
			}
			for(x=X-2; x<=X+2; x++){
				for(y=Y-2; y<=Y+2; y++){
					if(Math.abs(x-X)==2 && Math.abs(y-Y)==2) continue;	 
					if(grille[x+y*C] != 0)			 B++;
				}
			}
			if(A>=5 || B<=2) grille2[i]=1 else grille2[i]=0;
		}
	}
 
	// merge les grilles
 	for(i=0; i<L*C; i++){
		if(!bords(i%C,int(i/C))) grille[i] = grille2[i];
	}
} 
 
function bords(X:int,Y:int):Boolean{	if(X && Y && X!=C-1 && Y!=C-1) return false else return true};
function choisirTuile():int{		if(Math.random() < 0.4)	return 1 else return 0};	
function render():void{			for(i=0; i<C*L; i++) renduTuile(i,grille[i])};
 
function renduTuile(i:int,F:int):void{
	var t:Tuiles = new Tuiles();
	t.x = i%C*T;
	t.y = int(i/C)*T;
	t.gotoAndStop(F+1);
	addChild(t);
}

Etude du programme

Pour les graphismes c'est pareil que le labyrinthe, en dehors du fait que là je n'ai pas besoin d'entrée et de sortie, il suffira de tirer leurs emplacement parmi les cases libres à la fin si on veut vraiment en afficher, c'est un détail.

var grille:Array;
var grille2:Array; 
 
var C:int = 	48;
var L:int = 	48;
var T:int = 	10;
var i:int;
var j:int;
var X:int;
var Y:int;
var r:int;
 
init();

Là aussi c'est pareil que pour le labyrinthe.

function init(e:MouseEvent=null):void {
	while(numChildren>0) removeChildAt(0);
	grille  = [];
	grille2 = [];
	for(i=0; i<L*C; i++){
		grille.push(choisirTuile());
		grille2.push(1);
		if(bords(int(i%C),int(i/C))) grille[i]=1;
	}
	r = 10;
 	while(r--) generation();
 	render();
	stage.addEventListener(MouseEvent.CLICK, init);
}

J'utilise deux grilles, je vais remplir la seconde avec des murs et la première avec soit des murs soit des sols, au petit bonheur la chance, pour la première grille je vais également placer des murs tout autour de la grille histoire de la fermer.

“r” représente le nombre de générations que je souhaite pour affiner la grille, essayez avec des valeurs plus petites, par exemple 1, 2 ou 3 pour voir comment la grille s'affine à chaque génération.

function generation():void{
 
	var x:int; 
	var y:int; 
	var A:int;
	var B:int;
 
	for(i=0; i<L*C; i++){
		X = i%C;
		Y = i/C;
		if(!bords(X,Y)) {
			A = 0;
			B = 0;
			for(x=-1; x<=1; x++){
				for(y=-1; y<=1; y++){
					if(grille[x+X+(y+Y)*C] != 0) A++; 
				}
			}
			for(x=X-2; x<=X+2; x++){
				for(y=Y-2; y<=Y+2; y++){
					if(Math.abs(x-X)==2 && Math.abs(y-Y)==2)	continue;
					if(grille[x+y*C] != 0)				B++;
				}
			}
			if(A>=5 || B<=2) grille2[i]=1 else grille2[i]=0;
		}
	}
 
	// merge les grilles
 	for(i=0; i<L*C; i++){
		if(!bords(i%C,int(i/C))) grille[i] = grille2[i];
	}
}

A chaque génération, je parcours la première grille, je vérifie que la cellule que je teste n'est pas un bord, si ce n'est pas le cas je vais chercher toutes les voisines directes à cette cellule (les 8 cases qui sont autour et la cellule elle même) et je compte chaque mur que je trouve. Je fais immédiatement derrière une autre vérification des voisines plus éloignées avec cette fois avec un pas de deux cases. Si je tombe sur un bord je ne le compte pas, sinon si je tombe sur un mur je le compte.

Au sortir de ces deux boucle il me reste donc deux valeurs représentant les voisines proches (A) et les voisines plus éloignées (B). Si il y a plus de 4 mur dans le voisinage direct de la cellule ou moins de trois murs dans le voisinage lointain de la cellule alors sa représentation dans la deuxième grille se transforme en un mur, sinon elle se transforme en un sol.

Enfin, je modifie la première grille à l'aide des résultats de la seconde pour y ajouter les nouveaux états des cellules modifiées.

Et c'est tout ;-)

Plus vous allez avoir de générations, et plus vos cavernes auront un tracé précis.

function bords(X:int,Y:int):Boolean{	if(X && Y && X!=C-1 && Y!=C-1) return false else return true};
function choisirTuile():int{		if(Math.random() < 0.4) return 1 else return 0};	
function render():void{			for(i=0; i<C*L; i++) renduTuile(i,grille[i])};	
 
// rendu des tuiles
function renduTuile(i:int,F:int):void{
	var t:Tuiles = new Tuiles();
	t.x = i%C*T;
	t.y = int(i/C)*T;
	t.gotoAndStop(F+1);
	addChild(t);
}

Ces quatres dernières petites fonction sont assez simples, “bords” permet de trouver les bords de la grille, “choisir” permet de choisir aléatoirement un sol ou un mur et “render” lance le rendu de la grille, quand à “renduTuile” on l'a déjà vu avec le labyrinthe.

C'est terminé pour les cavernes, cependant vous allez remarquer que parfois il peut y avoir des endroits isolés, inaccessibles directement. Pour palier à ce problème certains utilisent un algorithme de remplissage (une technique que nous aborderons lors de la partie 2 de cet exercice) qui consiste à prendre un point de départ et de propager l'information aux tuiles voisines (ce sont des automates cellulaires), tant qu'on ne rencontre pas un mur. Avec cette méthode votre caverne va se remplir(comme si on l'inondait), une fois la propagation terminée il suffit de parcourir de nouveau toute la grille, toutes les tuiles n'étant pas du “liquide” ou des murs sont alors forcément des salles fermée où le “liquide” n'a pas pu se propager. Libre à vous à partir de là de choisir d'ouvrir ces salles (tracer un couloir d'une des tuiles de la salle fermée vers une tuile “liquide”) ou de les fermer totalement en les remplissant de murs. Il est important de ne pas laisser de salle fermée lorsque vous allez générer des objets dans votre caverne, sinon ces objets risquent de ne pas pouvoir être atteints.

Allez passons au plus compliqué, le donjon.

Le Donjon

Tout d'abord le résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Cliquez pour générer une nouvelle map.

Les sources sont disponibles en fin d'exercice

Etude préliminaire

La définition exacte d'un donjon ne va pas nous servir ici, considérons donc qu'il s'agit simplement d'un enchaînement de pièces et de couloirs que le joueur peut parcourir. Cela tombe bien, car nous savons déjà comment générer des couloirs, il suffit de tracer un labyrinthe. Reste à savoir comment tracer des pièces, les numéroter et y ajouter des portes.

Une des techniques pour créer un donjon est de générer un certain nombre de formes de salles, de les placer sur une grille de manière cohérente, de faire en sorte que les portes soient accessibles, puis de tracer des couloirs entre les salles. Je vais faire radicalement l'inverse, mes salles seront toutes rectangulaires mais de taille variables, je vais tracer mes couloirs avant de poser les pièces et je ne placerai mes portes qu'à la fin, cela sera certes moins diversifié, mais beaucoup plus simple.

Voyons comment cela se met en place.

Le code

Voyons d'abord tout le code d'un coup.

 
// déclarations
const C:uint = 48;
const L:uint = 48;
const T:uint = 10;
const tailleGrille:uint = C*L;
 
var i:int;
var j:int;
var A:int;
var B:int;
var X:int;
var Y:int;
var id:int;
 
var cell:Array;
var grille:Array;
var cellules:Array;
var mouvements:Array;
var grilleDonjon:Array;
var grilleObjets:Array;
var directions:Array;
var entree:Point;
var sortie:Point;
var rendu:Sprite;
var numCell:int;
var pas:int;
var retour:int;
 
init();
 
// initialisation du programme
function init():void{
	entree = 		new Point(1,1);
	sortie = 		new Point(L-2,C-1);
	rendu = 		new Sprite();
	addChild(rendu);
	stage.addEventListener(MouseEvent.CLICK, genere);
	genere();
}
 
// génération du donjon
function genere(e:MouseEvent=null):void{
	while(rendu.numChildren>0) rendu.removeChildAt(0);
	grille = 		[];
	grilleDonjon = 		[];
	grilleObjets = 		[];
	cellules = 		[];
	for (i=0;i<C*L;i++) {
		grille.push(1);
		grilleDonjon.push(0);
		grilleObjets.push(0);
	}
	creeLabyrinthe();
	creeDonjon();
	render();
}
 
// création du labyrinthe
function creeLabyrinthe():void{
 
	X = 		entree.x;
	Y =		entree.y;
	pas = 		3;
	mouvements = 	[X+Y*C];
	grille[X+Y*C] = 0;
 
	while(mouvements.length){
		directions = [];
		if (X+pas<L && grille[X+pas+Y*C]) 	directions.push("W");
		if (Y+pas<C && grille[X+(Y+pas)*C])	directions.push("N");
		if (X-pas>0 && grille[X-pas+Y*C]) 	directions.push("E");
		if (Y-pas>0 && grille[X+(Y-pas)*C]) 	directions.push("S");
		if(directions.length)	{
			switch (directions[int(Math.random()*directions.length)]){
				case "E" :
					for (i=0; i<=pas; i++) grille[X-i+Y*C] = 0;
					X -=  pas;
					break;
				case "W" :
					for (i=0; i<=pas; i++) grille[X+i+Y*C] = 0;
					X +=  pas;
					break;
				case "S" :
					for (i=0; i<=pas; i++) grille[X+(Y-i)*C] = 0;
					Y -=  pas;
					break;
				case "N" :
					for (i=0; i<=pas; i++) grille[X+(Y+i)*C] = 0;
					Y +=  pas;
					break;
			}
			mouvements.push(Y+X*C);
		} else {
			retour = mouvements.pop();
			X = retour/C;
			Y = retour%C;
		}
	}
}
 
// création du donjon
function creeDonjon():void{
 
	numCell = 		29;
	for(i=0; i<36; i++)	cellules.push([]);
 
	// tire aléatoirement les salles
	for each (cell in cellules){
		numCell++;
		for (i=0; i<64; i++) cell.push(1);
		id = 	cellules.indexOf(cell);
		X = 	Math.random()*8;
		Y = 	Math.random()*8;
		for (i=0; i<X; i++) {
			for (j=0; j<Y; j++) {
				cell[j+i*8] = numCell;
			}
		}
		for (i=0; i<cell.length; i++) {
			Y = i%8+(id%6)*8;
			X = int(i/8)+int(id/6)*8;
			if(X!=0 && Y!=0 && X<C-1 && Y<C-1) grilleDonjon[X+Y*C] = cell[i];
		}
	}
 
	// corrige la grille principale
	for (i=0; i<tailleGrille; i++){
		if(grilleDonjon[i]>1) grille[i] = 2; 
	}
 
	var t:Array;
 
	// identifie couloirs et pièces
	for (i=0; i<tailleGrille; i++){
 
		t = [];	
 
		if(grille[i]!=1){
 
			t = [1,1,1,1,0,1,1,1,1];
			if (grille[i-1]!=1) 	t[3] = 0;
			if (grille[i+1]!=1) 	t[5] = 0;
			if (grille[i-C]!=1) 	t[1] = 0;
			if (grille[i+C]!=1) 	t[7] = 0;
			if (grille[i-C-1]!=1) 	t[0] = 0;
			if (grille[i-C+1]!=1)	t[2] = 0;
			if (grille[i+C-1]!=1) 	t[6] = 0;
			if (grille[i+C+1]!=1) 	t[8] = 0;
 
			var libres:int = 		4-(t[3]+t[5]+t[1]+t[7]);
			var diagos:int = 		t[0]+t[2]+t[6]+t[8];
			var HG:int = 			t[0]+t[1]+t[3];
			var HD:int = 			t[1]+t[2]+t[5];
			var BG:int = 			t[3]+t[6]+t[7];
			var BD:int = 			t[5]+t[7]+t[8];
			var angle:Boolean = 	HG==0 || HD==0 || BG==0 || BD==0;
 
			if (libres==0) 			grille[i] = 1;
			if (libres==1) 			grille[i] = 6;
			if (libres==2) {
				if (t[3]+t[5]==2) 	grille[i] = 0;
				if (t[1]+t[7]==2) 	grille[i] = 0;
				if (diagos > 1) 	grille[i] = 0;
				if (angle) 		grille[i] = 2;
			}	
			if (libres==3) {
				if (diagos > 1) 	grille[i] = 0;
				if (angle) 		grille[i] = 2;
			}	
			if (libres==4) {
				if (diagos == 4) 	grille[i] = 0;
				else 			grille[i] = 2;
			}
		}
	}
 
	// corrige les pieces du donjon et pose les portes
	for (i=0; i<tailleGrille; i++){
 
		var g:int = grille[i-1];
		var d:int = grille[i+1];
		var h:int = grille[i-C];
		var b:int = grille[i+C];
 
		if(grilleDonjon[i]>10){
			if (g==2) grilleDonjon[i-1]=grilleDonjon[i];
			if (d==2) grilleDonjon[i+1]=grilleDonjon[i];
			if (h==2) grilleDonjon[i-C]=grilleDonjon[i];
			if (b==2) grilleDonjon[i+C]=grilleDonjon[i];
		}
		if(grille[i]==0){
			if (g==2) grille[i]=5;
			if (d==2) grille[i]=5;
			if (h==2) grille[i]=5;
			if (b==2) grille[i]=5;
		}
	}
 
	// corrige la grille principale	
	for (i=0; i<tailleGrille; i++){
		if(grille[i]==2) grille[i]=grilleDonjon[i];				
	}
 
	// pose les portes
	for (i=0; i<tailleGrille; i++){
		if(grille[i]==5){
			if (grille[i-1]>29) grilleObjets[i] = grille[i-1]-28;
			if (grille[i+1]>29) grilleObjets[i] = grille[i+1]-28;
			if (grille[i-C]>29) grilleObjets[i] = grille[i-C]-28;
			if (grille[i+C]>29) grilleObjets[i] = grille[i+C]-28;
		}
	}
 
	grille[sortie.y+sortie.x*L]=6;
}
 
// rendu graphique
function render():void{
	for (i=0; i<tailleGrille; i++){
		var f:int = 		grille[i]+1
		var h:int = 		grilleObjets[i];
		var g:String = 		"";
		X = 			int(i/C);
		Y = 			int(i%C);
		if (f>29)		g = (f-29).toString(), f=3;
		if (f==6) 		g = h.toString();
		renduTuile(X,Y,f,g);
		if(i==C+1) 		renduTuile(X,Y,5,"");
		if(i==C-1+(L-2)*C) 	renduTuile(X,Y,4,"");
	}
}
 
// rendu graphique des tuiles
function renduTuile(X,Y,F,I):void{
	var t:Tuiles = new Tuiles();
	t.x = X*T;
	t.y = Y*T;
	t.gotoAndStop(F);
	t.num.text = I;
	rendu.addChild(t);
}

Ca fait mal et c'est môche, mais rassurez vous, on a déjà vu une bonne partie de ce code avec le labyrinthe…

Etude du programme

Comme pour le labyrinthe, j'utilise un clip pour la représentation graphique de mes tuiles, j'y ajoute cependant un champ texte dynamique pour pouvoir inscrire le numéro des salles et des portes et trois couleurs supplémentaires, une pour les salles (blanc), une pour les portes (rose) et une pour les culs de sac (gris foncé). Permettez-moi de vous passer la déclaration des variables, constantes et tableaux, je pense que leurs noms sont assez explicites, de plus nous en avons déjà vu un bon paquet avec le labyrinthe.

La fonction “init” est également la même, il n'est pas utile de revenir dessus.

function genere(e:MouseEvent=null):void{
	while(rendu.numChildren>0) rendu.removeChildAt(0);
	grille = 		[];
	grilleDonjon = 		[];
	grilleObjets = 		[];
	cellules = 		[];
	for (i=0;i<C*L;i++) {
		grille.push(1);
		grilleDonjon.push(0);
		grilleObjets.push(0);
	}
	creeLabyrinthe();
	creeDonjon();
	render();
}

Cette fois j'ai une “grille” (elle sert au labyrinthe mais c'est aussi la grille principale, le résultat final), une “grilleDonjon” qui ne servira que pour créer le donjon, puis on l'oubliera dans un coin, une “grilleObjets” qui servira à stocker des objets, et un tableau de “cellules” dont je vous parlerai juste après. Comme vous pouvez le constater, la grille principale est remplie de murs, les deux autres sont remplies de sols. Je crée d'abord le labyrinthe (on à déjà vu ça), puis le donjon, et enfin je trace le rendu.

On s'épargne la partie labyrinthe et on attaque directement la partie donjon.

function creeDonjon():void{
 
	numCell = 		29;
	for(i=0; i<36; i++)	cellules.push([]);
 
	for each (cell in cellules){
		numCell++;
		for (i=0; i<64; i++) cell.push(1);
		id = 	cellules.indexOf(cell);
		X = 	Math.random()*8;
		Y = 	Math.random()*8;
		for (i=0; i<X; i++) {
			for (j=0; j<Y; j++) {
				cell[j+i*8] = numCell;
			}
		}
		for (i=0; i<cell.length; i++) {
			Y = i%8+(id%6)*8;
			X = int(i/8)+int(id/6)*8;
			if(X!=0 && Y!=0 && X<C-1 && Y<C-1) grilleDonjon[X+Y*C] = cell[i];
		}
	}
 
	for (i=0; i<tailleGrille; i++){
		if(grilleDonjon[i]>1) grille[i] = 2; 
	}
 
	var t:Array;
 
	for (i=0; i<tailleGrille; i++){
 
		t = [];	
 
		if(grille[i]!=1){
 
			t = [1,1,1,1,0,1,1,1,1];
			if (grille[i-1]!=1) 		t[3] = 0;
			if (grille[i+1]!=1) 		t[5] = 0;
			if (grille[i-C]!=1) 		t[1] = 0;
			if (grille[i+C]!=1) 		t[7] = 0;
			if (grille[i-C-1]!=1) 		t[0] = 0;
			if (grille[i-C+1]!=1)		t[2] = 0;
			if (grille[i+C-1]!=1) 		t[6] = 0;
			if (grille[i+C+1]!=1) 		t[8] = 0;
 
			var libres:int = 		4-(t[3]+t[5]+t[1]+t[7]);
			var diagos:int = 		t[0]+t[2]+t[6]+t[8];
			var HG:int = 			t[0]+t[1]+t[3];
			var HD:int = 			t[1]+t[2]+t[5];
			var BG:int = 			t[3]+t[6]+t[7];
			var BD:int = 			t[5]+t[7]+t[8];
			var angle:Boolean = 	HG==0 || HD==0 || BG==0 || BD==0;
 
			if (libres==0) 			grille[i] = 1;
			if (libres==1) 			grille[i] = 6;
			if (libres==2) {
				if (t[3]+t[5]==2) 	grille[i] = 0;
				if (t[1]+t[7]==2) 	grille[i] = 0;
				if (diagos > 1) 	grille[i] = 0;
				if (angle) 		grille[i] = 2;
			}	
			if (libres==3) {
				if (diagos > 1) 	grille[i] = 0;
				if (angle) 		grille[i] = 2;
			}	
			if (libres==4) {
				if (diagos == 4) 	grille[i] = 0;
				else 			grille[i] = 2;
			}
		}
	}
 
	for (i=0; i<tailleGrille; i++){
 
		var g:int = grille[i-1];
		var d:int = grille[i+1];
		var h:int = grille[i-C];
		var b:int = grille[i+C];
 
		if(grilleDonjon[i]>10){
			if (g==2) grilleDonjon[i-1]=grilleDonjon[i];
			if (d==2) grilleDonjon[i+1]=grilleDonjon[i];
			if (h==2) grilleDonjon[i-C]=grilleDonjon[i];
			if (b==2) grilleDonjon[i+C]=grilleDonjon[i];
		}
		if(grille[i]==0){
			if (g==2) grille[i]=5;
			if (d==2) grille[i]=5;
			if (h==2) grille[i]=5;
			if (b==2) grille[i]=5;
		}
	}
 
	for (i=0; i<tailleGrille; i++){
		if(grille[i]==2) grille[i]=grilleDonjon[i];				
	}
 
	for (i=0; i<tailleGrille; i++){
		if(grille[i]==7){
			if (grille[i-1]>29) grilleObjets[i] = grille[i-1]-28;
			if (grille[i+1]>29) grilleObjets[i] = grille[i+1]-28;
			if (grille[i-C]>29) grilleObjets[i] = grille[i-C]-28;
			if (grille[i+C]>29) grilleObjets[i] = grille[i+C]-28;
		}
	}
 
	grille[sortie.y+sortie.x*L]=6;
}

Ok, là c'est un peu lourd mais j'ai volontairement étiré le code pour le rendre moins abstrait, notez qu'on pourrait remplacer pas mal de choses par des boucles (voir le générateur de cavernes) mais cela rendrait l'ensemble moins lisible.

On va s'étudier ça pas à pas si vous voulez bien.

numCell = 		29;
for(i=0; i<36; i++)	cellules.push([]);

Je commence par donner un numéro de départ “numCell”, il me permet d'identifier chaque salle par un numéro unique, j'ai choisi arbitrairement le 30 pour commencer à numéroter mes salles, bien sur à vous d'adapter selon vos besoins.

Puis je crée 36 mini tableaux (que je nommerai plus tard “cell”) dans mon tableau de cellules, ce qui veut dire que ma grille va être découpée en 36 sous tableaux de taille identique.

for each (cell in cellules){
	numCell++;
	for (i=0; i<64; i++) cell.push(1);
	id = 	cellules.indexOf(cell);
	X = 	Math.random()*8;
	Y = 	Math.random()*8;
	for (i=0; i<X; i++) {
		for (j=0; j<Y; j++) {
			cell[j+i*8] = numCell;
		}
	}
	for (i=0; i<cell.length; i++) {
		Y = i%8+(id%6)*8;
		X = int(i/8)+int(id/6)*8;
		if(X!=0 && Y!=0 && X<C-1 && Y<C-1) grilleDonjon[X+Y*C] = cell[i];
	}
}

Pour chaque cellule de ce tableau, j'incrémente le compteur de salles, je la rempli de 64 murs, 64*36 me donnera le nombre de tuiles totales de ma grille (48*48), à vous d'adapter ces chiffres selon la taille de votre grille finale. Je note le numéro de la cellule (id), et je tire un X et un Y aléatoire allant de 0 à 7, soit 8 cases de long sur 8 cases de large au maximum (8*8 = 64). J'ai donc une grille de 48*48 découpée en 36 sous grilles de 8*8, ce sont les emplacements possibles de mes salles. Ce découpage me permet d'éviter que deux salles se superposent, chacune est contenue dans sa cellule.

Avec les valeurs de X et Y que je viens de tirer, je rempli chaque cellules avec des sols, ce qui me donne une pièce de taille variable dans chaque cellule. Chaque sol de chaque pièce comporte le numéro de la cellule qu'on rempli.

Enfin, une fois la pièce tracée, je parcours de nouveau tous les index de la cellule (notez les deux écritures possibles pour la la boucle du parcours des cellules, choisissez celle qui vous convient le mieux), j'y récupère la position de chaque tuile, je m'assure qu'il ne s'agit pas d'un bord de la map principale, et je modifie ma “grilleDonjon” en ajoutant la référence de la tuile.

A ce stade j'ai donc deux grilles, la principale qui contient le labyrinthe, et la secondaire qui contient les salles du donjon.

for (i=0; i<tailleGrille; i++){
	if(grilleDonjon[i]>1) grille[i] = 2; 
}

Je parcours une fois de plus ma grille, et je la compare à celle du donjon, dès que je trouve autre chose qu'un mur dans la grille du donjon, je modifie la grille principale pour y percer des trous, mes pièces.

Youpi, à ce stade ma grille principale est déjà presque un donjon, c'est un mix entre les couloirs du labyrinthe qui assurent que tous les points libres sont atteignables, et les pièces du donjon qui ne peuvent pas bloquer le joueur puisque ce ne sont que des trous de dimension aléatoire posés au beau milieu des couloirs. Mais le boulot n'est pas terminé, loin de là puisque c'est maintenant que les joyeusetés commencent.

Pour le moment, notre grille principale est un joyeux foutoir, il y a des chiffres partout, les couloirs sont encore considérés comme des couloirs même si ils traversent une pièce, etc…. tracez la grille et vous comprendrez mieux, bref, il me faut nettoyer tout ça avant d'aller plus loin.

var t:Array;
 
for (i=0; i<tailleGrille; i++){
 
	t = [];	
 
	if(grille[i]!=1){
 
		t = [1,1,1,1,0,1,1,1,1];
		if (grille[i-1]!=1) 		t[3] = 0;
		if (grille[i+1]!=1) 		t[5] = 0;
		if (grille[i-C]!=1) 		t[1] = 0;
		if (grille[i+C]!=1) 		t[7] = 0;
		if (grille[i-C-1]!=1) 		t[0] = 0;
		if (grille[i-C+1]!=1)		t[2] = 0;
		if (grille[i+C-1]!=1) 		t[6] = 0;
		if (grille[i+C+1]!=1) 		t[8] = 0;
 
		var libres:int = 		4-(t[3]+t[5]+t[1]+t[7]);
		var diagos:int = 		t[0]+t[2]+t[6]+t[8];
		var HG:int = 			t[0]+t[1]+t[3];
		var HD:int = 			t[1]+t[2]+t[5];
		var BG:int = 			t[3]+t[6]+t[7];
		var BD:int = 			t[5]+t[7]+t[8];
		var angle:Boolean = 	HG==0 || HD==0 || BG==0 || BD==0;
 
		if (libres==0) 			grille[i] = 1;
		if (libres==1) 			grille[i] = 6;
		if (libres==2) {
			if (t[3]+t[5]==2) 	grille[i] = 0;
			if (t[1]+t[7]==2) 	grille[i] = 0;
			if (diagos > 1) 	grille[i] = 0;
			if (angle) 		grille[i] = 2;
		}	
		if (libres==3) {
			if (diagos > 1) 	grille[i] = 0;
			if (angle) 		grille[i] = 2;
		}	
		if (libres==4) {
			if (diagos == 4) 	grille[i] = 0;
			else 			grille[i] = 2;
		}
	}
}

Oh je suis conscient, en me relisant, qu'il aurait certainement été possible de travailler directement avec une seule grille en évitant de devoir tout nettoyer, mais ….. mais rien, c'est une erreur de ma part, un manque de recul sans doute, bref c'est largement améliorable mais je ne vais pas me retaper la rédaction de tout l'exercice ce n'est pas si grave et ça suffira à montrer le principe.

Et justement, le principe est le suivant : pour chaque tuile de ma grille principale qui n'est pas un mur, je crée un tableau de 9 index, celui qui est au centre est un sol (c'est la tuile vide que je teste) et les autres sont des murs par défaut. Ensuite je regarde ses 8 voisines, pour chacune si je tombe sur un sol je modifie mon tableau. Je peux à présent compter les murs et les sols autour de ma tuile (si vous avez bien lu la partie sur la caverne c'est le même principe, vous pouvez d'ailleurs utiliser la même boucle au lieu d'étirer le code comme je l'ai fait).

Je compte les tuiles qui sont un sol et qui sont directement à côté de ma tuiles (pas de diagonales), et je compte les diagonales. Je vais également compter les angles, c'est à dire les tuiles voisines qui forment un angle à 90 degrés par lot de trois, vous allez comprendre pourquoi par la suite.

Je suis à présent en mesure de déterminer précisément ce qui entoure mes tuiles, il ne me reste plus qu'à faire quelques vérifications pour savoir si une tuile fait partie d'un couloir ou d'une pièce.

Je commence par vérifier les tuiles voisines qui ne sont pas des diagonales, on peut les considérer comme des chemins libres où le joueur peut marcher (rappel le joueur ne va jamais en diagonale pour le moment).

Si je n'ai aucune tuile libre c'est que la tuile est entourée de murs, je la transforme donc en un mur.
Si je n'ai qu'une seule tuile libre c'est forcément un cul de sac.
Si je n'ai que deux tuiles libres :

  • je vérifie si ces deux tuiles sont opposées, dans ce cas c'est un couloir.
  • je vérifie si au moins deux diagonale sont des murs dans ce cas c'est un croisement
  • je vérifie si un angle est possible et dans ce cas c'est une salle

Si je n'ai que trois tuiles de libres

  • je vérifie si au moins deux diagonales sont des murs dans ce cas c'est un croisement
  • je vérifie si un angle est possible et dans ce cas c'est une salle

Si j'ai quatre tuiles de libres

  • je vérifie si les quatre diagonales sont des murs dans ce cas c'est un croisement
  • sinon c'est une salle

Parlons rapidement des angles car je pense que cela peut encore être flou.
Un angle autour d'une tuile c'est ça :

001
001
111

Ma tuile test est au centre, ici j'ai un angle haut gauche, c'est à dire que les tuiles libres forment une ouverte en haut à gauche, grâce à ces “angles” je peux facilement déterminer si ma tuile testée fait partie d'un couloir ou d'une salle.

Bien, à présent ma grille principale est nettoyée, les couloirs sont des couloirs (0), les murs sont des murs (1), les culs de sac sont des culs de sac (6) et les salles sont des salles (2) dont le périmètre est clairement défini.

Mais ce n'est pas encore fini, car pour le moment mes salles sont clean sur la grille principale mais elles portent toutes le numéro de tuiles à afficher (2), et dans ma grille de donjon où je stocke le numéro des salles rien n'a été modifié, mes salles sont toujours dans l'état initial, c'est à dire avant la fusion des couloirs.

for (i=0; i<tailleGrille; i++){
 
	var g:int = grille[i-1];
	var d:int = grille[i+1];
	var h:int = grille[i-C];
	var b:int = grille[i+C];
 
	if(grilleDonjon[i]>10){
		if (g==2) grilleDonjon[i-1]=grilleDonjon[i];
		if (d==2) grilleDonjon[i+1]=grilleDonjon[i];
		if (h==2) grilleDonjon[i-C]=grilleDonjon[i];
		if (b==2) grilleDonjon[i+C]=grilleDonjon[i];
	}
	if(grille[i]==0){
		if (g==2) grille[i]=5;
		if (d==2) grille[i]=5;
		if (h==2) grille[i]=5;
		if (b==2) grille[i]=5;
	}
}

Je refait donc un passage sur toute la grille, je vais de nouveau chercher les voisines (cette fois je ne regarde pas les diagonales) de toutes mes tuiles, et je vais regarder si la tuile que je suis en train de tester à une valeur supérieure à 10 (arbitraire, j'aurai pu commencer directement à 30) pour la grille du donjon, ainsi je peux repérer les tuiles qui font partie de pièces dans la grille du donjon. Je vous rappelle que la grille du donjon n'a pas encore été nettoyée, c'est justement ce qu'on est en train de faire. Si cette tuile fait bien partie d'une pièce sur la grille du donjon, je regarde si les voisines correspondent à une salle dans la grille principale, si c'est le cas alors je corrige la grille du donjon.

Puis je vais poser les portes, c'est assez simple, il me suffit de regarder si la tuile que je teste est un sol, si c'est le cas si une des voisines est une salle c'est que je suis à l'entrée d'une salle, et donc que je dois poser une porte (5).

Youpi, mes deux grilles sont enfin propres, mais ce n'est pas encore fini.

for (i=0; i<tailleGrille; i++){
	if(grille[i]==2) grille[i]=grilleDonjon[i];				
}

Je refais un passage dans ma grille (non, c'est impossible de faire tout en une seule boucle car je dois m'assurer avant que les deux grilles sont bien corrigées), si je tombe sur une salle alors je corrige la valeur de la tuile par celle du donjon.

for (i=0; i<tailleGrille; i++){
	if(grille[i]==5){
		if (grille[i-1]>29) grilleObjets[i] = grille[i-1]-28;
		if (grille[i+1]>29) grilleObjets[i] = grille[i+1]-28;
		if (grille[i-C]>29) grilleObjets[i] = grille[i-C]-28;
		if (grille[i+C]>29) grilleObjets[i] = grille[i+C]-28;
	}
}

Allez, dernier passage, cette fois il s'agit de numéroter les portes, pour cela je regarde si je tombe sur une porte dans la grille principale, si c'est le cas je regarde les voisines (sans les diagonales), si l'une d'elles correspond à une pièce, j'enregistre le numéro dans la “grilleObjets” dont nous ne nous sommes pour l'instant pas servis. Cette grille à pour but de stocker les objets divers que l'on mettra dans le donjon, pour le moment je considère qu'une porte est un objet, elle sera donc présente dans cette grille.

grille[sortie.y+sortie.x*L]=6;

Lorsque j'ai fini de nettoyer mes grilles j'identifie la sortie que j'inscrit dans la grille principale. Notez que la sortie peut en fait se trouver n'importe où, par construction il n'existe pas d'endroit du donjon qu'on ne peut atteindre, votre sortie peut donc être placée à n'importe quel endroit libre, il en va de même pour l'entrée une fois que vous avez effectué tous les tests et manipulations.

Dernier petit travail, afficher le résultat :

function render():void{
	for (i=0; i<tailleGrille; i++){
		var f:int = 		grille[i]+1
		var h:int = 		grilleObjets[i];
		var g:String = 		"";
		X = 			int(i/C);
		Y = 			int(i%C);
		if (f>29)		g = (f-29).toString(), f=3;
		if (f==6) 		g = h.toString();
		renduTuile(X,Y,f,g);
		if(i==C+1) 		renduTuile(X,Y,5,"");
		if(i==C-1+(L-2)*C) 	renduTuile(X,Y,4,"");
	}
}
 
function renduTuile(X,Y,F,I):void{
	var t:Tuiles = new Tuiles();
	t.x = X*T;
	t.y = Y*T;
	t.gotoAndStop(F);
	t.num.text = I;
	rendu.addChild(t);
}

Nous avons déjà étudié ça au cours des précédents chapitres, la différence principale réside dans le fait que je dois afficher les numéro des pièces et des portes.

Et c'est terminé.

Conclusion

Dans cette première partie de l'exercice nous avons construit des structures, c'est à dire les plans qui nous seront utiles par la suite pour créer un jeu complet. Il n'y a rien de bien compliqué, pas de formules de math abominables, pas de choses vraiment abstraites, même pas de classes et de POO, la difficulté réside en fait dans les manipulations répétitives sur les grilles et tableaux. Si vous avez suivit toute la série d'exercices précédent vous ne devriez pas éprouver de difficultés ici. L'objectif de la seconde partie de l'exercice sera de poser de manière cohérente les différents objets du jeu dans les différentes structures.

Les sources

labyrinthe_mb.fla labyrinthe version CS6
labyrinthe_mb_cs5.fla labyrinthe version CS5
caverne_mb.fla caverne version CS6
caverne_mb_cs5.fla caverne version CS5
donjon_mb.fla donjon version CS6
donjon_mb_cs5.fla donjon version CS5