SOKOBAN (PARTIE 1) : PRÉPARONS NOTRE JEU

Créations

jicehel

il y a 6 ans

Durée: 30 minutes (à la louche)

Niveau: ancien débutant ayant suivi les premiers tutos

Prérequis

  • Avoir une Gamebuino META
  • Avoir fait l'atelier Installation de la Gamebuino META
  • Avoir suivi les tutoriels jusque au Casse-briques pour avoir les bases


RÉFLÉCHISSONS SUR CE QUE NOUS VOULONS FAIRE

Le jeu de Sokoban est un jeu où l’on est le gardien d’un entrepôt où se trouve, dans un petit labyrinthe dans lequel se trouve des caisses que l’on doit ranger à certains emplacements.

C’est un jeu classique, on a donc plein d’exemple de ce jeu. Selon moi, ce genre de jeu avec de principes simples est idéal pour commencer à essayer d’atteindre un objectif, mais bien sûr on peut aussi commencer un jeu sans modèle si l’on est créatif.

Dans un premier temps renseignons nous sur ce que l’on aura à faire :

Contrôler un personnage qui pourra se déplacer dans le labyrinthe s’il n’y a ni mur ni caisse pour le bloquer.

Le joueur pourra pousser une caisse s’il n’y a rien derrière cette caisse

Le niveau est terminé quand toutes les caisses sont sur les emplacements

Bon on va déjà se concentrer sur ces 3 éléments et voir les autres points sur lesquels on peut avoir à réfléchir.

Par exemple :

Est-ce que l’on dessine les niveaux ou est-ce que l’on utilise des sprites ?
Pour cet exemple, on va utiliser des sprites pour intégrer la couleur plus facilement. Pour les couleurs, on utilisera les couleurs de la palette de la META pour répondre à la charte de qualité.

Observons un jeu de Sokoban déjà existant :


On voit que le jeu peut être représenté par une grille de positions. Chaque case de cette grille pouvant contenir quelque chose : Un mur, le personnage, une caisse ou un point d’arrivée. On fera attention toutefois, une caisse peut être sur un point d’arrivée (c’est même le but du jeu) mais si on la déplace de nouveau, on doit pouvoir retrouver le point d’arrivée.


COMMENÇONS A ÉCRIRE NOTRE JEU


Nous avons vu dans le tuto « Hello World » la structure d’un programme et le principe des 2 fonctions : setup() et loop()

#include 

void setup() {
  gb.begin();
}

void loop() {
  while(!gb.update());
  gb.display.clear();

  // C'est ici que le plus gros du programme se déroule
 }

Que doit faire la boucle loop ?

  • Surveiller l’appui sur les touches
  • Faire respecter la logique du jeu
  • Afficher les graphismes
  • Jouer les bruitages


OK, sinon, il faut que l’on définisse notre niveau.

Pour ça on va définir sa taille en nombre de lignes et en nombre de colonnes à l’aide de la directive #define

En regardant sur Wikipedia sur le sujet du Sokoban, on apprend que l’on peut échanger de niveaux via des fichiers au format .xsb. Il y a même le premier niveau de la première version du jeu que nous allons utiliser pour notre programme :

    #####
    #   #
    #$  #
  ###  $##
  #  $ $ #
### # ## #   ######
#   # ## #####  ..#
# $  $          ..#
##### ### #@##  ..#
    #     #########
    #######


Comptons les lignes et les colonnes de ce niveau : On a 11 lignes et 19 colonnes

On écrira donc 

#define NB_LIGNES_NIVEAUX 11
#define NB_COLONNES_NIVEAUX 19


Légende :
# : mur
$ : caisse
. : destination 
* : caisse sur une zone de rangement 
@ : personnage 
+ : personnage sur une zone de rangement 

Tiens, on découvre donc sur Wikipédia comment ils ont résolu le problème du stockage de l’information quand on pousse une caisse sur une destination : ils remplacent le caractère tout simplement et si on retire la caisse, on remettra le « . » dans la case de la destination et « $ » sur la case contenant la caisse si ce n’est pas une zone de rangement (auquel cas il faudra mettre un « * ».

On remarque aussi le « + » et oui on avait oubli un cas : le personnage peut lui aussi se trouver sur une case de destination ponctuellement. Ils ont donc également ajouté le caractère « + » qui reprend les codes « @ » et « . »

Bien, bien, on avance. C’est une étape importante avant de coder. Bien penser à ce que l’on veut faire et lister les étapes et réfléchir à comment on va le faire.

Pour que notre niveau soit compatible avec les xsb, nous allons stocker les caractères dans un tableau de caractères que l’on va appeler « niveau » vu notre imagination débordante

On appelle tableau une variable composée de données de même type, stockée de manière contiguë en mémoire (les unes à la suite des autres).

donnée

donnée

donnée

...

donnée

donnée

donnée


Lorsque le tableau est composé de données de type simple, on parle de tableau monodimensionnel (ou vecteur). Sa syntaxe est la suivante :

type Nom_du_tableau [Nombre d'éléments]

type définit le type d'élément contenu dans le tableau définissant donc la taille d'une case du tableau en mémoire

Nom_du_tableau est le nom que l'on décide de donner au tableau, le nom du tableau suit les mêmes règles qu'un nom de variable

Nombre d'éléments est un nombre entier qui détermine le nombre de cases que le tableau doit comporter

Voici par exemple la définition d'un tableau de 8 éléments de type char :

char Tableau [8];

On peut également initialiser un tableau lors de sa définition ainsi:

type Nom_du_tableau [Taille1][Taille2]...[TailleN] = {a1, a2, ... aN};

Pour le tableau de 8 caractères, on pourrait écrire par exemple

char Tableau [8] = {'A',’B ', 'C', 'D', 'E', 'F', 'G', 'H'};

Cela est expliqué très joliment pour rappel dans le tuoriel: TapTap


Lorsqu’un tableau contient lui-même d'autres tableaux on parle alors de tableaux multidimensionnels (aussi matrice ou table). Il se définit de la manière suivante :

type Nom_du_tableau [a1][a2][a3] ... [aN]

Chaque élément entre crochets désigne le nombre d'éléments dans chaque dimension

Le nombre de dimensions n'est pas limité

Un tableau d'entiers positifs à deux dimensions (3 lignes, 4 colonnes) se définira avec la syntaxe suivante :

int Tableau [3][4];

On peut représenter un tel tableau de la manière suivante :


Tableau[0][0]

Tableau[0][1]

Tableau[0][2]

Tableau[0][3]

Tableau[1][0]

Tableau[1][1]

Tableau[1][2]

Tableau[1][3]

Tableau[2][0]

Tableau[2][1]

Tableau[2][2]

Tableau[2][3]


Si vous avez besoin d'un rappel sur les tableaux 2D (les matrices), je vous rappelle qu'elles sont expliquées dans le tutoriel Casse-briques


Voilà, on a la solution pour notre tableau pour notre niveau. Nous voulons un tableau qui va contenir un caractère correspondant au contenu de chaque case de notre niveau. Nous allons donc créer une matrice de caractère (type char). Nous l’appellerons niveau pour lui donner un nom parlant et ses dimensions seront NB_LIGNES_NIVEAUX x NB_COLONNES_NIVEAUX afin de pouvoir accéder à chacune des cases de notre niveau. On le déclarera donc ainsi :

char niveau[NB_LIGNES_NIVEAUX][NB_COLONNES_NIVEAUX] = 
{
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#',  ' ', ' ',  ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ',               ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#', '$', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', '#', '#', ' ', ' ', '$', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', ' ', ' ', '$', ' ', '$', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ '#', '#', '#', ' ', '#', ' ', '#', '#', ' ', '#', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', },
{ '#', ' ', ' ', ' ', '#', ' ', '#', '#', ' ', '#', '#', '#', '#', '#', ' ', ' ', '.', '.', '#', },
{ '#', ' ', '$', ' ', ' ', '$', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.', '.', '#', },
{ '#', '#', '#', '#', '#', ' ', '#', '#', '#', ' ', '#', '@', '#', '#', ' ', ' ', '.', '.', '#', },
{ ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', },
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', }
};


La première accolade indique que l’on initialise le tableau et chaque ligne en dessous correspond à un tableau avec le contenu de cette ligne dans notre tableau. On ferme ensuite notre tableau de lignes.

Attention n’oubliez pas que les tableaux commencent à l’indice 0, donc la première ligne à le numéro 0 et sa 3ème case à le numéro 2. Pur charger la variable Résultat avec le contenu de la 3ème colonne de la ligne 1, je devrais donc écrire :

Résultat = niveau[0][2] ;


A VOUS DE JOUER

Bon assez parlé, à vous de travailler. Faites un petit programme qui charge le tableau et affiche le contenu au format texte.

Attention, limitez le nombre de lignes affichées à 10 pour que ça tienne à l'écran, sinon ce ne sera pas joli...



SOLUTION

On utilise les constantes et un tableau (Voir TapTap)

On utilise les for (voir Attrape œufs) imbriqués (Voir Casse-briques)


#include <Gamebuino-Meta.h>

#define NB_LIGNES_NIVEAUX 11
#define NB_COLONNES_NIVEAUX 19

int NB_Lignes;

char niveau[NB_LIGNES_NIVEAUX][NB_COLONNES_NIVEAUX] = 
{
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', ' ', ' ', '#', '$', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', '#', '#', ' ', ' ', '$', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ ' ', ' ', '#', ' ', ' ', '$', ' ', '$', ' ', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', },
{ '#', '#', '#', ' ', '#', ' ', '#', '#', ' ', '#', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', },
{ '#', ' ', ' ', ' ', '#', ' ', '#', '#', ' ', '#', '#', '#', '#', '#', ' ', ' ', '.', '.', '#', },
{ '#', ' ', '$', ' ', ' ', '$', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.', '.', '#', },
{ '#', '#', '#', '#', '#', ' ', '#', '#', '#', ' ', '#', '@', '#', '#', ' ', ' ', '.', '.', '#', },
{ ' ', ' ', ' ', ' ', '#', ' ', ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', },
{ ' ', ' ', ' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', }
};

void setup() {
  gb.begin();
}

void loop() {
  while(!gb.update());
  gb.display.clear();

   // Astuce: avec print sans rien modifier, par défaut, on ne peut afficher que 10 lignes, pour l'affichage, on va donc brider le nombre de lignes à 10
   // On a de la chance on a moins de 20 colonnes. On n'a donc pas besoin de brider le nombre de colonnes à afficher dans notre programme de test
   NB_Lignes = NB_LIGNES_NIVEAUX ;
   if(NB_Lignes > 10) NB_Lignes = 10;

   // On affiche le contenu de chacune des cases
   for (int ligne=0;ligne<NB_Lignes;ligne++) {
     for (int colonne=0;colonne<NB_COLONNES_NIVEAUX;colonne++) {
       gb.display.printf("%c",niveau[ligne][colonne]);
      }
      gb.display.println("");
   }
}
Vous pouvez ensuite continuer sur la partie 2 du tutoriel: Partie 2

Voir la création

jicehel

NEW il y a 6 ans

Thanks for your votes. I'm on holidays atm but i'll write next step after. i think i'll make it with 8x8 blocs and scrolling, it's make 6 blocs height, if i pass to 10 pixels, it's only 4 blocs height, it's probably not enough to make a good idea of the level else i can do blocs of 6 pixels, it's let 8 blocs height and i could limit to 8 blocs width to let a part for informations (level, score, moves counter and maybe mini map of the level). I'll have to think and propose something when i'll be back for second part of this tuto  ;)

jicehel

NEW il y a 6 ans

Ouch j'ai corrigé le programme final, il y avait eu un problème de recopie quand je l'avais recopié dans cette création