Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Les tableaux de tableaux, ou tableaux à plusieurs dimensions

Compatible ActionScript 2. Cliquer pour en savoir plus sur les compatibilités.Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Nataly, le 31 octobre 2010

array.jpg

J'ouvre cet appendice afin de régler le sort des tableaux dits à plusieurs dimensions.
C'est une expression qui m'a jetée dans des gouffres d'interrogations quand je l'ai rencontrée, on m'avait dit, pour que je comprenne vite : “Alors voilà un tableau à deux dimensions tu imagines une grille, à trois dimensions tu imagines un cube, et puis après tu continues pareil…”

Ah oui ? Parce que je suis réputée comprendre la quatrième dimension moi ? C'est trop d'honneur qu'on me fait :) Schrödinger, son chat, sa boîte, j'y réfléchirai dans une autre vie :D

En fait c'est en “arbre” qu'il faut raisonner dès qu'on voyage au delà de la troisième dimension… Mais commençons par le début (sale manie, je sais).

Tableau à deux dimensions

Le principe

On le sait on peut mettre ce qu'on veut dans un tableau, alors pourquoi pas des tableaux ?

Imaginons trois tableaux contenant des choses bien différentes pour nous y retrouver facilement :

var tbVilles:Array=new Array("Paris","Lyon","marseille","bordeaux");
var tbNawak:Array=new Array(50,-12,100,0.5,"25",true);
var tbLettres:Array=new Array("A","B","C","D","E","F");

Maintenant “rangeons” ces tableaux dans un autre :

var leTab:Array= new Array();
leTab.push(tbVilles);
leTab.push(tbNawak);
leTab.push(tbLettres);

Nous voilà avec un tableau de tableaux.
leTab se manipule comme n'importe quel tableau, on accède donc à son contenu tout normalement avec la syntaxe à crochets qui nous est familière :

trace(leTab[0]) ;
Paris,Lyon,marseille,bordeaux


Et on est bien d 'accord que si les villes sont listées c'est un effet de la fonction trace qui applique par défaut un toString aux objets qu'on lui passe quand ils ont le culot de n'être pas au format chaine ;) Ce qu'on récupère à l'indice 0, c'est un tableau (tbVilles en l'espèce).
trace(typeof leTab[0]) 
trace(leTab[0] is Array)

Reste à lire le tableau tbVilles (par exemple).
Lire le tableau tbVilles (tout court) on sait : tbVilles[1] renvoie Lyon.
En conséquence :
leTab[0][1] renvoie Lyon tout pareil (leTab[0] c'est tbVilles).

Et comme rien ne remplace St Thomas, vous pouvez finir de vous convaincre du fait avec la testouillerie ci-après :

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

Notez que si vous tentez de lire des emplacements inexistants vous n'obtenez pas la même chose selon que vous dépassez les limites du tableau contenant ou du tableau contenu.
Normal : leTab[4] par exemple renvoie undefined et appliquer une syntaxe à crochets sur undefined génère une erreur (ben voui :mrgreen:) alors que leTab[1][6] renvoie “seulement” undefined (pas d'erreur).

Bien entendu un tableau - contenu dans un autre ou non - reste un tableau et toutes les techniques, méthodes, habitudes que vous avez prises sont toujours d'actualité.

// j'ajoute un élément au tableau rangé indice 0
leTab[0].push("Toulouse")
trace(leTab[0][4])
// j'enlève le dernier élément du tableau rangé indice 0
var dernierElt:*=leTab[0].pop()
trace(dernierElt)
trace(leTab[0])
Toulouse
Toulouse
Paris,Lyon,marseille,bordeaux

Cas fréquent : une grille

Les tableaux à deux dimensions sont souvent utilisés pour décrire le contenu d'une grille virtuelle.
Un tableau remplit de 8 tableaux contenant chacun 5 éléments, c'est en fin de compte ce qu'on représenterait, papier crayon en main, par une une grille de 8 lignes sur 5 colonnes.
voici qui donne une “grille vide” :

var tabPlanning:Array=new Array(8);
 
for (var i:int=0; i<8; i++) {
	tabPlanning[i]=new Array(5);
}

Admettons que chaque “ligne” représente un technicien et chaque “colonne” un jour de la semaine (lundi à vendredi), on peut facilement à “l'intersection” technicien/jour stocker le lieu d'intervention.

Le technicien 0 interviendra le jour 2 (mercredi) chez Durand & Durand, pourrait s'écrire :

tabPlanning[0][2]="Durand & Durand";

Lisons la “grille” :

for (i=0; i<8; i++) {
   trace("ligne "+ i+" : "+tabPlanning[i]);
}
ligne 0 : ,,Durand & durand,,
ligne 1 : ,,,,
ligne 2 : ,,,,
ligne 3 : ,,,,
ligne 4 : ,,,,
ligne 5 : ,,,,
ligne 6 : ,,,,
ligne 7 : ,,,,

Souvent quand on utilise des tableaux dans ce cadre (grille) l'emplacement zéro de chaque “ligne” (tableau) sert d'intitulé :

//une "grille" vide
var tabPlanning:Array=new Array(3);
for (var i:int=0; i<3; i++) {
	tabPlanning[i]=new Array(6);
}
//intitulés de ligne : l'emplacement 0 vaut le nom du technicien
tabPlanning[0][0]="Paul"
tabPlanning[1][0]="Corine"
tabPlanning[2][0]="Francis"
//remplir : Paul (Technicien 0) jours 1 et 2
tabPlanning[0][1]="Repos";
tabPlanning[0][2]="Comptoire du Fruit";
// lire
for (i=0; i<3; i++) {
	trace(tabPlanning[i]);
}
Paul,Repos,Comptoire du Fruit,,,
Corine,,,,,
Francis,,,,,

En tant que matrice

Voici une ligne de code qui fabrique et valorise ( à l'aide de 1 et de 0) un tableau à deux dimensions.

var unTab:Array=new Array(
  new Array(0,0,0,0,0)
  ,new Array(0,0,1,0,0)
  ,new Array(0,1,1,1,0)
  ,new Array(0,0,0,0,0));

Oh ! Un sous marin ! (dessiné par les 1)
Si, un sous marin de bataille navale, comme j'en disputais des après midi entières avec mon grand père quand j'étais enfant :)
J'ai pris soin de disposer les lignes de code de façon lisible et la coloration syntaxique fait le reste, d'ailleurs on aurait pu utiliser la syntaxe alternative (crochets) qui est encore plus lisible :

var unTab:Array=[
 [0,0,0,0,0], 
 [0,0,1,0,0],
 [0,1,1,1,0],
 [0,0,0,0,0]];

Chez vous vous pouvez tracer pour y voir plus clair :

var i:int
for (i=0; i<4; i++) {
	trace(unTab[i]);
}
0,0,0,0,0
0,0,1,0,0
0,1,1,1,0
0,0,0,0,0


:mrgreen: Et si on se faisait une bataille navale pour s'entrainer à la manipulation des tableaux de tableaux ?
D'accord, je sors…

Trois dimensions …

Et je reviens, parce qu'on n'en a pas fini.

Imaginons cette fois qu'on travaille à l'international (rêvons grands ;))
On a besoin de manipuler des distributeurs (le nom des boutiques) selon les villes, et les villes selon les pays. Avec des tableaux deux dimensions, on pourrait écrire ça :

var tabVillesEspagne:Array=["Barcelones","Cordou","Madrid","Valence"];
var tabVillesFrance:Array=["Anger","Paris","Toulouse"];
var tabVillesRU:Array=["Brighton","Londres"];
 
var tabDistributeurs:Array=[tabVillesEspagne,tabVillesFrance,tabVillesRU];

Ce qui est bien gentil pour nous permettre de manipuler les villes par pays, mais ce qui nous intéresse ce sont les distributeurs par ville (et par pays).
En conséquence, les tabVillesTruc doivent contenir non pas des noms de villes mais la liste des distributeurs, donc des tableaux, encore.
Pour notre usage, décidons d'entrer en index 0 le nom de la ville concernée (c'est nous qu'on décide, on fait bien ce qu'on veut !).

var tabDistribBarcelones:Array=["Barcelones","La tienda de Pedro","Hypermacato del sol"];
var tabDistribCordou:Array=["Cordou","La ultima","Todo por nada"];
var tabDistribMadrid:Array=["Madrid","Hypermacato de Madrid","Ahi","Carga"];
var tabDistribValence:Array=["Valence","No mas idea"];
 
var tabDistribAnger:Array=["Anger","AMag 1","AMag 2"];
var tabDistribParis:Array=["Paris","PMag 1","PMag 2","Pmag3"];
var tabDistribToulouse:Array=["Toulouse","TMag 1","TMag 2"];
 
var tabDistribBrighton:Array=["Brighton","shop 1","shop 2","shop 3","shop 4"];
var tabDistribLondres:Array=["Londres","Big shop 1","Big shop 2","Big shop 3"];
 
var tabVillesEspagne:Array=[tabDistribBarcelones,tabDistribCordou,tabDistribMadrid,tabDistribValence];
var tabVillesFrance:Array=[tabDistribAnger,tabDistribParis,tabDistribToulouse];
var tabVillesRU:Array=[tabDistribBrighton,tabDistribLondres];
 
var tabDistributeurs:Array=[tabVillesEspagne,tabVillesFrance,tabVillesRU];

Ouf ! C'est bien parce que c'est vous, je ne le ferais pas tous les jours non plus ! :mrgreen:

trace(tabDistributeurs[0][0][0]);
trace(tabDistributeurs[0][0][1]);
trace(tabDistributeurs[2][1][0]);
trace(tabDistributeurs[2][1][3]);
Barcelones
La tienda de Pedro
Londres
Big shop 3

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

… et plus si affinités

Et ce principe des “poupées russes” qui consiste à imbriquer des tableaux dans des tableaux peut se poursuivre à l'envi. Le seul souci qui puisse surgir c'est comment se le gribouiller vite fait sur un coin de calepin quand besoin est.
2D, on vient de le voir c'est facile, ça revient à une grille et il est vrai qu'en extrudant la grille sur une sorte d'axe Z, on obtient une représentation d'un tableau 3D… Quoique… C'est pas terriblement pratique avec un calepin (ou l'équivalent numérique).

Des tableaux imbriqués, se représentent tout bonnement sous forme d'arbre.

Voici par exemple la représentation du tableau tabDistributeurs du chapitre précédent. Je n'ai fait qu'ajouter une entrée en index 0 aux trois tableaux tabVillesTruc afin qu'on reste bien d'accord que rien n'oblige à mettre un tableau à chaque entrée.

var tabVillesEspagne:Array=["Espagne",tabDistribBarcelones,tabDistribCordou,tabDistribMadrid,tabDistribValence];
var tabVillesFrance:Array=["france",tabDistribAnger,tabDistribParis,tabDistribToulouse];
var tabVillesRU:Array=["RU",tabDistribBrighton,tabDistribLondres];


Si vous voulez vous trafiquer les tableaux pour voir :
(La fonction n'est pas un modèle du genre)
fla CS3


Les tableaux associatifs

Ce ne sont pas des tableaux, ce sont des objets !

Si j'en parle ici, c'est parce que c'est un moyen simple d'associer une valeur et une propriété et que l'intitulé (même en anglais : associative arrays1)) et la syntaxe à crochets peuvent prêter à confusion.

Là où un tableau permet d'atteindre les éléments stockés selon un numéro d'index, le tableau associatif permet d'atteindre ces mêmes valeurs selon une entrée au format chaine 2).
Un court exemple vaudra mieux qu'un long discours :

var oAgesMembres:Object=new Object();
oAgesMembres["Jean"]=34;
oAgesMembres["Pierre"]=57;
oAgesMembres["Julie"]=26;
 
trace(oAgesMembres.Jean)

Il s'agit d'un objet et de ses propriétés rien de plus, vous pouvez donc utiliser les syntaxes qui vous sont familières, par exemple :

var oAgesMembres:Object={Jean:34,Pierre:57,Julie:26};


On parcourt l'objet avec la structure for … in :

for (var v:String in oAgesMembres) {
	trace(v);
	trace(oAgesMembres[v])
}


Il se trouve qu'on peut obtenir aussi un tableau associatif avec un objet de type Array, mais c'est formellement déconseillé par la doc elle même :
N'utilisez pas la classe Array pour créer des associative arrays, qui sont des structures de données qui contiennent des éléments nommés et non pas des éléments chiffrés. Pour créer des tableaux associatifs, utilisez la classe Object. Bien que ActionScript vous permette de créer des tableaux associatifs à l'aide de la classe Array, vous ne pouvez pas associer les méthodes ou les propriétés de cette dernière à un tableau associatif.



Pour finir sur le thème, il existe aussi le dictionnaire (classe Dictionary). C'est le même principe : tableau associatif, si ce n'est que la clé d'entrée n'est plus une chaine mais un objet. Je vous renvoie à la doc ou au chapitre de ce très bon tuto de BillyBen pour plus ample information (il s'agirait de ne pas trop digresser non plus).


Exercices

Par lilive - le 6/12/2010

Comme le dit Nataly, le mieux est de pratiquer pour se familiariser avec tout cela. Elle nous propose un exercice très complet ci-dessous. J'ai pour ma part eu envie de rajouter quelques exercices de base pour commencer.

Gardons la ligne

Je mange beaucoup de chocolat est cela me fait soucis. J'ai décidé de surveiller ma consommation ce mois-ci, et j'ai pris soin de noter le nombre de plaquettes que j'ai mangées chaque jour les 4 dernières semaines. Maintenant je voudrais analyser ces données, que j'organise en tableau ainsi:

var tablettes:Array = [
	[1,2,1,0,1,2,3],
	[0,1,1,0,0,1,2],
	[1,1,2,5,0,0,0],
	[0,0,0,1,1,2,1]
];

Chaque ligne du tableau est une semaine, et contient un tableau de 7 éléments pour le nombre de plaquettes mangées chaque jour.

Exercices

1 - Ecrire le code qui va calculer le nombre total de tablettes mangées pendant les 4 semaines et qui va l'afficher avec l'instruction trace():

J'ai mangé 29 tablettes de chocolat ce mois-ci.

2 - Ecrire le code qui va trouver quel est le jour où j'ai mangé le plus de tablettes et l'afficher:

Le jour où j'ai mangé le plus de tablettes était le jour 3 de la semaine 2
Ce jour-là j'en ai mangé 5

3 - Ecrire le code qui affiche tous les jours où j'ai mangé 3 tablettes ou plus:

J'ai mangé 3 tablettes le jour 6 de la semaine 0
J'ai mangé 5 tablettes le jour 3 de la semaine 2

4- Ecrire le code qui affiche le premier jour où j'ai mangé 3 tablettes ou plus. Il n'est donc pas nécessaire de parcourir tout le tableau, il suffit de s'arrêter quand on a trouvé un jour qui correspond (indice: boucle while, ou encore break):

J'ai mangé 3 tablettes pour la première fois le jour 6 de la semaine 0 

Solutions

Télécharger les solutions:
chocolat.zip


Les distributeurs

Reprenons maintenant le tableau des distributeurs de Nataly:

var tabDistribBarcelones:Array=["Barcelones","La tienda de Pedro","Hypermacato del sol"];
var tabDistribCordou:Array=["Cordou","La ultima","Todo por nada"];
var tabDistribMadrid:Array=["Madrid","Hypermacato de Madrid","Ahi","Carga"];
var tabDistribValence:Array=["Valence","No mas idea"];
 
var tabDistribAnger:Array=["Anger","AMag 1","AMag 2"];
var tabDistribParis:Array=["Paris","PMag 1","PMag 2","Pmag3"];
var tabDistribToulouse:Array=["Toulouse","TMag 1","TMag 2"];
 
var tabDistribBrighton:Array=["Brighton","shop 1","shop 2","shop 3","shop 4"];
var tabDistribLondres:Array=["Londres","Big shop 1","Big shop 2","Big shop 3"];
 
var tabVillesEspagne:Array=["Espagne",tabDistribBarcelones,tabDistribCordou,tabDistribMadrid,tabDistribValence];
var tabVillesFrance:Array=["france",tabDistribAnger,tabDistribParis,tabDistribToulouse];
var tabVillesRU:Array=["RU",tabDistribBrighton,tabDistribLondres];
 
var tabDistributeurs:Array=[tabVillesEspagne,tabVillesFrance,tabVillesRU];

Et imaginons qu'il s'agit par exemple de magasins vendeurs de… chocolat :)

Exercices

1- Je voyage et je ne veux surtout pas manquer de chocolat. Quand je suis dans un pays, je veux connaitre quelles sont les villes où il y a des vendeurs de chocolat, pour éviter soigneusement de me rendre dans les autres.
Ecrire le code qui affiche toutes les villes d'un pays d'après son nom. Par exemple si nous définissons une variable:

// Nom du pays cherché
var pays:String = "RU";

Nous voulons avoir en sortie la liste des villes du pays qui correspond au contenu de la variable pays :

Dans le pays RU on peut trouver du chocolat dans les villes suivantes:
Brighton
Londres

Il doit ensuite suffire de changer la variable, sans rien changer au code, pour avoir la liste des villes d'un autre pays:

// Nom du pays cherché
var pays:String = "Espagne";
Dans le pays Espagne on peut trouver du chocolat dans les villes suivantes:
Barcelones
Cordou
Madrid
Valence


2- De retour de mon périple je me souviens avoir acheté un merveilleux chocolat dans un magasin. Je me souviens du nom du magasin, mais pas de la ville ni du pays où il se trouve.
Ecrire le code qui va retrouver où se situe ce magasin, que je puisse le contacter pour passer commande. Par exemple, si nous définissons la variable:

// Nom du distributeur recherché
var distrib:String = "shop 2";

Nous devons avoir en sortie:

Le distributeur shop 2 est présent dans 
la ville Brighton du pays RU

Faire en sorte qu'il suffise de changer le contenu de la variable distrib pour que le programme trouve l'emplacement de cet autre distributeur.

Penser qu'il est possible qu'un magasin de même nom soit présent dans plusieurs villes. Dans ce cas afficher toutes les villes où il est présent.

Si le magasin n'existe nulle part afficher:

Le distributeur Cacao Paradise est présent dans 
aucun pays.


Solutions

Télécharger les solutions:
voyage.zip
Bien sûr il y a toujours plusieurs façons de faire et vous avez pu vous débrouiller autrement.
Dans le code solution j'ai supposé que le tableau est obligatoirement “bien formé”. C'est-à-dire que chaque élément de tabDistributeurs est bien un tableau contenant en index 0 un nom de pays et ensuite des tableaux eux-même bien formés.
Ceci est une faiblesse de ce code, et il pourrait être intéressant d'y ajouter des tests pour qu'il s'adapte à une situation comme:
var tabDistributeurs:Array=[tabVillesEspagne,tabVillesFrance,tabVillesRU, "Japon"];



On se la fait cette bataille navale ?

Pour ce qui est de la théorie, tout a été dit, yapuka… Pratiquer :)

Si vous voulez vous familiariser avec les tableaux de tableaux, le mieux c'est encore d'en manipuler. Pour certaines têtes ça se met en place d'un coup, pour d'autres ça demande un peu d'entrainement, le temps de fabriquer les populations neuronales dédiées (ce n'est même pas une blague).

Grâce à Lilive les gammes sont faites, j'avais pour ma part préparé ce que je pensais être un “Petit TP rigolo”. Force est de constater que petit n'est pas l'adjectif qui sied (quant à rigolo je ne me prononcerai même pas). L'alibi était celui d'une bataille navale :

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

POU !

Précautions Oratoires d'Usage

Je parle bien d'alibi : il ne s'agit pas tant ici de voir la meilleure façon de développer une bataille navale en AS3 - à l'époque des mmorpg, c'est un poil obsolète de toutes façons :mrgreen:- que de se trouver une consigne qui permette de manipuler du tableau en veux tu en voilà.

D'ailleurs je ne vais pas tout détailler d'un bout à l'autre mais plutôt extraire les points intéressants qui peuvent être traités à coups de tableaux. Je vous laisserai le loisir de vous en servir à votre gré et d'organiser le tout à votre goût.

La démo n'est qu'une mise en œuvre possible parmi une foultitude d'autres, vous rencontrerez autant de façons de faire que de développeurs et de contextes.
Celle-ci manipule des tableaux à deux dimensions, des tableaux de tableaux 2D et des tableaux associatifs (objets)… tout pour plaire ! :)

Classes ou fla ?

Bien sûr j'aurais pu écrire un fichier .as à associer en tant que classe de document et - pourquoi pas - une autre classe à associer au clip de base (les cases du plateau). Si on considère que cette anim' n'est pas appelée à être déclinée, je ne vois vraiment pas l'intérêt de trois fichiers (un .fla et deux .as), j'ai donc pris le parti de tout développer dans l'ide. Rien ne vous empêche de prendre le parti contraire ;)


Le clip de base : case du plateau

Le plateau de jeu sera constitué d'une série d'instances d'un même clip qui affichera pour chaque case, à notre gré, les différents visuels possibles : case masquée (je pense brouillard : oui je joue peut-être un peu beaucoup à Civilization, ça laisse des traces), case d'eau, case de bâtiment : Porte-avions, Sous marin, Croiseur, etc.


B : brouillard - O : eau - C : Croiseur - SM : Sous marin - PA : Porte-avions - bordure : bordure.

Les différents visuels :

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

On sait que chaque case dévoilera son contenu sur clic, il suffit donc que le clip soit équipé d'un écouteur sur MouseEvent.CLICK qui enverra la tête de lecture à l'image considérée.
Cette image, le visuel à afficher, on le précisera quand on placera les bateaux. Par défaut c'est de l'eau. Décidons d'une variable nommée contient de type String.

code image 1 du clip Mv_Case :

stop();
//Les inits se font au choix, ici ou dans fab grille.
//Je préfère ici pour typer les variables
 
// le visuel affiché qd clique : "O" eau ; "SM" Sous marin ; "PA" Porte-avions ; etc
var contient:String="O";
 
buttonMode=true;
addEventListener(MouseEvent.CLICK,decouvre);
function decouvre(me:MouseEvent):void {
        // qd la case est découverte, c'est fini pour elle, on supprime l'écouteur
   	removeEventListener(MouseEvent.CLICK,decouvre);
 
	MovieClip(root).qdClicCase(this);
}
//A chaque nouveau plateau les instances seront supprimées, 
//pour permette au ramasse miettes de faire le ménage :
addEventListener(Event.REMOVED_FROM_STAGE, sup);
function sup(e:Event):void {
        // supprimer l'écouteur
	removeEventListener(MouseEvent.CLICK,decouvre);
}

Que ceux qui viennent d'AS2 ne s'inquiètent pas de la référence à la racine via la propriété root : je rappelle qu'en AS3 la propriété renvoie toujours le scenario principal du swf considéré, même inclus dans un autre.

Sur la racine

function qdClicCase(c:MovieClip) {
	var typeCase:String=c.contient;
	c.gotoAndStop(typeCase);
}


Bien qu'ayant pris le parti de développer dans l'ide, je trouve qu'il vaut mieux éviter de coder dans le clip, plus le code est regroupé au même endroit, plus la maintenance/évolution se fait facilement. C'est donc sur la racine que sera écrite la fonction de rappel qdClicCase.
Reste le minimum syndical requis sur le clip : initialisations (MovieClip est dynamique mais je préfère pouvoir typer les variables bien proprement ;)) et écouteur du clic.
Vous pouvez, si vous préférez, tout faire à l'initialisation de la grille (ajout d'instance, valorisation de ses variables et association d'écouteur).


Note à l'attention des utilisateurs de CS3
Après avoir converti les sources au format CS3, plus rien ne fonctionnait…
Pour des raisons que je ne m'explique toujours pas bien, il se trouve que sous CS3 les variables du clip ne sont pas initialisées “correctement”(trop tard).
Quand je parle de CS3, c'est parce que c'est le premier élément d'identification qui a surgit, en fait c'est une question de lecteur. Compiler pour lecteur 9 - quelque soit la version - entraine le délai à l'initialisation. Je me suis donc résignée à la classe de 10 lignes associée au clip Mv_Case. Vous la trouverez dans les sources CS3.


Générer la grille de jeu et le tableau associé

Générer le plateau de jeu c'est tout bonnement ajouter autant d'instances de Mv_Case que nécessaire et les disposer en lignes successives.

Au passage on va les ajouter à un tableau 2D (_tabGrille). Ça nous sera utile pour placer les bateaux de pouvoir atteindre n'importe quelle instance depuis ses coordonnées : _tabGrille[ligne,colonne].

Non, je ne respecte pas les conventions d'écriture : je choisis i et k comme variable d'incrément au lieu des traditionnelles i, j. Pô bien :-\
C'est parce que je suis conjointement myope et ahurie depuis toujours et que je confonds facilement i et j, par ailleurs depuis toujours aussi dans ma tête c'est le i de ligne et le k de kolonne, là voilà ! Je m'en remets donc à votre grand indulgence…


function fabGrille(pLig:int,pCol:int) {
	var i:int;
	var k:int;
	var caseBordure:MovieClip;
	var tab_c:Array;// tableau courant ds boucle (les lignes)
	init(pLig,pCol);
	for (i=0; i<_nbLig; i++) {
                // nouvelle ligne
		tab_c=new Array();
		for (k=0; k<_nbCol; k++) {
			var c:Mv_Case=new Mv_Case();
			// (les inits sont faites à la création de l'instance (voir clip Mv_Case))
			// dans le "tableau-ligne" une référence au clip 
			tab_c.push(c);
			// ajouter une instance du clip de base
			_grille.addChild(c);
			// disposer
			c.x=k*c.width;
			c.y=i*c.height;
			if (k==0||k==_nbCol-1) {
				// cas d'exception les bordures droite/gauche
				c.contient="bordure";
				c.gotoAndStop("bordure");
			}
 
		}
		// Ajouter la "ligne" dans le tableau 2D
		_tabGrille.push(tab_c);
	}
	//une bordure pour faire joli (haut et bas)
	for (k=0; k<_nbCol; k++) {
		//Ligne haut
		caseBordure=_tabGrille[0][k];
		caseBordure.gotoAndStop("bordure");
		caseBordure.contient="bordure";
		caseBordure.buttonMode=false;
		//Ligne bas
		caseBordure=_tabGrille[_nbLig-1][k];
		caseBordure.gotoAndStop("bordure");
		caseBordure.contient="bordure";
		caseBordure.buttonMode=false;
	}
}
// elle s'enrichira en cours de route…
function init(pLig:int,pCol:int) {
        // tableau stockant les réf aux instances de clips Case
	_tabGrille=new Array();
        // ajouter deux lignes pour les bordures haut bas
	_nbLig=pLig+2;
        // ajouter deux colones pour les bordures gauche droite
	_nbCol=pCol+2;
}


Ne reste plus qu'à ajouter un bouton sur la scène pour générer une nouvelle grille vierge, en tous cas sans bateaux.

 
var _nbLig:int;
var _nbCol:int;
var _tabGrille:Array;// le tableau 2D contenant les références aux instances de Mv_Case
 
// zoneJeu : instance déjà sur la scène (disposée à son gré, à la main)
var _largZone:Number=zoneJeu.width;
var _hautZone:Number=zoneJeu.height;
 
var _plateau:MovieClip=new MovieClip();
// le plateau contiendra la grille (plus pratique de supprimer carrément le clip grille que chacun des clips la constituant)
zoneJeu.addChild(_plateau);
 
var _grille:MovieClip;
 
btNv.addEventListener(MouseEvent.CLICK, nvPlateau);
 
function nvPlateau(me:MouseEvent) {
	if (_plateau.numChildren) {
		_plateau.removeChild(_grille);
	}
	_grille= new MovieClip();
	_plateau.addChild(_grille);
	fabGrille(10,10);
	// centrer
	_plateau.x=(_largZone-_plateau.width)/2<0?0:(_largZone-_plateau.width)/2;
	_plateau.y=(_hautZone-_plateau.height)/2<0?0:(_hautZone-_plateau.height)/2;
}

Disposer les bateaux de façon aléatoire

On arrive aux choses vraiment amusantes : disposer des bateaux sur la grille de façon aléatoire en faisant en sorte qu'ils ne se touchent pas (et à fortiori ne se superposent pas).

Des tableaux 2D comme matrices

On va raisonner en zone d'encombrement et utiliser des tableaux 2D pour les représenter.

Un bateau c'est un ensemble de cases disposées selon un même modèle (3 cases alignées pour un porte-avions, 2 cases pour un croiseur, 4 cases pour un sous marin…).
Sa zone d'encombrement ce sont toutes les cases adjacentes : on ne pourra pas y poser de “case bâtiment”.

Ce qui peut se représenter comme suit pour un porte-avion :

[1,1,1]
[1,2,1]
[1,2,1]
[1,2,1]
[1,1,1]

En considérant 2 pour case bâtiment, 1 pour case adjacente.

Pour un sous marin, qui est tout tordu, on considérera 0 pour les deux cases en coin qui ne sont ni de type bâtiment ni de type adjacente.

[0,1,1,1,0]
[1,1,2,1,1]
[1,2,2,2,1]
[1,1,1,1,1]

Poser un bâtiment c'est reproduire sur la grille du plateau l'emprunte de la matrice du bâtiment considéré : “écrire” des 0, des 1 ou des 2 dans les cases en correspondance de la grille du plateau.

Vérifier qu'une place est disponible, c'est superposer à cette même grille plateau la matrice du bâtiment et vérifier case par case qu'il n'y a pas conflit. Si, par exemple, deux cases “superposées” portent chacune un 2, ce n'est pas possible - 2 et 1, non plus - 1 et 1 en revanche ça marche.


Déclarons sans plus attendre un tableau 2D qui sera la zone d'encombrement d'un sous marin.

var tabZoneSM:Array=[
  [0,1,1,1,0],
  [1,1,2,1,1],
  [1,2,2,2,1],
  [1,1,1,1,1]];

Tester toutes les possibilités jusqu'à trouver une place

Le principe va être le suivant :

• Une fonction possible à qui on passe une matrice (tab 2D) et les coordonnées de son coin haut gauche sur la grille plateau. La fonction renvoie faux si la matrice n'est pas superposable au plateau. Dans le cas contraire, les données (0, 1, 2) portées par la matrice sont reproduites sur la portion de la grille plateau, et la fonction renvoie vrai.
• Une fonction marque invoquée par possible pour marquer (reproduire, recopier) les données de la matrice du bateau sur la grille plateau.

• Une fonction dispose qui appelle possible avec des coordonnées piochées au hasard jusqu'à ce que possible retourne vrai. dispose renvoie vrai ou faux selon.

• Une fonction ajouteBateau qui appelle dispose en faisant pivoter la matrice jusqu'à ce que la fonction retourne vrai.


En gros on va tester chaque coordonnée possible selon toutes les positions - orientation - de la matrice.

Fonctions possible et marque

Quand je parle de grille plateau, je pense au tableau _tabGrille qui en fera office : les instances qu'il contient peuvent porter l'information qui nous intéresse, à savoir c'est une case de “type” 0 (qui se superpose à n'importe quoi), 1 (adjacente ne peut se superposer qu'à 0 ou 1), ou 2 (bâtiment ne se superpose qu'à 0).
Il suffit de déclarer une variable supplémentaire dans Mv_Case, je l'appelle typeTerrain, elle vaut 0 par défaut.

image 1 de Mv_Case

var typeTerrain:int=0

code racine

function possible(pTabZ:Array,pLig:int,pCol:int,pType:String):Boolean {
	var i:int;
	var k:int;
	var clip_c:MovieClip;// clip courant
	//on superpose la zone d'encombrement du bateau à la _grille
	for (i=0; i<pTabZ.length; i++) {
		for (k=0; k<pTabZ[i].length; k++) {
			// pour chaque clip concerné (portion superposée) du tableau Grille
			if (pLig+i>=_nbLig||pCol+k>=_nbCol) {
				// le clip n'existe pas
				return false;
			}
			clip_c=_tabGrille[pLig+i][pCol+k];
			// typeTerrain vaut 0 se combine avec n'importe quoi
			// typeTerrain vaut 1 (bordure) ne se combine qu'avec 1
			if (clip_c.typeTerrain*pTabZ[i][k]>1) {
				return false;
			}
		}
	}
	// on n'est pas sorti donc c'est bon : on marque le bateau sur la grille
	marque(pTabZ,pLig,pCol,pType);
	return true;
}

la seule ruse ici, c'est de multiplier les “valeurs” des cases superposées (typeTerrain du clip de la grille et [ligne,colonne] pour la zone d'encombrement.
Si on obtient 0 c'est qu'une des cases vaut 0 donc accepte n'importe quoi.
Si on obtient 1 c'est que les deux cases valent 1.
Dans tous les autres cas c'est qu'une des cases au moins contient un bâtiment.

La fonction retourne donc faux si clip_c.typeTerrain*pTabZ[i][k]>1

marque
function marque(pTabZ:Array,pLig:int,pCol:int,pType:String):void {
	var i:int;
	var k:int;
	var clip_c:MovieClip;
 
	var iMax:int=pTabZ.length;
	for (i=0; i<iMax; i++) {
		var kMax=pTabZ[i].length;
		for (k=0; k<kMax; k++) {
			clip_c=_tabGrille[pLig+i][pCol+k];
                        // marquer le clip du type de terrain
			clip_c.typeTerrain=pTabZ[i][k];
			if (pTabZ[i][k]>1) {
				//ce n'est pas une case d'eau : le clip contientra le visuel idoine
				clip_c.contient=pType;
			}
		}
	}
}

Et voilà, il n'y a plus qu'à tester cette première étape en appelant directement possible, dans nvPlateau, après avoir généré le plateau (en fin de code, donc).

	trace("0/0 "+possible(tabZoneSM,0,0,"SM"));
	trace("8/6 "+possible(tabZoneSM,8,6,"SM"));
	trace("2/3 "+possible(tabZoneSM,2,3,"SM"));
	trace("4/4 "+possible(tabZoneSM,4,4,"SM"));
0/0 true
8/6 true
2/3 true
4/4 false

Essayer toutes les cases de la grille au hasard

Maintenant l'idée c'est d'invoquer possible pour chaque case de la grille, mais pas en balayant, sans quoi on les tasserait en haut à gauche.
Il nous faut par conséquent une liste des coordonnées disponibles et piocher dans cette liste, au hasard, coordonnées après coordonnées, jusqu'à ce que possible renvoie vrai.

La liste (un bête tableau) on va la remplir en même temps qu'on fabrique la grille. Appelons la _tabRef et remplissons la de Points (objets) dont x vaudra le numéro de ligne et y le numéro de colonne (ou le contraire si ça vous gène).

function fabGrille(pLig:int,pCol:int) {
	var i:int;
	var k:int;
	var caseBordure:MovieClip;
	var tab_c:Array;
	init(pLig,pCol);
	for (i=0; i<_nbLig; i++) {
		tab_c=new Array();
		for (k=0; k<_nbCol; k++) {
			var c:Mv_Case=new Mv_Case();
			tab_c.push(c);
			_grille.addChild(c);
			c.x=k*c.width;
			c.y=i*c.height;
			if (k==0||k==_nbCol-1) 
				c.contient="bordure";
				c.gotoAndStop("bordure");
			}
// modif ici----->       pour disposer les bateaux 
			// i vaut la ligne donc point.x vaudra le numéro de ligne, si ça vous trouble inversez (partout ds l'appel de possible (''dispose'') aussi)
			_tabRef.push(new Point(i,k));
		}
   [suite]

_tabRef est une globale rénitialisée dans init

function init(pLig:int,pCol:int) {
	_tabGrille=new Array();
//ici---> vider _tabRef
	_tabRef=new Array();
	_nbLig=pLig+2;
	_nbCol=pCol+2;
 
}
La fonction dispose

Quant à la fonction qui essaie tous les points je l'ai nommée dispose (mouais… peut mieux faire…). Elle pioche, point après point, dans un tableau qui est une copie de _tabRef. Pas _tabRef lui même : on compte bien l'utiliser plusieurs fois cette fonction dispose, à chaque fois on re-essaiera tous les points.

function dispose(pTabMatrice:Array,pType:String):Boolean {
	//essaie tous les points de la _grille et retourne le résultat 
	var tabDispo:Array=_tabRef.slice();
	var p:Point;
	var fait:Boolean;
	// boucle aussi longtemps que le bateau n'a pas pu être posé et qu'il reste des coordonnées à essayer
	do {
		p=pioche(tabDispo);
		//qd c'est possible le bateau est "posé" dans l'élan
		fait=possible(pTabMatrice,p.x,p.y,pType);
	} while (!fait && tabDispo.length>0);
 
	return fait;
}
La fonction pioche
function pioche(pTab:Array):Point {
	var idx:int=Math.random()*pTab.length;
	var elt:Point=pTab[idx];
	pTab.splice(idx,1);
	return elt;
}

Essayer toutes les orientations

L'idée c'est de “proposer” successivement à dispose chacune des orientations possibles d'un bateau (2 pour porte-avions et croiseur qui sont symétriques, 4 pour un sous marin), jusqu'à ce que l'une d'entre-elles trouve sa place.
Et tant qu'à faire, il faut aussi ne pas toujours commencer par la même orientation (qui a beaucoup de chances d'être admise) pour conserver un caractère parfaitement aléatoire à la disposition des bâtiments.

la fonction pivote

Elle imprime une rotation d'un quart de tour à la matrice passée en argument.
J'avoue : c'est çayjb qui me l'a donnée. Je vous la transmets telle que, sachant que dans notre cas de figure on n'a pas besoin du deuxième argument (mais comme ça vous êtes outillé ;))

function pivote(tab:Array, sens:Boolean = true):Array {
	var nvTab:Array=[];
 
	var colonnes:int=tab.length;
	var lignes:int=tab[0].length;
 
	var j:int;
	for (var i:int; i < lignes; i++) {
		nvTab[i]=[];
		for (j = 0; j < colonnes; j++) {
			if (sens) {
				nvTab[i][j]=tab[colonnes-j-1][i];
			} else {
				nvTab[i][j]=tab[j][lignes-i-1];
			}
		}
	}
	return nvTab;
}
la fonction ajouteBateau

Ce sera la fonction principale. Que vous décidiez d'ajouter systématiquement les mêmes bateaux (comme sur la démo), de les ajouter un par un au choix du joueur, de les déplacer en cours de partie… Quelque idée que vous ayez, c'est cette fonction que vous utiliserez.

Il y a bien sûr plusieurs voies possibles : soit on lui passe la matrice d'un bâtiment et son type, soit seulement la matrice d'un bâtiment (charge à elle de retrouver le type), soit encore on lui passe un tableau des orientations possibles (les 4 matrices pour un sous marin, 2 pour un croiseur…).
Chaque parti-pris porte ses avantages et ses inconvénients qui sont par ailleurs souvent très subjectifs. J'ai opté pour la troisième option en me disant que le nombre de positions possibles dépendant du type de bâtiment, ça fait une info de plus à stocker quelque part alors que dans un tableau on a l'info via la taille du tableau, et puis c'est tout facile de partir de n'importe quelle entrée dans un tableau et de boucler jusqu'à avoir tout parcouru… Je ne prétends pas que ce soit la meilleure technique, c'est celle qui m'est confortable, et puis ça fait encore du tableau et de la manipulation d'y-celui, or c'est le contexte du tuto ;)


pTabTypeBateau sera un tableau de matrices

function ajouteBateau(pTabTypeBateau:Array,pType:String):Boolean {
	var max:int=pTabTypeBateau.length;
        // un index au hasard
	var aa:int=Math.random()*max;
	var cpt:int=0;
	var fait:Boolean;
	do {
		// une matrice au hasard prise ds les tableau des matrices possibles
		fait=dispose(pTabTypeBateau[aa],pType);
		// index suivant
		aa=++aa%max;
        // boucle aussi longtemps que ce n'est pas fait et qu'il reste une orientation (matrice) à tester
	} while (!fait&&++cpt<max);
	return fait;
}
aa=++aa%max;

Cette ligne est équivalente à :

var temp:int=aa++;
aa=temp%max;

Elle permet de retourner à 0 si on atteint la valeur maximale, donc dans un tableau, de revenir au premier élément.


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

Voilà pour le gros de la mise en œuvre, la démo se contente d'ajouter dans une boucle une poignée de sous-marins. Le tableau tabSousMarins a été rempli à l'aide de la fonction pivote [swf]

	for (var i:int=0; i<6; i++) {
		trace("sm  "+ i+" posé "+ajouteBateau(tabSousMarins,"SM"));
	}

Je vous laisse bricoler les petites choses qui restent à votre goût ;)

Compter les points, gérer la partie

Pour gérer la partie il faut savoir, quand on découvre une case, si c'est une case d'eau ou non et quand c'est une case bâtiment, reconnaître le bâtiment touché et savoir s'il est seulement touché ou coulé (toutes ses cases sont découvertes).

POU :mrgreen: Il y a différentes façons de procéder, ce que je propose n'en est qu'une parmi tant d'autres, pas forcément “la meilleure”.

A la création du plateau

Pour reconnaitre le bâtiment dont dépend une case (quand on cliquera dessus) il faut avoir établi, en distribuant les bateaux sur la grille, une relation entre cases et bâtiment concerné.
J'ai choisi de stocker ces références dans un tableau (ben oui, encore !) que j'ai nommé _tabBatiments.

Quand un bâtiment est posé sur la grille (fonction marque) il faut ajouter une entrée à _tabBatiment, dans laquelle on stocke son type et le nombre de cases touchées (0 à la création).

_tabBatiments.push(new Array(pType,0));


Il faut aussi établir une relation entre chacune des cases et le bâtiment concerné.
Ce qui identifie le bâtiment c'est tout bonnement son numéro d'index dans le tableau _tabBatiments. Il suffit d'ajouter une variable au clip Mv_Case, je l'appelle idxBat et de l'initialiser à -1 (n'allons pas asssocier toutes les cases d'eau au premier bâtiment créé ;))

Ce qui nous donne (toujours dans marque) :

function marque(pTabZ:Array,pLig:int,pCol:int,pType:String):void {
	var i:int;
	[]
ICI-->	var idx:int=_tabBatiments.length;
	_tabBatiments.push(new Array(pType,0));
 
	[]
			if (pTabZ[i][k]>1) {
				//ce n'est pas une case d'eau : le clip contientra le visuel idoine
				clip_c.contient=pType;
				// dans le suivi du jeu on saura à quel batiment appartient la case
ICI -->				clip_c.idxBat=idx;
			}
 
[]


Pensez à réinitialiser _tabBatiments dans la fonction init ;)

Pendant le jeu

Pendant le jeu, ça veut dire quand le joueur clique une case, ça se passe donc dans la fonction de rappel qdClicCase.

Il faut (si ce n'est pas une case d'eau), mettre à jour le nombre de cases touchées et comparer au nombre de cases constituant le bateau entier.
Le nombre de cases touchées est dans _tabBatiments à l'index stocké dans idxBat de la case concernée :

_tabBatiments[c.idxBat][1]


Quant au nombre total de cases par type de bâtiment… A priori on ne le connait pas. Il faut donc le stocker quelque part. C'est là qu'on défouraille le tableau associatif, qu'on initialise une fois pour toute en début de code.

var _oNbCasesParType:Object=new Object  ;
_oNbCasesParType["SM"]=4;
_oNbCasesParType["PA"]=3;
_oNbCasesParType["C"]=2;


Voici donc la fonction complétée :

function qdClicCase(c:MovieClip) {
	var typeCase:String=c.contient;
	c.gotoAndStop(typeCase);
 
	if (typeCase!="O") {
		// le nombre de cases touchées est référencé ds le tableau des batiments index 1
                // mettre à jour et stocker ds variable
		var nbTouches:int=++_tabBatiments[c.idxBat][1];
                trace("Touché…")
		if (nbTouches>=_oNbCasesParType[typeCase]) {
			trace("Coulé !")
		}
	}
}


Voilà pour l'essentiel,à vous de terminer à votre guise.
Vous trouverez plus bas les sources très commentées (en tous cas je me suis appliquée ;)) de la démo en tête de chapitre.

Quelques mots à propos des sources

J'ai ajouté deux/trois sophistications au code illustré plus haut en rendant le nombre de lignes et de colonnes variable, en modifiant un peu le clip Mv_Case pour faire clignoter les bateaux non coulés en fin de partie, et en permettant aux bateaux de se toucher par les coins ou non (case à cocher).

Puisque le parti pris de développement est “tout ide” autant profiter des calques pour organiser le code et s'y retrouver plus facilement voici le contenu par calque :

• Matrices
Déclarations des matrices (zones d'encombrement) et des tableaux de matrices
Fonction initMatrices (pour gérer les coins touchants ou non)

• code Maitre
Les déclarations
Fonction fabGrille et subordonnées
Fonction ajouteBateaux et subordonnées

•Interface
Le code associé à l'interface : écouteurs, fonctions de rappel…

• gestion jeu
La fonction qdClicCase et subordonnées
• fonctions
Les quelques fonctions “utilitaires” :
pioche
pivote
detail - renvoie la chaine de “score” -



Les tableaux, quelle tuile !


Il semble que le tableau soit d'un recours fréquent quand on développe des jeux à base de tuiles (tiles), à tel point que Monsieur_SPI a fini par fabriquer un générateur de grille.

Je n'ai pas l'expérience de ce type de développement, je suis donc allée chercher confirmation auprès de sa longue expérience (:mrgreen:) il est formel :

“Par contre ce que je peux affirmer c'est que les tableaux, y compris les tableaux imbriqués, c'est indispensable quand on veut faire des jeux, encore plus quand c'est du jeu à base de tuiles qui est déjà la plupart du temps une imbrication de tableaux pour construire une grille”


Autre extrait (à propos de rapidité):

On peut donc en théorie toujours se passer des tableaux à deux ou plusieurs entrées en les remplaçant par des listes 3) qui sont beaucoup plus rapides à parcourir. Mais l'avantage du tableau à double entrée est sa structure qui permet de visuellement voir la grille quand on a le nez dans le code et aussi d'être plus souple lorsqu'on à besoin d'encapsuler des blocs d'informations de type différents (textes du personnage + caractéristiques du personnage + objets transportés + …)
1) on dit aussi hashes, voire table de hachage ou de mappage et je dois en oublier
2) on peut parler de clé d'entrée
3) Sachant qu'une liste c'est un tableau…