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

Prochaine mise à jour de CoddyRun v3.4
Nouveauté

Nouvelle fonction : paramètre()

Prochaine mise à jour de CoddyRun v3.4

Lire l'articleReplier l'article

Dans la prochaine mise à jour de CoddyRun v3.4 : Nouvelle fonction paramètre()

Une fonctionnalité majeure arrive dans CoddyRun : la possibilité de stocker, lire et organiser des données persistantes.

Cette nouvelle fonction permet de conserver des informations essentielles comme :

  • des scores,
  • des grilles de jeu,
  • des paramètres de progression,
  • des options personnalisées,
  • ou tout autre type de donnée utile à vos programmes.

Nouvelle fonction : paramètre(section, clé, valeur)

Elle permet de lire et écrire des valeurs dans des sections du fichier INI (CoddyRun.ini).

Écrire une valeur

paramètre('section', 'maCle', 52)
paramètre('section', 'maCle', "Mon texte")

Lire une valeur
La fonction renvoie la valeur associée à la clé :

x est un nombre vaut paramètre('section', 'maCle')

Gestion stricte des sections et clés

  • Si la section n’existe pas ? Erreur
  • Si la clé n’existe pas ? Erreur

Lors de la création, CoddyRun vérifie que la section n’existe pas déjà (pas de doublon)

Cette approche garantit une structure propre et évite les écritures accidentelles.

Synchronisation automatique au démarrage

À l’ouverture du programme :

  • CoddyRun parcourt le fichier CoddyRun.json, là où sont stockés vos programmes.
  • Il identifie toutes les sections déclarées dans vos scripts.
  • Il vérifie leur présence dans CoddyRun.ini.
  • Il purge automatiquement les sections du fichier INI qui ne sont plus utilisées.

Résultat : un fichier INI propre, cohérent, et parfaitement synchronisé avec vos programmes.

Une avancée qui rend CoddyRun plus puissant, plus flexible et idéal pour des projets de jeu ou d’outils évolués.

Pas encore noté
Domino Merge : un puzzle stratégique où chaque fusion compte
Article

Domino Merge

Un puzzle stratégique où chaque fusion compte

Lire l'articleReplier l'article

Domino Merge est un jeu de réflexion addictif qui combine placement, anticipation et réactions en chaîne. Le programme que tu as partagé décrit en détail le fonctionnement interne du jeu : tirage des dominos, règles de fusion, gestion du score, interface graphique et conditions de fin de partie. Voici une présentation complète et fidèle à ce que fait réellement le jeu.

Un plateau simple, une profondeur surprenante

Le jeu se déroule sur un plateau de 5 colonnes et 6 lignes, entièrement vide au départ.
Chaque domino occupe deux cases adjacentes, horizontalement ou verticalement.

Le joueur peut :

  • Placer le domino dans l’une des quatre orientations
  • Le jeter via la poubelle (coût : 10 pièces)
  • Afficher l’aide
  • Voir le domino suivant pour anticiper ses coups

Le message initial du jeu le dit clairement :

  • « Clique sur une case : le domino occupe deux cases »

Un tirage de dés pensé pour favoriser les fusions

Les valeurs des dés ne sont pas tirées uniformément. Le programme indique que :

  • Les valeurs 1, 2 et 3 sortent plus souvent
  • Les valeurs élevées sont plus rares
  • Un dé sur quatre est un double (valeurs identiques)

Ce système encourage les fusions fréquentes en début de partie, tout en rendant les valeurs hautes plus gratifiantes.

Rotation et placement : l’art d’optimiser l’espace

Le joueur peut tourner le domino grâce au bouton dédié.
Lors du placement, le jeu vérifie :

  • Si le domino dépasse du plateau
  • Si les deux cases sont libres
  • Si l’orientation est valide

En cas d’erreur, des messages clairs apparaissent :

  • « Il faut deux cases libres côte à côte »
  • « Le domino dépasse du plateau : tourne-le »

Le cœur du gameplay : les fusions en chaîne

Dès qu’un domino est posé, le jeu analyse le plateau pour détecter des groupes de trois dés identiques ou plus qui se touchent.
Le programme décrit précisément ce mécanisme :

  • « Trois dés identiques qui se touchent fusionnent en un dé supérieur. »

Lorsqu’un groupe est trouvé :

  • Les dés disparaissent
  • Un dé de valeur supérieure apparaît
  • Le joueur gagne des points

Une réaction en chaîne peut se produire si ce nouveau dé crée une nouvelle fusion

Les gains dépendent :

  • De la valeur du dé
  • Du nombre de dés fusionnés
  • Du multiplicateur de chaîne
  • Les valeurs élevées rapportent aussi davantage de pièces.

Score, pièces et progression

Le joueur commence avec 20 pièces.
Les fusions rapportent :

  • Des points, proportionnels à la valeur et à la taille du groupe
  • Des pièces, plus généreuses pour les valeurs élevées

Le jeu suit :

  • Le score actuel
  • Le record
  • Le meilleur dé obtenu

Fin de partie : quand il n’y a plus d’espace

La partie se termine lorsqu’il n’existe plus deux cases libres adjacentes.
Le jeu affiche alors un écran de fin avec :

  • Le score
  • Le meilleur dé
  • Un bouton pour recommencer

Une boucle de jeu fluide et intuitive

Le programme gère :

  • L’affichage complet
  • Les interactions de la souris
  • Les animations des dés
  • Les messages contextuels
  • Les transitions entre les états (jeu, aide, fin de partie)

Domino Merge est donc un jeu complet, cohérent, et très bien structuré.

Programme :

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

COLS est un nombre
LIGS est un nombre
COLS vaut 5
LIGS vaut 6

CS est un nombre
BX est un nombre
BY est un nombre
CS vaut 52
BX vaut 44
BY vaut 80

COUT_POUBELLE est un nombre
COUT_POUBELLE vaut 10

score est un nombre
score vaut 0
record est un nombre
record vaut 0
pieces est un nombre
pieces vaut 20
meilleurDomino est un nombre
meilleurDomino vaut 1

// Le domino en main : deux dés et une orientation de 0 à 3
valA est un nombre
valB est un nombre
sA est un nombre
sB est un nombre
orient est un nombre
orient vaut 0

derLig est un nombre
derCol est un nombre
der2Lig est un nombre
der2Col est un nombre
derLig vaut - 1
derCol vaut - 1
der2Lig vaut - 1
der2Col vaut - 1

finPartie est un booléen
finPartie vaut faux
aide est un booléen
aide vaut faux

message est un texte
message vaut "Clique sur une case : le domino occupe deux cases"

@ PLATEAU
plateau est un tableau
marque est un tableau
pour i de 0 à LIGS
    ligG est un tableau
    ligM est un tableau
    pour j de 0 à COLS
        ligG ajoute 0
        ligM ajoute faux
    fin pour
    plateau ajoute ligG
    marque ajoute ligM
fin pour

// Le plateau démarre entièrement vide

@ TIRAGE DES DOMINOS
// Les petites valeurs sortent bien plus souvent que les grandes,
// et un domino sur quatre est un double.
tirage est un nombre
tirage vaut hasard(1, 100)
si tirage <= 40 alors
    valA vaut 1
sinon si tirage <= 72 alors
    valA vaut 2
sinon si tirage <= 90 alors
    valA vaut 3
sinon
    valA vaut 4
fin si
si hasard(1, 100) <= 25 alors
    valB vaut valA
sinon
    tirage vaut hasard(1, 100)
    si tirage <= 40 alors
        valB vaut 1
    sinon si tirage <= 72 alors
        valB vaut 2
    sinon si tirage <= 90 alors
        valB vaut 3
    sinon
        valB vaut 4
    fin si
fin si

tirage vaut hasard(1, 100)
si tirage <= 40 alors
    sA vaut 1
sinon si tirage <= 72 alors
    sA vaut 2
sinon si tirage <= 90 alors
    sA vaut 3
sinon
    sA vaut 4
fin si
si hasard(1, 100) <= 25 alors
    sB vaut sA
sinon
    tirage vaut hasard(1, 100)
    si tirage <= 40 alors
        sB vaut 1
    sinon si tirage <= 72 alors
        sB vaut 2
    sinon si tirage <= 90 alors
        sB vaut 3
    sinon
        sB vaut 4
    fin si
fin si

@ DESSIN D'UN DÉ
procédure dessinerDomino(t, x, y, c, val, ombre)
    ray est un nombre
    ray vaut c / 4

    si ombre = vrai alors
        rectangle_arrondi(t, x + 1, y + 4, c, c, ray, rgba(0, 0, 0, 0.4))
    fin si

    coul est un texte
    sombre est un texte
    si val = 1 alors
        coul vaut #FF3B6B
        sombre vaut #D42B54
    sinon si val = 2 alors
        coul vaut #2D9CFF
        sombre vaut #1B76CC
    sinon si val = 3 alors
        coul vaut #FF9A2B
        sombre vaut #D97812
    sinon si val = 4 alors
        coul vaut #E040FB
        sombre vaut #B026C9
    sinon si val = 5 alors
        coul vaut #7C4DFF
        sombre vaut #5A2FCC
    sinon
        coul vaut #00E5C0
        sombre vaut #00B396
    fin si

    // Un socle plus foncé sous la face donne du volume au dé
    rectangle_arrondi(t, x, y, c, c, ray, sombre)
    rectangle_arrondi(t, x, y, c, c * 7 / 8, ray, coul)
    // Reflet du dessus
    rectangle_arrondi(t, x + c / 7, y + c / 9, c * 5 / 7, c / 9, 3, rgba(255, 255, 255, 0.3))

    // Les points, disposés comme sur un dé
    r est un nombre
    r vaut c / 11
    ax est un nombre
    bx est un nombre
    dx est un nombre
    ay est un nombre
    by est un nombre
    dy est un nombre
    ax vaut x + c / 4
    bx vaut x + c / 2
    dx vaut x + c * 3 / 4
    ay vaut y + c * 27 / 100
    by vaut y + c / 2
    dy vaut y + c * 73 / 100

    si val = 1 ou val = 3 ou val = 5 alors
        cercle(t, bx, by + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, bx, by, r, #FFFFFF)
    fin si
    si val >= 2 alors
        cercle(t, ax, ay + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, ax, ay, r, #FFFFFF)
        cercle(t, dx, dy + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, dx, dy, r, #FFFFFF)
    fin si
    si val >= 4 alors
        cercle(t, dx, ay + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, dx, ay, r, #FFFFFF)
        cercle(t, ax, dy + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, ax, dy, r, #FFFFFF)
    fin si
    si val = 6 alors
        cercle(t, ax, by + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, ax, by, r, #FFFFFF)
        cercle(t, dx, by + 1, r, rgba(0, 0, 0, 0.18))
        cercle(t, dx, by, r, #FFFFFF)
    fin si
fin procédure

@ UNE CASE VIDE
procédure dessinerCaseVide(t, x, y, c)
    rectangle_arrondi(t, x, y, c, c, c / 4, #45464C)
    rectangle_arrondi(t, x + c / 6, y + c / 8, c * 2 / 3, c / 10, 3, rgba(255, 255, 255, 0.04))
fin procédure

@ DESSIN D'UN DOMINO COMPLET (DEUX DÉS)
procédure dessinerCouple(t, x, y, c, v1, v2, dl, dc, ombre)
    // La barrette de liaison passe sous les deux dés
    si dc = 1 alors
        rectangle_arrondi(t, x + c - 3, y + c / 3, c / 4 + 6, c / 3, 3, #6B6D75)
    sinon
        rectangle_arrondi(t, x + c / 3, y + c - 3, c / 3, c / 4 + 6, 3, #6B6D75)
    fin si
    appelle dessinerDomino(t, x, y, c, v1, ombre)
    appelle dessinerDomino(t, x + dc * (c + 6), y + dl * (c + 6), c, v2, ombre)
fin procédure

@ UNE PIÈCE D'OR
procédure dessinerPiece(t, cx, cy, r)
    cercle(t, cx, cy + 1, r, #C99A19)
    cercle(t, cx, cy, r, #FFC93C)
    cercle(t, cx, cy, r * 3 / 5, #FFE9A8)
fin procédure

@ LA POUBELLE
procédure dessinerPoubelle(t, x, y, actif)
    fond est un texte
    cIcone est un texte
    si actif = vrai alors
        fond vaut #55565C
        cIcone vaut #E8EAF0
    sinon
        fond vaut #414248
        cIcone vaut #6B6D75
    fin si
    rectangle_arrondi(t, x, y + 2, 44, 48, 11, rgba(0, 0, 0, 0.3))
    rectangle_arrondi(t, x, y, 44, 48, 11, fond)
    rectangle_arrondi(t, x + 8, y + 11, 28, 4, 2, cIcone)
    rectangle_arrondi(t, x + 17, y + 7, 10, 4, 2, cIcone)
    rectangle_arrondi(t, x + 11, y + 17, 22, 23, 4, cIcone)
    rectangle(t, x + 17, y + 22, 3, 13, fond)
    rectangle(t, x + 25, y + 22, 3, 13, fond)
fin procédure

@ LE BOUTON DE ROTATION
procédure dessinerRotation(t, x, y)
    rectangle_arrondi(t, x, y + 2, 44, 48, 11, rgba(0, 0, 0, 0.3))
    rectangle_arrondi(t, x, y, 44, 48, 11, #55565C)
    // Anneau ouvert en haut à droite
    cercle(t, x + 22, y + 26, 14, #E8EAF0)
    cercle(t, x + 22, y + 26, 8, #55565C)
    rectangle(t, x + 22, y + 10, 15, 16, #55565C)
    // Pointe de la flèche
    pF est un tableau
    pF ajoute x + 18
    pF ajoute y + 10
    pF ajoute x + 34
    pF ajoute y + 10
    pF ajoute x + 26
    pF ajoute y + 24
    polygone(t, pF, #E8EAF0)
fin procédure

@ BOUCLE PRINCIPALE

tant que vrai

    // --- Forme du domino en main selon son orientation ---
    v1 est un nombre
    v2 est un nombre
    dl est un nombre
    dc est un nombre
    si orient = 0 alors
        v1 vaut valA
        v2 vaut valB
        dl vaut 0
        dc vaut 1
    sinon si orient = 1 alors
        v1 vaut valA
        v2 vaut valB
        dl vaut 1
        dc vaut 0
    sinon si orient = 2 alors
        v1 vaut valB
        v2 vaut valA
        dl vaut 0
        dc vaut 1
    sinon
        v1 vaut valB
        v2 vaut valA
        dl vaut 1
        dc vaut 0
    fin si

    // ================= AFFICHAGE =================
    effacer(graph)
    contour(graph, #0)
    remplir(graph, #2E2F34)

    // ---------- BANDEAU DU HAUT ----------
    rectangle_arrondi(graph, 12, 8, 376, 58, 14, #35363C)
    appelle dessinerPiece(graph, 34, 30, 11)
    label(graph, 50, 36, "" + pieces, #FFFFFF, 15)

    label(graph, 150, 38, "" + score, #FF9A2B, 22)
    label(graph, 244, 36, "??", #FFFFFF, 15)
    label(graph, 268, 37, "" + record, #FF9A2B, 18)

    rectangle_arrondi(graph, 344, 14, 34, 34, 9, #4B4C52)
    rectangle_arrondi(graph, 353, 21, 5, 20, 2, #E8EAF0)
    rectangle_arrondi(graph, 364, 21, 5, 20, 2, #E8EAF0)

    label(graph, 26, 60, message, #8A8C94, 10)

    // ---------- LE PLATEAU ----------
    rectangle_arrondi(graph, 36, 72, 328, 380, 16, #35363C)
    pour i de 0 à LIGS
        pour j de 0 à COLS
            xk est un nombre
            yk est un nombre
            xk vaut BX + j * CS + 3
            yk vaut BY + i * CS + 3
            si plateau[i][j] = 0 alors
                appelle dessinerCaseVide(graph, xk, yk, 46)
            sinon
                appelle dessinerDomino(graph, xk, yk, 46, plateau[i][j], vrai)
            fin si
        fin pour
    fin pour

    // ---------- ZONE DE JEU DU BAS ----------
    rectangle_arrondi(graph, 12, 458, 376, 84, 14, #35363C)

    actifP est un booléen
    actifP vaut faux
    si pieces >= COUT_POUBELLE alors
        actifP vaut vrai
    fin si
    appelle dessinerPoubelle(graph, 26, 466, actifP)
    appelle dessinerPiece(graph, 32, 528, 7)
    label(graph, 44, 533, "" + COUT_POUBELLE, #B9BCC4, 11)

    // Le domino en main, centré dans la zone quelle que soit son orientation
    xm est un nombre
    ym est un nombre
    si dc = 1 alors
        xm vaut 138
        ym vaut 478
    sinon
        xm vaut 160
        ym vaut 464
    fin si
    appelle dessinerCouple(graph, xm, ym, 38, v1, v2, dl, dc, vrai)

    appelle dessinerRotation(graph, 278, 466)

    // Le domino suivant, en petit
    label(graph, 338, 476, "suiv.", #8A8C94, 9)
    appelle dessinerCouple(graph, 344, 482, 18, sA, sB, 1, 0, faux)

    // ---------- SURCOUCHES ----------
    si aide = vrai alors
        rectangle(graph, 0, 0, 400, 550, rgba(0, 0, 0, 0.72))
        rectangle_arrondi(graph, 30, 110, 340, 330, 16, #4B4C52)
        label(graph, 58, 158, "COMMENT JOUER", #FFFFFF, 20)
        label(graph, 58, 200, "Chaque domino porte deux dés.", #D6D8DE, 13)
        label(graph, 58, 222, "Le bouton de droite le fait tourner", #D6D8DE, 13)
        label(graph, 58, 244, "dans les quatre positions.", #D6D8DE, 13)
        label(graph, 58, 282, "Clique sur une case libre : le premier", #D6D8DE, 13)
        label(graph, 58, 304, "dé s'y pose, le second juste à côté.", #D6D8DE, 13)
        label(graph, 58, 342, "Trois dés identiques qui se touchent", #D6D8DE, 13)
        label(graph, 58, 364, "fusionnent en un dé supérieur.", #D6D8DE, 13)
        label(graph, 58, 396, "Poubelle : " + COUT_POUBELLE + " pièces.", #D6D8DE, 13)
        label(graph, 58, 424, "Clique pour fermer", #FFC93C, 13)
    fin si

    si finPartie = vrai alors
        rectangle(graph, 0, 0, 400, 550, rgba(0, 0, 0, 0.78))
        rectangle_arrondi(graph, 40, 160, 320, 230, 16, #4B4C52)
        label(graph, 86, 212, "PLUS DE PLACE !", #FFFFFF, 24)
        label(graph, 86, 256, "Score : " + score, #FF9A2B, 20)
        label(graph, 86, 292, "Meilleur dé :", #D6D8DE, 14)
        appelle dessinerDomino(graph, 220, 268, 32, meilleurDomino, faux)
        label(graph, 86, 356, "Clique pour rejouer", #FFC93C, 15)
    fin si

    affiche graph

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

    si finPartie = vrai alors
        // ---------- NOUVELLE PARTIE ----------
        pour i de 0 à LIGS
            pour j de 0 à COLS
                plateau[i][j] vaut 0
            fin pour
        fin pour
        score vaut 0
        pieces vaut 20
        meilleurDomino vaut 1
        orient vaut 0
        derLig vaut - 1
        derCol vaut - 1
        finPartie vaut faux
        message vaut "Clique sur une case : le domino occupe deux cases"

    sinon si aide = vrai alors
        aide vaut faux

    sinon si xc >= 344 et xc <= 378 et yc >= 14 et yc <= 48 alors
        aide vaut vrai

    sinon si xc >= 278 et xc <= 322 et yc >= 466 et yc <= 514 alors
        // ---------- TOURNER LE DOMINO ----------
        orient vaut orient + 1
        si orient > 3 alors
            orient vaut 0
        fin si
        message vaut "Domino tourné"

    sinon si xc >= 26 et xc <= 70 et yc >= 466 et yc <= 514 alors
        // ---------- JETER LE DOMINO ----------
        si pieces >= COUT_POUBELLE alors
            pieces vaut pieces - COUT_POUBELLE
            valA vaut sA
            valB vaut sB
            tirage vaut hasard(1, 100)
            si tirage <= 40 alors
                sA vaut 1
            sinon si tirage <= 72 alors
                sA vaut 2
            sinon si tirage <= 90 alors
                sA vaut 3
            sinon
                sA vaut 4
            fin si
            si hasard(1, 100) <= 25 alors
                sB vaut sA
            sinon
                tirage vaut hasard(1, 100)
                si tirage <= 40 alors
                    sB vaut 1
                sinon si tirage <= 72 alors
                    sB vaut 2
                sinon si tirage <= 90 alors
                    sB vaut 3
                sinon
                    sB vaut 4
                fin si
            fin si
            message vaut "Domino jeté : - " + COUT_POUBELLE + " pièces"
        sinon
            message vaut "Il te faut " + COUT_POUBELLE + " pièces pour jeter"
        fin si

    sinon si xc >= BX et xc < BX + (COLS + 1) * CS et yc >= BY et yc < BY + (LIGS + 1) * CS alors
        // ---------- POSER LE DOMINO SUR DEUX CASES ----------
        l1 est un nombre
        c1 est un nombre
        c1 vaut arrondi_inferieur((xc - BX) / CS)
        l1 vaut arrondi_inferieur((yc - BY) / CS)
        c1 vaut limiter(c1, 0, COLS)
        l1 vaut limiter(l1, 0, LIGS)

        l2 est un nombre
        c2 est un nombre
        l2 vaut l1 + dl
        c2 vaut c1 + dc

        si l2 > LIGS ou c2 > COLS alors
            message vaut "Le domino dépasse du plateau : tourne-le"
        sinon si plateau[l1][c1] <> 0 ou plateau[l2][c2] <> 0 alors
            message vaut "Il faut deux cases libres côte à côte"
        sinon
            plateau[l1][c1] vaut v1
            plateau[l2][c2] vaut v2
            derLig vaut l1
            derCol vaut c1
            der2Lig vaut l2
            der2Col vaut c2
            message vaut "Domino posé"

            // Le suivant passe en main, et on tire un nouveau suivant
            valA vaut sA
            valB vaut sB
            tirage vaut hasard(1, 100)
            si tirage <= 40 alors
                sA vaut 1
            sinon si tirage <= 72 alors
                sA vaut 2
            sinon si tirage <= 90 alors
                sA vaut 3
            sinon
                sA vaut 4
            fin si
            si hasard(1, 100) <= 25 alors
                sB vaut sA
            sinon
                tirage vaut hasard(1, 100)
                si tirage <= 40 alors
                    sB vaut 1
                sinon si tirage <= 72 alors
                    sB vaut 2
                sinon si tirage <= 90 alors
                    sB vaut 3
                sinon
                    sB vaut 4
                fin si
            fin si

            // ---------- FUSIONS EN CHAÎNE ----------
            chaine est un nombre
            chaine vaut 0
            encore est un booléen
            encore vaut vrai

            tant que encore = vrai
                encore vaut faux

                pour i de 0 à LIGS
                    pour j de 0 à COLS
                        si plateau[i][j] > 0 et encore = faux alors
                            val est un nombre
                            val vaut plateau[i][j]

                            // --- Groupe connecté de même valeur, propagé par vagues ---
                            pour r de 0 à LIGS
                                pour c de 0 à COLS
                                    marque[r][c] vaut faux
                                fin pour
                            fin pour
                            marque[i][j] vaut vrai

                            propage est un booléen
                            propage vaut vrai
                            tant que propage = vrai
                                propage vaut faux
                                pour r de 0 à LIGS
                                    pour c de 0 à COLS
                                        si marque[r][c] = vrai alors
                                            pour d de 0 à 3
                                                dLi est un nombre
                                                dCo est un nombre
                                                si d = 0 alors
                                                    dLi vaut 0 - 1
                                                    dCo vaut 0
                                                sinon si d = 1 alors
                                                    dLi vaut 1
                                                    dCo vaut 0
                                                sinon si d = 2 alors
                                                    dLi vaut 0
                                                    dCo vaut 0 - 1
                                                sinon
                                                    dLi vaut 0
                                                    dCo vaut 1
                                                fin si
                                                vr est un nombre
                                                vc est un nombre
                                                vr vaut r + dLi
                                                vc vaut c + dCo
                                                si vr >= 0 et vr <= LIGS et vc >= 0 et vc <= COLS alors
                                                    si plateau[vr][vc] = val et marque[vr][vc] = faux alors
                                                        marque[vr][vc] vaut vrai
                                                        propage vaut vrai
                                                    fin si
                                                fin si
                                            fin pour
                                        fin si
                                    fin pour
                                fin pour
                            fin tant que

                            // --- Le groupe est-il assez grand ? ---
                            nbGroupe est un nombre
                            nbGroupe vaut 0
                            ligNaiss est un nombre
                            colNaiss est un nombre
                            ligNaiss vaut i
                            colNaiss vaut j
                            pour r de 0 à LIGS
                                pour c de 0 à COLS
                                    si marque[r][c] = vrai alors
                                        nbGroupe ajoute 1
                                        si r > ligNaiss alors
                                            ligNaiss vaut r
                                            colNaiss vaut c
                                        fin si
                                    fin si
                                fin pour
                            fin pour

                            // Le dé supérieur naît de préférence sur une case que tu viens de jouer
                            si der2Lig >= 0 alors
                                si marque[der2Lig][der2Col] = vrai alors
                                    ligNaiss vaut der2Lig
                                    colNaiss vaut der2Col
                                fin si
                            fin si
                            si derLig >= 0 alors
                                si marque[derLig][derCol] = vrai alors
                                    ligNaiss vaut derLig
                                    colNaiss vaut derCol
                                fin si
                            fin si

                            si nbGroupe >= 3 alors
                                pour r de 0 à LIGS
                                    pour c de 0 à COLS
                                        si marque[r][c] = vrai alors
                                            plateau[r][c] vaut 0
                                        fin si
                                    fin pour
                                fin pour

                                si val < 6 alors
                                    plateau[ligNaiss][colNaiss] vaut val + 1
                                    derLig vaut ligNaiss
                                    derCol vaut colNaiss
                                    der2Lig vaut - 1
                                    der2Col vaut - 1
                                    si val + 1 > meilleurDomino alors
                                        meilleurDomino vaut val + 1
                                    fin si
                                sinon
                                    derLig vaut - 1
                                    derCol vaut - 1
                                    der2Lig vaut - 1
                                    der2Col vaut - 1
                                fin si

                                gain est un nombre
                                gain vaut val * nbGroupe * 10 * (chaine + 1)
                                score ajoute gain
                                chaine ajoute 1

                                si val >= 4 alors
                                    pieces ajoute 3
                                sinon
                                    pieces ajoute 1
                                fin si

                                si val = 6 alors
                                    message vaut "Six sublimé ! + " + gain + " points"
                                sinon si chaine > 1 alors
                                    message vaut "Réaction en chaîne x" + chaine + " : + " + gain
                                sinon
                                    message vaut "Fusion de " + nbGroupe + " dés : + " + gain
                                fin si

                                encore vaut vrai
                            fin si
                        fin si
                    fin pour
                fin pour
            fin tant que

            // ---------- RESTE-T-IL DEUX CASES LIBRES CÔTE À CÔTE ? ----------
            placeRestante est un booléen
            placeRestante vaut faux
            pour i de 0 à LIGS
                pour j de 0 à COLS
                    si plateau[i][j] = 0 alors
                        si j < COLS alors
                            si plateau[i][j + 1] = 0 alors
                                placeRestante vaut vrai
                            fin si
                        fin si
                        si i < LIGS alors
                            si plateau[i + 1][j] = 0 alors
                                placeRestante vaut vrai
                            fin si
                        fin si
                    fin si
                fin pour
            fin pour
            si placeRestante = faux alors
                finPartie vaut vrai
                si score > record alors
                    record vaut score
                fin si
            fin si
        fin si
    fin si
fin tant que
5,0 / 5 (1 avis)
Bridge Constructor : Analyse et présentation d’un simulateur de construction de ponts
Article

Bridge Constructor

Analyse et présentation d’un simulateur de construction de ponts

Lire l'articleReplier l'article

Le programme présenté est un simulateur de construction de ponts où le joueur doit relier deux rives en respectant un budget, des contraintes physiques et la résistance des matériaux.

Il s’agit d’un moteur complet : gestion des points d’ancrage, des poutres, de la route, de la physique, du budget, des tests de résistance et de l’interface graphique.

L’objectif du jeu :

  • Relie la route de gauche à celle de droite

Structure générale du programme

Le code repose sur une boucle principale qui gère :

  • la mise en place du niveau
  • l’affichage du décor, du pont et de l’interface
  • les interactions du joueur (clics, choix de points, ajout de segments)
  • le test de résistance du pont
  • la progression entre les niveaux

Le programme utilise de nombreux tableaux pour stocker l’état du chantier : points, segments, tensions, chemin de la route, etc.

Décor et niveaux

Chaque niveau possède :

  • un nombre de colonnes (points d’ancrage)
  • une position d’arrivée
  • un éventuel pilier rocheux
  • un budget
  • un poids de camion

La fonction reglageNiveau définit ces paramètres.
Par exemple, pour le niveau 1 :

nbCol vaut 7… budg vaut 430… chg vaut 3

Chaque niveau possède aussi une consigne pédagogique, comme :

  • Le ravin s’élargit. Un tablier plat s’affaisse : triangule-le.

Construction du pont

Le joueur peut poser deux types de segments :

  • Route (genre = 1) : plus coûteuse, supporte le camion
  • Poutre (genre = 2) : moins chère, sert de structure

Le coût dépend de la longueur :

  • La route coûte plus cher que la poutre

Le programme empêche les segments trop longs :

  • Segment trop long : relie des points voisins

Simulation physique

La physique est un élément central du programme.
Chaque pas de simulation applique :

  • la gravité
  • un amortissement (xFrein)
  • la relaxation des poutres (xRelax)
  • la rupture si la tension dépasse xRupture

Le document précise :

si absolue(poutreTens[k]) > xRupture alors poutreVie[k] vaut faux

Le camion ajoute une charge sur le point qu’il traverse :

sy[noeudCharge] vaut sy[noeudCharge] + etat[9]

Test de résistance Lors du test :

  • Le programme cherche un chemin de route continu.
  • Le camion avance point par point.
  • La physique est simulée à chaque étape.
  • Les poutres cassées sont comptabilisées.
  • Un verdict est affiché.

Les trois verdicts possibles :

Pont rompu
"LE PONT S EST ROMPU… triangule davantage ta structure"

Pont solide mais trop cher
"Budget dépassé… une simple vis"

Pont homologué
"PONT HOMOLOGUÉ ! Coût… sur… autorisés"

Interface utilisateur

Le programme dessine :

  • le décor (falaises, pilier rocheux)
  • le pont
  • le camion
  • un bandeau d’information : niveau, budget, poids du camion, nombre de segments
  • des boutons : Route, Poutre, Annuler, Effacer, Tester, Niveau+

Un moteur complet de simulation

Ce programme est remarquable par :

  • la richesse de ses mécaniques
  • la gestion réaliste des contraintes physiques
  • la pédagogie des niveaux
  • la clarté de son architecture
  • l’intégration d’une interface graphique complète

Il constitue un véritable mini‑jeu de construction de ponts, entièrement codé dans un style procédural, avec une logique physique cohérente et des retours visuels immédiats.

Programme :

@ Configuration
scene est une toile
dimension(scene, 640, 430)

// Réglages de la physique
xGravite est un nombre
xGravite vaut 45 / 100
xFrein est un nombre
xFrein vaut 985 / 1000
xRelax est un nombre
xRelax vaut 1 / 2
xRupture est un nombre
xRupture vaut 12 / 100

// État général, rangé dans un tableau :
//   0 budget dépensé      1 mode (1 route, 2 poutre)
//   2 point choisi        3 nombre de segments
//   4 dernier résultat    5 nombre de points
//   6 budget maximal      7 poutres cassées
//   8 niveau courant      9 poids du camion
//  10 point de départ    11 point d arrivée
//  12 colonne du pilier (-1 si aucun)   13 nombre de colonnes
//  14 abscisse du bord gauche de la grille
//  15 longueur utile du chemin trouvé
etat est un tableau
pour k de 0 à 15
  etat ajoute 0
fin pour
etat[1] vaut 1
etat[2] vaut - 1
etat[8] vaut 1

message est un texte
message vaut "Relie la route de gauche à celle de droite"

@ Les tableaux du chantier
ptX est un tableau
ptY est un tableau
ptFixe est un tableau

poutreA est un tableau
poutreB est un tableau
poutreType est un tableau
poutreVie est un tableau
poutreRepos est un tableau
poutreTens est un tableau

sx est un tableau
sy est un tableau
oldx est un tableau
oldy est un tableau

chemin est un tableau
venantDe est un tableau
atteint est un tableau

@ Le nom du niveau
fonction nomDuNiveau(niv)
  si niv = 1 alors
    retourne "Le petit ravin"
  fin si
  si niv = 2 alors
    retourne "La brèche"
  fin si
  si niv = 3 alors
    retourne "Le dénivelé"
  fin si
  si niv = 4 alors
    retourne "Le pilier de roche"
  fin si
  si niv = 5 alors
    retourne "La longue portée"
  fin si
  si niv = 6 alors
    retourne "Le convoi lourd"
  fin si
  si niv = 7 alors
    retourne "Le grand canyon"
  fin si
  si niv = 8 alors
    retourne "L épreuve"
  fin si
  retourne "Chantier expert " + convertir_texte(niv - 8)
fin fonction

@ La consigne du niveau
fonction consigneDuNiveau(niv)
  si niv = 1 alors
    retourne "Une portée courte et un budget large : essaie un tablier soutenu par-dessous."
  fin si
  si niv = 2 alors
    retourne "Le ravin s élargit. Un tablier plat s affaisse : triangule-le."
  fin si
  si niv = 3 alors
    retourne "L arrivée est en hauteur. La route doit monter jusqu au point du haut."
  fin si
  si niv = 4 alors
    retourne "Un pilier de roche au milieu : appuie-toi dessus, c est un ancrage gratuit."
  fin si
  si niv = 5 alors
    retourne "Longue portée sans appui. Les triangles hauts travaillent mieux."
  fin si
  si niv = 6 alors
    retourne "Camion lourd. Chaque poutre doit être courte pour résister."
  fin si
  si niv = 7 alors
    retourne "Le grand canyon, et l arrivée en hauteur. Budget confortable."
  fin si
  retourne "Portée maximale, camion très lourd, budget serré. Bonne chance."
fin fonction

@ Le coût d un segment
// La route coûte plus cher que la poutre : c est ce qui pousse
// à ne poser du tablier que là où le camion doit passer.
fonction coutDuSegment(dlong, genre)
  si genre = 1 alors
    retourne arrondi(dlong * 40 / 100)
  fin si
  retourne arrondi(dlong * 24 / 100)
fin fonction

@ La couleur d une poutre selon sa tension
fonction couleurDeTension(tn, genre)
  ta est un nombre
  ta vaut absolue(tn)
  si ta > 9 / 100 alors
    retourne #7F1D1D
  fin si
  si ta > 6 / 100 alors
    retourne #EF4444
  fin si
  si ta > 3 / 100 alors
    retourne #F59E0B
  fin si
  si genre = 1 alors
    retourne #E2E8F0
  fin si
  retourne #38BDF8
fin fonction

@ Les réglages de chaque niveau
// Une fonction ne touche à aucune variable extérieure : elle se
// contente de renvoyer un nombre. quoi = 1 colonnes, 2 rangée
// d arrivée, 3 colonne du pilier, 4 budget, 5 poids du camion.
fonction reglageNiveau(niv, quoi)
  nbCol est un nombre
  ligneArr est un nombre
  colPil est un nombre
  budg est un nombre
  chg est un nombre

  si niv = 1 alors
    nbCol vaut 7
    ligneArr vaut 1
    colPil vaut - 1
    budg vaut 430
    chg vaut 3
  sinon si niv = 2 alors
    nbCol vaut 9
    ligneArr vaut 1
    colPil vaut - 1
    budg vaut 520
    chg vaut 4
  sinon si niv = 3 alors
    nbCol vaut 9
    ligneArr vaut 0
    colPil vaut - 1
    budg vaut 560
    chg vaut 4
  sinon si niv = 4 alors
    nbCol vaut 11
    ligneArr vaut 1
    colPil vaut 5
    budg vaut 560
    chg vaut 5
  sinon si niv = 5 alors
    nbCol vaut 11
    ligneArr vaut 1
    colPil vaut - 1
    budg vaut 640
    chg vaut 5
  sinon si niv = 6 alors
    nbCol vaut 11
    ligneArr vaut 1
    colPil vaut 5
    budg vaut 560
    chg vaut 7
  sinon si niv = 7 alors
    nbCol vaut 13
    ligneArr vaut 0
    colPil vaut - 1
    budg vaut 720
    chg vaut 6
  sinon si niv = 8 alors
    nbCol vaut 13
    ligneArr vaut 1
    colPil vaut - 1
    budg vaut 660
    chg vaut 8
  sinon
    nbCol vaut 13
    ligneArr vaut 1
    colPil vaut - 1
    budg vaut limiter(660 - (niv - 8) * 25, 420, 660)
    chg vaut 8 + (niv - 8)
  fin si

  si quoi = 1 alors
    retourne nbCol
  fin si
  si quoi = 2 alors
    retourne ligneArr
  fin si
  si quoi = 3 alors
    retourne colPil
  fin si
  si quoi = 4 alors
    retourne budg
  fin si
  retourne chg
fin fonction

@ Décor : la gorge, les falaises, le pilier éventuel
procédure dessinerDecor(t)
  remplir(t, #16233A)
  rectangle(t, 0, 30, 640, 200, #1E3A5F)
  rectangle(t, 0, 300, 640, 80, #0C1526)

  xG est un nombre
  xD est un nombre
  xG vaut etat[14]
  xD vaut etat[14] + (etat[13] - 1) * 44

  yD est un nombre
  yD vaut ptY[etat[11]]

  // Falaise de gauche
  rectangle(t, 0, 200, xG + 14, 180, #4B5563)
  rectangle(t, 0, 196, xG + 14, 8, #6B7280)
  rectangle(t, 0, 196, xG - 8, 6, #1F2937)

  // Falaise de droite, à la hauteur de l arrivée
  rectangle(t, xD - 14, yD - 10, 640 - xD + 14, 390 - yD, #4B5563)
  rectangle(t, xD - 14, yD - 14, 640 - xD + 14, 8, #6B7280)
  rectangle(t, xD + 8, yD - 14, 640 - xD, 6, #1F2937)

  // Pilier de roche, quand le niveau en comporte un
  si etat[12] >= 0 alors
    xP est un nombre
    xP vaut etat[14] + etat[12] * 44
    rectangle(t, xP - 13, 270, 26, 110, #4B5563)
    rectangle(t, xP - 16, 264, 32, 8, #6B7280)
  fin si
fin procédure

@ Le pont dans son état courant
procédure dessinerPont(t, avecTension)
  pour k de 0 à etat[3] - 1
    si poutreVie[k] = vrai alors
      ia est un nombre
      ib est un nombre
      ia vaut poutreA[k]
      ib vaut poutreB[k]
      coul est un texte
      si avecTension = vrai alors
        coul vaut couleurDeTension(poutreTens[k], poutreType[k])
      sinon si poutreType[k] = 1 alors
        coul vaut #E2E8F0
      sinon
        coul vaut #38BDF8
      fin si
      ep est un nombre
      si poutreType[k] = 1 alors
        ep vaut 6
      sinon
        ep vaut 3
      fin si
      ligne(t, sx[ia], sy[ia], sx[ib], sy[ib], coul, ep)
    fin si
  fin pour

  pour p de 0 à etat[5] - 1
    si ptFixe[p] = vrai alors
      cercle(t, sx[p], sy[p], 5, #FBBF24)
    sinon
      cercle(t, sx[p], sy[p], 4, #94A3B8)
    fin si
  fin pour

  si etat[2] >= 0 alors
    cercle(t, sx[etat[2]], sy[etat[2]], 9, #FDE68A)
    cercle(t, sx[etat[2]], sy[etat[2]], 5, #16233A)
  fin si
fin procédure

@ Le camion, dont la taille dit le poids
procédure dessinerCamion(t, cx, cy, poids)
  larg est un nombre
  larg vaut 34 + poids * 2
  rectangle_arrondi(t, cx - larg / 2, cy - 22, larg, 16, 3, #DC2626)
  rectangle_arrondi(t, cx - larg / 2, cy - 30, 18, 10, 3, #B91C1C)
  rectangle(t, cx - larg / 2 + 3, cy - 28, 10, 6, #BFDBFE)
  cercle(t, cx - larg / 2 + 9, cy - 4, 5, #0F172A)
  cercle(t, cx + larg / 2 - 9, cy - 4, 5, #0F172A)
fin procédure

@ Le bandeau d information
procédure dessinerInterface(t, msg)
  rectangle(t, 0, 0, 640, 30, #0B1020)
  label(t, 12, 21, "NIVEAU " + convertir_texte(etat[8]) + " - " + nomDuNiveau(etat[8]), #FDE68A, 14)

  coulBudget est un texte
  si etat[0] > etat[6] alors
    coulBudget vaut #EF4444
  sinon
    coulBudget vaut #4ADE80
  fin si
  label(t, 300, 21, "Budget " + convertir_texte(etat[0]) + " / " + convertir_texte(etat[6]), coulBudget, 13)
  label(t, 470, 21, "Camion " + convertir_texte(etat[9]) + " t", #93C5FD, 12)
  label(t, 560, 21, "Seg. " + convertir_texte(etat[3]), #64748B, 12)

  coulR est un texte
  coulP est un texte
  si etat[1] = 1 alors
    coulR vaut #E2E8F0
    coulP vaut #334155
  sinon
    coulR vaut #334155
    coulP vaut #38BDF8
  fin si
  rectangle_arrondi(t, 16, 388, 92, 32, 8, coulR)
  label(t, 34, 409, "ROUTE", #0F172A, 13)
  rectangle_arrondi(t, 116, 388, 92, 32, 8, coulP)
  label(t, 132, 409, "POUTRE", #0F172A, 13)
  rectangle_arrondi(t, 216, 388, 92, 32, 8, #64748B)
  label(t, 230, 409, "ANNULER", #FFFFFF, 12)
  rectangle_arrondi(t, 316, 388, 92, 32, 8, #64748B)
  label(t, 330, 409, "EFFACER", #FFFFFF, 12)
  rectangle_arrondi(t, 424, 388, 100, 32, 8, #16A34A)
  label(t, 444, 409, "TESTER", #FFFFFF, 14)
  rectangle_arrondi(t, 532, 388, 92, 32, 8, #7C3AED)
  label(t, 546, 409, "NIVEAU +", #FFFFFF, 12)

  label(t, 12, 378, msg, #FDE68A, 11)
fin procédure

@ Remise des points à leur place
procédure replacerPoints()
  pour p de 0 à etat[5] - 1
    sx[p] vaut ptX[p]
    sy[p] vaut ptY[p]
    oldx[p] vaut ptX[p]
    oldy[p] vaut ptY[p]
  fin pour
  pour k de 0 à etat[3] - 1
    poutreVie[k] vaut vrai
    poutreTens[k] vaut 0
  fin pour
  etat[7] vaut 0
fin procédure

@ Un pas de simulation
procédure unPasDePhysique(noeudCharge)
  pour p de 0 à etat[5] - 1
    si ptFixe[p] = faux alors
      vxp est un nombre
      vyp est un nombre
      vxp vaut (sx[p] - oldx[p]) * xFrein
      vyp vaut (sy[p] - oldy[p]) * xFrein
      oldx[p] vaut sx[p]
      oldy[p] vaut sy[p]
      sx[p] vaut sx[p] + vxp
      sy[p] vaut sy[p] + vyp + xGravite
    fin si
  fin pour

  // Le poids du camion s ajoute au point qu il traverse
  si noeudCharge >= 0 alors
    si ptFixe[noeudCharge] = faux alors
      sy[noeudCharge] vaut sy[noeudCharge] + etat[9]
    fin si
  fin si

  pour tour de 1 à 6
    pour k de 0 à etat[3] - 1
      si poutreVie[k] = vrai alors
        ia est un nombre
        ib est un nombre
        ia vaut poutreA[k]
        ib vaut poutreB[k]
        ex est un nombre
        ey est un nombre
        ex vaut sx[ib] - sx[ia]
        ey vaut sy[ib] - sy[ia]
        dd est un nombre
        dd vaut hypotenuse(ex, ey)
        si dd > 0 alors
          ecart est un nombre
          ecart vaut (dd - poutreRepos[k]) / dd * xRelax
          si ptFixe[ia] = faux alors
            sx[ia] vaut sx[ia] + ex * ecart
            sy[ia] vaut sy[ia] + ey * ecart
          fin si
          si ptFixe[ib] = faux alors
            sx[ib] vaut sx[ib] - ex * ecart
            sy[ib] vaut sy[ib] - ey * ecart
          fin si
        fin si
      fin si
    fin pour
  fin pour

  pour k de 0 à etat[3] - 1
    si poutreVie[k] = vrai alors
      ja est un nombre
      jb est un nombre
      ja vaut poutreA[k]
      jb vaut poutreB[k]
      fx est un nombre
      fy est un nombre
      fx vaut sx[jb] - sx[ja]
      fy vaut sy[jb] - sy[ja]
      dl est un nombre
      dl vaut hypotenuse(fx, fy)
      poutreTens[k] vaut (dl - poutreRepos[k]) / poutreRepos[k]
      si absolue(poutreTens[k]) > xRupture alors
        poutreVie[k] vaut faux
        etat[7] vaut etat[7] + 1
      fin si
    fin si
  fin pour
fin procédure

@ Recherche du chemin de la route
// Le tableau chemin est PRÉ-ALLOUÉ et sa longueur utile est
// rangée dans etat[15]. Une procédure ne peut pas ajouter
// d élément à un tableau global : elle n écrit que par index.
procédure chercherRoute()
  pour p de 0 à etat[5] - 1
    atteint[p] vaut faux
    venantDe[p] vaut - 1
  fin pour
  etat[15] vaut 0

  atteint[etat[10]] vaut vrai
  encore est un booléen
  encore vaut vrai
  tant que encore = vrai
    encore vaut faux
    pour k de 0 à etat[3] - 1
      si poutreType[k] = 1 et poutreVie[k] = vrai alors
        ia est un nombre
        ib est un nombre
        ia vaut poutreA[k]
        ib vaut poutreB[k]
        si atteint[ia] = vrai et atteint[ib] = faux alors
          atteint[ib] vaut vrai
          venantDe[ib] vaut ia
          encore vaut vrai
        fin si
        si atteint[ib] = vrai et atteint[ia] = faux alors
          atteint[ia] vaut vrai
          venantDe[ia] vaut ib
          encore vaut vrai
        fin si
      fin si
    fin pour
  fin tant que

  si atteint[etat[11]] = vrai alors
    // On compte d abord la longueur du chemin
    lg est un nombre
    lg vaut 0
    courant est un nombre
    courant vaut etat[11]
    tant que courant <> - 1
      lg vaut lg + 1
      courant vaut venantDe[courant]
    fin tant que
    etat[15] vaut lg

    // puis on le remplit de la fin vers le début
    idx est un nombre
    idx vaut lg - 1
    courant vaut etat[11]
    tant que courant <> - 1
      chemin[idx] vaut courant
      idx vaut idx - 1
      courant vaut venantDe[courant]
    fin tant que
  fin si
fin procédure


@ ===========================================================
@ Boucle principale
@ ===========================================================
aCharger est un booléen
aCharger vaut vrai

enMarche est un booléen
enMarche vaut vrai

tant que enMarche = vrai

  // =========================================================
  //   MISE EN PLACE DU NIVEAU
  //   Elle se fait ICI, au niveau principal : seule une
  //   instruction de ce niveau peut vider un tableau global
  //   ou y ajouter des éléments.
  // =========================================================
  si aCharger = vrai alors
    aCharger vaut faux

    nbColN est un nombre
    ligneArrN est un nombre
    colPilN est un nombre
    nbColN vaut reglageNiveau(etat[8], 1)
    ligneArrN vaut reglageNiveau(etat[8], 2)
    colPilN vaut reglageNiveau(etat[8], 3)

    etat[13] vaut nbColN
    etat[12] vaut colPilN
    etat[6] vaut reglageNiveau(etat[8], 4)
    etat[9] vaut reglageNiveau(etat[8], 5)
    etat[14] vaut arrondi((640 - (nbColN - 1) * 44) / 2)

    vide ptX
    vide ptY
    vide ptFixe
    pour cN de 0 à nbColN - 1
      pour rN de 0 à 2
        ptX ajoute etat[14] + cN * 44
        ptY ajoute 150 + rN * 60
        si cN = 0 ou cN = nbColN - 1 alors
          ptFixe ajoute vrai
        sinon si cN = colPilN et rN = 2 alors
          ptFixe ajoute vrai
        sinon
          ptFixe ajoute faux
        fin si
      fin pour
    fin pour

    etat[5] vaut longueur(ptX)
    etat[10] vaut 1
    etat[11] vaut (nbColN - 1) * 3 + ligneArrN

    vide poutreA
    vide poutreB
    vide poutreType
    vide poutreVie
    vide poutreRepos
    vide poutreTens
    etat[0] vaut 0
    etat[2] vaut - 1
    etat[3] vaut 0
    etat[7] vaut 0
    etat[15] vaut 0

    vide sx
    vide sy
    vide oldx
    vide oldy
    vide venantDe
    vide atteint
    vide chemin
    pour pN de 0 à etat[5] - 1
      sx ajoute ptX[pN]
      sy ajoute ptY[pN]
      oldx ajoute ptX[pN]
      oldy ajoute ptY[pN]
      venantDe ajoute - 1
      atteint ajoute faux
      chemin ajoute 0
    fin pour

    message vaut consigneDuNiveau(etat[8])
  fin si

  effacer(scene)
  appelle dessinerDecor(scene)
  appelle dessinerPont(scene, faux)
  appelle dessinerInterface(scene, message)
  affiche scene

  xc est un nombre
  yc est un nombre
  cliquer scene dans xc, yc

  si clique(xc, yc, 16, 388, 92, 32) alors
    etat[1] vaut 1
    etat[2] vaut - 1
    message vaut "Mode route : le camion ne roule que sur ce type de segment"

  sinon si clique(xc, yc, 116, 388, 92, 32) alors
    etat[1] vaut 2
    etat[2] vaut - 1
    message vaut "Mode poutre : moins cher, sert à soutenir la route"

  sinon si clique(xc, yc, 216, 388, 92, 32) alors
    si etat[3] > 0 alors
      dernierK est un nombre
      dernierK vaut etat[3] - 1
      exA est un nombre
      eyA est un nombre
      exA vaut ptX[poutreB[dernierK]] - ptX[poutreA[dernierK]]
      eyA vaut ptY[poutreB[dernierK]] - ptY[poutreA[dernierK]]
      etat[0] vaut etat[0] - coutDuSegment(hypotenuse(exA, eyA), poutreType[dernierK])
      poutreA supprime dernierK
      poutreB supprime dernierK
      poutreType supprime dernierK
      poutreVie supprime dernierK
      poutreRepos supprime dernierK
      poutreTens supprime dernierK
      etat[3] vaut etat[3] - 1
      appelle replacerPoints()
      message vaut "Dernier segment retiré"
    fin si

  sinon si clique(xc, yc, 316, 388, 92, 32) alors
    aCharger vaut vrai
    message vaut "Chantier remis à zéro"

  sinon si clique(xc, yc, 532, 388, 92, 32) alors
    etat[8] vaut etat[8] + 1
    aCharger vaut vrai

  sinon si clique(xc, yc, 424, 388, 100, 32) alors
    // =====================================================
    //   LE TEST DE RÉSISTANCE
    // =====================================================
    appelle chercherRoute()

    si etat[15] = 0 alors
      message vaut "La route ne relie pas encore les deux rives"
    sinon
      appelle replacerPoints()
      images est un tableau
      vide images

      pour tour de 1 à 12
        appelle unPasDePhysique(- 1)
      fin pour

      etape est un nombre
      etape vaut 0
      tant que etape < etat[15]
        noeud est un nombre
        noeud vaut chemin[etape]
        appelle unPasDePhysique(noeud)
        appelle unPasDePhysique(noeud)

        effacer(scene)
        appelle dessinerDecor(scene)
        appelle dessinerPont(scene, vrai)
        appelle dessinerCamion(scene, sx[noeud], sy[noeud], etat[9])
        appelle dessinerInterface(scene, "Passage du camion... poutres cassées : " + convertir_texte(etat[7]))
        images ajoute scene

        etape vaut etape + 1
      fin tant que

      pour tour de 1 à 8
        appelle unPasDePhysique(- 1)
        effacer(scene)
        appelle dessinerDecor(scene)
        appelle dessinerPont(scene, vrai)
        appelle dessinerInterface(scene, "Fin du passage - appuie sur C ensuite")
        images ajoute scene
      fin pour

      // =====================================================
      //   LE VERDICT, peint sur les dernières images
      // =====================================================
      appelle chercherRoute()

      titreFin est un texte
      ligne1Fin est un texte
      ligne2Fin est un texte
      coulFin est un texte
      coulTitre est un texte
      reussi est un booléen
      reussi vaut faux

      si etat[15] = 0 alors
        coulFin vaut #7F1D1D
        coulTitre vaut #FECACA
        titreFin vaut "LE PONT S EST ROMPU"
        ligne1Fin vaut convertir_texte(etat[7]) + " poutres ont cédé sous la charge"
        ligne2Fin vaut "Appuie sur C : triangule davantage ta structure"
        message vaut "Effondrement : renforce la structure"
      sinon si etat[0] > etat[6] alors
        coulFin vaut #78350F
        coulTitre vaut #FDE68A
        titreFin vaut "IL TIENT, MAIS..."
        ligne1Fin vaut "Budget dépassé de " + convertir_texte(etat[0] - etat[6]) + " EUR"
        ligne2Fin vaut "Note : une simple vis. Appuie sur C pour recommencer."
        message vaut "Pont solide mais trop cher : recommence"
      sinon
        reussi vaut vrai
        coulFin vaut #14532D
        coulTitre vaut #86EFAC
        titreFin vaut "PONT HOMOLOGUÉ !"
        ligne1Fin vaut "Coût " + convertir_texte(etat[0]) + " EUR sur " + convertir_texte(etat[6]) + " autorisés"
        ligne2Fin vaut "Appuie sur C pour le niveau suivant"
        message vaut "Bravo, chantier suivant"
      fin si

      pour tour de 1 à 6
        effacer(scene)
        appelle dessinerDecor(scene)
        appelle dessinerPont(scene, vrai)
        rectangle_arrondi(scene, 110, 130, 420, 120, 14, coulFin)
        label(scene, 140, 175, titreFin, coulTitre, 24)
        label(scene, 140, 210, ligne1Fin, #FFFFFF, 14)
        label(scene, 140, 234, ligne2Fin, #FDE68A, 13)
        appelle dessinerInterface(scene, message)
        images ajoute scene
      fin pour

      animation(images, 200, 0)

      toucheFin est un nombre
      appuyer ["c"] dans toucheFin

      si reussi = vrai alors
        etat[8] vaut etat[8] + 1
        aCharger vaut vrai
      sinon
        appelle replacerPoints()
      fin si
    fin si

  sinon
    // ---------- Clic sur un point de construction ----------
    trouve est un nombre
    trouve vaut - 1
    pour p de 0 à etat[5] - 1
      si clique(xc, yc, ptX[p], ptY[p], 13) alors
        trouve vaut p
      fin si
    fin pour

    si trouve < 0 alors
      etat[2] vaut - 1
      message vaut "Clique sur un point gris ou jaune"

    sinon si etat[2] < 0 alors
      etat[2] vaut trouve
      message vaut "Premier point choisi, clique le second"

    sinon si etat[2] = trouve alors
      etat[2] vaut - 1
      message vaut "Choix annulé"

    sinon
      pA est un nombre
      pB est un nombre
      pA vaut etat[2]
      pB vaut trouve

      dejaLa est un booléen
      dejaLa vaut faux
      pour k de 0 à etat[3] - 1
        si poutreA[k] = pA et poutreB[k] = pB alors
          dejaLa vaut vrai
        fin si
        si poutreA[k] = pB et poutreB[k] = pA alors
          dejaLa vaut vrai
        fin si
      fin pour

      ex2 est un nombre
      ey2 est un nombre
      ex2 vaut ptX[pB] - ptX[pA]
      ey2 vaut ptY[pB] - ptY[pA]
      dlong est un nombre
      dlong vaut hypotenuse(ex2, ey2)

      si dejaLa = vrai alors
        message vaut "Ces deux points sont déjà reliés"
      sinon si dlong > 110 alors
        message vaut "Segment trop long : relie des points voisins"
      sinon
        poutreA ajoute pA
        poutreB ajoute pB
        poutreType ajoute etat[1]
        poutreVie ajoute vrai
        poutreRepos ajoute dlong
        poutreTens ajoute 0
        etat[3] vaut etat[3] + 1
        etat[0] vaut etat[0] + coutDuSegment(dlong, etat[1])
        si etat[0] > etat[6] alors
          message vaut "Attention, budget dépassé de " + convertir_texte(etat[0] - etat[6]) + " EUR"
        sinon si etat[1] = 1 alors
          message vaut "Route posée pour " + convertir_texte(coutDuSegment(dlong, 1)) + " EUR"
        sinon
          message vaut "Poutre posée pour " + convertir_texte(coutDuSegment(dlong, 2)) + " EUR"
        fin si
      fin si
      etat[2] vaut - 1
    fin si
  fin si
fin tant que
Pas encore noté
CoddyRun Visualiser et mesurer les algorithmes de tri
Article

Visualiser et mesurer les algorithmes de tri

Un programme pédagogique pour le cours de NSI

Lire l'articleReplier l'article

Ce programme CoddyRun est un outil d’apprentissage conçu pour permettre aux élèves de voir, comprendre et mesurer le fonctionnement de trois algorithmes de tri fondamentaux :

  • le tri par sélection,
  • le tri par insertion,
  • le tri fusion ascendant.

Il ne se contente pas de trier : il compte les comparaisons, compte les écritures, affiche les étapes, et propose même un banc d’essai pour confronter les mesures expérimentales aux complexités théoriques.

Une interface visuelle pour comprendre les tris

Dès l’exécution, le programme crée une toile graphique de 700 × 540 pixels et génère un tableau de 24 valeurs aléatoires.
Chaque valeur est représentée par une barre verticale, dont la hauteur correspond à la valeur du tableau.

L’écran principal affiche :

  • le nom de l’algorithme en cours,
  • sa complexité théorique,
  • le nombre de comparaisons et écritures effectuées,

l’étape en cours ou l’état « TRIÉ ».

Les barres changent de couleur selon l’algorithme et l’avancement :

  • bleu pour les éléments non traités,
  • vert pour la zone déjà triée,
  • jaune pour l’élément en cours de traitement.

Cette visualisation permet aux élèves de suivre pas à pas la logique interne de chaque tri.

Trois algorithmes, trois comportements

Tri par sélection — O(n²)
Le programme cherche le plus petit élément du sous-tableau restant et l’échange avec la position courante.
Chaque étape correspond à une recherche du minimum dans la partie non triée.

Extrait du code :
« On cherche le plus petit du reste et on l’amène en tête. »

Tri par insertion — O(n²)
L’élément courant est inséré dans la partie déjà triée en le décalant vers la gauche jusqu’à sa position correcte.

Tri fusion ascendant — O(n log n)
Le tableau est fusionné par blocs de largeur croissante : 1, 2, 4, 8…
Chaque étape double la taille des blocs fusionnés.

Le programme utilise un tableau auxiliaire aux pour stocker les résultats intermédiaires.

Interaction : apprendre en manipulant

L’utilisateur peut :

  • [P] exécuter une seule étape,
  • [E] trier entièrement,
  • [N] générer un nouveau tableau,
  • [A] changer d’algorithme,
  • [B] lancer le banc d’essai.

Cette approche interactive permet de comparer visuellement la vitesse des algorithmes et de comprendre leur logique interne.

Le banc d’essai : mesurer la complexité Le programme propose un mode « banc d’essai » où les trois algorithmes trient exactement le même tableau, pour des tailles croissantes :

« 8, 16, 32, 64, 128 »

Pour chaque taille, il mesure le nombre de comparaisons effectuées :

  • resSel pour le tri par sélection,
  • resIns pour le tri par insertion,
  • resFus pour le tri fusion.

Les résultats sont affichés dans un tableau comparatif, accompagnés des valeurs théoriques :

  • n² / 2 pour les tris quadratiques,
  • n × log2(n) pour le tri fusion.

Enfin, des courbes sont tracées pour visualiser l’évolution du nombre de comparaisons en fonction de n.

Un outil idéal pour la pédagogie NSI

Ce programme est un excellent support pour :

  • comprendre la notion de complexité algorithmique,
  • visualiser les différences entre tris quadratiques et tris efficaces,
  • expérimenter sur des données réelles,
  • confronter théorie et pratique,
  • développer une intuition algorithmique.

Il transforme un concept abstrait en une expérience concrète, interactive et mesurable.

Conclusion

Ce programme CoddyRun est bien plus qu’un simple tri :
c’est un laboratoire visuel pour explorer les algorithmes, un outil de mesure, et un support pédagogique puissant pour les cours de NSI.

Il permet aux élèves de voir ce que les algorithmes font réellement, de comprendre pourquoi certains sont plus rapides que d’autres, et d’expérimenter par eux-mêmes.

Programme :

// ===========================================================
//   ALGORITHMES DE TRI : VISUALISER ET MESURER
//   Programme d'appui pour le cours de NSI
//   - tri par sélection      O(n²)
//   - tri par insertion      O(n²)
//   - tri fusion ascendant   O(n log n)
//   On compte les comparaisons et les écritures, puis on
//   confronte la mesure à la complexité théorique.
// ===========================================================

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

NBVAL est un nombre
NBVAL vaut 23

algo est un nombre
algo vaut 1
etape est un nombre
etape vaut 0
termine est un booléen
termine vaut faux
nbComp est un nombre
nbComp vaut 0
nbEcr est un nombre
nbEcr vaut 0

ecran est un texte
ecran vaut "tri"

message est un texte
message vaut "Appuie sur P pour exécuter une étape"

touche est un nombre

@ LE TABLEAU À TRIER
tab est un tableau
aux est un tableau
pour k de 0 à 127
    tab ajoute 0
    aux ajoute 0
fin pour
pour k de 0 à NBVAL
    tab[k] vaut hasard(5, 100)
fin pour

@ RÉSULTATS DU BANC D'ESSAI
tailles est un tableau
resSel est un tableau
resIns est un tableau
resFus est un tableau
pour k de 0 à 4
    tailles ajoute 0
    resSel ajoute 0
    resIns ajoute 0
    resFus ajoute 0
fin pour
bancFait est un booléen
bancFait vaut faux

@ UN SEGMENT (le langage ne dessine que des polygones)
procédure segment(t, x1, y1, x2, y2, ep, coul)
    p est un tableau
    p ajoute x1
    p ajoute y1 - ep
    p ajoute x2
    p ajoute y2 - ep
    p ajoute x2
    p ajoute y2 + ep
    p ajoute x1
    p ajoute y1 + ep
    polygone(t, p, coul)
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    effacer(vue)
    contour(vue, #0)
    remplir(vue, #EEF2F7)

    si ecran = "tri" alors

        // =======================================================
        //                 ÉCRAN DE VISUALISATION
        // =======================================================
        nomAlgo est un texte
        coutAlgo est un texte
        si algo = 1 alors
            nomAlgo vaut "TRI PAR SÉLECTION"
            coutAlgo vaut "O(n²)  —  environ n²/2 comparaisons"
        sinon si algo = 2 alors
            nomAlgo vaut "TRI PAR INSERTION"
            coutAlgo vaut "O(n²) au pire, O(n) sur un tableau déjà trié"
        sinon
            nomAlgo vaut "TRI FUSION"
            coutAlgo vaut "O(n log n)  —  environ n x log2(n) comparaisons"
        fin si

        rectangle_arrondi(vue, 12, 10, 676, 96, 14, #FFFFFF)
        label(vue, 28, 42, nomAlgo, #B45309, 20)
        label(vue, 28, 66, coutAlgo, #64748B, 12)
        label(vue, 28, 92, "n = " + (NBVAL + 1) + "  —  comparaisons : " + nbComp + "  —  écritures : " + nbEcr, #0F172A, 13)

        si termine = vrai alors
            label(vue, 520, 42, "TRIÉ", #16A34A, 20)
        sinon
            label(vue, 500, 42, "étape " + etape, #2563EB, 16)
        fin si

        // ---------- LES BARRES ----------
        rectangle_arrondi(vue, 12, 118, 676, 320, 14, #FFFFFF)
        pour k de 0 à NBVAL
            xb est un nombre
            hb est un nombre
            xb vaut 28 + k * 27
            hb vaut tab[k] * 26 / 10

            coulB est un texte
            coulB vaut #60A5FA
            // La zone déjà en place se colore différemment
            si algo <= 2 et k < etape alors
                coulB vaut #4ADE80
            fin si
            si algo <= 2 et k = etape et termine = faux alors
                coulB vaut #FBBF24
            fin si
            si termine = vrai alors
                coulB vaut #4ADE80
            fin si

            rectangle_arrondi(vue, xb, 410 - hb, 22, hb, 4, coulB)
            label(vue, xb + 2, 428, "" + tab[k], #94A3B8, 9)
        fin pour

        si algo = 3 et termine = faux alors
            label(vue, 30, 140, "Blocs de largeur " + etape + " en cours de fusion", #64748B, 12)
        fin si

        // ---------- COMMANDES ----------
        rectangle_arrondi(vue, 12, 450, 676, 78, 14, #FFFFFF)
        label(vue, 28, 478, "[P] une étape  —  [E] tout trier  —  [N] nouveau tableau", #2563EB, 13)
        label(vue, 28, 502, "[A] changer d'algorithme  —  [B] banc d'essai", #2563EB, 13)
        label(vue, 28, 522, message, #64748B, 11)

        affiche vue
        appuyer ["p", "e", "n", "a", "b"] dans touche

        // =======================================================
        //   EXÉCUTION : UNE ÉTAPE, C'EST UN TOUR DE BOUCLE EXTERNE
        // =======================================================
        faireEtapes est un nombre
        faireEtapes vaut 0
        si touche = 1 alors
            faireEtapes vaut 1
        sinon si touche = 2 alors
            faireEtapes vaut 999
        sinon si touche = 3 alors
            pour k de 0 à NBVAL
                tab[k] vaut hasard(5, 100)
            fin pour
            etape vaut 0
            si algo = 2 alors
                etape vaut 1
            sinon si algo = 3 alors
                etape vaut 1
            fin si
            termine vaut faux
            nbComp vaut 0
            nbEcr vaut 0
            message vaut "Nouveau tableau tiré au hasard"
        sinon si touche = 4 alors
            algo vaut algo + 1
            si algo > 3 alors
                algo vaut 1
            fin si
            pour k de 0 à NBVAL
                tab[k] vaut hasard(5, 100)
            fin pour
            etape vaut 0
            si algo = 2 alors
                etape vaut 1
            sinon si algo = 3 alors
                etape vaut 1
            fin si
            termine vaut faux
            nbComp vaut 0
            nbEcr vaut 0
            message vaut "Algorithme changé, nouveau tableau"
        sinon
            ecran vaut "banc"
        fin si

        compteur est un nombre
        compteur vaut 0
        tant que compteur < faireEtapes et termine = faux
            compteur ajoute 1

            si algo = 1 alors
                // ---------- TRI PAR SÉLECTION ----------
                // On cherche le plus petit du reste et on l'amène en tête.
                imin est un nombre
                imin vaut etape
                pour k de etape + 1 à NBVAL
                    nbComp ajoute 1
                    si tab[k] < tab[imin] alors
                        imin vaut k
                    fin si
                fin pour
                si imin <> etape alors
                    tmp est un nombre
                    tmp vaut tab[etape]
                    tab[etape] vaut tab[imin]
                    tab[imin] vaut tmp
                    nbEcr ajoute 3
                fin si
                etape ajoute 1
                si etape >= NBVAL alors
                    termine vaut vrai
                fin si

            sinon si algo = 2 alors
                // ---------- TRI PAR INSERTION ----------
                // On glisse l'élément courant à sa place dans le début trié.
                val est un nombre
                val vaut tab[etape]
                k est un nombre
                k vaut etape - 1
                continuer est un booléen
                continuer vaut vrai
                tant que continuer = vrai et k >= 0
                    nbComp ajoute 1
                    si tab[k] > val alors
                        tab[k + 1] vaut tab[k]
                        nbEcr ajoute 1
                        k vaut k - 1
                    sinon
                        continuer vaut faux
                    fin si
                fin tant que
                tab[k + 1] vaut val
                nbEcr ajoute 1
                etape ajoute 1
                si etape > NBVAL alors
                    termine vaut vrai
                fin si

            sinon
                // ---------- TRI FUSION ASCENDANT ----------
                // Une étape fusionne deux à deux tous les blocs de largeur "etape".
                deb est un nombre
                deb vaut 0
                tant que deb <= NBVAL
                    mil est un nombre
                    fin2 est un nombre
                    mil vaut deb + etape
                    fin2 vaut deb + 2 * etape
                    si mil > NBVAL + 1 alors
                        mil vaut NBVAL + 1
                    fin si
                    si fin2 > NBVAL + 1 alors
                        fin2 vaut NBVAL + 1
                    fin si

                    i1 est un nombre
                    i2 est un nombre
                    idx est un nombre
                    i1 vaut deb
                    i2 vaut mil
                    idx vaut deb
                    tant que i1 < mil ou i2 < fin2
                        prendre1 est un booléen
                        prendre1 vaut faux
                        si i1 < mil et i2 < fin2 alors
                            nbComp ajoute 1
                            si tab[i1] <= tab[i2] alors
                                prendre1 vaut vrai
                            fin si
                        sinon si i1 < mil alors
                            prendre1 vaut vrai
                        fin si
                        si prendre1 = vrai alors
                            aux[idx] vaut tab[i1]
                            i1 ajoute 1
                        sinon
                            aux[idx] vaut tab[i2]
                            i2 ajoute 1
                        fin si
                        nbEcr ajoute 1
                        idx ajoute 1
                    fin tant que
                    deb vaut fin2
                fin tant que

                pour k de 0 à NBVAL
                    tab[k] vaut aux[k]
                    nbEcr ajoute 1
                fin pour
                etape vaut etape * 2
                si etape > NBVAL alors
                    termine vaut vrai
                fin si
            fin si
        fin tant que

        si termine = vrai et faireEtapes > 0 alors
            message vaut "Tri terminé en " + nbComp + " comparaisons et " + nbEcr + " écritures"
        fin si

    sinon

        // =======================================================
        //                    BANC D'ESSAI
        //   Les trois algorithmes trient le MÊME tableau, pour
        //   des tailles qui doublent : 8, 16, 32, 64, 128.
        // =======================================================
        si bancFait = faux alors
            bancFait vaut vrai
            t est un nombre
            t vaut 8

            pour idT de 0 à 4
                tailles[idT] vaut t

                // Un tableau de référence, retiré à l'identique pour chaque tri
                pour k de 0 à t - 1
                    aux[k] vaut hasard(1, 999)
                fin pour

                // --- Sélection ---
                pour k de 0 à t - 1
                    tab[k] vaut aux[k]
                fin pour
                cpt est un nombre
                cpt vaut 0
                pour i de 0 à t - 2
                    im est un nombre
                    im vaut i
                    pour j de i + 1 à t - 1
                        cpt ajoute 1
                        si tab[j] < tab[im] alors
                            im vaut j
                        fin si
                    fin pour
                    si im <> i alors
                        tm est un nombre
                        tm vaut tab[i]
                        tab[i] vaut tab[im]
                        tab[im] vaut tm
                    fin si
                fin pour
                resSel[idT] vaut cpt

                // --- Insertion ---
                pour k de 0 à t - 1
                    tab[k] vaut aux[k]
                fin pour
                cpt vaut 0
                pour i de 1 à t - 1
                    vv est un nombre
                    vv vaut tab[i]
                    jj est un nombre
                    jj vaut i - 1
                    cont est un booléen
                    cont vaut vrai
                    tant que cont = vrai et jj >= 0
                        cpt ajoute 1
                        si tab[jj] > vv alors
                            tab[jj + 1] vaut tab[jj]
                            jj vaut jj - 1
                        sinon
                            cont vaut faux
                        fin si
                    fin tant que
                    tab[jj + 1] vaut vv
                fin pour
                resIns[idT] vaut cpt

                // --- Fusion ---
                pour k de 0 à t - 1
                    tab[k] vaut aux[k]
                fin pour
                cpt vaut 0
                larg est un nombre
                larg vaut 1
                tmpF est un tableau
                pour k de 0 à t - 1
                    tmpF ajoute 0
                fin pour
                tant que larg < t
                    dd est un nombre
                    dd vaut 0
                    tant que dd < t
                        mm est un nombre
                        ff est un nombre
                        mm vaut dd + larg
                        ff vaut dd + 2 * larg
                        si mm > t alors
                            mm vaut t
                        fin si
                        si ff > t alors
                            ff vaut t
                        fin si
                        a1 est un nombre
                        a2 est un nombre
                        ix est un nombre
                        a1 vaut dd
                        a2 vaut mm
                        ix vaut dd
                        tant que a1 < mm ou a2 < ff
                            pr est un booléen
                            pr vaut faux
                            si a1 < mm et a2 < ff alors
                                cpt ajoute 1
                                si tab[a1] <= tab[a2] alors
                                    pr vaut vrai
                                fin si
                            sinon si a1 < mm alors
                                pr vaut vrai
                            fin si
                            si pr = vrai alors
                                tmpF[ix] vaut tab[a1]
                                a1 ajoute 1
                            sinon
                                tmpF[ix] vaut tab[a2]
                                a2 ajoute 1
                            fin si
                            ix ajoute 1
                        fin tant que
                        dd vaut ff
                    fin tant que
                    pour k de 0 à t - 1
                        tab[k] vaut tmpF[k]
                    fin pour
                    larg vaut larg * 2
                fin tant que
                resFus[idT] vaut cpt

                t vaut t * 2
            fin pour
        fin si

        // ---------- AFFICHAGE DU BANC D'ESSAI ----------
        rectangle_arrondi(vue, 12, 10, 676, 62, 14, #FFFFFF)
        label(vue, 28, 40, "BANC D'ESSAI : NOMBRE DE COMPARAISONS", #B45309, 19)
        label(vue, 28, 62, "Les trois algorithmes trient exactement le même tableau", #64748B, 12)

        // Tableau de résultats
        rectangle_arrondi(vue, 12, 82, 676, 190, 14, #FFFFFF)
        label(vue, 30, 108, "n", #0F172A, 13)
        label(vue, 90, 108, "sélection", #DC2626, 13)
        label(vue, 200, 108, "insertion", #2563EB, 13)
        label(vue, 310, 108, "fusion", #16A34A, 13)
        label(vue, 420, 108, "n² / 2", #94A3B8, 13)
        label(vue, 540, 108, "n x log2(n)", #94A3B8, 13)

        pour idT de 0 à 4
            yl est un nombre
            yl vaut 136 + idT * 26
            nn est un nombre
            nn vaut tailles[idT]
            lg2 est un nombre
            lg2 vaut idT + 3
            label(vue, 30, yl, "" + nn, #0F172A, 13)
            label(vue, 90, yl, "" + resSel[idT], #DC2626, 13)
            label(vue, 200, yl, "" + resIns[idT], #2563EB, 13)
            label(vue, 310, yl, "" + resFus[idT], #16A34A, 13)
            label(vue, 420, yl, "" + arrondi(nn * nn / 2), #94A3B8, 13)
            label(vue, 540, yl, "" + (nn * lg2), #94A3B8, 13)
        fin pour

        // Courbes
        rectangle_arrondi(vue, 12, 282, 676, 200, 14, #FFFFFF)
        label(vue, 30, 306, "Comparaisons en fonction de n", #64748B, 12)
        maxi est un nombre
        maxi vaut resSel[4]
        si resIns[4] > maxi alors
            maxi vaut resIns[4]
        fin si

        pour idT de 0 à 3
            x1 est un nombre
            x2 est un nombre
            x1 vaut 60 + idT * 150
            x2 vaut 60 + (idT + 1) * 150
            appelle segment(vue, x1, 460 - arrondi(resSel[idT] * 130 / maxi), x2, 460 - arrondi(resSel[idT + 1] * 130 / maxi), 2, #DC2626)
            appelle segment(vue, x1, 460 - arrondi(resIns[idT] * 130 / maxi), x2, 460 - arrondi(resIns[idT + 1] * 130 / maxi), 2, #2563EB)
            appelle segment(vue, x1, 460 - arrondi(resFus[idT] * 130 / maxi), x2, 460 - arrondi(resFus[idT + 1] * 130 / maxi), 2, #16A34A)
        fin pour
        pour idT de 0 à 4
            xp est un nombre
            xp vaut 60 + idT * 150
            cercle(vue, xp, 460 - arrondi(resSel[idT] * 130 / maxi), 4, #DC2626)
            cercle(vue, xp, 460 - arrondi(resIns[idT] * 130 / maxi), 4, #2563EB)
            cercle(vue, xp, 460 - arrondi(resFus[idT] * 130 / maxi), 4, #16A34A)
            label(vue, xp - 8, 476, "n=" + tailles[idT], #94A3B8, 10)
        fin pour

        rectangle_arrondi(vue, 12, 492, 676, 38, 14, #FFFFFF)
        label(vue, 28, 516, "[B] revenir  —  [N] refaire le banc d'essai avec d'autres tableaux", #2563EB, 13)

        affiche vue
        appuyer ["p", "e", "n", "a", "b"] dans touche
        si touche = 3 alors
            bancFait vaut faux
        sinon si touche = 5 alors
            ecran vaut "tri"
        fin si
    fin si
fin tant que
Pas encore noté
Une IA sans intelligence : Comprendre, comparer et mesurer trois façons de jouer au morpion
Article

Une IA sans intelligence

Comprendre, comparer et mesurer trois façons de jouer au morpion

Lire l'articleReplier l'article

Quand un joueur perd contre l'ordinateur, il dit volontiers que « l'IA est forte ». Le mot impressionne, et il laisse imaginer une machine qui réfléchit, qui comprend le jeu, qui a un plan.

La réalité est beaucoup plus modeste, et beaucoup plus intéressante à enseigner : un adversaire de jeu ne comprend rien du tout. Il applique des règles, ou il compte. C'est tout.

Pour le montrer, j'ai fait s'affronter trois adversaires au morpion. Aucun des trois ne sait ce qu'est un alignement, une menace ou une stratégie. Et pourtant, deux d'entre eux sont très difficiles à battre.

Ce qu'on appelle « IA » dans un jeu

Dans un jeu, l'intelligence artificielle ne pense pas. Elle répond à une seule question, encore et encore :

Parmi tous les coups possibles, lequel jouer ?

Rien d'autre. Le programme reçoit une position, il rend une case. Toute la différence entre un adversaire ridicule et un adversaire redoutable tient dans la manière de répondre à cette question.

Il y a essentiellement trois familles de réponses, et on peut les classer par ce qu'elles coûtent à écrire.

Niveau zéro : le hasard

La réponse la plus simple consiste à choisir une case libre au sort.

tant que trouve = faux
    c vaut hasard(0, 8)
    si cel[c] = 0 alors
        choix vaut c
        trouve vaut vrai
    fin si
fin tant que

Cet adversaire ne sait pas qu'il joue. Il ne remarque pas qu'il va perdre au coup suivant, il ne voit pas qu'il pourrait gagner immédiatement.

Il a pourtant une utilité réelle : il sert de référence. Sans lui, impossible de dire si les autres sont bons. On ne mesure jamais une performance dans l'absolu, toujours contre quelque chose.

Niveau un : les règles en cascade

C'est de très loin la technique la plus répandue dans les jeux, et la plus rentable. On écrit une liste de priorités, et on prend la première qui s'applique.

Pour le morpion, quatre suffisent :

  • Si je peux gagner ce coup-ci, je le joue.
  • Sinon, si l'adversaire peut gagner au coup suivant, je l'en empêche.
  • Sinon, je prends le centre, puis un coin.
  • Sinon, un bord.

Comment le programme sait-il qu'il peut gagner ? Il n'a aucune notion d'alignement. Il triche, en quelque sorte : il pose son pion, regarde si la position est gagnante, puis le retire.

cel[k] vaut joueur
// on vérifie les huit alignements possibles
cel[k] vaut 0
si gagnant = joueur alors
    choix vaut k
fin si

C'est là le point essentiel : l'IA ne raisonne pas, elle essaie. Elle simule un coup, observe le résultat, annule. Neuf essais, et la décision est prise.

Cet adversaire ne voit qu'un seul coup à l'avance. Il ne prépare rien, il n'a aucun plan. Il gagne pourtant l'immense majorité de ses parties contre le hasard.

L'ordre des règles est la stratégie. Intervertissez les règles 1 et 2 bloquer avant de gagner, et l'adversaire devient absurde : il défend une position déjà perdue pour lui alors qu'il avait la victoire. Toute l'intelligence apparente tient dans cet ordre, décidé par le programmeur, pas par la machine.

Niveau deux : la simulation

Le troisième adversaire est le plus déroutant, parce qu'il ne connaît aucune règle du jeu. Ni le centre, ni les coins, ni l'idée de bloquer.

Voici tout ce qu'il fait. Pour chaque case libre :

  • il y pose son pion,
  • il termine la partie complètement au hasard, seize fois de suite,
  • il compte combien de ces parties absurdes il a gagnées.

Puis il joue la case dont le score est le meilleur.

C'est tout. Aucune stratégie n'a été programmée. Aucun humain ne lui a expliqué que le centre est une bonne case. Et pourtant il le découvre tout seul : statistiquement, les parties jouées n'importe comment à partir du centre se terminent mieux.

Cette méthode porte un nom "la recherche de Monte-Carlo" et c'est une cousine simplifiée de ce qui a permis aux programmes de jeu de Go de dépasser les meilleurs joueurs humains.

Le renversement de perspective est complet : là où l'IA à règles sait des choses, l'IA à simulation ne sait rien mais compte beaucoup.

Mesurer, pas croire

Une IA de jeu ne se juge pas à l'impression qu'elle donne. Elle se mesure.

Le programme fait donc s'affronter les trois adversaires, trente parties par confrontation, en alternant celui qui commence. Ce détail n'est pas cosmétique : au morpion, commencer est un avantage réel, et une mesure qui l'ignore ne vaut rien.

Les résultats sont sans appel. Le hasard ne gagne presque jamais contre les règles. La simulation, elle, tient la comparaison sans avoir reçu la moindre consigne stratégique.

Ce protocole vaut pour tout : quand vous ajoutez une règle à votre adversaire, faites-le jouer cent parties contre la version précédente. Si le taux de victoire ne bouge pas, la règle ne sert à rien, quelle que soit l'élégance de l'idée.

Pourquoi ça ressemble quand même à de l'intelligence

Un joueur humain qui perd contre l'IA à règles a l'impression qu'elle a compris son plan. C'est une illusion, et elle repose sur trois mécanismes.

La constance. L'ordinateur n'oublie jamais de vérifier s'il peut gagner. Un humain, si. Une machine qui ne se trompe jamais sur une chose simple paraît attentive.

La rapidité. La décision est instantanée. On prête volontiers de la réflexion à ce qui répond vite.

Notre penchant à prêter des intentions. Nous racontons une histoire derrière chaque coup — « il a vu ma menace » — alors que le programme a simplement parcouru neuf cases dans l'ordre.

Bonnes pratiques pour écrire un adversaire

Commencez par le hasard. C'est votre point de comparaison, et il permet de vérifier que le jeu fonctionne avant de penser à la stratégie.

Ajoutez les règles une par une, et mesurez chaque fois. Une règle qui n'améliore pas le taux de victoire est une règle à supprimer.

Rangez les règles par priorité, jamais en vrac. L'ordre est la stratégie.

Simulez plutôt que raisonner. « Je joue ici, que se passe-t-il ? » est presque toujours plus simple à écrire que « comment savoir si ce coup est bon ? ».

N'oubliez pas de rendre l'adversaire battable. Une IA parfaite au morpion ne perd jamais : le jeu devient sans intérêt. La bonne question n'est pas « comment le rendre plus fort », mais « comment le rendre agréable à affronter ». Un adversaire qui laisse passer une occasion de temps en temps est souvent meilleur, du point de vue du joueur, qu'un adversaire imbattable.

Conclusion : compter n'est pas comprendre

Trois adversaires, trois approches, aucune intelligence.

Le premier tire au sort. Le deuxième applique quatre règles dans un ordre choisi par un humain. Le troisième joue des milliers de parties absurdes et retient celles qui finissent bien.

Aucun des trois ne sait ce qu'est le morpion. Aucun n'a d'intention, de plan, ni de conscience de gagner. Il n'y a que des essais, des comparaisons et des comptages.

C'est peut-être la leçon la plus utile qu'un jeu puisse donner sur l'intelligence artificielle : ce que nous prenons pour de la réflexion n'est souvent qu'une énumération très rapide. Le programme n'est pas plus malin que nous. Il est seulement infatigable.

Et cette leçon-là dépasse largement le morpion.

Programme :

// ===========================================================
//   L'INTELLIGENCE ARTIFICIELLE SANS INTELLIGENCE
//   Trois adversaires au morpion, aucun ne « comprend » le jeu.
//     1. HASARD     joue n'importe où
//     2. RÈGLES     quatre priorités en cascade
//     3. SIMULATION joue au hasard dans sa tête, et compte
//   Écran DÉMO : une partie rejouée coup par coup, avec la
//   raison de chaque décision.
//   Écran TOURNOI : les trois s'affrontent, on mesure.
// ===========================================================

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

NBSIM est un nombre
NBSIM vaut 16
NBMATCH est un nombre
NBMATCH vaut 30

ia1 est un nombre
ia2 est un nombre
ia1 vaut 2
ia2 vaut 1

ecran est un texte
ecran vaut "demo"
relancer est un booléen
relancer vaut vrai
montre est un nombre
montre vaut 0
touche est un nombre

@ LE PLATEAU et LES ALIGNEMENTS
// Les neuf cases, numérotées de 0 à 8
cel est un tableau
sim est un tableau
pour k de 0 à 8
    cel ajoute 0
    sim ajoute 0
fin pour

// Les huit alignements gagnants, décrits une fois pour toutes
ln1 est un tableau
ln2 est un tableau
ln3 est un tableau
ln1 ajoute 0
ln2 ajoute 1
ln3 ajoute 2
ln1 ajoute 3
ln2 ajoute 4
ln3 ajoute 5
ln1 ajoute 6
ln2 ajoute 7
ln3 ajoute 8
ln1 ajoute 0
ln2 ajoute 3
ln3 ajoute 6
ln1 ajoute 1
ln2 ajoute 4
ln3 ajoute 7
ln1 ajoute 2
ln2 ajoute 5
ln3 ajoute 8
ln1 ajoute 0
ln2 ajoute 4
ln3 ajoute 8
ln1 ajoute 2
ln2 ajoute 4
ln3 ajoute 6

// Ordre de préférence de l'IA 2 : centre, puis coins, puis bords
prefer est un tableau
prefer ajoute 4
prefer ajoute 0
prefer ajoute 2
prefer ajoute 6
prefer ajoute 8
prefer ajoute 1
prefer ajoute 3
prefer ajoute 5
prefer ajoute 7

@ MÉMOIRE de LA PARTIE MONTRÉE
histCase est un tableau
histQui est un tableau
histTxt est un tableau
pour k de 0 à 8
    histCase ajoute 0
    histQui ajoute 0
    histTxt ajoute ""
fin pour
nbCoups est un nombre
nbCoups vaut 0
issue est un texte
issue vaut ""

@ RÉSULTATS DU TOURNOI
parA est un tableau
parB est un tableau
resA est un tableau
resB est un tableau
resN est un tableau
pour k de 0 à 2
    parA ajoute 1
    parB ajoute 2
    resA ajoute 0
    resB ajoute 0
    resN ajoute 0
fin pour

@ NOM D'UNE IA
procédure nomIA(t, x, y, n, coul, ta)
    txt est un texte
    si n = 1 alors
        txt vaut "HASARD"
    sinon si n = 2 alors
        txt vaut "RÈGLES"
    sinon
        txt vaut "SIMULATION"
    fin si
    label(t, x, y, txt, coul, ta)
fin procédure

@ UNE BARRE de pourcentage
procédure barrePct(t, x, y, larg, pct, coul)
    rectangle_arrondi(t, x, y, larg, 14, 7, #E2E8F0)
    lp est un nombre
    lp vaut arrondi(larg * pct / 100)
    si lp > 0 alors
        rectangle_arrondi(t, x, y, lp, 14, 7, coul)
    fin si
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    // =========================================================
    //   SIMULATION : le même bloc sert à la démo et au tournoi
    // =========================================================
    si relancer = vrai alors
        relancer vaut faux
        montre vaut 0

        nbPaires est un nombre
        nbParties est un nombre
        enregistre est un booléen
        si ecran = "demo" alors
            parA[0] vaut ia1
            parB[0] vaut ia2
            nbPaires vaut 1
            nbParties vaut 1
            enregistre vaut vrai
        sinon
            parA[0] vaut 1
            parB[0] vaut 2
            parA[1] vaut 1
            parB[1] vaut 3
            parA[2] vaut 2
            parB[2] vaut 3
            nbPaires vaut 3
            nbParties vaut NBMATCH
            enregistre vaut faux
        fin si

        pour ip de 0 à nbPaires - 1
            vA est un nombre
            vB est un nombre
            vN est un nombre
            vA vaut 0
            vB vaut 0
            vN vaut 0

            pour g de 1 à nbParties

                // ---------- une partie complète ----------
                pour k de 0 à 8
                    cel[k] vaut 0
                fin pour
                nbCoups vaut 0
                issue vaut ""

                // On alterne celui qui commence, pour que la mesure soit honnête
                joueur est un nombre
                joueur vaut 1
                si g - arrondi_inferieur(g / 2) * 2 = 0 alors
                    joueur vaut 2
                fin si

                vainqueur est un nombre
                vainqueur vaut 0
                tour est un nombre
                tour vaut 0

                tant que vainqueur = 0 et tour < 9

                    // Quelle IA joue ce coup ?
                    niv est un nombre
                    si joueur = 1 alors
                        niv vaut parA[ip]
                    sinon
                        niv vaut parB[ip]
                    fin si
                    adv est un nombre
                    adv vaut 3 - joueur

                    choix est un nombre
                    choix vaut - 1
                    raison est un texte
                    raison vaut ""

                    // =================================================
                    //   IA 1 : LE HASARD
                    //   Aucune notion de jeu. Une case libre au hasard.
                    // =================================================
                    si niv = 1 alors
                        trouve est un booléen
                        trouve vaut faux
                        essais est un nombre
                        essais vaut 0
                        tant que trouve = faux et essais < 200
                            essais ajoute 1
                            c est un nombre
                            c vaut hasard(0, 8)
                            si cel[c] = 0 alors
                                choix vaut c
                                trouve vaut vrai
                            fin si
                        fin tant que
                        raison vaut "au hasard"

                        // =================================================
                        //   IA 2 : QUATRE RÈGLES EN CASCADE
                        //   1 gagner  2 bloquer  3 centre  4 coin  5 bord
                        // =================================================
                    sinon si niv = 2 alors

                        // Règle 1 : puis-je gagner tout de suite ?
                        pour k de 0 à 8
                            si cel[k] = 0 et choix < 0 alors
                                cel[k] vaut joueur
                                gagnant est un nombre
                                gagnant vaut 0
                                pour L de 0 à 7
                                    x est un nombre
                                    x vaut cel[ln1[L]]
                                    si x > 0 et cel[ln2[L]] = x et cel[ln3[L]] = x alors
                                        gagnant vaut x
                                    fin si
                                fin pour
                                cel[k] vaut 0
                                si gagnant = joueur alors
                                    choix vaut k
                                    raison vaut "je gagne en jouant là"
                                fin si
                            fin si
                        fin pour

                        // Règle 2 : l'adversaire gagnerait-il ici ?
                        si choix < 0 alors
                            pour k de 0 à 8
                                si cel[k] = 0 et choix < 0 alors
                                    cel[k] vaut adv
                                    gagnant vaut 0
                                    pour L de 0 à 7
                                        x vaut cel[ln1[L]]
                                        si x > 0 et cel[ln2[L]] = x et cel[ln3[L]] = x alors
                                            gagnant vaut x
                                        fin si
                                    fin pour
                                    cel[k] vaut 0
                                    si gagnant = adv alors
                                        choix vaut k
                                        raison vaut "je bloque l'adversaire"
                                    fin si
                                fin si
                            fin pour
                        fin si

                        // Règles 3 à 5 : centre, puis coins, puis bords
                        si choix < 0 alors
                            pour k de 0 à 8
                                si choix < 0 et cel[prefer[k]] = 0 alors
                                    choix vaut prefer[k]
                                    si choix = 4 alors
                                        raison vaut "je prends le centre"
                                    sinon si choix = 0 ou choix = 2 ou choix = 6 ou choix = 8 alors
                                        raison vaut "je prends un coin"
                                    sinon
                                        raison vaut "il ne reste qu'un bord"
                                    fin si
                                fin si
                            fin pour
                        fin si

                        // =================================================
                        //   IA 3 : LA SIMULATION
                        //   Elle ne connaît aucune stratégie. Pour chaque
                        //   case libre, elle finit la partie au hasard un
                        //   grand nombre de fois et garde la case qui gagne
                        //   le plus souvent.
                        // =================================================
                    sinon
                        meilleur est un nombre
                        meilleur vaut 0 - 99999
                        pctBest est un nombre
                        pctBest vaut 0

                        pour k de 0 à 8
                            si cel[k] = 0 alors
                                note est un nombre
                                note vaut 0
                                gagnes est un nombre
                                gagnes vaut 0

                                pour n de 1 à NBSIM
                                    // On recopie la position et on la termine au hasard
                                    pour q de 0 à 8
                                        sim[q] vaut cel[q]
                                    fin pour
                                    sim[k] vaut joueur

                                    qui est un nombre
                                    qui vaut adv
                                    fini est un nombre
                                    fini vaut 0
                                    coupsSim est un nombre
                                    coupsSim vaut 1

                                    // Y a-t-il déjà un gagnant après ce coup ?
                                    pour L de 0 à 7
                                        x vaut sim[ln1[L]]
                                        si x > 0 et sim[ln2[L]] = x et sim[ln3[L]] = x alors
                                            fini vaut x
                                        fin si
                                    fin pour

                                    tant que fini = 0 et coupsSim < 9
                                        libre est un nombre
                                        libre vaut - 1
                                        essais vaut 0
                                        tant que libre < 0 et essais < 200
                                            essais ajoute 1
                                            c vaut hasard(0, 8)
                                            si sim[c] = 0 alors
                                                libre vaut c
                                            fin si
                                        fin tant que
                                        si libre < 0 alors
                                            coupsSim vaut 9
                                        sinon
                                            sim[libre] vaut qui
                                            coupsSim ajoute 1
                                            pour L de 0 à 7
                                                x vaut sim[ln1[L]]
                                                si x > 0 et sim[ln2[L]] = x et sim[ln3[L]] = x alors
                                                    fini vaut x
                                                fin si
                                            fin pour
                                            qui vaut 3 - qui
                                        fin si
                                    fin tant que

                                    si fini = joueur alors
                                        note ajoute 2
                                        gagnes ajoute 1
                                    sinon si fini = 0 alors
                                        note ajoute 1
                                    fin si
                                fin pour

                                si note > meilleur alors
                                    meilleur vaut note
                                    choix vaut k
                                    pctBest vaut arrondi(gagnes * 100 / NBSIM)
                                fin si
                            fin si
                        fin pour
                        raison vaut "simulation : " + pctBest + " % de parties gagnées"
                    fin si

                    // ---------- on joue le coup retenu ----------
                    si choix >= 0 alors
                        cel[choix] vaut joueur
                        si enregistre = vrai alors
                            histCase[nbCoups] vaut choix
                            histQui[nbCoups] vaut joueur
                            histTxt[nbCoups] vaut raison
                        fin si
                        nbCoups ajoute 1
                    fin si

                    // ---------- y a-t-il un alignement ? ----------
                    pour L de 0 à 7
                        x vaut cel[ln1[L]]
                        si x > 0 et cel[ln2[L]] = x et cel[ln3[L]] = x alors
                            vainqueur vaut x
                        fin si
                    fin pour

                    joueur vaut 3 - joueur
                    tour ajoute 1
                fin tant que

                si vainqueur = 1 alors
                    vA ajoute 1
                    issue vaut "Victoire du joueur 1"
                sinon si vainqueur = 2 alors
                    vB ajoute 1
                    issue vaut "Victoire du joueur 2"
                sinon
                    vN ajoute 1
                    issue vaut "Match nul"
                fin si
            fin pour

            resA[ip] vaut vA
            resB[ip] vaut vB
            resN[ip] vaut vN
        fin pour
    fin si

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

    rectangle_arrondi(vue, 12, 10, 676, 76, 14, #FFFFFF)
    label(vue, 26, 40, "UNE IA SANS INTELLIGENCE", #B45309, 20)
    label(vue, 26, 62, "Trois adversaires au morpion : aucun ne comprend le jeu, et pourtant…", #64748B, 12)
    label(vue, 26, 80, "[A] IA 1     [Z] IA 2     [T] démo / tournoi     [P] coup suivant     [R] relancer", #2563EB, 11)

    si ecran = "demo" alors

        // ---------- LE PLATEAU ----------
        rectangle_arrondi(vue, 12, 94, 330, 414, 14, #FFFFFF)
        label(vue, 30, 122, "Joueur 1 : ", #DC2626, 13)
        appelle nomIA(vue, 106, 122, ia1, #DC2626, 13)
        label(vue, 30, 144, "Joueur 2 : ", #2563EB, 13)
        appelle nomIA(vue, 106, 144, ia2, #2563EB, 13)

        pour k de 0 à 8
            lg est un nombre
            cl est un nombre
            lg vaut arrondi_inferieur(k / 3)
            cl vaut k - lg * 3
            xk est un nombre
            yk est un nombre
            xk vaut 46 + cl * 84
            yk vaut 170 + lg * 84
            rectangle_arrondi(vue, xk, yk, 76, 76, 10, #E7EDF5)

            // On ne montre que les coups déjà joués
            qui est un nombre
            qui vaut 0
            pour n de 0 à montre - 1
                si histCase[n] = k alors
                    qui vaut histQui[n]
                fin si
            fin pour

            si qui = 1 alors
                cercle(vue, xk + 38, yk + 38, 24, #FCA5A5)
                cercle(vue, xk + 38, yk + 38, 15, #E7EDF5)
            sinon si qui = 2 alors
                rectangle_arrondi(vue, xk + 16, yk + 34, 44, 8, 3, #93C5FD)
                rectangle_arrondi(vue, xk + 34, yk + 16, 8, 44, 3, #93C5FD)
            fin si
        fin pour

        label(vue, 30, 448, "Coup " + montre + " sur " + nbCoups, #0F172A, 13)
        si montre >= nbCoups alors
            label(vue, 150, 448, issue, #16A34A, 13)
        fin si
        label(vue, 30, 474, "Appuie sur [P] pour dérouler la partie", #94A3B8, 11)
        label(vue, 30, 496, "Chaque coup affiche la raison qui l'a produit.", #94A3B8, 11)

        // ---------- LE JOURNAL DES DÉCISIONS ----------
        rectangle_arrondi(vue, 352, 94, 336, 414, 14, #FFFFFF)
        label(vue, 370, 122, "POURQUOI CE COUP ?", #0F172A, 14)
        pour n de 0 à nbCoups - 1
            yn est un nombre
            yn vaut 152 + n * 38
            si n < montre alors
                coulJ est un texte
                si histQui[n] = 1 alors
                    coulJ vaut #DC2626
                sinon
                    coulJ vaut #2563EB
                fin si
                label(vue, 370, yn, "Coup " + (n + 1) + " - joueur " + histQui[n] + ", case " + (histCase[n] + 1), coulJ, 12)
                label(vue, 370, yn + 18, histTxt[n], #64748B, 11)
            sinon
                label(vue, 370, yn, "Coup " + (n + 1) + " - ?", #CBD5E1, 12)
            fin si
        fin pour

    sinon

        // ---------- LE TOURNOI ----------
        rectangle_arrondi(vue, 12, 94, 676, 414, 14, #FFFFFF)
        label(vue, 30, 124, "TOURNOI - " + NBMATCH + " parties par confrontation", #0F172A, 15)
        label(vue, 30, 146, "Chacun commence à tour de rôle, pour que la mesure soit honnête.", #94A3B8, 11)

        pour ip de 0 à 2
            yp est un nombre
            yp vaut 190 + ip * 96
            appelle nomIA(vue, 30, yp, parA[ip], #DC2626, 14)
            label(vue, 150, yp, "contre", #94A3B8, 12)
            appelle nomIA(vue, 210, yp, parB[ip], #2563EB, 14)

            pctA est un nombre
            pctB est un nombre
            pctN est un nombre
            pctA vaut arrondi(resA[ip] * 100 / NBMATCH)
            pctB vaut arrondi(resB[ip] * 100 / NBMATCH)
            pctN vaut 100 - pctA - pctB

            appelle barrePct(vue, 30, yp + 14, 300, pctA, #DC2626)
            label(vue, 342, yp + 26, pctA + " %", #DC2626, 12)
            appelle barrePct(vue, 30, yp + 36, 300, pctB, #2563EB)
            label(vue, 342, yp + 48, pctB + " %", #2563EB, 12)
            appelle barrePct(vue, 30, yp + 58, 300, pctN, #94A3B8)
            label(vue, 342, yp + 70, pctN + " % nuls", #94A3B8, 12)

            label(vue, 420, yp + 26, "victoires : " + resA[ip], #DC2626, 12)
            label(vue, 420, yp + 48, "victoires : " + resB[ip], #2563EB, 12)
            label(vue, 420, yp + 70, "nuls : " + resN[ip], #94A3B8, 12)
        fin pour

        label(vue, 30, 486, "Quatre règles écrasent le hasard. La simulation ne connaît aucune règle et fait aussi bien.", #B45309, 12)
    fin si

    affiche vue

    // =========================================================
    appuyer ["p", "a", "z", "t", "r"] dans touche

    si touche = 1 alors
        si montre < nbCoups alors
            montre ajoute 1
        fin si
    sinon si touche = 2 alors
        ia1 vaut ia1 + 1
        si ia1 > 3 alors
            ia1 vaut 1
        fin si
        relancer vaut vrai
    sinon si touche = 3 alors
        ia2 vaut ia2 + 1
        si ia2 > 3 alors
            ia2 vaut 1
        fin si
        relancer vaut vrai
    sinon si touche = 4 alors
        si ecran = "demo" alors
            ecran vaut "tournoi"
        sinon
            ecran vaut "demo"
        fin si
        relancer vaut vrai
    sinon
        relancer vaut vrai
    fin si
fin tant que
Pas encore noté
Mesurer l’aire d’un étang irrégulier grâce à la méthode de Monte-Carlo
Article

Mesurer l’aire d’un étang irrégulier grâce à la méthode de Monte-Carlo

Comment déterminer l’aire d’un étang aux contours irréguliers

Lire l'articleReplier l'article

Comment déterminer l’aire d’un étang dont la forme n’a rien de régulier ? Lorsqu’un contour est trop complexe pour être décrit par une formule simple, une méthode efficace consiste à utiliser… le hasard.
Cette approche, appelée méthode de Monte-Carlo, permet d’estimer une surface en réalisant des tirages aléatoires dans une zone connue.

Dans cette simulation, l’étang se trouve dans un champ rectangulaire de 60 mètres sur 35 mètres, soit 2100 m². L’étang lui-même est formé par la réunion de quatre ellipses, ce qui lui donne une forme irrégulière mais permet de tester facilement si un point tombe dans l’eau.

Principe de l’estimation

Le programme réalise des tirs aléatoires dans tout le champ.
Chaque tir tombe soit dans l’eau, soit dans l’herbe.
En comptant le nombre de tirs dans l’eau, on obtient une estimation de l’aire de l’étang.

Voici les calculs utilisés :

Aire totale du champ :

  • 2100 m²

Estimation de l’aire de l’étang :
aire estimée = 2100 × (tirs dans l’eau / tirs totaux)

Pourcentage de tirs dans l’eau :

  • pourcentage = (tirs dans l’eau × 100) / tirs totaux

Écart avec l’aire exacte :

  • écart = |aire estimée - aire exacte|
  • pourcentage d’erreur = (écart × 100) / aire exacte

Plus le nombre de tirs augmente, plus l’estimation se rapproche de la valeur réelle.

Un étang généré aléatoirement

Les quatre ellipses qui composent l’étang sont générées avec des valeurs aléatoires pour leurs centres et leurs rayons.
Par exemple :

  • « ecx[0] vaut hasard(18, 26) »
  • « erx[1] vaut hasard(8, 12) »

Chaque nouvelle simulation produit donc un étang différent.

Calcul de l’aire exacte

Pour disposer d’une référence, le programme calcule l’aire exacte en balayant tout le champ avec un pas de 0,5 mètre.
Chaque point du quadrillage est testé pour savoir s’il se trouve dans l’une des ellipses.
Ce calcul est long mais fournit une valeur précise, utilisée pour comparer la qualité de l’estimation.

Visualisation et convergence

La simulation affiche :

  • le champ et l’étang,
  • les impacts des tirs (jaune = eau, rouge = herbe),
  • le comptage en temps réel,
  • l’estimation actuelle,
  • l’écart avec l’aire exacte,

une courbe de convergence montrant comment l’estimation se stabilise au fil des tirs.

Les boutons permettent d’ajouter 1, 10, 100 ou 1000 tirs, de réinitialiser le comptage ou de générer un nouvel étang.

Une méthode simple pour un problème complexe

Cette simulation montre comment une méthode probabiliste peut résoudre un problème géométrique difficile.
Avec quelques dizaines de tirs, l’estimation reste approximative.
Avec plusieurs centaines, elle devient fiable.
Avec plusieurs milliers, elle se stabilise autour de l’aire réelle.

La méthode de Monte-Carlo devient ici visuelle, intuitive et accessible.

Programme :

// ===========================================================
//   MESURER UNE AIRE À PARTIR DE TIRS ALÉATOIRES
//
//   Un étang aux contours irréguliers occupe une partie d'un
//   champ rectangulaire de 60 m sur 35 m, soit 2100 m².
//   On tire au hasard sur tout le rectangle. Chaque tir tombe
//   dans l'eau ou dans l'herbe.
//
//   aire de l'étang  ˜  2100 x (tirs dans l'eau / tirs totaux)
//
//   Plus on tire, plus l'estimation se rapproche de la vérité.
// ===========================================================

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

LARGM est un nombre
HAUTM est un nombre
LARGM vaut 60
HAUTM vaut 35
AIRETOT est un nombre
AIRETOT vaut 2100

// Le champ à l'écran : 8 pixels pour 1 mètre
ECH est un nombre
CHAMPX est un nombre
CHAMPY est un nombre
ECH vaut 8
CHAMPX vaut 22
CHAMPY vaut 92

MAXPTS est un nombre
MAXPTS vaut 1500

nbTirs est un nombre
nbEau est un nombre
nbTirs vaut 0
nbEau vaut 0
estim est un nombre
estim vaut 0
aireVraie est un nombre
aireVraie vaut 0

message est un texte
message vaut "Tire quelques obus au hasard sur le champ"

@ L'ÉTANG
// Il est décrit par la réunion de quatre ellipses : le contour
// paraît irrégulier, et le test « suis-je dans l'eau ? » reste
// une simple inégalité.
ecx est un tableau
ecy est un tableau
erx est un tableau
ery est un tableau
pour e de 0 à 3
    ecx ajoute 0
    ecy ajoute 0
    erx ajoute 0
    ery ajoute 0
fin pour

@ LES TIRS MÉMORISÉS
tirX est un tableau
tirY est un tableau
tirEau est un tableau
pour k de 0 à MAXPTS
    tirX ajoute 0
    tirY ajoute 0
    tirEau ajoute faux
fin pour
nbPts est un nombre
nbPts vaut 0

@ HISTORIQUE DES ESTIMATIONS
histo est un tableau
histoN est un tableau
pour k de 0 à 79
    histo ajoute 0
    histoN ajoute 0
fin pour
nbHisto est un nombre
nbHisto vaut 0

nouvelEtang est un booléen
nouvelEtang vaut vrai

@ 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 + 10, y + 23, txt, #FFFFFF, 13)
fin procédure

@ BOUCLE PRINCIPALE
tant que vrai

    // =========================================================
    //   CRÉATION DE L'ÉTANG ET CALCUL DE SON AIRE EXACTE
    //   L'aire vraie est obtenue en balayant tout le champ avec
    //   un pas fin. C'est long, mais c'est la référence qui
    //   permet de juger la qualité de l'estimation.
    // =========================================================
    si nouvelEtang = vrai alors
        nouvelEtang vaut faux

        ecx[0] vaut hasard(18, 26)
        ecy[0] vaut hasard(14, 20)
        erx[0] vaut hasard(11, 15)
        ery[0] vaut hasard(7, 10)

        ecx[1] vaut hasard(30, 38)
        ecy[1] vaut hasard(10, 16)
        erx[1] vaut hasard(8, 12)
        ery[1] vaut hasard(5, 8)

        ecx[2] vaut hasard(26, 34)
        ecy[2] vaut hasard(20, 26)
        erx[2] vaut hasard(7, 11)
        ery[2] vaut hasard(4, 7)

        ecx[3] vaut hasard(10, 16)
        ecy[3] vaut hasard(18, 24)
        erx[3] vaut hasard(5, 9)
        ery[3] vaut hasard(4, 6)

        // Balayage fin : un point tous les 0,5 mètre
        dedansTot est un nombre
        totalPts est un nombre
        dedansTot vaut 0
        totalPts vaut 0
        // On avance par demi-mètres, en comptant en demi-mètres entiers :
        // aucun nombre à virgule n'apparaît dans le programme.
        pour ix de 0 à LARGM * 2 - 1
            pour iy de 0 à HAUTM * 2 - 1
                px est un nombre
                py est un nombre
                px vaut ix / 2
                py vaut iy / 2
                totalPts ajoute 1
                dedans est un booléen
                dedans vaut faux
                pour e de 0 à 3
                    ddx est un nombre
                    ddy est un nombre
                    ddx vaut (px - ecx[e]) / erx[e]
                    ddy vaut (py - ecy[e]) / ery[e]
                    si ddx * ddx + ddy * ddy <= 1 alors
                        dedans vaut vrai
                    fin si
                fin pour
                si dedans = vrai alors
                    dedansTot ajoute 1
                fin si
            fin pour
        fin pour
        aireVraie vaut arrondi(AIRETOT * dedansTot / totalPts)

        nbTirs vaut 0
        nbEau vaut 0
        nbPts vaut 0
        nbHisto vaut 0
        estim vaut 0
        message vaut "Nouvel étang : son aire exacte est de " + aireVraie + " m²"
    fin si

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

    rectangle_arrondi(vue, 12, 10, 676, 70, 14, #FFFFFF)
    label(vue, 26, 38, "MESURER UNE AIRE AVEC DES TIRS AU HASARD", #B45309, 18)
    label(vue, 26, 60, "Champ de " + LARGM + " m sur " + HAUTM + " m, soit " + AIRETOT + " m². On tire, on compte, on estime.", #64748B, 12)

    // ---------- LE CHAMP ET L'ÉTANG ----------
    rectangle_arrondi(vue, 12, 84, 500, 308, 14, #FFFFFF)
    rectangle(vue, CHAMPX, CHAMPY, LARGM * ECH, HAUTM * ECH, #86C46A)
    pour e de 0 à 3
        ellipse(vue, CHAMPX + ecx[e] * ECH, CHAMPY + ecy[e] * ECH, erx[e] * ECH, ery[e] * ECH, #2D7FD4)
    fin pour
    contour(vue, #334155, 2)
    rectangle(vue, CHAMPX, CHAMPY, LARGM * ECH, HAUTM * ECH, rgba(0, 0, 0, 0))
    contour(vue, #0)

    // Les impacts
    pour k de 0 à nbPts - 1
        si tirEau[k] = vrai alors
            cercle(vue, tirX[k], tirY[k], 2, #FDE68A)
        sinon
            cercle(vue, tirX[k], tirY[k], 2, #B91C1C)
        fin si
    fin pour

    label(vue, 24, 386, "jaune : dans l'eau  -  rouge : dans l'herbe", #94A3B8, 10)

    // ---------- LE COMPTAGE ----------
    rectangle_arrondi(vue, 520, 84, 168, 308, 14, #FFFFFF)
    label(vue, 536, 114, "COMPTAGE", #0F172A, 14)
    label(vue, 536, 144, "tirs : " + nbTirs, #334155, 13)
    label(vue, 536, 168, "dans l'eau : " + nbEau, #2563EB, 13)

    si nbTirs > 0 alors
        pct est un nombre
        pct vaut arrondi(nbEau * 1000 / nbTirs) / 10
        label(vue, 536, 192, "soit " + pct + " %", #64748B, 12)
    fin si

    label(vue, 536, 232, "ESTIMATION", #0F172A, 14)
    label(vue, 536, 256, "" + AIRETOT + " x " + nbEau, #94A3B8, 11)
    rectangle(vue, 536, 264, 96, 1, #94A3B8)
    si nbTirs > 0 alors
        label(vue, 578, 280, "" + nbTirs, #94A3B8, 11)
    sinon
        label(vue, 578, 280, "0", #94A3B8, 11)
    fin si
    label(vue, 536, 314, "" + estim + " m²", #16A34A, 20)

    label(vue, 536, 346, "aire exacte", #94A3B8, 11)
    label(vue, 536, 366, "" + aireVraie + " m²", #334155, 14)

    // ---------- BOUTONS ET COURBE ----------
    rectangle_arrondi(vue, 12, 394, 676, 114, 14, #FFFFFF)
    appelle bouton(vue, 24, 404, 62, "+ 1", #2563EB)
    appelle bouton(vue, 94, 404, 62, "+ 10", #2563EB)
    appelle bouton(vue, 164, 404, 70, "+ 100", #2563EB)
    appelle bouton(vue, 242, 404, 78, "+ 1000", #2563EB)
    appelle bouton(vue, 328, 404, 74, "Effacer", #64748B)
    appelle bouton(vue, 410, 404, 84, "Autre étang", #7C3AED)

    label(vue, 24, 470, message, #64748B, 11)
    si nbTirs > 0 alors
        ecart est un nombre
        ecart vaut absolue(estim - aireVraie)
        pctE est un nombre
        pctE vaut arrondi(ecart * 1000 / aireVraie) / 10
        label(vue, 24, 494, "écart avec l'aire exacte : " + ecart + " m², soit " + pctE + " %", #B45309, 11)
    fin si

    // Courbe de convergence
    rectangle(vue, 510, 400, 168, 100, #F8FAFC)
    label(vue, 512, 412, "convergence", #94A3B8, 9)
    // la ligne de l'aire exacte, au milieu du cadre
    rectangle(vue, 512, 452, 164, 1, #16A34A)
    // Échelle verticale : un écart de 300 m² occupe toute la demi-hauteur.
    // Sans cela, une erreur de 60 m² se traduirait par trois pixels.
    pour k de 0 à nbHisto - 2
        x1 est un nombre
        x2 est un nombre
        y1 est un nombre
        y2 est un nombre
        x1 vaut 512 + arrondi(k * 164 / 79)
        x2 vaut 512 + arrondi((k + 1) * 164 / 79)
        y1 vaut 452 - arrondi((histo[k] - aireVraie) * 46 / 300)
        y2 vaut 452 - arrondi((histo[k + 1] - aireVraie) * 46 / 300)
        y1 vaut limiter(y1, 404, 498)
        y2 vaut limiter(y2, 404, 498)
        p est un tableau
        p ajoute x1
        p ajoute y1 - 1
        p ajoute x2
        p ajoute y2 - 1
        p ajoute x2
        p ajoute y2 + 1
        p ajoute x1
        p ajoute y1 + 1
        polygone(vue, p, #DC2626)
    fin pour
    // Chaque estimation est aussi marquée d'un point : la courbe existe
    // dès le premier tir, avant même d'avoir un segment à tracer.
    pour k de 0 à nbHisto - 1
        xp est un nombre
        yp est un nombre
        xp vaut 512 + arrondi(k * 164 / 79)
        yp vaut 452 - arrondi((histo[k] - aireVraie) * 46 / 300)
        yp vaut limiter(yp, 404, 498)
        cercle(vue, xp, yp, 2, #DC2626)
    fin pour
    label(vue, 512, 500, "vert : aire exacte", #94A3B8, 9)

    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 >= 404 et yc <= 438 alors
        si xc >= 24 et xc <= 86 alors
            combien vaut 1
        sinon si xc >= 94 et xc <= 156 alors
            combien vaut 10
        sinon si xc >= 164 et xc <= 234 alors
            combien vaut 100
        sinon si xc >= 242 et xc <= 320 alors
            combien vaut 1000
        sinon si xc >= 328 et xc <= 402 alors
            nbTirs vaut 0
            nbEau vaut 0
            nbPts vaut 0
            nbHisto vaut 0
            estim vaut 0
            message vaut "Comptage remis à zéro"
        sinon si xc >= 410 et xc <= 494 alors
            nouvelEtang vaut vrai
        fin si
    fin si

    // =========================================================
    //   LES TIRS
    //   Un tir au hasard sur tout le rectangle, puis une seule
    //   question : ce point est-il dans l'eau ?
    // =========================================================
    n est un nombre
    n vaut 0
    tant que n < combien
        n ajoute 1

        // Coordonnées en mètres, au dixième près
        tx est un nombre
        ty est un nombre
        tx vaut hasard(0, LARGM * 10) / 10
        ty vaut hasard(0, HAUTM * 10) / 10

        dedans vaut faux
        pour e de 0 à 3
            ddx vaut (tx - ecx[e]) / erx[e]
            ddy vaut (ty - ecy[e]) / ery[e]
            si ddx * ddx + ddy * ddy <= 1 alors
                dedans vaut vrai
            fin si
        fin pour

        nbTirs ajoute 1
        si dedans = vrai alors
            nbEau ajoute 1
        fin si

        // On ne mémorise l'impact que si l'écran peut encore l'afficher
        si nbPts < MAXPTS alors
            tirX[nbPts] vaut CHAMPX + tx * ECH
            tirY[nbPts] vaut CHAMPY + ty * ECH
            tirEau[nbPts] vaut dedans
            nbPts ajoute 1
        fin si
    fin tant que

    si combien > 0 alors
        // *** LA FORMULE ***
        estim vaut arrondi(AIRETOT * nbEau / nbTirs)

        // On garde la trace de l'estimation pour la courbe
        si nbHisto < 80 alors
            histo[nbHisto] vaut estim
            histoN[nbHisto] vaut nbTirs
            nbHisto ajoute 1
        sinon
            pour k de 0 à 78
                histo[k] vaut histo[k + 1]
                histoN[k] vaut histoN[k + 1]
            fin pour
            histo[79] vaut estim
            histoN[79] vaut nbTirs
        fin si

        message vaut "Sur " + nbTirs + " tirs, " + nbEau + " sont tombés dans l'eau"
    fin si
fin tant que
Pas encore noté