• Contenu
  • Bas de page
logo ouidoulogo ouidoulogo ouidoulogo ouidou
  • Qui sommes-nous ?
  • Offres
    • 💻 Applications métier
    • 🤝 Collaboration des équipes
    • 🛡️ Sécurisation et optimisation du système d’information
    • 🔗 Transformation numérique
  • Expertises
    • 🖥️ Développement logiciel
    • ♾️ DevSecOps
    • ⚙️ Intégration de logiciels et négoce de licences
      • Atlassian : Jira, Confluence, Bitbucket…
      • Plateforme monday.com
      • GitLab
      • SonarQube
    • 📚​ Logiciel de CRM et de gestion
    • 🎨 UX/UI design
    • 🌐 Accessibilité Numérique
    • 🗂️​ Démarches simplifiées
    • 📝 Formations Atlassian
  • Références
  • Carrières
    • 🧐 Pourquoi rejoindre Ouidou ?
    • ✍🏻 Nous rejoindre
    • 👨‍💻 Rencontrer nos collaborateurs
    • 🚀 Grandir chez Ouidou
  • RSE
  • Ressources
    • 🗞️ Actualités
    • 🔍 Articles techniques
    • 📖 Livres blancs
    • 🎙️ Interviews Clients
Nous contacter
✕
À la découverte de Traefik
À la découverte de Traefik
25 mai 2020
Vous reprendrez bien un morceau ?
Vous reprendrez bien un morceau ?
4 juin 2020
Ressources > Articles techniques > Optionnal Chaining et Démineur

Optionnal Chaining et Démineur

Article écrit par Victor Darras

Bonjour à tous, aujourd’hui je vous propose un petit exercice d’algorithme avec JavaScript qui nous permettra d’aborder quelques points d’intérêt du langage.

J’aimerais notamment aborder la notion d’optional chaining, une nouvelle syntaxe de JavaScript permettant de chaîner des méthodes sans remonter la fameuse erreur TypeError: obj.method is undefined, pour l’instant disponible avec Babel et le plugin optional-chaining.

Pour cet exercice, nous allons générer une grille du vieux — mais indémodable — Démineur…

illustration Démineur sur Windows

Une grille à 2 dimensions

Je vous laisse parcourir ce code basique et ses quelques commentaires :

function generateMap(size = 12, difficulty = 0.1) {
  let grid = [];
  let bombs = 0; // Initialize a bomb count

  for (var y = 0; y < size; y++) {
    var row = [];
    for (var x = 0; x < size; x++) {
      // Here the magic happens: bomb or no bomb
      let val = Math.random() > (difficulty * size / 8) ? 0 : "💣";
      if (val === "💣") bombs += 1;
      row.push({
        value: val,
        active: false
      });
    }
    grid.push(row);
  }
  // A bit silly, but if there aren't enough bombs, just generate the map one more time.
  if(bombs < Math.round(3 / 4 * size)) return generateMap(size, difficulty);
  return grid;
}

export default generateMap;

Un Array pour chaque ligne dans un Array contenant toutes ces lignes et nous voilà avec un tableau à 2 dimensions.

Pour simplifier ma réflexion, j’ai décidé de faire l’algorithme en 2 parties distinctes. Dans cette suite, nous allons compter le nombre de bombes adjacentes à chacune des cellules de notre grille.

La logique du jeu

Une cellule contenant une bombe aura donc pour valeur “💣”. Une cellule ayant explosé “💥” et pour les autres, le nombre de bombes adjacentes.

Nous aurons besoin de cette fonction au démarrage de la partie afin de définir l’ensemble des valeurs que nous afficherons une fois une zone découverte. Nous nous en servirons aussi par la suite, après un click du joueur pour savoir si nous devons découvrir sa cellule ainsi que les potentielles cellules adjacentes sans bombe.

Voici l’ensemble de la fonction, nous verrons ensuite ses points d’intérêts :

function processMap (grid) {
  let hasChanges = false; // Keep track if the processing toggled a cell
  grid = grid.map((line, y) => {
    return line.map((cell, x) => {

      if (cell.active || cell.value === "💣" || cell.value === "💥") return cell; // No change needed

      const suroundings = getSuroundings(map, x, y);
      // Check if some empty and active cells exist
      if (suroundings.filter(sur => sur?.active && sur?.value <= 0).length >= 1) {
        hasChanges = true;
        return { ...cell, active: true };
      }
      // Else, return the cell with its suroundings count
      return {
        ...cell,
        value: suroundings.filter(sur => sur?.value === "💣" || sur?.value === "💥").length,
      };
    })
  }, []);

  if (hasChanges) return processMap(grid); // Propagate active cells
  return grid;
}

export default processMap;

On a donc 2 boucles imbriquées pour parcourir chaque ligne et chaque cellule. Pour chaque cellule, on va lister ses cellules voisine dans suroundings. Il y a ici un piège quand nous sommes sur les cellules du bord de la grille :

  • sur la première ligne il n’existe pas de cellule au-dessus
  • sur la dernière, aucune en dessous
  • première colonne, pas de cellule précédente
  • dernière colonne, pas de cellule suivante

Nous avons donc besoin d’une méthode qui liste les cellules adjacentes :

function getSuroundings(map, x, y) {
  return [
    map[y - 1]?.[x], // top
    map[y - 1]?.[x - 1], // top-left
    map[y - 1]?.[x + 1], // top-right
    map[y]?.[x - 1], // left
    map[y]?.[x + 1], // right
    map[y + 1]?.[x], // bottom
    map[y + 1]?.[x - 1], // bottom-left
    map[y + 1]?.[x + 1] // bottom-right
  ];
}

Pour éviter d’avoir à gérer explicitement ces cas, nous allons donc choisir d’utiliser un optional chaining operator ?. qui retournera undefined dans le cas où l’objet serait undefined et surtout ne lèvera pas d’erreur d’exécution de type :

TypeError: Cannot read property '0' of undefined

Maintenant dans le cas où l’une des cellules voisines est active et n’est entourée d’aucune bombe nous allons activer la cellule courante. Cela permet de découvrir des zones complètes (et vide) sans risque. Cette méthode devant être récursive pour étendre la zone à chaque occurrence, nous la relancerons en fin de fonction avec le flag hasChanges.

Comme vu précédemment, la variable sur est potentiellement undefined, on utilise donc la même astuce du ?. pour les 2 prochains bout de code.

// Check if some empty and active cells exist
if (suroundings.filter(sur => sur?.active && sur?.value === 0).length >= 1) {
  hasChanges = true;
  return { ...cell, active: true };
}

Enfin le fonctionnement relativement par défaut (utilisé à la génération de grille) consiste à compter le nombre de bombes entourant la cellule actuelle pour l’inscrire dans sa méthode value.

// Else, return the cell with its suroundings count
return {
  ...cell,
  value: suroundings.filter(sur => sur?.value === "💣" || sur?.value === "💥").length,
};

En fin de fonction on s’assure de relancer la propagation de la zone découverte s’il y a eu un changement, et on renvoie la grille mise à jour dans le cas contraire.

Installation du plugin Babel

Imaginons que vous ayez déjà un environnement Vue/React ou même Node pour faire tourner votre code JavaScript, il contient déjà sûrement le compilateur Babel.

Il vous suffit alors d’ajouter une dépendance de dev comme suis :

npm install --save-dev @babel/plugin-proposal-optional-chaining

Puis d’ajouter à votre fichier babel.config.js la ligne correspondant à notre plugin :

module.exports = {
  presets: [
    '@vue/app'
  ],
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}

On joue un peu ?

Je pense avoir fait le tour de la génération de grille pour le démineur et vous avez ici toute la logique nécessaire pour créer et mettre à jour votre jeu. Il ne reste qu’à ajouter interactions, visuels, musique, et milles autres détails pour faire de cet algo un jeu.

J’espère à travers ce simple exemple avoir su mettre en exergue l’intérêt de l’optional-chaining et je vous invite à tester la version plus complète et édulcorée du jeu qui m’a permis d’écrire cet article. J’aurais probablement l’occasion de revenir sur plusieurs éléments intéressant de cette app, n’hésitez pas dans les commentaires si un point vous intéresse particulièrement.

À lire aussi

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique

Lire la suite

intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 

Lire la suite

Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 

Lire la suite

image miniature les nouveautés Atlassian
26 février 2025

Les nouveautés Atlassian en 2025

Lire la suite

Articles associés

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique


Lire la suite
intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 


Lire la suite
Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 


Lire la suite

À propos

  • Qui sommes-nous ?
  • Références
  • RSE
  • Ressources

Offres

  • Applications métier
  • Collaboration des équipes
  • Sécurisation et optimisation du système d’information
  • Transformation numérique

Expertises

  • Développement logiciel
  • DevSecOps
  • Intégration de logiciels et négoce de licences
  • Logiciel de CRM et de gestion
  • UX/UI design
  • Accessibilité Numérique
  • Démarches simplifiées
  • Formations Atlassian

Carrières

  • Pourquoi rejoindre Ouidou ?
  • Nous rejoindre
  • Rencontrer nos collaborateurs
  • Grandir chez Ouidou

SIEGE SOCIAL
70-74 boulevard Garibaldi, 75015 Paris

Ouidou Nord
165 Avenue de Bretagne, 59000 Lille

Ouidou Rhône-Alpes
4 place Amédée Bonnet, 69002 Lyon

Ouidou Grand-Ouest
2 rue Crucy, 44000 Nantes

Ouidou Grand-Est
7 cour des Cigarières, 67000 Strasbourg

  • Linkedin Ouidou
  • GitHub Ouidou
  • Youtube Ouidou
© 2024 Ouidou | Tous droits réservés | Plan du site | Mentions légales | Déclaration d'accessibilité
    Nous contacter