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

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é
Construire un jeu complet : un excellent exercice de logique et d’expérience utilisateur.
Article

Construire un jeu complet

Construire un jeu complet : un excellent exercice de logique et d’expérience utilisateur.

Lire l'articleReplier l'article

Ce projet de développement d’un jeu de Tic-Tac-Toe (morpion) montre comment un exercice simple peut devenir un terrain d’apprentissage riche. Le programme intègre une interface graphique, une détection de victoire, une IA basique et une boucle de rejouabilité, ce qui permet de revisiter plusieurs fondamentaux du développement.

Les points clés abordés sont les suivants :

  • structuration du code,
  • gestion des états,
  • interaction utilisateur,
  • conception d’une IA simple,
  • mise en place d’un retour visuel clair.

Détection du gagnant : une logique essentielle La fonction de détection de victoire repose sur l’analyse des huit combinaisons gagnantes possibles.

La méthode consiste à :

  • définir les alignements potentiels,
  • extraire les indices concernés,
  • comparer les valeurs du plateau.

Dès que trois symboles identiques sont alignés, la victoire est identifiée.
Cette logique est concise, lisible et facilement réutilisable.

Dessin du plateau : transformer la logique en interface

La fonction de dessin génère une grille lisible et moderne :

  • fond uniforme,
  • quadrillage clair,
  • représentation graphique des X et O,
  • rafraîchissement visuel après chaque action.

Ce rendu améliore la compréhension du joueur et renforce l’expérience globale.

Le bot “facile” : une IA simple mais pertinente

Le bot applique une stratégie en trois étapes :

  • tenter de gagner si une opportunité existe,
  • bloquer le joueur si nécessaire,
  • jouer aléatoirement dans les autres cas.

Cette approche crée un adversaire accessible tout en introduisant une logique décisionnelle intéressante.

La boucle de jeu : une expérience fluide et continue

Le programme principal :

  • initialise une nouvelle partie,
  • attend les interactions du joueur,
  • met à jour le plateau,
  • vérifie les conditions de victoire,
  • déclenche le coup du bot,
  • affiche un écran de fin,
  • permet de relancer une partie immédiatement.

Une structure simple, robuste et agréable à utiliser.

Conclusion

Ce projet démontre qu’un jeu classique peut devenir un excellent support pour travailler la logique algorithmique, la structuration du code, la gestion d’une interface graphique et l’intégration d’une IA basique.
Un exercice complet, formateur et facilement extensible.

Si tu veux une version plus courte, plus technique ou orientée recrutement, je peux t’en préparer une autre.

Programme :

// -----------------------------------------------------------
//   FONCTION : y a-t-il un gagnant ?
// -----------------------------------------------------------
fonction gagnant(plateau)
    lignes est un tableau
    lignes ajoute "0,1,2"
    lignes ajoute "3,4,5"
    lignes ajoute "6,7,8"
    lignes ajoute "0,3,6"
    lignes ajoute "1,4,7"
    lignes ajoute "2,5,8"
    lignes ajoute "0,4,8"
    lignes ajoute "2,4,6"

    k est un nombre
    pour k de 0 a 7
        cases est un tableau
        cases vaut decouper(lignes[k], ",")
        x est un nombre
        b est un nombre
        d est un nombre
        x vaut convertir_nombre(cases[0])
        b vaut convertir_nombre(cases[1])
        d vaut convertir_nombre(cases[2])

        si plateau[x] <> "" et plateau[x] = plateau[b] et plateau[b] = plateau[d] alors
            retourne plateau[x]
        fin si
    fin pour

    retourne ""
fin fonction

// -----------------------------------------------------------
//   FONCTION : dessiner le plateau (sans chiffres)
// -----------------------------------------------------------
fonction dessiner(plateau)
    t est une toile
    t vaut dimension(t, 300, 300)
    t vaut remplir(t, "#1abc9c")

    // --- Le quadrillage ---
    ligne(t, 100, 0, 100, 300, "#159a80", 3)
    ligne(t, 200, 0, 200, 300, "#159a80", 3)
    ligne(t, 0, 100, 300, 100, "#159a80", 3)
    ligne(t, 0, 200, 300, 200, "#159a80", 3)

    c est un nombre
    pour c de 0 a 8
        marque est un texte
        marque vaut plateau[c]

        col est un nombre
        lig est un nombre
        col vaut c mod 3
        lig vaut arrondi_inferieur(c / 3)

        cx est un nombre
        cy est un nombre
        cx vaut col * 100
        cy vaut lig * 100

        si marque = "X" alors
            ligne(t, cx + 25, cy + 25, cx + 75, cy + 75, "#3a3a3a", 10)
            ligne(t, cx + 75, cy + 25, cx + 25, cy + 75, "#3a3a3a", 10)
        sinon si marque = "O" alors
            cercle(t, cx + 50, cy + 50, 28, "#faf3e0")
            cercle(t, cx + 50, cy + 50, 18, "#1abc9c")
        fin si
    fin pour

    affiche t
    retourne t
fin fonction

// -----------------------------------------------------------
//   FONCTION : créer l'écran de fin
// -----------------------------------------------------------
fonction afficher_fin(resultat)
    t_fin est une toile
    t_fin vaut dimension(t_fin, 300, 300)
    t_fin vaut remplir(t_fin, "#1abc9c")

    si resultat = "X" alors
        label(t_fin, 25, 130, "?? TU AS GAGNÉ !", "#ffffff", 24)
    sinon si resultat = "O" alors
        label(t_fin, 20, 130, "?? LE BOT A GAGNÉ", "#ffffff", 22)
    sinon
        label(t_fin, 55, 130, "?? MATCH NUL", "#ffffff", 24)
    fin si

    label(t_fin, 50, 180, "Clique pour rejouer", "#0e6e5c", 16)

    affiche t_fin
    retourne t_fin
fin fonction

// -----------------------------------------------------------
//   FONCTION : le bot facile (O)
// -----------------------------------------------------------
fonction coup_du_bot(plateau)
    c est un nombre
    libres est un tableau

    pour c de 0 a 8
        si plateau[c] = "" alors
            libres ajoute c
        fin si
    fin pour

    si longueur(libres) = 0 alors
        retourne - 1
    fin si

    // Une chance sur deux (hasard 0 ou 1) de jouer intelligent, sinon il joue au hasard
    chance est un nombre
    chance vaut hasard(0, 1)

    si chance = 1 alors
        // 1. Essayer de gagner
        pour c de 0 a 8
            si plateau[c] = "" alors
                plateau[c] vaut "O"
                si gagnant(plateau) = "O" alors
                    plateau[c] vaut ""
                    retourne c
                fin si
                plateau[c] vaut ""
            fin si
        fin pour

        // 2. Essayer de bloquer le joueur
        pour c de 0 a 8
            si plateau[c] = "" alors
                plateau[c] vaut "X"
                si gagnant(plateau) = "X" alors
                    plateau[c] vaut ""
                    retourne c
                fin si
                plateau[c] vaut ""
            fin si
        fin pour
    fin si

    // 3. Sinon, choix totalement aléatoire parmi les cases libres
    index est un nombre
    index vaut hasard(0, longueur(libres) - 1)
    retourne libres[index]
fin fonction

// -----------------------------------------------------------
//   PROGRAMME PRINCIPAL — la boucle du jeu infinie
// -----------------------------------------------------------

g est un tableau
maToile est une toile
toile_fin est une toile
fini est un booléen
coups est un nombre
x, y est un nombre
choix est un nombre
c, i, col, lig, pos est un nombre
vainqueur est un texte

// Boucle principale infinie pour rejouer
tant que vrai

    // --- INITIALISATION D'UNE NOUVELLE PARTIE ---
    vide g
    pour i de 1 a 9
        g ajoute ""
    fin pour

    fini vaut faux
    coups vaut 0
    vainqueur vaut ""
    maToile vaut dessiner(g)

    // --- BOUCLE D'UNE PARTIE ---
    tant que non fini
        cliquer maToile dans x, y

        choix vaut -1

        pour c de 0 a 8
            col vaut c mod 3
            lig vaut arrondi_inferieur(c / 3)

            si clique(x, y, col * 100, lig * 100, 100, 100) alors
                choix vaut c
            fin si
        fin pour

        si choix = -1 alors
            continue
        fin si

        si g[choix] <> "" alors
            maToile vaut dessiner(g)
        sinon
            // 1. Le joueur joue
            g[choix] vaut "X"
            coups ajoute 1

            maToile vaut dessiner(g)

            si gagnant(g) = "X" alors
                vainqueur vaut "X"
                fini vaut vrai
            sinon si coups = 9 alors
                vainqueur vaut "NUL"
                fini vaut vrai
            sinon
                // 2. Le bot joue
                pos vaut coup_du_bot(g)
                g[pos] vaut "O"
                coups ajoute 1

                maToile vaut dessiner(g)

                si gagnant(g) = "O" alors
                    vainqueur vaut "O"
                    fini vaut vrai
                sinon si coups = 9 alors
                    vainqueur vaut "NUL"
                    fini vaut vrai
                fin si
            fin si
        fin si
    fin tant que

    // --- FIN DE PARTIE ---
    toile_fin vaut afficher_fin(vainqueur)
    cliquer toile_fin dans x, y
fin tant que
Pas encore noté
Vue 3ème Personne : un moteur de perspective dynamique pour mondes infinis
Article

Vue 3ème Personne

Vue 3ème Personne : un moteur de perspective dynamique pour mondes infinis

Lire l'articleReplier l'article

La création d’un moteur de rendu en vue à la troisième personne est un défi passionnant, surtout lorsqu’il s’agit de représenter un monde infini avec une perspective qui s’agrandit naturellement à mesure que le joueur s’approche des éléments.

Ce projet explore une approche mathématique et procédurale permettant de simuler une caméra suiveuse immersive, sans modèle 3D, uniquement via des projections et des polygones.

Un moteur basé sur la perspective et la profondeur

Horizon, ciel et ambiance

Le rendu commence par une ligne d’horizon fixe et un ciel stylisé.
Cette base visuelle sert de repère pour la perspective et renforce l’effet de profondeur.

Rendu du terrain : l’algorithme du peintre

Le moteur dessine le monde du fond vers l’avant, une technique inspirée de l’algorithme du peintre.
Chaque couche de terrain est calculée en fonction de sa distance au joueur :

  • plus la tuile est éloignée, plus elle est petite
  • plus elle est proche, plus elle est grande

La projection utilise une formule simple mais efficace :

  • sLoin = 180 / dLoin
  • sPres = 180 / dPres
  • sCentre = 180 / dCentre

Ces valeurs déterminent la taille apparente des tuiles selon leur profondeur.

Projection 3D simulée

Chaque tuile est représentée par un quadrilatère dont les sommets sont calculés pour simuler une perspective :

  • sommet haut gauche
  • sommet haut droit
  • sommet bas droit
  • sommet bas gauche

Cette projection donne l’illusion d’un sol en 3D, sans utiliser de modèles polygonaux complexes.

Génération procédurale du terrain

Le type de sol est déterminé par des règles mathématiques :

  • Herbe
  • Chemins (via mod 14)
  • Eau (via une formule organique)

Cette logique crée un monde cohérent, infini et varié, sans stockage de données.

Décors avec grandissement dynamique
Les arbres, rochers et maisons sont générés procéduralement et agrandissent naturellement lorsqu’ils se rapprochent du joueur.

L’échelle dépend de la profondeur :

  • hTaille = sCentre * 0.85

Ce facteur donne un effet d’approche très immersif, proche d’une caméra 3D.

Personnage principal en vue 3P

Le joueur est représenté par un personnage stylisé placé en bas de l’écran.
La caméra ne bouge pas : c’est le monde qui se déplace autour du joueur, renforçant l’effet de vue à la troisième personne.

Déplacements et collisions

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

  • impossibilité d’entrer dans l’eau
  • impossibilité d’entrer dans une maison
  • Aucune carte de collision n’est stockée : tout est calculé à la volée.

Pourquoi cette approche est intéressante ?

  • Perspective dynamique sans 3D
  • Monde infini généré en temps réel
  • Effet caméra suiveuse sans transformation complexe
  • Décors qui grandissent naturellement
  • Performances optimisées grâce au rendu du fond vers l’avant
  • Base idéale pour un jeu d’aventure, un RPG ou un simulateur

Programme :

@ Vue à la troisième personne (3P) avec grandissement de perspective

toile1 est une toile
dimension(toile1, 400, 400)

// 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, #0f172a)

    // 1. Ciel et ligne d'horizon
    rectangle(toile1, 0, 0, 400, 120, #0f172a)
    cercle(toile1, 320, 40, 20, #f1c40f)

    // 2. Rendu du terrain du fond vers l'avant (Algorithme du peintre)
    iY est un nombre
    pour iY de 0 à 15
        rY est un nombre
        rY vaut 15 - iY

        rX est un nombre
        pour rX de - 8 à 8
            gx est un nombre
            gy est un nombre
            gx vaut jX + rX
            gy vaut jY + rY

            nx est un nombre
            ny est un nombre
            nx vaut gx + 50000
            ny vaut gy + 50000

            // Projection en perspective 3D
            dLoin est un nombre
            dPres est un nombre
            dLoin vaut rY + 0.5 + 2.0
            dPres vaut rY - 0.5 + 2.0

            sLoin est un nombre
            sPres est un nombre
            sLoin vaut 180 / dLoin
            sPres vaut 180 / dPres

            xHautG est un nombre
            yHautG est un nombre
            xHautD est un nombre
            yHautD est un nombre
            xBasD est un nombre
            yBasD est un nombre
            xBasG est un nombre
            yBasG est un nombre

            xHautG vaut 200 + (rX - 0.5) * sLoin * 1.2
            yHautG vaut 120 + sLoin * 2.5
            xHautD vaut 200 + (rX + 0.5) * sLoin * 1.2
            yHautD vaut 120 + sLoin * 2.5

            xBasD vaut 200 + (rX + 0.5) * sPres * 1.2
            yBasD vaut 120 + sPres * 2.5
            xBasG vaut 200 + (rX - 0.5) * sPres * 1.2
            yBasG vaut 120 + sPres * 2.5

            dCentre est un nombre
            dCentre vaut rY + 2.0
            sCentre est un nombre
            sCentre vaut 180 / dCentre

            cx est un nombre
            cy est un nombre
            cx vaut 200 + rX * sCentre * 1.2
            cy vaut 120 + sCentre * 2.5

            typeSol est un nombre
            typeSol vaut 0

            si nx mod 14 = 0 ou ny mod 14 = 0 alors
                typeSol vaut 1
            sinon si (nx * 3 + ny * 2) mod 31 < 2 alors
                typeSol vaut 2
            fin si

            ptsSol est un tableau
            ptsSol ajoute xHautG
            ptsSol ajoute yHautG
            ptsSol ajoute xHautD
            ptsSol ajoute yHautD
            ptsSol ajoute xBasD
            ptsSol ajoute yBasD
            ptsSol ajoute xBasG
            ptsSol ajoute yBasG

            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)

            // Décors
            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
                sinon si hash < 14 alors
                    typeDecor vaut 2
                sinon si hash = 15 alors
                    typeDecor vaut 3
                fin si
            fin si

            // Échelle augmentée pour accentuer l'effet d'approche
            hTaille est un nombre
            hTaille vaut sCentre * 0.85

            si typeDecor = 1 alors
                // Arbre
                ellipse(toile1, cx, cy, hTaille * 0.3, hTaille * 0.1, #0e171e)
                rectangle(toile1, cx - hTaille * 0.08, cy - hTaille * 0.6, hTaille * 0.16, hTaille * 0.6, #5d4037)
                cercle(toile1, cx, cy - hTaille * 0.85, hTaille * 0.35, #2e7d32)
            sinon si typeDecor = 2 alors
                // Rocher
                ellipse(toile1, cx, cy, hTaille * 0.25, hTaille * 0.1, #0e171e)
                cercle(toile1, cx, cy - hTaille * 0.15, hTaille * 0.2, #7f8c8d)
            sinon si typeDecor = 3 alors
                // Maison
                mLargeur est un nombre
                mHauteur est un nombre
                mLargeur vaut hTaille * 0.7
                mHauteur vaut hTaille * 0.5

                rectangle(toile1, cx - mLargeur / 2, cy - mHauteur, mLargeur, mHauteur, #bdc3c7)
                toit3P est un tableau
                toit3P ajoute cx - mLargeur * 0.55
                toit3P ajoute cy - mHauteur
                toit3P ajoute cx
                toit3P ajoute cy - mHauteur - hTaille * 0.35
                toit3P ajoute cx + mLargeur * 0.55
                toit3P ajoute cy - mHauteur
                polygone(toile1, toit3P, #c0392b)
                rectangle(toile1, cx - mLargeur * 0.15, cy - mHauteur * 0.5, mLargeur * 0.3, mHauteur * 0.5, #5d4037)
            fin si
        fin pour
    fin pour

    // 3. Personnage principal
    pX est un nombre
    pY est un nombre
    pX vaut 200
    pY vaut 340

    ellipse(toile1, pX, pY + 12, 18, 6, #0a0f14)
    rectangle(toile1, pX - 10, pY - 20, 20, 28, #e74c3c)
    rectangle(toile1, pX - 6, pY - 16, 12, 18, #795548)
    cercle(toile1, pX, pY - 28, 10, #f1c40f)

    // Interface HUD
    contour(toile1, #0)
    rectangle(toile1, 0, 0, 400, 40, rgba(17, 17, 17, 0.2))
    label(toile1, 10, 16, "Vue 3ème Personne (Caméra Suiveuse)", #ffffff, 12)

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

    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

    // Détection de collision
    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

    si solFutur <> 2 et decorFutur <> 3 alors
        jX vaut nouvX
        jY vaut nouvY
    fin si
fin tant que
Pas encore noté
Sokoban en 3D isométrique : un projet mêlant algorithmie, génération procédurale et rendu graphique
Article

Sokoban en 3D isométrique

Sokoban en 3D isométrique : un projet mêlant algorithmie, génération procédurale et rendu graphique

Lire l'articleReplier l'article

Ce programme illustre comment l’algorithmie, la logique de jeu et le rendu visuel peuvent s’intégrer pour créer une expérience complète inspirée du célèbre Sokoban.
L’ensemble repose sur une architecture qui combine génération procédurale, gestion des interactions et affichage isométrique 3D.

Génération procédurale d’un niveau jouable

Le système construit automatiquement une salle fermée, y place des caisses, des cibles et un joueur, puis applique une série de mouvements aléatoires pour obtenir une configuration réaliste et jouable.

Points clés :

  • Création d’une grille 10×10 avec murs et zones libres.
  • Placement des caisses uniquement sur des cibles, jamais sur les bords.
  • Mélange contrôlé via des déplacements simulés.

Validation stricte du niveau :

  • bon nombre de caisses,
  • aucune caisse bloquée,
  • au moins une caisse hors cible pour garantir un vrai défi.

Extrait du programme :
« si caisseSurBord() alors valide vaut faux »
« si caissesHorsCible() = 0 alors valide vaut faux »

Chaque partie démarre ainsi avec un puzzle cohérent et intéressant.

Logique de déplacement et gestion des collisions
Le déplacement du joueur suit les règles classiques du Sokoban :

  • impossibilité de traverser les murs,
  • poussée des caisses uniquement si l’espace derrière est libre,
  • distinction entre caisse sur cible (*) et caisse hors cible (b).

Extrait du programme :
« si estCible(al, ac) alors poser(al, ac, "") sinon poser(al, ac, "b") »

Cette mécanique garantit une fidélité totale aux principes du jeu original.

Rendu isométrique 3D : immersion et lisibilité

La partie graphique repose sur un rendu isométrique détaillé :

  • sols en losange,
  • murs en volume,
  • caisses en 3D avec variations visuelles,
  • personnage stylisé avec ombre, tronc, tête et yeux.

Chaque élément est dessiné via des polygones, ce qui crée une ambiance visuelle cohérente et agréable.

Extrait du programme :
« polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c) »

Le résultat est une carte isométrique claire, immersive et parfaitement adaptée à un jeu de réflexion.

Une architecture complète de mini-jeu

Ce projet combine :

  • génération procédurale,
  • gestion des états,
  • interactions clavier,
  • rendu graphique avancé,
  • boucle de jeu complète,
  • détection de victoire.

Un exemple concret de la manière dont l’algorithmie peut servir la créativité, et comment un ensemble de fonctions peut devenir une expérience ludique cohérente.

Perspectives

Ce type de moteur ouvre la voie à :

  • la création automatique de puzzles,
  • l’ajout d’animations,
  • l’intégration dans un moteur plus large,
  • l’export vers le web ou le mobile,
  • l’expérimentation IA autour de la résolution de niveaux.

Programme :

// ============================================================
//  PARAMETRES
// ============================================================
lignes est un nombre
lignes vaut 10
colonnes est un nombre
colonnes vaut 10
nbCaisses est un nombre
nbCaisses vaut 5
melange est un nombre
melange vaut 60

// ============================================================
//  GRILLE
// ============================================================
niveau est un tableau
cibles est un tableau

jl est un nombre
jc est un nombre
jl vaut 0
jc vaut 0

fonction caseAt(li, co)
    ln est un texte
    ln vaut niveau[li]
    retourne ln[co]
fin fonction

fonction cibleAt(li, co)
    ln est un texte
    ln vaut cibles[li]
    retourne ln[co]
fin fonction

procedure poser(li, co, nc)
    ln est un texte
    ln vaut niveau[li]
    nouvelle est un texte
    nouvelle vaut ""
    k est un nombre
    k vaut 1
    tant que k <= longueur(ln)
        si k = co alors
            nouvelle vaut nouvelle + nc
        sinon
            nouvelle vaut nouvelle + ln[k]
        fin si
        k vaut k + 1
    fin tant que
    niveau[li] vaut nouvelle
fin procedure

procedure poserCible(li, co)
    ln est un texte
    ln vaut cibles[li]
    nouvelle est un texte
    nouvelle vaut ""
    k est un nombre
    k vaut 1
    tant que k <= longueur(ln)
        si k = co alors
            nouvelle vaut nouvelle + "c"
        sinon
            nouvelle vaut nouvelle + ln[k]
        fin si
        k vaut k + 1
    fin tant que
    cibles[li] vaut nouvelle
fin procedure

fonction estCible(li, co)
    si cibleAt(li, co) = "c" alors
        retourne vrai
    fin si
    retourne faux
fin fonction

fonction estLibre(li, co)
    ca est un texte
    ca vaut caseAt(li, co)
    si (ca = "#") ou (ca = "b") ou (ca = "*") alors
        retourne faux
    fin si
    retourne vrai
fin fonction

// ============================================================
//  GENERATION DU NIVEAU
// ============================================================
procedure construireSalle()
    vide niveau
    vide cibles
    li est un nombre
    li vaut 0
    tant que li < lignes
        ligneN est un texte
        ligneN vaut ""
        ligneC est un texte
        ligneC vaut ""
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            bord est un booleen
            bord vaut faux
            si (li = 0) ou (li = lignes - 1) alors
                bord vaut vrai
            fin si
            si (co = 1) ou (co = colonnes) alors
                bord vaut vrai
            fin si
            si bord alors
                ligneN vaut ligneN + "#"
            sinon
                ligneN vaut ligneN + "."
            fin si
            ligneC vaut ligneC + "."
            co vaut co + 1
        fin tant que
        niveau ajoute ligneN
        cibles ajoute ligneC
        li vaut li + 1
    fin tant que
fin procedure

procedure placerCaisses()
    poses est un nombre
    poses vaut 0
    essais est un nombre
    essais vaut 0
    tant que (poses < nbCaisses) et (essais < 800)
        essais vaut essais + 1
        rl est un nombre
        rl vaut hasard(2, lignes - 3)
        rc est un nombre
        rc vaut hasard(3, colonnes - 2)
        si caseAt(rl, rc) = "." alors
            appelle poser(rl, rc, "*")
            appelle poserCible(rl, rc)
            poses vaut poses + 1
        fin si
    fin tant que
fin procedure

procedure placerJoueur()
    ok est un booleen
    ok vaut faux
    essais est un nombre
    essais vaut 0
    tant que (non ok) et (essais < 800)
        essais vaut essais + 1
        rl est un nombre
        rl vaut hasard(1, lignes - 2)
        rc est un nombre
        rc vaut hasard(2, colonnes - 1)
        si caseAt(rl, rc) = "." alors
            appelle poser(rl, rc, "j")
            jl vaut rl
            jc vaut rc
            ok vaut vrai
        fin si
    fin tant que
fin procedure

procedure tirerUnCoup()
    dl est un nombre
    dc est un nombre
    choix est un nombre
    choix vaut hasard(1, 4)
    dl vaut 0
    dc vaut 0
    si choix = 1 alors
        dl vaut - 1
    sinon si choix = 2 alors
        dl vaut 1
    sinon si choix = 3 alors
        dc vaut - 1
    sinon
        dc vaut 1
    fin si

    reculL est un nombre
    reculL vaut jl + dl
    reculC est un nombre
    reculC vaut jc + dc
    caisseL est un nombre
    caisseL vaut jl - dl
    caisseC est un nombre
    caisseC vaut jc - dc

    si estLibre(reculL, reculC) alors
        derriere est un texte
        derriere vaut caseAt(caisseL, caisseC)
        tire est un booleen
        tire vaut faux
        si (derriere = "b") ou (derriere = "*") alors
            surBord est un booleen
            surBord vaut faux
            si (jl = 1) ou (jl = lignes - 2) alors
                surBord vaut vrai
            fin si
            si (jc = 2) ou (jc = colonnes - 1) alors
                surBord vaut vrai
            fin si
            si (non surBord) et (hasard(0, 1) = 1) alors
                tire vaut vrai
            fin si
        fin si

        si estCible(jl, jc) alors
            appelle poser(jl, jc, "c")
        sinon
            appelle poser(jl, jc, ".")
        fin si

        si tire alors
            si estCible(caisseL, caisseC) alors
                appelle poser(caisseL, caisseC, "c")
            sinon
                appelle poser(caisseL, caisseC, ".")
            fin si
            si estCible(jl, jc) alors
                appelle poser(jl, jc, "*")
            sinon
                appelle poser(jl, jc, "b")
            fin si
        fin si

        appelle poser(reculL, reculC, "j")
        jl vaut reculL
        jc vaut reculC
    fin si
fin procedure

fonction caisseSurBord()
    li est un nombre
    li vaut 1
    tant que li < lignes - 1
        co est un nombre
        co vaut 2
        tant que co < colonnes
            car est un texte
            car vaut caseAt(li, co)
            si (car = "b") ou (car = "*") alors
                si (li = 1) ou (li = lignes - 2) ou (co = 2) ou (co = colonnes - 1) alors
                    retourne vrai
                fin si
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne faux
fin fonction

fonction compteCaisses()
    n est un nombre
    n vaut 0
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            car est un texte
            car vaut caseAt(li, co)
            si (car = "b") ou (car = "*") alors
                n vaut n + 1
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne n
fin fonction

fonction caissesHorsCible()
    n est un nombre
    n vaut 0
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            si caseAt(li, co) = "b" alors
                n vaut n + 1
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne n
fin fonction

procedure genererNiveau()
    valide est un booleen
    valide vaut faux
    tentatives est un nombre
    tentatives vaut 0

    tant que (non valide) et (tentatives < 60)
        tentatives vaut tentatives + 1
        appelle construireSalle()
        appelle placerCaisses()
        appelle placerJoueur()
        n est un nombre
        n vaut 0
        tant que n < melange
            appelle tirerUnCoup()
            n vaut n + 1
        fin tant que

        valide vaut vrai
        si compteCaisses() <> nbCaisses alors
            valide vaut faux
        fin si
        si caisseSurBord() alors
            valide vaut faux
        fin si
        si caissesHorsCible() = 0 alors
            valide vaut faux
        fin si
    fin tant que
fin procedure

// ============================================================
//  DESSIN ISOMETRIQUE 3D
// ============================================================
tw est un nombre
tw vaut 56
th est un nombre
th vaut 28

g est une toile
dimension(g, 650, 400)

procedure dessiner()
    dégradé(g, [#1e2a38, #2c3e50])

    ox est un nombre
    ox vaut 300
    oy est un nombre
    oy vaut 45  // <-- Ajusté pour centrer parfaitement la map 10x10 en hauteur

    somm est un nombre
    somm vaut 1
    tant que somm <= lignes + colonnes
        li est un nombre
        li vaut 0
        tant que li < lignes
            co est un nombre
            co vaut 1
            tant que co <= colonnes
                si (li + co) = somm alors
                    car est un texte
                    car vaut caseAt(li, co)

                    cx est un nombre
                    cx vaut ox + (co - li) * (tw / 2)
                    cy est un nombre
                    cy vaut oy + (co + li) * (th / 2)

                    // 1. Sol
                    si car <> "#" alors
                        polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c)
                        ligne(g, cx, cy - th/2, cx + tw/2, cy, #2c3e50, 1)
                        ligne(g, cx + tw/2, cy, cx, cy + th/2, #2c3e50, 1)
                        ligne(g, cx, cy + th/2, cx - tw/2, cy, #2c3e50, 1)
                        ligne(g, cx - tw/2, cy, cx, cy - th/2, #2c3e50, 1)

                        // Cible : caisse 3D rouge transparente
                        si estCible(li, co) alors
                            si car <> "*" alors
                                hcib est un nombre
                                hcib vaut 24
                                polygone(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.35))
                                polygone(g, cx - tw/2, cy - hcib, cx, cy + th/2 - hcib, cx, cy + th/2, cx - tw/2, cy, rgba(192, 57, 43, 0.45))
                                polygone(g, cx, cy + th/2 - hcib, cx + tw/2, cy - hcib, cx + tw/2, cy, cx, cy + th/2, rgba(146, 43, 33, 0.45))

                                ligne(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx - tw/2, cy - hcib, cx, cy - th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx, cy + th/2 - hcib, cx, cy + th/2, rgba(231, 76, 60, 0.8), 1)
                            fin si
                        fin si
                    fin si

                    // 2. Mur
                    si car = "#" alors
                        h est un nombre
                        h vaut 28
                        polygone(g, cx, cy - th/2 - h, cx + tw/2, cy - h, cx, cy + th/2 - h, cx - tw/2, cy - h, #243342)
                        polygone(g, cx - tw/2, cy - h, cx, cy + th/2 - h, cx, cy + th/2, cx - tw/2, cy, #141c26)
                        polygone(g, cx, cy + th/2 - h, cx + tw/2, cy - h, cx + tw/2, cy, cx, cy + th/2, #1a2531)
                        ligne(g, cx, cy + th/2 - h, cx, cy + th/2, #101820, 1)
                        ligne(g, cx, cy + th/2 - h, cx - tw/2, cy - h, #101820, 1)
                        ligne(g, cx, cy + th/2 - h, cx + tw/2, cy - h, #101820, 1)
                    fin si

                    // 3. Caisse
                    si (car = "b") ou (car = "*") alors
                        cTop est un texte
                        cTop vaut #f5b041
                        cLeft est un texte
                        cLeft vaut #d68910
                        cRight est un texte
                        cRight vaut #b9770e

                        si car = "*" alors
                            cTop vaut #2ecc71
                            cLeft vaut #27ae60
                            cRight vaut #1e8449
                        fin si

                        hc est un nombre
                        hc vaut 24
                        polygone(g, cx, cy - th/2 - hc, cx + tw/2, cy - hc, cx, cy + th/2 - hc, cx - tw/2, cy - hc, cTop)
                        polygone(g, cx - tw/2, cy - hc, cx, cy + th/2 - hc, cx, cy + th/2, cx - tw/2, cy, cLeft)
                        polygone(g, cx, cy + th/2 - hc, cx + tw/2, cy - hc, cx + tw/2, cy, cx, cy + th/2, cRight)
                        ligne(g, cx, cy + th/2 - hc, cx, cy + th/2, #875c06, 1)
                    fin si

                    // 4. Joueur (Personnage amélioré)
                    si car = "j" alors
                        // Ombre au sol
                        ellipse(g, cx, cy, 18, 9, rgba(0, 0, 0, 0.4))

                        // Dimensions du corps
                        pw est un nombre
                        pw vaut 20
                        ph est un nombre
                        ph vaut 10
                        pj est un nombre
                        pj vaut 18 // hauteur du tronc

                        // Tronc (Pull rouge)
                        polygone(g, cx, cy - ph/2 - pj, cx + pw/2, cy - pj, cx, cy + ph/2 - pj, cx - pw/2, cy - pj, #e74c3c)
                        polygone(g, cx - pw/2, cy - pj, cx, cy + ph/2 - pj, cx, cy + ph/2, cx - pw/2, cy, #c0392b)
                        polygone(g, cx, cy + ph/2 - pj, cx + pw/2, cy - pj, cx + pw/2, cy, cx, cy + ph/2, #922b21)

                        // Tête (Cercle couleur peau)
                        cTop est un nombre
                        cTop vaut cy - pj - 10
                        cercle(g, cx, cTop, 11, #f5cba7)

                        // Yeux
                        cercle(g, cx - 4, cTop - 2, 2, #2c3e50)
                        cercle(g, cx + 4, cTop - 2, 2, #2c3e50)
                    fin si

                fin si
                co vaut co + 1
            fin tant que
            li vaut li + 1
        fin tant que
        somm vaut somm + 1
    fin tant que

    affiche g
fin procedure

// ============================================================
//  DEPLACEMENT
// ============================================================
procedure deplacer(dl, dc)
    nl est un nombre
    nl vaut jl + dl
    nc est un nombre
    nc vaut jc + dc
    dest est un texte
    dest vaut caseAt(nl, nc)
    bloque est un booleen
    bloque vaut faux

    si dest = "#" alors
        bloque vaut vrai
    fin si

    si (non bloque) et ((dest = "b") ou (dest = "*")) alors
        al est un nombre
        al vaut nl + dl
        ac est un nombre
        ac vaut nc + dc
        apres est un texte
        apres vaut caseAt(al, ac)
        si (apres = "#") ou (apres = "b") ou (apres = "*") alors
            bloque vaut vrai
        sinon
            si estCible(al, ac) alors
                appelle poser(al, ac, "*")
            sinon
                appelle poser(al, ac, "b")
            fin si
            si estCible(nl, nc) alors
                appelle poser(nl, nc, "c")
            sinon
                appelle poser(nl, nc, ".")
            fin si
        fin si
    fin si

    si non bloque alors
        si estCible(jl, jc) alors
            appelle poser(jl, jc, "c")
        sinon
            appelle poser(jl, jc, ".")
        fin si
        appelle poser(nl, nc, "j")
        jl vaut nl
        jc vaut nc
    fin si
fin procedure

fonction gagne()
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            si caseAt(li, co) = "b" alors
                retourne faux
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne vrai
fin fonction

// ============================================================
//  PARTIE
// ============================================================
appelle genererNiveau()

fini est un booleen
fini vaut faux
action est un nombre
action vaut 0
coups est un nombre
coups vaut 0

tant que non fini
    appelle dessiner()

    // Attente de la frappe d'une touche au clavier (z, s, q, d, r, x)
    appuyer ["z", "s", "q", "d", "r", "x"] dans action

    // action = 1 (z), 2 (s), 3 (q), 4 (d), 5 (r), 6 (x)
    si action = 1 alors
        appelle deplacer(-1, 0)
        coups vaut coups + 1
    sinon si action = 2 alors
        appelle deplacer(1, 0)
        coups vaut coups + 1
    sinon si action = 3 alors
        appelle deplacer(0, -1)
        coups vaut coups + 1
    sinon si action = 4 alors
        appelle deplacer(0, 1)
        coups vaut coups + 1
    sinon si action = 5 alors
        appelle genererNiveau()
        coups vaut 0
    sinon si action = 6 alors
        fini vaut vrai
    fin si

    si non fini alors
        si gagne() alors
            fini vaut vrai
        fin si
    fin si
fin tant que

effacer(g)
appelle dessiner()
si gagne() alors
    couleur(#2ecc71, "GAGNE en " + convertir_texte(coups) + " coups ! Bravo !")
sinon
    couleur(#e74c3c, "Partie terminee. A bientot !")
fin si
Pas encore noté
EduCode v3.2 arrive avec une nouveauté très attendue
Nouveauté

EduCode v3.2

EduCode v3.2 arrive avec une nouveauté très attendue.

Lire l'articleReplier l'article

EduCode 3.2 arrive avec une nouveauté très attendue : l'interaction à la souris sur les toiles.

Deux ajouts complémentaires, dans la philosophie du langage (simple, en français, lisible) :

  • Le mot-clé « cliquer » : le programme attend un clic sur le dessin et récupère sa position exacte.
  • La fonction « clique » : elle vérifie si le clic tombe dans une zone précise, rectangulaire ou circulaire.

Ensemble, ils ouvrent la porte aux boutons, aux menus graphiques, aux cibles à attraper et aux jeux de plateau entièrement jouables à la souris, de vraies petites applications interactives, en quelques lignes.

À découvrir dans la version v3.2, prochainement disponible en téléchargement.

Exemples :

maToile est une toile
dimension(maToile, 300, 200)
remplir(maToile, #0d1117)

x, y, i est un nombre vaut 0

pour i de 1 à 5
    affiche maToile
    cliquer maToile dans x, y
    cercle(maToile, x, y, 4, #e53935)

    affiche maToile
    affiche 'Clic ' + i + ' en (X : ' + x + ', Y : ' + y + ')'
fin pour

affiche 'Fin du programme.'
maToile est une toile
dimension(maToile, 300, 200)
remplir(maToile, '#0d1117')

// bouton : mêmes valeurs pour le dessin et le test
rectangle(maToile, 40, 80, 100, 45, '#1976d2')
label(maToile, 60, 108, 'Jouer', '#ffffff', 16)
affiche maToile

x, y est un nombre
clicok est un booléen vaut faux

tant que non clicok
    cliquer maToile dans x, y
    si clique(x, y, 40, 80, 100, 45) alors
        affiche 'Tu as cliqué sur le bouton !'
        clicok vaut vrai
    sinon
        affiche 'Raté, à côté du bouton.'
        clicok vaut faux
    fin si
fin tant que

affiche 'Fin du programme.'
Pas encore noté
Construire une île procédurale avec du bruit fractal
Article

Construire une île

Construire une île procédurale avec du bruit fractal

Lire l'articleReplier l'article

La génération procédurale est un formidable terrain d’apprentissage : elle permet de créer des mondes crédibles, variés et naturels… à partir de simples formules mathématiques.
Dans cet atelier, j’ai développé un générateur d’île procédurale destiné à comprendre les bases du bruit, du relief et des biomes.

L’objectif : montrer comment un paysage complet peut émerger d’un hachage pseudo-aléatoire et de quelques interpolations bien choisies.

Paramètres du monde

L’île est rendue sur une grille de 100 × 100 blocs, chaque bloc étant un “gros pixel” de 4×4.
Quelques constantes définissent le comportement global :

  • GRAINE : change la forme de l’île
  • ECHELLE : contrôle la taille des collines
  • BLOC : résolution du rendu

Modifier une seule valeur suffit à obtenir une nouvelle île.

Comprendre le bruit procédural

Hachage pseudo-aléatoire

La fonction hache(x, y) génère un nombre pseudo-aléatoire stable pour chaque coordonnée.
Cela garantit que le terrain ne scintille pas : une même position produit toujours la même valeur.

Fraction et interpolation

EduCode ne disposant pas de frac(), elle est recréée pour isoler la partie décimale.
Ensuite, une interpolation douce (courbe en S) permet d’obtenir des transitions naturelles entre les valeurs des coins.

Bruit multi-octaves

Le relief final est obtenu en superposant plusieurs couches :

  • grandes collines
  • détails intermédiaires
  • micro-variations

Chaque octave ajoute de la richesse au paysage, comme en modélisation 3D.

Génération de l’île

Une île n’est pas qu’un relief : c’est aussi une forme.
Pour cela, un masque radial est appliqué :

  • au centre : hauteur maximale
  • vers les bords : la hauteur diminue progressivement
  • l’exposant accentue la pente des côtes

Ce simple calcul transforme un bruit brut en une île entourée d’océan.

Biomes : donner vie au paysage
Selon la hauteur finale, chaque bloc reçoit une couleur correspondant à un biome :

  • Océan profond
  • Mer
  • Plage
  • Herbe claire
  • Forêt
  • Roche / sommet

En quelques lignes, le terrain devient lisible, naturel et immersif.

Résultat : une île unique, stable et pédagogique
Ce générateur montre que :

  • un paysage peut être créé sans aucune image, uniquement via des fonctions mathématiques
  • la génération procédurale est un excellent outil pour comprendre le bruit, les interpolations et les biomes
  • chaque île est différente, mais toujours cohérente
  • l’approche est idéale pour des ateliers, des cours ou des projets créatifs

Conclusion

Il est possible de créer une île complète, réaliste et variée à partir de quelques fonctions de bruit et d’un masque radial.
La génération procédurale est un formidable levier pédagogique : elle permet de comprendre la logique derrière les mondes ouverts, les jeux vidéo, les simulations et les outils créatifs.

Programme :

@ ATELIER PEDAGOGIQUE : Generateur d'ile procedural

// ==========================================
// 1. REGLAGES
// ==========================================
constante LARGEUR vaut 400
constante HAUTEUR vaut 400
constante BLOC vaut 4 // gros pixel (4 => grille 100x100)
constante GRAINE vaut 7 // change ce nombre = nouvelle ile
constante ECHELLE vaut 55 // taille des collines (+ grand = + large)

_toile est une toile
dimension(_toile, LARGEUR, HAUTEUR)

// ==========================================
// 2. OUTILS DE BRUIT
// ==========================================
// Partie decimale d'un nombre (EduCode n'a pas de "frac").
fonction frac(v)
    retourne v - arrondi_inferieur(v)
fin fonction

// Hachage pseudo-aleatoire : a partir de (x, y) renvoie un nombre
// "au hasard" mais TOUJOURS LE MEME pour les memes coordonnees.
// C'est ce qui rend le terrain stable (pas de scintillement).
fonction hache(x, y)
    n est un nombre
    n vaut sinus(x * 127.1 + y * 311.7 + GRAINE * 13.13) * 43758.5453
    retourne frac(absolue(n))
fin fonction

// Bruit LISSE : interpole entre les valeurs de hachage des 4 coins
// de la case -> transitions douces au lieu de sauts brusques.
fonction bruit(x, y)
    x0 est un nombre
    x0 vaut arrondi_inferieur(x)
    y0 est un nombre
    y0 vaut arrondi_inferieur(y)
    fx est un nombre
    fx vaut frac(x)
    fy est un nombre
    fy vaut frac(y)

    // Adoucissement (courbe en S) pour des collines rondes
    ux est un nombre
    ux vaut fx * fx * (3 - 2 * fx)
    uy est un nombre
    uy vaut fy * fy * (3 - 2 * fy)

    // Valeurs des 4 coins
    x est un nombre
    x vaut hache(x0, y0)
    b est un nombre
    b vaut hache(x0 + 1, y0)
    c est un nombre
    c vaut hache(x0, y0 + 1)
    d est un nombre
    d vaut hache(x0 + 1, y0 + 1)

    // Interpolation horizontale puis verticale
    ab est un nombre
    ab vaut x + (b - x) * ux
    cd est un nombre
    cd vaut c + (d - c) * ux
    retourne ab + (cd - ab) * uy
fin fonction

// Bruit multi-octaves : on superpose plusieurs echelles de bruit
// (grosses collines + details fins) -> cotes decoupees, plus naturel.
fonction relief(x, y)
    total est un nombre
    total vaut 0
    total vaut total + bruit(x / ECHELLE, y / ECHELLE) * 0.55
    total vaut total + bruit(x / (ECHELLE / 2), y / (ECHELLE / 2)) * 0.30
    total vaut total + bruit(x / (ECHELLE / 4), y / (ECHELLE / 4)) * 0.15
    retourne total
fin fonction

// ==========================================
// 3. DESSIN DE L'ILE
// ==========================================
cx est un nombre
cx vaut LARGEUR / 2
cy est un nombre
cy vaut HAUTEUR / 2
rayonMax est un nombre
rayonMax vaut LARGEUR / 2

py est un nombre
py vaut 0
tant que py < HAUTEUR
    px est un nombre
    px vaut 0
    tant que px < LARGEUR

        // Hauteur brute donnee par le bruit
        h est un nombre
        h vaut relief(px, py)

        // Masque radial : distance au centre, normalisee entre 0 et 1
        dist est un nombre
        dist vaut racine(puissance(px - cx, 2) + puissance(py - cy, 2)) / rayonMax

        // On abaisse la hauteur vers les bords (dist grand => forte baisse)
        // L'exposant accentue la cote pres du bord.
        h vaut h - puissance(dist, 2) * 1.15

        // --- Choix du biome selon la hauteur finale ---
        _couleur est un texte
        si h < -0.05 alors
            _couleur vaut #2C6BA8 // ocean profond
        sinon si h < 0.08 alors
            _couleur vaut #3E86C4 // ocean
        sinon si h < 0.14 alors
            _couleur vaut #E4D9A8 // plage (sable)
        sinon si h < 0.30 alors
            _couleur vaut #6FA84A // herbe claire
        sinon si h < 0.48 alors
            _couleur vaut #3F7A34 // foret
        sinon
            _couleur vaut #7C8B84 // roche / sommet
        fin si

        rectangle(_toile, px, py, BLOC, BLOC, _couleur)

        px vaut px + BLOC
    fin tant que
    py vaut py + BLOC
fin tant que

// ==========================================
// 4. LEGENDE
// ==========================================
label(_toile, 14, 24, "île procédural", #FFFFFF, 13)
affiche _toile
Pas encore noté