Aller au contenu principal

Dessiner le Labyrinthe

Objectif

L'objectif de cette séance est de comprendre et d'appliquer les structures de données pour dessiner le labyrinthe de notre jeu Pacman.

Nous allons apprendre :

  • à représenter le labyrinthe sous forme de tableau (array) en JavaScript,
  • et à afficher le tableau du labyrinthe à l'écran avec Expo.

Notions théoriques

Pour représenter notre labyrinthe, nous allons utiliser un tableau à 2 dimensions.

Chaque élément du tableau correspondra à une case du labyrinthe : un mur ou un chemin.

Les tableaux en Javascript

Les tableaux en JavaScript sont des collections ordonnées de valeurs qui peuvent être de différents types.

Les tableaux en JavaScript :

  • sont flexibles,
  • peuvent changer de taille dynamiquement,
  • et offrent des méthodes pour effectuer des opérations telles que l'itération et la transformation des éléments.

Exemple d'un tableau simple :

let fruits = ["pomme", "banane", "cerise"];

Exemple d'un tableau de tableaux (aussi appelé tableau multidimensionnel) :

let grille = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];

astuce

En JavaScript, un tableau à 2 dimensions peut être vu comme un tableau de tableaux.

Voici un exemple de représentation d'un petit labyrinthe :

const labyrinthe = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 1, 0, 1],
[1, 0, 1, 0, 1],
[1, 1, 1, 1, 1]
];
  • Dans ce tableau, 1 représente un mur et 0 un chemin.
  • Ce tableau représente un labyrinthe avec un contour de murs et un chemin à l'intérieur.
let ou const

En JavaScript, let et const sont des mots-clés utilisés pour déclarer des variables.

  • let permet de déclarer des variables dont la valeur peut changer,
  • tandis que const est utilisé pour des variables dont la valeur ne peut pas changer.

Exemple pratique

Pour transformer notre tableau en un véritable labyrinthe visuel, nous pouvons utiliser le composant View de React Native.

  • Voici un exemple de fonction pour dessiner une case :

    function Case(props) {
    const estMur = props.estMur;
    const styleCase = {
    width: 40,
    height: 40,
    backgroundColor: estMur ? 'black' : 'blue',
    };

    return <View style={styleCase} />;
    }
    Explications des lignes de la fonction Case.

    • Déclaration de la fonction : Case(props) déclare une fonction qui prend un objet props en paramètre. props est un objet standard en React qui permet de passer des données et des configurations aux composants.

    • Extraction de la propriété estMur : La ligne const estMur = props.estMur; extrait la propriété estMur de l'objet props. Cette propriété est utilisée pour déterminer si la case à dessiner est un mur ou non.

    • Définition du style de la case : La constante styleCase est un objet qui définit les styles CSS pour le composant View qui sera retourné. Les styles définissent la largeur et la hauteur de la case (width et height sont tous deux de 40 pixels). La couleur de fond (backgroundColor) est conditionnelle : si estMur est vrai (c'est-à-dire si la case représente un mur), la couleur de fond sera noire ('black'), sinon elle sera bleue ('blue').

    • Rendu du composant : La fonction retourne un élément View avec le style appliqué via l'attribut style. En React Native, View est un composant conteneur qui peut être utilisé pour envelopper d'autres composants. Il est similaire à une div en HTML.


    En résumé, la fonction Case retourne une case de 40 pixels de côté qui sera soit noire si elle représente un mur, soit bleue sinon.

  • Voici un exemple de fonction pour dessiner toutes les cases, en faisant appel à la fonction Case() pour chaque case du labyrinthe :

    function Labyrinthe(props) {
    const labyrinthe = props.labyrinthe;
    return (
    <View style={{ flexDirection: 'column' }}>
    {labyrinthe.map(function(ligne, indexLigne) {
    return (
    <View key={indexLigne} style={{ flexDirection: 'row' }}>
    {ligne.map(function(caseLabyrinthe, indexColonne) {
    return (
    <Case
    key={indexColonne}
    estMur={caseLabyrinthe === 1}
    />
    );
    })}
    </View>
    );
    })}
    </View>
    );
    }
    Explications des lignes de la fonction Labyrinthe.

    La fonction Labyrinthe prend un seul argument, props, qui est un objet contenant toutes les propriétés (props) passées au composant Labyrinthe lorsqu'il est utilisé dans un autre composant.

    Voici une explication détaillée de ce que fait chaque partie du code :

    • Extraction de labyrinthe depuis props: La première ligne à l'intérieur de la fonction extrait la propriété labyrinthe de l'objet props. Cette propriété labyrinthe est supposée être un tableau à deux dimensions représentant le labyrinthe, où chaque sous-tableau représente une ligne du labyrinthe.

    • Rendu du composant View racine: Le composant retourne un élément View de React Native, qui est l'équivalent d'une div dans le web. Cet élément View est configuré pour disposer ses enfants en colonne avec le style flexDirection: 'column'.

    • Itération sur les lignes du labyrinthe: À l'intérieur de cet élément View, le code itère sur chaque ligne du labyrinthe en utilisant la méthode map. Pour chaque ligne, une fonction est appelée qui prend la ligne actuelle et son index (indexLigne) en tant qu'arguments.

    • Rendu des lignes du labyrinthe: Pour chaque ligne, un autre élément View est retourné, qui représente une ligne du labyrinthe. Cet élément View est configuré pour disposer ses enfants en ligne avec le style flexDirection: 'row'. La clé (key) est utilisée pour aider React à identifier quels éléments ont changé, ont été ajoutés ou sont restés les mêmes lors des mises à jour de la liste.

    • Itération sur les cases de chaque ligne: À l'intérieur de chaque ligne, une autre itération est faite sur chaque case de cette ligne avec la méthode map. Pour chaque case, une fonction est appelée qui prend la valeur de la case (caseLabyrinthe) et l'index de la colonne (indexColonne) en tant qu'arguments.

    • Rendu des cases individuelles: Pour chaque case de la ligne, le composant Case est retourné. Ce composant est probablement un autre composant personnalisé qui sait comment dessiner une case individuelle du labyrinthe. Il reçoit une prop estMur qui est un booléen déterminé par la condition caseLabyrinthe === 1. Cela suggère que dans le tableau labyrinthe, une valeur de 1 représente un mur et toute autre valeur représente un espace vide. Le key est également utilisé ici pour les mêmes raisons d'optimisation des performances.


    En résumé, la fonction Labyrinthe prend un tableau à deux dimensions représentant un labyrinthe et le transforme en une représentation visuelle à l'aide de composants React Native. Chaque ligne est rendue comme une série de View en ligne, et chaque case est rendue par le composant Case, qui change d'apparence en fonction de savoir si elle est un mur ou un espace vide.

Dans ce code, nous définissons 2 fonctions :

  • Case pour dessiner une case individuelle,
  • et Labyrinthe pour dessiner le labyrinthe entier en utilisant Case.

Elements de code à connaître

  • Array.map(): Cette méthode crée un nouveau tableau avec les résultats de l'appel d'une fonction fournie sur chaque élément du tableau appelant.
  • React Native Views: Les View sont des conteneurs qui supportent le style et le layout. Ils sont l'équivalent des div en HTML.

Test de mémorisation/compréhension


Que représente le `0` dans notre tableau de labyrinthe ?


Quelle méthode JavaScript permet de parcourir un tableau et de retourner un nouveau tableau ?


En React Native, quel composant utilise-t-on principalement pour créer un conteneur ?


Quelle propriété de style détermine la direction des enfants d'un `View` en React Native ?


Pourquoi utilisons-nous un tableau à deux dimensions pour représenter un labyrinthe ?



TP : Dessiner le labyrinthe

Votre mission

Votre mission consiste à dessiner un labyrinthe (avec des chemins et des murs).

Etapes

Voici un exemple de code qui permet dessiner un labyrinthe :

  1. Créez un nouveau fichier GenererationLabyrinthe.js à la racine de votre projet (à côté de App.js).

    // GenererationLabyrinthe.js

  2. Ajoutez la fonction genererLabyrinthe(rows, cols) dans le fichier GenererationLabyrinthe.js :

    function genererLabyrinthe(rows, cols) {
    const labyrinthe = [
    ...
    ];

    return labyrinthe;
    }
  3. Ajoutez le tableau labyrinthe fourni ci-dessous, dans votre fichier GenererationLabyrinthe.js, pour représenter le labyrinthe de base :

    const labyrinthe = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1], // Mur supérieur
    [1, 0, 0, 0, 0, 0, 0, 0, 1], // Chemin horizontal
    [1, 0, 1, 1, 1, 1, 1, 0, 1], // 2 passages sur la ligne 3
    [1, 0, 0, 0, 0, 0, 0, 0, 1], // Chemin horizontal
    [1, 1, 1, 1, 0, 1, 1, 1, 1], // 1 passage sur la ligne 5
    [1, 0, 0, 0, 0, 0, 0, 0, 1], // Chemin horizontal
    [1, 1, 1, 0, 1, 1, 1, 0, 1], // 2 passages sur la ligne 7
    [1, 0, 0, 0, 0, 0, 0, 0, 1], // Chemin horizontal
    [1, 1, 1, 1, 1, 1, 1, 1, 1] // Mur inférieur
    ];
    • Dans ce tableau, 1 représente un mur et 0 un chemin.
    • Ce tableau représente un labyrinthe avec un contour de murs et un chemin à l'intérieur.
  4. Ajoutez l'export de la fonction genererLabyrinthe à la fin du fichier GenererationLabyrinthe.js :

    export default genererLabyrinthe;
  5. Importez le composant genererLabyrinthe, dans votre App.js, pour accéder au tableau labyrinthe :

    import genererLabyrinthe from './GenererationLabyrinthe';

  1. Créez un nouveau fichier Labyrinthe.js à la racine de votre projet (à côté de App.js).

    // Labyrinthe.js
    import React from 'react';
    import { View } from 'react-native';

    const tailleCase = 40;

    function Case(props) {
    ...
    }

    function Labyrinthe(props) {
    ...
    }
  2. Implémentez la fonction Case pour afficher une case du labyrinthe.

    ...

    function Case(props) {
    const estMur = props.estMur;
    const styleCase = {
    width: tailleCase,
    height: tailleCase,
    backgroundColor: estMur ? 'black' : 'blue',
    };

    return <View style={styleCase} />;
    }
  3. Implémentez la fonction Labyrinthe pour afficher toutes les cases du labyrinthe.

    ...

    function Labyrinthe(props) {
    const labyrinthe = props.labyrinthe;
    return (
    <View style={{ flexDirection: 'column' }}>
    {labyrinthe.map(function(ligne, indexLigne) {
    return (
    <View key={indexLigne} style={{ flexDirection: 'row' }}>
    {ligne.map(function(caseLabyrinthe, indexColonne) {
    return (
    <Case
    key={indexColonne}
    estMur={caseLabyrinthe === 1}
    />
    );
    })}
    </View>
    );
    })}
    </View>
    );
    }
  4. Ajoutez l'export de la fonction Labyrinthe à la fin du fichier Labyrinthe.js :

    export default Labyrinthe;
  5. Effacez la ligne d'import de la Barre de statut, inutile, dans le fichier App.js.

    Voici la ligne inutile, à effacer :

    import { StatusBar } from 'expo-status-bar';
  6. Importez le composant Labyrinthe, dans votre App.js, pour rendre la fonction Labyrinthe accessible dans l'application :

    import Labyrinthe from './Labyrinthe';
  7. Créez une variable (constante) labyrinthe pour stocker le tableau du labyrinthe dans le programme principal App.js.

    import Labyrinthe from './Labyrinthe';

    const labyrinthe = genererLabyrinthe(11, 11);

    export default function App() {

  1. Effacez les lignes inutiles dans le composant View du fichier App.js.

    Voici les 2 lignes inutiles, à effacer :

    <Text>Open up App.js to start working on your app!</Text>
    <StatusBar style="auto" />
  2. Ajoutez le composant Labyrinthe dans le composant View pour afficher le labyrinthe :

    return (
    <View style={styles.container}>
    <View>
    <Labyrinthe labyrinthe={labyrinthe} />
    </View>
    </View>
    );
  3. Testez votre application pour vous assurer que le labyrinthe s'affiche correctement :

Une solution
info

Vous pouvez inspecter le code HTML généré par l'exécution du code Javascript par votre navigateur (clic droit + Inspecter) :

astuce

Vous êtes invités à créer un autre tableau pour dessiner un autre labyrinthe :

const labyrinthe = [
...
];

et à actualiser la page dans votre navigateur pour découvrir le nouveau labyrinthe et tester votre application.

Pour générer un tableau de façon aléatoire.