III. Le jeu▲
Le principe du jeu est bien simple, diriger Mario afin qu'il range des caisses à des emplacements bien précis dans un entrepôt. Nous utiliserons plus tard le terme emplacement cible pour décrire ces emplacements. Bien que simple comme principe, ce jeu peut vraiment donner des maux de tête pour qui veut résoudre des grilles difficiles.
Dans cette section, nous allons concevoir et programmer les méthodes qui nous permettront de voir le jeu sur l'écran de l'émulateur et de rendre le jeu fonctionnel. Vu que le jeu peut facilement être très dur à résoudre, nous fixerons un temps maximal pour la résolution d'une grille, au bout duquel la partie sera considérée comme perdue.
Nous allons utiliser une approche qui consiste à séparer le travail à faire en trois parties. Et chaque partie sera contenue dans une unité.
- La première contiendra les données du programme : déclaration globale de types, de variables ainsi
que des constantes.
- La deuxième concerne tout ce qui touche à l'affichage du jeu sur l'écran.
- La dernière contiendra le moteur du jeu. MIDlet Pascal impose qu'on utilise d'autres unités
que dans la partie Implementation d'une unité. Ces contraintes nous obligeront à décrire la boucle
principale du jeu dans cette unité afin de pouvoir utiliser les méthodes que nous allons définir.
III-A. Les données du programme▲
Nous avons précédemment convenu que nous assimilerions l'entrepôt à un labyrinthe, que nous allons représenter par une matrice. Chaque élément de la matrice contiendra un nombre qui représente le type d'élément qui se trouve à cet emplacement dans le labyrinthe. Les différents éléments que pourra contenir le labyrinthe sont : Mario, une caisse, une cible, une caisse bien placée, un mur ou le vide. En Pascal standard, nous aurions pu utiliser un type énuméré pour représenter les différents éléments de la grille (la matrice). Nous allons utiliser une astuce qui consiste à garder ces différentes valeurs dans les champs d'un type Record. Nous utiliserons aussi cette même approche pour représenter les différents sens de déplacements possible de Mario dans la grille.
Pour des raisons de performance, nous conserverons la position de Mario dans une variable globale et non dans la grille.
Voici le code de cette unité :
III-B. La vue du programme▲
En séparant le programme en trois parties : données - vue - traitement, on peut se focaliser sur une partie sans connaître le détail des autres. Ainsi, la vue de notre jeu va uniquement concerner l'affichage du labyrinthe avec ses différents éléments : Mario, caisse, ... On affichera le Timer qui permet de limiter le temps de résolution.
Les données du labyrinthe se trouvent dans la variable globale header.Grille. Chaque élément de cette grille est de type Integer dont la valeur est identique à un champ de la variable header.sEntite. Souvenez-vous que la position de Mario est définie dans la variable header.MarioPos. Il sera donc affiché après l'affichage de la grille.
L'affichage du labyrinthe va se faire de la façon suivante : on parcourt la grille, pour un élément donné on détermine l'entité se trouvant à cette position puis on affiche l'image de cette entité.
if
(header.grille[i, j]=sEntite.mur) then
drawImage(imgMur, x+debutX, y+debutY)
else
if
(header.grille[i, j]=sEntite.caisse) then
drawImage(imgCaisse, x+debutX, y+debutY)
else
if
(header.grille[i, j]=sEntite.OKCaisse) then
drawImage(imgOKCaisse, x+debutX, y+debutY)
else
if
(header.grille[i, j]=sEntite.cible) then
drawImage(imgCible, x+debutX, y+debutY);
Pour le Timer, on affichera l'image centrée d'une horloge suivie du temps écoulé sous la forme mm :ss.
Nous avons vu dans les précédents tutoriels que l'affichage à l'écran n'est mis à jour qu'après un appel de la méthode Repaint.
Voici le code de l'unité :
III-C. Le moteur du jeu▲
Nous allons maintenant rendre le jeu jouable et interactif en développant les règles du jeu, en gérant les collisions et en développant la boucle principale du jeu.
Penchons-nous tout d'abord sur les règles du jeu :
- Mario doit se déplacer dans le labyrinthe, et pousser des caisses
- Mario ne peut pousser une caisse que si l'espace après elle est vide ou est un emplacement cible
- Mario ne peut pas passer à travers un mur ou franchir les limites du labyrinthe
- Le jeu est gagné si toutes les caisses se trouvent sur les objectifs
- Nous ajoutons que la partie est perdue si le joueur décide d'abandonner la partie ou si le temps maximal de résolution est épuisé
Les règles ne sont pas très compliquées comme vous pouvez le voir. Mais nous allons être prudents pour ne pas oublier de détails.
- Avant de pousser une caisse, on doit considérer deux choses; si la caisse est déjà sur un emplacement cible (une caisse bien placée), nous devons indiquer dans la grille que l'emplacement est un emplacement cible après avoir poussé la caisse. Si la position que doit occuper la caisse après le déplacement est un emplacement cible, nous devons indiquer dans la grille que cet emplacement contient une caisse bien placée
- Avant de chercher à déplacer Mario, nous devons vérifier si le nouvel emplacement est vide ou est un emplacement cible. Un autre cas possible sera que cet emplacement contient une caisse que Mario peut pousser
La boucle principale du jeu se chargera de contrôler la pression des touches du clavier. Selon la touche appuyée, on décidera s'il faut chercher à déplacer Mario selon la direction voulue ou abandonner la partie. On vérifiera à chaque fois si le temps limite n'est pas épuisé ou si la partie n'a pas encore été gagnée.
Voici le code résumant tous ces discours :
III-D. Autres unités et assemblage du programme▲
III-D-1. Autre Unité▲
Vous vous souvenez sans doute que je vous avais demandé demander de télécharger deux types de fichiers : les images et les grilles du Mario Sokoban. On a déjà utilisé une partie des images pour l'affichage du jeu. Mais je ne vous ai pas encore dit à quoi vont nous servir ces grilles et comment les utiliser.
Ces grilles contiennent en fait la position des différents objets ou entités que contient le labyrinthe. Dans les codes que je vous ai donnés, on a supposé que la grille a déjà été initialisée avec les différentes entités au départ. Nous allons maintenant voir comment initialiser la grille du jeu.
Nous utilisons un format spécial pour enregistrer les données du labyrinthe. Regardons un exemple :
0022222222
0020333332
0020555032
002020003222
002020105302
002020005002
002025552002
002000000002
002222222222
</end
>
Chaque chiffre représente une entité (caisse, Mario, ...). Ce sont en fait ces mêmes chiffres que nous avions utilisés pour définir les constantes définissant ces entités dans les données du programme.
sEntite.vide := 0
;
sEntite.mario := 1
;
sEntite.mur := 2
;
sEntite.cible := 3
;
sEntite.caisse := 5
;
sEntite.okCaisse := 6
;
La position des chiffres est la même que pour les éléments de la matrice. Nous allons donc créer une nouvelle unité où nous mettrons les traitements permettant de charger une grille ainsi d'autres procédures permettant la gestion du Timer et la libération du buffer du clavier.
III-D-2. Assemblage du programme▲
Nous venons de terminer la partie la plus importante du projet. Il ne nous reste qu'à écrire le programme principal, compiler puis exécuter le projet.
program
sokoban;
uses
jeu, header, affichage, misc;
var
etat : integer
;
begin
misc.chargerGrille('grille1'
);
etat:=jeu.jouer;
end
.
Nous verrons dans la prochaine section comment ajouter des menus et un écran d'accueil à notre programme.
Vous pouvez télécharger la version actuelle du projet : sokoban.zip.