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

Le Hasard Maîtrisé : pourquoi un bon jeu ne laisse jamais tout au hasard
Article

Le Hasard Maîtrisé

Pourquoi un bon jeu ne laisse jamais tout au hasard

Lire l'articleReplier l'article

Dans la plupart des langages, la fonction hasard() (ou random()) semble être la solution magique pour générer de l’imprévu. Pourtant, dans le game design, le hasard brut est rarement une bonne idée. Un jeu intéressant n’est pas un chaos : c’est un système où l’aléatoire est orienté, contraint, validé.

Le programme Le Hasard Maîtrisé illustre cette idée à travers trois démonstrations fondamentales :

  • Pondérer : toutes les valeurs ne doivent pas avoir la même chance.
  • Exclure : certaines zones doivent être interdites au tirage.
  • Valider : on rejette les résultats qui ne conviennent pas.

Ces trois techniques sont au cœur de la conception de niveaux, de loot, de cartes, de puzzles, et de mécaniques équilibrées.

Pondérer : donner une forme au hasard

La première démonstration compare trois types de tirages :

Tirage uniforme
Chaque face d’un dé (1 à 6) a la même probabilité.
Résultat : une distribution plate, sans surprise.

Tirage pondéré
Le programme découpe 100 en tranches inégales :

  • 40 % ? 1
  • 25 % ? 2
  • 15 % ? 3
  • 10 % ? 4
  • 6 % ? 5
  • 4 % ? 6

Résultat : une distribution orientée, où les petites valeurs dominent.

Somme de deux dés
Combiner deux tirages uniformes produit une courbe en cloche :
le 7 apparaît six fois plus souvent que le 2 ou le 12.

Pourquoi c’est essentiel en game design ?

Parce que le hasard brut produit souvent des situations injustes ou déséquilibrées.

Exemples :

  • Un jeu de fusion où les grosses pièces sortent trop souvent devient sans intérêt.
  • Un RPG où les objets rares tombent trop facilement perd toute progression.
  • Un jeu de stratégie où les ressources apparaissent n’importe comment devient ingérable.

Décider de la forme de la distribution, c’est du game design.

Exclure : interdire certaines zones au tirage

La deuxième démonstration concerne le démineur.

Version brute
Les mines sont placées n’importe où.
Résultat : sur 400 parties, la case du premier clic contient une mine plusieurs dizaines de fois.

Version contrainte
Le programme interdit toute mine :

  • sur la case du premier clic,
  • dans les 8 cases autour.

Résultat : 0 explosion au premier clic, par construction.

La contrainte tient en une seule ligne :

  • si absolue(mi - 4) <= 1 et absolue(mj - 4) <= 1 alors refuser

Pourquoi c’est essentiel ?

Parce qu’un jeu doit être juste.

Le joueur ne doit jamais perdre avant d’avoir joué.
Le hasard doit servir le plaisir, pas le punir.

C’est pour cela que :

  • les jeux de cartes évitent les mains impossibles,
  • les roguelikes garantissent un minimum de ressources,
  • les jeux de puzzle empêchent les configurations insolubles.

Le hasard doit être guidé.

Valider : recommencer tant que le résultat n’est pas bon

La troisième démonstration génère une carte 12×12 avec des murs.

Version brute
Les murs sont placés au hasard.
Résultat : la carte contient souvent des zones inatteignables, isolées du point de départ.

Le programme calcule l’accessibilité en propageant une vague depuis la case (1,1).
Il compte ensuite les cases libres mais inaccessibles.

Version validée
Le programme génère une carte…
puis la rejette si elle est trop morcelée.

Il recommence jusqu’à obtenir :

  • au moins 60 cases accessibles,
  • une structure cohérente,
  • un niveau jouable.

Résultat : une carte toujours praticable, obtenue en quelques essais.

Pourquoi c’est essentiel ?

Parce qu’un niveau généré procéduralement doit respecter des critères :

  • être jouable,
  • être intéressant,
  • être équilibré,
  • être cohérent.

La génération procédurale moderne (Minecraft, Hades, Dead Cells, Slay the Spire…) repose sur ce principe :

  • Générer ? Tester ? Rejeter ? Recommencer

Le hasard brut ne suffit jamais.
La validation est indispensable.

Ce que ce programme enseigne Ce programme est une véritable leçon de game design :

  • Le hasard doit être modelé (pondération).
  • Le hasard doit être limité (exclusion).
  • Le hasard doit être contrôlé (validation).

Un bon jeu n’est pas un tirage aléatoire.
C’est un système où l’aléatoire est maîtrisé, orienté, équilibré.

Conclusion Le Hasard Maîtrisé montre que la qualité d’un jeu dépend rarement de la fonction hasard(), mais toujours des contraintes qu’on impose au tirage.

C’est cette maîtrise qui permet :

  • d’éviter les injustices,
  • de garantir la jouabilité,
  • de créer des niveaux cohérents,
  • d’équilibrer les récompenses,
  • de rendre l’expérience agréable.

Le hasard n’est pas l’ennemi du game design.
Le hasard non maîtrisé, si.

Programme :

// ===========================================================
//   LE HASARD MAÎTRISÉ
//   hasard() ne suffit jamais. Ce qui fait la qualité d'un jeu,
//   ce sont les contraintes qu'on impose au tirage.
//   Trois démonstrations :
//     [A] pondérer   [Z] exclure   [E] valider
// ===========================================================

@ CONFIGURATION
vue est une toile
dimension(vue, 700, 520)

ecran est un texte
ecran vaut "ponderer"
touche est un nombre
NBTIR est un nombre
NBTIR vaut 600

@ DONNÉES DES TROIS DÉMONSTRATIONS
// --- 1. comptages des tirages ---
cptUni est un tableau
cptPon est un tableau
cptDeux est un tableau
pour k de 0 à 12
    cptUni ajoute 0
    cptPon ajoute 0
    cptDeux ajoute 0
fin pour

// --- 2. mines du démineur ---
minesA est un tableau
minesB est un tableau
pour i de 0 à 8
    ligA est un tableau
    ligB est un tableau
    pour j de 0 à 8
        ligA ajoute 0
        ligB ajoute 0
    fin pour
    minesA ajoute ligA
    minesB ajoute ligB
fin pour
perduesA est un nombre
perduesB est un nombre
perduesA vaut 0
perduesB vaut 0

// --- 3. cartes générées ---
carteA est un tableau
carteB est un tableau
accesA est un tableau
pour i de 0 à 11
    lgA est un tableau
    lgB est un tableau
    lgAc est un tableau
    pour j de 0 à 11
        lgA ajoute 0
        lgB ajoute 0
        lgAc ajoute faux
    fin pour
    carteA ajoute lgA
    carteB ajoute lgB
    accesA ajoute lgAc
fin pour
isolesA est un nombre
essaisB est un nombre
libresB est un nombre
isolesA vaut 0
essaisB vaut 0
libresB vaut 0

refaire est un booléen
refaire vaut vrai

@ UNE BARRE D'HISTOGRAMME
procédure barre(t, x, ybas, larg, haut, coul, txt, val)
    rectangle_arrondi(t, x, ybas - haut, larg, haut, 3, coul)
    label(t, x + 2, ybas + 14, txt, #64748B, 10)
    label(t, x - 1, ybas - haut - 6, "" + val, #334155, 9)
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    // =========================================================
    //   CALCULS : on refait tous les tirages à la demande
    // =========================================================
    si refaire = vrai alors
        refaire vaut faux

        // ---------- 1. UNIFORME, PONDÉRÉ, SOMME DE DEUX DÉS ----------
        pour k de 0 à 12
            cptUni[k] vaut 0
            cptPon[k] vaut 0
            cptDeux[k] vaut 0
        fin pour

        pour n de 1 à NBTIR
            // Tirage brut : chaque face a la même chance
            u est un nombre
            u vaut hasard(1, 6)
            cptUni[u] vaut cptUni[u] + 1

            // Tirage pondéré : on découpe 100 en tranches inégales
            p est un nombre
            t100 est un nombre
            t100 vaut hasard(1, 100)
            si t100 <= 40 alors
                p vaut 1
            sinon si t100 <= 65 alors
                p vaut 2
            sinon si t100 <= 80 alors
                p vaut 3
            sinon si t100 <= 90 alors
                p vaut 4
            sinon si t100 <= 96 alors
                p vaut 5
            sinon
                p vaut 6
            fin si
            cptPon[p] vaut cptPon[p] + 1

            // Somme de deux dés : combiner deux hasards en change la forme
            s est un nombre
            s vaut hasard(1, 6) + hasard(1, 6)
            cptDeux[s] vaut cptDeux[s] + 1
        fin pour

        // ---------- 2. LES MINES DU DÉMINEUR ----------
        // À gauche : 10 mines posées n'importe où.
        // À droite : jamais sur la case du premier clic ni autour.
        pour i de 0 à 8
            pour j de 0 à 8
                minesA[i][j] vaut 0
                minesB[i][j] vaut 0
            fin pour
        fin pour

        posees est un nombre
        posees vaut 0
        tant que posees < 10
            mi est un nombre
            mj est un nombre
            mi vaut hasard(0, 8)
            mj vaut hasard(0, 8)
            si minesA[mi][mj] = 0 alors
                minesA[mi][mj] vaut 1
                posees ajoute 1
            fin si
        fin tant que

        posees vaut 0
        tant que posees < 10
            mi vaut hasard(0, 8)
            mj vaut hasard(0, 8)
            libre est un booléen
            libre vaut vrai
            si minesB[mi][mj] = 1 alors
                libre vaut faux
            fin si
            // La contrainte tient en une ligne : rien près du premier clic
            si absolue(mi - 4) <= 1 et absolue(mj - 4) <= 1 alors
                libre vaut faux
            fin si
            si libre = vrai alors
                minesB[mi][mj] vaut 1
                posees ajoute 1
            fin si
        fin tant que

        // Statistique : sur 400 parties, combien explosent au premier clic ?
        perduesA vaut 0
        perduesB vaut 0
        pour n de 1 à 400
            // version brute : on repose 10 mines et on regarde la case (4,4)
            touchee est un booléen
            touchee vaut faux
            restant est un nombre
            restant vaut 10
            caseM est un tableau
            // on tire 10 cases distinctes parmi 81 en testant la case du clic
            pour k de 1 à 10
                ci est un nombre
                cj est un nombre
                ci vaut hasard(0, 8)
                cj vaut hasard(0, 8)
                si ci = 4 et cj = 4 alors
                    touchee vaut vrai
                fin si
            fin pour
            si touchee = vrai alors
                perduesA ajoute 1
            fin si
        fin pour

        // ---------- 3. UNE CARTE PRATICABLE ----------
        // À gauche : murs au hasard, sans vérification.
        // À droite : on rejette la carte tant qu'elle est trop morcelée.
        pour i de 0 à 11
            pour j de 0 à 11
                si i = 0 ou i = 11 ou j = 0 ou j = 11 alors
                    carteA[i][j] vaut 1
                sinon si hasard(1, 100) <= 34 alors
                    carteA[i][j] vaut 1
                sinon
                    carteA[i][j] vaut 0
                fin si
                accesA[i][j] vaut faux
            fin pour
        fin pour
        carteA[1][1] vaut 0

        // Propagation depuis le coin : quelles cases peut-on atteindre ?
        accesA[1][1] vaut vrai
        vague est un booléen
        vague vaut vrai
        tant que vague = vrai
            vague vaut faux
            pour i de 1 à 10
                pour j de 1 à 10
                    si accesA[i][j] = vrai alors
                        pour d de 0 à 3
                            di est un nombre
                            dj est un nombre
                            si d = 0 alors
                                di vaut 0 - 1
                                dj vaut 0
                            sinon si d = 1 alors
                                di vaut 1
                                dj vaut 0
                            sinon si d = 2 alors
                                di vaut 0
                                dj vaut 0 - 1
                            sinon
                                di vaut 0
                                dj vaut 1
                            fin si
                            si carteA[i + di][j + dj] = 0 et accesA[i + di][j + dj] = faux alors
                                accesA[i + di][j + dj] vaut vrai
                                vague vaut vrai
                            fin si
                        fin pour
                    fin si
                fin pour
            fin pour
        fin tant que

        isolesA vaut 0
        pour i de 1 à 10
            pour j de 1 à 10
                si carteA[i][j] = 0 et accesA[i][j] = faux alors
                    isolesA ajoute 1
                fin si
            fin pour
        fin pour

        // Version validée : on recommence tant que le résultat ne convient pas
        essaisB vaut 0
        bonne est un booléen
        bonne vaut faux
        tant que bonne = faux et essaisB < 60
            essaisB ajoute 1
            pour i de 0 à 11
                pour j de 0 à 11
                    si i = 0 ou i = 11 ou j = 0 ou j = 11 alors
                        carteB[i][j] vaut 1
                    sinon si hasard(1, 100) <= 34 alors
                        carteB[i][j] vaut 1
                    sinon
                        carteB[i][j] vaut 0
                    fin si
                fin pour
            fin pour
            carteB[1][1] vaut 0

            // On réutilise accesA comme brouillon de calcul
            pour i de 0 à 11
                pour j de 0 à 11
                    accesA[i][j] vaut faux
                fin pour
            fin pour
            accesA[1][1] vaut vrai
            vague vaut vrai
            tant que vague = vrai
                vague vaut faux
                pour i de 1 à 10
                    pour j de 1 à 10
                        si accesA[i][j] = vrai alors
                            pour d de 0 à 3
                                si d = 0 alors
                                    di vaut 0 - 1
                                    dj vaut 0
                                sinon si d = 1 alors
                                    di vaut 1
                                    dj vaut 0
                                sinon si d = 2 alors
                                    di vaut 0
                                    dj vaut 0 - 1
                                sinon
                                    di vaut 0
                                    dj vaut 1
                                fin si
                                si carteB[i + di][j + dj] = 0 et accesA[i + di][j + dj] = faux alors
                                    accesA[i + di][j + dj] vaut vrai
                                    vague vaut vrai
                                fin si
                            fin pour
                        fin si
                    fin pour
                fin pour
            fin tant que

            libresB vaut 0
            pour i de 1 à 10
                pour j de 1 à 10
                    si accesA[i][j] = vrai alors
                        libresB ajoute 1
                    fin si
                fin pour
            fin pour
            si libresB >= 60 alors
                bonne vaut vrai
            fin si
        fin tant que

        // On recalcule l'accessibilité de la carte de gauche pour l'affichage
        pour i de 0 à 11
            pour j de 0 à 11
                accesA[i][j] vaut faux
            fin pour
        fin pour
        accesA[1][1] vaut vrai
        vague vaut vrai
        tant que vague = vrai
            vague vaut faux
            pour i de 1 à 10
                pour j de 1 à 10
                    si accesA[i][j] = vrai alors
                        pour d de 0 à 3
                            si d = 0 alors
                                di vaut 0 - 1
                                dj vaut 0
                            sinon si d = 1 alors
                                di vaut 1
                                dj vaut 0
                            sinon si d = 2 alors
                                di vaut 0
                                dj vaut 0 - 1
                            sinon
                                di vaut 0
                                dj vaut 1
                            fin si
                            si carteA[i + di][j + dj] = 0 et accesA[i + di][j + dj] = faux alors
                                accesA[i + di][j + dj] vaut vrai
                                vague vaut vrai
                            fin si
                        fin pour
                    fin si
                fin pour
            fin pour
        fin tant que
    fin si

    // =========================================================
    //                        AFFICHAGE
    // =========================================================
    effacer(vue)
    contour(vue, #0)
    remplir(vue, #EEF2F7)

    rectangle_arrondi(vue, 12, 10, 676, 74, 14, #FFFFFF)
    label(vue, 26, 40, "LE HASARD MAÎTRISÉ", #B45309, 20)
    label(vue, 26, 62, "hasard() ne suffit jamais : ce sont les contraintes qui font la qualité du tirage", #64748B, 12)
    label(vue, 26, 80, "[A] pondérer     [Z] exclure     [E] valider     [R] refaire les tirages", #2563EB, 11)

    si ecran = "ponderer" alors

        // -------------------------------------------------------
        //   1. PONDÉRER : toutes les faces ne se valent pas
        // -------------------------------------------------------
        rectangle_arrondi(vue, 12, 92, 676, 416, 14, #FFFFFF)
        label(vue, 26, 118, "1. PONDÉRER — " + NBTIR + " tirages", #0F172A, 15)

        label(vue, 40, 150, "hasard(1, 6)", #DC2626, 12)
        label(vue, 40, 168, "chaque face a la même chance", #94A3B8, 10)
        pour v de 1 à 6
            hb est un nombre
            hb vaut arrondi(cptUni[v] * 140 / 200)
            appelle barre(vue, 34 + (v - 1) * 28, 340, 20, hb, #F87171, "" + v, cptUni[v])
        fin pour

        label(vue, 250, 150, "tranches de 1 à 100", #2563EB, 12)
        label(vue, 250, 168, "40 % de 1, puis 25, 15, 10, 6, 4", #94A3B8, 10)
        pour v de 1 à 6
            hb vaut arrondi(cptPon[v] * 140 / 250)
            appelle barre(vue, 244 + (v - 1) * 28, 340, 20, hb, #60A5FA, "" + v, cptPon[v])
        fin pour

        label(vue, 448, 150, "somme de deux dés", #16A34A, 12)
        label(vue, 448, 168, "combiner deux hasards change la forme", #94A3B8, 10)
        pour v de 2 à 12
            hb vaut arrondi(cptDeux[v] * 140 / 120)
            appelle barre(vue, 442 + (v - 2) * 21, 340, 15, hb, #4ADE80, "" + v, cptDeux[v])
        fin pour

        label(vue, 40, 386, "Le tirage uniforme est plat. Le tirage pondéré privilégie les petites valeurs.", #334155, 12)
        label(vue, 40, 408, "La somme de deux dés dessine une cloche : le 7 sort six fois plus souvent que le 2.", #334155, 12)
        label(vue, 40, 438, "Dans un jeu de fusion, sans pondération le joueur reçoit trop de grosses pièces", #64748B, 11)
        label(vue, 40, 456, "et n'a jamais besoin de fusionner. La règle du jeu tient dans le tirage.", #64748B, 11)
        label(vue, 40, 486, "Écrire hasard(1, 6) est facile. Décider de la forme de la distribution, c'est du game design.", #B45309, 11)

    sinon si ecran = "exclure" alors

        // -------------------------------------------------------
        //   2. EXCLURE : interdire une zone au tirage
        // -------------------------------------------------------
        rectangle_arrondi(vue, 12, 92, 676, 416, 14, #FFFFFF)
        label(vue, 26, 118, "2. EXCLURE — les mines du démineur", #0F172A, 15)

        label(vue, 40, 146, "Tirage brut", #DC2626, 13)
        label(vue, 40, 164, "10 mines n'importe où", #94A3B8, 10)
        label(vue, 400, 146, "Tirage contraint", #16A34A, 13)
        label(vue, 400, 164, "rien sur la case cliquée ni autour", #94A3B8, 10)

        pour i de 0 à 8
            pour j de 0 à 8
                xa est un nombre
                xb est un nombre
                ya est un nombre
                xa vaut 40 + j * 26
                xb vaut 400 + j * 26
                ya vaut 180 + i * 26

                rectangle_arrondi(vue, xa, ya, 23, 23, 4, #E2E8F0)
                rectangle_arrondi(vue, xb, ya, 23, 23, 4, #E2E8F0)
                si minesA[i][j] = 1 alors
                    cercle(vue, xa + 11, ya + 11, 7, #DC2626)
                fin si
                si minesB[i][j] = 1 alors
                    cercle(vue, xb + 11, ya + 11, 7, #DC2626)
                fin si
                // La case du premier clic et son voisinage
                si absolue(i - 4) <= 1 et absolue(j - 4) <= 1 alors
                    contour(vue, #2563EB, 2)
                    rectangle_arrondi(vue, xb, ya, 23, 23, 4, rgba(37, 99, 235, 0.12))
                    contour(vue, #0)
                fin si
                si i = 4 et j = 4 alors
                    contour(vue, #2563EB, 2)
                    rectangle_arrondi(vue, xa, ya, 23, 23, 4, rgba(37, 99, 235, 0.12))
                    contour(vue, #0)
                fin si
            fin pour
        fin pour

        label(vue, 40, 436, "Sur 400 parties, une mine se trouve sous le premier clic : " + perduesA + " fois", #DC2626, 12)
        label(vue, 40, 458, "Avec la zone d'exclusion : 0 fois, par construction", #16A34A, 12)
        label(vue, 40, 486, "Une seule ligne de contrainte : si absolue(mi - 4) <= 1 et absolue(mj - 4) <= 1 alors refuser", #B45309, 11)

    sinon

        // -------------------------------------------------------
        //   3. VALIDER : rejeter ce qui ne convient pas
        // -------------------------------------------------------
        rectangle_arrondi(vue, 12, 92, 676, 416, 14, #FFFFFF)
        label(vue, 26, 118, "3. VALIDER — une carte praticable", #0F172A, 15)

        label(vue, 40, 146, "Carte brute", #DC2626, 13)
        label(vue, 40, 164, "en rouge : les cases inatteignables", #94A3B8, 10)
        label(vue, 400, 146, "Carte validée", #16A34A, 13)
        label(vue, 400, 164, "rejetée tant que trop morcelée", #94A3B8, 10)

        pour i de 0 à 11
            pour j de 0 à 11
                xa vaut 40 + j * 21
                xb vaut 400 + j * 21
                ya vaut 180 + i * 21

                coulA est un texte
                si carteA[i][j] = 1 alors
                    coulA vaut #64748B
                sinon si accesA[i][j] = vrai alors
                    coulA vaut #86EFAC
                sinon
                    coulA vaut #FCA5A5
                fin si
                rectangle_arrondi(vue, xa, ya, 18, 18, 3, coulA)

                coulB est un texte
                si carteB[i][j] = 1 alors
                    coulB vaut #64748B
                sinon
                    coulB vaut #86EFAC
                fin si
                rectangle_arrondi(vue, xb, ya, 18, 18, 3, coulB)
            fin pour
        fin pour

        cercle(vue, 49, 189, 5, #2563EB)
        cercle(vue, 409, 189, 5, #2563EB)

        label(vue, 40, 452, "Carte brute : " + isolesA + " cases libres mais inatteignables depuis le point bleu", #DC2626, 12)
        label(vue, 40, 474, "Carte validée : " + libresB + " cases accessibles, obtenue au bout de " + essaisB + " essai(s)", #16A34A, 12)
        label(vue, 40, 498, "Générer puis vérifier, et recommencer si besoin : c'est le prix d'un niveau toujours jouable.", #B45309, 11)
    fin si

    affiche vue

    // =========================================================
    appuyer ["a", "z", "e", "r"] dans touche
    si touche = 1 alors
        ecran vaut "ponderer"
    sinon si touche = 2 alors
        ecran vaut "exclure"
    sinon si touche = 3 alors
        ecran vaut "valider"
    sinon
        refaire vaut vrai
    fin si
fin tant que
Pas encore noté
K-means : regrouper des données sans connaître les groupes
Article

K-means

Regrouper des données sans connaître les groupes

Lire l'articleReplier l'article

Ce programme illustre l’un des algorithmes les plus connus en apprentissage automatique : K-means, une méthode capable de regrouper des données sans savoir ce qu’elle cherche.

On part ici de 300 points répartis autour de 4 centres. L’algorithme ne connaît ni ces centres, ni le vrai groupe de chaque point. Il ne reçoit que les coordonnées et le nombre de groupes à trouver.

Deux gestes simples, répétés jusqu’à stabilisation

K-means fonctionne en alternant deux étapes :

Chaque point rejoint le centre le plus proche.
Le programme calcule la distance entre un point et chacun des centres, puis l’assigne au plus proche.

Chaque centre se replace au milieu de ses points.
On recalcule la position du centre comme la moyenne des coordonnées des points qui lui appartiennent.

Ces deux gestes sont répétés jusqu’à ce que plus aucun point ne change de groupe : l’algorithme est alors stabilisé.

Départ malin ou départ au hasard

Le programme montre aussi l’importance de l’initialisation :

Départ au hasard : les centres sont placés n’importe où.
Résultat : l’algorithme peut se tromper, fusionner des groupes ou en découper un en plusieurs.
Départ malin : le premier centre est choisi au hasard, les suivants sont les points les plus éloignés des centres déjà posés.
Résultat : les centres sont bien répartis, et K-means retrouve beaucoup mieux la structure réelle.

Une visualisation en deux cadres

L’écran affiche :

  • À gauche : les vrais groupes, avec leurs couleurs d’origine.
  • À droite : ce que K-means a trouvé, avec les centres représentés par des croix rouges.

On peut suivre :

  • le nombre d’itérations,
  • le nombre de points ayant changé de groupe,
  • la concordance avec la vérité (en %),
  • la stabilisation de l’algorithme.

Ce que montre la simulation

Cette démonstration met en évidence :

  • la simplicité de K-means,
  • sa capacité à retrouver des structures sans supervision,
  • l’importance cruciale du choix des centres initiaux,
  • la rapidité de convergence de l’algorithme.

En quelques étapes seulement, K-means parvient à reconstruire des groupes qu’il n’a jamais vus, uniquement à partir des distances entre points.

Programme :

// ===========================================================
//   K-MEANS : REGROUPER SANS SAVOIR CE QU'ON CHERCHE
//
//   300 points sont tirés autour de 4 centres. L'algorithme
//   ne connaît NI ces centres, NI le groupe de chaque point.
//   Il ne connaît que les coordonnées, et le chiffre 4.
//
//   Il répète deux gestes jusqu'à ce que plus rien ne bouge :
//     1. chaque point rejoint le centre le plus proche
//     2. chaque centre se replace au milieu de ses points
//
//   À gauche : la vérité. À droite : ce qu'il a trouvé.
// ===========================================================

@ CONFIGURATION
vue est une toile
dimension(vue, 700, 520)

NBPTS est un nombre
NBPTS vaut 299
NBGRP est un nombre
NBGRP vaut 3

// Repères des deux cadres à l'écran
GX est un nombre
DX2 est un nombre
GY est un nombre
GX vaut 24
DX2 vaut 368
GY vaut 112

iteration est un nombre
iteration vaut 0
changes est un nombre
changes vaut 0
inertie est un nombre
inertie vaut 0
concord est un nombre
concord vaut 0
stable est un booléen
stable vaut faux

message est un texte
message vaut "Clique sur « une étape » et regarde les centres se déplacer"

nouveauxPoints est un booléen
nouveauxPoints vaut vrai
nouveauxCentres est un booléen
nouveauxCentres vaut faux
initMalin est un booléen
initMalin vaut vrai

@ LES DONNÉES
ptX est un tableau
ptY est un tableau
ptVrai est un tableau
ptGrp est un tableau
pour k de 0 à NBPTS
    ptX ajoute 0
    ptY ajoute 0
    ptVrai ajoute 0
    ptGrp ajoute - 1
fin pour

// Les vrais centres, que l'algorithme ne verra jamais
vraiX est un tableau
vraiY est un tableau
// Les centres trouvés par l'algorithme
cenX est un tableau
cenY est un tableau
coulG est un tableau
pour g de 0 à NBGRP
    vraiX ajoute 0
    vraiY ajoute 0
    cenX ajoute 0
    cenY ajoute 0
fin pour
coulG ajoute #2563EB
coulG ajoute #16A34A
coulG ajoute #F59E0B
coulG ajoute #A855F7

@ UNE CROIX
procédure croix(t, cx, cy, r, coul)
    p1 est un tableau
    p1 ajoute cx - r
    p1 ajoute cy - r + 3
    p1 ajoute cx - r + 3
    p1 ajoute cy - r
    p1 ajoute cx + r
    p1 ajoute cy + r - 3
    p1 ajoute cx + r - 3
    p1 ajoute cy + r
    polygone(t, p1, coul)
    p2 est un tableau
    p2 ajoute cx + r - 3
    p2 ajoute cy - r
    p2 ajoute cx + r
    p2 ajoute cy - r + 3
    p2 ajoute cx - r + 3
    p2 ajoute cy + r
    p2 ajoute cx - r
    p2 ajoute cy + r - 3
    polygone(t, p2, coul)
fin procédure

@ UN BOUTON
procédure bouton(t, x, y, l, txt, coul)
    rectangle_arrondi(t, x, y + 2, l, 34, 8, rgba(15, 23, 42, 0.2))
    rectangle_arrondi(t, x, y, l, 34, 8, coul)
    label(t, x + 12, y + 23, txt, #FFFFFF, 13)
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    // =========================================================
    //   FABRICATION DU JEU DE DONNÉES
    //   Chaque point est tiré autour d'un des quatre centres.
    //   La moyenne de trois tirages donne un nuage plus dense
    //   au milieu qu'aux bords, comme des données réelles.
    // =========================================================
    si nouveauxPoints = vrai alors
        nouveauxPoints vaut faux

        // Les quatre centres se répartissent dans les quatre coins,
        // avec un décalage au hasard. Des centres totalement libres
        // se chevauchent souvent, et aucun algorithme ne peut alors
        // retrouver des groupes qui n'existent pas vraiment.
        pour g de 0 à NBGRP
            bx est un nombre
            by est un nombre
            si g = 0 alors
                bx vaut 28
                by vaut 28
            sinon si g = 1 alors
                bx vaut 72
                by vaut 28
            sinon si g = 2 alors
                bx vaut 28
                by vaut 72
            sinon
                bx vaut 72
                by vaut 72
            fin si
            vraiX[g] vaut bx + hasard(0 - 7, 7)
            vraiY[g] vaut by + hasard(0 - 7, 7)
        fin pour

        pour i de 0 à NBPTS
            g est un nombre
            g vaut i - arrondi_inferieur(i / 4) * 4
            ptVrai[i] vaut g
            dx est un nombre
            dy est un nombre
            dx vaut (hasard(0 - 17, 17) + hasard(0 - 17, 17) + hasard(0 - 17, 17)) / 3
            dy vaut (hasard(0 - 17, 17) + hasard(0 - 17, 17) + hasard(0 - 17, 17)) / 3
            ptX[i] vaut limiter(vraiX[g] + dx, 2, 98)
            ptY[i] vaut limiter(vraiY[g] + dy, 2, 98)
            ptGrp[i] vaut - 1
        fin pour
        nouveauxCentres vaut vrai
    fin si

    // ---------- DÉPART DE L'ALGORITHME ----------
    // L'algorithme doit bien poser ses quatre centres quelque part.
    // Deux façons de faire, et le résultat n'est pas du tout le même.
    si nouveauxCentres = vrai alors
        nouveauxCentres vaut faux

        si initMalin = faux alors
            // ---- DÉPART AU HASARD ----
            // Quatre points pris n'importe où. Rien n'empêche d'en
            // piocher trois dans le même paquet, et l'algorithme
            // découpera alors ce paquet en trois en fusionnant les autres.
            pour g de 0 à NBGRP
                prisH est un nombre
                prisH vaut hasard(0, NBPTS)
                cenX[g] vaut ptX[prisH]
                cenY[g] vaut ptY[prisH]
            fin pour
            message vaut "Départ au hasard : relance plusieurs fois, il se trompe parfois"

        sinon
            // ---- DÉPART MALIN ----
            // Le premier centre est tiré au hasard. Chacun des suivants
            // est le point le PLUS ÉLOIGNÉ de tous les centres déjà posés.
            pris est un nombre
            pris vaut hasard(0, NBPTS)
            cenX[0] vaut ptX[pris]
            cenY[0] vaut ptY[pris]

            pour g de 1 à NBGRP
                meilleurPt est un nombre
                plusLoin est un nombre
                meilleurPt vaut 0
                plusLoin vaut 0 - 1

                pour i de 0 à NBPTS
                    // distance du point i au centre déjà posé le plus proche
                    dMin est un nombre
                    dMin vaut 999999
                    pour h de 0 à g - 1
                        ex est un nombre
                        ey est un nombre
                        ex vaut ptX[i] - cenX[h]
                        ey vaut ptY[i] - cenY[h]
                        dd est un nombre
                        dd vaut ex * ex + ey * ey
                        si dd < dMin alors
                            dMin vaut dd
                        fin si
                    fin pour
                    // on garde le point dont le voisin le plus proche est le plus loin
                    si dMin > plusLoin alors
                        plusLoin vaut dMin
                        meilleurPt vaut i
                    fin si
                fin pour

                cenX[g] vaut ptX[meilleurPt]
                cenY[g] vaut ptY[meilleurPt]
            fin pour
            message vaut "Départ malin : les centres sont posés le plus loin possible les uns des autres"
        fin si

        pour i de 0 à NBPTS
            ptGrp[i] vaut - 1
        fin pour
        iteration vaut 0
        changes vaut 0
        inertie vaut 0
        concord vaut 0
        stable vaut faux
    fin si

    // =========================================================
    //                        AFFICHAGE
    // =========================================================
    effacer(vue)
    contour(vue, #0)
    remplir(vue, #EEF2F7)

    rectangle_arrondi(vue, 12, 10, 676, 62, 14, #FFFFFF)
    label(vue, 26, 36, "K-MEANS : TROUVER LES GROUPES TOUT SEUL", #B45309, 18)
    label(vue, 26, 58, "300 points, 4 groupes. L'algorithme ne connaît que les coordonnées.", #64748B, 12)

    // ---------- CADRE DE GAUCHE : LA VÉRITÉ ----------
    rectangle_arrondi(vue, 12, 78, 332, 322, 14, #FFFFFF)
    label(vue, 26, 102, "LES VRAIS GROUPES", #0F172A, 13)
    rectangle(vue, GX, GY, 300, 270, #F8FAFC)
    pour i de 0 à NBPTS
        cercle(vue, GX + ptX[i] * 3, GY + ptY[i] * 27 / 10, 3, coulG[ptVrai[i]])
    fin pour

    // ---------- CADRE DE DROITE : LA TROUVAILLE ----------
    rectangle_arrondi(vue, 356, 78, 332, 322, 14, #FFFFFF)
    label(vue, 370, 102, "CE QUE L'ALGORITHME A TROUVÉ", #0F172A, 13)
    rectangle(vue, DX2, GY, 300, 270, #F8FAFC)
    pour i de 0 à NBPTS
        coulP est un texte
        si ptGrp[i] < 0 alors
            coulP vaut #CBD5E1
        sinon
            coulP vaut coulG[ptGrp[i]]
        fin si
        cercle(vue, DX2 + ptX[i] * 3, GY + ptY[i] * 27 / 10, 3, coulP)
    fin pour
    // Les centres trouvés, en croix rouge
    pour g de 0 à NBGRP
        xcr est un nombre
        ycr est un nombre
        xcr vaut DX2 + cenX[g] * 3
        ycr vaut GY + cenY[g] * 27 / 10
        appelle croix(vue, xcr, ycr, 11, #FFFFFF)
        appelle croix(vue, xcr, ycr, 8, #DC2626)
    fin pour
    label(vue, 370, 394, "Les croix rouges sont les centres calculés", #DC2626, 10)

    // ---------- CHIFFRES ET BOUTONS ----------
    rectangle_arrondi(vue, 12, 408, 676, 100, 14, #FFFFFF)

    label(vue, 26, 432, "Itération : " + iteration, #0F172A, 13)
    label(vue, 140, 432, "Points changeant de groupe : " + changes, #2563EB, 13)
    si iteration > 0 alors
        label(vue, 380, 432, "Concordance avec la vérité : " + concord + " %", #16A34A, 13)
    fin si
    si stable = vrai alors
        label(vue, 600, 432, "STABILISÉ", #16A34A, 13)
    fin si

    appelle bouton(vue, 26, 444, 96, "Une étape", #2563EB)
    appelle bouton(vue, 130, 444, 116, "Jusqu'au bout", #2563EB)
    appelle bouton(vue, 254, 444, 130, "Autres centres", #7C3AED)
    appelle bouton(vue, 392, 444, 130, "Autres points", #64748B)
    txtInit est un texte
    coulInit est un texte
    si initMalin = vrai alors
        txtInit vaut "Départ : malin"
        coulInit vaut #16A34A
    sinon
        txtInit vaut "Départ : hasard"
        coulInit vaut #DC2626
    fin si
    appelle bouton(vue, 530, 444, 140, txtInit, coulInit)

    label(vue, 26, 500, message, #64748B, 11)

    affiche vue

    // =========================================================
    //                        LA SOURIS
    // =========================================================
    xc est un nombre
    yc est un nombre
    cliquer vue dans xc, yc

    combien est un nombre
    combien vaut 0

    si yc >= 444 et yc <= 478 alors
        si xc >= 26 et xc <= 122 alors
            combien vaut 1
        sinon si xc >= 130 et xc <= 246 alors
            combien vaut 20
        sinon si xc >= 254 et xc <= 384 alors
            nouveauxCentres vaut vrai
        sinon si xc >= 392 et xc <= 522 alors
            nouveauxPoints vaut vrai
        sinon si xc >= 530 et xc <= 670 alors
            si initMalin = vrai alors
                initMalin vaut faux
            sinon
                initMalin vaut vrai
            fin si
            nouveauxCentres vaut vrai
        fin si
    fin si

    // =========================================================
    //   L'ALGORITHME : DEUX GESTES RÉPÉTÉS
    // =========================================================
    n est un nombre
    n vaut 0
    tant que n < combien et stable = faux
        n ajoute 1
        iteration ajoute 1

        // ---------- GESTE 1 : chacun rejoint le centre le plus proche ----------
        changes vaut 0
        inertie vaut 0
        pour i de 0 à NBPTS
            meilleur est un nombre
            distMin est un nombre
            meilleur vaut 0
            distMin vaut 999999
            pour g de 0 à NBGRP
                ex est un nombre
                ey est un nombre
                ex vaut ptX[i] - cenX[g]
                ey vaut ptY[i] - cenY[g]
                dd est un nombre
                dd vaut ex * ex + ey * ey
                si dd < distMin alors
                    distMin vaut dd
                    meilleur vaut g
                fin si
            fin pour
            si ptGrp[i] <> meilleur alors
                changes ajoute 1
            fin si
            ptGrp[i] vaut meilleur
            inertie vaut inertie + distMin
        fin pour

        // ---------- GESTE 2 : chaque centre se replace au milieu ----------
        pour g de 0 à NBGRP
            sx est un nombre
            sy est un nombre
            combienG est un nombre
            sx vaut 0
            sy vaut 0
            combienG vaut 0
            pour i de 0 à NBPTS
                si ptGrp[i] = g alors
                    sx vaut sx + ptX[i]
                    sy vaut sy + ptY[i]
                    combienG ajoute 1
                fin si
            fin pour
            si combienG > 0 alors
                cenX[g] vaut sx / combienG
                cenY[g] vaut sy / combienG
            fin si
        fin pour

        // Plus personne ne change de groupe : c'est terminé
        si changes = 0 alors
            stable vaut vrai
        fin si
    fin tant que

    // =========================================================
    //   MESURE : combien de points sont dans le bon groupe ?
    //   L'algorithme numérote ses groupes comme il veut. On
    //   compare donc chaque groupe trouvé au vrai groupe le
    //   plus représenté à l'intérieur.
    // =========================================================
    si iteration > 0 alors
        bienPlaces est un nombre
        bienPlaces vaut 0
        pour g de 0 à NBGRP
            majorite est un nombre
            majorite vaut 0
            pour v de 0 à NBGRP
                compte est un nombre
                compte vaut 0
                pour i de 0 à NBPTS
                    si ptGrp[i] = g et ptVrai[i] = v alors
                        compte ajoute 1
                    fin si
                fin pour
                si compte > majorite alors
                    majorite vaut compte
                fin si
            fin pour
            bienPlaces vaut bienPlaces + majorite
        fin pour
        concord vaut arrondi(bienPlaces * 100 / (NBPTS + 1))
    fin si

    si combien > 0 alors
        si stable = vrai alors
            message vaut "Terminé en " + iteration + " itérations : " + concord + " % des points sont dans le bon groupe"
        sinon
            message vaut "Itération " + iteration + " : " + changes + " points ont changé de groupe"
        fin si
    fin si
fin tant que
Pas encore noté
Développer une logique de jeu : les fondations indispensables CoddyRun
Article

Développer une logique de jeu

Les fondations indispensables

Lire l'articleReplier l'article

Créer un jeu ne commence jamais par les graphismes, les animations ou les effets visuels. La véritable base, celle qui détermine si un jeu fonctionne, s’appelle la logique de jeu. C’est elle qui définit les règles, les interactions, les comportements, les limites, et tout ce qui fait qu’un univers virtuel devient cohérent.

Développer une logique de jeu, c’est construire un système où chaque action a une conséquence, où chaque élément du monde obéit à des règles précises, et où le joueur peut interagir de manière fluide et compréhensible.

Comprendre ce qu’est la logique de jeu

La logique de jeu regroupe :

  • les règles (ce qui est autorisé ou interdit),
  • les états (position du joueur, vie, inventaire, météo…),
  • les interactions (collision, combat, dialogue),
  • les réactions (ce qui se passe quand une règle est déclenchée),
  • la progression (objectifs, niveaux, récompenses).

Elle est indépendante du rendu graphique.
Un bon jeu peut être jouable même sans images, simplement avec des valeurs et des règles.

Le monde du jeu : structure et représentation

Avant d’afficher quoi que ce soit, il faut définir comment le monde est représenté :

Les modèles les plus courants :

  • Grille (RPG, stratégie, jeux isométriques)
  • Coordonnées continues (jeux d’action, plateformes)
  • Graphes (donjons, réseaux de zones)
  • Systèmes physiques (simulation, jeux de course)

Chaque modèle impose une logique différente.
Par exemple, une carte en grille permet de raisonner en lignes/colonnes, ce qui simplifie les collisions et les déplacements.

Les règles : le cœur du gameplay

Les règles définissent ce que le joueur peut faire :

  • se déplacer,
  • sauter,
  • attaquer,
  • ramasser un objet,
  • ouvrir une porte,
  • éviter un obstacle.

Elles doivent être simples, cohérentes, et prévisibles.
Un joueur doit comprendre intuitivement ce qui est possible ou non.

Exemple de règle :

  • Si la case est un rocher, le joueur ne peut pas avancer.

Une règle simple, mais essentielle pour créer un monde crédible.

Les états : suivre ce qui change

Un jeu est un système dynamique.
Les états permettent de suivre :

  • la position du joueur,
  • la vie,
  • les objets possédés,
  • les ennemis,
  • les événements en cours.

Chaque action modifie un état, et chaque état influence les règles.

Exemple :

  • Position du joueur : (ligne, colonne)
  • Santé : 100
  • Inventaire : [clé, potion]

Les interactions : quand le joueur agit

Une interaction est un lien entre une action et une règle.

Exemples :

  • Le joueur appuie sur une touche ? déplacement.
  • Le joueur touche un ennemi ? perte de vie.
  • Le joueur entre dans une zone ? déclenche un dialogue.

Pour être fluide, une interaction doit être :

  • détectée,
  • analysée,
  • validée ou refusée,
  • exécutée.

La boucle de jeu : le moteur invisible

Tous les jeux fonctionnent grâce à une boucle principale :

  • Lire les actions du joueur
  • Mettre à jour les états
  • Appliquer les règles
  • Dessiner le résultat
  • Recommencer

Cette boucle tourne des dizaines de fois par seconde.
C’est elle qui donne vie au jeu.

Séparer logique et affichage

Une erreur fréquente chez les débutants : mélanger la logique et le rendu.

La logique doit répondre à des questions comme :

  • Le joueur peut-il avancer ?
  • L’ennemi voit-il le joueur ?
  • L’objet est-il ramassable ?

L’affichage doit répondre à :

  • Comment dessiner le joueur ?
  • Quelle animation jouer ?
  • Où placer les éléments sur l’écran ?

Séparer les deux permet :

  • d’éviter les bugs,
  • de rendre le jeu plus flexible,
  • de faciliter l’évolution du projet.

Construire une logique solide : les bonnes pratiques

Voici les principes essentiels :

- Raisonner en données, pas en pixels
Les pixels ne sont qu’une conséquence visuelle.

- Définir des règles simples et cohérentes
Un bon gameplay repose sur des règles claires.

- Utiliser des structures adaptées
Tableaux, objets, graphes… selon le type de jeu.

- Tester chaque règle isolément
Une règle mal définie peut casser tout le système.

- Documenter les comportements
Un jeu est un système complexe : note tout.

Conclusion : la logique, c’est l’âme du jeu

Développer une logique de jeu, c’est créer un univers qui fonctionne, qui réagit, qui vit.
Les graphismes viennent ensuite, comme une couche esthétique posée sur une mécanique solide.

Un jeu bien conçu est avant tout un ensemble de règles intelligentes, cohérentes et agréables à manipuler.
C’est cette logique qui transforme un simple programme en une expérience.

Programme :

// ===========================================================
//   LA LOGIQUE DE JEU : LES FONDATIONS
//   Programme d'illustration
//
//   Les sections portent les numéros des paragraphes de
//   l'article. La démonstration principale est la touche [V] :
//   elle bascule entre un affichage graphique et un affichage
//   en texte. Le jeu reste identique, parce que la logique
//   ne sait rien de la façon dont elle est dessinée.
// ===========================================================

@ CONFIGURATION
vue est une toile
dimension(vue, 700, 520)

NBCOL est un nombre
NBLIG est un nombre
NBCOL vaut 9
NBLIG vaut 7

CASE est un nombre
MAPX est un nombre
MAPY est un nombre
CASE vaut 38
MAPX vaut 24
MAPY vaut 104

// ===========================================================
// @ 2. LE MONDE : UNE GRILLE
//   Le monde est un tableau de lignes et de colonnes.
//   Ce choix décide de tout le reste : les déplacements se
//   raisonnent en cases, jamais en pixels.
//   0 sol   1 rocher   2 clé   3 porte   4 trésor
// ===========================================================
monde est un tableau
depart est un tableau
pour i de 0 à NBLIG
    ligM est un tableau
    ligD est un tableau
    pour j de 0 à NBCOL
        ligM ajoute 0
        ligD ajoute 0
    fin pour
    monde ajoute ligM
    depart ajoute ligD
fin pour

// Des rochers
depart[1][2] vaut 1
depart[2][2] vaut 1
depart[3][2] vaut 1
depart[5][5] vaut 1
depart[4][7] vaut 1
depart[1][6] vaut 1
depart[2][6] vaut 1
depart[6][3] vaut 1
// Une clé, une porte, un trésor
depart[6][1] vaut 2
depart[3][5] vaut 3
depart[1][8] vaut 4

// ===========================================================
// @ 4. LES ÉTATS : CE QUI CHANGE AU COURS DE LA PARTIE
//   Chaque action modifie un état, et chaque état influence
//   les règles. Ces valeurs suffisent à décrire la partie.
// ===========================================================
pLig est un nombre
pCol est un nombre
vie est un nombre
aLaCle est un booléen
tours est un nombre
partieFinie est un booléen
victoire est un booléen

nbE est un nombre
nbE vaut 2
eLig est un tableau
eCol est un tableau
eDir est un tableau
pour k de 0 à 1
    eLig ajoute 0
    eCol ajoute 0
    eDir ajoute 1
fin pour

// L'affichage est un simple réglage : il ne touche à aucune règle
modeTexte est un booléen
modeTexte vaut faux

journal est un texte
etapeBoucle est un texte
journal vaut "La partie commence"
etapeBoucle vaut "en attente d'une action"

touche est un nombre

@ REMISE EN PLACE DE LA PARTIE
pour i de 0 à NBLIG
    pour j de 0 à NBCOL
        monde[i][j] vaut depart[i][j]
    fin pour
fin pour
pLig vaut 6
pCol vaut 0
vie vaut 100
aLaCle vaut faux
tours vaut 0
partieFinie vaut faux
victoire vaut faux
eLig[0] vaut 2
eCol[0] vaut 4
eDir[0] vaut 1
eLig[1] vaut 5
eCol[1] vaut 8
eDir[1] vaut 0 - 1

// ===========================================================
// @ 7. L'AFFICHAGE, VERSION GRAPHIQUE
//   Il ne fait que lire les états. Il n'en modifie aucun.
// ===========================================================
procédure dessinerCarteGraphique(t)
    pour i de 0 à NBLIG
        pour j de 0 à NBCOL
            xc est un nombre
            yc est un nombre
            xc vaut MAPX + j * CASE
            yc vaut MAPY + i * CASE

            contenu est un nombre
            contenu vaut monde[i][j]

            si contenu = 1 alors
                rectangle_arrondi(t, xc + 2, yc + 2, CASE - 4, CASE - 4, 6, #6B7280)
                cercle(t, xc + 14, yc + 14, 4, #9CA3AF)
            sinon
                rectangle_arrondi(t, xc + 2, yc + 2, CASE - 4, CASE - 4, 6, #E7EDF5)
            fin si

            si contenu = 2 alors
                cercle(t, xc + 15, yc + 18, 7, #FBBF24)
                rectangle(t, xc + 20, yc + 16, 12, 4, #FBBF24)
            sinon si contenu = 3 alors
                rectangle_arrondi(t, xc + 8, yc + 6, CASE - 16, CASE - 12, 4, #92400E)
                cercle(t, xc + 26, yc + 22, 3, #FDE68A)
            sinon si contenu = 4 alors
                rectangle_arrondi(t, xc + 7, yc + 14, CASE - 14, CASE - 22, 3, #B45309)
                rectangle(t, xc + 7, yc + 18, CASE - 14, 4, #FDE68A)
            fin si
        fin pour
    fin pour

    // Les ennemis
    pour k de 0 à nbE - 1
        xe est un nombre
        ye est un nombre
        xe vaut MAPX + eCol[k] * CASE + 19
        ye vaut MAPY + eLig[k] * CASE + 19
        cercle(t, xe, ye, 11, #DC2626)
        cercle(t, xe - 4, ye - 3, 2, #FFFFFF)
        cercle(t, xe + 4, ye - 3, 2, #FFFFFF)
    fin pour

    // Le joueur
    xp est un nombre
    yp est un nombre
    xp vaut MAPX + pCol * CASE + 19
    yp vaut MAPY + pLig * CASE + 19
    cercle(t, xp, yp, 12, #2563EB)
    cercle(t, xp - 4, yp - 3, 2, #FFFFFF)
    cercle(t, xp + 4, yp - 3, 2, #FFFFFF)
fin procédure

// ===========================================================
// @ 7. L'AFFICHAGE, VERSION TEXTE
//   Exactement le même état, dessiné autrement.
//   La preuve qu'un jeu est jouable sans une seule image.
// ===========================================================
procédure dessinerCarteTexte(t)
    pour i de 0 à NBLIG
        pour j de 0 à NBCOL
            xc est un nombre
            yc est un nombre
            xc vaut MAPX + j * CASE
            yc vaut MAPY + i * CASE

            sign est un texte
            coulS est un texte
            sign vaut "."
            coulS vaut #475569

            si monde[i][j] = 1 alors
                sign vaut "#"
                coulS vaut #94A3B8
            sinon si monde[i][j] = 2 alors
                sign vaut "C"
                coulS vaut #FBBF24
            sinon si monde[i][j] = 3 alors
                sign vaut "P"
                coulS vaut #92400E
            sinon si monde[i][j] = 4 alors
                sign vaut "T"
                coulS vaut #B45309
            fin si

            pour k de 0 à nbE - 1
                si eLig[k] = i et eCol[k] = j alors
                    sign vaut "E"
                    coulS vaut #DC2626
                fin si
            fin pour

            si pLig = i et pCol = j alors
                sign vaut "@"
                coulS vaut #2563EB
            fin si

            label(t, xc + 13, yc + 27, sign, coulS, 21)
        fin pour
    fin pour
fin procédure

// ===========================================================
// @ 6. LA BOUCLE DE JEU
//   1. lire l'action    2. appliquer les règles
//   3. mettre à jour les états    4. dessiner    5. recommencer
// ===========================================================
tant que vrai

    // ---------- ÉTAPE 4 : DESSINER ----------
    effacer(vue)
    contour(vue, #0)
    remplir(vue, #EEF2F7)

    rectangle_arrondi(vue, 12, 10, 676, 78, 14, #FFFFFF)
    label(vue, 26, 40, "LA LOGIQUE DE JEU", #B45309, 20)
    si modeTexte = vrai alors
        label(vue, 26, 64, "Affichage : TEXTE  -  la logique est strictement la même", #16A34A, 12)
    sinon
        label(vue, 26, 64, "Affichage : GRAPHIQUE  -  appuie sur [V] pour voir la différence", #2563EB, 12)
    fin si
    label(vue, 26, 82, "Boucle de jeu : " + etapeBoucle, #94A3B8, 11)

    rectangle_arrondi(vue, 12, 94, 404, 322, 14, #FFFFFF)
    si modeTexte = vrai alors
        appelle dessinerCarteTexte(vue)
    sinon
        appelle dessinerCarteGraphique(vue)
    fin si

    // ---------- LE TABLEAU DES ÉTATS ----------
    rectangle_arrondi(vue, 424, 94, 264, 322, 14, #FFFFFF)
    label(vue, 442, 122, "LES ÉTATS", #0F172A, 14)
    label(vue, 442, 152, "position : ligne " + pLig + ", colonne " + pCol, #475569, 12)
    coulVie est un texte
    si vie <= 40 alors
        coulVie vaut #DC2626
    sinon
        coulVie vaut #16A34A
    fin si
    label(vue, 442, 176, "santé : " + vie, coulVie, 12)
    txtCle est un texte
    si aLaCle = vrai alors
        txtCle vaut "inventaire : [clé]"
    sinon
        txtCle vaut "inventaire : [ ]"
    fin si
    label(vue, 442, 200, txtCle, #475569, 12)
    label(vue, 442, 224, "tours joués : " + tours, #475569, 12)
    label(vue, 442, 248, "ennemi 1 : ligne " + eLig[0] + ", colonne " + eCol[0], #DC2626, 11)
    label(vue, 442, 268, "ennemi 2 : ligne " + eLig[1] + ", colonne " + eCol[1], #DC2626, 11)

    label(vue, 442, 306, "LA RÈGLE DÉCLENCHÉE", #0F172A, 14)
    label(vue, 442, 332, journal, #B45309, 11)

    si partieFinie = vrai alors
        si victoire = vrai alors
            label(vue, 442, 372, "PARTIE GAGNÉE", #16A34A, 16)
        sinon
            label(vue, 442, 372, "PARTIE PERDUE", #DC2626, 16)
        fin si
    fin si

    rectangle_arrondi(vue, 12, 424, 676, 88, 14, #FFFFFF)
    label(vue, 26, 448, "[Z] [Q] [S] [D] se déplacer  -  [V] changer d'affichage  -  [R] recommencer", #2563EB, 13)
    label(vue, 26, 472, "Règles : un rocher bloque  -  la porte demande la clé  -  un ennemi fait perdre 25 de vie", #64748B, 11)
    label(vue, 26, 490, "Le trésor gagne la partie  -  à 0 de santé, la partie est perdue", #64748B, 11)
    label(vue, 26, 506, "Aucune de ces règles ne parle de pixels : elles ne connaissent que des lignes et des colonnes.", #94A3B8, 11)

    affiche vue

    // ---------- ÉTAPE 1 : LIRE L'ACTION DU JOUEUR ----------
    appuyer ["z", "s", "q", "d", "v", "r"] dans touche
    etapeBoucle vaut "action lue"

    si touche = 5 alors
        // Changer d'affichage ne modifie aucun état du jeu
        si modeTexte = vrai alors
            modeTexte vaut faux
        sinon
            modeTexte vaut vrai
        fin si
        journal vaut "Affichage changé : la partie n'a pas bougé"

    sinon si touche = 6 alors
        pour i de 0 à NBLIG
            pour j de 0 à NBCOL
                monde[i][j] vaut depart[i][j]
            fin pour
        fin pour
        pLig vaut 6
        pCol vaut 0
        vie vaut 100
        aLaCle vaut faux
        tours vaut 0
        partieFinie vaut faux
        victoire vaut faux
        eLig[0] vaut 2
        eCol[0] vaut 4
        eDir[0] vaut 1
        eLig[1] vaut 5
        eCol[1] vaut 8
        eDir[1] vaut 0 - 1
        journal vaut "Nouvelle partie"

    sinon si partieFinie = vrai alors
        journal vaut "La partie est terminée : appuie sur [R]"

    sinon
        // -------------------------------------------------------
        // @ 5. L'INTERACTION : détectée, analysée, validée, exécutée
        // -------------------------------------------------------
        nLig est un nombre
        nCol est un nombre
        nLig vaut pLig
        nCol vaut pCol

        si touche = 1 alors
            nLig vaut pLig - 1
        sinon si touche = 2 alors
            nLig vaut pLig + 1
        sinon si touche = 3 alors
            nCol vaut pCol - 1
        sinon
            nCol vaut pCol + 1
        fin si

        etapeBoucle vaut "règles appliquées"
        bouger est un booléen
        bouger vaut faux

        // ===================================================
        // @ 3. LES RÈGLES : ce qui est autorisé ou interdit
        // ===================================================

        // Règle 1 : on ne sort pas du monde
        si nLig < 0 ou nLig > NBLIG ou nCol < 0 ou nCol > NBCOL alors
            journal vaut "Règle 1 : on ne sort pas de la carte"

        // Règle 2 : un rocher est infranchissable
        sinon si monde[nLig][nCol] = 1 alors
            journal vaut "Règle 2 : un rocher bloque le passage"

        // Règle 3 : la porte n'accepte que le porteur de la clé
        sinon si monde[nLig][nCol] = 3 et aLaCle = faux alors
            journal vaut "Règle 3 : la porte est fermée, il faut la clé"

        sinon
            bouger vaut vrai
        fin si

        // ===================================================
        // @ 4. LA MISE À JOUR DES ÉTATS
        // ===================================================
        si bouger = vrai alors
            pLig vaut nLig
            pCol vaut nCol
            tours ajoute 1

            // Règle 4 : la clé se ramasse et entre dans l'inventaire
            si monde[pLig][pCol] = 2 alors
                aLaCle vaut vrai
                monde[pLig][pCol] vaut 0
                journal vaut "Règle 4 : clé ramassée, elle entre dans l'inventaire"

            // Règle 5 : la porte s'ouvre et disparaît
            sinon si monde[pLig][pCol] = 3 alors
                monde[pLig][pCol] vaut 0
                journal vaut "Règle 5 : la clé ouvre la porte"

            // Règle 6 : le trésor met fin à la partie
            sinon si monde[pLig][pCol] = 4 alors
                partieFinie vaut vrai
                victoire vaut vrai
                journal vaut "Règle 6 : trésor atteint, partie gagnée"

            sinon
                journal vaut "Déplacement autorisé"
            fin si

            // ---------------------------------------------------
            //   Le monde évolue aussi de son côté : les ennemis
            //   font leur ronde après chaque action du joueur.
            // ---------------------------------------------------
            si partieFinie = faux alors
                pour k de 0 à nbE - 1
                    sCol est un nombre
                    sCol vaut eCol[k] + eDir[k]
                    demiTour est un booléen
                    demiTour vaut faux
                    si sCol < 0 ou sCol > NBCOL alors
                        demiTour vaut vrai
                    sinon si monde[eLig[k]][sCol] = 1 alors
                        demiTour vaut vrai
                    fin si
                    si demiTour = vrai alors
                        eDir[k] vaut 0 - eDir[k]
                    sinon
                        eCol[k] vaut sCol
                    fin si
                fin pour

                // Règle 7 : se retrouver sur un ennemi coûte de la santé
                pour k de 0 à nbE - 1
                    si eLig[k] = pLig et eCol[k] = pCol alors
                        vie vaut vie - 25
                        journal vaut "Règle 7 : un ennemi te touche, - 25 de santé"
                    fin si
                fin pour

                // Règle 8 : sans santé, la partie s'arrête
                si vie <= 0 alors
                    vie vaut 0
                    partieFinie vaut vrai
                    victoire vaut faux
                    journal vaut "Règle 8 : santé à zéro, partie perdue"
                fin si
            fin si
        fin si

        etapeBoucle vaut "états mis à jour, on redessine"
    fin si
fin tant que
Pas encore noté
Déplacement en isométrie : Comprendre et construire une carte 2D en pseudo-3D
Article

Déplacement en isométrie

Comprendre et construire une carte 2D en pseudo-3D

Lire l'articleReplier l'article

Créer une carte en isométrie est l’un des moyens les plus élégants de donner une impression de profondeur dans un jeu 2D. Le programme présenté ici illustre parfaitement ce principe : une carte 10×10, un personnage, quelques obstacles, et une projection isométrique simple mais efficace. L’ensemble constitue une base idéale pour comprendre comment fonctionne le rendu isométrique et comment gérer un déplacement sur une grille.

Objectif du programme

Le but est de représenter une carte en vue isométrique, puis de permettre à un personnage de se déplacer dessus à l’aide des touches Z, Q, S, D.
L’écran n’est qu’une projection : le déplacement se raisonne en lignes et colonnes, jamais en pixels. C’est la projection isométrique qui transforme ces coordonnées logiques en coordonnées visuelles.

La configuration de la vue

La fenêtre de rendu est définie à 640 × 420 pixels.
Deux valeurs essentielles contrôlent la géométrie isométrique :

  • DX = 32 : demi-largeur d’une case
  • DY = 16 : demi-hauteur d’une case

Règle d’or : la hauteur vaut la moitié de la largeur, ce qui donne l’effet isométrique.

Le point d’origine (0,0) de la carte est placé à :

  • ORIGX = 320
  • ORIGY = 80

C’est autour de ce point que la carte va se déployer.

Construction de la carte

La carte est un tableau 10×10 rempli de valeurs :

  • 0 : herbe
  • 1 : eau
  • 2 : rocher

Quelques éléments sont ajoutés pour donner du relief :

  • Un étang au nord-est (cases d’eau)
  • Plusieurs rochers dispersés

Cette carte est volontairement simple, mais elle illustre parfaitement la logique d’un monde en grille.

Dessiner une case isométrique

Une case isométrique est un losange composé de quatre points autour d’un centre.
La procédure dessinerCase construit ce losange à partir des coordonnées projetées cx et cy.

Chaque case est ensuite colorée selon son type :

  • bleu pour l’eau
  • vert pour l’herbe
  • gris pour les rochers

Dessiner un rocher en pseudo-3D Les rochers sont composés de trois polygones :

  • une face gauche
  • une face droite
  • un dessus (une case isométrique décalée vers le haut)

Ce petit volume donne un effet 3D très convaincant malgré sa simplicité.

Dessiner le personnage Le personnage est constitué de formes simples :

  • une ombre elliptique
  • un corps rectangulaire arrondi
  • une tête circulaire
  • deux yeux

Ce design minimaliste suffit à le rendre lisible et sympathique.

La projection isométrique

C’est le cœur du programme : deux lignes qui transforment une position logique (ligne, colonne) en position visuelle (cx, cy).

  • cx = ORIGX + (colonne - ligne) * DX
  • cy = ORIGY + (colonne + ligne) * DY

Cette transformation crée l’effet isométrique :

  • les lignes s’inclinent vers le sud-ouest
  • les colonnes s’inclinent vers le sud-est
  • les cases semblent former un damier en perspective

La boucle principale

À chaque frame :

  • L’écran est effacé et recoloré.
  • La carte est parcourue ligne par ligne, colonne par colonne.
  • Chaque case est projetée et dessinée.
  • Le personnage est dessiné lorsqu’on arrive sur sa case.
  • Des informations sont affichées en haut de l’écran.

L’ordre de dessin garantit que les cases du bas recouvrent celles du haut, ce qui simule la profondeur.

Le déplacement

Le joueur utilise Z, Q, S, D pour se déplacer :

  • Z : ligne – 1
  • S : ligne + 1
  • Q : colonne – 1
  • D : colonne + 1

Avant de valider le déplacement, le programme vérifie :

  • si la case est hors carte
  • si c’est de l’eau
  • si c’est un rocher

Selon le cas, un message s’affiche :
« Tu es au bord de la carte », « Tu ne sais pas nager », « Un rocher te bloque », ou « Tu avances ».

Ce que ce programme enseigne

Ce code est un excellent exemple pédagogique pour comprendre :

  • la logique d’une carte en grille
  • la projection isométrique
  • la gestion des obstacles
  • le rendu d’objets 2D en pseudo-3D
  • la séparation entre logique (lignes/colonnes) et affichage (pixels)

Il constitue une base solide pour développer :

  • un RPG isométrique
  • un jeu de stratégie
  • un moteur de rendu isométrique plus avancé
  • un éditeur de cartes

Conclusion

Ce programme montre qu’avec quelques formules simples et une bonne organisation, il est possible de créer une carte isométrique claire, lisible et interactive. La projection isométrique reste l’un des outils les plus élégants pour donner de la profondeur à un jeu 2D, et ce code en offre une démonstration limpide.

Programme :

// ===========================================================
//   SE DÉPLACER SUR UNE CARTE EN ISOMÉTRIE
//   Exemple simple : une carte 10 x 10 et un personnage
// ===========================================================

@ CONFIGURATION
vue est une toile
dimension(vue, 640, 420)

// Demi-largeur et demi-hauteur d'une case à l'écran.
// La règle de l'isométrie : la hauteur vaut la moitié de la largeur.
DX est un nombre
DY est un nombre
DX vaut 32
DY vaut 16

// Point de l'écran où se trouve la case (0, 0)
ORIGX est un nombre
ORIGY est un nombre
ORIGX vaut 320
ORIGY vaut 80

// Position du personnage sur la carte
pLig est un nombre
pCol est un nombre
pLig vaut 4
pCol vaut 4

message est un texte
message vaut "Déplace-toi avec Z, Q, S, D"

touche est un nombre

@ LA CARTE
// 0 = herbe, 1 = eau, 2 = rocher
carte est un tableau
pour i de 0 à 9
    uneLigne est un tableau
    pour j de 0 à 9
        uneLigne ajoute 0
    fin pour
    carte ajoute uneLigne
fin pour

// Un étang au nord-est
pour i de 1 à 3
    pour j de 6 à 8
        carte[i][j] vaut 1
    fin pour
fin pour

// Quelques rochers
carte[6][2] vaut 2
carte[6][3] vaut 2
carte[7][3] vaut 2
carte[2][2] vaut 2
carte[8][7] vaut 2

@ DESSINER UNE CASE
// Une case isométrique est un losange : quatre points autour de son centre.
procédure dessinerCase(t, cx, cy, clr)
    points est un tableau
    points ajoute cx
    points ajoute cy - DY
    points ajoute cx + DX
    points ajoute cy
    points ajoute cx
    points ajoute cy + DY
    points ajoute cx - DX
    points ajoute cy
    polygone(t, points, clr)
fin procédure

@ DESSINER UN ROCHER
procédure dessinerRocher(t, cx, cy)
    // Face gauche
    fg est un tableau
    fg ajoute cx - DX
    fg ajoute cy - 20
    fg ajoute cx
    fg ajoute cy - 20 + DY
    fg ajoute cx
    fg ajoute cy + DY
    fg ajoute cx - DX
    fg ajoute cy
    polygone(t, fg, #6B7280)

    // Face droite
    fd est un tableau
    fd ajoute cx
    fd ajoute cy - 20 + DY
    fd ajoute cx + DX
    fd ajoute cy - 20
    fd ajoute cx + DX
    fd ajoute cy
    fd ajoute cx
    fd ajoute cy + DY
    polygone(t, fd, #4B5563)

    // Dessus
    appelle dessinerCase(t, cx, cy - 20, #9CA3AF)
fin procédure

@ DESSINER LE PERSONNAGE
procédure dessinerPerso(t, cx, cy)
    ellipse(t, cx, cy, 12, 6, rgba(0, 0, 0, 0.35))
    rectangle_arrondi(t, cx - 5, cy - 22, 10, 14, 3, #38BDF8)
    cercle(t, cx, cy - 28, 7, #FCD34D)
    cercle(t, cx - 2, cy - 29, 1, #0F172A)
    cercle(t, cx + 2, cy - 29, 1, #0F172A)
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    effacer(vue)
    remplir(vue, #0F172A)

    // ---------------------------------------------------------
    //   LE RENDU
    //   On parcourt la carte ligne par ligne, colonne par colonne.
    //   Cet ordre suffit : une case ne peut être masquée que par
    //   une case de ligne ou de colonne plus grande, donc dessinée
    //   après elle.
    // ---------------------------------------------------------
    pour i de 0 à 9
        pour j de 0 à 9

            // *** LA PROJECTION ISOMÉTRIQUE, C'EST CES DEUX LIGNES ***
            cx est un nombre
            cy est un nombre
            cx vaut ORIGX + (j - i) * DX
            cy vaut ORIGY + (j + i) * DY

            valeur est un nombre
            valeur vaut carte[i][j]

            si valeur = 1 alors
                appelle dessinerCase(vue, cx, cy, #2563EB)
            sinon si valeur = 2 alors
                appelle dessinerCase(vue, cx, cy, #3F8F52)
                appelle dessinerRocher(vue, cx, cy)
            sinon
                appelle dessinerCase(vue, cx, cy, #3F8F52)
            fin si

            // Le personnage se dessine quand on arrive sur sa case
            si i = pLig et j = pCol alors
                appelle dessinerPerso(vue, cx, cy)
            fin si
        fin pour
    fin pour

    // ---------- INFORMATIONS ----------
    rectangle(vue, 0, 0, 640, 46, rgba(15, 23, 42, 0.85))
    label(vue, 16, 22, "DÉPLACEMENT EN ISOMÉTRIE", #F8FAFC, 15)
    label(vue, 16, 40, "cx = ORIGX + (colonne - ligne) x " + DX + "      cy = ORIGY + (colonne + ligne) x " + DY, #38BDF8, 11)
    label(vue, 420, 22, "Ligne " + (pLig + 1) + "   Colonne " + (pCol + 1), #FDE68A, 14)
    label(vue, 420, 40, message, #94A3B8, 11)

    affiche vue

    // ---------------------------------------------------------
    //   LE DÉPLACEMENT
    //   On raisonne en lignes et en colonnes, jamais en pixels.
    //   L'écran n'est qu'une conséquence de la projection.
    // ---------------------------------------------------------
    appuyer ["z", "s", "q", "d"] dans touche

    nLig est un nombre
    nCol est un nombre
    nLig vaut pLig
    nCol vaut pCol

    si touche = 1 alors
        nLig vaut pLig - 1
    sinon si touche = 2 alors
        nLig vaut pLig + 1
    sinon si touche = 3 alors
        nCol vaut pCol - 1
    sinon
        nCol vaut pCol + 1
    fin si

    si nLig < 0 ou nLig > 9 ou nCol < 0 ou nCol > 9 alors
        message vaut "Tu es au bord de la carte"
    sinon si carte[nLig][nCol] = 1 alors
        message vaut "Tu ne sais pas nager"
    sinon si carte[nLig][nCol] = 2 alors
        message vaut "Un rocher te bloque"
    sinon
        pLig vaut nLig
        pCol vaut nCol
        message vaut "Tu avances"
    fin si
fin tant que
Pas encore noté
Comprendre la copie des tableaux dans CoddyRun
Article

Comprendre la copie des tableaux dans CoddyRun

Pourquoi vos tableaux semblent liés et comment obtenir une vraie copie

Lire l'articleReplier l'article

Dans CoddyRun, certains comportements concernant les tableaux peuvent surprendre, surtout lorsqu’on pense créer une copie indépendante d’un tableau existant. Beaucoup d’utilisateurs constatent que modifier un tableau entraîne la modification d’un autre… alors qu’ils pensaient avoir créé deux tableaux distincts.

Ce phénomène est normal, et il ne s’agit pas d’un bug. Il découle d’un principe fondamental partagé par de nombreux langages modernes : la copie des références pour les types composés.

Types simples vs types composés
Pour bien comprendre, il faut distinguer deux catégories de valeurs :

Types simples

  • nombre
  • texte
  • booléen

La valeur est copiée.
Chaque variable possède sa propre donnée.

Types composés

  • tableau
  • enregistrement
  • dictionnaire

La référence est copiée.
Autrement dit, deux variables peuvent pointer vers le même objet en mémoire.
Modifier l’un revient à modifier l’autre.

Ce comportement est identique dans Python, JavaScript, Java, et CoddyRun suit cette logique.

Exemple du problème : une copie qui n’en est pas une

// tableau à modifier
tab est un tableau
// tableau initial
init est un tableau
i est un nombre

// chargement du tableau tab
pour i de 1 à 10
    tab ajoute hasard(100)
fin pour

// copie pour garder l'initialisation
init vaut tab
affiche tab

// modification du résultat
tab[0] vaut 999

affiche tab
affiche init

Résultat observé
init contient aussi le 999.
Les deux tableaux évoluent ensemble.

Pourquoi ?
La ligne :

init vaut tab

ne crée pas une copie.
Elle dit simplement : init désigne désormais le même tableau que tab.

Le tableau vide créé au début pour init est immédiatement abandonné.

Comment créer une vraie copie ?

Pour obtenir un tableau indépendant, il faut copier les éléments un par un :

tab, init est un tableau
i est un nombre

pour i de 1 à 10
    tab ajoute hasard(100)
fin pour

// copie réelle, élément par élément
pour i de 0 à longueur(tab) - 1
    init ajoute tab[i]
fin pour

tab[0] vaut 999
affiche 'tab = ' + tab // le 999 est là
affiche 'init = ' + init // valeur d'origine conservée

Ici, init contient une vraie copie du tableau original.

Utiliser la fonction fusionner() pour simplifier

CoddyRun propose une fonction pratique :

fusionner()

Elle renvoie un nouveau tableau, totalement indépendant.

tab est un tableau
init est un tableau
i est un nombre

// chargement du tableau tab
pour i de 1 à 10
    tab ajoute hasard(100)
fin pour

// copie réelle : fusionner renvoie un NOUVEAU tableau
init vaut fusionner(tab, init)

affiche 'tab : ' + tab
affiche 'init : ' + init

// modification
tab[0] vaut 999

affiche 'tab : ' + tab // le 999 apparaît
affiche 'init : ' + init // valeur d'origine conservée

Avantage

  • Pas besoin de boucle
  • Pas de risque de confusion
  • Copie propre et indépendante

Conclusion

Le comportement observé par les utilisateurs est normal : les tableaux sont des objets complexes, copiés par référence. Pour obtenir une copie indépendante, il faut soit :

  • recopier les éléments un par un,
  • soit utiliser la fonction fusionner().

Comprendre cette logique permet d’éviter des erreurs subtiles et de mieux maîtriser la manipulation des structures de données dans CoddyRun.

Pas encore noté
Comment transférer mes programmes EduCode vers CoddyRun
Astuce

Comment transférer mes programmes

Migrer vos programmes EduCode vers CoddyRun

Lire l'articleReplier l'article

Pour migrer vos programmes EduCode vers CoddyRun, suivez ces étapes :

  • Ouvrez le dossier Documents.
  • Dans le dossier EduCode, copiez le fichier EduCode.json.
  • Collez ce fichier dans le dossier CoddyRun.
  • Dans le dossier CoddyRun, supprimez le fichier CoddyRun.json déjà présent.
  • Renommez EduCode.json en CoddyRun.json.

Une fois le transfert terminé, supprimez le dossier EduCode de vos Documents.

Finalisation du transfert et lancement de CoddyRun

Une fois le fichier transféré et renommé :

  • Exécutez CoddyRun.exe.
  • CoddyRun va automatiquement charger le fichier CoddyRun.json que vous venez d'importer.
  • Vos programmes apparaîtront maintenant dans la section : Mes Programmes.
Pas encore noté