CoddyRun
Blog

Articles & Actualités

Les publications CoddyRun : nouveautés du logiciel, astuces de pseudo-code, retours d'expérience et actualités de l'environnement pédagogique.

69 publications

Construire un monde isométrique infini grâce à la génération procédurale
Article

Construire un monde isométrique infini

Construire un monde isométrique infini grâce à la génération procédurale

Lire l'articleReplier l'article

Développer un moteur capable de créer un univers isométrique infini, cohérent et entièrement dynamique représente un défi passionnant. Ce projet explore une approche où chaque tuile, chaque décor et chaque élément du terrain est généré en temps réel, sans aucune carte préchargée ni tableau de données.
Le monde n’existe que lorsqu’il est rendu à l’écran.

Architecture du moteur

Un espace infini basé sur des coordonnées
Le joueur évolue dans un monde théoriquement illimité.
Chaque tuile est calculée à partir de sa position (gx, gy) et projetée en isométrie via une transformation simple :

  • (rX - rY) * 24 pour l’axe horizontal
  • (rX + rY) * 12 pour l’axe vertical

Cette méthode garantit un rendu fluide, même lors de déplacements rapides.

Génération procédurale du sol

Le terrain repose sur des règles mathématiques :

  • Herbe par défaut
  • Chemins lorsque nx mod 14 = 0 ou ny mod 14 = 0
  • Eau via une formule organique : (nx 3 + ny 2) mod 31 < 2

Ces règles créent un monde structuré, avec des routes régulières et des rivières naturelles.

Rendu isométrique avec relief

Chaque tuile est composée de :

  • une tranche 3D pour le volume
  • un losange supérieur pour la surface

Le résultat : un style isométrique propre, lisible et immersif.

Décors procéduraux

Un hash mathématique détermine la présence d’éléments naturels ou architecturaux :

  • arbres
  • rochers
  • maisons

Chaque décor est dessiné via des polygones et formes géométriques, ce qui donne une identité visuelle unique.

Déplacements et collisions

Les déplacements (ZQSD) sont validés par une vérification procédurale :

  • type de sol futur
  • type de décor futur

Impossible d’entrer dans l’eau ou dans une maison : la logique reste cohérente partout dans le monde, sans tableau de collision.

Intérêt du projet

Monde infini sans stockage

  • Performances optimisées : seules les tuiles visibles sont calculées
  • Simplicité d’extension : nouveaux biomes, décors, règles
  • Approche pédagogique pour comprendre la génération procédurale

Conclusion

Ce projet démontre qu’un monde infini, cohérent et vivant peut émerger uniquement de règles mathématiques. La génération procédurale ouvre des perspectives fascinantes pour la création d’univers interactifs.

Programme :

@ Carte Isométrique Infinie et Dynamique avec Génération Procédurale

toile1 est une toile
dimension(toile1, 500, 450)

// Position du joueur dans le monde infini
jX est un nombre
jY est un nombre
jX vaut 0
jY vaut 0

action est un nombre

@ Boucle principale
tant que vrai
    effacer(toile1)
    remplir(toile1, #111923)

    // Balayage du champ de vision (rayon autour du joueur)
    rY est un nombre
    rX est un nombre
    pour rY de - 10 à 10
        pour rX de - 10 à 10
            gx est un nombre
            gy est un nombre
            gx vaut jX + rX
            gy vaut jY + rY

            // Coordonnées écran relatives au centre (200, 200)
            cx est un nombre
            cy est un nombre
            cx vaut 200 + (rX - rY) * 24
            cy vaut 200 + (rX + rY) * 12

            // Culling : Rendu uniquement si la tuile touche la toile 400x400
            si cx >= - 60 et cx <= 460 et cy >= - 60 et cy <= 460 alors

                // Offsets positifs pour le calcul procédural
                nx est un nombre
                ny est un nombre
                nx vaut gx + 50000
                ny vaut gy + 50000

                // 1. Génération procédurale du Sol (sans tableau)
                typeSol est un nombre
                typeSol vaut 0 // 0 = Herbe

                // Réseau de routes infini
                si nx mod 14 = 0 ou ny mod 14 = 0 alors
                    typeSol vaut 1 // Chemin
                    // Rivières organiques procédurales
                sinon si (nx * 3 + ny * 2) mod 31 < 2 alors
                    typeSol vaut 2 // Eau
                fin si

                // Tranche 3D (épais/relief du sol)
                ptsBord est un tableau
                ptsBord ajoute cx - 24
                ptsBord ajoute cy
                ptsBord ajoute cx
                ptsBord ajoute cy + 12
                ptsBord ajoute cx + 24
                ptsBord ajoute cy
                ptsBord ajoute cx + 24
                ptsBord ajoute cy + 4
                ptsBord ajoute cx
                ptsBord ajoute cy + 16
                ptsBord ajoute cx - 24
                ptsBord ajoute cy + 4
                polygone(toile1, ptsBord, #16222d)

                // Face supérieure de la tuile (Losange 48x24)
                ptsSol est un tableau
                ptsSol ajoute cx
                ptsSol ajoute cy - 12
                ptsSol ajoute cx + 24
                ptsSol ajoute cy
                ptsSol ajoute cx
                ptsSol ajoute cy + 12
                ptsSol ajoute cx - 24
                ptsSol ajoute cy

                si typeSol = 0 alors
                    polygone(toile1, ptsSol, #27ae60)
                sinon si typeSol = 1 alors
                    polygone(toile1, ptsSol, #d35400)
                sinon
                    polygone(toile1, ptsSol, #2980b9)
                fin si

                contour(toile1, #1a252f, 1)

                // 2. Génération procédurale du Décor
                typeDecor est un nombre
                typeDecor vaut 0

                si typeSol = 0 alors
                    hash est un nombre
                    hash vaut (nx * 17 + ny * 37) mod 100
                    si hash < 10 alors
                        typeDecor vaut 1 // Arbre
                    sinon si hash < 14 alors
                        typeDecor vaut 2 // Rocher
                    sinon si hash = 15 alors
                        typeDecor vaut 3 // Maison
                    fin si
                fin si

                // Dessin des décors
                si typeDecor = 1 alors
                    rectangle(toile1, cx - 2, cy - 18, 4, 12, #5d4037)
                    cercle(toile1, cx, cy - 22, 10, #2e7d32)
                    cercle(toile1, cx - 3, cy - 25, 6, #388e3c)
                sinon si typeDecor = 2 alors
                    cercle(toile1, cx, cy - 4, 7, #7f8c8d)
                    cercle(toile1, cx - 2, cy - 6, 4, #95a5a6)
                sinon si typeDecor = 3 alors
                    fGauche est un tableau
                    fGauche ajoute cx - 12
                    fGauche ajoute cy
                    fGauche ajoute cx
                    fGauche ajoute cy + 6
                    fGauche ajoute cx
                    fGauche ajoute cy - 18
                    fGauche ajoute cx - 12
                    fGauche ajoute cy - 24
                    polygone(toile1, fGauche, #bdc3c7)

                    fDroite est un tableau
                    fDroite ajoute cx
                    fDroite ajoute cy + 6
                    fDroite ajoute cx + 12
                    fDroite ajoute cy
                    fDroite ajoute cx + 12
                    fDroite ajoute cy - 24
                    fDroite ajoute cx
                    fDroite ajoute cy - 18
                    polygone(toile1, fDroite, #95a5a6)

                    toit est un tableau
                    toit ajoute cx - 12
                    toit ajoute cy - 24
                    toit ajoute cx
                    toit ajoute cy - 32
                    toit ajoute cx + 12
                    toit ajoute cy - 24
                    toit ajoute cx
                    toit ajoute cy - 18
                    polygone(toile1, toit, #c0392b)

                    rectangle(toile1, cx - 3, cy - 2, 5, 7, #5d4037)
                fin si

                // 3. Personnage principal (centré)
                si rX = 0 et rY = 0 alors
                    ellipse(toile1, cx, cy + 3, 9, 4, #0a0f14)
                    rectangle(toile1, cx - 3, cy - 14, 6, 12, #e74c3c)
                    cercle(toile1, cx, cy - 18, 5, #f1c40f)
                fin si

            fin si
        fin pour
    fin pour

    // Interface HUD & Coordonnées GPS
    contour(toile1, #0)
    rectangle(toile1, 0, 0, 400, 45, rgba(17, 17, 17, 0.2))
    label(toile1, 10, 18, "Monde Isométrique Infini", #ffffff, 13)

    txtCoords est un texte
    txtCoords vaut "Position X : " + jX + " | Y : " + jY
    label(toile1, 10, 35, txtCoords, #f1c40f, 11)

    affiche toile1

    appuyer ["z", "s", "q", "d"] dans action

    nouvX est un nombre
    nouvY est un nombre
    nouvX vaut jX
    nouvY vaut jY

    si action = 1 alors
        nouvY vaut jY - 1
    sinon si action = 2 alors
        nouvY vaut jY + 1
    sinon si action = 3 alors
        nouvX vaut jX - 1
    sinon si action = 4 alors
        nouvX vaut jX + 1
    fin si

    // Verification des collisions procédurales à la volée
    chkX est un nombre
    chkY est un nombre
    chkX vaut nouvX + 50000
    chkY vaut nouvY + 50000

    solFutur est un nombre
    solFutur vaut 0
    si chkX mod 14 = 0 ou chkY mod 14 = 0 alors
        solFutur vaut 1
    sinon si (chkX * 3 + chkY * 2) mod 31 < 2 alors
        solFutur vaut 2
    fin si

    decorFutur est un nombre
    decorFutur vaut 0
    si solFutur = 0 alors
        hFutur est un nombre
        hFutur vaut (chkX * 17 + chkY * 37) mod 100
        si hFutur = 15 alors
            decorFutur vaut 3
        fin si
    fin si

    // Collision avec l'eau et les bâtiments
    si solFutur <> 2 et decorFutur <> 3 alors
        jX vaut nouvX
        jY vaut nouvY
    fin si

fin tant que
Pas encore noté
Construire un moteur 3D… en salle de classe : quand l’apprentissage du code devient une aventure créative
Article

Construire un moteur 3D

Construire un moteur 3D… en salle de classe : quand l’apprentissage du code devient une aventure créative

Lire l'articleReplier l'article

Un projet qui illustre parfaitement ce que signifie apprendre à coder en pensant différemment : un moteur 3D voxel isométrique, entièrement développé dans EduCode.

Ce programme permet aux élèves de manipuler un environnement 3D, de construire des blocs, de gérer une caméra, de zoomer, de tourner la scène, et même d’afficher un personnage animé. Le tout dans un langage pédagogique pensé pour rendre la logique accessible.

Un moteur 3D… expliqué simplement

Le cœur du moteur repose sur une boucle de rendu qui projette chaque bloc en isométrie. Comme le dit le code :

Un moteur 3D… expliqué simplement
Le cœur du moteur repose sur une boucle de rendu qui projette chaque bloc en isométrie. Comme le dit le code :

« graph est une toile » « tant que vrai… effacer(graph) »

Chaque frame reconstruit la scène : sol, blocs empilés, personnage, interface HUD. Les élèves découvrent ainsi les notions de :
- Projection isométrique
- Gestion de dictionnaires pour stocker les blocs
- Rendu multi‑faces (haut, gauche, droite)
- Caméra orientable (0°, 90°, 180°, 270°)
- Zoom dynamique
- Interaction clavier en temps réel
- Deuxième élément
- Troisième élément

Construire, détruire, expérimenter

Le moteur intègre une vraie logique de gameplay :
- E → Empiler un bloc
- R → Casser le bloc du dessus
- C → Changer de matériau (Pierre, Brique, Bois, Verre, Feuillage)
- Z / S / Q / D → Se déplacer
- T → Tourner la caméra
- + / - → Zoomer

Chaque bloc est stocké dans un dictionnaire avec sa hauteur et son type. Le code le montre très bien :

« typesBlocs[cle3D] vaut typeBlocConstruction »

Les élèves comprennent ainsi comment fonctionne un moteur de jeu : stockage, rendu, interaction.

Un rendu 3D immersif

Le moteur dessine chaque bloc avec trois faces colorées, créant un effet 3D convaincant. Le personnage lui-même est composé de formes simples : rectangles arrondis, ellipses, cercles… une manière ludique d’aborder la composition graphique.

Pourquoi ce projet compte

Parce qu’il montre que l’apprentissage du code peut être créatif, visuel, motivant.

EduCode n’est pas seulement un outil : c’est un terrain de jeu intellectuel où les élèves apprennent à :
- structurer leurs idées,
- comprendre la logique spatiale,
- manipuler des données,
- créer un univers interactif.

Et comme le rappelle le programme lui-même :
« Apprendre à coder, c'est avant tout apprendre à penser ! »

Conclusion

Ce moteur 3D n’est pas juste un exercice : c’est une preuve que, même avec un langage pédagogique, on peut créer des expériences riches, immersives et formatrices.

Programme :

graph est une toile
dimension(graph, 600, 500)

// Position du joueur
jX est un nombre
jY est un nombre
jX vaut 0
jY vaut 0

// Facteur de zoom (échelle de rendu)
echelle est un nombre
echelle vaut 1,0

// Orientation de la caméra (0: 0°, 1: 90°, 2: 180°, 3: 270°)
orientation est un nombre
orientation vaut 0

// Dictionnaires pour la hauteur et les types de blocs personnalisés
hauteursBlocs est un dictionnaire
hauteursBlocs vaut [:]

typesBlocs est un dictionnaire
typesBlocs vaut [:]

// Type de bloc actif (1: Pierre, 2: Brique, 3: Bois, 4: Verre, 5: Feuillage)
typeBlocConstruction est un nombre
typeBlocConstruction vaut 1

action est un nombre

@ Boucle principale
tant que vrai
  effacer(graph)
  remplir(graph, #090d16) // Fond ciel de nuit profond

  // Calcul des pas isométriques dynamiques selon le zoom
  dx est un nombre
  dy est un nombre
  dx vaut 24 * echelle
  dy vaut 12 * echelle

  // Balayage du champ de vision (rayon autour du joueur)
  rY est un nombre
  rX est un nombre
  pour rY de - 10 à 10
    pour rX de - 10 à 10

      // Rotation des coordonnées du monde autour du joueur selon l'orientation
      offX est un nombre
      offY est un nombre

      si orientation = 0 alors
        offX vaut rX
        offY vaut rY
      sinon si orientation = 1 alors
        offX vaut - rY
        offY vaut rX
      sinon si orientation = 2 alors
        offX vaut - rX
        offY vaut - rY
      sinon
        offX vaut rY
        offY vaut - rX
      fin si

      gx est un nombre
      gy est un nombre
      gx vaut jX + offX
      gy vaut jY + offY

      // Projection isométrique centrée sur la toile (300, 250)
      cx est un nombre
      cy est un nombre
      cx vaut 300 + (rX - rY) * dx
      cy vaut 250 + (rX + rY) * dy

      // Culling élargi pour le rendu
      si cx >= - 100 et cx <= 700 et cy >= - 100 et cy <= 600 alors

        cleCoord est un texte
        cleCoord vaut convertir_texte(gx) + "_" + convertir_texte(gy)

        hauteurTotale est un nombre
        hauteurTotale vaut 0
        si contient(hauteursBlocs, cleCoord) alors
          hauteurTotale vaut hauteursBlocs[cleCoord]
        fin si

        // --- 1. RENDU DU SOL DE BASE ---
        baseYVal est un nombre
        baseYVal vaut cy

        profondeurSol est un nombre
        profondeurSol vaut 20 * echelle

        // Face Gauche du sol de base
        ptsSolFG est un tableau
        ptsSolFG ajoute cx - dx
        ptsSolFG ajoute baseYVal
        ptsSolFG ajoute cx
        ptsSolFG ajoute baseYVal + dy
        ptsSolFG ajoute cx
        ptsSolFG ajoute baseYVal + dy + profondeurSol
        ptsSolFG ajoute cx - dx
        ptsSolFG ajoute baseYVal + profondeurSol
        polygone(graph, ptsSolFG, #1e293b)

        // Face Droite du sol de base
        ptsSolFD est un tableau
        ptsSolFD ajoute cx
        ptsSolFD ajoute baseYVal + dy
        ptsSolFD ajoute cx + dx
        ptsSolFD ajoute baseYVal
        ptsSolFD ajoute cx + dx
        ptsSolFD ajoute baseYVal + profondeurSol
        ptsSolFD ajoute cx
        ptsSolFD ajoute baseYVal + dy + profondeurSol
        polygone(graph, ptsSolFD, #0f172a)

        // Face Supérieure du sol de base (Herbe)
        ptsSolTop est un tableau
        ptsSolTop ajoute cx
        ptsSolTop ajoute baseYVal - dy
        ptsSolTop ajoute cx + dx
        ptsSolTop ajoute baseYVal
        ptsSolTop ajoute cx
        ptsSolTop ajoute baseYVal + dy
        ptsSolTop ajoute cx - dx
        ptsSolTop ajoute baseYVal
        polygone(graph, ptsSolTop, #22c55e)
        contour(graph, #15803d, 1)

        // --- 2. RENDU DES BLOCS EMPILÉS EN HAUTEUR ---
        si hauteurTotale > 0 alors
          hauteurCube est un nombre
          hauteurCube vaut 24 * echelle

          pour k de 0 à hauteurTotale - 1
            cle3D_rendu est un texte
            cle3D_rendu vaut cleCoord + "_" + convertir_texte(k)

            typeBlocVal est un nombre
            si contient(typesBlocs, cle3D_rendu) alors
              typeBlocVal vaut typesBlocs[cle3D_rendu]
            sinon
              typeBlocVal vaut 1
            fin si

            couleurTop est un texte
            couleurGauche est un texte
            couleurDroite est un texte

            // Matériaux : 1=Pierre, 2=Brique, 3=Bois, 4=Verre, 5=Feuillage
            si typeBlocVal = 1 alors
              couleurTop vaut #f1f5f9
              couleurGauche vaut #94a3b8
              couleurDroite vaut #64748b
            sinon si typeBlocVal = 2 alors
              couleurTop vaut #f87171
              couleurGauche vaut #dc2626
              couleurDroite vaut #b91c1c
            sinon si typeBlocVal = 3 alors
              couleurTop vaut #fbbf24
              couleurGauche vaut #d97706
              couleurDroite vaut #b45309
            sinon si typeBlocVal = 4 alors
              // Verre (semi-transparent)
              couleurTop vaut rgba(224, 242, 254, 0.65)
              couleurGauche vaut rgba(148, 163, 184, 0.55)
              couleurDroite vaut rgba(100, 116, 139, 0.55)
            sinon
              // Feuillage (semi-transparent)
              couleurTop vaut rgba(74, 222, 128, 0.8)
              couleurGauche vaut rgba(22, 163, 74, 0.75)
              couleurDroite vaut rgba(21, 128, 61, 0.75)
            fin si

            lCY est un nombre
            lCY vaut cy - hauteurCube - (k * hauteurCube)

            // Face Gauche
            ptsFG est un tableau
            ptsFG ajoute cx - dx
            ptsFG ajoute lCY
            ptsFG ajoute cx
            ptsFG ajoute lCY + dy
            ptsFG ajoute cx
            ptsFG ajoute lCY + dy + hauteurCube
            ptsFG ajoute cx - dx
            ptsFG ajoute lCY + hauteurCube
            polygone(graph, ptsFG, couleurGauche)

            // Face Droite
            ptsFD est un tableau
            ptsFD ajoute cx
            ptsFD ajoute lCY + dy
            ptsFD ajoute cx + dx
            ptsFD ajoute lCY
            ptsFD ajoute cx + dx
            ptsFD ajoute lCY + hauteurCube
            ptsFD ajoute cx
            ptsFD ajoute lCY + dy + hauteurCube
            polygone(graph, ptsFD, couleurDroite)

            // Face Supérieure
            ptsTop est un tableau
            ptsTop ajoute cx
            ptsTop ajoute lCY - dy
            ptsTop ajoute cx + dx
            ptsTop ajoute lCY
            ptsTop ajoute cx
            ptsTop ajoute lCY + dy
            ptsTop ajoute cx - dx
            ptsTop ajoute lCY
            polygone(graph, ptsTop, couleurTop)

            contour(graph, #475569, 1)
          fin pour
        fin si

        // --- 3. PERSONNAGE PRINCIPAL 3D ---
        si rX = 0 et rY = 0 alors
          decalagePersoY est un nombre
          decalagePersoY vaut hauteurTotale * 24 * echelle

          pCY est un nombre
          pCY vaut cy - decalagePersoY

          // Ombre au sol
          ellipse(graph, cx, pCY + (2 * echelle), 12 * echelle, 6 * echelle, rgba(0, 0, 0, 0.4))

          // Jambes
          rectangle_arrondi(graph, cx - (4 * echelle), pCY - (10 * echelle), 8 * echelle, 10 * echelle, 2, #1e3a8a)

          // Bras gauche
          rectangle_arrondi(graph, cx - (9 * echelle), pCY - (21 * echelle), 4 * echelle, 9 * echelle, 2, #fcd34d)

          // Corps
          rectangle_arrondi(graph, cx - (5 * echelle), pCY - (22 * echelle), 10 * echelle, 13 * echelle, 2, #38bdf8)
          rectangle(graph, cx - (1.5 * echelle), pCY - (22 * echelle), 3 * echelle, 13 * echelle, #e0f2fe)

          // Bras droit
          rectangle_arrondi(graph, cx + (5 * echelle), pCY - (21 * echelle), 4 * echelle, 9 * echelle, 2, #fcd34d)

          // Tête
          rectangle_arrondi(graph, cx - (6 * echelle), pCY - (32 * echelle), 12 * echelle, 11 * echelle, 3, #fcd34d)

          // Cheveux
          rectangle_arrondi(graph, cx - (6.5 * echelle), pCY - (34 * echelle), 13 * echelle, 5 * echelle, 2, #ea580c)

          // Yeux
          cercle(graph, cx - (2.5 * echelle), pCY - (27 * echelle), 1.5 * echelle, #0f172a)
          cercle(graph, cx + (2.5 * echelle), pCY - (27 * echelle), 1.5 * echelle, #0f172a)
        fin si

      fin si
    fin pour
  fin pour

  // --- INTERFACE HUD & INFORMATIONS ---
  contour(graph, #0)
  rectangle(graph, 0, 0, 600, 65, rgba(15, 23, 42, 0.55))
  label(graph, 12, 18, "Voxel 3D Engine", #ffffff, 12)

  // Nom du matériau actif
  nomMateriau est un texte
  si typeBlocConstruction = 1 alors
    nomMateriau vaut "Pierre"
  sinon si typeBlocConstruction = 2 alors
    nomMateriau vaut "Brique"
  sinon si typeBlocConstruction = 3 alors
    nomMateriau vaut "Bois"
  sinon si typeBlocConstruction = 4 alors
    nomMateriau vaut "Verre"
  sinon
    nomMateriau vaut "Feuillage"
  fin si

  txtInfos est un texte
  txtInfos vaut "X : " + jX + " / Y : " + jY + " # Matériau : " + nomMateriau + " # Zoom : " + convertir_texte(arrondi(echelle * 100)) + "% # Rotation : " + (orientation * 90) + "°"
  label(graph, 12, 34, txtInfos, #38bdf8, 11)
  label(graph, 12, 52, " Bouger [Z S Q D] # Empiler [E] # Casser [R] # Matériau [C] # Zoom [ + / - ] # Rotation [T]", #ffffff, 10)

  // Barre inférieure
  rectangle(graph, 0, 470, 600, 30, rgba(15, 23, 42, 0.55))
  label(graph, 12, 490, "EduCode v3.1", #FFFFFF, 14)
  label(graph, 150, 490, "Apprendre à coder, c'est avant tout apprendre à penser !", #FFFFFF, 14)

  // --- ENCADRÉ D'INVENTAIRE (EN BAS À DROITE) ---
  rectangle_arrondi(graph, 510, 385, 75, 75, 6, rgba(15, 23, 42, 0.55))
  contour(graph, #ffffff, 1)

  // Couleurs du bloc pour l'aperçu
  couleurSelTop est un texte
  couleurSelGauche est un texte
  couleurSelDroite est un texte

  si typeBlocConstruction = 1 alors
    couleurSelTop vaut #f1f5f9
    couleurSelGauche vaut #94a3b8
    couleurSelDroite vaut #64748b
  sinon si typeBlocConstruction = 2 alors
    couleurSelTop vaut #f87171
    couleurSelGauche vaut #dc2626
    couleurSelDroite vaut #b91c1c
  sinon si typeBlocConstruction = 3 alors
    couleurSelTop vaut #fbbf24
    couleurSelGauche vaut #d97706
    couleurSelDroite vaut #b45309
  sinon si typeBlocConstruction = 4 alors
    couleurSelTop vaut rgba(224, 242, 254, 0.65)
    couleurSelGauche vaut rgba(148, 163, 184, 0.55)
    couleurSelDroite vaut rgba(100, 116, 139, 0.55)
  sinon
    couleurSelTop vaut rgba(74, 222, 128, 0.8)
    couleurSelGauche vaut rgba(22, 163, 74, 0.75)
    couleurSelDroite vaut rgba(21, 128, 61, 0.75)
  fin si

  // Rendu 3D du mini-bloc d'aperçu
  mcx est un nombre
  mcy est un nombre
  mcx vaut 547
  mcy vaut 415

  mdx est un nombre
  mdy est un nombre
  mh est un nombre
  mdx vaut 16
  mdy vaut 8
  mh vaut 16

  // Face Gauche aperçu
  pSelFG est un tableau
  pSelFG ajoute mcx - mdx
  pSelFG ajoute mcy
  pSelFG ajoute mcx
  pSelFG ajoute mcy + mdy
  pSelFG ajoute mcx
  pSelFG ajoute mcy + mdy + mh
  pSelFG ajoute mcx - mdx
  pSelFG ajoute mcy + mh
  polygone(graph, pSelFG, couleurSelGauche)

  // Face Droite aperçu
  pSelFD est un tableau
  pSelFD ajoute mcx
  pSelFD ajoute mcy + mdy
  pSelFD ajoute mcx + mdx
  pSelFD ajoute mcy
  pSelFD ajoute mcx + mdx
  pSelFD ajoute mcy + mh
  pSelFD ajoute mcx
  pSelFD ajoute mcy + mdy + mh
  polygone(graph, pSelFD, couleurSelDroite)

  // Face Supérieure aperçu
  pSelTop est un tableau
  pSelTop ajoute mcx
  pSelTop ajoute mcy - mdy
  pSelTop ajoute mcx + mdx
  pSelTop ajoute mcy
  pSelTop ajoute mcx
  pSelTop ajoute mcy + mdy
  pSelTop ajoute mcx - mdx
  pSelTop ajoute mcy
  polygone(graph, pSelTop, couleurSelTop)

  contour(graph, #ffffff, 1)

  affiche graph

  // --- GESTION DES ENTRÉES CLAVIER ---
  appuyer ["z", "s", "q", "d", "e", "r", "c", "+", "-", "t"] dans action

  mvtX est un nombre
  mvtY est un nombre
  mvtX vaut 0
  mvtY vaut 0

  si action = 1 alors
    // Z (Avancer)
    si orientation = 0 alors
      mvtX vaut 0
      mvtY vaut - 1
    sinon si orientation = 1 alors
      mvtX vaut 1
      mvtY vaut 0
    sinon si orientation = 2 alors
      mvtX vaut 0
      mvtY vaut 1
    sinon
      mvtX vaut - 1
      mvtY vaut 0
    fin si
  sinon si action = 2 alors
    // S (Reculer)
    si orientation = 0 alors
      mvtX vaut 0
      mvtY vaut 1
    sinon si orientation = 1 alors
      mvtX vaut - 1
      mvtY vaut 0
    sinon si orientation = 2 alors
      mvtX vaut 0
      mvtY vaut - 1
    sinon
      mvtX vaut 1
      mvtY vaut 0
    fin si
  sinon si action = 3 alors
    // Q (Aller à gauche)
    si orientation = 0 alors
      mvtX vaut - 1
      mvtY vaut 0
    sinon si orientation = 1 alors
      mvtX vaut 0
      mvtY vaut - 1
    sinon si orientation = 2 alors
      mvtX vaut 1
      mvtY vaut 0
    sinon
      mvtX vaut 0
      mvtY vaut 1
    fin si
  sinon si action = 4 alors
    // D (Aller à droite)
    si orientation = 0 alors
      mvtX vaut 1
      mvtY vaut 0
    sinon si orientation = 1 alors
      mvtX vaut 0
      mvtY vaut 1
    sinon si orientation = 2 alors
      mvtX vaut - 1
      mvtY vaut 0
    sinon
      mvtX vaut 0
      mvtY vaut - 1
    fin si
  sinon si action = 5 alors
    // EMPILER UN BLOC SOUS LE JOUEUR
    cleAction est un texte
    cleAction vaut convertir_texte(jX) + "_" + convertir_texte(jY)

    zActuel est un nombre

    si contient(hauteursBlocs, cleAction) alors
      zActuel vaut hauteursBlocs[cleAction]
      hauteursBlocs[cleAction] vaut hauteursBlocs[cleAction] + 1
    sinon
      zActuel vaut 0
      hauteursBlocs[cleAction] vaut 1
    fin si

    cle3D est un texte
    cle3D vaut cleAction + "_" + convertir_texte(zActuel)
    typesBlocs[cle3D] vaut typeBlocConstruction
  sinon si action = 6 alors
    // CASSER LE BLOC DU DESSUS
    cleAction2 est un texte
    cleAction2 vaut convertir_texte(jX) + "_" + convertir_texte(jY)

    si contient(hauteursBlocs, cleAction2) alors
      zSommet est un nombre
      zSommet vaut hauteursBlocs[cleAction2] - 1

      cle3D_suppr est un texte
      cle3D_suppr vaut cleAction2 + "_" + convertir_texte(zSommet)
      typesBlocs supprime cle3D_suppr

      hauteursBlocs[cleAction2] vaut hauteursBlocs[cleAction2] - 1
      si hauteursBlocs[cleAction2] <= 0 alors
        hauteursBlocs supprime cleAction2
      fin si
    fin si
  sinon si action = 7 alors
    // CHANGER DE TYPE DE MATÉRIAU (1 à 5)
    typeBlocConstruction vaut typeBlocConstruction + 1
    si typeBlocConstruction > 5 alors
      typeBlocConstruction vaut 1
    fin si
  sinon si action = 8 alors
    // ZOOM AVANT (+)
    echelle vaut limiter(echelle + 0.15, 0.6, 2.0)
  sinon si action = 9 alors
    // ZOOM ARRIÈRE (-)
    echelle vaut limiter(echelle - 0.15, 0.6, 2.0)
  sinon si action = 10 alors
    // ROTATION CAMÉRA (T)
    orientation vaut orientation + 1
    si orientation > 3 alors
      orientation vaut 0
    fin si
  fin si

  // Application des déplacements libres
  si action >= 1 et action <= 4 alors
    jX vaut jX + mvtX
    jY vaut jY + mvtY
  fin si
fin tant que
Pas encore noté
Agent 47 Go : un exercice de game design tactique et d’algorithmie appliquée
Article

Agent 47 Go

Agent 47 Go : un exercice de game design tactique et d’algorithmie appliquée

Lire l'articleReplier l'article

Dans le domaine du game design, il existe une zone fascinante : celle où des mécaniques simples, presque brutes, parviennent à créer une expérience stratégique riche. Agent 47 Go illustre parfaitement cette idée en combinant génération procédurale, IA déterministe et rendu isométrique minimaliste pour produire un gameplay d’infiltration au tour par tour.

Une carte générée dynamiquement, toujours jouable

Le système repose sur une grille 8×8, entièrement reconstruite à chaque partie. La logique garantit un chemin viable entre le point de départ et l’objectif, tout en ajoutant des embranchements secondaires uniquement lorsqu’ils restent connectés.

« carte[indexChemin] vaut 1 » « si adj = 1 alors carte[idxCourant] vaut 1 »

Ce choix permet de créer des niveaux variés, sans jamais sacrifier la jouabilité.

Des gardes qui patrouillent selon une IA simple mais efficace
Les gardes suivent une direction fixe, qu’ils inversent lorsqu’ils rencontrent un obstacle ou un autre garde. Ce comportement, entièrement déterministe, crée une patrouille crédible, lisible, mais suffisamment imprévisible pour obliger le joueur à anticiper.

« gardesDir[i] vaut (d + 2) mod 4 »

Une mécanique légère, mais qui génère une vraie tension tactique.

Un gameplay d’infiltration au tour par tour

Le joueur se déplace avec ZQSD, chaque action déclenchant la réaction des gardes. Les collisions, éliminations et conditions de victoire/défaite sont évaluées à chaque tour.

« si gardesX[i] = pX et gardesY[i] = pY alors etatJeu vaut 2 »

Une boucle de jeu simple, mais qui récompense l’observation et la planification.

Un rendu isométrique épuré et lisible

Le moteur graphique repose sur des formes géométriques (ellipses, rectangles, lignes) pour représenter les cases, le joueur, les gardes et l’objectif. Le résultat : une lecture immédiate de la scène, même sans assets complexes.

Un terrain d’expérimentation riche
Ce programme met en lumière plusieurs axes techniques :

  • génération procédurale contrôlée,
  • gestion d’état dans un moteur de jeu,
  • IA comportementale simple mais cohérente,
  • rendu isométrique minimaliste,
  • mécaniques d’infiltration au tour par tour.

Autant de briques qui peuvent servir de base à des systèmes plus avancés : pathfinding, IA adaptative, niveaux multi-étages, ou encore gestion de vision et de bruit.

Conclusion

Agent 47 Go démontre qu’avec une architecture claire et des règles bien pensées, il est possible de créer une expérience tactique engageante, même dans un environnement minimaliste. Un bel exemple de ce que l’algorithmie peut apporter au game design.

Programme :

@ Agent 47 Go
toile1 est une toile
dimension(toile1, 600, 450)

dimensionGrille vaut 8
etatJeu est un nombre
etatJeu vaut 3 // 0 = En jeu, 1 = Victoire, 2 = Défaite, 3 = Génération

carte est un tableau
gardesX est un tableau
gardesY est un tableau
gardesDir est un tableau
gardesActif est un tableau

pX est un nombre
pY est un nombre
action est un nombre

tant que vrai

    @ 1. GÉNÉRATION SÉCURISÉE de LA MAP
    si etatJeu = 3 alors
        tant que longueur(gardesX) > 0
            gardesX supprime 0
        fin tant que
        tant que longueur(gardesY) > 0
            gardesY supprime 0
        fin tant que
        tant que longueur(gardesDir) > 0
            gardesDir supprime 0
        fin tant que
        tant que longueur(gardesActif) > 0
            gardesActif supprime 0
        fin tant que

        tant que longueur(carte) < dimensionGrille * dimensionGrille
            carte ajoute 0
        fin tant que
        tant que longueur(carte) > dimensionGrille * dimensionGrille
            carte supprime 0
        fin tant que

        pour i de 0 à (dimensionGrille * dimensionGrille) - 1
            carte[i] vaut 0
        fin pour

        cx est un nombre
        cy est un nombre
        cx vaut 0
        cy vaut 0
        carte[0] vaut 1

        tant que cx < dimensionGrille - 1 ou cy < dimensionGrille - 1
            si cx = dimensionGrille - 1 alors
                cy ajoute 1
            sinon si cy = dimensionGrille - 1 alors
                cx ajoute 1
            sinon
                r vaut hasard(0, 1)
                si r = 0 alors
                    cx ajoute 1
                sinon
                    cy ajoute 1
                fin si
            fin si

            indexChemin vaut (cy * dimensionGrille) + cx
            si indexChemin >= 0 et indexChemin < longueur(carte) alors
                carte[indexChemin] vaut 1
            fin si
        fin tant que

        pour i de 1 à 15
            rx vaut hasard(0, dimensionGrille - 1)
            ry vaut hasard(0, dimensionGrille - 1)

            si rx >= 0 et rx < dimensionGrille et ry >= 0 et ry < dimensionGrille alors
                idxCourant vaut (ry * dimensionGrille) + rx
                si idxCourant >= 0 et idxCourant < longueur(carte) et carte[idxCourant] = 0 alors
                    adj vaut 0

                    si rx > 0 alors
                        idxG vaut (ry * dimensionGrille) + rx - 1
                        si idxG >= 0 et idxG < longueur(carte) et carte[idxG] > 0 alors adj vaut 1 fin si
                    fin si
                    si rx < dimensionGrille - 1 alors
                        idxD vaut (ry * dimensionGrille) + rx + 1
                        si idxD >= 0 et idxD < longueur(carte) et carte[idxD] > 0 alors adj vaut 1 fin si
                    fin si
                    si ry > 0 alors
                        idxH vaut ((ry - 1) * dimensionGrille) + rx
                        si idxH >= 0 et idxH < longueur(carte) et carte[idxH] > 0 alors adj vaut 1 fin si
                    fin si
                    si ry < dimensionGrille - 1 alors
                        idxB vaut ((ry + 1) * dimensionGrille) + rx
                        si idxB >= 0 et idxB < longueur(carte) et carte[idxB] > 0 alors adj vaut 1 fin si
                    fin si

                    si adj = 1 alors
                        carte[idxCourant] vaut 1
                    fin si
                fin si
            fin si
        fin pour

        indexArrivee vaut ((dimensionGrille - 1) * dimensionGrille) + (dimensionGrille - 1)
        si indexArrivee >= 0 et indexArrivee < longueur(carte) alors
            carte[indexArrivee] vaut 2
        fin si

        pX vaut 0
        pY vaut 0

        nbG vaut hasard(2, 4)
        pour i de 1 à nbG
            trouve vaut faux
            essais vaut 0
            tant que non trouve et essais < 40
                gxRand vaut hasard(0, dimensionGrille - 1)
                gyRand vaut hasard(0, dimensionGrille - 1)

                si gxRand >= 0 et gxRand < dimensionGrille et gyRand >= 0 et gyRand < dimensionGrille alors
                    idxGrd vaut (gyRand * dimensionGrille) + gxRand
                    si idxGrd >= 0 et idxGrd < longueur(carte) alors
                        si carte[idxGrd] = 1 et (gxRand <> 0 ou gyRand <> 0) et (gxRand <> dimensionGrille - 1 ou gyRand <> dimensionGrille - 1) alors
                            libre vaut vrai
                            pour j de 0 à longueur(gardesX) - 1
                                si gardesX[j] = gxRand et gardesY[j] = gyRand alors libre vaut faux fin si
                            fin pour

                            si libre alors
                                gardesX ajoute gxRand
                                gardesY ajoute gyRand
                                gardesDir ajoute hasard(0, 3)
                                gardesActif ajoute 1
                                trouve vaut vrai
                            fin si
                        fin si
                    fin si
                fin si
                essais ajoute 1
            fin tant que
        fin pour

        etatJeu vaut 0
    fin si

    @ 2. RENDU VISUEL AMÉLIORÉ & AGRANDI (ZOOM ISOMÉTRIQUE)
    effacer(toile1)
    remplir(toile1, #0b131d)

    offX vaut 280
    offY vaut 80

    pour gy de 0 à dimensionGrille - 1
        pour gx de 0 à dimensionGrille - 1
            idxRendu1 vaut (gy * dimensionGrille) + gx
            si idxRendu1 >= 0 et idxRendu1 < longueur(carte) et carte[idxRendu1] > 0 alors
                c1x vaut offX + (gx - gy) * 36
                c1y vaut offY + (gx + gy) * 20

                si gx < dimensionGrille - 1 alors
                    idxR2 vaut (gy * dimensionGrille) + gx + 1
                    si idxR2 >= 0 et idxR2 < longueur(carte) et carte[idxR2] > 0 alors
                        c2x vaut offX + (gx + 1 - gy) * 36
                        c2y vaut offY + (gx + 1 + gy) * 20
                        ligne(toile1, c1x, c1y, c2x, c2y, rgba(255, 255, 255, 0.15), 6)
                    fin si
                fin si

                si gy < dimensionGrille - 1 alors
                    idxR3 vaut ((gy + 1) * dimensionGrille) + gx
                    si idxR3 >= 0 et idxR3 < longueur(carte) et carte[idxR3] > 0 alors
                        c2x vaut offX + (gx - (gy + 1)) * 36
                        c2y vaut offY + (gx + (gy + 1)) * 20
                        ligne(toile1, c1x, c1y, c2x, c2y, rgba(255, 255, 255, 0.15), 6)
                    fin si
                fin si
            fin si
        fin pour
    fin pour

    pour gy de 0 à dimensionGrille - 1
        pour gx de 0 à dimensionGrille - 1
            idxRendu4 vaut (gy * dimensionGrille) + gx
            si idxRendu4 >= 0 et idxRendu4 < longueur(carte) alors
                valN vaut carte[idxRendu4]
                si valN > 0 alors
                    cx vaut offX + (gx - gy) * 36
                    cy vaut offY + (gx + gy) * 20

                    si valN = 2 alors
                        ellipse(toile1, cx, cy + 3, 20, 11, rgba(0, 0, 0, 0.4))
                        ellipse(toile1, cx, cy, 18, 10, #145a32)
                        ellipse(toile1, cx, cy - 3, 15, 8, #27ae60)
                    sinon
                        ellipse(toile1, cx, cy + 3, 18, 10, rgba(0, 0, 0, 0.4))
                        ellipse(toile1, cx, cy, 16, 9, #34495e)
                        ellipse(toile1, cx, cy - 3, 13, 7, #bdc3c7)
                    fin si

                    @ Affichage du Joueur (Agent 47) avec position fixe et cohérente sur sa case
                    si pX = gx et pY = gy alors
                        ellipse(toile1, cx, cy + 2, 10, 5, rgba(0, 0, 0, 0.6))
                        rectangle(toile1, cx - 5, cy - 24, 10, 20, #17202a)
                        rectangle(toile1, cx - 2, cy - 20, 4, 10, #c0392b)
                        cercle(toile1, cx, cy - 25, 5, #f5cba7)
                    fin si

                    pour i de 0 à longueur(gardesX) - 1
                        si gardesActif[i] = 1 et gardesX[i] = gx et gardesY[i] = gy alors
                            ellipse(toile1, cx, cy + 2, 10, 5, rgba(0, 0, 0, 0.6))
                            rectangle(toile1, cx - 5, cy - 22, 10, 18, #d4ac0d)
                            cercle(toile1, cx, cy - 26, 6, #eaeded)

                            vx vaut 0
                            vy vaut 0
                            dir vaut gardesDir[i]
                            si dir = 0 alors vx vaut 18 vy vaut 10 fin si
                            si dir = 1 alors vx vaut - 18 vy vaut 10 fin si
                            si dir = 2 alors vx vaut - 18 vy vaut - 10 fin si
                            si dir = 3 alors vx vaut 18 vy vaut - 10 fin si

                            ligne(toile1, cx, cy - 16, cx + vx, cy - 16 + vy, rgba(231, 76, 60, 0.85), 2.5)
                        fin si
                    fin pour
                fin si
            fin si
        fin pour
    fin pour

    @ 3. INTERFACE UTILISATEUR
    rectangle(toile1, 0, 0, 600, 55, rgba(11, 19, 29, 0.85))
    si etatJeu = 0 alors
        label(toile1, 15, 22, "AGENT 47 : Infiltration tactique", #ffffff, 14)
        label(toile1, 15, 42, "Mouvements: Z Q S D  |  Attendre: A", #95a5a6, 11)
    sinon si etatJeu = 1 alors
        rectangle(toile1, 130, 140, 340, 110, rgba(11, 19, 29, 0.95))
        label(toile1, 205, 185, "OBJECTIF ATTEINT", #27ae60, 18)
        label(toile1, 160, 215, "Appuyez sur ESPACE pour continuer", #ffffff, 11)
    sinon si etatJeu = 2 alors
        rectangle(toile1, 150, 140, 300, 110, rgba(11, 19, 29, 0.95))
        label(toile1, 235, 185, "REPÉRÉ / ÉLIMINÉ", #c0392b, 18)
        label(toile1, 165, 215, "Appuyez sur ESPACE pour réessayer", #ffffff, 11)
    fin si

    affiche toile1

    @ 4. GESTION DES TOURS et COMBAT
    appuyer ["z", "s", "q", "d", " ", "a"] dans action

    si etatJeu = 1 ou etatJeu = 2 alors
        si action = 5 alors
            etatJeu vaut 3
        fin si
    sinon si etatJeu = 0 alors

        nx vaut pX
        ny vaut pY
        tourJoue vaut faux

        si action = 1 alors ny soustrait 1 fin si
        si action = 2 alors ny ajoute 1 fin si
        si action = 3 alors nx soustrait 1 fin si
        si action = 4 alors nx ajoute 1 fin si
        si action = 6 alors tourJoue vaut vrai fin si

        si nx >= 0 et nx < dimensionGrille et ny >= 0 et ny < dimensionGrille alors
            idxJoueur vaut (ny * dimensionGrille) + nx
            si idxJoueur >= 0 et idxJoueur < longueur(carte) alors
                si carte[idxJoueur] > 0 alors
                    pX vaut nx
                    pY vaut ny
                    tourJoue vaut vrai
                fin si
            fin si
        fin si

        si tourJoue = vrai alors

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 et gardesX[i] = pX et gardesY[i] = pY alors
                    gardesActif[i] vaut 0
                fin si
            fin pour

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 alors
                    si gardesX[i] >= 0 et gardesX[i] < dimensionGrille et gardesY[i] >= 0 et gardesY[i] < dimensionGrille alors
                        d vaut gardesDir[i]
                        gnx vaut gardesX[i]
                        gny vaut gardesY[i]

                        si d = 0 alors gnx ajoute 1 fin si
                        si d = 1 alors gny ajoute 1 fin si
                        si d = 2 alors gnx soustrait 1 fin si
                        si d = 3 alors gny soustrait 1 fin si

                        si gnx >= 0 et gnx < dimensionGrille et gny >= 0 et gny < dimensionGrille alors
                            idxG2 vaut (gny * dimensionGrille) + gnx
                            si idxG2 >= 0 et idxG2 < longueur(carte) alors
                                si carte[idxG2] > 0 alors
                                    caseLibre vaut vrai
                                    pour j de 0 à longueur(gardesX) - 1
                                        si i <> j et gardesActif[j] = 1 et gardesX[j] = gnx et gardesY[j] = gny alors
                                            caseLibre vaut faux
                                        fin si
                                    fin pour

                                    si caseLibre alors
                                        gardesX[i] vaut gnx
                                        gardesY[i] vaut gny
                                    sinon
                                        gardesDir[i] vaut (d + 2) mod 4
                                    fin si
                                sinon
                                    gardesDir[i] vaut (d + 2) mod 4
                                fin si
                            sinon
                                gardesDir[i] vaut (d + 2) mod 4
                            fin si
                        sinon
                            gardesDir[i] vaut (d + 2) mod 4
                        fin si
                    fin si
                fin si
            fin pour

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 alors
                    si gardesX[i] >= 0 et gardesX[i] < dimensionGrille et gardesY[i] >= 0 et gardesY[i] < dimensionGrille alors
                        si gardesX[i] = pX et gardesY[i] = pY alors
                            etatJeu vaut 2
                        fin si

                        fx vaut gardesX[i]
                        fy vaut gardesY[i]
                        si gardesDir[i] = 0 alors fx ajoute 1 fin si
                        si gardesDir[i] = 1 alors fy ajoute 1 fin si
                        si gardesDir[i] = 2 alors fx soustrait 1 fin si
                        si gardesDir[i] = 3 alors fy soustrait 1 fin si

                        si fx >= 0 et fx < dimensionGrille et fy >= 0 et fy < dimensionGrille alors
                            idxF vaut (fy * dimensionGrille) + fx
                            si idxF >= 0 et idxF < longueur(carte) alors
                                si pX = fx et pY = fy alors
                                    etatJeu vaut 2
                                fin si
                            fin si
                        fin si
                    fin si
                fin si
            fin pour

            idxFinValidation vaut (pY * dimensionGrille) + pX
            si etatJeu <> 2 et idxFinValidation >= 0 et idxFinValidation < longueur(carte) et carte[idxFinValidation] = 2 alors
                etatJeu vaut 1
            fin si

        fin si
    fin si

fin tant que
Pas encore noté