Design Patterns #1 – Singleton

Deux mois se sont écoulés depuis la publication de la pseudo préface de ce dossier ambitieux sur les design patterns en ActionScript 2.0.

Voici enfin aujourd’hui la première partie, chargée d’introduire les modèles de création et d’exposer au grand jour le pattern Singleton.

Une petite mise en garde de rigueur avant de commencer :

Pour aborder cette série d’articles, il est indispensable que vous possédiez déjà des connaissances de base en programmation orientée objet.

Maîtriser la syntaxe ActionScript 2.0 sera biensûr un atout majeur qui vous permettra de décortiquer les exemples donnés tout au long des différents dossiers.

En dernier préambule, j’aimerai insister aussi sur le fait que la plupart des patterns prennent vraiment tout leur sens lorsqu’ils sont employés à l’intérieur de véritables applications ou plutôt … Laissez moi reformuler ma phrase : L’intérêt de chaque pattern devient évident et limpide lorque l’on s’est déjà heurté au problème dont il se propose d’être le remède. Pour résumer, c’est quand on commence à se confronter aux réelles difficultés liées au développement que ces élégantes abstractions deviennent limpides. Il est en effet souvent très difficile (voir impossible) de cultiver la robustesse, la sécurité, la durabilité ou l’évolutivité du code sans recourir à toutes ces bottes secrètes. Cet apprentissage (comme la plupart) peut biensûr se dérouler de façon totalement intuitive tout au long des expériences de chacun. Passer par la théorie c’est se donner des chances supplémentaires de voir clair vite et bien.

En général un modèle de conception se décompose en quatre parties :

- Le nom

- Le problème

- La solution

- Les conséquences

Ces caractéristiques portent des noms assez explicites pour m’éviter la rédaction inutile d’un glossaire pompeux et redondant. Si tel n’était pas le cas, je reste persuadé que les explications qui suivront vous aideront à mettre en lumière leur vocation respective.

La première famille de modèles que nous allons donc aborder est spécialisée dans la création, comprenez par là : Processus d’instanciation.

Il s’agit des modèles créateurs.

L’intérêt majeur de cette famille, c’est de privilégier l’abstraction d’un processus de création plutôt que de coder celui-ci en dur et se heurter par là même (tôt ou tard) à une inertie écrasante du système, que je qualifierai (avec humour) de fossilisation irréversible de l’application.

Pour prendre une image du monde réel (encore une), il est plus facile en tant que patron d’un hôtel d’employer un cuisinier pour répondre à l’attente de ses clients concernant leur appétit et goûts respectifs, plutôt que de laisser la possibilité à chacun de se restaurer en mettant à disposition locaux, ustensiles recettes et provisions de l’établissement. Il n’est pas besoin d’être un savant informaticien pour cerner les problèmes de logistique engendrés par la deuxième solution.

Offrir la reponsabilité à une entité de s’occuper de la création de chaque plat en s’adaptant à la demande de notre aimable clientèle ainsi qu’aux contraintes imposées par le système (stock, coûts …) nous permet non seulement d’occulter la fabrication des plats mais aussi d’interchanger le détenteur de cette responsabilité selon nos besoins ou exigences (grand chef cusinier pour les soirées V.I.P contre équipe d’apprentis pour les grands rushs de midi).

Pour résumer, le principe fédérateur de cette famille de patterns, c’est d’encapsuler la connaissance des classes concrètes (recette de chaque plat) que le système (hôtel) utilise et masquer le déroulement et le paramétrage d’une instanciation (fabrication d’un plat).

Pour commencer, voici la liste des patterns appartenant à cette famille de modèles (modèles créateurs) :

- Fabrique abstraite

- Monteur

- Fabrication

- Prototype

- Singleton

Nous allons commencer cette étude par un patterns très utilisé, j’ai nommé le Singleton.

Il est souvent important au sein d’une application de pouvoir s’assurer premièrement de l’unicité d’un objet, deuxièmement de son accessibilité à tout moment par les autres objets de l’application sans avoir recours à des références multiples.

Comme un exemple est souvent beaucoup plus parlant qu’une longue explication technnique, nous allons illustrer l’utilisation de ce pattern en nous appuyant sur une situation concréte. Pour les besoins de cette démonstration nous allons créer une classe abstraite Connection dont le rôle sera de créer une connexion socket entre le client Flash et un serveur unique prédéfini.

Vous conviendrez au vu de l’énoncé qu’il serait maladroit voir catastrophique d’avoir des instances multiples de Connection.

En utilisant le pattern Singleton sur notre classe Connection nous garantissons le fait qu’il ne peut y avoir qu’une seule instance de connexion dans notre application.

Cerise sur le gâteau ce pattern fournira un point d’accès unique et global à tous les éléments de notre application nécessitant les services de celle-ci.

// Classe fictive pour illustrer le déploiement du pattern Singleton

class Connection
{
 	// Membre statique protected (faute de pouvoir être placé en privé)
 	// servant à stocker une référence de notre connection, .
 	private static var _oInstance:Connection;

 	// Le constructeur est placé en protected.
 	// Ceci garantit déjà l'impossibilité d'instancier directement une connection
 	// sans passer par le point d'accès.
     private function Connection()
 	{
  		trace("Constructeur appelé");
  	}

 	// L'unique point d'accès qui empêche toute duplication.
     public static function get instance() : Connection
     {
  		// Pour créer une connection (instance de Connection)
  		// On s'assure que celle-ci n'existe pas déjà.
  		if (_oInstance == undefined) _oInstance = new Connection();
  		// Dans tous les cas, on retourne une référence vers l'objet
  		// à celui qui en a fait la demande.
  		return _oInstance;
      }

 	// On prévoit une méthode pour détruire notre instance
 	// en cas de besoin de réinitialisation
 	// ou de libération de la mémoire.
 	public static function release() : Void
 	{
  		// Si l'instance existe on la détruit.
  		if (_oInstance)
  		{
   			trace("instance détruite");
   			delete _oInstance;
   		}
  	}

 	// Maintenant que le pattern est déployé voici le coeur
 	// de notre objet exposé avec ses différentes méthodes :
 	public function connect() : Void {}
 	public function disconnect() : Void {}
}

Note : Le fait que le player Flash ne gére pas le muti-threading nous protège des soucis d’initialisation différée comme dans certains langages comme C++.

La question légitime que l’on est maintenant en droit de se poser face au modéle Singleton c’est pourquoi ne pas passer en statique toutes les méthodes comme je le fais avec la SoundFactory ?

Les deux seuls avantages en ActionScript à retirer sont :

- D’avoir plusieurs instances d’une classe mère par extensions multiples. – D’avoir la possibilité en cours de développement de modifier le design parceque l’on s’aperçoit que l’on nécessite finalement plusieurs instances de la même classe.

Voici pour finir une fiche technnique pour résumer les caractéristiques du modèle Singleton :

Nom : Singleton

Le problème : S’assurer de l’unicité de l’instance d’une classe

La solution :

- Définir un membre privé static pour stocker l’instance.

- Définir une méthode publique pour accéder à celle-ci.

- Mettre le constructeur en protected (private en AS 2.0).

Les conséquences :

- Contrôle d’accès à l’instance régulé.

- Changement de stratégie possible à tout moment.

Et pour accompagner ce résumé voici une implémentation générique du pattern Singleton en ActionScript 2.0 :

class Singleton
{
 	private static var _oInstance:Singleton;

     private function Singleton() {}

     public static function getInstance() : Singleton
     {
  		if (_oInstance == undefined) _oInstance = new Singleton();
  		return _oInstance;
      }

 	public static function release() : Void
 	{
  		if (_oInstance) delete _oInstance;
  	}
}

Le prochain article traitera du modèle fabrique abstraite. En attendant n’hésitez pas à me faire partager vos éventuelles questions ou commentaires.

Les fichiers source sont disponibles ici.

SoundFactory

J’ai eu besoin pour Atomic Boarder d’ajouter du son et d’avoir une facade qui encapsule toute la gestion sonore.

Je vous propose de partager le noyau de cette classe qui peut servir sur n’importe quel projet (application ou jeu) et peut-être permettre à certains d’entre vous de comprendre les mécanismes et avantages offerts par un design de ce type.

Comme je l’avais déjà illustré à l’époque, l’objectif est simple et le principe efficace.
Il s’agit de créer une classe avec des membres statiques qui s’occupera d’instancier, de stocker et gérer un parc d’objets.

Au chapitre des avantages :
- Le fait de gérer l’instanciation et la destruction des objets au même endroit et d’offrir pour unique référence aux classes qui les utilisent un numéro d’id permet de bénéficier d’une propreté maximale. Les références sont virtuelles et coupées de l’objet lui-même au niveau du bytecode
- Offrir une une interface unique et bien encapsulée pour gérer un même type d’objets nous permet de pouvoir modifier à n’importe quel moment le fonctionnement interne de celle-ci pour ajouter ou enlever des fonctionnalités.
- Permettre tout ce qui est batch process, appliquer une même méthode à tous les objets du même type (ex : muteAllSounds() ). Réaliser des tris, des sous-groupes …
- Dernier point, ce type de classe peut être une manière habile pour éviter l’héritage abusif dans bien des cas. Rien de plus facile par exemple d’ajouter une méthode fade à un notre objet Sound en implémentant une méthode statique supplémentaire (à notre facade) du nom de makeFade qui prendra pour paramètre le numéro d’id du son concerné.

Assez parlé, voici une illustration basique :

class com.petepx.SoundFactory
{
 	private static var _bIsInitialized:Boolean = false;
 	private static var _oSounds:Object = new Object();
 	private static var _nCount:Number = 1;
 	private static var _oContainer:MovieClip;
 	private static var _bIsOn:Boolean = true;
 	private static var _noSound:Sound = _makeSound("", 0);

 	private function SoundFactory() {}

 	private static function _init(mcTarget:MovieClip) : Void
 	{
  		mcTarget == undefined ?_oContainer =
  			_level0.createEmptyMovieClip("__snd__", 65536) : _oContainer = mcTarget;
  		_bIsInitialized = true;
  	}

 	private static function _makeSound(linkageID:String, soundID:Number) : Sound
 	{
  		var container = _oContainer.createEmptyMovieClip("sound" + soundID, soundID);
  		var sound = new Sound(container);
  		sound.attachSound(linkageID);
  		return sound;
  	}

 	/* -------------------- */

 	public static function init(mcTarget:MovieClip) : Void { _init(mcTarget); }

 	public static function getSound(id:Number) : Sound
 	{
  		return _bIsOn ? _oSounds[id] : _noSound;
  	}

 	public static function removeSound(id:Number) : Void
 	{
  		_oContainer["sound" + id ].removeMovieClip();
  		delete _oSounds[id];
  	}

 	public static function addSound(linkageID:String) : Number
 	{
  		if (!_bIsInitialized) _init();
  		_oSounds[_nCount] = _makeSound(linkageID, _nCount);
  		_nCount++;
  		return _nCount - 1;
  	}

 	public static function addSounds(a:Array) : Void
 	{
  		for (var c=0; c< a.length; c++) addSound(a[c]);
  	}

 	public static function toggleOnOff() : Void { _bIsOn ? goOff() : goOn(); }
 	public static function get isOn() : Boolean { return _bIsOn; }
 	public static function goOn() : Void { _bIsOn = true; }
 	public static function goOff() : Void
 	{
  		_bIsOn = false;
  		stopAllSounds();
  	}
}
// Exemple d'utilisation
import com.petepx.SoundFactory;

// initialisation, purement optionnelle
SoundFactory.init(myMcLib);

// Ajouter une liste de sons en fournissant leurs linkages
SoundFactory.addSounds(['boom', 'crack', 'clang', 'dring']);

// On joue le son n°3 et on l'efface
SoundFactory.getSound(3).start();
SoundFactory.removeSound(3);

// On ajoute un son en récupérant son numéro d'id
var n:Number = SoundFactory.addSound('bong');

// On utilise l'id stocké pour pouvoir jouer le son associé
SoundFactory.getSound(n).start();

// On coupe le son de la factory
SoundFactory.goOff();

Joey Lott AS2.0 video master-class

Joey Lott vient de publier chez lynda.com un cd-rom pédagogique intitulé : Learning ActionScript 2.0 in Macromedia Flash MX 2004.

joeyas2.jpg

Connaissant les talents didactiques de Joey, je ne peux que recommander ce cd les yeux fermés à tous ceux pour qui la langue anglaise n’est pas un handicap et qui voudraient explorer les arcanes de l’ActionScript 2.0 sous la houlette de ce fervent maïeuticien.

Pour l’anecdote, j’ai cédé les droits à Joey d’un de mes titres le temple invisible pour la réalisation de ce cd. Je devrais recevoir du coup sous peu une copie et vous faire un compte-rendu sur la pertinence de son contenu.

Ce week-end je vais préparer une partie de l’interview consacrée à Joey Lott, n’hésitez pas à me glisser dans les commentaires certaines questions que vous aimeriez voir figurer.

isométrie et z-sorting

En m’intéressant de près à l’isométrie, je me suis aperçu que les méthodes pronées par Jobe Makar (Flash MX Games Design Demystified) et consorts au sujet du z-sorting n’étaient pas du tout adaptées pour un moteur 3D isométrique dont l’objectif final serait d’afficher des animations.

Je vous propose de partager aujourd’hui la solution que j’utilise. Elle vous permettra de gérer les profondeurs sans bug d’affichage dans un moteur 3d isométrique animé.

Le concept récurrent pour gérer la profondeur d’un objet dans un tileworld isométrique est d’assigner un z-sorting incrémenté de gauche à droite sur l’axe des x, puis de bas en haut sur l’axe des y comme indiqué sur le schéma ci-dessous :

Ce système fonctionne à merveille tant que l’on se contente du fait que chaque objet se trouvant à une position (x,y) se déplacera sur une case voisine d’un seul jet, sans interpolation de mouvement, autrement dit sans se trouver à un instant t à cheval entre deux cases.
Pourquoi me direz vous ?

Considérons l’exemple ci-dessous :

Un personnage se trouvant à la position 1,1 et voulant se rendre à la position 1,0 va devoir choisir son camp. S’il garde 4 comme profondeur le temps de l’animation pour se rendre sur la case destination, il va passer devant l’objet qui se trouve en 2,0.

S’il choisit de prendre directement sa profondeur d’arrivée (1), il sera le temps de son trajet affiché derrière l’objet se trouvant en 2,0.

On est confronté au même probléme dans le sens inverse comme vous pouvez le remarquer :

La plupart des moteurs utilisant ce système réussissent à tricher en jouant sur la taille des objets à l’intérieur des cases et sur l’instant ou la valeur z-sorting de l’objet en mouvement est modifiée. Mais ces solutions au bout du compte ne sont vraiment pas satisfaisantes pour gérer l’affichage de façon propre et logique.

D’où l’idée d’opter pour une organisation comme celle-ci, avec un z-sorting incrémenté en diagonales :

Cette fois-ci les animations sur l’axe des y peuvent-être gérées sans bug d’affichage :

Mais les esprits judicieux auront remarqué que le probléme est maintenant répercuté sur l’axe des x :

Sans plus attendre, voici la solution finale, le même tableau mais avec des intervalles en dizaine :

Hum … Quoi de mieux me direz-vous ?
L’idée est d’incrémenter de 15 la profondeur d’un personnage voulant se déplacer vers la droite sur l’axe des x (50 dans l’exemple devient 65) et de lui donner à la fin de son parcours la profondeur de la case d’arrivée (80 dans l’exemple ci-dessous) :

Même chose dans l’autre sens, on enléve 15 à la profondeur d’un personnage voulant se déplacer vers la gauche sur l’axe des x (50 devenant 35 dans le cas précis) et on lui attribue à la fin de son trajet la profondeur de la case destination (il passe donc de 35 à 20 en profondeur une fois arrivé) :

Dernier point, quelle méthode utiliser pour calculer la profondeur d’un objet en connaissant la largeur et la hauteur de la grille ainsi que sa position x et y ?
C’était le sujet d’un précédent thread, et je remercie Didier d’avoir trouvé une solution très élégante et rapide pour gérer cette problématique.
Voici le code que j’utilise :

 private function _sum(n:Number) : Number {

         return (n * (n + 1) / 2);
    }
 
   public function calculDepth(x:Number, y:Number) {

         var r:Number = _sum(x + y) + x + 1;
         r = ((x + y) > (grid.x-1)) ? r -= _sum(x + y - (grid.x-1)) : r;
         r = ((x + y) > grid.y) ? r -= _sum(x + y - grid.y) : r;
         return (r*10);
    }

Voila, j’espère que le concept que j’ai développé et utilise couramment pourra contribuer à remplir un peu le vide auquel je me suis confronté lors de mes investigations sur le sujet.

Je tiens à remercier tout particulièrement Joan pour ses idées et Didier pour son algorithme de calcul remarquable ainsi que Jean-Louis pour la conversion en AS.
Comme d’habitude, vos questions, commentaires ou critiques sont les bienvenue.

tween

Je profite de ce début de semaine pour vous présenter l’une des nouvelles classes MX2004 : Tween.as

Aucune documentation n’est disponible actuellement, j’ai donc pris mon courage à deux mains pour décortiquer le fichier .as et en extraire les mécanismes qui au final sont somme toute assez simples.

Cette classe permet de réaliser une interpolation sur la propriété d’un objet de son choix.

Dans l’exemple ci-dessous j’ai pris un exemple parlant pour vous illustrer la marche à suivre :

import mx.transitions.Tween;
 
voiture = {};
voiture.vitesse = 100;
var test:Tween = new Tween (voiture, "vitesse", null, 0, 200, 60);
 
this.onEnterFrame = function() {
 	trace(voiture.vitesse);
}

Les paramètres du constructeur de la classe Tween sont organisés comme suit :
- L’objet cible : voiture
- La propriété à interpoler sous forme String : « vitesse »
- Une easing equation : null (par défaut : c*t/d + b ).
- La valeur de départ de la propriété : 0 Si j’avais voulu conserver la valeur initiale de la propriété vitesse de l’objet voiture, j’aurais biensûr remplacé 0 par voiture.vitesse
- La valeur finale : 200
- La durée en images/secondes : 60
Il est possible d’ajouter un paramètre optionnel, un booléen qui définira si la durée est spécifiée en images/secondes ou en secondes.
Voici la même interpolation sur soixante secondes :

var test:Tween = new Tween (voiture, "vitesse", null, 0, 200, 60, true);

Des messages sont diffusés par défaut pour d’éventuels callbacks :
onMotionStarted, onMotionStopped, onMotionResumed, onMotionChanged, onMotionLooped et onMotionFinished.
Les noms parlent d’eux-même je pense.
Voici un exemple pratique pour illustrer leur utilisation :

this.test.onMotionFinished = function(obj) {
 	trace("La voiture roule à 200kms/h");
}

Pour finir, une panoplie de méthodes est aussi disponible vous permettant de contrôler votre interpolation comme si elle figurait sur une timeline virtuelle : start(), stop(), resume(), rewind(), forward(), yoyo() … La aussi, les noms parlent d’eux même tout comme la liste de propriétés publiques mises à notre disposition : time, duration, FPS, position et finish.

Un dernier exemple en images pour conclure : Une interpolation de mouvement sur un MovieClip moto utilsant une équation easeInOut.
Dés fois un dessin vaut mieux qu’une longue explication :

var test:Tween = new Tween (moto, "_x", mx.transitions.easing.Strong.easeInOut, moto._x, 600, 60, false);

Note : Les équations d’easing se trouvent dans le répertoire mx.transitions.easing.Strong. Libre à vous de rajouter le set complet AS2.0 de Robert Penner ou de créer les votre avec l’outil développé par Timothee Groleau.

Voilà, j’espère que cette présentation rapide vous aura permis de dégrossir le sujet. Je ne suis volontairement pas rentré dans les détails laissant à chacun le soin d’expérimenter lui-même les rouages de ce superbe outil.

gPathFinder 2.0

Avec la bénédiction de Grant Skinner (« I had a quick look the AS2 pathfinder, and it looks good … »), voici GPathFinder converti en AS2.0 par mes soins.

Je l’ai transformé en méthode statique unique avec tous les paramètres de configuration en entrée. Au final, on obtient un gain de vitesse hallucinant, (deux à trois fois plus rapide) ainsi qu’un code mieux encapsulé.

Vous pouvez l’essayer ici et récupérer le code source ci-dessous.

[kml_flashembed movie="http://www.tweenpix.net/files/GPathFinder.swf" width="500" height="400"/]

Voici le code :

/*** GPathFinder class by Grant Skinner, http://gskinner.com/ ***/
// converted in AS2.0 by Francis Bourre, http://www.tweenpix.net

 
class GPathFinder {
 
	public static function findPath(map:Array, start:Object, end:Object, depth:Number, level:Number) : Array {

		var $paths:Array = [];
		// get paths based on level:
		$paths[0] = $findPath(map, start, end, 1, false, depth);
		if ( level > 0) {
			$paths[1] = $findPath(map, start, end, 0, false, depth);
		}

		if ( level > 1) {
			$paths[2] = $findPath(map, end, start, 1, true, depth);
		}

		if ( level > 2) {
			$paths[3] = $findPath(map, end, start, 0, true, depth);
		}

		// sort on weight:
		$paths.sort($pathSort);

		// set path and return it;
		var path:Array = $paths[0];
		return path;
	}

	private static function $pathSort (fVal1:Object, fVal2:Object) : Number {

		if (fVal1.weight < fVal2.weight) {
			return -1;
		}

		else if (fVal1.weight > fVal2.weight) {
			return 1;
		}

		return 0;
	}

	/*** PathFinder.$findPath
	searches for a path from start point to end point.
	dir is a boolean, and specifies the search direction (true = favour x, false = favour y)
	rev indicates whether the path should be reversed after it is found.
	***/
	private static function $findPath(map:Array, fStart:Object, fEnd:Object, fDir:Number, fRev:Boolean, depth:Number) : Object {

 		// set up:
 		var curx:Number = fStart.x;
 		var cury:Number = fStart.y;
 		var pathWeight:Number = 0;
 		var pathx:Array = [];
 		var pathy:Array = [];
 		var endx:Number = fEnd.x;
 		var endy:Number = fEnd.y;

 		// try to find a path:

 		while(true) {
  			//
  			if ((curx == pathx[pathWeight-4])&&(cury == pathy[pathWeight-4])) {
   				pathWeight=10000;
   				break;
   			}

  			var oldx:Number = pathx[pathWeight-1];
  			var oldy:Number = pathy[pathWeight-1];
  			pathx.push(curx);
  			pathy.push(cury);
  			pathWeight++;
  			if (endx == curx && endy == cury) break;
  			if (pathWeight > depth) {
   				pathWeight = 10000;
   				break;
   			}

  			if (fDir) { // favour x movements
   				// move towards end:
   				if ((curx < endx)&&(oldx != curx+1)&&(map[curx+1][cury])) {
    					curx++;
    					continue;
    				}

   				if ((curx > endx)&&(oldx != curx-1)&&(map[curx-1][cury])) {
    					curx--; continue;
    				}

   				if ((cury < endy)&&(oldy != cury+1)&&(map[curx][cury+1])) {
    					cury++;
    					continue;
    				}

   				if ((cury > endy)&&(oldy != cury-1)&&(map[curx][cury-1])) {
    					cury--;
    					continue;
    				}

   				// can't move towards try lateral:
   				if (curx == endx) {
    					if ((oldx != curx+1)&&(map[curx+1][cury])) {
     						curx++;
     						continue;
     					}

    					if ((oldx != curx-1)&&(map[curx-1][cury])) {
     						curx--;
     						continue;
     					}
    				} else if (cury == endy) {

    					if ((oldy != cury+1)&&(map[curx][cury+1])) {
     						cury++;
     						continue;
     					}
    					if ((oldy != cury-1)&&(map[curx][cury-1])) {
     						cury--;
     						continue;
     					}

    				}
   				// can't move lateral, try away:
   				if ((curx > endx)&&(oldx != curx+1)&&(map[curx+1][cury])) {
    					curx++;
    					continue;
    				}

   				else if ((oldx != curx-1)&&(map[curx-1][cury])) {
    					curx--;
    					continue;
    				}

   				if ((cury > endy)&&(oldy != cury+1)&&(map[curx][cury+1])) {
    					cury++;
    					continue;
    				}

   				else if ((oldy != cury-1)&&(map[curx][cury-1])) {
    					cury--;
    					continue;
    				}

   			} else { // favour y movements
   				// move towards end:
   				if ((cury < endy)&&(oldy != cury+1)&&(map[curx][cury+1])) {
    					cury++;
    					continue;
    				}

   				if ((cury > endy)&&(oldy != cury-1)&&(map[curx][cury-1])) {
    					cury--;
    					continue;
    				}

   				if ((curx < endx)&&(oldx != curx+1)&&(map[curx+1][cury])) {
    					curx++;
    					continue;
    				}

   				if ((curx > endx)&&(oldx != curx-1)&&(map[curx-1][cury])) {
    					curx--;
    					continue;
    				}

   				// can't move towards try lateral:
   				if (cury == endy) {
    					if ((oldy != cury-1)&&(map[curx][cury-1])) {
     						cury--;
     						continue;
     					}

    					if ((oldy != cury+1)&&(map[curx][cury+1])) {
     						cury++;
     						continue;
     					}
    				} else if (curx == endx) {

    					if ((oldx != curx-1)&&(map[curx-1][cury])) {
     						curx--;
     						continue;
     					}
    					if ((oldx != curx+1)&&(map[curx+1][cury])) {
     						curx++;
     						continue;
     					}

    				}
   				// can't move laterally, try away:
   				if ((cury < endy)&&(oldy != cury-1)&&(map[curx][cury-1])) {
    					cury--;
    					continue;
    				}

   				else if ((oldy != cury+1)&&(map[curx][cury+1])) {
    					cury++;
    					continue;
    				}

   				if ((curx < endx)&&(oldx != curx-1)&&(map[curx-1][cury])) {
    					curx--;
    					continue;
    				}

   				else if ((oldx != curx+1)&&(map[curx+1][cury])) {
    					curx++;
    					continue;
    				}

   			} // end if

  			// darn, we're stuck.
  			pathWeight = 10000;
  			break;
  		} // end while
 		// flip the path if it is reversed, and if we were successful

 		if (fRev && pathWeight < 10000) {
  			pathx.reverse();
  			pathy.reverse();
  		}

 		return {xPath:pathx,yPath:pathy,weight:pathWeight,$dir:fDir};
 	}
 
}

Dans les jours qui viennent, je vous ferai un comparatif illustré de trois pathfinders Flash testés pour vous.

A suivre …