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