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

Explorer un labyrinthe grâce au backtracking
Article

Explorer un labyrinthe grâce au backtracking

Une animation qui fait comprendre l’algorithme

Lire l'articleReplier l'article

Le backtracking est souvent perçu comme une technique abstraite : on “essaie”, on “échoue”, on “revient en arrière”… mais ce n’est pas toujours évident de visualiser ce qui se passe réellement.
Ce programme transforme cette idée en animation vivante, où chaque étape du raisonnement devient visible.

Objectif : trouver un chemin de S à E dans un labyrinthe

Le labyrinthe est composé de cases libres, de murs, d’impasses et d’un point de départ et d’arrivée. L’algorithme explore les cases une par une, avance quand c’est possible, recule quand il est bloqué, et finit par trouver (ou non) un chemin valide.

Une visualisation pensée pour apprendre

L’animation montre :

  • les cases visitées en vert,
  • les impasses en rouge,
  • la case en cours d’examen en jaune,
  • la pile d’appels récursifs qui grandit et rétrécit en temps réel,
  • des compteurs qui suivent les visites, les retours arrière et la profondeur maximale atteinte.

On voit littéralement l’algorithme réfléchir, se tromper, corriger, progresser.

Le principe du backtracking illustré

L’algorithme suit une logique simple :

  • Avancer tant qu’une direction est possible.
  • Si toutes les directions échouent, marquer l’impasse et revenir en arrière.
  • Répéter jusqu’à trouver la sortie ou conclure qu’il n’y en a pas.

Cette mécanique, souvent difficile à imaginer, devient limpide grâce à l’animation.

Un final visuel

Si un chemin est trouvé, l’animation se termine par un clignotement doré du parcours complet : une manière élégante de mettre en valeur la solution.

Pourquoi ce programme est précieux pédagogiquement

  • Il dédramatise la récursion en la rendant visible.
  • Il montre que les impasses font partie du processus, et même qu’elles aident l’algorithme à “apprendre”.
  • Il permet de comprendre que le backtracking n’est pas magique : c’est une méthode systématique, patiente, méthodique.
  • Il offre une lecture intuitive d’un concept souvent jugé difficile.

Programme :

// Le backtracking expliqué : sortir d'un labyrinthe
@ toile et mémoire de l'animation
frames est un tableau
maToile est une toile
dimension(maToile, 650, 480)

@ Compteurs de l'animation
// On les range dans un tableau : ainsi les fonctions peuvent
// les modifier sans problème de portée de variable.
//
//   stats[0] = cases visitées        stats[5] = colonne examinée
//   stats[1] = retours arrière       stats[6] = ligne examinée
//   stats[2] = profondeur de la pile stats[7] = mode victoire (0/1)
//   stats[3] = profondeur maximale   stats[8] = clignotement (0/1)
//   stats[4] = 0 exploration / 1 retour arrière / 2 succès
stats est un tableau
pour k de 0 à 8
    stats ajoute 0
fin pour
stats[5] vaut 99 // 99 = "aucune case examinée" (hors plateau)
stats[6] vaut 99

@ Le labyrinthe (0 = libre, 1 = mur)
//        col  0  1  2  3  4  5
//  lig 0      S  .  .  .  .  .    <- grand piège : couloir sans issue
//  lig 1      .  #  #  #  #  .
//  lig 2      .  .  .  .  #  .
//  lig 3      #  .  #  #  #  #
//  lig 4      .  .  .  .  .  .
//  lig 5      .  #  #  #  #  E
//
// L'algorithme essaie DROITE en premier : il fonce donc dans le
// couloir du haut, explore 8 cases pour rien, puis dépile tout.
plateau est un tableau

ligne0 est un tableau
ligne0 ajoute 0
ligne0 ajoute 0
ligne0 ajoute 0
ligne0 ajoute 0
ligne0 ajoute 0
ligne0 ajoute 0
plateau ajoute ligne0

ligne1 est un tableau
ligne1 ajoute 0
ligne1 ajoute 1
ligne1 ajoute 1
ligne1 ajoute 1
ligne1 ajoute 1
ligne1 ajoute 0
plateau ajoute ligne1

ligne2 est un tableau
ligne2 ajoute 0
ligne2 ajoute 0
ligne2 ajoute 0
ligne2 ajoute 0
ligne2 ajoute 1
ligne2 ajoute 0
plateau ajoute ligne2

ligne3 est un tableau
ligne3 ajoute 1
ligne3 ajoute 0
ligne3 ajoute 1
ligne3 ajoute 1
ligne3 ajoute 1
ligne3 ajoute 1
plateau ajoute ligne3

ligne4 est un tableau
ligne4 ajoute 0
ligne4 ajoute 0
ligne4 ajoute 0
ligne4 ajoute 0
ligne4 ajoute 0
ligne4 ajoute 0
plateau ajoute ligne4

ligne5 est un tableau
ligne5 ajoute 0
ligne5 ajoute 1
ligne5 ajoute 1
ligne5 ajoute 1
ligne5 ajoute 1
ligne5 ajoute 0
plateau ajoute ligne5

@ Dessin d'une image de l'animation
procédure dessinerEtat()
    effacer(maToile)
    remplir(maToile, #0e121b)

    valeurCase est un nombre
    x est un nombre
    y est un nombre
    largeurEtapes est un nombre
    largeurRetours est un nombre
    nbBlocs est un nombre
    dernierBloc est un nombre
    xb est un nombre

    // Bandeau de titre
    rectangle_arrondi(maToile, 16, 12, 618, 44, 12, #1b2233)
    label(maToile, 36, 36, "BACKTRACKING : essayer, échouer, revenir en arrière", #ecf0f1, 17)
    label(maToile, 36, 51, "Recherche récursive d'un chemin de S vers E", #7f8fa6, 11)

    // Cadre du plateau
    rectangle_arrondi(maToile, 16, 66, 342, 342, 16, #1b2233)

    pour lig de 0 à 5
        pour col de 0 à 5
            valeurCase vaut plateau[lig][col]

            // Position de la case : 22 et 72 = première case, 56 = écart
            x vaut 22 + col * 56
            y vaut 72 + lig * 56

            // Halo jaune autour de la case en cours d'examen
            si col = stats[5] et lig = stats[6] alors
                rectangle_arrondi(maToile, x - 5, y - 5, 60, 60, 14, #f6c945)
            fin si

            // Ombre portée : donne du relief aux cases
            rectangle_arrondi(maToile, x + 2, y + 3, 50, 50, 10, #080b11)

            si valeurCase = 1 alors
                // Mur : rectangle sombre avec un liseré plus clair
                rectangle_arrondi(maToile, x, y, 50, 50, 10, #33405a)
                rectangle_arrondi(maToile, x + 8, y + 8, 34, 34, 7, #3d4d6b)
            sinon si valeurCase = 2 alors
                // Chemin en cours : encore dans la pile d'appels
                si stats[7] = 1 et stats[8] = 1 alors
                    rectangle_arrondi(maToile, x, y, 50, 50, 10, #f6d743)
                sinon
                    rectangle_arrondi(maToile, x, y, 50, 50, 10, #27d17c)
                fin si
                rectangle_arrondi(maToile, x + 18, y + 18, 14, 14, 7, #ffffff)
            sinon si valeurCase = 3 alors
                // Impasse : on ne repassera plus jamais par là
                rectangle_arrondi(maToile, x, y, 50, 50, 10, #e85b5b)
                label(maToile, x + 17, y + 35, "X", #ffffff, 22)
            sinon
                // Case libre encore inexplorée
                rectangle_arrondi(maToile, x, y, 50, 50, 10, #e8eef6)
            fin si
        fin pour
    fin pour

    // Pastilles Départ (0,0) et Arrivée (5,5)
    rectangle_arrondi(maToile, 35, 85, 24, 24, 12, #1b2233)
    label(maToile, 42, 102, "S", #ffffff, 15)
    rectangle_arrondi(maToile, 315, 365, 24, 24, 12, #1b2233)
    label(maToile, 322, 382, "E", #ffffff, 15)

    // Panneau latéral : légende et compteurs
    rectangle_arrondi(maToile, 366, 66, 268, 342, 16, #1b2233)
    label(maToile, 388, 92, "TABLEAU DE BORD", #ecf0f1, 14)
    rectangle_arrondi(maToile, 388, 100, 224, 2, 1, #2b3650)

    label(maToile, 388, 122, "LÉGENDE", #6c7d95, 11)

    rectangle_arrondi(maToile, 388, 131, 16, 16, 5, #33405a)
    label(maToile, 412, 144, "Mur infranchissable", #dfe6ee, 13)

    rectangle_arrondi(maToile, 388, 155, 16, 16, 5, #e8eef6)
    label(maToile, 412, 168, "Case libre, non visitée", #dfe6ee, 13)

    rectangle_arrondi(maToile, 388, 179, 16, 16, 5, #27d17c)
    label(maToile, 412, 192, "Chemin en cours (pile)", #dfe6ee, 13)

    rectangle_arrondi(maToile, 388, 203, 16, 16, 5, #e85b5b)
    label(maToile, 412, 216, "Impasse : retour arrière", #dfe6ee, 13)

    rectangle_arrondi(maToile, 388, 227, 16, 16, 5, #f6c945)
    label(maToile, 412, 240, "Case examinée", #dfe6ee, 13)

    rectangle_arrondi(maToile, 388, 254, 224, 2, 1, #2b3650)

    // Barre : nombre de cases visitées
    label(maToile, 388, 276, "Cases visitées", #6c7d95, 11)
    rectangle_arrondi(maToile, 388, 282, 224, 8, 4, #2b3650)
    largeurEtapes vaut stats[0] * 6
    si largeurEtapes > 224 alors
        largeurEtapes vaut 224
    fin si
    si largeurEtapes > 0 alors
        rectangle_arrondi(maToile, 388, 282, largeurEtapes, 8, 4, #3fa9f5)
    fin si

    // Barre : nombre de retours arrière
    label(maToile, 388, 306, "Retours arrière (backtracks)", #6c7d95, 11)
    rectangle_arrondi(maToile, 388, 312, 224, 8, 4, #2b3650)
    largeurRetours vaut stats[1] * 18
    si largeurRetours > 224 alors
        largeurRetours vaut 224
    fin si
    si largeurRetours > 0 alors
        rectangle_arrondi(maToile, 388, 312, largeurRetours, 8, 4, #e85b5b)
    fin si

    // La pile d'appels récursifs, dessinée bloc par bloc
    label(maToile, 388, 336, "Pile d'appels récursifs", #6c7d95, 11)
    pour i de 0 à 11
        rectangle_arrondi(maToile, 388 + i * 18, 344, 15, 24, 4, #232d44)
    fin pour

    nbBlocs vaut stats[2]
    si nbBlocs > 12 alors
        nbBlocs vaut 12
    fin si
    dernierBloc vaut nbBlocs - 1
    si nbBlocs > 0 alors
        pour i de 0 à dernierBloc
            xb vaut 388 + i * 18
            si i = dernierBloc alors
                rectangle_arrondi(maToile, xb, 344, 15, 24, 4, #f6c945)
            sinon
                rectangle_arrondi(maToile, xb, 344, 15, 24, 4, #3fa9f5)
            fin si
        fin pour
    fin si
    label(maToile, 388, 388, "1 bloc = 1 appel récursif", #6c7d95, 11)

    // Barre d'état : ce que fait l'algorithme à cet instant
    rectangle_arrondi(maToile, 16, 418, 618, 46, 12, #1b2233)
    si stats[4] = 2 alors
        rectangle_arrondi(maToile, 30, 430, 6, 22, 3, #f6d743)
        label(maToile, 48, 448, "Chemin trouvé ! La pile contient la solution complète.", #f6d743, 13)
    sinon si stats[4] = 1 alors
        rectangle_arrondi(maToile, 30, 430, 6, 22, 3, #e85b5b)
        label(maToile, 48, 448, "Impasse : aucune direction possible, on dépile et on recule.", #ffb3b3, 13)
    sinon
        rectangle_arrondi(maToile, 30, 430, 6, 22, 3, #27d17c)
        label(maToile, 48, 448, "Exploration : on avance et on empile un appel récursif.", #b8f0d3, 13)
    fin si

    // On mémorise l'image
    frames ajoute maToile
fin procédure

@ L'algorithme de backtracking
// Principe : essayer une direction, et si elle échoue, revenir
// exactement dans l'état d'avant pour essayer la suivante.
fonction chercherChemin(c, l)
    // 1. Sortie du plateau ?
    si c < 0 ou c > 5 ou l < 0 ou l > 5 alors
        retourne faux
    fin si

    valeurCase est un nombre
    valeurCase vaut plateau[l][c]

    // 2. Mur (1), case déjà dans le chemin (2) ou impasse connue (3)
    // Le cas "3" est important : une impasse déjà découverte n'est
    // jamais réexplorée, l'algorithme apprend de ses échecs.
    si valeurCase = 1 ou valeurCase = 2 ou valeurCase = 3 alors
        retourne faux
    fin si

    // 3. On empile un appel : la profondeur augmente
    stats[2] vaut stats[2] + 1
    si stats[2] > stats[3] alors
        stats[3] vaut stats[2]
    fin si

    // 4. On marque la case comme "chemin en cours"
    plateau[l][c] vaut 2
    stats[0] vaut stats[0] + 1
    stats[4] vaut 0
    stats[5] vaut c
    stats[6] vaut l
    appelle dessinerEtat()

    // 5. Condition d'arrêt : on est arrivé en bas à droite
    si c = 5 et l = 5 alors
        stats[4] vaut 2
        appelle dessinerEtat()
        retourne vrai
    fin si

    // 6. On essaie les 4 directions, dans l'ordre
    // Droite
    si chercherChemin(c + 1, l) = vrai alors
        retourne vrai
    fin si
    // Bas
    si chercherChemin(c, l + 1) = vrai alors
        retourne vrai
    fin si
    // Gauche
    si chercherChemin(c - 1, l) = vrai alors
        retourne vrai
    fin si
    // Haut
    si chercherChemin(c, l - 1) = vrai alors
        retourne vrai
    fin si

    // 7. RETOUR ARRIÈRE
    // Les 4 directions ont échoué : cette case ne mène nulle part.
    // On la marque en rouge, on dépile, et on rend "faux" à
    // l'appel précédent qui essaiera sa direction suivante.
    plateau[l][c] vaut 3
    stats[1] vaut stats[1] + 1
    stats[2] vaut stats[2] - 1
    stats[4] vaut 1
    stats[5] vaut c
    stats[6] vaut l
    appelle dessinerEtat()

    retourne faux
fin fonction

@ Lancement du programme
// Trois images identiques au début : le temps de lire le plateau
appelle dessinerEtat()
appelle dessinerEtat()
appelle dessinerEtat()

resultatFinal est un booléen
resultatFinal vaut chercherChemin(0, 0)

si resultatFinal = vrai alors
    // Petit final : le chemin solution clignote en or
    stats[4] vaut 2
    stats[7] vaut 1
    stats[5] vaut 99
    stats[6] vaut 99
    pour p de 0 à 9
        si stats[8] = 0 alors
            stats[8] vaut 1
        sinon
            stats[8] vaut 0
        fin si
        appelle dessinerEtat()
    fin pour
sinon
    affiche "Aucun chemin possible."
fin si

animation(frames, 250)
Pas encore noté
2048 : Comprendre la logique derrière le jeu
Article

Créer un 2048 en programmation

Comprendre la logique derrière le jeu

Lire l'articleReplier l'article

Le célèbre jeu 2048 paraît simple : on glisse des tuiles, elles fusionnent, le score augmente… mais derrière cette simplicité se cache une mécanique algorithmique très riche.

Ce programme propose une version complète du jeu, entièrement codée, qui permet de comprendre en profondeur comment fonctionne 2048.

Un plateau dynamique et réactif

Le jeu repose sur un plateau 4×4 où chaque case contient une tuile numérotée ou reste vide.
À chaque tour :

  • le joueur appuie sur Z Q S D pour glisser les tuiles,
  • les tuiles se déplacent dans la direction choisie,
  • les tuiles identiques se fusionnent pour former une valeur plus grande,
  • une nouvelle tuile apparaît aléatoirement.

Cette mécanique crée un jeu à la fois stratégique et imprévisible.

La fusion : le cœur du gameplay

Lorsqu’on glisse les tuiles :

  • les cases vides sont d’abord éliminées,
  • les tuiles identiques se regroupent,
  • une seule fusion par déplacement est autorisée,
  • le score augmente en fonction des valeurs fusionnées.

Cette logique garantit un comportement fidèle au vrai 2048.

Détection de fin de partie

Le programme vérifie en continu si le joueur peut encore jouer :

  • une case vide = un coup possible,
  • deux tuiles voisines identiques = une fusion possible.

Si plus aucun mouvement n’est possible, le jeu affiche un écran de fin et propose de recommencer.

Objectif 2048 : un événement spécial

Quand la tuile 2048 apparaît :

  • le jeu célèbre l’événement,
  • le joueur peut choisir de continuer pour viser 4096,
  • le score et le meilleur score sont mis en avant.

C’est une belle manière de valoriser la progression du joueur.

Une interface claire et pédagogique

Le programme affiche :

  • le plateau,
  • les tuiles colorées selon leur valeur,
  • le score et le meilleur score,
  • les touches utilisées,
  • des messages d’aide et de feedback.

Tout est pensé pour rendre le jeu agréable et compréhensible, même pour un débutant.

Pourquoi ce programme est idéal pour apprendre

Ce projet permet de découvrir :

  • la gestion d’un tableau 2D,
  • les déplacements conditionnels,
  • la fusion de données,
  • la génération aléatoire,
  • la détection d’état du jeu,
  • la création d’une interface graphique.

C’est un excellent exercice pour progresser en logique, en algorithmique et en conception de jeux.

Programme :

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

// Plateau 4x4 : 0 = case vide
plateau est un tableau
pour i de 0 à 3
    ligne_p est un tableau
    pour j de 0 à 3
        ligne_p ajoute 0
    fin pour
    plateau ajoute ligne_p
fin pour

// Compteurs rangés dans un tableau : les procédures peuvent ainsi
// les modifier sans problème de portée de variable.
//   compteurs[0] = score      compteurs[2] = un mouvement a eu lieu (0/1)
//   compteurs[1] = meilleur   compteurs[3] = 2048 atteint (0 non, 1 oui, 2 déjà fêté)
compteurs est un tableau
pour k de 0 à 3
    compteurs ajoute 0
fin pour

message est un texte
message vaut "Joue avec les touches Z Q S D"

// Dernière touche utilisée : sert à éclairer la bonne touche à l'écran
derniere est un texte
derniere vaut ""


@ Couleur de fond d'une tuile
fonction fondDeTuile(v)
    res est un texte
    si v = 0 alors
        res vaut #1B2436
    sinon si v = 2 alors
        res vaut #EEE4DA
    sinon si v = 4 alors
        res vaut #EDE0C8
    sinon si v = 8 alors
        res vaut #F2B179
    sinon si v = 16 alors
        res vaut #F59563
    sinon si v = 32 alors
        res vaut #F67C5F
    sinon si v = 64 alors
        res vaut #F65E3B
    sinon si v = 128 alors
        res vaut #EDCF72
    sinon si v = 256 alors
        res vaut #EDCC61
    sinon si v = 512 alors
        res vaut #EDC850
    sinon si v = 1024 alors
        res vaut #EDC53F
    sinon si v = 2048 alors
        res vaut #EDC22E
    sinon
        res vaut #3C3A32
    fin si
    retourne res
fin fonction

@ Couleur du chiffre d'une tuile
fonction encreDeTuile(v)
    res est un texte
    si v <= 4 alors
        res vaut #6B6357
    sinon
        res vaut #FFFFFF
    fin si
    retourne res
fin fonction


@ Dessin d'une tuile
procédure dessinerTuile(t, x, y, v)
    fond est un texte
    fond vaut fondDeTuile(v)
    rectangle_arrondi(t, x, y, 74, 74, 11, fond)

    si v > 0 alors
        corpsTxt est un nombre
        decal est un nombre
        // Le chiffre est centré "à la main" : plus il est long,
        // plus on décale le texte vers la gauche.
        si v < 10 alors
            corpsTxt vaut 30
            decal vaut 28
        sinon si v < 100 alors
            corpsTxt vaut 30
            decal vaut 20
        sinon si v < 1000 alors
            corpsTxt vaut 26
            decal vaut 12
        sinon
            corpsTxt vaut 21
            decal vaut 9
        fin si

        coulTxt est un texte
        txt est un texte
        coulTxt vaut encreDeTuile(v)
        txt vaut "" + v
        label(t, x + decal, y + 47, txt, coulTxt, corpsTxt)
    fin si
fin procédure


@ Dessin d'une touche du clavier
procédure dessinerTouche(t, x, y, lettre, active)
    si active = vrai alors
        rectangle_arrondi(t, x, y, 52, 40, 10, #FDE68A)
        label(t, x + 19, y + 27, lettre, #1E293B, 19)
    sinon
        rectangle_arrondi(t, x, y, 52, 40, 10, #1E293B)
        rectangle_arrondi(t, x + 6, y + 5, 40, 11, 5, #263448)
        label(t, x + 19, y + 27, lettre, #CBD5E1, 19)
    fin si
fin procédure


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

    // En-tête : titre, score et meilleur score
    rectangle_arrondi(t, 10, 8, 380, 88, 14, #1E293B)
    label(t, 22, 46, "2048", #F8FAFC, 28)

    rectangle_arrondi(t, 170, 18, 100, 44, 10, #0F172A)
    label(t, 184, 36, "SCORE", #94A3B8, 11)
    label(t, 184, 56, "" + compteurs[0], #F8FAFC, 17)

    rectangle_arrondi(t, 280, 18, 100, 44, 10, #0F172A)
    label(t, 294, 36, "MEILLEUR", #94A3B8, 11)
    label(t, 294, 56, "" + compteurs[1], #F8FAFC, 17)

    label(t, 22, 86, msg, #FDE68A, 13)

    // Plateau : tuile de 74 px, écart de 80 px, coin en 43 et 116
    rectangle_arrondi(t, 31, 104, 338, 338, 16, #1E293B)
    xP est un nombre
    yP est un nombre
    pour i de 0 à 3
        pour j de 0 à 3
            xP vaut 43 + j * 80
            yP vaut 116 + i * 80
            appelle dessinerTuile(t, xP, yP, plateau[i][j])
        fin pour
    fin pour

    // Rappel des touches : Z au-dessus, Q S D en dessous
    appelle dessinerTouche(t, 174, 450, "Z", tPrec = "Z")
    appelle dessinerTouche(t, 108, 498, "Q", tPrec = "Q")
    appelle dessinerTouche(t, 174, 498, "S", tPrec = "S")
    appelle dessinerTouche(t, 240, 498, "D", tPrec = "D")

    affiche t
fin procédure


@ Glissement de toutes les tuiles
// sens : 0 gauche, 1 droite, 2 haut, 3 bas
// Une ligne est traitée en trois temps : on la lit dans l'ordre du
// déplacement, on enlève les trous, on fusionne les voisines égales.
procédure glisserTuiles(sens)
    r est un nombre
    c est un nombre
    nbS est un nombre
    nbF est un nombre
    idx est un nombre
    fusionnee est un nombre
    nouv est un nombre
    memeValeur est un booléen

    compteurs[2] vaut 0

    pour lg de 0 à 3

        // 1. Lecture de la ligne (ou de la colonne) dans le bon sens
        lues est un tableau
        pour p de 0 à 3
            si sens = 0 alors
                r vaut lg
                c vaut p
            sinon si sens = 1 alors
                r vaut lg
                c vaut 3 - p
            sinon si sens = 2 alors
                r vaut p
                c vaut lg
            sinon
                r vaut 3 - p
                c vaut lg
            fin si
            lues ajoute plateau[r][c]
        fin pour

        // 2. On enlève les cases vides
        sansTrous est un tableau
        nbS vaut 0
        pour p de 0 à 3
            si lues[p] > 0 alors
                sansTrous ajoute lues[p]
                nbS ajoute 1
            fin si
        fin pour

        // 3. Fusion des voisines identiques (chaque tuile ne fusionne
        //    qu'une seule fois par déplacement)
        apresFusion est un tableau
        nbF vaut 0
        idx vaut 0
        tant que idx < nbS
            memeValeur vaut faux
            si idx < nbS - 1 alors
                si sansTrous[idx] = sansTrous[idx + 1] alors
                    memeValeur vaut vrai
                fin si
            fin si

            si memeValeur = vrai alors
                fusionnee vaut sansTrous[idx] * 2
                apresFusion ajoute fusionnee
                nbF ajoute 1
                compteurs[0] vaut compteurs[0] + fusionnee
                si fusionnee >= 2048 alors
                    si compteurs[3] = 0 alors
                        compteurs[3] vaut 1
                    fin si
                fin si
                idx vaut idx + 2
            sinon
                apresFusion ajoute sansTrous[idx]
                nbF ajoute 1
                idx vaut idx + 1
            fin si
        fin tant que

        // 4. Réécriture de la ligne, complétée par des cases vides
        pour p de 0 à 3
            nouv vaut 0
            si p < nbF alors
                nouv vaut apresFusion[p]
            fin si

            si sens = 0 alors
                r vaut lg
                c vaut p
            sinon si sens = 1 alors
                r vaut lg
                c vaut 3 - p
            sinon si sens = 2 alors
                r vaut p
                c vaut lg
            sinon
                r vaut 3 - p
                c vaut lg
            fin si

            si plateau[r][c] <> nouv alors
                compteurs[2] vaut 1
            fin si
            plateau[r][c] vaut nouv
        fin pour
    fin pour
fin procédure


@ Apparition d'une nouvelle tuile
procédure poserNouvelleTuile()
    libresL est un tableau
    libresC est un tableau
    nbL est un nombre
    nbL vaut 0

    pour i de 0 à 3
        pour j de 0 à 3
            si plateau[i][j] = 0 alors
                libresL ajoute i
                libresC ajoute j
                nbL ajoute 1
            fin si
        fin pour
    fin pour

    si nbL > 0 alors
        choix est un nombre
        rr est un nombre
        cc est un nombre
        valNouvelle est un nombre
        choix vaut hasard(1, nbL) - 1
        rr vaut libresL[choix]
        cc vaut libresC[choix]
        // Un 4 une fois sur dix, un 2 le reste du temps
        valNouvelle vaut 2
        si hasard(1, 10) = 1 alors
            valNouvelle vaut 4
        fin si
        plateau[rr][cc] vaut valNouvelle
    fin si
fin procédure


@ Y a-t-il encore un coup possible ?
// La partie est perdue quand le plateau est plein ET qu'aucune paire
// de voisines n'est identique. On parcourt tout le plateau avec un
// drapeau, sans sortie anticipée de boucle.
fonction aucunCoupPossible()
    coupTrouve est un booléen
    coupTrouve vaut faux

    pour i de 0 à 3
        pour j de 0 à 3
            // Une case vide est toujours un coup possible
            si plateau[i][j] = 0 alors
                coupTrouve vaut vrai
            fin si
            // Deux voisines horizontales identiques : fusion possible
            si j < 3 alors
                si plateau[i][j] = plateau[i][j + 1] alors
                    coupTrouve vaut vrai
                fin si
            fin si
            // Deux voisines verticales identiques : fusion possible
            si i < 3 alors
                si plateau[i][j] = plateau[i + 1][j] alors
                    coupTrouve vaut vrai
                fin si
            fin si
        fin pour
    fin pour

    perdu est un booléen
    perdu vaut vrai
    si coupTrouve = vrai alors
        perdu vaut faux
    fin si
    retourne perdu
fin fonction


@ Écran de fin de partie
procédure afficherFin(t)
    rectangle_arrondi(t, 40, 190, 320, 170, 20, #1E293B)
    rectangle_arrondi(t, 40, 190, 320, 8, 4, #EF4444)
    label(t, 68, 240, "PARTIE TERMINÉE", #FCA5A5, 24)
    label(t, 68, 274, "Score : " + compteurs[0], #F8FAFC, 16)
    label(t, 68, 300, "Meilleur : " + compteurs[1], #CBD5E1, 13)
    label(t, 60, 338, "Appuie sur Z, Q, S ou D pour rejouer", #93C5FD, 13)
    affiche t
fin procédure

@ Écran des 2048 atteints
procédure afficherObjectif(t)
    rectangle_arrondi(t, 40, 190, 320, 170, 20, #1E293B)
    label(t, 68, 238, "2048 ATTEINT !", #FDE68A, 26)
    label(t, 68, 274, "Score : " + compteurs[0], #F8FAFC, 16)
    label(t, 68, 300, "Tu peux continuer pour viser 4096", #CBD5E1, 13)
    label(t, 60, 338, "Appuie sur Z, Q, S ou D pour continuer", #93C5FD, 13)
    affiche t
fin procédure


@ Boucle principale
appelle poserNouvelleTuile()
appelle poserNouvelleTuile()

enJeu est un booléen
enJeu vaut vrai

tant que enJeu = vrai
    appelle afficherJeu(maToile, message, derniere)

    // Le programme attend l'une de ces 4 touches et range son numéro
    // dans "action" : 1 pour 'z', 2 pour 'q', 3 pour 's', 4 pour 'd'.
    action est un nombre
    appuyer ['z', 'q', 's', 'd'] dans action

    // On traduit le numéro de touche en sens de glissement
    // (0 gauche, 1 droite, 2 haut, 3 bas)
    sensJoueur est un nombre
    si action = 1 alors
        sensJoueur vaut 2
        derniere vaut "Z"
    sinon si action = 2 alors
        sensJoueur vaut 0
        derniere vaut "Q"
    sinon si action = 3 alors
        sensJoueur vaut 3
        derniere vaut "S"
    sinon
        sensJoueur vaut 1
        derniere vaut "D"
    fin si

    appelle glisserTuiles(sensJoueur)

    si compteurs[2] = 1 alors
        appelle poserNouvelleTuile()
        si compteurs[0] > compteurs[1] alors
            compteurs[1] vaut compteurs[0]
        fin si
        message vaut "Score : " + compteurs[0]
    sinon
        message vaut "Ce mouvement ne change rien, essaie un autre sens"
    fin si

    // Première fois que 2048 apparaît
    si compteurs[3] = 1 alors
        compteurs[3] vaut 2
        appelle afficherJeu(maToile, "Objectif atteint !", derniere)
        appelle afficherObjectif(maToile)
        suiteObj est un nombre
        appuyer ['z', 'q', 's', 'd'] dans suiteObj
    fin si

    perduMaintenant est un booléen
    perduMaintenant vaut aucunCoupPossible()

    si perduMaintenant = vrai alors
        appelle afficherJeu(maToile, "Plateau plein, aucune fusion possible", derniere)
        appelle afficherFin(maToile)
        suiteFin est un nombre
        appuyer ['z', 'q', 's', 'd'] dans suiteFin

        pour i de 0 à 3
            pour j de 0 à 3
                plateau[i][j] vaut 0
            fin pour
        fin pour
        compteurs[0] vaut 0
        compteurs[2] vaut 0
        compteurs[3] vaut 0
        derniere vaut ""
        appelle poserNouvelleTuile()
        appelle poserNouvelleTuile()
        message vaut "Nouvelle partie !"
    fin si
fin tant que
Pas encore noté
Créer un jeu du type Cascade est un excellent projet pour comprendre les bases de la logique de jeu
Article

Comment créer un jeu comme : Cascade

Créer un jeu du type Cascade est un excellent projet pour comprendre les bases de la logique de jeu

Lire l'articleReplier l'article

Créer un jeu de type Cascade est un excellent moyen d’apprendre la logique des jeux de réflexion, la gestion d’un plateau, les réactions en chaîne et l’interaction avec le joueur. Même sans entrer dans le code, on peut comprendre clairement les grandes étapes nécessaires pour concevoir un tel jeu.

Concevoir le plateau de jeu

Un jeu comme Cascade repose sur une grille composée de tuiles colorées.
Il faut définir :

  • la taille du plateau,
  • les différentes couleurs ou types de tuiles,
  • la manière dont elles apparaissent au début de la partie.

Le plateau est le cœur du jeu : c’est là que tout se passe.

Définir les règles et les objectifs

Pour donner un but au joueur, on fixe :

  • une couleur à collecter,
  • une quantité de tuiles à obtenir,
  • un nombre de coups limité,
  • des niveaux qui deviennent progressivement plus difficiles.

Ces règles créent un cadre clair et motivant.

Détecter les combinaisons

Le principe de Cascade repose sur la détection d’alignements :

  • horizontaux,
  • verticaux,
  • ou en carré.

Quand plusieurs tuiles identiques sont regroupées, elles doivent être reconnues comme une combinaison à supprimer.
C’est cette logique qui crée les réactions en chaîne.

Gérer les réactions : destruction, gravité, nouvelles tuiles

Une fois une combinaison trouvée :

  • les tuiles disparaissent,
  • celles du dessus tombent pour combler les trous,
  • de nouvelles tuiles apparaissent en haut.

Ce cycle peut se répéter plusieurs fois, créant l’effet de cascade qui donne son nom au jeu.

Ajouter des tuiles spéciales

Pour enrichir le gameplay, on peut introduire :

  • des fusées qui détruisent une ligne,
  • des bombes qui détruisent une zone,
  • des arc-en-ciel qui éliminent une couleur entière.

Ces éléments rendent le jeu plus dynamique et plus stratégique.

Gérer les actions du joueur

Le joueur doit pouvoir :

  • sélectionner une tuile,
  • choisir une tuile voisine,
  • échanger les deux,
  • déclencher les effets des tuiles spéciales.

L’interface doit être claire, intuitive et réactive.

Déterminer la fin de partie

Une partie peut se terminer :

  • par une victoire si l’objectif est atteint,
  • par une défaite si les coups sont épuisés,
  • par un blocage si aucun coup n’est possible.

Dans chaque cas, un écran dédié informe le joueur et lui propose de continuer ou de recommencer.

Faire évoluer le jeu

Pour garder l’intérêt du joueur :

  • les niveaux augmentent,
  • les objectifs deviennent plus exigeants,
  • les coups disponibles diminuent,
  • les couleurs à collecter changent.
  • Cette progression donne au jeu une vraie durée de vie.

Conclusion

Créer un jeu comme Cascade, c’est combiner une logique de grille, des règles simples, des réactions en chaîne spectaculaires et une interface claire.

Pas encore noté
Untangle : anatomie d’un jeu de démêlage géométrique basé sur les graphes
Article

Untangle

Anatomie d’un jeu de démêlage géométrique basé sur les graphes

Lire l'articleReplier l'article

Le programme présenté met en œuvre un jeu de réflexion très apprécié : Untangle, un puzzle où le joueur doit déplacer des sommets pour éliminer tous les croisements entre les arêtes d’un graphe.

Ce jeu repose sur une combinaison de géométrie, détection de collisions, génération procédurale, et une interface graphique réactive.

L’objectif est simple : déplacer les points jusqu’à ce que plus aucune arête ne se croise.
Mais derrière cette simplicité se cache une architecture algorithmique riche.

Configuration générale

Le jeu s’affiche dans une zone de :

  • 360 px de large
  • une zone d’en-tête de 40 px
  • une aire de jeu de 360 px
  • une zone de pied de 44 px

Le nombre de sommets dépend du niveau :

  • Petit : 8 sommets
  • Moyen : 12 sommets
  • Grand : 16 sommets

Chaque sommet est représenté par un cercle de rayon 7 px, et les interactions reposent sur des distances pré-calculées :

  • PRISE = 324 ? distance maximale pour sélectionner un sommet
  • ECART = 1024 ? distance minimale entre deux sommets

Ces valeurs sont des distances au carré, ce qui évite d’utiliser des racines carrées dans les calculs.

Géométrie : le cœur du jeu Le programme repose sur plusieurs fonctions géométriques essentielles.

Orientation de trois points
La fonction orient(s1, s2, s3) détermine si le triplet est :

  • en rotation trigonométrique (1),
  • en rotation horaire (-1),
  • ou aligné (0).

Cette orientation est la base de la détection des croisements.

Détection de croisement entre deux segments
La fonction croisement_sommets(e1, e2, f1, f2) vérifie si les segments [e1,e2] et [f1,f2] se croisent.

Elle applique le test classique :

  • les extrémités de chaque segment doivent être de part et d’autre de l’autre segment,
  • les segments partageant un sommet ne comptent jamais comme croisés.

Vérification qu’un sommet ne repose pas sur une arête
La fonction sommet_sur_arete(w, u, v) empêche la génération d’arêtes qui passeraient trop près d’un sommet.
Elle utilise une projection perpendiculaire et compare des distances au carré.

Validation d’une arête
La fonction arete_valide(u, v) garantit qu’une nouvelle arête :

  • n’existe pas déjà,
  • ne croise aucune arête existante,
  • ne passe pas sur un sommet.
  • C’est la clé pour générer un graphe planaire.

Génération du réseau : un graphe planaire aléatoire

La génération se déroule en plusieurs étapes.

Placement des sommets de la solution
Les sommets sont placés aléatoirement mais espacés :

« On compare des carrés pour ne pas avoir besoin de racine carrée. »

Si trop d’essais échouent, le programme accepte une position pour éviter les boucles infinies.

Mélange des paires de sommets
Toutes les paires (u,v) sont listées puis mélangées.
Cela permet de tester les arêtes dans un ordre aléatoire.

**Ajout des arêtes valides
*Le programme ajoute jusqu’à 2 NB_SOMMETS arêtes, en respectant la planéité.

Aucun sommet isolé
Si un sommet n’a aucune arête, le programme lui en ajoute une, toujours en respectant les contraintes géométriques.

Le résultat est un graphe planaire, sans croisement, qui servira de solution.

Mélange : rendre le puzzle jouable

Une fois le graphe généré, les sommets sont permutés sur les positions de la solution.
Cela garantit que :

  • le graphe reste résoluble,
  • mais les arêtes se croisent.

Le programme vérifie qu’il y a au moins un croisement avant de commencer la partie.

Interface et dessin

La procédure dessiner_scene() construit l’affichage :

Fond et titre

  • Fond bleu nuit
  • Titre rouge (jeu) ou vert (victoire)

Barre de progression
Elle indique la proportion d’arêtes propres (sans croisement).

Arêtes
Les arêtes sont dessinées en deux passes :

  • Arêtes propres en bleu
  • Arêtes croisées en rouge, plus épaisses

Sommets
Chaque sommet est un cercle :

  • blanc (normal)
  • jaune (sélectionné)

Barre de boutons
Quatre actions :

  • Nouveau
  • Annuler
  • Mélanger
  • Taille du réseau

Boucle de jeu : interaction et logique

À chaque clic :

Sélection d’un sommet
Le programme cherche le sommet le plus proche du clic (distance au carré).
Si un sommet est sélectionné, il est mis en surbrillance.

Déplacement d’un sommet
Si le joueur clique dans le vide :

  • le sommet sélectionné est déplacé,
  • le déplacement est enregistré dans l’historique,
  • les distances minimales sont vérifiées pour éviter les collisions.

Annulation
Le joueur peut annuler le dernier déplacement grâce à l’historique.

Détection des croisements
Après chaque action :

  • les arêtes croisées sont recalculées,
  • la barre de progression est mise à jour,
  • si nb_croix = 0, la victoire est déclarée.

Victoire

Lorsque le graphe est totalement démêlé :

  • toutes les arêtes deviennent vertes,
  • le titre change,
  • le joueur peut rejouer ou changer la taille du réseau.

Conclusion

Ce programme est un excellent exemple de jeu géométrique interactif, combinant :

  • calculs d’orientation,
  • détection de croisements,
  • génération de graphes planaires,
  • interface fluide,
  • gestion de l’historique,
  • interactions précises.

Le résultat est un puzzle élégant, stimulant, et techniquement solide, où chaque déplacement rapproche le joueur de la satisfaction d’un réseau parfaitement démêlé.

Programme :

@ Configuration et Variables Globales
LARGEUR est un nombre vaut 360
HAUT_ENTETE est un nombre vaut 40
ZONE est un nombre vaut 360 // hauteur de l'aire de jeu
HAUT_PIED est un nombre vaut 44

niveau est un nombre vaut 1 // 0 = Petit (8), 1 = Moyen (12), 2 = Grand (16)
NB_SOMMETS est un nombre vaut 12
RAYON est un nombre vaut 7 // rayon d'un sommet
PRISE est un nombre vaut 324 // 18 px au carré : distance de sélection
ECART est un nombre vaut 1024 // 32 px au carré : écart minimal entre deux sommets

slot_x est un tableau // positions de la solution (jamais modifiées)
slot_y est un tableau
pos_x est un tableau // positions actuelles des sommets
pos_y est un tableau
ar_a est un tableau // arêtes : sommet de départ
ar_b est un tableau // arêtes : sommet d'arrivée
croise est un tableau // 1 si l'arête est impliquée dans un croisement

hist_v est un tableau // historique : sommet déplacé
hist_x est un tableau // historique : ancienne abscisse
hist_y est un tableau // historique : ancienne ordonnée

gagne est un booléen vaut faux
rejouer est un booléen vaut faux
continuer est un booléen vaut vrai
doit_melanger est un booléen vaut vrai
a_selection est un booléen vaut faux
selection est un nombre vaut 0
nb_croix est un nombre vaut 0 // nombre de paires d'arêtes qui se croisent
nb_propres est un nombre vaut 0 // nombre d'arêtes sans aucun croisement

maToile est une toile
dimension(maToile, LARGEUR, HAUT_ENTETE + ZONE + HAUT_PIED)

@ Géométrie
// Orientation du triplet de sommets (s1, s2, s3)
// 1 = sens trigonométrique, -1 = sens horaire, 0 = alignés
fonction orient(s1, s2, s3)
    det est un nombre
    det vaut (pos_x[s2] - pos_x[s1]) * (pos_y[s3] - pos_y[s1]) - (pos_y[s2] - pos_y[s1]) * (pos_x[s3] - pos_x[s1])
    si det > 0 alors
        retourne 1
    fin si
    si det < 0 alors
        retourne 0 - 1
    fin si
    retourne 0
fin fonction

// Deux segments [ab] et [cd] se croisent-ils ?
// Principe : ils se croisent si f1 et f2 sont de part et d'autre de (e1 e2)
// ET si e1 et e2 sont de part et d'autre de (f1 f2).
// Deux arêtes qui partagent un sommet ne comptent jamais comme un croisement.
fonction croisement_sommets(e1, e2, f1, f2)
    si e1 = f1 alors
        retourne faux
    fin si
    si e1 = f2 alors
        retourne faux
    fin si
    si e2 = f1 alors
        retourne faux
    fin si
    si e2 = f2 alors
        retourne faux
    fin si

    o1 est un nombre
    o1 vaut orient(e1, e2, f1)
    o2 est un nombre
    o2 vaut orient(e1, e2, f2)
    o3 est un nombre
    o3 vaut orient(f1, f2, e1)
    o4 est un nombre
    o4 vaut orient(f1, f2, e2)

    si o1 = o2 alors
        retourne faux
    fin si
    si o3 = o4 alors
        retourne faux
    fin si
    retourne vrai
fin fonction

// Le sommet w est-il posé (presque) sur le segment [uv] ?
// Sert à la génération : évite les arêtes qui passent sur un sommet.
// On compare des carrés pour ne pas avoir besoin de racine carrée.
fonction sommet_sur_arete(w, u, v)
    si w = u alors
        retourne faux
    fin si
    si w = v alors
        retourne faux
    fin si

    // Le pied de la perpendiculaire tombe-t-il entre u et v ?
    ps1 est un nombre
    ps1 vaut (pos_x[w] - pos_x[u]) * (pos_x[v] - pos_x[u]) + (pos_y[w] - pos_y[u]) * (pos_y[v] - pos_y[u])
    ps2 est un nombre
    ps2 vaut (pos_x[w] - pos_x[v]) * (pos_x[u] - pos_x[v]) + (pos_y[w] - pos_y[v]) * (pos_y[u] - pos_y[v])
    si ps1 > 0 et ps2 > 0 alors
        det est un nombre
        det vaut (pos_x[v] - pos_x[u]) * (pos_y[w] - pos_y[u]) - (pos_y[v] - pos_y[u]) * (pos_x[w] - pos_x[u])
        long2 est un nombre
        long2 vaut (pos_x[v] - pos_x[u]) * (pos_x[v] - pos_x[u]) + (pos_y[v] - pos_y[u]) * (pos_y[v] - pos_y[u])
        // distance^2 = det^2 / long2 ; on veut distance < 12
        si det * det < 144 * long2 alors
            retourne vrai
        fin si
    fin si
    retourne faux
fin fonction

// Peut-on ajouter l'arête (u, v) sans rien croiser ni écraser un sommet ?
fonction arete_valide(u, v)
    pour k de 0 à longueur(ar_a) - 1
        si ar_a[k] = u et ar_b[k] = v alors
            retourne faux
        fin si
        si ar_a[k] = v et ar_b[k] = u alors
            retourne faux
        fin si
        si croisement_sommets(u, v, ar_a[k], ar_b[k]) = vrai alors
            retourne faux
        fin si
    fin pour
    pour w de 0 à NB_SOMMETS - 1
        si sommet_sur_arete(w, u, v) = vrai alors
            retourne faux
        fin si
    fin pour
    retourne vrai
fin fonction

// Compte les paires d'arêtes qui se croisent
fonction compter_croisements()
    total est un nombre
    total vaut 0
    pour i de 0 à longueur(ar_a) - 2
        pour j de i + 1 à longueur(ar_a) - 1
            si croisement_sommets(ar_a[i], ar_b[i], ar_a[j], ar_b[j]) = vrai alors
                total vaut total + 1
            fin si
        fin pour
    fin pour
    retourne total
fin fonction

@ Interface et Dessin
procédure dessiner_scene()
    effacer(maToile)
    remplir(maToile, #0f0f1e)

    couleur_ok est un texte
    couleur_ok vaut #4a6fa5
    couleur_ko est un texte
    couleur_ko vaut #e94560
    si gagne = vrai alors
        couleur_ok vaut #4ade80
        couleur_ko vaut #4ade80
    fin si

    // Titre
    si gagne = vrai alors
        label(maToile, 8, 17, "RÉSEAU DÉMÊLÉ ! Cliquez pour rejouer", #4ade80, 12)
    sinon
        label(maToile, 8, 17, "Untangle - déplacez les sommets", #e94560, 12)
    fin si

    // Barre de progression : proportion d'arêtes sans croisement
    rectangle_arrondi(maToile, 8, 24, LARGEUR - 16, 8, 4, #1b1b2f)
    larg est un nombre
    larg vaut (LARGEUR - 16) * nb_propres / longueur(ar_a)
    si larg > 0 alors
        rectangle_arrondi(maToile, 8, 24, larg, 8, 4, couleur_ok)
    fin si

    // Arêtes : d'abord les propres, puis les fautives par-dessus
    pour k de 0 à longueur(ar_a) - 1
        si croise[k] = 0 alors
            ligne(maToile, pos_x[ar_a[k]], pos_y[ar_a[k]], pos_x[ar_b[k]], pos_y[ar_b[k]], couleur_ok, 2)
        fin si
    fin pour
    pour k de 0 à longueur(ar_a) - 1
        si croise[k] = 1 alors
            ligne(maToile, pos_x[ar_a[k]], pos_y[ar_a[k]], pos_x[ar_b[k]], pos_y[ar_b[k]], couleur_ko, 3)
        fin si
    fin pour

    // Sommets
    pour s de 0 à NB_SOMMETS - 1
        si a_selection = vrai et selection = s alors
            cercle(maToile, pos_x[s], pos_y[s], RAYON + 5, #ffd166)
            cercle(maToile, pos_x[s], pos_y[s], RAYON, #0f0f1e)
            cercle(maToile, pos_x[s], pos_y[s], RAYON - 3, #ffd166)
        sinon
            cercle(maToile, pos_x[s], pos_y[s], RAYON, #e6e6e6)
            cercle(maToile, pos_x[s], pos_y[s], RAYON - 4, #0f0f1e)
        fin si
    fin pour

    // Barre de boutons
    by est un nombre
    by vaut HAUT_ENTETE + ZONE + 5
    pour ib de 0 à 3
        bx est un nombre
        bx vaut 4 + ib * 88
        rectangle_arrondi(maToile, bx, by, 84, 32, 6, #1b1b2f)
    fin pour

    label(maToile, 26, by + 21, "Nouveau", #e6e6e6, 11)
    label(maToile, 115, by + 21, "Annuler", #e6e6e6, 11)
    label(maToile, 196, by + 21, "Mélanger", #e6e6e6, 11)
    si niveau = 0 alors
        label(maToile, 297, by + 21, "Petit", #e6e6e6, 11)
    sinon si niveau = 1 alors
        label(maToile, 293, by + 21, "Moyen", #e6e6e6, 11)
    sinon
        label(maToile, 293, by + 21, "Grand", #e6e6e6, 11)
    fin si

    affiche maToile
fin procédure

@ Programme Principal
tant que continuer = vrai

    @ Création d'un nouveau réseau
    // Vide tout ce qui reste de la partie précédente
    tant que longueur(slot_x) > 0
        slot_x supprime (longueur(slot_x) - 1)
        slot_y supprime (longueur(slot_y) - 1)
    fin tant que
    tant que longueur(pos_x) > 0
        pos_x supprime (longueur(pos_x) - 1)
        pos_y supprime (longueur(pos_y) - 1)
    fin tant que
    tant que longueur(ar_a) > 0
        ar_a supprime (longueur(ar_a) - 1)
        ar_b supprime (longueur(ar_b) - 1)
    fin tant que
    tant que longueur(croise) > 0
        croise supprime (longueur(croise) - 1)
    fin tant que

    // --- 1. Positions de la solution, bien réparties ---
    echecs est un nombre
    echecs vaut 0
    tant que longueur(slot_x) < NB_SOMMETS
        nx est un nombre
        nx vaut 26 + hasard(LARGEUR - 52)
        ny est un nombre
        ny vaut HAUT_ENTETE + 26 + hasard(ZONE - 52)

        libre est un nombre
        libre vaut 1
        pour k de 0 à longueur(slot_x) - 1
            dx est un nombre
            dx vaut nx - slot_x[k]
            dy est un nombre
            dy vaut ny - slot_y[k]
            si dx * dx + dy * dy < ECART alors
                libre vaut 0
            fin si
        fin pour

        si libre = 1 alors
            slot_x ajoute nx
            slot_y ajoute ny
            echecs vaut 0
        sinon
            echecs vaut echecs + 1
            // Si la place manque vraiment, on accepte quand même pour ne pas boucler
            si echecs > 300 alors
                slot_x ajoute nx
                slot_y ajoute ny
                echecs vaut 0
            fin si
        fin si
    fin tant que

    // Les sommets partent sur leur position solution : le réseau est donc
    // planaire pendant toute la phase de génération
    pour i de 0 à NB_SOMMETS - 1
        pos_x ajoute slot_x[i]
        pos_y ajoute slot_y[i]
    fin pour

    // --- 2. Liste mélangée de toutes les paires de sommets ---
    paires est un tableau
    pour u de 0 à NB_SOMMETS - 2
        pour v de u + 1 à NB_SOMMETS - 1
            paires ajoute u * NB_SOMMETS + v
        fin pour
    fin pour
    pour i de 0 à longueur(paires) - 1
        j est un nombre
        j vaut hasard(longueur(paires) - 1)
        tmp est un nombre
        tmp vaut paires[i]
        paires[i] vaut paires[j]
        paires[j] vaut tmp
    fin pour

    // --- 3. On ajoute les arêtes qui ne croisent rien ---
    ar_max est un nombre
    ar_max vaut 2 * NB_SOMMETS
    p est un nombre
    p vaut 0
    tant que p < longueur(paires) et longueur(ar_a) < ar_max
        cd est un nombre
        cd vaut paires[p]
        u2 est un nombre
        u2 vaut arrondi_inferieur(cd / NB_SOMMETS)
        v2 est un nombre
        v2 vaut cd mod NB_SOMMETS
        si arete_valide(u2, v2) = vrai alors
            ar_a ajoute u2
            ar_b ajoute v2
        fin si
        p vaut p + 1
    fin tant que

    // --- 4. Aucun sommet ne doit rester isolé ---
    pour w de 0 à NB_SOMMETS - 1
        degre est un nombre
        degre vaut 0
        pour k de 0 à longueur(ar_a) - 1
            si ar_a[k] = w alors
                degre vaut degre + 1
            fin si
            si ar_b[k] = w alors
                degre vaut degre + 1
            fin si
        fin pour
        si degre = 0 alors
            pour z de 0 à NB_SOMMETS - 1
                si z > w et degre = 0 alors
                    si arete_valide(w, z) = vrai alors
                        ar_a ajoute w
                        ar_b ajoute z
                        degre vaut 1
                    fin si
                fin si
                si z < w et degre = 0 alors
                    si arete_valide(z, w) = vrai alors
                        ar_a ajoute z
                        ar_b ajoute w
                        degre vaut 1
                    fin si
                fin si
            fin pour
        fin si
    fin pour

    pour k de 0 à longueur(ar_a) - 1
        croise ajoute 0
    fin pour

    gagne vaut faux
    rejouer vaut faux
    doit_melanger vaut vrai
    a_selection vaut faux

    @ Boucle de jeu
    tant que gagne = faux et rejouer = faux

        // --- Mélange des positions (début de partie ou bouton Mélanger) ---
        si doit_melanger = vrai alors
            tant que longueur(hist_v) > 0
                hist_v supprime (longueur(hist_v) - 1)
                hist_x supprime (longueur(hist_x) - 1)
                hist_y supprime (longueur(hist_y) - 1)
            fin tant que
            a_selection vaut faux

            nb_croix vaut 0
            essais est un nombre
            essais vaut 0
            // On permute les sommets sur les emplacements de la solution :
            // la partie reste donc toujours résoluble
            tant que nb_croix = 0 et essais < 40
                ordre est un tableau
                pour i de 0 à NB_SOMMETS - 1
                    ordre ajoute i
                fin pour
                pour i de 0 à NB_SOMMETS - 1
                    j2 est un nombre
                    j2 vaut hasard(NB_SOMMETS - 1)
                    tmp2 est un nombre
                    tmp2 vaut ordre[i]
                    ordre[i] vaut ordre[j2]
                    ordre[j2] vaut tmp2
                fin pour
                pour i de 0 à NB_SOMMETS - 1
                    pos_x[i] vaut slot_x[ordre[i]]
                    pos_y[i] vaut slot_y[ordre[i]]
                fin pour
                nb_croix vaut compter_croisements()
                essais vaut essais + 1
            fin tant que

            doit_melanger vaut faux
        fin si

        // --- Marquage des arêtes fautives ---
        pour k de 0 à longueur(croise) - 1
            croise[k] vaut 0
        fin pour
        nb_croix vaut 0
        pour i de 0 à longueur(ar_a) - 2
            pour j de i + 1 à longueur(ar_a) - 1
                si croisement_sommets(ar_a[i], ar_b[i], ar_a[j], ar_b[j]) = vrai alors
                    croise[i] vaut 1
                    croise[j] vaut 1
                    nb_croix vaut nb_croix + 1
                fin si
            fin pour
        fin pour

        nb_propres vaut 0
        pour k de 0 à longueur(croise) - 1
            si croise[k] = 0 alors
                nb_propres vaut nb_propres + 1
            fin si
        fin pour

        appelle dessiner_scene()

        clic_x est un nombre
        clic_y est un nombre
        cliquer maToile dans clic_x, clic_y

        // --- Clic dans l'aire de jeu ---
        si clic_y >= HAUT_ENTETE alors
            si clic_y < HAUT_ENTETE + ZONE alors

                // Cherche le sommet le plus proche du clic
                touche est un booléen
                touche vaut faux
                vise est un nombre
                vise vaut 0
                meilleure est un nombre
                meilleure vaut PRISE
                pour s de 0 à NB_SOMMETS - 1
                    ex est un nombre
                    ex vaut clic_x - pos_x[s]
                    ey est un nombre
                    ey vaut clic_y - pos_y[s]
                    d2 est un nombre
                    d2 vaut ex * ex + ey * ey
                    si d2 < meilleure alors
                        meilleure vaut d2
                        vise vaut s
                        touche vaut vrai
                    fin si
                fin pour

                si touche = vrai alors
                    // Clic sur un sommet : on le sélectionne, ou on le désélectionne
                    si a_selection = vrai et selection = vise alors
                        a_selection vaut faux
                    sinon
                        selection vaut vise
                        a_selection vaut vrai
                    fin si
                sinon
                    // Clic dans le vide : on y dépose le sommet sélectionné
                    si a_selection = vrai alors
                        place est un booléen
                        place vaut vrai
                        si clic_x < 20 alors
                            place vaut faux
                        fin si
                        si clic_x > LARGEUR - 20 alors
                            place vaut faux
                        fin si
                        si clic_y < HAUT_ENTETE + 20 alors
                            place vaut faux
                        fin si
                        si clic_y > HAUT_ENTETE + ZONE - 20 alors
                            place vaut faux
                        fin si
                        pour s de 0 à NB_SOMMETS - 1
                            si s = selection alors
                                // on s'ignore soi-même
                            sinon
                                gx est un nombre
                                gx vaut clic_x - pos_x[s]
                                gy est un nombre
                                gy vaut clic_y - pos_y[s]
                                si gx * gx + gy * gy < ECART alors
                                    place vaut faux
                                fin si
                            fin si
                        fin pour

                        si place = vrai alors
                            hist_v ajoute selection
                            hist_x ajoute pos_x[selection]
                            hist_y ajoute pos_y[selection]
                            pos_x[selection] vaut clic_x
                            pos_y[selection] vaut clic_y
                            a_selection vaut faux
                        fin si
                    fin si
                fin si
            fin si
        fin si

        // --- Clic sur la barre de boutons ---
        si clic_y >= HAUT_ENTETE + ZONE alors

            // Nouveau réseau
            si clic_x >= 4 et clic_x < 88 alors
                rejouer vaut vrai
            fin si

            // Annuler le dernier déplacement
            si clic_x >= 92 et clic_x < 176 alors
                si longueur(hist_v) > 0 alors
                    k2 est un nombre
                    k2 vaut longueur(hist_v) - 1
                    sommet_defait est un nombre
                    sommet_defait vaut hist_v[k2]
                    pos_x[sommet_defait] vaut hist_x[k2]
                    pos_y[sommet_defait] vaut hist_y[k2]
                    hist_v supprime (k2)
                    hist_x supprime (k2)
                    hist_y supprime (k2)
                    a_selection vaut faux
                fin si
            fin si

            // Mélanger de nouveau le même réseau
            si clic_x >= 180 et clic_x < 264 alors
                doit_melanger vaut vrai
            fin si

            // Nombre de sommets
            si clic_x >= 268 et clic_x < 352 alors
                niveau vaut (niveau + 1) mod 3
                si niveau = 0 alors
                    NB_SOMMETS vaut 8
                sinon si niveau = 1 alors
                    NB_SOMMETS vaut 12
                sinon
                    NB_SOMMETS vaut 16
                fin si
                rejouer vaut vrai
            fin si
        fin si

        si rejouer = faux alors
            si doit_melanger = faux alors
                si nb_croix = 0 alors
                    gagne vaut vrai
                fin si
            fin si
        fin si
    fin tant que

    @ Écran de victoire
    si gagne = vrai alors
        a_selection vaut faux
        pour k de 0 à longueur(croise) - 1
            croise[k] vaut 0
        fin pour
        nb_propres vaut longueur(ar_a)

        appelle dessiner_scene()

        clic_bis_x est un nombre
        clic_bis_y est un nombre
        cliquer maToile dans clic_bis_x, clic_bis_y

        si clic_bis_y >= HAUT_ENTETE + ZONE alors
            si clic_bis_x >= 268 et clic_bis_x < 352 alors
                niveau vaut (niveau + 1) mod 3
                si niveau = 0 alors
                    NB_SOMMETS vaut 8
                sinon si niveau = 1 alors
                    NB_SOMMETS vaut 12
                sinon
                    NB_SOMMETS vaut 16
                fin si
            fin si
        fin si
    fin si

fin tant que
Pas encore noté
Créer une animation d’addition posée
Article

Créer une animation d’addition posée

Analyse détaillée d’un programme éducatif

Lire l'articleReplier l'article

Ce programme réalise une animation élégante et pédagogique : il montre pas à pas comment effectuer une addition posée, en décomposant les nombres, en affichant les retenues, et en mettant en évidence chaque colonne au moment où elle est calculée.

C’est un excellent exemple de programmation créative appliquée à l’enseignement des mathématiques.

Configuration générale

L’animation se déroule sur une toile de :

  • 560 px de large
  • 420 px de haut

Les différentes zones d’affichage sont soigneusement définies :

  • BASE_X : position horizontale de la colonne des unités
  • COL_LARG : largeur d’une colonne de chiffres
  • Y_NB1, Y_NB2, Y_RETENUE, Y_RESULTAT : hauteurs des différentes lignes (retenues, premier nombre, second nombre, résultat)

Cette structuration permet un rendu propre, aligné, fidèle à la présentation traditionnelle de l’addition posée.

Saisie et préparation des nombres

Les deux nombres sont demandés à l’utilisateur :

  • Premier nombre
  • Deuxième nombre

Ils sont ensuite transformés en valeurs positives et arrondis à l’entier inférieur.

Décomposition en chiffres
Chaque nombre est découpé en chiffres individuels :

  • On extrait les unités (mod 10)
  • On divise par 10 pour passer à la colonne suivante
  • On stocke les chiffres dans un tableau

Les chiffres sont donc rangés de droite à gauche, ce qui correspond parfaitement à la logique de l’addition posée.

Préparation des colonnes

Le programme détermine :

  • le nombre total de colonnes (nbCol)
  • un tableau resultat pour les chiffres du résultat
  • un tableau retenues pour les retenues intermédiaires

Chaque case est initialisée à -1, ce qui signifie “non encore calculé”.

La procédure dessinerScene : cœur graphique de l’animation

Cette procédure construit une image complète représentant l’état actuel de l’opération.

Fond et titre
Un dégradé gris clair donne un aspect moderne et propre.
Un label affiche l’opération :

  • Addition posée : A + B

Affichage des retenues
Chaque retenue déjà calculée est affichée en rouge au-dessus de sa colonne.

Affichage des deux nombres
Les chiffres du premier et du second nombre sont affichés en bleu, parfaitement alignés sur leurs colonnes respectives.

Le signe + est placé à gauche du second nombre.

Trait de séparation
Un trait horizontal marque la zone du résultat, comme dans une addition posée classique.

Mise en surbrillance de la colonne active
Lors du calcul, la colonne en cours est entourée d’un rectangle jaune pâle :

  • on y réaffiche les chiffres concernés,
  • on y affiche la retenue éventuelle,
  • cela guide visuellement l’utilisateur.

Affichage du résultat partiel
Les chiffres déjà calculés du résultat apparaissent en vert, légèrement plus grands pour attirer l’œil.

Message explicatif
Un texte en bas de l’image explique ce qui se passe :

  • On pose l’opération…
  • 3 + 7 = 10
  • J’écris 0 et je retiens 1

Chaque étape est donc accompagnée d’un commentaire pédagogique.

Calcul animé colonne par colonne L’addition se fait de droite à gauche, comme dans la méthode scolaire.

Pour chaque colonne :

  • On récupère les chiffres da et db
  • On ajoute la retenue éventuelle

On calcule :

  • le chiffre du résultat (mod 10)
  • la nouvelle retenue (total / 10)
  • On affiche une scène expliquant le calcul
  • On affiche une scène montrant l’écriture du résultat et de la retenue

Chaque étape est répétée plusieurs fois pour ralentir l’animation et permettre la lecture.

Affichage final

Une fois toutes les colonnes calculées :

  • le programme affiche le résultat complet,
  • avec un message final :

Résultat : A + B = C

Conclusion

Ce programme est un superbe exemple de pédagogie visuelle appliquée à l’algorithmique.
Il combine :

  • décomposition numérique,
  • gestion des retenues,
  • animation progressive,
  • mise en valeur de la colonne active,
  • messages explicatifs,
  • rendu graphique propre et moderne.

Le résultat est une animation éducative, idéale pour expliquer l’addition posée à des élèves, ou pour illustrer un cours de programmation créative.

Programme :

@ Configuration et Paramètres
LARG est un nombre
LARG vaut 560
HAUT est un nombre
HAUT vaut 420

film est un tableau

// Position d'affichage
COL_LARG est un nombre
COL_LARG vaut 42 // largeur d'une colonne de chiffres
BASE_X est un nombre
BASE_X vaut 400 // x du chiffre des unités
Y_RETENUE est un nombre
Y_RETENUE vaut 90
Y_NB1 est un nombre
Y_NB1 vaut 150
Y_NB2 est un nombre
Y_NB2 vaut 210
Y_TRAIT est un nombre
Y_TRAIT vaut 240
Y_RESULTAT est un nombre
Y_RESULTAT vaut 300

@ Saisie des deux nombres
nombre1 est un nombre
demande "Premier nombre : " dans nombre1
nombre1 vaut arrondi_inferieur(absolue(nombre1))

nombre2 est un nombre
demande "Deuxième nombre : " dans nombre2
nombre2 vaut arrondi_inferieur(absolue(nombre2))

@ Décomposition en chiffres
chiffresA est un tableau
chiffresB est un tableau

na est un nombre
na vaut nombre1
si na = 0 alors
    chiffresA ajoute 0
fin si
tant que na > 0
    chiffresA ajoute na mod 10
    na vaut arrondi_inferieur(na / 10)
fin tant que

nbb est un nombre
nbb vaut nombre2
si nbb = 0 alors
    chiffresB ajoute 0
fin si
tant que nbb > 0
    chiffresB ajoute nbb mod 10
    nbb vaut arrondi_inferieur(nbb / 10)
fin tant que

// Nombre de colonnes = max des deux longueurs, +1 pour une retenue finale
nbCol est un nombre
nbCol vaut maximum(longueur(chiffresA), longueur(chiffresB)) + 1

// Tableaux résultat et retenues (init à vide = -1)
resultat est un tableau
retenues est un tableau
ic est un nombre
pour ic de 0 à nbCol - 1
    resultat ajoute -1
    retenues ajoute -1
fin pour

@ Fonctions utilitaires
fonction chiffreA(col)
    si col < longueur(chiffresA) alors
        retourne chiffresA[col]
    fin si
    retourne 0
fin fonction

fonction chiffreB(col)
    si col < longueur(chiffresB) alors
        retourne chiffresB[col]
    fin si
    retourne 0
fin fonction

@ Procédure de Dessin
procedure dessinerScene(colActive, msg)
    t est une toile
    dimension(t, LARG, HAUT)

    // Remplacement du fond uni par un joli dégradé
    dégradé(t, [#f8f9fa, #e9ecef])

    label(t, 20, 35, "Addition posée : " + convertir_texte(nombre1) + " + " + convertir_texte(nombre2), #495057, 18)

    // --- Les retenues ---
    rc est un nombre
    pour rc de 0 à nbCol - 1
        si retenues[rc] >= 0 alors
            xr est un nombre
            xr vaut BASE_X - rc * COL_LARG
            label(t, xr + 4, Y_RETENUE, convertir_texte(retenues[rc]), #e63946, 18)
        fin si
    fin pour

    // --- Premier nombre ---
    c1 est un nombre
    pour c1 de 0 à longueur(chiffresA) - 1
        x1 est un nombre
        x1 vaut BASE_X - c1 * COL_LARG
        label(t, x1, Y_NB1, convertir_texte(chiffresA[c1]), #1c3d8f, 24)
    fin pour

    // --- Signe + et deuxième nombre ---
    label(t, BASE_X - (nbCol - 1) * COL_LARG - 36, Y_NB2, "+", #1c3d8f, 24)
    c2 est un nombre
    pour c2 de 0 à longueur(chiffresB) - 1
        x2 est un nombre
        x2 vaut BASE_X - c2 * COL_LARG
        label(t, x2, Y_NB2, convertir_texte(chiffresB[c2]), #1c3d8f, 24)
    fin pour

    // --- Trait de séparation ---
    xGauche est un nombre
    xGauche vaut BASE_X - (nbCol - 1) * COL_LARG - 10
    ligne(t, xGauche, Y_TRAIT, BASE_X + 34, Y_TRAIT, #212529, 3)

    // --- Colonne active mise en surbrillance ---
    si colActive >= 0 alors
        xa est un nombre
        xa vaut BASE_X - colActive * COL_LARG
        rectangle_arrondi(t, xa - 6, Y_RETENUE - 24, COL_LARG - 4, Y_RESULTAT - Y_RETENUE + 60, 8, #fff3bf)

        si colActive < longueur(chiffresA) alors
            label(t, xa, Y_NB1, convertir_texte(chiffresA[colActive]), #1c3d8f, 24)
        fin si
        si colActive < longueur(chiffresB) alors
            label(t, xa, Y_NB2, convertir_texte(chiffresB[colActive]), #1c3d8f, 24)
        fin si
        si retenues[colActive] >= 0 alors
            label(t, xa + 4, Y_RETENUE, convertir_texte(retenues[colActive]), #e63946, 18)
        fin si
    fin si

    // --- Résultat ---
    colMax est un nombre
    colMax vaut 0
    cm est un nombre
    pour cm de 0 à nbCol - 1
        si resultat[cm] >= 1 alors
            colMax vaut cm
        fin si
    fin pour

    rr est un nombre
    pour rr de 0 à nbCol - 1
        si resultat[rr] >= 0 et rr <= colMax alors
            xres est un nombre
            xres vaut BASE_X - rr * COL_LARG
            label(t, xres, Y_RESULTAT, convertir_texte(resultat[rr]), #2b8a3e, 28) // Vert un peu plus grand
        fin si
    fin pour

    // --- Message explicatif ---
    label(t, 20, 380, msg, #495057, 18) // Police légèrement agrandie

    film ajoute t
fin procedure

@ Calcul Animé
vide film

pour pause de 1 à 2
    appelle dessinerScene(-1, "On pose l'opération. On calcule de droite à gauche.")
fin pour

retenue est un nombre
retenue vaut 0

col est un nombre
pour col de 0 à nbCol - 1
    da est un nombre
    da vaut chiffreA(col)
    db est un nombre
    db vaut chiffreB(col)

    total_col est un nombre
    total_col vaut da + db + retenue

    chiffreResultat est un nombre
    chiffreResultat vaut total_col mod 10
    nouvelleRetenue est un nombre
    nouvelleRetenue vaut arrondi_inferieur(total_col / 10)

    msg1 est un texte
    si retenue > 0 alors
        msg1 vaut convertir_texte(da) + " + " + convertir_texte(db) + " + " + convertir_texte(retenue) + " (retenue) = " + convertir_texte(total_col)
    sinon
        msg1 vaut convertir_texte(da) + " + " + convertir_texte(db) + " = " + convertir_texte(total_col)
    fin si

    // Remplace les appels multiples par une boucle de pause
    pour pause de 1 à 3
        appelle dessinerScene(col, msg1)
    fin pour

    resultat[col] vaut chiffreResultat

    msg2 est un texte
    msg2 vaut "J'écris " + convertir_texte(chiffreResultat)
    si nouvelleRetenue > 0 alors
        msg2 vaut msg2 + " et je retiens " + convertir_texte(nouvelleRetenue)
        si col + 1 < nbCol alors
            retenues[col + 1] vaut nouvelleRetenue
        fin si
    fin si

    pour pause de 1 à 3
        appelle dessinerScene(col, msg2)
    fin pour

    retenue vaut nouvelleRetenue
fin pour

@ Résultat Final
total est un nombre
total vaut nombre1 + nombre2

pour finrep de 1 à 8
    appelle dessinerScene(-1, "Résultat : " + convertir_texte(nombre1) + " + " + convertir_texte(nombre2) + " = " + convertir_texte(total))
fin pour

animation(film, 600, 0) // Animation légèrement ralentie pour bien lire
Pas encore noté
Créer une animation de collision automobile
Article

Créer une animation de collision automobile

Analyse complète d’un programme graphique

Lire l'articleReplier l'article

Ce programme réalise une animation spectaculaire en trois actes :

  • Deux voitures s’approchent l’une de l’autre,
  • Elles entrent en collision,
  • Elles restent immobiles, écrasées, après l’impact.

L’ensemble repose sur une combinaison de dessin vectoriel, de physique simplifiée, et d’une gestion précise de l’animation image par image.
Voyons comment ce programme construit cette scène.

Préparation de l’environnement graphique

L’animation repose sur un tableau graph, qui stocke toutes les images successives.
La toile fait :

  • 600 px de large
  • 320 px de haut

La route est placée à routeY = 220, ce qui laisse un ciel bleu en haut et une zone asphaltée en bas.

Un point central, centreImpact, représente l’endroit où les deux voitures se percuteront.

La procédure voiture : dessiner un véhicule complet

Le cœur du programme est une procédure capable de dessiner une voiture dans n’importe quelle position, avec :

  • une couleur de carrosserie
  • une couleur de toit
  • une direction (1 = vers la droite, -1 = vers la gauche)
  • un niveau d’écrasement de l’avant

Cette procédure assemble plusieurs éléments graphiques :

Composants du véhicule

  • Ombre au sol : ellipse sombre pour l’effet de volume
  • Châssis : rectangle arrondi, dont l’avant peut être raccourci pour -simuler l’écrasement
  • Pare-chocs : petit rectangle arrondi qui se déforme
  • Cabine : bloc supérieur légèrement décalé vers l’arrière
  • Vitres : deux rectangles arrondis avec un reflet diagonal
  • Phare avant : cercle jaune/blanc
  • Feu arrière : cercle rouge
  • Roues : cercles sombres avec jantes claires

Le calcul de l’avant et de l’arrière du châssis permet de simuler la déformation lors du choc : plus ecrase augmente, plus l’avant se rapproche du centre du véhicule.

Phase 1 : l’approche

Pendant 25 images, les deux voitures roulent l’une vers l’autre :

  • La voiture rouge part de la gauche et avance vers la droite.
  • La voiture bleue part de la droite et avance vers la gauche.

La route est dessinée avec :

  • un rectangle gris foncé,
  • une bordure plus claire,
  • des pointillés jaunes espacés régulièrement.

Les positions des voitures évoluent linéairement :

gaucheXpos = 60 + approche * 6
droiteXpos = largeurToile - 60 - approche * 6

Cette phase crée une tension visuelle : les véhicules se rapprochent inexorablement du point d’impact.

Phase 2 : l’impact

C’est la partie la plus riche du programme.
Elle combine :

  • déformation progressive des voitures,
  • flash lumineux,
  • projection de débris,
  • étoile d’impact façon bande dessinée,
  • rebond léger après le choc.

Flash blanc
Pendant les premières images, un cercle blanc apparaît au centre, diminuant progressivement.
Cela simule l’éclair du choc.

Déformation
L’avant des voitures se comprime :

ecrasement = minimum(impact * 2, 22)

Puis un léger recul se produit après quelques images :

recul = minimum(impact - 6, 8)

Débris projetés
45 débris sont générés avec :

  • une direction aléatoire (angle),
  • une vitesse aléatoire,
  • une couleur choisie parmi quatre teintes (jaune, orange, rouge, gris).

Chaque débris suit une trajectoire parabolique :

  • déplacement horizontal et vertical selon sa vitesse,
  • ajout d’une petite gravité : 0,3 * impact².

Les débris diminuent de taille au fil du temps.

Étoile “BOUM”
Pendant les premières images, une étoile explosive est dessinée :

  • 8 branches,
  • couleur rose vif,
  • rayon qui augmente à chaque frame.

Cela donne un effet cartoon très dynamique.

Phase 3 : après l’accident

Les voitures sont désormais immobiles :

  • avant complètement écrasé (ecrase = 22),
  • légèrement reculées,
  • débris retombés.

Cette phase dure 20 images, créant un temps de repos après l’action.

Pause finale et animation
Quelques images identiques sont ajoutées pour figer la scène, puis :

animation(graph, 60, 1)

Lance l’animation à 60 ms par image, en boucle.

Conclusion

Ce programme est un excellent exemple de programmation graphique narrative.
Il combine :

  • dessin vectoriel détaillé,
  • animation image par image,
  • physique simplifiée,
  • effets visuels dynamiques,
  • gestion de déformation,
  • éléments stylisés (flash, étoile, débris).

Le résultat est une scène vivante, expressive, presque cinématographique.

Programme :

graph est un tableau

largeurToile est un nombre vaut 600
hauteurToile est un nombre vaut 320

routeY est un nombre vaut 220

// Point de collision (au centre)
centreImpact est un nombre vaut largeurToile / 2

// --- Procedure : dessine une voiture ---
// direction = 1 (regarde/roule vers la droite) ou -1 (vers la gauche)
// ecrase = niveau de deformation de l'AVANT (0 = intacte)
procedure voiture(img, vx, vy, carrosserie, toit, direction, ecrase)
    // vx = centre du chassis. L'avant est du cote "direction".

    // --- Ombre au sol ---
    ellipse(img, vx, vy + 28, 44, 7, #333333)

    // --- Bornes avant / arriere du chassis ---
    // L'arriere reste fixe, l'avant se rapproche du centre (ecrasement)
    arriereX est un nombre vaut vx - direction * 40
    avantX est un nombre vaut vx + direction * (40 - ecrase)

    // Largeur reelle du chassis apres ecrasement
    gaucheX est un nombre vaut minimum(arriereX, avantX)
    largChassis est un nombre vaut absolue(avantX - arriereX)

    // --- Chassis bas (deux tons pour le volume) ---
    rectangle_arrondi(img, gaucheX, vy, largChassis, 26, 6, carrosserie)
    // Bas de caisse plus sombre
    rectangle(img, gaucheX + 2, vy + 18, largChassis - 4, 6, toit)

    // --- Pare-chocs a l'avant (se plie quand ca cogne) ---
    pcX est un nombre vaut avantX - direction * 3
    rectangle_arrondi(img, pcX - 3, vy + 12, 6, 12, 2, #adb5bd)

    // --- Cabine (toit) centree, legerement vers l'arriere ---
    cabX est un nombre vaut vx - direction * 4
    rectangle_arrondi(img, cabX - 21, vy - 20, 42, 24, 7, toit)

    // --- Vitres avec reflet ---
    rectangle_arrondi(img, cabX - 16, vy - 16, 15, 15, 3, #a8dadc)
    rectangle_arrondi(img, cabX + 1, vy - 16, 15, 15, 3, #a8dadc)
    // Reflet diagonal
    ligne(img, cabX - 14, vy - 3, cabX - 6, vy - 14, #e8f4f8, 2)
    ligne(img, cabX + 3, vy - 3, cabX + 11, vy - 14, #e8f4f8, 2)

    // --- Phare a l'avant (sur le nez, meme deforme) ---
    phareX est un nombre vaut avantX - direction * 3
    cercle(img, phareX, vy + 7, 4, #fff3b0)
    cercle(img, phareX, vy + 7, 2, #ffffff)

    // --- Feu arriere rouge ---
    feuX est un nombre vaut arriereX + direction * 2
    cercle(img, feuX, vy + 7, 3, #d00000)

    // --- Roues avec jante ---
    cercle(img, vx - 22, vy + 26, 10, #1b263b)
    cercle(img, vx + 22, vy + 26, 10, #1b263b)
    cercle(img, vx - 22, vy + 26, 4, #ced4da)
    cercle(img, vx + 22, vy + 26, 4, #ced4da)
fin procedure

// ============================================================
//  PHASE 1 : APPROCHE
// ============================================================
approche est un nombre
pour approche de 0 a 24
    img est une toile
    dimension(img, largeurToile, hauteurToile)
    degrade(img, [#caf0f8, #e8f5e9])

    // Route
    rectangle(img, 0, routeY + 30, largeurToile, hauteurToile - routeY - 30, #495057)
    rectangle(img, 0, routeY + 30, largeurToile, 4, #6c757d)
    pointille est un nombre
    pour pointille de 20 a largeurToile - 1 pas 60
        rectangle(img, pointille, routeY + 52, 30, 5, #ffd60a)
    fin pour

    gaucheXpos est un nombre vaut 60 + approche * 6
    droiteXpos est un nombre vaut largeurToile - 60 - approche * 6

    // Voiture rouge roule vers la DROITE (direction 1)
    appelle voiture(img, gaucheXpos, routeY, #e63946, #a4161a, 1, 0)
    // Voiture bleue roule vers la GAUCHE (direction -1)
    appelle voiture(img, droiteXpos, routeY, #457b9d, #1d3557, -1, 0)

    graph ajoute img
fin pour

// ============================================================
//  PHASE 2 : IMPACT
// ============================================================
nbDebris est un nombre vaut 45
debVX, debVY, debTeinte est un tableau

d est un nombre
pour d de 0 a nbDebris - 1
    ang est un nombre vaut hasard(0, 628) / 100
    vit est un nombre vaut hasard(2, 10)

    debVX ajoute cosinus(ang) * vit
    debVY ajoute sinus(ang) * vit - 4
    choixC est un nombre vaut hasard(1, 4)

    si choixC = 1 alors
        debTeinte ajoute #ffd60a
    sinon si choixC = 2 alors
        debTeinte ajoute #ff6d00
    sinon si choixC = 3 alors
        debTeinte ajoute #e63946
    sinon
        debTeinte ajoute #6c757d
    fin si
fin pour

// Positions au contact : les AVANTS se touchent au centre
gaucheFin est un nombre vaut centreImpact - 40
droiteFin est un nombre vaut centreImpact + 40

impact est un nombre
pour impact de 0 a 30
    img est une toile
    dimension(img, largeurToile, hauteurToile)
    degrade(img, [#caf0f8, #e8f5e9])

    rectangle(img, 0, routeY + 30, largeurToile, hauteurToile - routeY - 30, #495057)
    rectangle(img, 0, routeY + 30, largeurToile, 4, #6c757d)
    pointille2 est un nombre
    pour pointille2 de 20 a largeurToile - 1 pas 60
        rectangle(img, pointille2, routeY + 52, 30, 5, #ffd60a)
    fin pour

    // Flash blanc a l'impact
    si impact < 4 alors
        cercle(img, centreImpact, routeY + 5, 60 - impact * 8, #ffffff)
    fin si

    // Deformation de l'AVANT qui augmente puis se stabilise
    ecrasement est un nombre vaut minimum(impact * 2, 22)

    // Petit rebond apres le choc
    recul est un nombre vaut 0
    si impact > 6 alors
        recul vaut minimum(impact - 6, 8)
    fin si

    appelle voiture(img, gaucheFin - recul, routeY, #e63946, #a4161a, 1, ecrasement)
    appelle voiture(img, droiteFin + recul, routeY, #457b9d, #1d3557, -1, ecrasement)

    // Debris depuis le point d'impact
    db est un nombre
    pour db de 0 a nbDebris - 1
        px, py est un nombre
        px vaut centreImpact + debVX[db] * impact
        py vaut routeY + 5 + debVY[db] * impact + 0,3 * impact * impact
        si py < routeY + 55 alors
            tailleDb est un nombre vaut 4 - impact * 0,08
            si tailleDb < 1 alors
                tailleDb vaut 1
            fin si
            cercle(img, px, py, tailleDb, debTeinte[db])
        fin si
    fin pour

    // Etoile d'impact "BOUM"
    si impact < 8 alors
        rayonEtoile est un nombre vaut 30 + impact * 4

        branche est un nombre
        pour branche de 0 a 7
            angleB est un nombre vaut branche * 0,785
            exB est un nombre vaut centreImpact + cosinus(angleB) * rayonEtoile
            eyB est un nombre vaut routeY + 5 + sinus(angleB) * rayonEtoile

            ligne(img, centreImpact, routeY + 5, exB, eyB, #ff477e, 3)
        fin pour
    fin si

    graph ajoute img
fin pour

// ============================================================
//  PHASE 3 : APRES (epaves ecrasees, immobiles)
// ============================================================
apres est un nombre
pour apres de 0 a 20
    img est une toile
    dimension(img, largeurToile, hauteurToile)
    degrade(img, [#caf0f8, #e8f5e9])

    rectangle(img, 0, routeY + 30, largeurToile, hauteurToile - routeY - 30, #495057)
    rectangle(img, 0, routeY + 30, largeurToile, 4, #6c757d)
    pointille3 est un nombre
    pour pointille3 de 20 a largeurToile - 1 pas 60
        rectangle(img, pointille3, routeY + 52, 30, 5, #ffd60a)
    fin pour

    // Epaves : avant ecrase, legerement reculees
    appelle voiture(img, gaucheFin - 8, routeY, #e63946, #a4161a, 1, 22)
    appelle voiture(img, droiteFin + 8, routeY, #457b9d, #1d3557, -1, 22)

    graph ajoute img
fin pour

// Pause finale
pauseFin est un nombre
pour pauseFin de 0 a 8
    graph ajoute graph[longueur(graph) - 1]
fin pour

animation(graph, 60, 1)
Pas encore noté