CoddyRun
Blog

Articles & Actualités

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

69 publications

Construire un moteur 3D minimaliste : quand l’algorithmique rencontre le rendu en temps réel
Article

Construire un moteur 3D minimaliste

Construire un moteur 3D minimaliste : quand l’algorithmique rencontre le rendu en temps réel

Lire l'articleReplier l'article

Développer un moteur 3D en raycasting à partir de zéro est un exercice fascinant : il combine algorithmique, mathématiques, génération procédurale et rendu graphique.
Ce projet illustre parfaitement comment des concepts simples, bien maîtrisés, peuvent produire une expérience immersive et dynamique.

Génération procédurale : un labyrinthe créé par DFS

La carte 24×24 est générée via un Depth-First Search (DFS), garantissant un labyrinthe unique et entièrement parcourable.

Points clés :

  • Grille initiale remplie de murs.
  • Exploration en profondeur avec backtracking.
  • Sélection aléatoire des voisins pour créer des chemins variés.
  • Cassage des murs intermédiaires pour former des couloirs cohérents.

Un algorithme simple, efficace, et idéal pour comprendre les bases de la génération procédurale.

Déplacements fluides et gestion des collisions

Le joueur est positionné au centre de la première cellule ouverte.
Les déplacements reposent sur un vecteur directionnel calculé à partir de l’angle de vue, avec une fonction dédiée pour éviter toute traversée de mur.

Contrôles :

  • Z / S : avancer / reculer
  • Q / D : pivoter à gauche / droite

La fonction tenteDeplacement() vérifie la cellule cible avant de valider le mouvement, assurant une navigation réaliste.

Raycasting : un rendu 3D inspiré des moteurs historiques

Le moteur utilise 150 rayons couvrant un champ de vision de 60°.

Chaque rayon calcule :

  • direction,
  • distance à l’impact,
  • hauteur du mur,
  • correction de perspective,
  • ombrage selon la distance et le côté touché.

Le rendu inclut :

  • un dégradé de ciel, un sol lissé, des murs ombrés, et une profondeur visuelle convaincante.

Mini-carte dynamique : comprendre l’espace en un coup d’œil

La mini-carte 24×24 affiche :

  • les murs en bleu,
  • les couloirs en gris foncé,
  • la position du joueur,
  • son orientation via un vecteur de direction.

Un outil essentiel pour visualiser la structure du labyrinthe généré.

Un projet complet et formateur

Ce moteur 3D réunit :

  • génération procédurale,
  • mathématiques appliquées,
  • optimisation en temps réel,
  • rendu graphique,
  • gestion d’interactions utilisateur.

Une excellente base pour comprendre les fondations des moteurs 3D modernes, tout en restant accessible et pédagogique.

Programme :

@ 0. CONSTANTES
constante TAILLE_CARTE vaut 24 // Carte 24x24
constante LARGEUR_ECRAN vaut 600
constante HAUTEUR_ECRAN vaut 400
constante NB_RAYONS vaut 150
constante EPAISSEUR_RAYON vaut 4

@ 1. CARTE DU MONDE (GÉNÉRATION ALÉATOIRE AVEC DFS)
carte est un tableau

// 1.1 Remplir toute la carte de murs (1)
pour i de 0 à (TAILLE_CARTE * TAILLE_CARTE) - 1
    carte ajoute 1
fin pour

// 1.2 Initialisation de l'algorithme DFS
pile est un tableau
startX est un nombre
startX vaut 1
startY est un nombre
startY vaut 1

// Marquer la position de départ comme chemin (0)
idxDepart est un nombre
idxDepart vaut (startY * TAILLE_CARTE) + startX
carte[idxDepart] vaut 0
pile ajoute idxDepart

// 1.3 Boucle principale du parcours en profondeur
tant que longueur(pile) > 0
    courantIdx est un nombre
    courantIdx vaut dernier(pile)
    cx est un nombre
    cx vaut courantIdx mod TAILLE_CARTE
    cy est un nombre
    cy vaut arrondi_inferieur(courantIdx / TAILLE_CARTE)

    voisins est un tableau

    // Vérifier le voisin du HAUT (y - 2)
    si cy - 2 > 0 alors
        idxVaut est un nombre
        idxVaut vaut ((cy - 2) * TAILLE_CARTE) + cx
        si carte[idxVaut] = 1 alors
            voisins ajoute 0
        fin si
    fin si

    // Vérifier le voisin du BAS (y + 2)
    si cy + 2 < TAILLE_CARTE - 1 alors
        idxVaut est un nombre
        idxVaut vaut ((cy + 2) * TAILLE_CARTE) + cx
        si carte[idxVaut] = 1 alors
            voisins ajoute 1
        fin si
    fin si

    // Vérifier le voisin de GAUCHE (x - 2)
    si cx - 2 > 0 alors
        idxVaut est un nombre
        idxVaut vaut (cy * TAILLE_CARTE) + (cx - 2)
        si carte[idxVaut] = 1 alors
            voisins ajoute 2
        fin si
    fin si

    // Vérifier le voisin de DROITE (x + 2)
    si cx + 2 < TAILLE_CARTE - 1 alors
        idxVaut est un nombre
        idxVaut vaut (cy * TAILLE_CARTE) + (cx + 2)
        si carte[idxVaut] = 1 alors
            voisins ajoute 3
        fin si
    fin si

    // Si on a des voisins non visités
    si longueur(voisins) > 0 alors
        rIdx est un nombre
        rIdx vaut hasard(0, longueur(voisins) - 1)
        dir est un nombre
        dir vaut voisins[rIdx]

        nx est un nombre
        nx vaut cx
        ny est un nombre
        ny vaut cy
        mx est un nombre // Position du mur à casser
        mx vaut cx
        my est un nombre
        my vaut cy

        si dir = 0 alors
            ny vaut cy - 2
            my vaut cy - 1
        sinon si dir = 1 alors
            ny vaut cy + 2
            my vaut cy + 1
        sinon si dir = 2 alors
            nx vaut cx - 2
            mx vaut cx - 1
        sinon si dir = 3 alors
            nx vaut cx + 2
            mx vaut cx + 1
        fin si

        // Casser le mur entre les deux cases
        idxMur est un nombre
        idxMur vaut (my * TAILLE_CARTE) + mx
        carte[idxMur] vaut 0

        // Marquer la nouvelle case comme chemin
        idxNouveau est un nombre
        idxNouveau vaut (ny * TAILLE_CARTE) + nx
        carte[idxNouveau] vaut 0

        // Empiler la nouvelle position
        pile ajoute idxNouveau
    sinon
        // Cul-de-sac : on dépile (backtracking)
        pile supprime longueur(pile) - 1
    fin si
fin tant que

@ 2. POSITION ET DIRECTION DU JOUEUR
px, py est un nombre
px vaut 1,5 // Départ centré dans la première case générée par le DFS
py vaut 1,5

angleDeg est un nombre
angleDeg vaut 0

enCours est un booléen
enCours vaut vrai

action est un nombre

maToile est une toile
dimension(maToile, LARGEUR_ECRAN, HAUTEUR_ECRAN)

tant que enCours
    effacer(maToile)

    @ --- CIEL : Dégradé lissé ---
    pour bandeC de 0 à 39
        tC est un nombre
        tC vaut bandeC / 39
        rC est un nombre
        rC vaut arrondi(5 + tC * 20)
        gC est un nombre
        gC vaut arrondi(15 + tC * 50)
        bC est un nombre
        bC vaut arrondi(35 + tC * 80)
        rectangle(maToile, 0, bandeC * 5, LARGEUR_ECRAN, 6, rgb(rC, gC, bC))
    fin pour

    @ --- SOL : Dégradé lissé ---
    pour bandeS de 0 à 39
        tS est un nombre
        tS vaut bandeS / 39
        rS est un nombre
        rS vaut arrondi(10 + tS * 40)
        gS est un nombre
        gS vaut arrondi(15 + tS * 40)
        bS est un nombre
        bS vaut arrondi(20 + tS * 45)
        rectangle(maToile, 0, 200 + bandeS * 5, LARGEUR_ECRAN, 6, rgb(rS, gS, bS))
    fin pour

    @ 3. BOUCLE de RENDU 3D (RAYCASTING)
    pour slice de 0 à NB_RAYONS - 1
        offsetAngle est un nombre
        offsetAngle vaut - 30 + (slice * (60 / (NB_RAYONS - 1)))

        rayAngle est un nombre
        rayAngle vaut angleDeg + offsetAngle
        radRay est un nombre
        radRay vaut radians(rayAngle)

        rDirX est un nombre
        rDirX vaut cosinus(radRay)
        rDirY est un nombre
        rDirY vaut sinus(radRay)

        si absolue(rDirX) < 0.0001 alors
            rDirX vaut 0.0001
        fin si
        si absolue(rDirY) < 0.0001 alors
            rDirY vaut 0.0001
        fin si

        mX est un nombre
        mX vaut arrondi_inferieur(px)
        mY est un nombre
        mY vaut arrondi_inferieur(py)

        deltaX est un nombre
        deltaX vaut absolue(1 / rDirX)
        deltaY est un nombre
        deltaY vaut absolue(1 / rDirY)

        sX, sY, sideX, sideY est un nombre

        si rDirX < 0 alors
            sX vaut - 1
            sideX vaut (px - mX) * deltaX
        sinon
            sX vaut 1
            sideX vaut (mX + 1 - px) * deltaX
        fin si

        si rDirY < 0 alors
            sY vaut - 1
            sideY vaut (py - mY) * deltaY
        sinon
            sY vaut 1
            sideY vaut (mY + 1 - py) * deltaY
        fin si

        toucher est un booléen
        toucher vaut faux
        cote est un nombre
        cote vaut 0

        tant que non toucher
            si sideX < sideY alors
                sideX vaut sideX + deltaX
                mX vaut mX + sX
                cote vaut 0
            sinon
                sideY vaut sideY + deltaY
                mY vaut mY + sY
                cote vaut 1
            fin si

            si (mX < 0) ou (mX > TAILLE_CARTE - 1) ou (mY < 0) ou (mY > TAILLE_CARTE - 1) alors
                toucher vaut vrai
            sinon
                idxC est un nombre
                idxC vaut (mY * TAILLE_CARTE) + mX
                si carte[idxC] > 0 alors
                    toucher vaut vrai
                fin si
            fin si
        fin tant que

        distBrute est un nombre
        si cote = 0 alors
            distBrute vaut sideX - deltaX
        sinon
            distBrute vaut sideY - deltaY
        fin si

        distCorr est un nombre
        distCorr vaut distBrute * cosinus(radians(offsetAngle))
        si distCorr < 0.1 alors
            distCorr vaut 0.1
        fin si

        hautMur est un nombre
        hautMur vaut 300 / distCorr
        hautMur vaut limiter(hautMur, 5, 450)

        topY est un nombre
        topY vaut 200 - (hautMur / 2)
        posX est un nombre
        posX vaut slice * EPAISSEUR_RAYON

        intensite est un nombre
        intensite vaut 1 - (distCorr / 18)
        intensite vaut limiter(intensite, 0.05, 1.0)

        si cote = 1 alors
            intensite vaut intensite * 0.6
        fin si

        rBase, vBase, bBase est un nombre
        si (mX + mY) mod 2 = 0 alors
            rBase vaut 200
            vBase vaut 80
            bBase vaut 40
        sinon
            rBase vaut 70
            vBase vaut 110
            bBase vaut 150
        fin si

        rMur est un nombre
        rMur vaut arrondi(rBase * intensite)
        vMur est un nombre
        vMur vaut arrondi(vBase * intensite)
        bMur est un nombre
        bMur vaut arrondi(bBase * intensite)

        couleurMur est un texte
        couleurMur vaut rgb(rMur, vMur, bMur)

        rectangle(maToile, posX, topY, EPAISSEUR_RAYON, hautMur, couleurMur)

        lisere est un texte
        lisere vaut rgb(arrondi(rMur * 0.3), arrondi(vMur * 0.3), arrondi(bMur * 0.3))
        rectangle(maToile, posX, topY, EPAISSEUR_RAYON, 3, lisere)
        rectangle(maToile, posX, topY + hautMur - 3, EPAISSEUR_RAYON, 3, lisere)
    fin pour

    @ 4. MINI-CARTE OPTIMISÉE (RADAR 24x24)
    rectangle(maToile, 8, 8, 126, 126, rgba(0, 0, 0, 0.75))

    pour my de 0 à TAILLE_CARTE - 1
        pour mx de 0 à TAILLE_CARTE - 1
            idxM est un nombre
            idxM vaut (my * TAILLE_CARTE) + mx
            si carte[idxM] = 1 alors
                rectangle(maToile, 11 + (mx * 5), 11 + (my * 5), 4, 4, #3B82F6)
            sinon
                rectangle(maToile, 11 + (mx * 5), 11 + (my * 5), 4, 4, #1B2437)
            fin si
        fin pour
    fin pour

    radVue est un nombre
    radVue vaut radians(angleDeg)
    joueurX est un nombre
    joueurX vaut 11 + (px * 5)
    joueurY est un nombre
    joueurY vaut 11 + (py * 5)

    ligne(maToile, joueurX, joueurY, joueurX + cosinus(radVue) * 8, joueurY + sinus(radVue) * 8, #FBBF24, 2)
    cercle(maToile, joueurX, joueurY, 2, #EF4444)

    rectangle(maToile, 142, 12, 290, 24, rgba(0, 0, 0, 0.6))
    label(maToile, 150, 29, "Z / S : Déplacer   |   Q / D : Orienter", #FFFFFF, 13)

    affiche maToile

    @ 5. GESTION DES COMMANDES
    appuyer ["x", "z", "s", "q", "d"] dans action

    radMove est un nombre
    radMove vaut radians(angleDeg)
    dirX est un nombre
    dirX vaut cosinus(radMove)
    dirY est un nombre
    dirY vaut sinus(radMove)

    si action = 2 alors
        appelle tenteDeplacement(1)
    sinon si action = 3 alors
        appelle tenteDeplacement(-1)
    sinon si action = 4 alors
        angleDeg soustrait 90
    sinon si action = 5 alors
        angleDeg ajoute 90
    fin si

fin tant que

@ 6. FONCTION DE DEPLACEMENT
fonction tenteDeplacement(sens)
    nextX est un nombre
    nextX vaut px + (dirX * 0.8 * sens)
    nextY est un nombre
    nextY vaut py + (dirY * 0.8 * sens)

    cX est un nombre
    cX vaut arrondi_inferieur(nextX)
    cY est un nombre
    cY vaut arrondi_inferieur(nextY)

    si (cX < 0) ou (cX > TAILLE_CARTE - 1) ou (cY < 0) ou (cY > TAILLE_CARTE - 1) alors
        retourne faux
    fin si

    idxCheck est un nombre
    idxCheck vaut (cY * TAILLE_CARTE) + cX
    si carte[idxCheck] = 0 alors
        px vaut cX + 0.5
        py vaut cY + 0.5
    fin si
    retourne vrai
fin fonction
Pas encore noté
Concevoir un quiz interactif : de la logique au design
Article

Concevoir un quiz interactif

Concevoir un quiz interactif : de la logique au design

Lire l'articleReplier l'article

Créer un quiz efficace ne se résume pas à afficher des questions.
C’est un véritable travail de structure, de design, d’ergonomie et de gestion des interactions.
Voici un exemple concret de mise en œuvre : un quiz EduCode complet, animé, structuré et entièrement interactif.

Structurer les questions : la base du quiz

Tout commence par une procédure simple et robuste permettant d’ajouter une question :

  • un énoncé
  • quatre propositions
  • un index de bonne réponse

Chaque question est stockée dans un enregistrement, ce qui garantit :

  • une organisation claire
  • une manipulation facile
  • une extensibilité immédiate (ajout, modification, réutilisation)

Cette approche permet de gérer 20 questions sans complexité supplémentaire.

Construire une interface intuitive

Un quiz doit être agréable à parcourir.
L’interface repose sur plusieurs éléments visuels :

  • une barre d’en-tête avec le titre et le score
  • une barre de progression dynamique
  • un cadre d’énoncé
  • quatre boutons de réponse
  • un bouton "Suivant" affiché uniquement après validation

Les couleurs jouent un rôle essentiel :

  • Bleu pour les éléments interactifs
  • Gris foncé pour le fond
  • Vert pour une bonne réponse
  • Rouge pour une mauvaise réponse sélectionnée

Cette palette crée une ambiance moderne, proche des applications éducatives actuelles.

Gérer les interactions utilisateur

Le cœur du quiz repose sur la gestion du clic :

  • détection de la zone cliquée
  • identification de la réponse choisie
  • validation
  • mise à jour du score
  • affichage de la correction
  • passage à la question suivante

Chaque étape est pensée pour être fluide, sans temps mort, avec un retour visuel immédiat.

Une boucle de jeu simple et efficace

Pour chaque question :

  • Affichage de l’interface
  • Attente du clic utilisateur
  • Vérification de la réponse
  • Mise à jour du score
  • Affichage de la correction
  • Clic sur "Suivant" pour continuer

Cette boucle crée un rythme naturel, proche des applications de quiz professionnelles.

Un écran final motivant

À la fin des 20 questions, un écran de bilan affiche :

  • le score
  • le pourcentage de réussite
  • un message personnalisé selon le niveau

Trois niveaux :

  • Excellent (= 80 %)
  • Bon travail (= 50 %)
  • À revoir (< 50 %)

Un bouton REJOUER permet de recommencer immédiatement, favorisant l’apprentissage par répétition.

Programme :

@ Initialisation des procédures et structures de questions

procédure ajouterQ(tab, txt, o1, o2, o3, o4, r)
    item est un enregistrement
    item.enonce vaut txt
    item.opt1 vaut o1
    item.opt2 vaut o2
    item.opt3 vaut o3
    item.opt4 vaut o4
    item.rep vaut r
    tab ajoute item
fin procédure

procédure dessinerBouton(t, x, y, l, h, txt, numOpt, repOpt, choixOpt, valide)
    couleurBouton est un texte
    couleurBouton vaut #393E46

    si valide alors
        si numOpt = repOpt alors
            couleurBouton vaut #2E7D32 // Vert si bonne réponse
        sinon si numOpt = choixOpt alors
            couleurBouton vaut #C62828 // Rouge si mauvaise réponse choisie
        sinon
            couleurBouton vaut #252A34 // Assombri sinon
        fin si
    fin si

    rectangle_arrondi(t, x, y, l, h, 8, couleurBouton)
    label(t, x + 20, y + 30, txt, #FFFFFF, 14)
fin procédure

procédure dessinerQuiz(t, q, numQ, totalQ, score, choixUtilisateur, validationAffichee)
    effacer(t)
    remplir(t, #1A1D24)

    // Barre d'en-tête
    rectangle(t, 0, 0, 640, 50, #252A34)
    label(t, 20, 32, "QUIZ EDUCODE", #00ADB5, 20)
    label(t, 210, 32, "Question " + convertir_texte(numQ) + " / " + convertir_texte(totalQ), #EEEEEE, 16)
    label(t, 480, 32, "Score : " + convertir_texte(score), #FFD700, 16)

    // Barre de progression
    rectangle(t, 0, 50, 640, 6, #393E46)
    largeurBarre est un nombre
    largeurBarre vaut arrondi((numQ / totalQ) * 640)
    rectangle(t, 0, 50, largeurBarre, 6, #00ADB5)

    // Cadre de l'énoncé
    rectangle_arrondi(t, 30, 75, 580, 50, 8, #252A34)
    label(t, 45, 106, q.enonce, #FFFFFF, 14)

    // Boutons de réponses
    appelle dessinerBouton(t, 30, 140, 580, 48, q.opt1, 1, q.rep, choixUtilisateur, validationAffichee)
    appelle dessinerBouton(t, 30, 200, 580, 48, q.opt2, 2, q.rep, choixUtilisateur, validationAffichee)
    appelle dessinerBouton(t, 30, 260, 580, 48, q.opt3, 3, q.rep, choixUtilisateur, validationAffichee)
    appelle dessinerBouton(t, 30, 320, 580, 48, q.opt4, 4, q.rep, choixUtilisateur, validationAffichee)

    // Bouton Suivant / Info
    si validationAffichee alors
        rectangle_arrondi(t, 220, 380, 200, 40, 20, #00ADB5)
        label(t, 290, 405, "SUIVANT", #FFFFFF, 14)
    sinon
        label(t, 200, 405, "Clique sur la bonne réponse...", #AAAAAA, 13)
    fin si

    affiche t
fin procédure

@ Chargement des 20 questions

questions est un tableau

appelle ajouterQ(questions, "1. Quel est le séparateur décimal en EduCode ?", "A) La virgule (,)", "B) Le point (.)", "C) Deux-points (:)", "D) L'espace", 2)
appelle ajouterQ(questions, "2. Comment ferme-t-on une condition si ?", "A) finsi", "B) fin si", "C) fin", "D) }", 2)
appelle ajouterQ(questions, "3. À quel index commence un tableau ?", "A) Index 0", "B) Index 1", "C) Index -1", "D) Index 0.5", 1)
appelle ajouterQ(questions, "4. À quel index commence un texte ?", "A) Index 0", "B) Index 1", "C) Index 2", "D) Index 0.5", 2)
appelle ajouterQ(questions, "5. Quel mot-clé sert à exécuter une procédure ?", "A) lancer", "B) executer", "C) appelle", "D) fonction", 3)
appelle ajouterQ(questions, "6. Écriture correcte d'une couleur hexadécimale ?", "A) '#FF0000'", "B) #FF0000", "C) rouge", "D) rgb(rouge)", 2)
appelle ajouterQ(questions, "7. Déclaration correcte d'un nombre ?", "A) x = 0", "B) x est un nombre", "C) nombre x", "D) var x : nombre", 2)
appelle ajouterQ(questions, "8. Comment affecter une valeur à une variable ?", "A) x = 10", "B) x vaut 10", "C) x est 10", "D) x <- 10", 2)
appelle ajouterQ(questions, "9. Quelle syntaxe crée un dictionnaire vide ?", "A) []", "B) {}", "C) [:]", "D) dict()", 3)
appelle ajouterQ(questions, "10. Quel mot-clé permet de sortir d'une boucle ?", "A) arrete", "B) break", "C) stopper", "D) fin", 1)
appelle ajouterQ(questions, "11. Quelle fonction calcule la racine carrée ?", "A) sqrt(x)", "B) racine(x)", "C) rad(x)", "D) sqr(x)", 2)
appelle ajouterQ(questions, "12. Quel mot-clé arrête le programme ?", "A) stopper", "B) fin", "C) quitter", "D) terminer", 4)
appelle ajouterQ(questions, "13. Comment s'écrit un commentaire d'une ligne ?", "A) //", "B) #", "C) --", "D) /*", 1)
appelle ajouterQ(questions, "14. Fonction pour tester la zone d'un clic ?", "A) cliquer()", "B) clique()", "C) zone()", "D) collision()", 2)
appelle ajouterQ(questions, "15. Comment ajouter un élément à un tableau t ?", "A) t.push(x)", "B) t ajoute x", "C) t + x", "D) t insere x", 2)
appelle ajouterQ(questions, "16. Quel est le type pour une chaîne de caractères ?", "A) string", "B) texte", "C) char", "D) message", 2)
appelle ajouterQ(questions, "17. Comment se termine une boucle tant que ?", "A) fin tant que", "B) fintantque", "C) wend", "D) fin tantque", 1)
appelle ajouterQ(questions, "18. Fonction pour mesurer un temps en ms ?", "A) temps()", "B) horloge()", "C) chrono()", "D) timer()", 3)
appelle ajouterQ(questions, "19. Opérateur pour le reste d'une division ?", "A) %", "B) mod", "C) rem", "D) div", 2)
appelle ajouterQ(questions, "20. Fonction pour effacer le dessin d'une toile ?", "A) nettoyer(t)", "B) effacer(t)", "C) raz(t)", "D) vider(t)", 2)

@ Boucle principale du jeu

maToile est une toile
dimension(maToile, 640, 440)

rejouer est un booléen
rejouer vaut vrai

tant que rejouer
    score est un nombre
    score vaut 0

    i est un nombre
    pour i de 0 à 19
        actuelle est un enregistrement
        actuelle vaut questions[i]

        choix est un nombre
        choix vaut 0

        // 1. Afficher l'énoncé de la question
        appelle dessinerQuiz(maToile, actuelle, i + 1, 20, score, 0, faux)

        // 2. Attente de la sélection utilisateur
        attenteClic est un booléen
        attenteClic vaut vrai
        clicX, clicY est un nombre

        tant que attenteClic
            cliquer maToile dans clicX, clicY

            si clique(clicX, clicY, 30, 140, 580, 48) alors
                choix vaut 1
                attenteClic vaut faux
            sinon si clique(clicX, clicY, 30, 200, 580, 48) alors
                choix vaut 2
                attenteClic vaut faux
            sinon si clique(clicX, clicY, 30, 260, 580, 48) alors
                choix vaut 3
                attenteClic vaut faux
            sinon si clique(clicX, clicY, 30, 320, 580, 48) alors
                choix vaut 4
                attenteClic vaut faux
            fin si
        fin tant que

        // Mise à jour du score
        si choix = actuelle.rep alors
            score ajoute 1
        fin si

        // 3. Afficher la correction
        appelle dessinerQuiz(maToile, actuelle, i + 1, 20, score, choix, vrai)

        // 4. Attente du clic sur "SUIVANT"
        attenteSuivant est un booléen
        attenteSuivant vaut vrai

        tant que attenteSuivant
            cliquer maToile dans clicX, clicY
            si clique(clicX, clicY, 220, 380, 200, 40) alors
                attenteSuivant vaut faux
            fin si
        fin tant que
    fin pour

    // Écran final de bilan
    effacer(maToile)
    remplir(maToile, #1A1D24)
    rectangle_arrondi(maToile, 70, 40, 500, 360, 12, #252A34)

    label(maToile, 220, 90, "QUIZ TERMINÉ !", #00ADB5, 24)

    pct est un nombre
    pct vaut arrondi(pourcentage(score, 20))

    label(maToile, 180, 160, "Votre score : " + convertir_texte(score) + " / 20", #EEEEEE, 20)
    label(maToile, 210, 210, "Réussite : " + convertir_texte(pct) + " %", #FFD700, 20)

    msg est un texte
    si pct >= 80 alors
        msg vaut "Excellent ! Vous maîtrisez EduCode !"
    sinon si pct >= 50 alors
        msg vaut "Bon travail ! Encore un petit effort."
    sinon
        msg vaut "Revoyez la spécification EduCode !"
    fin si

    label(maToile, 140, 270, msg, #FFFFFF, 15)

    // Bouton Rejouer
    rectangle_arrondi(maToile, 220, 320, 200, 50, 25, #00ADB5)
    label(maToile, 275, 352, "REJOUER", #FFFFFF, 16)
    affiche maToile

    // Attente du clic sur "REJOUER"
    attenteRejouer est un booléen
    attenteRejouer vaut vrai

    tant que attenteRejouer
        cliquer maToile dans clicX, clicY
        si clique(clicX, clicY, 220, 320, 200, 50) alors
            attenteRejouer vaut faux
        fin si
    fin tant que
fin tant que
Pas encore noté
Cette mise à jour apporte des améliorations majeures au moteur et à l’environnement de développement
Actualité

Version 3.2 : en cours de finalisation !

Cette mise à jour apporte des améliorations majeures au moteur et à l’environnement de développement

Lire l'articleReplier l'article

Performances

Optimisation profonde de l’interpréteur : jusqu’à 30 % de gain sur les tâches intensives (animations, 3D, simulations, boucles lourdes).

Nouveau mot-clé : cliquer

Attendez un clic utilisateur sur une toile et récupérez directement les coordonnées :

tant que choix = 0
    affiche maToile
    cliquer maToile dans x, y

    si clique(x, y, 40, 80, 100, 45) alors
        choix vaut 1
    fin si
    si clique(x, y, 210, 102, 28) alors
        choix vaut 2
    fin si
fin tant que

Affectation directe dans les propriétés d’objet

Fini les variables temporaires :

classe joueur:
    nom est un texte
    score est un nombre
fin classe
demande "Ton nom ? " dans joueur.nom

Nouvelles fonctions intégrées

  • liste_fichiers : remplit un tableau avec les chemins complets des fichiers.
  • clique : vérifie si un clic se situe dans une zone définie de la toile.
maToile est une toile
x, y est un nombre
rectangle(maToile, 40, 80, 100, 45, #1976d2)
cliquer maToile dans  x, y

si clique(x, y, 40, 80, 100, 45) alors

Interface améliorée

  • Correction du positionnement du menu d’auto-complétion.
  • Nouveau panneau Structure du programme : vue hiérarchique, filtre, mise à jour en direct.

Nouveauté : Favoris

Ajoutez vos programmes à vos favoris, retrouvez vos projets en cours en un instant, et organisez votre liste selon vos besoins.

La version 3.2 arrive très bientôt. Préparez-vous à un vrai confort de développement.

Pas encore noté
Choisissez la puissance adaptée à vos projets éducatifs, animations et exercices de logique.
Actualité

Matériel recommandé pour une utilisation fluide d’EduCode

EduCode : Du simple projet aux animations avancées, quelle configuration choisir ?

Lire l'articleReplier l'article

Configuration minimale

Pour l’initiation, les projets simples et les petites animations.

  • CPU : Intel Core i3 / i5 (4–6 cœurs) ou AMD Ryzen 3 / 5
  • RAM : 8 Go

Configuration recommandée

Pour un usage fluide, des projets éducatifs intermédiaires et des animations plus complexes.

  • CPU : Intel Core i5 / i7 ou AMD Ryzen 5 / 7
  • RAM : 16 Go
Pas encore noté
Un projet inspiré du jeu Isotiles – Isometric Puzzle Game
Article

ISOTILES - Isometric Puzzle Game

ISOTILES : Quand l’algorithmique façonne un puzzle isométrique intelligent

Lire l'articleReplier l'article

Un projet inspiré du jeu Isotiles – Isometric Puzzle Game (Steam), revisité pour l’apprentissage et l’expérimentation.

ISOTILES – est un moteur de génération de puzzles isométriques conçu pour explorer la logique, l’algorithmique et le game design. Le concept s’appuie sur une idée simple : chaque tuile représente un nombre de passages possibles, et le joueur doit consommer ces passages avant d’atteindre la tuile d’arrivée.

Un système de tuiles basé sur la logique des passages

Chaque couleur correspond à une contrainte précise :

  • Bleu : 1 passage
  • Violet : 2 passages
  • Gris : pont neutre
  • Rouge : départ
  • Jaune : arrivée

L’objectif est de parcourir la carte en optimisant ses déplacements, tout en évitant les situations de blocage. Une fois tous les passages consommés, la tuile d’arrivée devient accessible.

Un générateur de cartes 100 % solvables

Le cœur du projet repose sur un algorithme qui :

  • construit un chemin aléatoire contrôlé,
  • évite les retours inutiles,
  • vérifie que la case d’arrivée n’est visitée qu’une seule fois,
  • calcule le nombre réel de passages par tuile,
  • attribue automatiquement les couleurs,
  • centre la carte dans l’espace isométrique,
  • garantit que chaque puzzle est solvable, sans être trivial.

Ce système permet de produire des cartes uniques, cohérentes et adaptées à un apprentissage progressif.

Un rendu isométrique généré entièrement par code

Le moteur graphique :

  • calcule les coordonnées isométriques,
  • dessine les faces des tuiles (haut, gauche, droite),
  • applique les couleurs et ombrages,
  • affiche un personnage stylisé,
  • met à jour l’affichage en temps réel selon les déplacements.

Le résultat est un environnement visuel propre, lisible et entièrement généré par le programme.

Un outil pédagogique pour comprendre l’algorithmique

ISOTILES – constitue un excellent support pour :

  • enseigner les structures de données,
  • comprendre la génération procédurale,
  • manipuler les coordonnées isométriques,
  • analyser les états de jeu (victoire, blocage),
  • expérimenter la logique de parcours et de contraintes.

Ce projet combine mathématiques, logique, créativité et programmation, et peut servir de base à des ateliers, des cours ou des projets personnels.

Programme :

@ Initialisation du moteur et des variables
graph est une toile
dimension(graph, 600, 500)

jX est un nombre
jY est un nombre
echelle est un nombre
echelle vaut 1.3
action est un nombre

map est un dictionnaire
nbBleu est un nombre
etatJeu est un nombre // 0 = En cours, 1 = Gagné, 2 = Bloqué

minX est un nombre
maxX est un nombre
minY est un nombre
maxY est un nombre
centreMapX est un nombre
centreMapY est un nombre

@ Générateur de cartes complexes et 100% solvables
procédure genererMap()
    map vaut [:]
    nbBleu vaut 0
    etatJeu vaut 0

    longueurChemin est un nombre
    longueurChemin vaut 24

    reussi est un booléen
    reussi vaut faux
    tentatives est un nombre
    tentatives vaut 0

    tant que non reussi et tentatives < 200
        tentatives ajoute 1
        map vaut [:]

        cheminX est un tableau
        cheminY est un tableau
        cheminX ajoute 0
        cheminY ajoute 0

        currX est un nombre
        currY est un nombre
        currX vaut 0
        currY vaut 0

        pasStep est un nombre
        pour pasStep de 1 à longueurChemin
            voisinsX est un tableau
            voisinsY est un tableau

            // Nord (interdit de revenir au départ 0,0)
            si currY - 1 <> 0 ou currX <> 0 alors
                voisinsX ajoute currX
                voisinsY ajoute currY - 1
            fin si
            // Sud
            si currY + 1 <> 0 ou currX <> 0 alors
                voisinsX ajoute currX
                voisinsY ajoute currY + 1
            fin si
            // Ouest
            si currY <> 0 ou currX - 1 <> 0 alors
                voisinsX ajoute currX - 1
                voisinsY ajoute currY
            fin si
            // Est
            si currY <> 0 ou currX + 1 <> 0 alors
                voisinsX ajoute currX + 1
                voisinsY ajoute currY
            fin si

            choixIdx est un nombre
            choixIdx vaut hasard(0, longueur(voisinsX) - 1)

            currX vaut voisinsX[choixIdx]
            currY vaut voisinsY[choixIdx]

            cheminX ajoute currX
            cheminY ajoute currY
        fin pour

        endX est un nombre
        endY est un nombre
        endX vaut currX
        endY vaut currY

        // Vérification que la case d'arrivée n'est visitée qu'à la fin
        nbFinVisites est un nombre
        nbFinVisites vaut 0
        idxStep est un nombre
        pour idxStep de 0 à longueurChemin
            si cheminX[idxStep] = endX et cheminY[idxStep] = endY alors
                nbFinVisites ajoute 1
            fin si
        fin pour

        si (endX <> 0 ou endY <> 0) et nbFinVisites = 1 alors
            reussi vaut vrai

            visites est un dictionnaire
            pour idxStep de 1 à longueurChemin - 1
                kVis est un texte
                kVis vaut convertir_texte(cheminX[idxStep]) + "_" + convertir_texte(cheminY[idxStep])
                si contient(visites, kVis) alors
                    vCount est un nombre
                    vCount vaut visites[kVis]
                    visites[kVis] vaut vCount + 1
                sinon
                    visites[kVis] vaut 1
                fin si
            fin pour

            tabKeysVisites est un tableau
            tabKeysVisites vaut cles(visites)

            iVis est un nombre
            pour iVis de 0 à longueur(tabKeysVisites) - 1
                kVis vaut tabKeysVisites[iVis]
                numPassages est un nombre
                numPassages vaut visites[kVis]

                si numPassages = 1 alors
                    map[kVis] vaut 1 // Bleue (1 passage)
                sinon si numPassages = 2 alors
                    map[kVis] vaut 5 // Violette (2 passages)
                sinon
                    map[kVis] vaut 3 // Grise (Pont / Neutre)
                fin si
            fin pour

            // Arrivée (Jaune = 4)
            cleFin est un texte
            cleFin vaut convertir_texte(endX) + "_" + convertir_texte(endY)
            map[cleFin] vaut 4
        fin si
    fin tant que

    // Départ (Rouge = 2)
    map["0_0"] vaut 2
    jX vaut 0
    jY vaut 0

    // Calcul du nombre de passages et centrage
    tabCles est un tableau
    tabCles vaut cles(map)
    nbBleu vaut 0

    i est un nombre
    pour i de 0 à longueur(tabCles) - 1
        kTmp est un texte
        kTmp vaut tabCles[i]
        tVal est un nombre
        tVal vaut map[kTmp]

        si tVal = 1 alors
            nbBleu ajoute 1
        sinon si tVal = 5 alors
            nbBleu ajoute 2
        fin si

        coords est un tableau
        coords vaut decouper(kTmp, "_")
        cxVal est un nombre
        cyVal est un nombre
        cxVal vaut convertir_nombre(coords[0])
        cyVal vaut convertir_nombre(coords[1])

        si i = 0 alors
            minX vaut cxVal
            maxX vaut cxVal
            minY vaut cyVal
            maxY vaut cyVal
        sinon
            minX vaut minimum(minX, cxVal)
            maxX vaut maximum(maxX, cxVal)
            minY vaut minimum(minY, cyVal)
            maxY vaut maximum(maxY, cyVal)
        fin si
    fin pour

    centreMapX vaut (minX + maxX) / 2
    centreMapY vaut (minY + maxY) / 2
fin procédure

// Génération initiale
appelle genererMap()

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

    dx est un nombre
    dy est un nombre
    dx vaut 24 * echelle
    dy vaut 12 * echelle

    sMin est un nombre
    sMax est un nombre
    sMin vaut (minX - 1) + (minY - 1)
    sMax vaut (maxX + 1) + (maxY + 1)

    sSum est un nombre
    gx est un nombre
    pour sSum de sMin à sMax
        pour gx de minX - 1 à maxX + 1
            gy est un nombre
            gy vaut sSum - gx

            si gy >= minY - 1 et gy <= maxY + 1 alors
                txtCleCoord est un texte
                txtCleCoord vaut convertir_texte(gx) + "_" + convertir_texte(gy)

                si contient(map, txtCleCoord) alors
                    typeTuile est un nombre
                    typeTuile vaut map[txtCleCoord]

                    relX est un nombre
                    relY est un nombre
                    relX vaut gx - centreMapX
                    relY vaut gy - centreMapY

                    cx est un nombre
                    cy est un nombre
                    cx vaut 300 + (relX - relY) * dx
                    cy vaut 250 + (relX + relY) * dy

                    hauteurBase est un nombre
                    hauteurBase vaut 10 * echelle

                    cTop est un texte
                    cGau est un texte
                    cDro est un texte

                    si typeTuile = 1 alors
                        cTop vaut #38bdf8 // Bleu (1 passage)
                        cGau vaut #0284c7
                        cDro vaut #0369a1
                    sinon si typeTuile = 2 alors
                        cTop vaut #f87171 // Rouge (Consommé)
                        cGau vaut #dc2626
                        cDro vaut #991b1b
                    sinon si typeTuile = 3 alors
                        cTop vaut #94a3b8 // Gris (Pont / Neutre)
                        cGau vaut #64748b
                        cDro vaut #475569
                    sinon si typeTuile = 4 alors
                        cTop vaut #facc15 // Jaune (Arrivée)
                        cGau vaut #ca8a04
                        cDro vaut #854d0e
                    sinon si typeTuile = 5 alors
                        cTop vaut #c084fc // Violet (2 passages)
                        cGau vaut #9333ea
                        cDro vaut #6b21a8
                    fin si

                    lCY est un nombre
                    lCY vaut cy - hauteurBase

                    ptsFG est un tableau
                    ptsFG ajoute cx - dx
                    ptsFG ajoute lCY
                    ptsFG ajoute cx
                    ptsFG ajoute lCY + dy
                    ptsFG ajoute cx
                    ptsFG ajoute lCY + dy + hauteurBase
                    ptsFG ajoute cx - dx
                    ptsFG ajoute lCY + hauteurBase
                    polygone(graph, ptsFG, cGau)

                    ptsFD est un tableau
                    ptsFD ajoute cx
                    ptsFD ajoute lCY + dy
                    ptsFD ajoute cx + dx
                    ptsFD ajoute lCY
                    ptsFD ajoute cx + dx
                    ptsFD ajoute lCY + hauteurBase
                    ptsFD ajoute cx
                    ptsFD ajoute lCY + dy + hauteurBase
                    polygone(graph, ptsFD, cDro)

                    ptsTop est un tableau
                    ptsTop ajoute cx
                    ptsTop ajoute lCY - dy
                    ptsTop ajoute cx + dx
                    ptsTop ajoute lCY
                    ptsTop ajoute cx
                    ptsTop ajoute lCY + dy
                    ptsTop ajoute cx - dx
                    ptsTop ajoute lCY
                    polygone(graph, ptsTop, cTop)

                    contour(graph, rgba(255, 255, 255, 0.15), 1)

                    // Joueur
                    si gx = jX et gy = jY alors
                        pCY est un nombre
                        pCY vaut lCY - (2 * echelle)

                        pDx est un nombre
                        pDy est un nombre
                        pDz est un nombre
                        pDx vaut 8 * echelle
                        pDy vaut 4 * echelle
                        pDz vaut 14 * echelle

                        ellipse(graph, cx, pCY + pDy, 14 * echelle, 7 * echelle, rgba(0, 0, 0, 0.4))

                        pJG est un tableau
                        pJG ajoute cx - pDx
                        pJG ajoute pCY - pDz
                        pJG ajoute cx
                        pJG ajoute pCY - pDz + pDy
                        pJG ajoute cx
                        pJG ajoute pCY + pDy
                        pJG ajoute cx - pDx
                        pJG ajoute pCY
                        polygone(graph, pJG, #cbd5e1)

                        pJD est un tableau
                        pJD ajoute cx
                        pJD ajoute pCY - pDz + pDy
                        pJD ajoute cx + pDx
                        pJD ajoute pCY - pDz
                        pJD ajoute cx + pDx
                        pJD ajoute pCY
                        pJD ajoute cx
                        pJD ajoute pCY + pDy
                        polygone(graph, pJD, #94a3b8)

                        pJT est un tableau
                        pJT ajoute cx
                        pJT ajoute pCY - pDz - pDy
                        pJT ajoute cx + pDx
                        pJT ajoute pCY - pDz
                        pJT ajoute cx
                        pJT ajoute pCY - pDz + pDy
                        pJT ajoute cx - pDx
                        pJT ajoute pCY - pDz
                        polygone(graph, pJT, #ffffff)

                        contour(graph, #0)

                        polygone(graph, cx + (1 * echelle), pCY - pDz + (2 * echelle), cx + (6 * echelle), pCY - pDz - (0.5 * echelle), cx + (6 * echelle), pCY - (3 * echelle), cx + (1 * echelle), pCY - (0.5 * echelle), #38bdf8)
                    fin si
                fin si
            fin si
        fin pour
    fin pour

    // Interface utilisateur
    rectangle(graph, 0, 0, 600, 65, rgba(15, 23, 42, 0.9))
    label(graph, 12, 18, "ISOTILES - PUZZLE EDUCODE v3.1", #ffffff, 13)

    si etatJeu = 1 alors
        label(graph, 12, 36, "VICTOIRE ! Appuie sur [R] pour la carte suivante.", #4ade80, 12)
    sinon si etatJeu = 2 alors
        label(graph, 12, 36, "BLOQUÉ ! Plus de coup possible. Appuie sur [R].", #f87171, 12)
    sinon
        txtInfos est un texte
        txtInfos vaut "Passages restants : " + convertir_texte(nbBleu)
        label(graph, 12, 36, txtInfos, #38bdf8, 12)
    fin si

    label(graph, 12, 52, "Bleu = 1 pass. | Violet = 2 pass. | Gris = Pont", #94a3b8, 12)

    affiche graph

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

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

    si action >= 1 et action <= 4 et etatJeu = 0 alors
        si action = 1 alors
            mvtY vaut - 1
        sinon si action = 2 alors
            mvtY vaut 1
        sinon si action = 3 alors
            mvtX vaut - 1
        sinon si action = 4 alors
            mvtX vaut 1
        fin si

        cibX est un nombre
        cibY est un nombre
        cibX vaut jX + mvtX
        cibY vaut jY + mvtY

        txtCleCib est un texte
        txtCleCib vaut convertir_texte(cibX) + "_" + convertir_texte(cibY)

        si contient(map, txtCleCib) alors
            typeCib est un nombre
            typeCib vaut map[txtCleCib]

            si typeCib = 1 ou typeCib = 3 ou typeCib = 4 ou typeCib = 5 alors
                jX vaut cibX
                jY vaut cibY

                si typeCib = 1 alors
                    map[txtCleCib] vaut 2
                    nbBleu soustrait 1
                sinon si typeCib = 5 alors
                    map[txtCleCib] vaut 1
                    nbBleu soustrait 1
                fin si

                si typeCib = 4 et nbBleu = 0 alors
                    etatJeu vaut 1
                fin si

                // Test de blocage
                si etatJeu = 0 alors
                    txtNord est un texte
                    txtNord vaut convertir_texte(jX) + "_" + convertir_texte(jY - 1)
                    txtSud est un texte
                    txtSud vaut convertir_texte(jX) + "_" + convertir_texte(jY + 1)
                    txtOuest est un texte
                    txtOuest vaut convertir_texte(jX - 1) + "_" + convertir_texte(jY)
                    txtEst est un texte
                    txtEst vaut convertir_texte(jX + 1) + "_" + convertir_texte(jY)

                    vN est un booléen
                    vN vaut faux
                    si contient(map, txtNord) alors
                        typeN est un nombre
                        typeN vaut map[txtNord]
                        si typeN = 1 ou typeN = 3 ou typeN = 5 ou (typeN = 4 et nbBleu = 0) alors
                            vN vaut vrai
                        fin si
                    fin si

                    vS est un booléen
                    vS vaut faux
                    si contient(map, txtSud) alors
                        typeS est un nombre
                        typeS vaut map[txtSud]
                        si typeS = 1 ou typeS = 3 ou typeS = 5 ou (typeS = 4 et nbBleu = 0) alors
                            vS vaut vrai
                        fin si
                    fin si

                    vO est un booléen
                    vO vaut faux
                    si contient(map, txtOuest) alors
                        typeO est un nombre
                        typeO vaut map[txtOuest]
                        si typeO = 1 ou typeO = 3 ou typeO = 5 ou (typeO = 4 et nbBleu = 0) alors
                            vO vaut vrai
                        fin si
                    fin si

                    vE est un booléen
                    vE vaut faux
                    si contient(map, txtEst) alors
                        typeE est un nombre
                        typeE vaut map[txtEst]
                        si typeE = 1 ou typeE = 3 ou typeE = 5 ou (typeE = 4 et nbBleu = 0) alors
                            vE vaut vrai
                        fin si
                    fin si

                    si non vN et non vS et non vO et non vE alors
                        etatJeu vaut 2
                    fin si
                fin si
            fin si
        fin si
    fin si

    si action = 5 alors
        appelle genererMap()
    fin si

fin tant que
Pas encore noté
Générer un Sudoku complet : un projet mêlant algorithmique, logique et rendu graphique
Article

Générer un Sudoku complet

Un projet mêlant algorithmique, logique et rendu graphique

Lire l'articleReplier l'article

Ce programme propose une approche complète pour créer un Sudoku jouable : génération d’une grille valide, création d’un puzzle avec cases manquantes, et rendu visuel sur une toile.
Il combine algorithmique, backtracking, manipulation de tableaux et dessin d’interface.

Construction de la grille : une structure 9×9 prête à être remplie

La première étape consiste à initialiser une grille vide de 9 lignes et 9 colonnes, chaque case contenant la valeur 0.
Cette structure sert de base à l’algorithme de résolution.

Vérification des contraintes : respecter les règles du Sudoku

La fonction de validation contrôle trois éléments essentiels :

  • la ligne,
  • la colonne,
  • le bloc 3×3.

Elle garantit qu’un chiffre proposé ne viole aucune règle du Sudoku.
Cette étape est indispensable pour permettre un remplissage correct de la grille.

Génération de la solution : un backtracking complet

L’algorithme de backtracking remplit la grille en cherchant la prochaine case vide, puis en essayant les chiffres de 1 à 9 dans un ordre aléatoire.

Pour chaque tentative :

  • si le chiffre est valide, il est placé,
  • l’algorithme continue,
  • en cas d’impasse, il revient en arrière.

Ce processus se poursuit jusqu’à ce que la grille soit entièrement remplie.
Le résultat est une solution complète et cohérente.

Création du puzzle : retirer des valeurs pour former l’énigme

Une fois la grille solutionnée, le programme retire un nombre défini de cases (ici 45).
Chaque retrait est aléatoire, ce qui permet de générer des puzzles variés.
Le niveau de difficulté dépend du nombre de cases supprimées.

Rendu graphique : dessiner un Sudoku lisible et structuré Le programme utilise une toile de 450×450 pixels pour afficher la grille.

Le rendu inclut :

  • les chiffres présents,
  • les lignes de séparation,
  • des traits plus épais pour les blocs 3×3,
  • une mise en page claire et minimaliste.

Chaque case est représentée par un espace de 50×50 pixels, ce qui assure une lecture confortable.

Exécution finale : un Sudoku généré et affiché automatiquement

Le programme :

  • génère une solution complète,
  • crée un puzzle en retirant des valeurs,
  • dessine la grille sur une toile,
  • affiche le résultat.

Il constitue une base solide pour aller plus loin : ajout d’interactions, vérification des réponses, interface utilisateur, ou export du puzzle.

Conclusion

Ce projet démontre comment un Sudoku peut être généré de manière algorithmique grâce au backtracking, puis rendu visuellement via une interface graphique.
Il combine logique, structure de données, aléatoire contrôlé et dessin, offrant un excellent exercice pour progresser en algorithmique et en développement d’outils interactifs.

Programme :

?@ Initialisation

// Déclaration de la grille de Sudoku 9x9
grille_sudoku est un tableau
pour lig de 0 à 8
    ligne_g est un tableau
    pour col de 0 à 8
        ligne_g ajoute 0
    fin pour
    grille_sudoku ajoute ligne_g
fin pour

@ Fonctions de résolution (Backtracking)

// Vérifie si placer 'val' aux coordonnées (l, c) est autorisé par les règles
fonction estValide(g, l, c, val)
    // 1. Vérification de la ligne
    pour i de 0 à 8
        si g[l][i] = val alors
            retourne faux
        fin si
    fin pour

    // 2. Vérification de la colonne
    pour i de 0 à 8
        si g[i][c] = val alors
            retourne faux
        fin si
    fin pour

    // 3. Vérification du bloc 3x3
    debutL est un nombre
    debutL vaut arrondi_inferieur(l / 3) * 3
    debutC est un nombre
    debutC vaut arrondi_inferieur(c / 3) * 3

    pour i de 0 à 2
        pour j de 0 à 2
            si g[debutL + i][debutC + j] = val alors
                retourne faux
            fin si
        fin pour
    fin pour

    retourne vrai
fin fonction

// Algorithme de Backtracking pour remplir la grille
fonction generer(g)
    l est un nombre
    c est un nombre
    trouve est un booléen
    trouve vaut faux

    // Trouver la prochaine case vide (valeur 0)
    pour i de 0 à 8
        pour j de 0 à 8
            si g[i][j] = 0 alors
                l vaut i
                c vaut j
                trouve vaut vrai
                arrête
            fin si
        fin pour
        si trouve = vrai alors
            arrête
        fin si
    fin pour

    // Condition d'arrêt : plus aucune case vide, le Sudoku est terminé
    si trouve = faux alors
        retourne vrai
    fin si

    // Préparer les chiffres de 1 à 9 et les mélanger pour la génération aléatoire
    possibles est un tableau
    pour n de 1 à 9
        possibles ajoute n
    fin pour
    possibles vaut melanger(possibles)

    num est un nombre
    valide est un booléen
    suite est un booléen

    pour k de 0 à 8
        num vaut possibles[k]
        valide vaut estValide(g, l, c, num)

        si valide = vrai alors
            // Essayer ce chiffre
            g[l][c] vaut num

            // Continuer avec la grille modifiée
            suite vaut generer(g)
            si suite = vrai alors
                retourne vrai
            fin si

            // Échec (impasse) : annuler (Backtrack) et essayer le suivant
            g[l][c] vaut 0
        fin si
    fin pour

    // Aucun chiffre n'a fonctionné, on doit reculer
    retourne faux
fin fonction

@ Création du puzzle

// Retire un nombre défini de chiffres pour le joueur
procédure faireTrous(g, nb)
    trous_faits est un nombre
    trous_faits vaut 0
    l est un nombre
    c est un nombre

    tant que trous_faits < nb
        l vaut hasard(0, 8)
        c vaut hasard(0, 8)
        si g[l][c] <> 0 alors
            g[l][c] vaut 0
            trous_faits ajoute 1
        fin si
    fin tant que
fin procédure

@ Affichage sur la toile

// Rendu visuel de la grille de Sudoku
procédure dessinerSudoku(t, g)
    remplir(t, #FFFFFF)

    x est un nombre
    y est un nombre
    val est un nombre
    txt est un texte

    // 1. Dessiner les cases et les chiffres
    pour l de 0 à 8
        pour c de 0 à 8
            x vaut c * 50
            y vaut l * 50
            val vaut g[l][c]

            si val <> 0 alors
                txt vaut convertir_texte(val)
                label(t, x + 16, y + 35, txt, #1E2430, 24)
            fin si
        fin pour
    fin pour

    // 2. Dessiner les lignes de la grille
    epaisseur est un nombre
    pour i de 0 à 9
        // Les lignes séparant les blocs 3x3 sont plus épaisses
        si i mod 3 = 0 alors
            epaisseur vaut 3
        sinon
            epaisseur vaut 1
        fin si

        // Lignes verticales
        ligne(t, i * 50, 0, i * 50, 450, #000000, epaisseur)
        // Lignes horizontales
        ligne(t, 0, i * 50, 450, i * 50, #000000, epaisseur)
    fin pour
fin procédure

@ Exécution principale

// 1. Remplir complètement la grille avec une solution valide
succes est un booléen
succes vaut generer(grille_sudoku)

// 2. Retirer 45 numéros pour créer le défi
appelle faireTrous(grille_sudoku, 45)

// 3. Préparer et afficher la toile (canevas)
ma_toile est une toile
dimension(ma_toile, 450, 450)
appelle dessinerSudoku(ma_toile, grille_sudoku)

affiche ma_toile
Pas encore noté