CoddyRun
Blog

Articles & Actualités

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

74 publications

Développement d’un RPG isométrique : Les Terres d’Aurion
Article

Développement d’un RPG isométrique

Les Terres d’Aurion

Lire l'articleReplier l'article

Le jeu repose sur une grande carte composée de forêts, chemins, lacs, falaises et habitations.

Chaque élément du décor, arbres, rochers, maisons, fleurs, coffres, feu de camp est dessiné directement par le programme, donnant un rendu unique et entièrement personnalisé.

Les monstres apparaissent dans la forêt, avec des niveaux et des types variés, créant une exploration dynamique et imprévisible.

Un système de combat complet

Les affrontements se déroulent dans un écran dédié où le joueur peut :

  • attaquer,
  • lancer un sort,
  • boire une potion,
  • tenter de fuir.

Les monstres ripostent, certains sont rapides, d’autres puissants, et le boss final — l’Ombre — représente le défi ultime de l’aventure.

Une progression scénarisée

L’histoire se déroule autour d’une quête principale :

  • rencontrer la doyenne du village,
  • s’entraîner dans la forêt,
  • affronter l’Ombre dans la falaise,
  • revenir annoncer la victoire.

Des PNJ accompagnent le joueur :

  • la doyenne Maélis,
  • le garde du chemin,
  • Borin, le marchand qui propose potions, amélioration d’épée et renforcement d’armure.
  • Des coffres cachés, un feu de camp pour se régénérer, et des dialogues intégrés enrichissent l’expérience.

Une aventure complète

Une fois l’Ombre vaincue, le jeu affiche un écran de victoire célébrant la libération des terres et la réussite du héros.
L’ensemble forme une petite aventure cohérente, jouable du début à la fin, avec exploration, combats, progression et conclusion.

Programme :

@ CONFIGURATION
graph est une toile
dimension(graph, 640, 520)

COTE est un nombre
COTE vaut 19

// --- Position et caméra ---
jX est un nombre
jY est un nombre
jX vaut 5
jY vaut 17

zoom est un nombre
zoom vaut 100
orientation est un nombre
orientation vaut 0

// --- Fiche du héros ---
niv est un nombre
niv vaut 1
xp est un nombre
xp vaut 0
xpSuiv est un nombre
xpSuiv vaut 30
pv est un nombre
pvMax est un nombre
pv vaut 40
pvMax vaut 40
mana est un nombre
manaMax est un nombre
mana vaut 12
manaMax vaut 12
attaque est un nombre
attaque vaut 7
defense est un nombre
defense vaut 3
pieces est un nombre
pieces vaut 25
potions est un nombre
potions vaut 2
epee est un nombre
epee vaut 0
armure est un nombre
armure vaut 0

// --- Avancement de l'histoire ---
// quete : 0 pas encore parlé au chef, 1 en cours, 2 Ombre vaincue
quete est un nombre
quete vaut 0
ombreVaincue est un booléen
ombreVaincue vaut faux

// --- Écran actif : monde, combat, dialogue, boutique, fin ---
ecran est un texte
ecran vaut "monde"

message est un texte
message vaut "Va parler au chef du village (le personnage en rouge)"

// --- Adversaire du combat en cours ---
mNom est un texte
mTyp est un nombre
mNiv est un nombre
mPv est un nombre
mPvMax est un nombre
mAtt est un nombre
mDef est un nombre
mLig est un nombre
mCol est un nombre
cbMsg1 est un texte
cbMsg2 est un texte
cbMsg1 vaut ""
cbMsg2 vaut ""

// --- Dialogue en cours ---
dlgNom est un texte
dlgL1 est un texte
dlgL2 est un texte
dlgL3 est un texte
dlgNom vaut ""
dlgL1 vaut ""
dlgL2 vaut ""
dlgL3 vaut ""

action est un nombre

@ GRILLES DU MONDE
// terr : 0 herbe, 1 eau, 2 chemin, 3 pierre
// objt : 0 rien, 1 arbre, 2 rocher, 3 mur, 5 coffre, 6 panneau,
//        7 chef, 8 marchand, 9 garde, 10 monstre, 11 fleur, 12 feu de camp
terr est un tableau
objt est un tableau
mobTyp est un tableau
mobNiv est un tableau
pour i de 0 à COTE
    lT est un tableau
    lO est un tableau
    lMt est un tableau
    lMn est un tableau
    pour j de 0 à COTE
        lT ajoute 0
        lO ajoute 0
        lMt ajoute 0
        lMn ajoute 0
    fin pour
    terr ajoute lT
    objt ajoute lO
    mobTyp ajoute lMt
    mobNiv ajoute lMn
fin pour

@ CONSTRUCTION DU MONDE

// --- La falaise de pierre, au nord ---
pour i de 0 à 2
    pour j de 0 à COTE
        terr[i][j] vaut 3
        objt[i][j] vaut 2
    fin pour
fin pour
// Un couloir laissé libre dans la falaise
pour i de 0 à 2
    objt[i][14] vaut 0
    objt[i][15] vaut 0
fin pour

// --- Le lac ---
pour i de 8 à 11
    pour j de 12 à 17
        terr[i][j] vaut 1
    fin pour
fin pour
terr[8][17] vaut 0
terr[11][12] vaut 0

// --- Les chemins ---
pour i de 3 à 18
    terr[i][5] vaut 2
fin pour
pour j de 5 à 15
    terr[4][j] vaut 2
fin pour
pour i de 2 à 4
    terr[i][14] vaut 2
fin pour
pour i de 16 à 17
    pour j de 4 à 6
        terr[i][j] vaut 2
    fin pour
fin pour

// --- La forêt ---
pour i de 6 à 13
    pour j de 0 à COTE
        si terr[i][j] = 0 et objt[i][j] = 0 alors
            si hasard(1, 100) <= 22 alors
                objt[i][j] vaut 1
            sinon si hasard(1, 100) <= 8 alors
                objt[i][j] vaut 11
            fin si
        fin si
    fin pour
fin pour

// --- Le village, au sud ---
pour j de 2 à 3
    objt[15][j] vaut 3
    objt[14][j] vaut 3
fin pour
pour j de 7 à 8
    objt[15][j] vaut 3
    objt[14][j] vaut 3
fin pour
pour j de 3 à 6
    objt[19][j] vaut 3
fin pour

objt[16][6] vaut 7
objt[17][6] vaut 8
objt[17][4] vaut 12
objt[14][4] vaut 6
objt[4][10] vaut 9

// --- Coffres cachés dans la nature ---
objt[9][2] vaut 5
objt[12][17] vaut 5
objt[6][18] vaut 5

// --- L'Ombre garde la falaise ---
objt[1][14] vaut 10
mobTyp[1][14] vaut 4
mobNiv[1][14] vaut 8

// --- Monstres errants ---
poses est un nombre
poses vaut 0
essais est un nombre
essais vaut 0
tant que poses < 12 et essais < 600
    essais ajoute 1
    oi est un nombre
    oj est un nombre
    oi vaut hasard(5, 13)
    oj vaut hasard(0, COTE)
    si terr[oi][oj] = 0 et objt[oi][oj] = 0 alors
        objt[oi][oj] vaut 10
        nvM est un nombre
        nvM vaut hasard(1, 3)
        si oi <= 6 alors
            nvM vaut hasard(3, 5)
        fin si
        mobNiv[oi][oj] vaut nvM
        si nvM <= 2 alors
            mobTyp[oi][oj] vaut 1
        sinon si nvM <= 4 alors
            mobTyp[oi][oj] vaut 2
        sinon
            mobTyp[oi][oj] vaut 3
        fin si
        poses ajoute 1
    fin si
fin tant que

// La case de départ reste dégagée
objt[17][5] vaut 0
objt[16][5] vaut 0

@ TUILE DE TERRAIN
procédure dessinerSol(t, cx, cy, dx, dy, prof, cTop, cG, cD)
    // contour règle le trait de TOUT ce qui suit : on l'active ici, on l'éteint à la fin
    contour(t, #1B4332, 1)

    pFG est un tableau
    pFG ajoute cx - dx
    pFG ajoute cy
    pFG ajoute cx
    pFG ajoute cy + dy
    pFG ajoute cx
    pFG ajoute cy + dy + prof
    pFG ajoute cx - dx
    pFG ajoute cy + prof
    polygone(t, pFG, cG)

    pFD est un tableau
    pFD ajoute cx
    pFD ajoute cy + dy
    pFD ajoute cx + dx
    pFD ajoute cy
    pFD ajoute cx + dx
    pFD ajoute cy + prof
    pFD ajoute cx
    pFD ajoute cy + dy + prof
    polygone(t, pFD, cD)

    pTop est un tableau
    pTop ajoute cx
    pTop ajoute cy - dy
    pTop ajoute cx + dx
    pTop ajoute cy
    pTop ajoute cx
    pTop ajoute cy + dy
    pTop ajoute cx - dx
    pTop ajoute cy
    polygone(t, pTop, cTop)

    contour(t, #0)
fin procédure

@ CUBE
procédure dessinerCube(t, cx, cy, dx, dy, h, cTop, cG, cD)
    contour(t, #1F2937, 1)

    lCY est un nombre
    lCY vaut cy - h

    pFG est un tableau
    pFG ajoute cx - dx
    pFG ajoute lCY
    pFG ajoute cx
    pFG ajoute lCY + dy
    pFG ajoute cx
    pFG ajoute lCY + dy + h
    pFG ajoute cx - dx
    pFG ajoute lCY + h
    polygone(t, pFG, cG)

    pFD est un tableau
    pFD ajoute cx
    pFD ajoute lCY + dy
    pFD ajoute cx + dx
    pFD ajoute lCY
    pFD ajoute cx + dx
    pFD ajoute lCY + h
    pFD ajoute cx
    pFD ajoute lCY + dy + h
    polygone(t, pFD, cD)

    pTop est un tableau
    pTop ajoute cx
    pTop ajoute lCY - dy
    pTop ajoute cx + dx
    pTop ajoute lCY
    pTop ajoute cx
    pTop ajoute lCY + dy
    pTop ajoute cx - dx
    pTop ajoute lCY
    polygone(t, pTop, cTop)

    contour(t, #0)
fin procédure

@ PERSONNAGE (héros et PNJ partagent le même dessin)
procédure dessinerPerso(t, cx, cy, e, cHaut, cBas, cChev)
    ellipse(t, cx, cy + (2 * e), 11 * e, 5 * e, rgba(0, 0, 0, 0.4))
    rectangle_arrondi(t, cx - (4 * e), cy - (10 * e), 8 * e, 10 * e, 2, cBas)
    rectangle_arrondi(t, cx - (9 * e), cy - (20 * e), 4 * e, 9 * e, 2, #FCD34D)
    rectangle_arrondi(t, cx - (5 * e), cy - (21 * e), 10 * e, 12 * e, 2, cHaut)
    rectangle_arrondi(t, cx + (5 * e), cy - (20 * e), 4 * e, 9 * e, 2, #FCD34D)
    rectangle_arrondi(t, cx - (6 * e), cy - (30 * e), 12 * e, 10 * e, 3, #FCD34D)
    rectangle_arrondi(t, cx - (6 * e), cy - (32 * e), 13 * e, 5 * e, 2, cChev)
    cercle(t, cx - (2 * e), cy - (25 * e), 1 * e, #0F172A)
    cercle(t, cx + (2 * e), cy - (25 * e), 1 * e, #0F172A)
fin procédure

@ MONSTRE
procédure dessinerMonstre(t, cx, cy, e, typ)
    ellipse(t, cx, cy + (2 * e), 12 * e, 6 * e, rgba(0, 0, 0, 0.4))

    si typ = 1 alors
        // Gluant
        ellipse(t, cx, cy - (8 * e), 13 * e, 10 * e, #22C55E)
        ellipse(t, cx - (4 * e), cy - (12 * e), 4 * e, 3 * e, rgba(255, 255, 255, 0.45))
        cercle(t, cx - (4 * e), cy - (9 * e), 2 * e, #052E16)
        cercle(t, cx + (4 * e), cy - (9 * e), 2 * e, #052E16)

    sinon si typ = 2 alors
        // Loup
        ellipse(t, cx, cy - (10 * e), 14 * e, 7 * e, #64748B)
        cercle(t, cx + (8 * e), cy - (16 * e), 6 * e, #94A3B8)
        pOr est un tableau
        pOr ajoute cx + (4 * e)
        pOr ajoute cy - (20 * e)
        pOr ajoute cx + (7 * e)
        pOr ajoute cy - (28 * e)
        pOr ajoute cx + (10 * e)
        pOr ajoute cy - (20 * e)
        polygone(t, pOr, #475569)
        cercle(t, cx + (7 * e), cy - (17 * e), 2 * e, #EF4444)
        cercle(t, cx + (12 * e), cy - (16 * e), 1 * e, #0F172A)

    sinon si typ = 3 alors
        // Squelette
        rectangle_arrondi(t, cx - (2 * e), cy - (18 * e), 4 * e, 18 * e, 2, #E2E8F0)
        rectangle(t, cx - (7 * e), cy - (16 * e), 14 * e, 2 * e, #CBD5E1)
        rectangle(t, cx - (6 * e), cy - (12 * e), 12 * e, 2 * e, #CBD5E1)
        cercle(t, cx, cy - (23 * e), 6 * e, #F8FAFC)
        cercle(t, cx - (2 * e), cy - (24 * e), 2 * e, #0F172A)
        cercle(t, cx + (2 * e), cy - (24 * e), 2 * e, #0F172A)

    sinon
        // L'Ombre
        ellipse(t, cx, cy, 18 * e, 8 * e, rgba(88, 28, 135, 0.55))
        appelle dessinerCube(t, cx, cy, 11 * e, 6 * e, 26 * e, #6D28D9, #4C1D95, #3B1073)
        cercle(t, cx - (5 * e), cy - (24 * e), 3 * e, #F87171)
        cercle(t, cx + (5 * e), cy - (24 * e), 3 * e, #F87171)
        ellipse(t, cx, cy - (36 * e), 10 * e, 5 * e, rgba(168, 85, 247, 0.5))
    fin si
fin procédure

@ DÉCOR
procédure dessinerDecor(t, cx, cy, val, dx, dy, e)
    si val = 1 alors
        // Arbre
        ellipse(t, cx, cy + (2 * e), 10 * e, 5 * e, rgba(0, 0, 0, 0.35))
        rectangle(t, cx - (3 * e), cy - (18 * e), 6 * e, 18 * e, #92400E)
        cercle(t, cx, cy - (26 * e), 11 * e, #15803D)
        cercle(t, cx - (6 * e), cy - (20 * e), 8 * e, #166534)
        cercle(t, cx + (6 * e), cy - (21 * e), 8 * e, #22C55E)

    sinon si val = 2 alors
        // Rocher
        appelle dessinerCube(t, cx, cy, dx * 3 / 4, dy * 3 / 4, 14 * e, #9CA3AF, #6B7280, #4B5563)
        cercle(t, cx - (4 * e), cy - (18 * e), 3 * e, #D1D5DB)

    sinon si val = 3 alors
        // Mur de maison, avec son toit
        appelle dessinerCube(t, cx, cy, dx, dy, 20 * e, #C2410C, #9A3412, #7C2D12)
        appelle dessinerCube(t, cx, cy - (20 * e), dx, dy, 8 * e, #DC2626, #B91C1C, #991B1B)

    sinon si val = 5 alors
        // Coffre
        appelle dessinerCube(t, cx, cy, dx * 2 / 3, dy * 2 / 3, 12 * e, #A16207, #854D0E, #713F12)
        rectangle(t, cx - (3 * e), cy - (9 * e), 6 * e, 3 * e, #FDE68A)

    sinon si val = 6 alors
        // Panneau
        rectangle(t, cx - (1 * e), cy - (14 * e), 3 * e, 14 * e, #78350F)
        rectangle_arrondi(t, cx - (10 * e), cy - (24 * e), 20 * e, 11 * e, 2, #B45309)
        rectangle(t, cx - (7 * e), cy - (20 * e), 14 * e, 2 * e, #FDE68A)

    sinon si val = 11 alors
        // Fleurs
        cercle(t, cx - (5 * e), cy - (2 * e), 2 * e, #F9A8D4)
        cercle(t, cx + (4 * e), cy - (4 * e), 2 * e, #FDE68A)
        cercle(t, cx + (1 * e), cy + (2 * e), 2 * e, #93C5FD)

    sinon si val = 12 alors
        // Feu de camp
        cercle(t, cx, cy, 9 * e, #57534E)
        rectangle(t, cx - (8 * e), cy - (4 * e), 16 * e, 3 * e, #78350F)
        ellipse(t, cx, cy - (12 * e), 6 * e, 10 * e, rgba(249, 115, 22, 0.9))
        ellipse(t, cx, cy - (10 * e), 3 * e, 6 * e, #FDE68A)
    fin si
fin procédure

@ INTERFACE DU MONDE
procédure dessinerHud(t, nv, p, pM, mn, mM, x, xS, po, pot, ep, ar, q, msg)
    rectangle(t, 0, 0, 640, 74, rgba(15, 23, 42, 0.8))
    label(t, 12, 20, "LES TERRES D'AURION", #FFFFFF, 13)
    label(t, 200, 20, "Niveau " + nv, #FDE68A, 13)

    // Barre de vie
    rectangle_arrondi(t, 280, 9, 130, 12, 6, #334155)
    lg est un nombre
    lg vaut arrondi(130 * p / pM)
    lg vaut limiter(lg, 0, 130)
    si lg > 0 alors
        rectangle_arrondi(t, 280, 9, lg, 12, 6, #EF4444)
    fin si
    label(t, 418, 19, "PV " + p + "/" + pM, #FCA5A5, 11)

    // Barre de mana
    rectangle_arrondi(t, 500, 9, 80, 12, 6, #334155)
    lm est un nombre
    lm vaut arrondi(80 * mn / mM)
    lm vaut limiter(lm, 0, 80)
    si lm > 0 alors
        rectangle_arrondi(t, 500, 9, lm, 12, 6, #3B82F6)
    fin si
    label(t, 586, 19, "MP " + mn, #93C5FD, 11)

    // Barre d'expérience
    rectangle_arrondi(t, 12, 30, 200, 8, 4, #334155)
    lx est un nombre
    lx vaut arrondi(200 * x / xS)
    lx vaut limiter(lx, 0, 200)
    si lx > 0 alors
        rectangle_arrondi(t, 12, 30, lx, 8, 4, #A855F7)
    fin si
    label(t, 220, 38, "XP " + x + "/" + xS, #E9D5FF, 11)

    label(t, 320, 38, "Or : " + po + "   Potions : " + pot + "   Épée +" + ep + "   Armure +" + ar, #CBD5E1, 11)

    txtQ est un texte
    si q = 0 alors
        txtQ vaut "Quête : parler au chef du village"
    sinon si q = 1 alors
        txtQ vaut "Quête : vaincre l'Ombre, au nord de la falaise"
    sinon
        txtQ vaut "Quête : annoncer ta victoire au chef"
    fin si
    label(t, 12, 56, txtQ, #FDE68A, 11)
    label(t, 340, 56, "[Z S Q D] bouger  [E] potion  [T] caméra  [+/-] zoom", #94A3B8, 10)

    rectangle(t, 0, 486, 640, 34, rgba(15, 23, 42, 0.8))
    label(t, 12, 508, msg, #F8FAFC, 13)
fin procédure

@ ÉCRAN DE COMBAT
procédure dessinerCombat(t, nom, nvM, mp, mpM, typ, p, pM, mn, mM, pot, l1, l2)
    effacer(t)
    remplir(t, #0F172A)
    contour(t, #0)

    // Décor de fond
    rectangle(t, 0, 0, 640, 250, #1E293B)
    ellipse(t, 400, 250, 220, 60, #334155)
    ellipse(t, 150, 330, 200, 50, #1E293B)

    label(t, 20, 34, "COMBAT", #F87171, 20)

    // L'adversaire
    label(t, 380, 70, nom + "  (niveau " + nvM + ")", #FCA5A5, 15)
    rectangle_arrondi(t, 380, 82, 220, 14, 7, #334155)
    lm est un nombre
    lm vaut arrondi(220 * mp / mpM)
    lm vaut limiter(lm, 0, 220)
    si lm > 0 alors
        rectangle_arrondi(t, 380, 82, lm, 14, 7, #EF4444)
    fin si
    label(t, 380, 112, mp + " / " + mpM + " PV", #CBD5E1, 12)
    appelle dessinerMonstre(t, 470, 240, 3, typ)

    // Le héros, de dos
    appelle dessinerPerso(t, 150, 330, 3, #38BDF8, #1E3A8A, #EA580C)
    label(t, 40, 380, "Toi", #93C5FD, 14)
    rectangle_arrondi(t, 40, 388, 200, 14, 7, #334155)
    lp est un nombre
    lp vaut arrondi(200 * p / pM)
    lp vaut limiter(lp, 0, 200)
    si lp > 0 alors
        rectangle_arrondi(t, 40, 388, lp, 14, 7, #22C55E)
    fin si
    label(t, 40, 418, "PV " + p + "/" + pM + "     MP " + mn + "/" + mM, #CBD5E1, 12)

    // Journal de combat
    contour(t, #475569, 1)
    rectangle_arrondi(t, 300, 370, 320, 60, 8, rgba(2, 6, 23, 0.85))
    contour(t, #0)
    label(t, 312, 392, l1, #FDE68A, 12)
    label(t, 312, 414, l2, #FDE68A, 12)

    // Menu d'actions
    rectangle(t, 0, 450, 640, 70, rgba(15, 23, 42, 0.95))
    label(t, 20, 476, "[A] Attaquer", #F8FAFC, 14)
    label(t, 170, 476, "[M] Magie (6 MP)", #93C5FD, 14)
    label(t, 360, 476, "[P] Potion (" + pot + ")", #C4B5FD, 14)
    label(t, 520, 476, "[F] Fuir", #FCA5A5, 14)
    label(t, 20, 502, "Appuie sur la lettre de ton choix", #64748B, 11)
    affiche t
fin procédure

@ ÉCRAN DE DIALOGUE
procédure dessinerDialogue(t, nom, l1, l2, l3)
    contour(t, #FDE68A, 1)
    rectangle_arrondi(t, 60, 300, 520, 160, 12, rgba(2, 6, 23, 0.95))
    contour(t, #0)
    label(t, 80, 330, nom, #FDE68A, 16)
    label(t, 80, 364, l1, #F8FAFC, 13)
    label(t, 80, 388, l2, #F8FAFC, 13)
    label(t, 80, 412, l3, #F8FAFC, 13)
    label(t, 80, 444, "[E] continuer", #93C5FD, 12)
    affiche t
fin procédure

@ ÉCRAN DE BOUTIQUE
procédure dessinerBoutique(t, po, pot, ep, ar)
    contour(t, #FDE68A, 1)
    rectangle_arrondi(t, 60, 240, 520, 230, 12, rgba(2, 6, 23, 0.95))
    contour(t, #0)
    label(t, 80, 272, "ÉCHOPPE DE BORIN", #FDE68A, 18)
    label(t, 80, 300, "Ta bourse : " + po + " pièces d'or", #CBD5E1, 13)

    prixEp est un nombre
    prixAr est un nombre
    prixEp vaut 60 + ep * 50
    prixAr vaut 55 + ar * 50

    label(t, 80, 336, "[P]  Potion de soin", #F8FAFC, 14)
    label(t, 400, 336, "20 or", #FDE68A, 14)
    label(t, 80, 366, "[A]  Affûter l'épée  (+" + ep + " ? +" + (ep + 1) + ")", #F8FAFC, 14)
    label(t, 400, 366, prixEp + " or", #FDE68A, 14)
    label(t, 80, 396, "[R]  Renforcer l'armure  (+" + ar + " ? +" + (ar + 1) + ")", #F8FAFC, 14)
    label(t, 400, 396, prixAr + " or", #FDE68A, 14)
    label(t, 80, 440, "[Q]  Sortir de l'échoppe", #93C5FD, 13)
    label(t, 300, 440, "Potions en poche : " + pot, #64748B, 12)
    affiche t
fin procédure

@ ÉCRAN DE VICTOIRE
procédure dessinerFin(t, nv, po)
    effacer(t)
    remplir(t, #1E1B4B)
    contour(t, #0)
    pour k de 0 à 40
        cercle(t, hasard(0, 640), hasard(0, 300), hasard(1, 2), #FDE68A)
    fin pour
    label(t, 120, 180, "?? LES TERRES SONT LIBRES", #FDE68A, 26)
    label(t, 120, 230, "L'Ombre a été vaincue et le village respire.", #F8FAFC, 15)
    label(t, 120, 262, "Héros de niveau " + nv + ", " + po + " pièces d'or en poche.", #CBD5E1, 14)
    label(t, 120, 320, "Merci d'avoir joué.", #93C5FD, 14)
    affiche t
fin procédure

@ BOUCLE PRINCIPALE

tant que vrai

    // ==================================================
    //                     LE MONDE
    // ==================================================
    si ecran = "monde" alors

        effacer(graph)
        remplir(graph, #071018)
        contour(graph, #0)

        ech est un nombre
        ech vaut zoom / 100
        dx est un nombre
        dy est un nombre
        dx vaut 26 * ech
        dy vaut 13 * ech
        profSol est un nombre
        profSol vaut 14 * ech

        // De l'arrière vers l'avant : rY puis rX croissants
        pour rY de - 8 à 8
            pour rX de - 8 à 8

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

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

                si gx >= 0 et gx <= COTE et gy >= 0 et gy <= COTE alors
                    cx est un nombre
                    cy est un nombre
                    cx vaut 320 + (rX - rY) * dx
                    cy vaut 250 + (rX + rY) * dy

                    si cx >= - 80 et cx <= 720 et cy >= - 80 et cy <= 580 alors
                        tv est un nombre
                        ov est un nombre
                        tv vaut terr[gy][gx]
                        ov vaut objt[gy][gx]

                        // --- Le terrain ---
                        si tv = 1 alors
                            // L'eau est légèrement en creux
                            appelle dessinerSol(graph, cx, cy + (5 * ech), dx, dy, profSol, #1D4ED8, #1E3A8A, #172554)
                            ellipse(graph, cx - (6 * ech), cy + (2 * ech), 6 * ech, 2 * ech, rgba(191, 219, 254, 0.5))
                        sinon si tv = 2 alors
                            appelle dessinerSol(graph, cx, cy, dx, dy, profSol, #B08968, #8C6B4F, #6B5039)
                        sinon si tv = 3 alors
                            appelle dessinerSol(graph, cx, cy, dx, dy, profSol, #6B7280, #4B5563, #374151)
                        sinon
                            appelle dessinerSol(graph, cx, cy, dx, dy, profSol, #3F8F52, #2F6E3E, #235430)
                        fin si

                        // --- Ce qui se dresse dessus ---
                        si ov = 10 alors
                            appelle dessinerMonstre(graph, cx, cy, ech, mobTyp[gy][gx])
                        sinon si ov = 7 alors
                            appelle dessinerPerso(graph, cx, cy, ech, #DC2626, #7F1D1D, #E2E8F0)
                        sinon si ov = 8 alors
                            appelle dessinerPerso(graph, cx, cy, ech, #16A34A, #14532D, #78350F)
                        sinon si ov = 9 alors
                            appelle dessinerPerso(graph, cx, cy, ech, #94A3B8, #475569, #1F2937)
                        sinon si ov > 0 alors
                            appelle dessinerDecor(graph, cx, cy, ov, dx, dy, ech)
                        fin si
                    fin si
                fin si
            fin pour
        fin pour

        // Le héros est peint en dernier : rien ne peut le masquer
        appelle dessinerPerso(graph, 320, 250, ech, #38BDF8, #1E3A8A, #EA580C)

        appelle dessinerHud(graph, niv, pv, pvMax, mana, manaMax, xp, xpSuiv, pieces, potions, epee, armure, quete, message)
        affiche graph

        appuyer ["z", "s", "q", "d", "e", "t", "+", "-"] dans action

        sX est un nombre
        sY est un nombre
        sX vaut 0
        sY vaut 0
        bouge est un booléen
        bouge vaut faux

        si action = 1 alors
            sY vaut 0 - 1
            bouge vaut vrai
        sinon si action = 2 alors
            sY vaut 1
            bouge vaut vrai
        sinon si action = 3 alors
            sX vaut 0 - 1
            bouge vaut vrai
        sinon si action = 4 alors
            sX vaut 1
            bouge vaut vrai
        sinon si action = 5 alors
            si potions > 0 et pv < pvMax alors
                potions vaut potions - 1
                soin est un nombre
                soin vaut 30 + niv * 4
                pv vaut limiter(pv + soin, 0, pvMax)
                message vaut "Potion bue : + " + soin + " PV"
            sinon si potions <= 0 alors
                message vaut "Tu n'as plus aucune potion"
            sinon
                message vaut "Tu es déjà en pleine forme"
            fin si
        sinon si action = 6 alors
            orientation vaut orientation + 1
            si orientation > 3 alors
                orientation vaut 0
            fin si
        sinon si action = 7 alors
            zoom vaut limiter(zoom + 20, 60, 180)
        sinon si action = 8 alors
            zoom vaut limiter(zoom - 20, 60, 180)
        fin si

        si bouge = vrai alors
            // La même rotation que pour l'affichage
            mX est un nombre
            mY est un nombre
            si orientation = 0 alors
                mX vaut sX
                mY vaut sY
            sinon si orientation = 1 alors
                mX vaut 0 - sY
                mY vaut sX
            sinon si orientation = 2 alors
                mX vaut 0 - sX
                mY vaut 0 - sY
            sinon
                mX vaut sY
                mY vaut 0 - sX
            fin si

            nX est un nombre
            nY est un nombre
            nX vaut jX + mX
            nY vaut jY + mY

            si nX < 0 ou nX > COTE ou nY < 0 ou nY > COTE alors
                message vaut "Les terres s'arrêtent ici"
            sinon
                tCible est un nombre
                oCible est un nombre
                tCible vaut terr[nY][nX]
                oCible vaut objt[nY][nX]

                si tCible = 1 alors
                    message vaut "Le lac est trop profond pour toi"

                sinon si oCible = 1 alors
                    message vaut "Un arbre te barre la route"
                sinon si oCible = 2 alors
                    message vaut "Un rocher infranchissable"
                sinon si oCible = 3 alors
                    message vaut "Une maison aux volets clos"

                sinon si oCible = 6 alors
                    message vaut "Panneau : « Nord = la falaise. N'y va pas seul. »"

                sinon si oCible = 12 alors
                    pv vaut pvMax
                    mana vaut manaMax
                    message vaut "Tu te reposes près du feu : PV et MP au maximum"

                sinon si oCible = 5 alors
                    objt[nY][nX] vaut 0
                    gain est un nombre
                    gain vaut hasard(20, 45)
                    pieces ajoute gain
                    potions ajoute 1
                    message vaut "Coffre ouvert : " + gain + " or et une potion !"

                sinon si oCible = 7 alors
                    // Le chef du village
                    dlgNom vaut "Maélis, doyenne du village"
                    si ombreVaincue = vrai alors
                        ecran vaut "fin"
                    sinon si quete = 0 alors
                        quete vaut 1
                        dlgL1 vaut "Une Ombre s'est réveillée au nord, dans la falaise."
                        dlgL2 vaut "Nos récoltes noircissent. Va la combattre,"
                        dlgL3 vaut "mais entraîne-toi d'abord dans la forêt."
                        ecran vaut "dialogue"
                    sinon
                        dlgL1 vaut "L'Ombre rôde toujours au nord de la falaise."
                        dlgL2 vaut "Chasse les monstres pour gagner des niveaux,"
                        dlgL3 vaut "et vois Borin pour ton équipement."
                        ecran vaut "dialogue"
                    fin si

                sinon si oCible = 8 alors
                    ecran vaut "boutique"

                sinon si oCible = 9 alors
                    si quete = 0 alors
                        dlgNom vaut "Garde du chemin"
                        dlgL1 vaut "Halte. Personne ne monte vers la falaise"
                        dlgL2 vaut "sans l'accord de la doyenne Maélis."
                        dlgL3 vaut "Va lui parler, elle est sur la place."
                        ecran vaut "dialogue"
                    sinon
                        objt[nY][nX] vaut 0
                        dlgNom vaut "Garde du chemin"
                        dlgL1 vaut "La doyenne t'a donc choisi. Passe."
                        dlgL2 vaut "Le chemin monte vers le nord, puis tourne."
                        dlgL3 vaut "Que la lumière t'accompagne, héros."
                        ecran vaut "dialogue"
                    fin si

                sinon si oCible = 10 alors
                    // Un combat commence
                    mTyp vaut mobTyp[nY][nX]
                    mNiv vaut mobNiv[nY][nX]
                    mLig vaut nY
                    mCol vaut nX
                    si mTyp = 1 alors
                        mNom vaut "Gluant des bois"
                    sinon si mTyp = 2 alors
                        mNom vaut "Loup gris"
                    sinon si mTyp = 3 alors
                        mNom vaut "Squelette errant"
                    sinon
                        mNom vaut "L'OMBRE"
                    fin si
                    si mTyp = 4 alors
                        mPvMax vaut 170
                        mAtt vaut 24
                        mDef vaut 7
                    sinon
                        mPvMax vaut 16 + mNiv * 9
                        mAtt vaut 5 + mNiv * 3
                        mDef vaut mNiv
                    fin si
                    mPv vaut mPvMax
                    cbMsg1 vaut mNom + " surgit devant toi !"
                    cbMsg2 vaut ""
                    ecran vaut "combat"

                sinon
                    jX vaut nX
                    jY vaut nY
                    si oCible = 11 alors
                        message vaut "Tu traverses un tapis de fleurs"
                    sinon si tCible = 2 alors
                        message vaut "Tu suis le chemin"
                    sinon si tCible = 3 alors
                        message vaut "La roche crisse sous tes pas"
                    sinon
                        message vaut "Tu avances dans les herbes hautes"
                    fin si
                fin si
            fin si
        fin si

    // ==================================================
    //                    LE COMBAT
    // ==================================================
    sinon si ecran = "combat" alors

        appelle dessinerCombat(graph, mNom, mNiv, mPv, mPvMax, mTyp, pv, pvMax, mana, manaMax, potions, cbMsg1, cbMsg2)
        appuyer ["a", "m", "p", "f"] dans action

        riposte est un booléen
        riposte vaut vrai
        fuite est un booléen
        fuite vaut faux

        si action = 1 alors
            // Attaquer
            dg est un nombre
            dg vaut attaque + epee * 4 + hasard(0, 5) - mDef
            dg vaut limiter(dg, 1, 999)
            mPv vaut mPv - dg
            cbMsg1 vaut "Tu frappes : - " + dg + " PV pour " + mNom

        sinon si action = 2 alors
            // Magie
            si mana >= 6 alors
                mana vaut mana - 6
                dg vaut 9 + niv * 4 + hasard(0, 7)
                mPv vaut mPv - dg
                cbMsg1 vaut "Éclair de lumière : - " + dg + " PV (la défense ne protège pas)"
            sinon
                cbMsg1 vaut "Pas assez de mana pour lancer le sort"
            fin si

        sinon si action = 3 alors
            // Potion
            si potions > 0 alors
                potions vaut potions - 1
                soin vaut 30 + niv * 4
                pv vaut limiter(pv + soin, 0, pvMax)
                cbMsg1 vaut "Tu bois une potion : + " + soin + " PV"
            sinon
                cbMsg1 vaut "Tes poches sont vides"
            fin si

        sinon
            // Fuir
            si mTyp = 4 alors
                cbMsg1 vaut "Impossible de fuir l'Ombre !"
            sinon si hasard(1, 100) <= 60 alors
                fuite vaut vrai
                riposte vaut faux
            sinon
                cbMsg1 vaut "Ta fuite échoue !"
            fin si
        fin si

        si fuite = vrai alors
            ecran vaut "monde"
            message vaut "Tu prends la fuite, le souffle court"

        sinon si mPv <= 0 alors
            // Victoire
            objt[mLig][mCol] vaut 0
            gainXp est un nombre
            gainOr est un nombre
            gainXp vaut 12 + mNiv * 9
            gainOr vaut 8 + mNiv * 6
            si mTyp = 4 alors
                gainXp vaut 200
                gainOr vaut 300
                ombreVaincue vaut vrai
                quete vaut 2
            fin si
            xp ajoute gainXp
            pieces ajoute gainOr

            // Montées de niveau
            tant que xp >= xpSuiv
                xp vaut xp - xpSuiv
                niv ajoute 1
                pvMax ajoute 12
                manaMax ajoute 5
                attaque ajoute 2
                defense ajoute 1
                xpSuiv vaut 30 + niv * 20
                pv vaut pvMax
                mana vaut manaMax
            fin tant que

            // Un nouveau monstre apparaît ailleurs dans la forêt
            si mTyp <> 4 alors
                essR est un nombre
                essR vaut 0
                placeOk est un booléen
                placeOk vaut faux
                tant que placeOk = faux et essR < 200
                    essR ajoute 1
                    ri est un nombre
                    rj est un nombre
                    ri vaut hasard(5, 13)
                    rj vaut hasard(0, COTE)
                    si terr[ri][rj] = 0 et objt[ri][rj] = 0 alors
                        objt[ri][rj] vaut 10
                        nvN est un nombre
                        nvN vaut hasard(1, 4)
                        mobNiv[ri][rj] vaut nvN
                        si nvN <= 2 alors
                            mobTyp[ri][rj] vaut 1
                        sinon si nvN <= 4 alors
                            mobTyp[ri][rj] vaut 2
                        sinon
                            mobTyp[ri][rj] vaut 3
                        fin si
                        placeOk vaut vrai
                    fin si
                fin tant que
            fin si

            ecran vaut "monde"
            si mTyp = 4 alors
                message vaut "L'OMBRE EST VAINCUE ! Retourne voir la doyenne Maélis"
            sinon
                message vaut mNom + " vaincu : + " + gainXp + " XP, + " + gainOr + " or"
            fin si

        sinon si riposte = vrai alors
            // Le monstre riposte
            dgM est un nombre
            dgM vaut mAtt + hasard(0, 4) - (defense + armure * 3)
            dgM vaut limiter(dgM, 1, 999)
            pv vaut pv - dgM
            cbMsg2 vaut mNom + " riposte : - " + dgM + " PV"

            si pv <= 0 alors
                // Défaite : on se réveille au village, allégé de la moitié de sa bourse
                pv vaut arrondi(pvMax / 2)
                mana vaut manaMax
                perte est un nombre
                perte vaut arrondi(pieces / 2)
                pieces vaut pieces - perte
                jX vaut 5
                jY vaut 17
                ecran vaut "monde"
                message vaut "Tu t'effondres... On te ramène au village. Perdu : " + perte + " or"
            fin si
        fin si

    // ==================================================
    //                   LE DIALOGUE
    // ==================================================
    sinon si ecran = "dialogue" alors
        appelle dessinerDialogue(graph, dlgNom, dlgL1, dlgL2, dlgL3)
        appuyer ["e"] dans action
        ecran vaut "monde"
        message vaut "Tu reprends ta route"

    // ==================================================
    //                   LA BOUTIQUE
    // ==================================================
    sinon si ecran = "boutique" alors
        appelle dessinerBoutique(graph, pieces, potions, epee, armure)
        appuyer ["p", "a", "r", "q"] dans action

        si action = 1 alors
            si pieces >= 20 alors
                pieces vaut pieces - 20
                potions ajoute 1
                message vaut "Potion achetée"
            sinon
                message vaut "Pas assez d'or pour une potion"
            fin si
        sinon si action = 2 alors
            coutE est un nombre
            coutE vaut 60 + epee * 50
            si epee >= 3 alors
                message vaut "Borin : « Ton épée est déjà parfaite. »"
            sinon si pieces >= coutE alors
                pieces vaut pieces - coutE
                epee ajoute 1
                message vaut "Épée affûtée : + " + epee
            sinon
                message vaut "Il te manque " + (coutE - pieces) + " or"
            fin si
        sinon si action = 3 alors
            coutA est un nombre
            coutA vaut 55 + armure * 50
            si armure >= 3 alors
                message vaut "Borin : « Cette armure ne peut plus être renforcée. »"
            sinon si pieces >= coutA alors
                pieces vaut pieces - coutA
                armure ajoute 1
                message vaut "Armure renforcée : + " + armure
            sinon
                message vaut "Il te manque " + (coutA - pieces) + " or"
            fin si
        sinon
            ecran vaut "monde"
            message vaut "Borin : « Reviens quand tu veux, héros. »"
        fin si

    // ==================================================
    //                    LA VICTOIRE
    // ==================================================
    sinon
        appelle dessinerFin(graph, niv, pieces)
        appuyer ["e"] dans action
    fin si
fin tant que
Pas encore noté
EduCode devient CoddyRun : Un nouveau nom pour la même mission
Actualité

EduCode devient CoddyRun

Un nouveau nom pour la même mission

Lire l'articleReplier l'article

Le logiciel EduCode change officiellement de nom et devient CoddyRun.

Pourquoi ce changement ?

Ce changement de nom résulte d’une contrainte juridique : un dépôt de marque existant empêchait l’utilisation de l’appellation EduCode. Afin de garantir une identité claire, disponible et protégée, un nouveau nom a été adopté : CoddyRun.

Pourquoi un nom non francophone ?

Le projet reste entièrement francisé dans son interface, sa documentation et sa philosophie.

Le choix d’un nom non francophone répond à plusieurs impératifs :

  • disponibilité juridique,
  • absence d’ambiguïté dans les registres de marque,
  • simplicité de mémorisation,
  • facilité d’usage dans différents contextes numériques.

CoddyRun remplit ces critères tout en conservant une identité accessible et cohérente.

Ce qui change… et ce qui ne change pas

  • Le site officiel est désormais : https://coddyrun.fr
  • La version CoddyRun v3.2 est strictement identique à EduCode v3.2
  • La version CoddyRun v3.3 sera publiée directement sur le nouveau site
  • Le support technique est désormais joignable à l’adresse : support@coddyrun.fr

Le domaine educode-academie.fr sera progressivement retiré (Redirection active durant la transition)

Une nouvelle étape pour le projet

Ce changement de nom marque une évolution importante, tout en conservant la même ambition : proposer un outil pédagogique clair, accessible et moderne pour apprendre la programmation.

Pas encore noté
Construire un Démineur : Un projet qui va bien au-delà du simple jeu
Article

Construire un Démineur

Un projet qui va bien au-delà du simple jeu

Lire l'articleReplier l'article

Développer une version complète et visuelle du Démineur révèle à quel point ce classique est un excellent terrain d’apprentissage.

Derrière une mécanique simple se cache une véritable opportunité de travailler la logique, la gestion d’état et l’expérience utilisateur.

Ce que ce programme intègre

  • Une grille dynamique 9×9 entièrement générée
  • Un système de niveaux progressifs (+2 mines par niveau)
  • Un mode creuser / drapeau avec un bouton interactif
  • Une interface graphique moderne : couleurs cohérentes, icônes, messages contextuels
  • Une mécanique essentielle : aucune mine autour du premier clic

Une propagation automatique des zones à 0 mine, fidèle aux versions professionnelles du jeu

« C’est parti, bonne chance ! »
« Zone vide ouverte ! »
« Mine touchée… »

Ces messages sont générés en temps réel selon les actions du joueur, ce qui rend l’expérience vivante et intuitive.

Un projet riche techniquement

Le programme repose sur quatre tableaux synchronisés (mines, vus, drapeaux, voisins) et une boucle de jeu qui gère :

  • le placement intelligent des mines
  • le calcul des voisins
  • la propagation en cascade
  • la détection de victoire ou de défaite
  • la remise à zéro pour le niveau suivant

Un excellent exercice pour travailler :

  • la logique algorithmique
  • la gestion d’état
  • les interactions utilisateur
  • la structuration d’un programme complet
  • le design d’une interface graphique cohérente

Programme :

@ CONFIGURATION
maToile est une toile
dimension(maToile, 400, 550)

OFFX est un nombre
OFFX vaut 20
OFFY est un nombre
OFFY vaut 130
CELL est un nombre
CELL vaut 40
MINES_DEPART est un nombre
MINES_DEPART vaut 10
NB_CASES est un nombre
NB_CASES vaut 81

niveau est un nombre
niveau vaut 1
nbMines est un nombre
nbMines vaut MINES_DEPART
mode est un texte
mode vaut "creuser"
message est un texte
message vaut "Clique sur une case pour commencer"

@ GRILLES
// mines : y a-t-il une mine ? / vus : case creusée ?
// drapeaux : drapeau posé ? / voisins : nombre de mines autour
mines est un tableau
vus est un tableau
drapeaux est un tableau
voisins est un tableau
pour i de 0 à 8
    ligneM est un tableau
    ligneV est un tableau
    ligneD est un tableau
    ligneN est un tableau
    pour j de 0 à 8
        ligneM ajoute faux
        ligneV ajoute faux
        ligneD ajoute faux
        ligneN ajoute 0
    fin pour
    mines ajoute ligneM
    vus ajoute ligneV
    drapeaux ajoute ligneD
    voisins ajoute ligneN
fin pour

@ DESSIN D'UNE CASE
procédure dessinerCase(t, x, y, vis, dr, estMine, nb, fini)
    si vis = vrai alors
        // Case creusée : fond sombre
        rectangle_arrondi(t, x, y, 36, 36, 8, #172036)
        si estMine = vrai alors
            rectangle_arrondi(t, x, y, 36, 36, 8, #7F1D1D)
            label(t, x + 8, y + 27, "??", #FFFFFF, 18)
        sinon si nb > 0 alors
            coulNb est un texte
            si nb = 1 alors
                coulNb vaut #60A5FA
            sinon si nb = 2 alors
                coulNb vaut #34D399
            sinon si nb = 3 alors
                coulNb vaut #FBBF24
            sinon si nb = 4 alors
                coulNb vaut #F472B6
            sinon si nb = 5 alors
                coulNb vaut #C084FC
            sinon
                coulNb vaut #F87171
            fin si
            label(t, x + 12, y + 27, "" + nb, coulNb, 20)
        fin si
    sinon
        // Case encore cachée : relief gris
        rectangle_arrondi(t, x, y, 36, 36, 8, #475569)
        rectangle_arrondi(t, x + 4, y + 4, 28, 10, 5, #64748B)
        si dr = vrai alors
            label(t, x + 8, y + 28, "??", #FFFFFF, 18)
        sinon si fini = vrai et estMine = vrai alors
            label(t, x + 8, y + 28, "??", #FCA5A5, 18)
        fin si
    fin si
fin procédure

@ EN-TÊTE
procédure dessinerEntete(t, niv, restants, md, reste)
    rectangle_arrondi(t, 10, 10, 380, 110, 16, #1E293B)
    label(t, 22, 45, "DÉMINEUR", #F8FAFC, 24)
    label(t, 22, 72, "Niveau " + niv, #CBD5E1, 16)

    couleurDrap est un texte
    si restants < 0 alors
        couleurDrap vaut #F87171
    sinon
        couleurDrap vaut #FDE68A
    fin si
    label(t, 250, 45, "?? " + restants, couleurDrap, 20)
    label(t, 250, 72, "Reste : " + reste, #93C5FD, 14)

    // Bouton de changement de mode
    coulB est un texte
    txtB est un texte
    si md = "drapeau" alors
        coulB vaut #B45309
        txtB vaut "MODE DRAPEAU ??"
    sinon
        coulB vaut #1D4ED8
        txtB vaut "MODE CREUSER"
    fin si
    rectangle_arrondi(t, 22, 84, 180, 26, 10, coulB)
    label(t, 34, 102, txtB, #FFFFFF, 14)
    label(t, 210, 102, "(clique pour changer)", #94A3B8, 11)
fin procédure

@ AFFICHAGE COMPLET
procédure afficherJeu(t, gM, gV, gD, gN, niv, restants, md, reste, msg, fini)
    effacer(t)
    remplir(t, #0B1020)
    appelle dessinerEntete(t, niv, restants, md, reste)

    // Cadre de la grille
    rectangle_arrondi(t, 12, OFFY - 8, 376, 376, 12, #0F172A)

    pour i de 0 à 8
        pour j de 0 à 8
            xC est un nombre
            yC est un nombre
            xC vaut OFFX + j * CELL
            yC vaut OFFY + i * CELL
            appelle dessinerCase(t, xC, yC, gV[i][j], gD[i][j], gM[i][j], gN[i][j], fini)
        fin pour
    fin pour

    // Bandeau de message en bas de la toile
    rectangle_arrondi(t, 10, 512, 380, 16, 0, #0B1020)
    label(t, 14, 528, msg, #FDE68A, 14)

    affiche t
fin procédure

@ ÉCRAN DE FIN
procédure afficherFin(t, gagne, niv, mn)
    rectangle_arrondi(t, 40, 200, 320, 170, 20, #1E293B)
    si gagne = vrai alors
        label(t, 70, 250, "?? TERRAIN DÉMINÉ !", #FDE68A, 24)
        label(t, 70, 285, "Niveau " + niv + " réussi", #F8FAFC, 16)
        label(t, 70, 310, "Attention, il y aura plus de mines...", #CBD5E1, 13)
        label(t, 70, 350, "Clique pour le niveau suivant", #93C5FD, 14)
    sinon
        label(t, 70, 250, "?? BOUM !", #FCA5A5, 26)
        label(t, 70, 285, "Tu as creusé sur une mine", #F8FAFC, 16)
        label(t, 70, 310, "Niveau atteint : " + niv + " (" + mn + " mines)", #CBD5E1, 13)
        label(t, 70, 350, "Clique pour rejouer", #93C5FD, 14)
    fin si
    affiche t
fin procédure

@ BOUCLE PRINCIPALE

// Les mines ne sont placées qu'au premier clic : on ne perd jamais au 1er coup
premierClic est un booléen
premierClic vaut vrai

perdu est un booléen
perdu vaut faux
gagne est un booléen
gagne vaut faux

enJeu est un booléen
enJeu vaut vrai

tant que enJeu = vrai

    // --- PHASE 1 : COMPTAGE ET TEST DE VICTOIRE ---
    nbDrap est un nombre
    nbDrap vaut 0
    nbVus est un nombre
    nbVus vaut 0
    pour i de 0 à 8
        pour j de 0 à 8
            si drapeaux[i][j] = vrai alors
                nbDrap ajoute 1
            fin si
            si vus[i][j] = vrai alors
                nbVus ajoute 1
            fin si
        fin pour
    fin pour

    restants est un nombre
    restants vaut nbMines - nbDrap
    reste est un nombre
    reste vaut NB_CASES - nbMines - nbVus

    si perdu = faux et premierClic = faux et reste <= 0 alors
        gagne vaut vrai
    fin si

    // --- PHASE 2 : FIN DE PARTIE ---
    si gagne = vrai ou perdu = vrai alors
        appelle afficherJeu(maToile, mines, vus, drapeaux, voisins, niveau, restants, mode, reste, message, vrai)
        appelle afficherFin(maToile, gagne, niveau, nbMines)
        xF est un nombre
        yF est un nombre
        cliquer maToile dans xF, yF

        si gagne = vrai alors
            niveau ajoute 1
            // 2 mines de plus par niveau (maximum 24)
            nbMines vaut MINES_DEPART + 2 * (niveau - 1)
            nbMines vaut limiter(nbMines, MINES_DEPART, 24)
            message vaut "Niveau " + niveau + " : " + nbMines + " mines cachées"
        sinon
            niveau vaut 1
            nbMines vaut MINES_DEPART
            message vaut "Nouvelle partie : " + nbMines + " mines"
        fin si

        // Remise à zéro de la grille
        pour r de 0 à 8
            pour c de 0 à 8
                mines[r][c] vaut faux
                vus[r][c] vaut faux
                drapeaux[r][c] vaut faux
                voisins[r][c] vaut 0
            fin pour
        fin pour
        premierClic vaut vrai
        perdu vaut faux
        gagne vaut faux
        mode vaut "creuser"

    sinon

        // --- PHASE 3 : INTERACTION ---
        appelle afficherJeu(maToile, mines, vus, drapeaux, voisins, niveau, restants, mode, reste, message, faux)

        x_clic est un nombre
        y_clic est un nombre
        cliquer maToile dans x_clic, y_clic

        si y_clic < OFFY alors
            // Clic dans l'en-tête : bouton de mode
            si x_clic >= 22 et x_clic <= 202 et y_clic >= 84 et y_clic <= 110 alors
                si mode = "drapeau" alors
                    mode vaut "creuser"
                    message vaut "Mode creuser : tu ouvres les cases"
                sinon
                    mode vaut "drapeau"
                    message vaut "Mode drapeau : tu marques les mines"
                fin si
            fin si

        sinon si y_clic >= OFFY + 9 * CELL alors
            message vaut "Clique sur la grille"

        sinon
            cJ est un nombre
            cI est un nombre
            cJ vaut arrondi_inferieur((x_clic - OFFX) / CELL)
            cI vaut arrondi_inferieur((y_clic - OFFY) / CELL)
            cJ vaut limiter(cJ, 0, 8)
            cI vaut limiter(cI, 0, 8)

            si mode = "drapeau" alors
                // --- POSE / RETRAIT D'UN DRAPEAU ---
                si vus[cI][cJ] = vrai alors
                    message vaut "Cette case est déjà creusée"
                sinon si drapeaux[cI][cJ] = vrai alors
                    drapeaux[cI][cJ] vaut faux
                    message vaut "Drapeau retiré"
                sinon
                    drapeaux[cI][cJ] vaut vrai
                    message vaut "Drapeau posé"
                fin si

            sinon
                // --- CREUSER ---
                si drapeaux[cI][cJ] = vrai alors
                    message vaut "Retire d'abord le drapeau"
                sinon si vus[cI][cJ] = vrai alors
                    message vaut "Cette case est déjà creusée"
                sinon

                    // Placement des mines au premier clic (jamais autour du clic)
                    si premierClic = vrai alors
                        premierClic vaut faux
                        posees est un nombre
                        posees vaut 0
                        tant que posees < nbMines
                            mr est un nombre
                            mc est un nombre
                            mr vaut hasard(0, 8)
                            mc vaut hasard(0, 8)
                            libre est un booléen
                            libre vaut vrai
                            si mines[mr][mc] = vrai alors
                                libre vaut faux
                            fin si
                            si absolue(mr - cI) <= 1 et absolue(mc - cJ) <= 1 alors
                                libre vaut faux
                            fin si
                            si libre = vrai alors
                                mines[mr][mc] vaut vrai
                                posees ajoute 1
                            fin si
                        fin tant que

                        // Calcul du nombre de mines voisines de chaque case
                        pour r de 0 à 8
                            pour c de 0 à 8
                                n est un nombre
                                n vaut 0
                                pour dr de - 1 à 1
                                    pour dc de - 1 à 1
                                        vr est un nombre
                                        vc est un nombre
                                        vr vaut r + dr
                                        vc vaut c + dc
                                        si vr >= 0 et vr <= 8 et vc >= 0 et vc <= 8 alors
                                            si mines[vr][vc] = vrai alors
                                                n ajoute 1
                                            fin si
                                        fin si
                                    fin pour
                                fin pour
                                voisins[r][c] vaut n
                            fin pour
                        fin pour
                        message vaut "C'est parti, bonne chance !"
                    fin si

                    si mines[cI][cJ] = vrai alors
                        vus[cI][cJ] vaut vrai
                        perdu vaut vrai
                        message vaut "Mine touchée..."
                    sinon
                        vus[cI][cJ] vaut vrai

                        // Propagation : on ouvre en cascade toutes les zones à 0 mine
                        propage est un booléen
                        propage vaut vrai
                        tant que propage = vrai
                            propage vaut faux
                            pour r de 0 à 8
                                pour c de 0 à 8
                                    si vus[r][c] = vrai et voisins[r][c] = 0 alors
                                        pour dr de - 1 à 1
                                            pour dc de - 1 à 1
                                                pr est un nombre
                                                pc est un nombre
                                                pr vaut r + dr
                                                pc vaut c + dc
                                                si pr >= 0 et pr <= 8 et pc >= 0 et pc <= 8 alors
                                                    si vus[pr][pc] = faux et drapeaux[pr][pc] = faux alors
                                                        vus[pr][pc] vaut vrai
                                                        propage vaut vrai
                                                    fin si
                                                fin si
                                            fin pour
                                        fin pour
                                    fin si
                                fin pour
                            fin pour
                        fin tant que

                        si voisins[cI][cJ] = 0 alors
                            message vaut "Zone vide ouverte !"
                        sinon
                            message vaut "" + voisins[cI][cJ] + " mine(s) autour de cette case"
                        fin si
                    fin si
                fin si
            fin si
        fin si
    fin si
fin tant que
Pas encore noté
La mise à jour 3.3 introduit deux fonctions très attendues qui simplifient le dessin de formes essentielles : les flèches et les triangles.
Nouveauté

Nouvelles fonctions à venir en version 3.3

La mise à jour 3.3 introduit deux fonctions très attendues qui simplifient le dessin de formes essentielles : les flèches et les triangles.

Lire l'articleReplier l'article

flèche()
Cette fonction trace une flèche complète : une ligne suivie d’une pointe orientée selon le dernier segment.

Elle accepte :

  • deux points pour une flèche simple,
  • plusieurs points pour créer des chemins, coudes ou zigzags,
  • une couleur, une épaisseur et une taille de pointe optionnelles.

La pointe s’adapte automatiquement à la direction finale, ce qui rend les schémas, flux et annotations beaucoup plus lisibles. Les coordonnées peuvent être données individuellement ou dans un tableau.

triangle()
Pensée comme un raccourci de polygone, cette fonction dessine un triangle plein à partir de trois sommets.

Elle est plus claire, plus rapide à écrire et applique automatiquement le contour défini. Une couleur optionnelle permet de remplir la forme.

En résumé

La version 3.3 améliore nettement la lisibilité du code et la fluidité du dessin :
flèche() facilite la création de flèches précises et personnalisables,
triangle() simplifie la réalisation de triangles sans passer par un polygone complet.

Pas encore noté
Mise en avant du surlignage de la ligne d’erreur dans la version 3.3
Nouveauté

Mise en avant du surlignage de la ligne d’erreur

Une erreur enfin visible au premier coup d’œil

Lire l'articleReplier l'article

La version 3.3 introduit une amélioration qui va changer la manière de comprendre et corriger les erreurs : le surlignage automatique de la ligne fautive dans l’éditeur. Cette nouveauté vise à rendre le débogage plus rapide, plus intuitif et beaucoup moins frustrant, surtout pour les débutants.

Une erreur enfin visible au premier coup d’œil

Jusqu’ici, l’erreur était signalée dans la console, mais l’utilisateur devait ensuite retrouver le numéro de la ligne concernée dans le code. Cette étape, souvent fastidieuse, disparaît enfin.

Avec la 3.3, cette étape disparaît : la ligne problématique est directement mise en évidence dans l’éditeur, ce qui permet de comprendre immédiatement où se situe le problème.

Un gain de temps considérable

Le surlignage réduit les allers-retours entre console et éditeur. L’utilisateur peut corriger l’erreur dès qu’elle apparaît, sans perdre le fil de son raisonnement. Cette fluidité est particulièrement utile dans les projets longs ou les exercices comportant de nombreuses conditions et boucles.

Un apprentissage facilité

Pour les débutants, voir la ligne exacte où l’erreur se produit aide à mieux comprendre la logique du langage. Le lien entre message d’erreur et code devient concret : on visualise, on corrige, on progresse.

Une expérience plus professionnelle

Ce comportement rapproche l’environnement de travail des standards des IDE modernes. Le surlignage rend l’interface plus réactive, plus pédagogique et plus agréable à utiliser au quotidien.

Pas encore noté
un puzzle logique entièrement codé en pseudo-code français
Article

Lights Out

un puzzle logique entièrement codé en pseudo-code français

Lire l'articleReplier l'article

Ce programme propose une version complète du célèbre jeu Lights Out, entièrement écrite en pseudo-code français. Le principe est simple : chaque clic bascule une case et ses quatre voisines. Le but est d’éteindre toutes les lumières du plateau… mais derrière cette simplicité se cache une mécanique algorithmique très riche.

Un plateau 5×5, entre logique et stratégie

Le jeu repose sur une grille de 25 cases, chacune pouvant être :

  • éteinte (0),
  • allumée (1).

Chaque niveau commence par un brouillage aléatoire, obtenu en simulant des appuis au hasard. Cette méthode garantit que la grille est toujours résoluble, ce qui est essentiel pour un puzzle logique.

Le fonctionnement du clic : un basculement en croix

Lorsqu’on clique sur une case :

  • la case centrale bascule,
  • la case de droite bascule,
  • la case de gauche bascule,
  • la case du haut bascule,
  • la case du bas bascule.

Ce comportement en croix est la signature du jeu Lights Out.
Une propriété mathématique intéressante est exploitée : appuyer deux fois au même endroit annule l’effet, ce qui permet de gérer la solution comme un simple ensemble de bascules.

Un indice intégré : les appuis restants pour gagner

Le programme calcule et affiche un indice optionnel : les cases sur lesquelles il reste à appuyer pour résoudre la grille.
Ces cases apparaissent sous forme de points blancs, uniquement lorsque le joueur clique sur le bouton "Indice".

C’est une aide précieuse pour comprendre la logique du puzzle, sans donner la solution complète d’un coup.

Un brouillage progressif selon le niveau

Chaque victoire :

  • augmente le niveau,
  • augmente le nombre d’appuis aléatoires utilisés pour brouiller la grille
  • rend donc la grille plus complexe.

Le jeu devient progressivement plus difficile, tout en restant toujours résoluble.

Une interface claire et agréable

Le programme affiche :

  • un en-tête avec le niveau, le nombre de coups et le nombre de lumières allumées,
  • un plateau coloré avec halo lumineux pour les cases allumées,
  • un bouton "Nouvelle grille",
  • un bouton "Indice",
  • un message d’aide dynamique.

L’ensemble donne une expérience fluide, intuitive et très visuelle.

Un écran de victoire motivant

Quand toutes les lumières sont éteintes :

  • un écran de victoire apparaît,
  • le nombre de coups est affiché,
  • le joueur est invité à passer au niveau suivant.

C’est une boucle de progression simple mais efficace.

Un excellent exercice d’algorithmique

Ce programme est idéal pour travailler :

  • la manipulation de tableaux 2D,
  • la gestion d’un puzzle logique,
  • la mise en place d’un système d’indice,
  • la création d’une interface graphique interactive,
  • la logique des transformations locales (basculement en croix),
  • la génération de grilles résolubles.

C’est un projet parfait pour les élèves en SNT, NSI, ou toute personne souhaitant comprendre comment on code un puzzle logique de A à Z.

Programme :

@ Configuration
maToile est une toile
dimension(maToile, 400, 570)

// Plateau 5x5 : 0 = lumière éteinte, 1 = lumière allumée
plateau est un tableau
// Cases sur lesquelles il reste à appuyer pour gagner (l'indice)
plateauIndice est un tableau

pour i de 0 à 4
    ligne_p est un tableau
    ligne_a est un tableau
    pour j de 0 à 4
        ligne_p ajoute 0
        ligne_a ajoute 0
    fin pour
    plateau ajoute ligne_p
    plateauIndice ajoute ligne_a
fin pour

niveau est un nombre
niveau vaut 1
coups est un nombre
coups vaut 0
avecIndice est un booléen
avecIndice vaut faux
message est un texte
message vaut "Un clic bascule la case et ses 4 voisines"

// Décalages des 5 cases touchées par un appui : centre, droite,
// gauche, bas, haut. Décalés de +1 pour éviter les nombres négatifs.
decCol est un tableau
decLig est un tableau
decCol ajoute 1
decLig ajoute 1
decCol ajoute 2
decLig ajoute 1
decCol ajoute 0
decLig ajoute 1
decCol ajoute 1
decLig ajoute 2
decCol ajoute 1
decLig ajoute 0


@ Appui sur une case
// Bascule la case et ses voisines, puis met à jour l'indice.
// Astuce mathématique : appuyer deux fois au même endroit ne change
// rien, donc l'ensemble des appuis restants se met à jour par une
// simple bascule lui aussi.
procédure basculerCases(lig, cln)
    rr est un nombre
    cc est un nombre
    pour d de 0 à 4
        rr vaut lig + decLig[d] - 1
        cc vaut cln + decCol[d] - 1
        si rr >= 0 et rr <= 4 et cc >= 0 et cc <= 4 alors
            si plateau[rr][cc] = 1 alors
                plateau[rr][cc] vaut 0
            sinon
                plateau[rr][cc] vaut 1
            fin si
        fin si
    fin pour

    si plateauIndice[lig][cln] = 1 alors
        plateauIndice[lig][cln] vaut 0
    sinon
        plateauIndice[lig][cln] vaut 1
    fin si
fin procédure


@ Brouillage du plateau
// On part d'un plateau entièrement éteint et on appuie au hasard :
// la grille obtenue est donc forcément résoluble.
procédure brouillerPlateau(nbClics)
    ligH est un nombre
    clnH est un nombre
    pour i de 0 à 4
        pour j de 0 à 4
            plateau[i][j] vaut 0
            plateauIndice[i][j] vaut 0
        fin pour
    fin pour
    pour n de 1 à nbClics
        ligH vaut hasard(0, 4)
        clnH vaut hasard(0, 4)
        appelle basculerCases(ligH, clnH)
    fin pour
fin procédure


@ Comptage des lumières allumées
fonction nbAllumees()
    n est un nombre
    n vaut 0
    pour i de 0 à 4
        pour j de 0 à 4
            si plateau[i][j] = 1 alors
                n ajoute 1
            fin si
        fin pour
    fin pour
    retourne n
fin fonction

@ Comptage des appuis restants dans l'indice
fonction nbIndices()
    n est un nombre
    n vaut 0
    pour i de 0 à 4
        pour j de 0 à 4
            si plateauIndice[i][j] = 1 alors
                n ajoute 1
            fin si
        fin pour
    fin pour
    retourne n
fin fonction


@ Affichage complet
procédure afficherJeu(t, niv, cp, msg, aide)
    effacer(t)
    remplir(t, #0B1020)

    nbOn est un nombre
    nbAide est un nombre
    nbOn vaut nbAllumees()
    nbAide vaut nbIndices()

    // En-tête
    rectangle_arrondi(t, 10, 10, 380, 100, 16, #1E293B)
    label(t, 22, 44, "LIGHTS OUT", #F8FAFC, 24)
    label(t, 22, 72, "Niveau " + niv, #CBD5E1, 15)
    label(t, 230, 72, "Coups : " + cp, #93C5FD, 15)
    label(t, 22, 98, "Allumées : " + nbOn + " / 25", #CBD5E1, 13)
    label(t, 230, 98, "Indice : " + nbAide + " appuis", #FDE68A, 13)

    // Plateau : case de 64 px, écart de 72 px, coin en 20 et 130
    xP est un nombre
    yP est un nombre
    pour i de 0 à 4
        pour j de 0 à 4
            xP vaut 20 + j * 72
            yP vaut 130 + i * 72

            // Ombre portée
            rectangle_arrondi(t, xP + 2, yP + 4, 64, 64, 16, #070B16)

            si plateau[i][j] = 1 alors
                // Lumière allumée : halo puis tuile jaune
                rectangle_arrondi(t, xP - 4, yP - 4, 72, 72, 20, #3A2E12)
                rectangle_arrondi(t, xP, yP, 64, 64, 16, #FFC53D)
                rectangle_arrondi(t, xP + 8, yP + 7, 48, 14, 7, #FFE9A8)
            sinon
                // Lumière éteinte
                rectangle_arrondi(t, xP, yP, 64, 64, 16, #172036)
                rectangle_arrondi(t, xP + 8, yP + 7, 48, 14, 7, #1F2B45)
            fin si

            // Point blanc : case faisant partie de la solution
            si aide = vrai alors
                si plateauIndice[i][j] = 1 alors
                    rectangle_arrondi(t, xP + 26, yP + 26, 12, 12, 6, #FFFFFF)
                fin si
            fin si
        fin pour
    fin pour

    // Boutons
    rectangle_arrondi(t, 10, 496, 180, 44, 14, #334155)
    label(t, 50, 524, "Nouvelle grille", #F8FAFC, 15)
    rectangle_arrondi(t, 210, 496, 180, 44, 14, #334155)
    label(t, 275, 524, "Indice", #FDE68A, 15)

    label(t, 14, 560, msg, #FDE68A, 13)
    affiche t
fin procédure


@ Écran de victoire
procédure afficherFin(t, niv, cp)
    rectangle_arrondi(t, 40, 190, 320, 180, 20, #1E293B)
    label(t, 70, 240, "TOUT ÉTEINT !", #FDE68A, 26)
    label(t, 70, 278, "Niveau " + niv + " réussi en " + cp + " coups", #F8FAFC, 15)
    label(t, 70, 306, "Le niveau suivant sera plus brouillé", #CBD5E1, 13)
    label(t, 70, 348, "Clique pour continuer", #93C5FD, 14)
    affiche t
fin procédure


@ Boucle principale
nbBrouillage est un nombre
nbBrouillage vaut 3
appelle brouillerPlateau(nbBrouillage)
tant que nbAllumees() = 0
    appelle brouillerPlateau(nbBrouillage)
fin tant que

enJeu est un booléen
enJeu vaut vrai

tant que enJeu = vrai
    appelle afficherJeu(maToile, niveau, coups, message, avecIndice)

    x_clic est un nombre
    y_clic est un nombre
    cliquer maToile dans x_clic, y_clic

    // L'indice ne reste affiché que le temps d'un clic
    avecIndice vaut faux

    si y_clic >= 496 et y_clic <= 540 alors
        si x_clic <= 195 alors
            // Bouton "Nouvelle grille" : même niveau, autre brouillage
            appelle brouillerPlateau(nbBrouillage)
            tant que nbAllumees() = 0
                appelle brouillerPlateau(nbBrouillage)
            fin tant que
            coups vaut 0
            message vaut "Nouvelle grille au niveau " + niveau
        sinon
            // Bouton "Indice"
            avecIndice vaut vrai
            message vaut "Les points blancs sont les appuis à faire"
        fin si

    sinon si y_clic >= 130 et y_clic < 490 et x_clic >= 20 et x_clic < 380 alors
        clnClic est un nombre
        ligClic est un nombre
        clnClic vaut arrondi_inferieur((x_clic - 20) / 72)
        ligClic vaut arrondi_inferieur((y_clic - 130) / 72)
        clnClic vaut limiter(clnClic, 0, 4)
        ligClic vaut limiter(ligClic, 0, 4)

        appelle basculerCases(ligClic, clnClic)
        coups ajoute 1
        message vaut "5 lumières basculées"

        si nbAllumees() = 0 alors
            appelle afficherJeu(maToile, niveau, coups, "Bravo !", faux)
            appelle afficherFin(maToile, niveau, coups)
            xFin est un nombre
            yFin est un nombre
            cliquer maToile dans xFin, yFin

            niveau ajoute 1
            coups vaut 0
            nbBrouillage vaut limiter(2 + niveau, 3, 14)
            appelle brouillerPlateau(nbBrouillage)
            tant que nbAllumees() = 0
                appelle brouillerPlateau(nbBrouillage)
            fin tant que
            message vaut "Niveau " + niveau + " : " + nbBrouillage + " appuis cachés"
        fin si
    fin si
fin tant que
Pas encore noté