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





