EduCode
EduCode
← Retour au site
Blog

Articles & Actualités

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

19 publications

Agent 47 Go : un exercice de game design tactique et d’algorithmie appliquée
Article

Agent 47 Go

Agent 47 Go : un exercice de game design tactique et d’algorithmie appliquée

Lire l'articleReplier l'article

Dans le domaine du game design, il existe une zone fascinante : celle où des mécaniques simples, presque brutes, parviennent à créer une expérience stratégique riche. Agent 47 Go illustre parfaitement cette idée en combinant génération procédurale, IA déterministe et rendu isométrique minimaliste pour produire un gameplay d’infiltration au tour par tour.

Une carte générée dynamiquement, toujours jouable

Le système repose sur une grille 8×8, entièrement reconstruite à chaque partie. La logique garantit un chemin viable entre le point de départ et l’objectif, tout en ajoutant des embranchements secondaires uniquement lorsqu’ils restent connectés.

« carte[indexChemin] vaut 1 » « si adj = 1 alors carte[idxCourant] vaut 1 »

Ce choix permet de créer des niveaux variés, sans jamais sacrifier la jouabilité.

Des gardes qui patrouillent selon une IA simple mais efficace
Les gardes suivent une direction fixe, qu’ils inversent lorsqu’ils rencontrent un obstacle ou un autre garde. Ce comportement, entièrement déterministe, crée une patrouille crédible, lisible, mais suffisamment imprévisible pour obliger le joueur à anticiper.

« gardesDir[i] vaut (d + 2) mod 4 »

Une mécanique légère, mais qui génère une vraie tension tactique.

Un gameplay d’infiltration au tour par tour

Le joueur se déplace avec ZQSD, chaque action déclenchant la réaction des gardes. Les collisions, éliminations et conditions de victoire/défaite sont évaluées à chaque tour.

« si gardesX[i] = pX et gardesY[i] = pY alors etatJeu vaut 2 »

Une boucle de jeu simple, mais qui récompense l’observation et la planification.

Un rendu isométrique épuré et lisible

Le moteur graphique repose sur des formes géométriques (ellipses, rectangles, lignes) pour représenter les cases, le joueur, les gardes et l’objectif. Le résultat : une lecture immédiate de la scène, même sans assets complexes.

Un terrain d’expérimentation riche
Ce programme met en lumière plusieurs axes techniques :

  • génération procédurale contrôlée,
  • gestion d’état dans un moteur de jeu,
  • IA comportementale simple mais cohérente,
  • rendu isométrique minimaliste,
  • mécaniques d’infiltration au tour par tour.

Autant de briques qui peuvent servir de base à des systèmes plus avancés : pathfinding, IA adaptative, niveaux multi-étages, ou encore gestion de vision et de bruit.

Conclusion

Agent 47 Go démontre qu’avec une architecture claire et des règles bien pensées, il est possible de créer une expérience tactique engageante, même dans un environnement minimaliste. Un bel exemple de ce que l’algorithmie peut apporter au game design.

Programme :

@ Agent 47 Go
toile1 est une toile
dimension(toile1, 600, 450)

dimensionGrille vaut 8
etatJeu est un nombre
etatJeu vaut 3 // 0 = En jeu, 1 = Victoire, 2 = Défaite, 3 = Génération

carte est un tableau
gardesX est un tableau
gardesY est un tableau
gardesDir est un tableau
gardesActif est un tableau

pX est un nombre
pY est un nombre
action est un nombre

tant que vrai

    @ 1. GÉNÉRATION SÉCURISÉE de LA MAP
    si etatJeu = 3 alors
        tant que longueur(gardesX) > 0
            gardesX supprime 0
        fin tant que
        tant que longueur(gardesY) > 0
            gardesY supprime 0
        fin tant que
        tant que longueur(gardesDir) > 0
            gardesDir supprime 0
        fin tant que
        tant que longueur(gardesActif) > 0
            gardesActif supprime 0
        fin tant que

        tant que longueur(carte) < dimensionGrille * dimensionGrille
            carte ajoute 0
        fin tant que
        tant que longueur(carte) > dimensionGrille * dimensionGrille
            carte supprime 0
        fin tant que

        pour i de 0 à (dimensionGrille * dimensionGrille) - 1
            carte[i] vaut 0
        fin pour

        cx est un nombre
        cy est un nombre
        cx vaut 0
        cy vaut 0
        carte[0] vaut 1

        tant que cx < dimensionGrille - 1 ou cy < dimensionGrille - 1
            si cx = dimensionGrille - 1 alors
                cy ajoute 1
            sinon si cy = dimensionGrille - 1 alors
                cx ajoute 1
            sinon
                r vaut hasard(0, 1)
                si r = 0 alors
                    cx ajoute 1
                sinon
                    cy ajoute 1
                fin si
            fin si

            indexChemin vaut (cy * dimensionGrille) + cx
            si indexChemin >= 0 et indexChemin < longueur(carte) alors
                carte[indexChemin] vaut 1
            fin si
        fin tant que

        pour i de 1 à 15
            rx vaut hasard(0, dimensionGrille - 1)
            ry vaut hasard(0, dimensionGrille - 1)

            si rx >= 0 et rx < dimensionGrille et ry >= 0 et ry < dimensionGrille alors
                idxCourant vaut (ry * dimensionGrille) + rx
                si idxCourant >= 0 et idxCourant < longueur(carte) et carte[idxCourant] = 0 alors
                    adj vaut 0

                    si rx > 0 alors
                        idxG vaut (ry * dimensionGrille) + rx - 1
                        si idxG >= 0 et idxG < longueur(carte) et carte[idxG] > 0 alors adj vaut 1 fin si
                    fin si
                    si rx < dimensionGrille - 1 alors
                        idxD vaut (ry * dimensionGrille) + rx + 1
                        si idxD >= 0 et idxD < longueur(carte) et carte[idxD] > 0 alors adj vaut 1 fin si
                    fin si
                    si ry > 0 alors
                        idxH vaut ((ry - 1) * dimensionGrille) + rx
                        si idxH >= 0 et idxH < longueur(carte) et carte[idxH] > 0 alors adj vaut 1 fin si
                    fin si
                    si ry < dimensionGrille - 1 alors
                        idxB vaut ((ry + 1) * dimensionGrille) + rx
                        si idxB >= 0 et idxB < longueur(carte) et carte[idxB] > 0 alors adj vaut 1 fin si
                    fin si

                    si adj = 1 alors
                        carte[idxCourant] vaut 1
                    fin si
                fin si
            fin si
        fin pour

        indexArrivee vaut ((dimensionGrille - 1) * dimensionGrille) + (dimensionGrille - 1)
        si indexArrivee >= 0 et indexArrivee < longueur(carte) alors
            carte[indexArrivee] vaut 2
        fin si

        pX vaut 0
        pY vaut 0

        nbG vaut hasard(2, 4)
        pour i de 1 à nbG
            trouve vaut faux
            essais vaut 0
            tant que non trouve et essais < 40
                gxRand vaut hasard(0, dimensionGrille - 1)
                gyRand vaut hasard(0, dimensionGrille - 1)

                si gxRand >= 0 et gxRand < dimensionGrille et gyRand >= 0 et gyRand < dimensionGrille alors
                    idxGrd vaut (gyRand * dimensionGrille) + gxRand
                    si idxGrd >= 0 et idxGrd < longueur(carte) alors
                        si carte[idxGrd] = 1 et (gxRand <> 0 ou gyRand <> 0) et (gxRand <> dimensionGrille - 1 ou gyRand <> dimensionGrille - 1) alors
                            libre vaut vrai
                            pour j de 0 à longueur(gardesX) - 1
                                si gardesX[j] = gxRand et gardesY[j] = gyRand alors libre vaut faux fin si
                            fin pour

                            si libre alors
                                gardesX ajoute gxRand
                                gardesY ajoute gyRand
                                gardesDir ajoute hasard(0, 3)
                                gardesActif ajoute 1
                                trouve vaut vrai
                            fin si
                        fin si
                    fin si
                fin si
                essais ajoute 1
            fin tant que
        fin pour

        etatJeu vaut 0
    fin si

    @ 2. RENDU VISUEL AMÉLIORÉ & AGRANDI (ZOOM ISOMÉTRIQUE)
    effacer(toile1)
    remplir(toile1, #0b131d)

    offX vaut 280
    offY vaut 80

    pour gy de 0 à dimensionGrille - 1
        pour gx de 0 à dimensionGrille - 1
            idxRendu1 vaut (gy * dimensionGrille) + gx
            si idxRendu1 >= 0 et idxRendu1 < longueur(carte) et carte[idxRendu1] > 0 alors
                c1x vaut offX + (gx - gy) * 36
                c1y vaut offY + (gx + gy) * 20

                si gx < dimensionGrille - 1 alors
                    idxR2 vaut (gy * dimensionGrille) + gx + 1
                    si idxR2 >= 0 et idxR2 < longueur(carte) et carte[idxR2] > 0 alors
                        c2x vaut offX + (gx + 1 - gy) * 36
                        c2y vaut offY + (gx + 1 + gy) * 20
                        ligne(toile1, c1x, c1y, c2x, c2y, rgba(255, 255, 255, 0.15), 6)
                    fin si
                fin si

                si gy < dimensionGrille - 1 alors
                    idxR3 vaut ((gy + 1) * dimensionGrille) + gx
                    si idxR3 >= 0 et idxR3 < longueur(carte) et carte[idxR3] > 0 alors
                        c2x vaut offX + (gx - (gy + 1)) * 36
                        c2y vaut offY + (gx + (gy + 1)) * 20
                        ligne(toile1, c1x, c1y, c2x, c2y, rgba(255, 255, 255, 0.15), 6)
                    fin si
                fin si
            fin si
        fin pour
    fin pour

    pour gy de 0 à dimensionGrille - 1
        pour gx de 0 à dimensionGrille - 1
            idxRendu4 vaut (gy * dimensionGrille) + gx
            si idxRendu4 >= 0 et idxRendu4 < longueur(carte) alors
                valN vaut carte[idxRendu4]
                si valN > 0 alors
                    cx vaut offX + (gx - gy) * 36
                    cy vaut offY + (gx + gy) * 20

                    si valN = 2 alors
                        ellipse(toile1, cx, cy + 3, 20, 11, rgba(0, 0, 0, 0.4))
                        ellipse(toile1, cx, cy, 18, 10, #145a32)
                        ellipse(toile1, cx, cy - 3, 15, 8, #27ae60)
                    sinon
                        ellipse(toile1, cx, cy + 3, 18, 10, rgba(0, 0, 0, 0.4))
                        ellipse(toile1, cx, cy, 16, 9, #34495e)
                        ellipse(toile1, cx, cy - 3, 13, 7, #bdc3c7)
                    fin si

                    @ Affichage du Joueur (Agent 47) avec position fixe et cohérente sur sa case
                    si pX = gx et pY = gy alors
                        ellipse(toile1, cx, cy + 2, 10, 5, rgba(0, 0, 0, 0.6))
                        rectangle(toile1, cx - 5, cy - 24, 10, 20, #17202a)
                        rectangle(toile1, cx - 2, cy - 20, 4, 10, #c0392b)
                        cercle(toile1, cx, cy - 25, 5, #f5cba7)
                    fin si

                    pour i de 0 à longueur(gardesX) - 1
                        si gardesActif[i] = 1 et gardesX[i] = gx et gardesY[i] = gy alors
                            ellipse(toile1, cx, cy + 2, 10, 5, rgba(0, 0, 0, 0.6))
                            rectangle(toile1, cx - 5, cy - 22, 10, 18, #d4ac0d)
                            cercle(toile1, cx, cy - 26, 6, #eaeded)

                            vx vaut 0
                            vy vaut 0
                            dir vaut gardesDir[i]
                            si dir = 0 alors vx vaut 18 vy vaut 10 fin si
                            si dir = 1 alors vx vaut - 18 vy vaut 10 fin si
                            si dir = 2 alors vx vaut - 18 vy vaut - 10 fin si
                            si dir = 3 alors vx vaut 18 vy vaut - 10 fin si

                            ligne(toile1, cx, cy - 16, cx + vx, cy - 16 + vy, rgba(231, 76, 60, 0.85), 2.5)
                        fin si
                    fin pour
                fin si
            fin si
        fin pour
    fin pour

    @ 3. INTERFACE UTILISATEUR
    rectangle(toile1, 0, 0, 600, 55, rgba(11, 19, 29, 0.85))
    si etatJeu = 0 alors
        label(toile1, 15, 22, "AGENT 47 : Infiltration tactique", #ffffff, 14)
        label(toile1, 15, 42, "Mouvements: Z Q S D  |  Attendre: A", #95a5a6, 11)
    sinon si etatJeu = 1 alors
        rectangle(toile1, 130, 140, 340, 110, rgba(11, 19, 29, 0.95))
        label(toile1, 205, 185, "OBJECTIF ATTEINT", #27ae60, 18)
        label(toile1, 160, 215, "Appuyez sur ESPACE pour continuer", #ffffff, 11)
    sinon si etatJeu = 2 alors
        rectangle(toile1, 150, 140, 300, 110, rgba(11, 19, 29, 0.95))
        label(toile1, 235, 185, "REPÉRÉ / ÉLIMINÉ", #c0392b, 18)
        label(toile1, 165, 215, "Appuyez sur ESPACE pour réessayer", #ffffff, 11)
    fin si

    affiche toile1

    @ 4. GESTION DES TOURS et COMBAT
    appuyer ["z", "s", "q", "d", " ", "a"] dans action

    si etatJeu = 1 ou etatJeu = 2 alors
        si action = 5 alors
            etatJeu vaut 3
        fin si
    sinon si etatJeu = 0 alors

        nx vaut pX
        ny vaut pY
        tourJoue vaut faux

        si action = 1 alors ny soustrait 1 fin si
        si action = 2 alors ny ajoute 1 fin si
        si action = 3 alors nx soustrait 1 fin si
        si action = 4 alors nx ajoute 1 fin si
        si action = 6 alors tourJoue vaut vrai fin si

        si nx >= 0 et nx < dimensionGrille et ny >= 0 et ny < dimensionGrille alors
            idxJoueur vaut (ny * dimensionGrille) + nx
            si idxJoueur >= 0 et idxJoueur < longueur(carte) alors
                si carte[idxJoueur] > 0 alors
                    pX vaut nx
                    pY vaut ny
                    tourJoue vaut vrai
                fin si
            fin si
        fin si

        si tourJoue = vrai alors

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 et gardesX[i] = pX et gardesY[i] = pY alors
                    gardesActif[i] vaut 0
                fin si
            fin pour

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 alors
                    si gardesX[i] >= 0 et gardesX[i] < dimensionGrille et gardesY[i] >= 0 et gardesY[i] < dimensionGrille alors
                        d vaut gardesDir[i]
                        gnx vaut gardesX[i]
                        gny vaut gardesY[i]

                        si d = 0 alors gnx ajoute 1 fin si
                        si d = 1 alors gny ajoute 1 fin si
                        si d = 2 alors gnx soustrait 1 fin si
                        si d = 3 alors gny soustrait 1 fin si

                        si gnx >= 0 et gnx < dimensionGrille et gny >= 0 et gny < dimensionGrille alors
                            idxG2 vaut (gny * dimensionGrille) + gnx
                            si idxG2 >= 0 et idxG2 < longueur(carte) alors
                                si carte[idxG2] > 0 alors
                                    caseLibre vaut vrai
                                    pour j de 0 à longueur(gardesX) - 1
                                        si i <> j et gardesActif[j] = 1 et gardesX[j] = gnx et gardesY[j] = gny alors
                                            caseLibre vaut faux
                                        fin si
                                    fin pour

                                    si caseLibre alors
                                        gardesX[i] vaut gnx
                                        gardesY[i] vaut gny
                                    sinon
                                        gardesDir[i] vaut (d + 2) mod 4
                                    fin si
                                sinon
                                    gardesDir[i] vaut (d + 2) mod 4
                                fin si
                            sinon
                                gardesDir[i] vaut (d + 2) mod 4
                            fin si
                        sinon
                            gardesDir[i] vaut (d + 2) mod 4
                        fin si
                    fin si
                fin si
            fin pour

            pour i de 0 à longueur(gardesX) - 1
                si gardesActif[i] = 1 alors
                    si gardesX[i] >= 0 et gardesX[i] < dimensionGrille et gardesY[i] >= 0 et gardesY[i] < dimensionGrille alors
                        si gardesX[i] = pX et gardesY[i] = pY alors
                            etatJeu vaut 2
                        fin si

                        fx vaut gardesX[i]
                        fy vaut gardesY[i]
                        si gardesDir[i] = 0 alors fx ajoute 1 fin si
                        si gardesDir[i] = 1 alors fy ajoute 1 fin si
                        si gardesDir[i] = 2 alors fx soustrait 1 fin si
                        si gardesDir[i] = 3 alors fy soustrait 1 fin si

                        si fx >= 0 et fx < dimensionGrille et fy >= 0 et fy < dimensionGrille alors
                            idxF vaut (fy * dimensionGrille) + fx
                            si idxF >= 0 et idxF < longueur(carte) alors
                                si pX = fx et pY = fy alors
                                    etatJeu vaut 2
                                fin si
                            fin si
                        fin si
                    fin si
                fin si
            fin pour

            idxFinValidation vaut (pY * dimensionGrille) + pX
            si etatJeu <> 2 et idxFinValidation >= 0 et idxFinValidation < longueur(carte) et carte[idxFinValidation] = 2 alors
                etatJeu vaut 1
            fin si

        fin si
    fin si

fin tant que