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.

74 publications

Génération de Labyrinthe & Rendu 3D Isométrique : Exploration Technique
Article

Génération de Labyrinthe & Rendu 3D Isométrique

Génération de Labyrinthe & Rendu 3D Isométrique : Exploration Technique

Lire l'articleReplier l'article

Ce projet s’inscrit dans une démarche d’expérimentation autour de la génération procédurale, des algorithmes de parcours, et du rendu isométrique 3D basé sur des primitives géométriques.
L’objectif : comprendre, tester et maîtriser un pipeline complet allant de la création d’un labyrinthe à son affichage en perspective isométrique.

Génération procédurale : DFS (Depth-First Search)

La structure du labyrinthe repose sur un DFS carving, une méthode simple mais extrêmement efficace pour produire des labyrinthes cohérents et non triviaux.

Caractéristiques :

  • Grille 11×11
  • Carving par sauts de 2 cases pour créer des couloirs
  • Stack pour le backtracking
  • Marquage des cellules visitées
  • Percement des murs via midpoint (cur + next) / 2
  • Sortie placée en bas-droite

Ce type d’algorithme garantit :

  • un chemin unique entre deux points,
  • une génération rapide,
  • une structure lisible et exploitable pour un rendu 3D.

Rendu isométrique 3D : géométrie pure

Le rendu repose entièrement sur des polygones, lignes et ellipses, sans sprites ni textures.
Chaque cellule est transformée en tuile isométrique selon ses coordonnées (u, v).

Points techniques :

  • Tuiles : tw = 36, th = 18
  • Murs 3D composés de trois faces + contours
  • Dalles de sol avec shading
  • Pion joueur en pseudo-3D
  • Sortie mise en valeur par une dalle verte

Rendu par diagonales (u + v) pour garantir la bonne superposition des tuiles

Ce pipeline offre un contrôle total sur la géométrie, les ombres, la lisibilité et l’esthétique.

Minimap 2D intégrée

Une minimap 2D accompagne le rendu isométrique pour améliorer la navigation :

  • Cellules mur/sol/sortie différenciées
  • Position du joueur affichée en temps réel
  • Interface compacte sous la zone de commandes

Elle permet de conserver une vision globale du labyrinthe malgré la perspective.

Gameplay & interactions

Les déplacements se font via Z Q S D, avec :

  • gestion des collisions,
  • détection de la sortie,
  • états du jeu (génération, jeu, victoire),
  • régénération complète via ESPACE.

Une boucle simple, efficace, idéale pour tester différentes configurations de labyrinthe.

Enseignements techniques

Ce projet met en lumière plusieurs aspects intéressants :

- maîtrise des algorithmes de génération procédurale,
- construction d’un pipeline de rendu isométrique custom,
- optimisation de l’ordre de dessin pour éviter les artefacts,
- réflexion sur la lisibilité dans un environnement pseudo-3D,
- structuration d’un mini-moteur graphique basé sur primitives.
- Un excellent terrain d’expérimentation pour des projets plus ambitieux :
jeux de stratégie, donjons procéduraux, moteurs isométriques, simulations, etc.

Programme :

@ Initialisation et Déclarations
toile1 est une toile
dimension(toile1, 550, 520)

gridW est un nombre
gridH est un nombre
gridW vaut 11
gridH vaut 11

g est un tableau
visite est un tableau

posU est un nombre
posV est un nombre
posU vaut 1
posV vaut 1

etatJeu est un nombre
etatJeu vaut 3 // 3 = Génération, 0 = Jeu, 1 = Victoire

monAction est un nombre
monAction vaut 0

// Variables DFS Génération
stackU est un tableau
stackV est un tableau
voisinsU est un tableau
voisinsV est un tableau

curU est un nombre
curV est un nombre
nextU est un nombre
nextV est un nombre
midU est un nombre
midV est un nombre
nbVoisins est un nombre
choix est un nombre
idx est un nombre

i est un nombre
u est un nombre
v est un nombre
sommeDiag est un nombre
valeurCase est un nombre

// Variables Rendu Isométrique 3D
tw est un nombre
tw vaut 36
th est un nombre
th vaut 18
hMur est un nombre
hMur vaut 18

// Centrage du Labyrinthe
offX est un nombre
offX vaut 275
offY est un nombre
offY vaut 225

cx est un nombre
cy est un nombre

// Minimap sous l'interface
cellSize est un nombre
cellSize vaut 8
mapOffX est un nombre
mapOffX vaut 231
mapOffY est un nombre
mapOffY vaut 72
mcx est un nombre
mcy est un nombre

// Boucle principale
tant que vrai

    @ 1. GÉNÉRATION DU LABYRINTHE (DFS 2D)
    si etatJeu = 3 alors
        vide g
        vide visite
        vide stackU
        vide stackV

        pour i de 0 à (gridW * gridH) - 1
            g ajoute 0
            visite ajoute 0
        fin pour

        posU vaut 1
        posV vaut 1
        idx vaut (posV * gridW) + posU
        visite[idx] vaut 1
        g[idx] vaut 1

        stackU ajoute posU
        stackV ajoute posV

        tant que longueur(stackU) > 0
            curU vaut dernier(stackU)
            curV vaut dernier(stackV)

            vide voisinsU
            vide voisinsV

            // Haut
            si curV >= 3 alors
                idx vaut ((curV - 2) * gridW) + curU
                si visite[idx] = 0 alors
                    voisinsU ajoute curU
                    voisinsV ajoute (curV - 2)
                fin si
            fin si
            // Bas
            si curV <= gridH - 4 alors
                idx vaut ((curV + 2) * gridW) + curU
                si visite[idx] = 0 alors
                    voisinsU ajoute curU
                    voisinsV ajoute (curV + 2)
                fin si
            fin si
            // Gauche
            si curU >= 3 alors
                idx vaut (curV * gridW) + (curU - 2)
                si visite[idx] = 0 alors
                    voisinsU ajoute (curU - 2)
                    voisinsV ajoute curV
                fin si
            fin si
            // Droite
            si curU <= gridW - 4 alors
                idx vaut (curV * gridW) + (curU + 2)
                si visite[idx] = 0 alors
                    voisinsU ajoute (curU + 2)
                    voisinsV ajoute curV
                fin si
            fin si

            nbVoisins vaut longueur(voisinsU)
            si nbVoisins > 0 alors
                choix vaut hasard(0, nbVoisins - 1)
                nextU vaut voisinsU[choix]
                nextV vaut voisinsV[choix]

                midU vaut (curU + nextU) / 2
                midV vaut (curV + nextV) / 2

                idx vaut (midV * gridW) + midU
                g[idx] vaut 1

                idx vaut (nextV * gridW) + nextU
                g[idx] vaut 1
                visite[idx] vaut 1

                stackU ajoute nextU
                stackV ajoute nextV
            sinon
                stackU supprime (longueur(stackU) - 1)
                stackV supprime (longueur(stackV) - 1)
            fin si
        fin tant que

        // Marquer la sortie
        idx vaut ((gridH - 2) * gridW) + (gridW - 2)
        g[idx] vaut 2

        posU vaut 1
        posV vaut 1
        etatJeu vaut 0
    fin si

    @ 2. RENDU GRAPHIQUE
    effacer(toile1)
    rectangle(toile1, 0, 0, 550, 520, #0f172a)

    // --- 2.1 INTERFACE DE COMMANDES (EN HAUT) ---
    rectangle(toile1, 0, 0, 550, 44, #1e293b)
    ligne(toile1, 0, 45, 550, 45, #334155, 1)
    label(toile1, 20, 20, "LABYRINTHE 3D ISOMÉTRIQUE", #f8fafc, 12)
    label(toile1, 20, 40, "Déplacement : Z Q S D  |  Nouveau : ESPACE", #94a3b8, 10)

    // --- 2.2 MINIMAP 2D ---
    rectangle(toile1, mapOffX - 8, mapOffY - 6, (gridW * cellSize) + 16, (gridH * cellSize) + 16, #1e293b)
    ligne(toile1, mapOffX - 8, mapOffY - 6, mapOffX + (gridW * cellSize) + 8, mapOffY - 6, #334155, 1)

    pour v de 0 à gridH - 1
        pour u de 0 à gridW - 1
            idx vaut (v * gridW) + u
            valeurCase vaut g[idx]
            mcx vaut mapOffX + (u * cellSize)
            mcy vaut mapOffY + (v * cellSize)

            si valeurCase = 0 alors
                rectangle(toile1, mcx, mcy, cellSize - 1, cellSize - 1, #475569)
            sinon si valeurCase = 1 alors
                rectangle(toile1, mcx, mcy, cellSize - 1, cellSize - 1, #090d16)
            sinon si valeurCase = 2 alors
                rectangle(toile1, mcx, mcy, cellSize - 1, cellSize - 1, #10b981)
            fin si

            si posU = u et posV = v alors
                cercle(toile1, mcx + (cellSize / 2), mcy + (cellSize / 2), 3, #f43f5e)
            fin si
        fin pour
    fin pour

    // --- 2.3 LABYRINTHE 3D ISOMÉTRIQUE (CENTRÉ AU MILIEU) ---
    sommeDiag vaut 0
    tant que sommeDiag <= (gridW + gridH - 2)
        v vaut 0
        tant que v < gridH
            u vaut 0
            tant que u < gridW
                si (u + v) = sommeDiag alors
                    idx vaut (v * gridW) + u
                    valeurCase vaut g[idx]

                    cx vaut offX + (u - v) * (tw / 2)
                    cy vaut offY + (u + v) * (th / 2)

                    si valeurCase = 0 alors
                        // MURS 3D ISOMÉTRIQUES (Sombres et contrastés)
                        polygone(toile1, cx, cy - th / 2 - hMur, cx + tw / 2, cy - hMur, cx, cy + th / 2 - hMur, cx - tw / 2, cy - hMur, #475569)
                        polygone(toile1, cx - tw / 2, cy - hMur, cx, cy + th / 2 - hMur, cx, cy + th / 2, cx - tw / 2, cy, #334155)
                        polygone(toile1, cx, cy + th / 2 - hMur, cx + tw / 2, cy - hMur, cx + tw / 2, cy, cx, cy + th / 2, #1e293b)

                        // Contour des murs
                        ligne(toile1, cx, cy - th / 2 - hMur, cx + tw / 2, cy - hMur, #64748b, 1)
                        ligne(toile1, cx + tw / 2, cy - hMur, cx, cy + th / 2 - hMur, #64748b, 1)
                        ligne(toile1, cx, cy + th / 2 - hMur, cx - tw / 2, cy - hMur, #64748b, 1)
                        ligne(toile1, cx - tw / 2, cy - hMur, cx, cy - th / 2 - hMur, #64748b, 1)
                        ligne(toile1, cx, cy + th / 2 - hMur, cx, cy + th / 2, #64748b, 1)
                    sinon
                        // DALLE DE SOL (Teinte anthracite)
                        polygone(toile1, cx, cy - th / 2, cx + tw / 2, cy, cx, cy + th / 2, cx - tw / 2, cy, #1e293b)
                        ligne(toile1, cx - tw / 2, cy, cx, cy + th / 2, #334155, 1)
                        ligne(toile1, cx, cy + th / 2, cx + tw / 2, cy, #334155, 1)

                        // Sortie (Vert Émeraude)
                        si valeurCase = 2 alors
                            polygone(toile1, cx, cy - th / 2, cx + tw / 2, cy, cx, cy + th / 2, cx - tw / 2, cy, #10b981)
                        fin si

                        // Pion Joueur (Rose Néon)
                        si posU = u et posV = v alors
                            ellipse(toile1, cx, cy, 8, 4, rgba(0, 0, 0, 0.4))
                            polygone(toile1, cx, cy - 16, cx + 6, cy - 12, cx, cy - 8, cx - 6, cy - 12, #fb7185)
                            polygone(toile1, cx - 6, cy - 12, cx, cy - 8, cx, cy - 2, cx - 6, cy - 6, #f43f5e)
                            polygone(toile1, cx, cy - 8, cx + 6, cy - 12, cx + 6, cy - 6, cx, cy - 2, #e11d48)
                            cercle(toile1, cx, cy - 16, 2, #ffe4e6)
                        fin si
                    fin si
                fin si
                u vaut u + 1
            fin tant que
            v vaut v + 1
        fin tant que
        sommeDiag vaut sommeDiag + 1
    fin tant que

    // MESSAGE DE VICTOIRE
    si etatJeu = 1 alors
        rectangle(toile1, 125, 260, 300, 80, rgba(30, 41, 59, 0.95))
        ligne(toile1, 125, 260, 425, 260, #10b981, 3)
        label(toile1, 205, 290, "VICTOIRE !", #10b981, 18)
        label(toile1, 155, 320, "Appuie sur ESPACE pour rejouer", #f8fafc, 11)
    fin si

    affiche toile1

    @ 3. DÉPLACEMENTS DU JOUEUR (Z, Q, S, D)
    appuyer ["z", "s", "q", "d", " "] dans monAction

    si monAction = 5 alors
        etatJeu vaut 3
    sinon si etatJeu = 0 alors
        nextU vaut posU
        nextV vaut posV

        si monAction = 1 alors nextV vaut posV - 1 fin si
        si monAction = 2 alors nextV vaut posV + 1 fin si
        si monAction = 3 alors nextU vaut posU - 1 fin si
        si monAction = 4 alors nextU vaut posU + 1 fin si

        si nextU >= 0 et nextU < gridW et nextV >= 0 et nextV < gridH alors
            idx vaut (nextV * gridW) + nextU
            si g[idx] > 0 alors
                posU vaut nextU
                posV vaut nextV
            fin si
        fin si

        idx vaut (posV * gridW) + posU
        si g[idx] = 2 alors
            etatJeu vaut 1
        fin si
    fin si
fin tant que
Pas encore noté
Exploration 2D : une map générée avec obstacles, décor et trésor !
Article

Exploration 2D

Exploration 2D : une map générée avec obstacles, décor et trésor !

Lire l'articleReplier l'article

Un système de déplacement dans une grille 2D permet de traverser différents types de terrains, éviter les obstacles et atteindre un coffre bien caché.

Ce projet peut paraître simple au premier regard, mais la logique derrière lui ouvre la porte à beaucoup plus : génération procédurale, collisions, chemin garanti, décor varié… une vraie base pour la suite.

Décors générés aléatoirement :

La carte contient plusieurs types de cases :

  • Herbe (0) – zones accessibles
  • Mur (1) – obstacles en pierre
  • Forêt (2) – arbres infranchissables
  • Eau (3) – zones bloquantes
  • Trésor (4) – objectif final

Les bords sont des murs, et l’intérieur est rempli aléatoirement pour créer une vraie petite aventure.

Chemin garanti jusqu’au trésor :

Un creusement automatique assure qu’un passage existe toujours entre le joueur et le coffre.
Peu importe la génération, une route sûre mène à la victoire.

Déplacement du joueur :

Le personnage se déplace case par case avec :

  • Z : Haut
  • S : Bas
  • D : Droite
  • Q : Gauche

Les collisions empêchent de traverser murs, arbres ou eau.

Objectif :

  • Atteindre la case du trésor.
  • Quand le joueur arrive dessus : VICTOIRE !

Graphismes simples mais efficaces :

  • Herbe fleurie, forêt dense, eau animée, mur texturé, coffre doré… et un petit personnage rouge avec des yeux expressifs.

Un système parfait pour créer :

  • un jeu d’exploration
  • un labyrinthe dynamique

Programme :

@ Initialisation de la toile et des dimensions
maToile est une toile
dimension(maToile, 320, 320)

tailleTUILE, nbLignes, nbColonnes est un nombre
tailleTUILE vaut 32
nbLignes vaut 10
nbColonnes vaut 10

// Position du joueur et du trésor
joueurX, joueurY est un nombre
joueurX vaut 1
joueurY vaut 1

tresorX, tresorY est un nombre
tresorX vaut nbColonnes - 2
tresorY vaut nbLignes - 2

// Types de décors :
// 0 = Herbe (Passage)
// 1 = Mur (Obstacle)
// 2 = Forêt (Obstacle)
// 3 = Eau (Obstacle)
// 4 = Trésor (Objectif)

grilleJeu est un tableau
i, j, tyAlea, idxActuel est un nombre

@ 1. Génération aléatoire des décors
pour i de 0 à nbLignes - 1
    pour j de 0 à nbColonnes - 1
        // Murs extérieurs de la carte
        si i = 0 ou i = nbLignes - 1 ou j = 0 ou j = nbColonnes - 1 alors
            grilleJeu ajoute 1
        sinon
            // Obstacles aléatoires
            tyAlea vaut hasard(0, 9)
            si tyAlea = 0 ou tyAlea = 1 alors
                grilleJeu ajoute 2 // Forêt
            sinon si tyAlea = 2 alors
                grilleJeu ajoute 3 // Eau
            sinon si tyAlea = 3 alors
                grilleJeu ajoute 1 // Mur
            sinon
                grilleJeu ajoute 0 // Herbe
            fin si
        fin si
    fin pour
fin pour

@ 2. CREUSEMENT DU CHEMIN GARANTI (Path Carving)
curX, curY, idxPath, dirAlea est un nombre
curX vaut joueurX
curY vaut joueurY

// On avance pas à pas du départ vers le trésor en dégageant de l'herbe (0)
tant que curX <> tresorX ou curY <> tresorY
    idxPath vaut curY * nbColonnes + curX
    grilleJeu[idxPath] vaut 0

    // Choix aléatoire de la direction (droite ou bas) vers la cible
    si curX < tresorX et curY < tresorY alors
        dirAlea vaut hasard(0, 1)
        si dirAlea = 0 alors
            curX vaut curX + 1
        sinon
            curY vaut curY + 1
        fin si
    sinon si curX < tresorX alors
        curX vaut curX + 1
    sinon
        curY vaut curY + 1
    fin si
fin tant que

// Placement du trésor à la fin du chemin garanti
idxTresor est un nombre
idxTresor vaut tresorY * nbColonnes + tresorX
grilleJeu[idxTresor] vaut 4

@ 3. Boucle principale de jeu
jeuActif est un booléen
jeuActif vaut vrai

actionClavier, valCase, px, py, nxtX, nxtY, idxCible est un nombre

tant que jeuActif = vrai
    effacer(maToile)
    remplir(maToile, #1a202c)

    @ Affichage du décor
    pour i de 0 à nbLignes - 1
        pour j de 0 à nbColonnes - 1
            idxActuel vaut i * nbColonnes + j
            valCase vaut grilleJeu[idxActuel]
            px vaut j * tailleTUILE
            py vaut i * tailleTUILE

            // 0 : HERBE (Fleuries)
            si valCase = 0 alors
                rectangle(maToile, px, py, tailleTUILE, tailleTUILE, #38a169)
                cercle(maToile, px + 16, py + 16, 2, #f6e05e)

                // 1 : MUR DE PIERRE
            sinon si valCase = 1 alors
                rectangle(maToile, px, py, tailleTUILE, tailleTUILE, #4a5568)
                rectangle(maToile, px + 2, py + 2, tailleTUILE - 4, tailleTUILE - 4, #718096)

                // 2 : ARBRE / FORÊT
            sinon si valCase = 2 alors
                rectangle(maToile, px, py, tailleTUILE, tailleTUILE, #2f855a)
                cercle(maToile, px + 16, py + 16, 11, #276749)
                cercle(maToile, px + 16, py + 16, 7, #48bb78)

                // 3 : EAU
            sinon si valCase = 3 alors
                rectangle(maToile, px, py, tailleTUILE, tailleTUILE, #3182ce)
                rectangle(maToile, px + 4, py + 10, 12, 3, #63b3ed)
                rectangle(maToile, px + 16, py + 20, 12, 3, #63b3ed)

                // 4 : COFFRE AU TRÉSOR
            sinon si valCase = 4 alors
                rectangle(maToile, px, py, tailleTUILE, tailleTUILE, #d69e2e)
                rectangle(maToile, px + 6, py + 8, 20, 16, #744210)
                rectangle(maToile, px + 8, py + 12, 16, 8, #ecc94b)
            fin si
        fin pour
    fin pour

    @ Affichage du Joueur
    px vaut joueurX * tailleTUILE
    py vaut joueurY * tailleTUILE
    cercle(maToile, px + 16, py + 16, 12, #e53e3e)
    cercle(maToile, px + 12, py + 12, 3, #ffffff)
    cercle(maToile, px + 20, py + 12, 3, #ffffff)
    cercle(maToile, px + 12, py + 12, 1, #000000)
    cercle(maToile, px + 20, py + 12, 1, #000000)

    affiche maToile

    @ Entrées clavier
    appuyer ["z", "s", "d", "q", "x"] dans actionClavier

    nxtX vaut joueurX
    nxtY vaut joueurY

    // Mouvements : 1=Z, 2=S, 3=D, 4=Q, 5=X
    si actionClavier = 1 alors
        nxtY vaut joueurY - 1
    sinon si actionClavier = 2 alors
        nxtY vaut joueurY + 1
    sinon si actionClavier = 3 alors
        nxtX vaut joueurX + 1
    sinon si actionClavier = 4 alors
        nxtX vaut joueurX - 1
    sinon si actionClavier = 5 alors
        jeuActif vaut faux
    fin si

    @ Déplacement et Collisions
    idxCible vaut nxtY * nbColonnes + nxtX

    // Autoriser le passage uniquement sur l'herbe (0) ou le trésor (4)
    si grilleJeu[idxCible] = 0 ou grilleJeu[idxCible] = 4 alors
        joueurX vaut nxtX
        joueurY vaut nxtY
    fin si

    // Victoire
    si joueurX = tresorX et joueurY = tresorY alors
        affiche "VICTOIRE ! Vous avez trouvé le trésor !"
        jeuActif vaut faux
    fin si
fin tant que

affiche "Fin de la partie."
Pas encore noté
EduCode 3.2 - Structure du programme
Nouveauté

EduCode 3.2 : Structure du programme

EduCode 3.2 - Le panneau : Structure du programme

Lire l'articleReplier l'article

Avec la version 3.2, le panneau Structure du programme évolue en profondeur pour devenir un véritable plan de navigation de votre code, digne des grands éditeurs tout en restant simple et lisible.

Une vue hiérarchique, enfin

Fini la liste plate où toutes les variables se mélangeaient. Les variables locales apparaissent désormais indentées sous leur fonction ou procédure, tandis que les variables globales restent au premier niveau. D'un coup d'œil, vous voyez qui appartient à quoi.

Toujours à jour, automatiquement

Le panneau se rafraîchit en direct pendant que vous tapez. Plus de numéros de ligne obsolètes ni d'éléments fantômes : la structure reflète votre code en permanence, sans avoir à fermer et rouvrir le panneau.

Il suit votre curseur

Déplacez-vous dans l'éditeur : l'élément correspondant se surligne automatiquement dans la structure, et le panneau défile pour le garder visible. Vous savez toujours où vous êtes, même dans un long programme.

Recherchez, trouvez, filtrez

Un nouveau champ de filtre en haut du panneau vous permet de retrouver instantanément une fonction, une procédure ou une variable par son nom — ou même par un nom de paramètre.

Les constantes rejoignent la structure

Les constantes sont désormais détectées, quelle que soit la syntaxe utilisée

Ces améliorations seront disponibles pour tout le monde dans EduCode v3.2.
Ouvrez la structure de votre programme, tapez quelques lignes… et regardez-la vivre. Bonne programmation !

Pas encore noté
Développer un moteur de jeu façon NES : un projet complet mêlant génération procédurale, physique et animation
Article

Développer un moteur de jeu façon NES

Un projet complet mêlant génération procédurale, physique et animation

Lire l'articleReplier l'article

Ce projet consiste à recréer un runner inspiré de l’esthétique et des mécaniques de Super Mario sur NES. L’objectif est de combiner plusieurs dimensions du développement de jeux : génération procédurale, gestion de la physique, interactions, animations image par image et rendu graphique pixel-art.

Structure du monde : grille, dimensions et éléments

Le jeu repose sur une carte composée de 14 colonnes et 8 lignes, chaque case mesurant 46 pixels.
Une ligne de sol fixe sert de référence pour la physique et le placement des obstacles.

La carte est représentée sous forme de tableau de chaînes de caractères, chaque caractère correspondant à un élément :

  • sol,
  • briques,
  • blocs mystère,
  • tuyaux,
  • pièces,
  • buissons,
  • ennemis.

Cette approche permet une lecture rapide et un rendu efficace.

Génération procédurale : un monde qui défile et se renouvelle

Chaque colonne est générée dynamiquement selon des probabilités :

  • tuyaux (15 %),
  • blocs mystère ou briques (25 %),
  • Goombas (15 %),
  • buissons décoratifs (20 %).

Le défilement horizontal remplace la colonne de gauche par une nouvelle colonne générée, ce qui crée un environnement infini.
À chaque défilement, la distance augmente et le score progresse automatiquement.

Physique et interactions : un moteur simple mais robuste

Le moteur physique gère :

  • la détection du sol,
  • les collisions avec les obstacles,
  • les sauts en arc sinusoidal,
  • l’écrasement des ennemis,
  • le ramassage des pièces,
  • les bonus issus des blocs mystère.

Les actions disponibles sont : avancer, sauter, reculer.
Chaque action déclenche une mise à jour de la position, des collisions et du score.

Le système de vies et l’état « estVivant » permettent de gérer la fin de partie.

Rendu graphique : un hommage au pixel-art NES

Le jeu utilise un ensemble de procédures dédiées au dessin :

  • Mario en pixel-art,
  • Goombas,
  • pièces,
  • briques,
  • blocs mystère,
  • tuyaux,
  • nuages,
  • décor du sol.

Chaque élément est dessiné à partir de rectangles, cercles et lignes, ce qui recrée fidèlement l’esthétique 8-bit.

Le décor est redessiné à chaque frame, puis Mario est ajouté à sa position courante.

Animations : marche et saut fluides

Les déplacements ne sont pas instantanés :

  • la marche est animée sur 6 frames,
  • le saut sur 8 frames avec une trajectoire en arc.

Chaque animation est construite en interpolant la position de départ et d’arrivée, puis en affichant une séquence d’images.

Ce système donne au jeu une sensation de fluidité malgré son style rétro.

Boucle principale : un runner jouable et cohérent

La boucle de jeu :

  • attend les actions du joueur,
  • applique la physique,
  • déclenche les animations,
  • met à jour le score,
  • vérifie les collisions,
  • gère les vies,
  • s’arrête lorsque Mario n’a plus de vie.

Le message d’état et les informations (vies, pièces, score) sont affichés en haut de l’écran, comme dans les jeux NES originaux.

Conclusion

Ce programme illustre la manière dont un jeu rétro peut servir de terrain d’expérimentation complet : génération procédurale, moteur physique, gestion des collisions, animations, rendu graphique et boucle de gameplay.
Un projet qui démontre qu’il est possible de recréer une expérience NES cohérente en combinant des techniques simples mais bien structurées.

Programme :

@ Configuration et Dimensions
COLS est un nombre
COLS vaut 14
ROWS est un nombre
ROWS vaut 8

CASE est un nombre
CASE vaut 46
margeH est un nombre
margeH vaut 50
LARG est un nombre
LARG vaut COLS * CASE
HAUT est un nombre
HAUT vaut ROWS * CASE + margeH

LIGNE_SOL est un nombre
LIGNE_SOL vaut 6

@ État du Jeu
carte est un tableau

posX est un nombre
posX vaut 3
posY est un nombre
posY vaut 5

distance est un nombre
distance vaut 0
pieces est un nombre
pieces vaut 0
score est un nombre
score vaut 0
sauts est un nombre
sauts vaut 0
vies est un nombre
vies vaut 3
estVivant est un booléen
estVivant vaut vrai

@ Accès et Outils Carte
fonction caseA(li, co)
    si li < 0 ou li >= ROWS ou co < 0 ou co >= COLS alors
        retourne "."
    fin si
    retourne copier(carte[li], co + 1, 1)
fin fonction

procédure poserCase(li, co, caract)
    si li >= 0 et li < ROWS et co >= 0 et co < COLS alors
        ancienne est un texte
        ancienne vaut carte[li]
        avant est un texte
        avant vaut copier(ancienne, 1, co)
        apres est un texte
        apres vaut copier(ancienne, co + 2, COLS - co - 1)
        carte[li] vaut avant + caract + apres
    fin si
fin procédure

fonction estSolide(li, co)
    si co < 0 ou co >= COLS alors
        retourne faux
    fin si
    si li >= ROWS alors
        retourne vrai
    fin si
    c est un texte
    c vaut caseA(li, co)
    si c = "#" ou c = "B" ou c = "?" ou c = "-" ou c = "T" ou c = "t" alors
        retourne vrai
    fin si
    retourne faux
fin fonction

@ Génération Procédurale du Monde
procédure genererColonne(co)
    li est un nombre
    pour li de 0 à ROWS - 1
        si li >= LIGNE_SOL alors
            appelle poserCase(li, co, "#")
        sinon
            appelle poserCase(li, co, ".")
        fin si
    fin pour

    si co > 5 alors
        tirage est un nombre
        tirage vaut hasard(1, 100)

        // Tuyau vert (15% de chance)
        si tirage <= 15 et caseA(5, co - 1) <> "T" et caseA(5, co - 1) <> "t" alors
            hauteurT est un nombre
            hauteurT vaut hasard(1, 2)
            si hauteurT = 1 alors
                appelle poserCase(5, co, "T")
            sinon
                appelle poserCase(5, co, "T")
                appelle poserCase(4, co, "t")
            fin si

            // Blocs Brique / Mystère (25% de chance)
        sinon si tirage <= 40 alors
            typeB est un nombre
            typeB vaut hasard(1, 100)
            si typeB <= 50 alors
                appelle poserCase(3, co, "?")
            sinon
                appelle poserCase(3, co, "B")
            fin si
            si hasard(1, 100) <= 40 alors
                appelle poserCase(2, co, "o")
            fin si

            // Ennemi Goomba (15% de chance)
        sinon si tirage <= 55 et caseA(5, co - 1) <> "G" alors
            appelle poserCase(5, co, "G")

            // Buisson de décor (20% de chance)
        sinon si tirage <= 75 alors
            appelle poserCase(5, co, "W")
        fin si
    fin si
fin procédure

procédure construireCarteInitiale()
    vide carte
    li est un nombre
    pour li de 0 à ROWS - 1
        ligneVide est un texte
        ligneVide vaut ""
        co est un nombre
        pour co de 0 à COLS - 1
            ligneVide vaut ligneVide + "."
        fin pour
        carte ajoute ligneVide
    fin pour

    pour co de 0 à COLS - 1
        appelle genererColonne(co)
    fin pour
fin procédure

procédure defiler()
    li est un nombre
    pour li de 0 à ROWS - 1
        ancienne est un texte
        ancienne vaut carte[li]
        nouvelleCase est un texte
        si li >= LIGNE_SOL alors
            nouvelleCase vaut "#"
        sinon
            nouvelleCase vaut "."
        fin si
        carte[li] vaut copier(ancienne, 2, COLS - 1) + nouvelleCase
    fin pour

    appelle genererColonne(COLS - 1)
    distance vaut distance + 1
    score vaut score + 10
fin procédure

@ Physique
procédure ajusterHauteurAtterrissage()
    h est un nombre
    h vaut 0
    trouve est un booléen
    trouve vaut faux

    tant que h < ROWS et non trouve
        si estSolide(h, posX) alors
            posY vaut h - 1
            trouve vaut vrai
        sinon
            h vaut h + 1
        fin si
    fin tant que

    si non trouve alors
        posY vaut LIGNE_SOL - 1
    fin si
fin procédure

@ Graphismes NES 8 - bit
procédure dessinerMario(g, cx, cy)
    rectangle(g, cx + 12, cy + 2, 22, 6, #e52521)
    rectangle(g, cx + 12, cy + 8, 28, 4, #e52521)
    rectangle(g, cx + 8, cy + 12, 12, 8, #6e3b12)
    rectangle(g, cx + 20, cy + 12, 16, 12, #f2a65a)
    rectangle(g, cx + 24, cy + 16, 16, 5, #6e3b12)
    rectangle(g, cx + 8, cy + 24, 28, 8, #e52521)
    rectangle(g, cx + 14, cy + 24, 16, 16, #0024d6)
    rectangle(g, cx + 10, cy + 32, 24, 8, #0024d6)
    rectangle(g, cx + 14, cy + 28, 3, 3, #ffd700)
    rectangle(g, cx + 27, cy + 28, 3, 3, #ffd700)
    rectangle(g, cx + 6, cy + 40, 12, 6, #6e3b12)
    rectangle(g, cx + 26, cy + 40, 12, 6, #6e3b12)
fin procédure

procédure dessinerGoomba(g, cx, cy)
    cercle(g, cx + 23, cy + 18, 16, #9c4a00)
    rectangle(g, cx + 7, cy + 18, 32, 16, #9c4a00)
    rectangle(g, cx + 12, cy + 12, 5, 10, #ffffff)
    rectangle(g, cx + 29, cy + 12, 5, 10, #ffffff)
    rectangle(g, cx + 14, cy + 15, 3, 7, #000000)
    rectangle(g, cx + 29, cy + 15, 3, 7, #000000)
    ligne(g, cx + 10, cy + 9, cx + 18, cy + 13, #000000, 2)
    ligne(g, cx + 36, cy + 9, cx + 28, cy + 13, #000000, 2)
    rectangle(g, cx + 4, cy + 32, 14, 10, #000000)
    rectangle(g, cx + 28, cy + 32, 14, 10, #000000)
fin procédure

procédure dessinerPiece(g, cx, cy)
    cercle(g, cx + 23, cy + 23, 12, #ffd700)
    cercle(g, cx + 23, cy + 23, 8, #fc9838)
    rectangle(g, cx + 21, cy + 17, 4, 12, #ffffff)
fin procédure

procédure dessinerBuisson(g, cx, cy)
    cercle(g, cx + 12, cy + 30, 12, #00a800)
    cercle(g, cx + 23, cy + 22, 15, #00a800)
    cercle(g, cx + 34, cy + 30, 12, #00a800)
    rectangle(g, cx + 4, cy + 30, 38, 14, #00a800)
fin procédure

procédure dessinerSol(g, cx, cy)
    rectangle(g, cx, cy, CASE, CASE, #d84818)
    ligne(g, cx, cy + 15, cx + CASE, cy + 15, #000000, 2)
    ligne(g, cx, cy + 31, cx + CASE, cy + 31, #000000, 2)
    ligne(g, cx + 22, cy, cx + 22, cy + 15, #000000, 2)
    ligne(g, cx + 11, cy + 15, cx + 11, cy + 31, #000000, 2)
    ligne(g, cx + 33, cy + 15, cx + 33, cy + 31, #000000, 2)
fin procédure

procédure dessinerTuyau(g, cx, cy, estHaut)
    si estHaut alors
        rectangle(g, cx - 2, cy, CASE + 4, 18, #000000)
        rectangle(g, cx, cy + 2, CASE, 14, #00a800)
        rectangle(g, cx + 4, cy + 2, 6, 14, #a0f020)
        rectangle(g, cx + 3, cy + 18, CASE - 6, CASE - 18, #000000)
        rectangle(g, cx + 5, cy + 18, CASE - 10, CASE - 18, #00a800)
        rectangle(g, cx + 8, cy + 18, 5, CASE - 18, #a0f020)
    sinon
        rectangle(g, cx + 3, cy, CASE - 6, CASE, #000000)
        rectangle(g, cx + 5, cy, CASE - 10, CASE, #00a800)
        rectangle(g, cx + 8, cy, 5, CASE, #a0f020)
    fin si
fin procédure

procédure dessinerBlocMystere(g, cx, cy, actif)
    si actif alors
        rectangle(g, cx, cy, CASE, CASE, #000000)
        rectangle(g, cx + 2, cy + 2, CASE - 4, CASE - 4, #fc9838)
        rectangle(g, cx + 5, cy + 5, CASE - 10, CASE - 10, #e5a823)
        label(g, cx + 16, cy + 32, "?", #000000,22)
    sinon
        rectangle(g, cx, cy, CASE, CASE, #000000)
        rectangle(g, cx + 2, cy + 2, CASE - 4, CASE - 4, #8b5a2b)
        rectangle(g, cx + 6, cy + 6, CASE - 12, CASE - 12, #6e3b12)
    fin si
fin procédure

procédure dessinerBrique(g, cx, cy)
    rectangle(g, cx, cy, CASE, CASE, #000000)
    rectangle(g, cx + 2, cy + 2, CASE - 4, CASE - 4, #b84418)
    ligne(g, cx + 2, cy + 15, cx + CASE - 2, cy + 15, #000000, 2)
    ligne(g, cx + 2, cy + 30, cx + CASE - 2, cy + 30, #000000, 2)
    ligne(g, cx + 22, cy + 2, cx + 22, cy + 15, #000000, 2)
    ligne(g, cx + 12, cy + 15, cx + 12, cy + 30, #000000, 2)
    ligne(g, cx + 32, cy + 15, cx + 32, cy + 30, #000000, 2)
fin procédure

procédure dessinerNuage(g, cx, cy)
    cercle(g, cx + 12, cy + 16, 12, #4a82e4)
    cercle(g, cx + 24, cy + 10, 15, #4a82e4)
    cercle(g, cx + 36, cy + 16, 12, #4a82e4)
    rectangle(g, cx + 10, cy + 16, 28, 10, #4a82e4)

    cercle(g, cx + 12, cy + 14, 12, #ffffff)
    cercle(g, cx + 24, cy + 8, 15, #ffffff)
    cercle(g, cx + 36, cy + 14, 12, #ffffff)
    rectangle(g, cx + 10, cy + 14, 28, 10, #ffffff)
fin procédure

procédure dessinerDecor(g)
    degrade(g, [#5c94fc, #87ceeb])

    appelle dessinerNuage(g, 40, 65)
    appelle dessinerNuage(g, 280, 75)
    appelle dessinerNuage(g, 500, 60)

    li est un nombre
    pour li de 0 à ROWS - 1
        co est un nombre
        pour co de 0 à COLS - 1
            cx est un nombre
            cx vaut co * CASE
            cy est un nombre
            cy vaut li * CASE + margeH
            c est un texte
            c vaut caseA(li, co)

            si c = "#" alors
                appelle dessinerSol(g, cx, cy)
            sinon si c = "B" alors
                appelle dessinerBrique(g, cx, cy)
            sinon si c = "?" alors
                appelle dessinerBlocMystere(g, cx, cy, vrai)
            sinon si c = "-" alors
                appelle dessinerBlocMystere(g, cx, cy, faux)
            sinon si c = "T" alors
                appelle dessinerTuyau(g, cx, cy, vrai)
            sinon si c = "t" alors
                appelle dessinerTuyau(g, cx, cy, faux)
            sinon si c = "G" alors
                appelle dessinerGoomba(g, cx, cy)
            sinon si c = "W" alors
                appelle dessinerBuisson(g, cx, cy)
            sinon si c = "o" alors
                appelle dessinerPiece(g, cx, cy)
            fin si
        fin pour
    fin pour
fin procédure

procédure dessinerStatut(g, message, coulMsg)
    rectangle(g, 0, 0, LARG, 42, #000000)
    label(g, 20, 26, "MARIO  x" + vies, #ffffff, 14)
    label(g, 160, 26, "PIÈCES : " + pieces, #ffd700, 14)
    label(g, 320, 26, "SCORE : " + score, #ffffff, 14)
    label(g, 500, 26, "WORLD 1-1", #ffffff, 14)

    rectangle(g, 0, HAUT - 28, LARG, 28, #000000)
    label(g, 20, HAUT - 10, message, coulMsg, 12)
    label(g, LARG - 280, HAUT - 10, "D : Avancer  Z : Sauter  Q : Reculer", #adb5bd, 11)
fin procédure

procédure dessinerScene(message, coulMsg)
    g est une toile
    dimension(g, LARG, HAUT)
    appelle dessinerDecor(g)

    jcx est un nombre
    jcx vaut posX * CASE
    jcy est un nombre
    jcy vaut posY * CASE + margeH
    appelle dessinerMario(g, jcx, jcy)

    appelle dessinerStatut(g, message, coulMsg)
    affiche g
fin procédure

@ Animations
procédure animerMarche(colD, ligD, message, coulMsg)
    frames est un tableau
    xDep est un nombre
    xDep vaut colD * CASE
    yDep est un nombre
    yDep vaut ligD * CASE + margeH
    xArr est un nombre
    xArr vaut posX * CASE
    yArr est un nombre
    yArr vaut posY * CASE + margeH

    etape est un nombre
    pour etape de 0 à 5
        gf est une toile
        dimension(gf, LARG, HAUT)
        appelle dessinerDecor(gf)

        jx est un nombre
        jx vaut xDep + (xArr - xDep) * etape / 5
        jy est un nombre
        jy vaut yDep + (yArr - yDep) * etape / 5

        appelle dessinerMario(gf, jx, jy)
        appelle dessinerStatut(gf, message, coulMsg)
        frames ajoute gf
    fin pour
    animation(frames, 30)
fin procédure

procédure animerSaut(colD, ligD, message, coulMsg)
    frames est un tableau
    xDep est un nombre
    xDep vaut colD * CASE
    yDep est un nombre
    yDep vaut ligD * CASE + margeH
    xArr est un nombre
    xArr vaut posX * CASE
    yArr est un nombre
    yArr vaut posY * CASE + margeH

    etape est un nombre
    pour etape de 0 à 8
        gf est une toile
        dimension(gf, LARG, HAUT)
        appelle dessinerDecor(gf)

        prog est un nombre
        prog vaut etape / 8

        jx est un nombre
        jx vaut xDep + (xArr - xDep) * prog

        hauteurBase est un nombre
        hauteurBase vaut yDep + (yArr - yDep) * prog
        arcSaut est un nombre
        arcSaut vaut sinus(prog * pi()) * 65

        appelle dessinerMario(gf, jx, hauteurBase - arcSaut)
        appelle dessinerStatut(gf, message, coulMsg)
        frames ajoute gf
    fin pour
    animation(frames, 25)
fin procédure

@ Interactions
procédure verifierRamassagePiece()
    si caseA(posY, posX) = "o" alors
        appelle poserCase(posY, posX, ".")
        pieces vaut pieces + 1
        score vaut score + 200
    fin si
fin procédure

procédure avancer()
    zoneDefil est un nombre
    zoneDefil vaut 6

    si non estSolide(posY, posX + 1) alors
        hauteurAvant est un nombre
        hauteurAvant vaut posY

        si posX >= zoneDefil alors
            appelle defiler()
        sinon
            posX vaut posX + 1
        fin si

        appelle ajusterHauteurAtterrissage()
        appelle verifierRamassagePiece()

        si caseA(posY, posX) = "G" alors
            si hauteurAvant < posY alors
                appelle poserCase(posY, posX, ".")
                score vaut score + 500
            sinon
                vies vaut vies - 1
                appelle poserCase(posY, posX, ".")
                si vies <= 0 alors
                    estVivant vaut faux
                fin si
            fin si
        fin si
    fin si
fin procédure

procédure sauter()
    zoneDefil est un nombre
    zoneDefil vaut 6
    colCible est un nombre
    colCible vaut posX + 1

    // Vérification des caisses '?' dans la colonne de destination
    liBloc est un nombre
    pour liBloc de 0 à ROWS - 1
        si caseA(liBloc, colCible) = "?" alors
            si posY > liBloc alors
                // Mario est EN DESSOUS : explosion de la caisse
                appelle poserCase(liBloc, colCible, ".")

                // Si une pièce se trouve au-dessus, elle tombe jusqu'au sol / obstacle
                si liBloc > 0 et caseA(liBloc - 1, colCible) = "o" alors
                    appelle poserCase(liBloc - 1, colCible, ".")

                    hChute est un nombre
                    hChute vaut liBloc + 1
                    tant que hChute < ROWS et non estSolide(hChute, colCible)
                        hChute vaut hChute + 1
                    fin tant que

                    appelle poserCase(hChute - 1, colCible, "o")
                fin si

                // Attribution du bonus
                tirageBonus est un nombre
                tirageBonus vaut hasard(1, 100)
                si tirageBonus <= 50 alors
                    pieces vaut pieces + 1
                    score vaut score + 200
                sinon si tirageBonus <= 80 alors
                    score vaut score + 1000
                sinon
                    vies vaut vies + 1
                fin si
            fin si
        fin si
    fin pour

    // Écrasement Goomba
    si caseA(posY, colCible) = "G" ou caseA(5, colCible) = "G" alors
        appelle poserCase(posY, colCible, ".")
        appelle poserCase(5, colCible, ".")
        score vaut score + 500
    fin si

    si posX >= zoneDefil alors
        appelle defiler()
    sinon
        posX vaut posX + 1
    fin si

    sauts vaut sauts + 1
    appelle ajusterHauteurAtterrissage()
    appelle verifierRamassagePiece()
fin procédure

procédure reculer()
    si posX > 0 et non estSolide(posY, posX - 1) alors
        hauteurAvant est un nombre
        hauteurAvant vaut posY

        posX vaut posX - 1
        appelle ajusterHauteurAtterrissage()
        appelle verifierRamassagePiece()

        si caseA(posY, posX) = "G" alors
            si hauteurAvant < posY alors
                appelle poserCase(posY, posX, ".")
                score vaut score + 500
            sinon
                vies vaut vies - 1
                appelle poserCase(posY, posX, ".")
                si vies <= 0 alors
                    estVivant vaut faux
                fin si
            fin si
        fin si
    fin si
fin procédure

@ Boucle Principale
appelle construireCarteInitiale()
appelle ajusterHauteurAtterrissage()
appelle dessinerScene("Appuie sur D pour avancer et Z pour sauter !", #ffffff)

continuer est un booléen
continuer vaut vrai

tant que continuer et estVivant
    action est un nombre
    action vaut 1
    appuyer ["d", "z", "q", "a"] dans action

    colAvant est un nombre
    colAvant vaut posX
    ligAvant est un nombre
    ligAvant vaut posY

    si action = 1 alors
        appelle avancer()
        si vies > 0 alors
            appelle animerMarche(colAvant, ligAvant, "En route !", #ffffff)
        fin si
    sinon si action = 2 alors
        appelle sauter()
        si vies > 0 alors
            appelle animerSaut(colAvant, ligAvant, "Saut !", #ffd700)
        fin si
    sinon si action = 3 alors
        appelle reculer()
        si vies > 0 alors
            appelle animerMarche(colAvant, ligAvant, "Recul...", #ffffff)
        fin si
    sinon
        continuer vaut faux
    fin si
fin tant que

affiche 'GAME OVER'
Pas encore noté
Développer un système de combat dynamique : retour sur la création de Dungeon Arena
Article

Dungeon Arena : concevoir un système de combat interactif

Développer un système de combat dynamique : retour sur la création de Dungeon Arena

Lire l'articleReplier l'article

Ce projet explore la conception d’un système de combat interactif, entièrement codé, intégrant une interface graphique, une boucle de jeu, des mécaniques de progression et une génération procédurale de monstres.
Une manière concrète de transformer du code en une expérience visuelle et ludique.

Une interface pensée comme un mini-jeu complet

Le programme initialise une toile graphique :

  • « dimension(graph, 600, 500) »
  • « remplir(graph, #0b0f19) »

Puis construit une interface composée de :

  • un HUD supérieur (titre, étage, or),
  • une zone de combat avec barre de vie du monstre,
  • un portrait animé du monstre (formes, couleurs, cornes, yeux),
  • une fiche du joueur (PV, Mana, Attaque, Bouclier),
  • un journal d’action,
  • quatre boutons interactifs permettant d’agir à chaque tour.

L’ensemble donne une vraie impression de jeu : clair, lisible, cohérent.

Un système de combat au tour par tour

Le cœur du programme repose sur une boucle infinie qui :

  • dessine l’interface,
  • attend un clic,
  • exécute l’action choisie,
  • met à jour les PV, Mana, Bouclier,
  • gère la riposte du monstre,
  • teste la victoire ou la défaite.

Les actions disponibles :

Attaque à l’épée : dégâts physiques + régénération de Mana

Boule de feu : attaque magique à fort impact

Soin : récupération de PV + bouclier

Forge : amélioration permanente des stats

Chaque choix influence directement le tour suivant, créant une vraie dynamique stratégique.

Une génération procédurale des monstres

Le programme inclut une procédure dédiée :

  • nouveauMonstre()

Elle génère automatiquement :

  • le niveau du monstre,
  • ses PV,
  • son attaque,
  • son type (Gobelin, Orc, Golem),
  • sa couleur,
  • son nom,
  • et même des boss tous les 5 étages.

Les boss bénéficient d’un boost de statistiques :

  • PV × 1.5
  • Attaque × 1.3
  • Couleur spécifique (#ef4444)

Cette logique crée une progression naturelle, avec des combats de plus en plus difficiles.

Une mécanique de riposte intelligente

Après chaque action du joueur, si le monstre est encore vivant :

  • il attaque,
  • le bouclier absorbe une partie des dégâts,
  • le journal affiche les événements du tour.

Le système gère même les cas où le bouclier absorbe tout ou seulement une partie des dégâts.

Victoire, défaite et boucle de progression

Le jeu propose trois états :

  • Combat (0)
  • Victoire d’étage (1)
  • Défaite (2)

Chaque état modifie l’interface et les actions disponibles :

  • Victoire ? bouton “Monstre suivant”
  • Défaite ? bouton “Recommencer”
  • Combat ? quatre actions tactiques

La progression est fluide, intuitive et entièrement gérée par le code.

Ce que ce projet démontre

  • La capacité du code à créer une expérience interactive complète.
  • L’intérêt de combiner graphisme, logique de jeu, événements utilisateur et progression procédurale.
  • La puissance d’une boucle de jeu bien structurée.
  • La richesse pédagogique d’un projet qui mélange interface, gameplay et algorithmique.

Programme :

@ Initialisation de la canvas
graph est une toile
dimension(graph, 600, 500)

// Statistiques du Joueur
jHPMax est un nombre
jHPMax vaut 100

jHP est un nombre
jHP vaut 100

jManaMax est un nombre
jManaMax vaut 50

jMana est un nombre
jMana vaut 50

jAttaque est un nombre
jAttaque vaut 18

jBouclier est un nombre
jBouclier vaut 0

jOr est un nombre
jOr vaut 0

// Statistiques du Monstre
mNiveau est un nombre
mNiveau vaut 1

mHPMax est un nombre
mHPMax vaut 60

mHP est un nombre
mHP vaut 60

mAttaque est un nombre
mAttaque vaut 7

mNom est un texte
mNom vaut "Gobelin Vert"

mType est un nombre
mType vaut 1

mCouleur est un texte
mCouleur vaut #22c55e

// État du jeu (0 = Combat, 1 = Victoire Étage, 2 = Défaite)
etatJeu est un nombre
etatJeu vaut 0

logTxt est un texte
logTxt vaut "À toi de jouer ! Clique sur un bouton en bas."

clicX est un nombre
clicY est un nombre

@ Génération du monstre suivant
procédure nouveauMonstre()
    mNiveau vaut mNiveau + 1
    mHPMax vaut 40 + (mNiveau * 22)
    mHP vaut mHPMax
    mAttaque vaut 5 + (mNiveau * 3)

    mType vaut hasard(1, 3)

    si mType = 1 alors
        mNom vaut "Gobelin Nv." + convertir_texte(mNiveau)
        mCouleur vaut #22c55e
    sinon si mType = 2 alors
        mNom vaut "Orc Guerrier Nv." + convertir_texte(mNiveau)
        mCouleur vaut #a855f7
    sinon
        mNom vaut "Golem de Pierre Nv." + convertir_texte(mNiveau)
        mCouleur vaut #64748b
    fin si

    // Boss tous les 5 étages
    si mNiveau = 5 ou mNiveau = 10 ou mNiveau = 15 alors
        mType vaut 4
        mNom vaut "BOSS Dragon Infernal Nv." + convertir_texte(mNiveau)
        mCouleur vaut #ef4444
        mHPMax vaut mHPMax * 1.5
        mHP vaut mHPMax
        mAttaque vaut mAttaque * 1.3
    fin si
fin procédure

@ Boucle principale
tant que vrai
    effacer(graph)
    remplir(graph, #0b0f19)

    // --- 1. HUD SUPÉRIEUR ---
    rectangle(graph, 0, 0, 600, 40, #0f172a)
    label(graph, 15, 25, "DUNGEON ARENA", #ffffff, 13)
    label(graph, 260, 25, "Étage : " + convertir_texte(mNiveau), #38bdf8, 12)
    label(graph, 450, 25, "Or : " + convertir_texte(jOr) + " G", #facc15, 12)

    // --- 2. ARENE DE COMBAT (MONSTRE VISIBLE) ---
    rectangle_arrondi(graph, 20, 50, 560, 180, 8, #1e293b)
    contour(graph, #334155, 2)

    // Nom et PV Monstre
    label(graph, 35, 75, mNom, #ffffff, 14)

    mHPNet est un nombre
    mHPNet vaut maximum(0, mHP)
    ratioHPm est un nombre
    ratioHPm vaut mHPNet / mHPMax

    rectangle_arrondi(graph, 250, 60, 310, 20, 4, #0f172a)
    rectangle_arrondi(graph, 250, 60, ratioHPm * 310, 20, 4, #ef4444)
    txtHPm est un texte
    txtHPm vaut convertir_texte(arrondi(mHPNet)) + " / " + convertir_texte(arrondi(mHPMax)) + " PV"
    label(graph, 350, 75, txtHPm, #ffffff, 10)

    // DESSIN DU MONSTRE EN GRAND AU CENTRE
    ellipse(graph, 300, 200, 60, 14, rgba(0, 0, 0, 0.4))
    rectangle_arrondi(graph, 265, 130, 70, 65, 12, mCouleur)
    cercle(graph, 300, 115, 32, mCouleur)

    // Yeux
    cercle(graph, 288, 110, 9, #ffffff)
    cercle(graph, 312, 110, 9, #ffffff)
    cercle(graph, 288, 110, 4, #dc2626)
    cercle(graph, 312, 110, 4, #dc2626)

    // Bouche
    rectangle(graph, 286, 128, 28, 8, #0f172a)
    ptsDent1 est un tableau
    ptsDent1 ajoute 290
    ptsDent1 ajoute 128
    ptsDent1 ajoute 294
    ptsDent1 ajoute 134
    ptsDent1 ajoute 298
    ptsDent1 ajoute 128
    polygone(graph, ptsDent1, #ffffff)

    ptsDent2 est un tableau
    ptsDent2 ajoute 302
    ptsDent2 ajoute 128
    ptsDent2 ajoute 306
    ptsDent2 ajoute 134
    ptsDent2 ajoute 310
    ptsDent2 ajoute 128
    polygone(graph, ptsDent2, #ffffff)

    // Oreilles / Cornes selon le type
    si mType = 1 alors
        ptsOreilleG est un tableau
        ptsOreilleG ajoute 270
        ptsOreilleG ajoute 110
        ptsOreilleG ajoute 240
        ptsOreilleG ajoute 95
        ptsOreilleG ajoute 265
        ptsOreilleG ajoute 125
        polygone(graph, ptsOreilleG, mCouleur)

        ptsOreilleD est un tableau
        ptsOreilleD ajoute 330
        ptsOreilleD ajoute 110
        ptsOreilleD ajoute 360
        ptsOreilleD ajoute 95
        ptsOreilleD ajoute 335
        ptsOreilleD ajoute 125
        polygone(graph, ptsOreilleD, mCouleur)
    sinon si mType = 4 alors
        ptsCorneG est un tableau
        ptsCorneG ajoute 280
        ptsCorneG ajoute 90
        ptsCorneG ajoute 265
        ptsCorneG ajoute 65
        ptsCorneG ajoute 290
        ptsCorneG ajoute 85
        polygone(graph, ptsCorneG, #991b1b)

        ptsCorneD est un tableau
        ptsCorneD ajoute 320
        ptsCorneD ajoute 90
        ptsCorneD ajoute 335
        ptsCorneD ajoute 65
        ptsCorneD ajoute 310
        ptsCorneD ajoute 85
        polygone(graph, ptsCorneD, #991b1b)
    fin si

    rectangle_arrondi(graph, 248, 140, 18, 40, 6, mCouleur)
    rectangle_arrondi(graph, 334, 140, 18, 40, 6, mCouleur)

    // --- 3. INFOS DU JOUEUR ---
    rectangle_arrondi(graph, 20, 240, 560, 75, 8, #0f172a)
    contour(graph, #38bdf8, 1)

    label(graph, 35, 262, "VOTRE HÉROS", #ffffff, 12)

    // PV Joueur
    jHPNet est un nombre
    jHPNet vaut maximum(0, jHP)
    ratioHPj est un nombre
    ratioHPj vaut jHPNet / jHPMax

    label(graph, 35, 283, "PV", #4ade80, 10)
    rectangle_arrondi(graph, 65, 272, 180, 14, 3, #1e293b)
    rectangle_arrondi(graph, 65, 272, ratioHPj * 180, 14, 3, #22c55e)
    label(graph, 115, 283, convertir_texte(arrondi(jHPNet)) + "/" + convertir_texte(jHPMax), #ffffff, 9)

    // Mana Joueur
    ratioMPj est un nombre
    ratioMPj vaut jMana / jManaMax

    label(graph, 35, 303, "MP", #38bdf8, 10)
    rectangle_arrondi(graph, 65, 292, 180, 14, 3, #1e293b)
    rectangle_arrondi(graph, 65, 292, ratioMPj * 180, 14, 3, #38bdf8)
    label(graph, 115, 303, convertir_texte(arrondi(jMana)) + "/" + convertir_texte(jManaMax), #ffffff, 9)

    // Stats
    label(graph, 275, 283, "Attaque : " + convertir_texte(jAttaque), #fbbf24, 11)
    label(graph, 275, 303, "Bouclier : " + convertir_texte(jBouclier), #38bdf8, 11)

    // --- 4. JOURNAL ---
    rectangle_arrondi(graph, 20, 322, 560, 32, 6, #1e293b)
    contour(graph, #475569, 1)
    label(graph, 30, 343, logTxt, #f1f5f9, 10)

    // --- 5. BOUTONS D'ACTION ---
    si etatJeu = 0 alors
        rectangle_arrondi(graph, 20, 362, 270, 56, 6, #dc2626)
        label(graph, 35, 387, "1. ATTAQUER À L'ÉPÉE", #ffffff, 12)
        label(graph, 35, 405, "Gratuit | Regen +8 MP", #fca5a5, 9)

        rectangle_arrondi(graph, 310, 362, 270, 56, 6, #d97706)
        label(graph, 325, 387, "2. BOULE DE FEU", #ffffff, 12)
        label(graph, 325, 405, "Coût: 20 MP | Gros dégâts", #fde68a, 9)

        rectangle_arrondi(graph, 20, 426, 270, 56, 6, #16a34a)
        label(graph, 35, 451, "3. SE SOIGNER", #ffffff, 12)
        label(graph, 35, 469, "Coût: 15 MP | +30 PV & Shield", #86efac, 9)

        rectangle_arrondi(graph, 310, 426, 270, 56, 6, #7c3aed)
        label(graph, 325, 451, "4. FORGE (Amélioration)", #ffffff, 12)
        label(graph, 325, 469, "Coût: 35 Or | +7 Atk, +20 PV", #c4b5fd, 9)

    sinon si etatJeu = 1 alors
        rectangle_arrondi(graph, 150, 375, 300, 65, 8, #16a34a)
        label(graph, 212, 403, "MONSTRE SUIVANT ->", #ffffff, 15)
        label(graph, 198, 424, "Clique ici pour continuer le donjon", #dcfce7, 10)

    sinon si etatJeu = 2 alors
        rectangle_arrondi(graph, 150, 375, 300, 65, 8, #dc2626)
        label(graph, 205, 412, "RECOMMENCER", #ffffff, 16)
        label(graph, 190, 430, "Tu as perdu ! Clique ici pour rejouer", #fee2e2, 10)
    fin si

    affiche graph

    // --- DÉTECTION DES CLICS ET TOURS ---
    tourJoue est un booléen
    tourJoue vaut faux

    cliquer graph dans clicX, clicY

    si etatJeu = 0 alors

        // CLIC SUR 1. ATTAQUE ÉPÉE
        si clique(clicX, clicY, 20, 362, 270, 56) alors
            degatsJ est un nombre
            degatsJ vaut jAttaque + hasard(-2, 3)
            mHP vaut mHP - degatsJ

            jMana vaut minimum(jManaMax, jMana + 8)
            logTxt vaut "Tu attaques à l'épée (-" + convertir_texte(degatsJ) + " PV au monstre) !"
            tourJoue vaut vrai

            // CLIC SUR 2. BOULE DE FEU
        sinon si clique(clicX, clicY, 310, 362, 270, 56) alors
            si jMana >= 20 alors
                jMana vaut jMana - 20
                degatsJ vaut (jAttaque * 2.2) + hasard(-1, 4)
                mHP vaut mHP - degatsJ
                logTxt vaut "Boule de feu magique ! (-" + convertir_texte(arrondi(degatsJ)) + " PV au monstre)"
                tourJoue vaut vrai
            sinon
                logTxt vaut "Pas assez de Mana ! (20 MP requis)"
            fin si

            // CLIC SUR 3. SE SOIGNER
        sinon si clique(clicX, clicY, 20, 426, 270, 56) alors
            si jMana >= 15 alors
                jMana vaut jMana - 15
                jHP vaut minimum(jHPMax, jHP + 30)
                jBouclier vaut jBouclier + 15
                logTxt vaut "Sort de soin lancé ! +30 PV et +15 Bouclier."
                tourJoue vaut vrai
            sinon
                logTxt vaut "Pas assez de Mana ! (15 MP requis)"
            fin si

            // CLIC SUR 4. FORGE
        sinon si clique(clicX, clicY, 310, 426, 270, 56) alors
            si jOr >= 35 alors
                jOr vaut jOr - 35
                jAttaque vaut jAttaque + 7
                jHPMax vaut jHPMax + 20
                jHP vaut jHP + 20
                logTxt vaut "Forge : Attaque +7 et PV Max +20 !"
            sinon
                logTxt vaut "Pas assez d'or ! (35 Or requis)"
            fin si
        fin si

        // TEST MORT DU MONSTRE
        si mHP <= 0 alors
            mHP vaut 0
            gainOr est un nombre
            gainOr vaut 15 + (mNiveau * 10)
            jOr vaut jOr + gainOr
            etatJeu vaut 1
            logTxt vaut "VICTOIRE ! Monstre terrassé (+ " + convertir_texte(gainOr) + " Or) !"
            tourJoue vaut faux
        fin si

        // RIPOSTE DU MONSTRE
        si tourJoue et mHP > 0 alors
            degatsM est un nombre
            degatsM vaut mAttaque + hasard(-2, 2)

            si jBouclier > 0 alors
                si jBouclier >= degatsM alors
                    jBouclier vaut jBouclier - degatsM
                    degatsM vaut 0
                sinon
                    degatsM vaut degatsM - jBouclier
                    jBouclier vaut 0
                fin si
            fin si

            jHP vaut jHP - degatsM
            logTxt vaut logTxt + " | Riposte : - " + convertir_texte(arrondi(degatsM)) + " PV"

            si jHP <= 0 alors
                jHP vaut 0
                etatJeu vaut 2
                logTxt vaut "DÉFAITE... Tu as perdu tous tes PV."
            fin si
        fin si

    sinon si etatJeu = 1 alors
        // CLIC MONSTRE SUIVANT
        si clique(clicX, clicY, 150, 375, 300, 65) alors
            appelle nouveauMonstre()
            etatJeu vaut 0
            jMana vaut jManaMax
            logTxt vaut "Nouveau combat engagé ! À toi de jouer."
        fin si

    sinon si etatJeu = 2 alors
        // CLIC RECOMMENCER
        si clique(clicX, clicY, 150, 375, 300, 65) alors
            jHPMax vaut 100
            jHP vaut 100
            jManaMax vaut 50
            jMana vaut 50
            jAttaque vaut 18
            jBouclier vaut 0
            jOr vaut 0
            mNiveau vaut 0
            appelle nouveauMonstre()
            etatJeu vaut 0
            logTxt vaut "Nouvelle partie commencée ! Bonne chance."
        fin si
    fin si
fin tant que
Pas encore noté
Génération procédurale : création d’un labyrinthe circulaire avec un algorithme DFS
Article

Création d’un labyrinthe circulaire avec un algorithme DFS

Génération procédurale : création d’un labyrinthe circulaire avec un algorithme DFS

Lire l'articleReplier l'article

Ce projet explore la création d’un labyrinthe circulaire en combinant algorithmique, géométrie polaire et rendu graphique.
L’objectif : transformer une structure mathématique en une représentation visuelle cohérente et esthétique.

Structure du labyrinthe

Le labyrinthe repose sur une grille polaire composée :

  • d’anneaux (r),
  • de secteurs angulaires (s),
  • de murs circulaires (arcs),
  • de murs radiaux (lignes).

Chaque cellule (r, s) possède deux types de murs :

  • murArc pour les arcs internes/externes,
  • murRadial pour les séparations angulaires.

Génération : un DFS avec backtracking

L’algorithme utilise une exploration en profondeur (DFS) :

  • Départ au centre.
  • Sélection aléatoire d’un voisin non visité.
  • Suppression du mur correspondant.
  • Retour arrière lorsqu’aucune option n’est disponible.

Ce processus produit un labyrinthe parfait, sans cycles et entièrement connecté.
Le chemin solution est capturé automatiquement au moment où l’algorithme atteint la cellule de sortie.

Rendu graphique : arcs et radiales

Le tracé repose sur une procédure dédiée :

  • dessinerArc(), qui subdivise un arc en segments pour obtenir une courbe fluide.

Les murs sont ensuite dessinés selon leur type, et la solution est mise en valeur par une couleur spécifique (#F59E0B).
Le résultat : un labyrinthe circulaire lisible, harmonieux et entièrement généré par le code.

Ce que ce projet met en lumière

La richesse des représentations polaires dans la génération procédurale.

La capacité d’un DFS à produire des structures visuelles complexes.

L’intérêt de combiner mathématiques, algorithmique et graphisme pour créer des outils pédagogiques ou interactifs.

Programme :

@ procédure de dessin d'un arc filaire
procédure dessinerArc(laToile, x, y, r, angleDeb, angleFin, coul, ep)
    pasAngle est un nombre
    pasAngle vaut (angleFin - angleDeb) / 8

    k est un nombre
    pour k de 0 à 7
        aA est un nombre
        aB est un nombre
        aA vaut radians(angleDeb + k * pasAngle)
        aB vaut radians(angleDeb + (k + 1) * pasAngle)

        x1 est un nombre
        y1 est un nombre
        x2 est un nombre
        y2 est un nombre

        x1 vaut x + r * cosinus(aA)
        y1 vaut y + r * sinus(aA)
        x2 vaut x + r * cosinus(aB)
        y2 vaut y + r * sinus(aB)

        ligne(laToile, x1, y1, x2, y2, coul, ep)
    fin pour
fin procédure

@ Configuration et toile
maToile est une toile
dimension(maToile, 400, 400)
remplir(maToile, #0F172A)

cx est un nombre
cy est un nombre
rMin est un nombre
largeurAnneau est un nombre
nbAnneaux est un nombre
nbSecteurs est un nombre

cx vaut 200
cy vaut 200
rMin vaut 25
largeurAnneau vaut 28
nbAnneaux vaut 5
nbSecteurs vaut 12

nbTotal est un nombre
nbTotal vaut nbAnneaux * nbSecteurs

// Définition de la sortie
sExit est un nombre
sExit vaut hasard(0, nbSecteurs - 1)
targetIdx est un nombre
targetIdx vaut (nbAnneaux - 1) * nbSecteurs + sExit

@ Initialisation des Structures
estVisite est un tableau
murArc est un tableau
murRadial est un tableau

i est un nombre
pour i de 0 à nbTotal - 1
    estVisite ajoute 0
    murArc ajoute 1
    murRadial ajoute 1
fin pour

pileR est un tableau
pileS est un tableau
solR est un tableau
solS est un tableau
solTrouvee est un nombre
solTrouvee vaut 0

// Départ au centre (0, 0)
pileR ajoute 0
pileS ajoute 0
estVisite[0] vaut 1

nbVisites est un nombre
nbVisites vaut 1

@ Algorithme de Génération (DFS) et Capture de la Solution
tant que nbVisites < nbTotal
    idxPile est un nombre
    idxPile vaut longueur(pileR) - 1

    currR est un nombre
    currS est un nombre
    currR vaut pileR[idxPile]
    currS vaut pileS[idxPile]

    voisinsR est un tableau
    voisinsS est un tableau
    voisinsType est un tableau

    // Voisin extérieur (r + 1)
    si currR < nbAnneaux - 1 alors
        idxExt est un nombre
        idxExt vaut (currR + 1) * nbSecteurs + currS
        si estVisite[idxExt] = 0 alors
            voisinsR ajoute (currR + 1)
            voisinsS ajoute currS
            voisinsType ajoute 1
        fin si
    fin si

    // Voisin intérieur (r - 1)
    si currR > 0 alors
        idxInt est un nombre
        idxInt vaut (currR - 1) * nbSecteurs + currS
        si estVisite[idxInt] = 0 alors
            voisinsR ajoute (currR - 1)
            voisinsS ajoute currS
            voisinsType ajoute 2
        fin si
    fin si

    // Voisin horaire (s + 1)
    sHor est un nombre
    sHor vaut (currS + 1) mod nbSecteurs
    idxHor est un nombre
    idxHor vaut currR * nbSecteurs + sHor
    si estVisite[idxHor] = 0 alors
        voisinsR ajoute currR
        voisinsS ajoute sHor
        voisinsType ajoute 3
    fin si

    // Voisin anti-horaire (s - 1)
    sAnti est un nombre
    sAnti vaut (currS - 1 + nbSecteurs) mod nbSecteurs
    idxAnti est un nombre
    idxAnti vaut currR * nbSecteurs + sAnti
    si estVisite[idxAnti] = 0 alors
        voisinsR ajoute currR
        voisinsS ajoute sAnti
        voisinsType ajoute 4
    fin si

    nbVoisins est un nombre
    nbVoisins vaut longueur(voisinsR)

    si nbVoisins > 0 alors
        choix est un nombre
        choix vaut hasard(0, nbVoisins - 1)

        prochainR est un nombre
        prochainS est un nombre
        typeMouv est un nombre

        prochainR vaut voisinsR[choix]
        prochainS vaut voisinsS[choix]
        typeMouv vaut voisinsType[choix]

        idxCourant est un nombre
        idxCourant vaut currR * nbSecteurs + currS

        si typeMouv = 1 alors
            murArc[idxCourant] vaut 0
        sinon si typeMouv = 2 alors
            idxVoisest est un nombre
            idxVoisest vaut prochainR * nbSecteurs + prochainS
            murArc[idxVoisest] vaut 0
        sinon si typeMouv = 3 alors
            murRadial[idxCourant] vaut 0
        sinon si typeMouv = 4 alors
            idxVoisest est un nombre
            idxVoisest vaut currR * nbSecteurs + prochainS
            murRadial[idxVoisest] vaut 0
        fin si

        idxSuivant est un nombre
        idxSuivant vaut prochainR * nbSecteurs + prochainS
        estVisite[idxSuivant] vaut 1
        nbVisites ajoute 1

        pileR ajoute prochainR
        pileS ajoute prochainS

        // Capture du chemin de la solution lors de la découverte
        si idxSuivant = targetIdx et solTrouvee = 0 alors
            kp est un nombre
            pour kp de 0 à longueur(pileR) - 1
                solR ajoute pileR[kp]
                solS ajoute pileS[kp]
            fin pour
            solTrouvee vaut 1
        fin si
    sinon
        idxDernier est un nombre
        idxDernier vaut longueur(pileR) - 1
        pileR supprime idxDernier
        pileS supprime idxDernier
    fin si
fin tant que

// Ouverture du mur de sortie
murArc[targetIdx] vaut 0

@ Dessin des Murs du Labyrinthe
appelle dessinerArc(maToile, cx, cy, rMin, 0, 360, #38BDF8, 2)

an est un nombre
sec est un nombre
pour an de 0 à nbAnneaux - 1
    pour sec de 0 à nbSecteurs - 1
        idxCell est un nombre
        idxCell vaut an * nbSecteurs + sec

        a1deg est un nombre
        a2deg est un nombre
        a1deg vaut (sec * 360) / nbSecteurs
        a2deg vaut ((sec + 1) * 360) / nbSecteurs

        rayonInt est un nombre
        rayonExt est un nombre
        rayonInt vaut rMin + an * largeurAnneau
        rayonExt vaut rayonInt + largeurAnneau

        // Mur arc extérieur
        si murArc[idxCell] = 1 alors
            appelle dessinerArc(maToile, cx, cy, rayonExt, a1deg, a2deg, #38BDF8, 2)
        fin si

        // Mur radial horaire
        si murRadial[idxCell] = 1 alors
            radAngle est un nombre
            radAngle vaut radians(a2deg)

            xStart est un nombre
            yStart est un nombre
            xEnd est un nombre
            yEnd est un nombre

            xStart vaut cx + rayonInt * cosinus(radAngle)
            yStart vaut cy + rayonInt * sinus(radAngle)
            xEnd vaut cx + rayonExt * cosinus(radAngle)
            yEnd vaut cy + rayonExt * sinus(radAngle)

            ligne(maToile, xStart, yStart, xEnd, yEnd, #38BDF8, 2)
        fin si
    fin pour
fin pour

@ Tracé de la Solution (Lignes droites en radial, arcs en circulaires)
nbSol est un nombre
nbSol vaut longueur(solR)

si nbSol > 0 alors
    ks est un nombre
    pour ks de 0 à nbSol - 2
        r1 est un nombre
        s1 est un nombre
        r2 est un nombre
        s2 est un nombre

        r1 vaut solR[ks]
        s1 vaut solS[ks]
        r2 vaut solR[ks + 1]
        s2 vaut solS[ks + 1]

        si r1 = r2 alors
            // Déplacement le long d'un même anneau -> Tracé d'un arc curviligne
            radCell est un nombre
            radCell vaut rMin + (r1 + 0.5) * largeurAnneau

            a1deg est un nombre
            a1deg vaut (s1 + 0.5) * 360 / nbSecteurs

            pasSec est un nombre
            pasSec vaut 360 / nbSecteurs

            a2deg est un nombre
            si (s1 + 1) mod nbSecteurs = s2 alors
                a2deg vaut a1deg + pasSec
            sinon
                a2deg vaut a1deg - pasSec
            fin si

            appelle dessinerArc(maToile, cx, cy, radCell, a1deg, a2deg, #F59E0B, 3)
        sinon
            // Déplacement entre deux anneaux -> Tracé d'une ligne radiale
            angRad est un nombre
            angRad vaut radians((s1 + 0.5) * 360 / nbSecteurs)

            rad1 est un nombre
            rad2 est un nombre
            rad1 vaut rMin + (r1 + 0.5) * largeurAnneau
            rad2 vaut rMin + (r2 + 0.5) * largeurAnneau

            x1 est un nombre
            y1 est un nombre
            x2 est un nombre
            y2 est un nombre

            x1 vaut cx + rad1 * cosinus(angRad)
            y1 vaut cy + rad1 * sinus(angRad)
            x2 vaut cx + rad2 * cosinus(angRad)
            y2 vaut cy + rad2 * sinus(angRad)

            ligne(maToile, x1, y1, x2, y2, #F59E0B, 3)
        fin si
    fin pour

    // Extension finale vers l'extérieur du labyrinthe
    rDernier est un nombre
    sDernier est un nombre
    rDernier vaut solR[nbSol - 1]
    sDernier vaut solS[nbSol - 1]

    angFin est un nombre
    angFin vaut radians((sDernier + 0.5) * 360 / nbSecteurs)

    radCenterCell est un nombre
    radOut est un nombre
    radCenterCell vaut rMin + (rDernier + 0.5) * largeurAnneau
    radOut vaut rMin + nbAnneaux * largeurAnneau + 12

    xStartSol est un nombre
    yStartSol est un nombre
    xEndSol est un nombre
    yEndSol est un nombre

    xStartSol vaut cx + radCenterCell * cosinus(angFin)
    yStartSol vaut cy + radCenterCell * sinus(angFin)
    xEndSol vaut cx + radOut * cosinus(angFin)
    yEndSol vaut cy + radOut * sinus(angFin)

    ligne(maToile, xStartSol, yStartSol, xEndSol, yEndSol, #F59E0B, 3)
fin si

affiche maToile
Pas encore noté