Ce projet explore la conception d’un système de combat interactif, entièrement codé, intégrant une interface graphique, une boucle de jeu, des mécaniques de progression et une génération procédurale de monstres.
Une manière concrète de transformer du code en une expérience visuelle et ludique.
Une interface pensée comme un mini-jeu complet
Le programme initialise une toile graphique :
- « dimension(graph, 600, 500) »
- « remplir(graph, #0b0f19) »
Puis construit une interface composée de :
- un HUD supérieur (titre, étage, or),
- une zone de combat avec barre de vie du monstre,
- un portrait animé du monstre (formes, couleurs, cornes, yeux),
- une fiche du joueur (PV, Mana, Attaque, Bouclier),
- un journal d’action,
- quatre boutons interactifs permettant d’agir à chaque tour.
L’ensemble donne une vraie impression de jeu : clair, lisible, cohérent.
Un système de combat au tour par tour
Le cœur du programme repose sur une boucle infinie qui :
- dessine l’interface,
- attend un clic,
- exécute l’action choisie,
- met à jour les PV, Mana, Bouclier,
- gère la riposte du monstre,
- teste la victoire ou la défaite.
Les actions disponibles :
Attaque à l’épée : dégâts physiques + régénération de Mana
Boule de feu : attaque magique à fort impact
Soin : récupération de PV + bouclier
Forge : amélioration permanente des stats
Chaque choix influence directement le tour suivant, créant une vraie dynamique stratégique.
Une génération procédurale des monstres
Le programme inclut une procédure dédiée :
- nouveauMonstre()
Elle génère automatiquement :
- le niveau du monstre,
- ses PV,
- son attaque,
- son type (Gobelin, Orc, Golem),
- sa couleur,
- son nom,
- et même des boss tous les 5 étages.
Les boss bénéficient d’un boost de statistiques :
- PV × 1.5
- Attaque × 1.3
- Couleur spécifique (#ef4444)
Cette logique crée une progression naturelle, avec des combats de plus en plus difficiles.
Une mécanique de riposte intelligente
Après chaque action du joueur, si le monstre est encore vivant :
- il attaque,
- le bouclier absorbe une partie des dégâts,
- le journal affiche les événements du tour.
Le système gère même les cas où le bouclier absorbe tout ou seulement une partie des dégâts.
Victoire, défaite et boucle de progression
Le jeu propose trois états :
- Combat (0)
- Victoire d’étage (1)
- Défaite (2)
Chaque état modifie l’interface et les actions disponibles :
- Victoire ? bouton “Monstre suivant”
- Défaite ? bouton “Recommencer”
- Combat ? quatre actions tactiques
La progression est fluide, intuitive et entièrement gérée par le code.
Ce que ce projet démontre
- La capacité du code à créer une expérience interactive complète.
- L’intérêt de combiner graphisme, logique de jeu, événements utilisateur et progression procédurale.
- La puissance d’une boucle de jeu bien structurée.
- La richesse pédagogique d’un projet qui mélange interface, gameplay et algorithmique.
Programme :
@ Initialisation de la canvas
graph est une toile
dimension(graph, 600, 500)
// Statistiques du Joueur
jHPMax est un nombre
jHPMax vaut 100
jHP est un nombre
jHP vaut 100
jManaMax est un nombre
jManaMax vaut 50
jMana est un nombre
jMana vaut 50
jAttaque est un nombre
jAttaque vaut 18
jBouclier est un nombre
jBouclier vaut 0
jOr est un nombre
jOr vaut 0
// Statistiques du Monstre
mNiveau est un nombre
mNiveau vaut 1
mHPMax est un nombre
mHPMax vaut 60
mHP est un nombre
mHP vaut 60
mAttaque est un nombre
mAttaque vaut 7
mNom est un texte
mNom vaut "Gobelin Vert"
mType est un nombre
mType vaut 1
mCouleur est un texte
mCouleur vaut #22c55e
// État du jeu (0 = Combat, 1 = Victoire Étage, 2 = Défaite)
etatJeu est un nombre
etatJeu vaut 0
logTxt est un texte
logTxt vaut "À toi de jouer ! Clique sur un bouton en bas."
clicX est un nombre
clicY est un nombre
@ Génération du monstre suivant
procédure nouveauMonstre()
mNiveau vaut mNiveau + 1
mHPMax vaut 40 + (mNiveau * 22)
mHP vaut mHPMax
mAttaque vaut 5 + (mNiveau * 3)
mType vaut hasard(1, 3)
si mType = 1 alors
mNom vaut "Gobelin Nv." + convertir_texte(mNiveau)
mCouleur vaut #22c55e
sinon si mType = 2 alors
mNom vaut "Orc Guerrier Nv." + convertir_texte(mNiveau)
mCouleur vaut #a855f7
sinon
mNom vaut "Golem de Pierre Nv." + convertir_texte(mNiveau)
mCouleur vaut #64748b
fin si
// Boss tous les 5 étages
si mNiveau = 5 ou mNiveau = 10 ou mNiveau = 15 alors
mType vaut 4
mNom vaut "BOSS Dragon Infernal Nv." + convertir_texte(mNiveau)
mCouleur vaut #ef4444
mHPMax vaut mHPMax * 1.5
mHP vaut mHPMax
mAttaque vaut mAttaque * 1.3
fin si
fin procédure
@ Boucle principale
tant que vrai
effacer(graph)
remplir(graph, #0b0f19)
// --- 1. HUD SUPÉRIEUR ---
rectangle(graph, 0, 0, 600, 40, #0f172a)
label(graph, 15, 25, "DUNGEON ARENA", #ffffff, 13)
label(graph, 260, 25, "Étage : " + convertir_texte(mNiveau), #38bdf8, 12)
label(graph, 450, 25, "Or : " + convertir_texte(jOr) + " G", #facc15, 12)
// --- 2. ARENE DE COMBAT (MONSTRE VISIBLE) ---
rectangle_arrondi(graph, 20, 50, 560, 180, 8, #1e293b)
contour(graph, #334155, 2)
// Nom et PV Monstre
label(graph, 35, 75, mNom, #ffffff, 14)
mHPNet est un nombre
mHPNet vaut maximum(0, mHP)
ratioHPm est un nombre
ratioHPm vaut mHPNet / mHPMax
rectangle_arrondi(graph, 250, 60, 310, 20, 4, #0f172a)
rectangle_arrondi(graph, 250, 60, ratioHPm * 310, 20, 4, #ef4444)
txtHPm est un texte
txtHPm vaut convertir_texte(arrondi(mHPNet)) + " / " + convertir_texte(arrondi(mHPMax)) + " PV"
label(graph, 350, 75, txtHPm, #ffffff, 10)
// DESSIN DU MONSTRE EN GRAND AU CENTRE
ellipse(graph, 300, 200, 60, 14, rgba(0, 0, 0, 0.4))
rectangle_arrondi(graph, 265, 130, 70, 65, 12, mCouleur)
cercle(graph, 300, 115, 32, mCouleur)
// Yeux
cercle(graph, 288, 110, 9, #ffffff)
cercle(graph, 312, 110, 9, #ffffff)
cercle(graph, 288, 110, 4, #dc2626)
cercle(graph, 312, 110, 4, #dc2626)
// Bouche
rectangle(graph, 286, 128, 28, 8, #0f172a)
ptsDent1 est un tableau
ptsDent1 ajoute 290
ptsDent1 ajoute 128
ptsDent1 ajoute 294
ptsDent1 ajoute 134
ptsDent1 ajoute 298
ptsDent1 ajoute 128
polygone(graph, ptsDent1, #ffffff)
ptsDent2 est un tableau
ptsDent2 ajoute 302
ptsDent2 ajoute 128
ptsDent2 ajoute 306
ptsDent2 ajoute 134
ptsDent2 ajoute 310
ptsDent2 ajoute 128
polygone(graph, ptsDent2, #ffffff)
// Oreilles / Cornes selon le type
si mType = 1 alors
ptsOreilleG est un tableau
ptsOreilleG ajoute 270
ptsOreilleG ajoute 110
ptsOreilleG ajoute 240
ptsOreilleG ajoute 95
ptsOreilleG ajoute 265
ptsOreilleG ajoute 125
polygone(graph, ptsOreilleG, mCouleur)
ptsOreilleD est un tableau
ptsOreilleD ajoute 330
ptsOreilleD ajoute 110
ptsOreilleD ajoute 360
ptsOreilleD ajoute 95
ptsOreilleD ajoute 335
ptsOreilleD ajoute 125
polygone(graph, ptsOreilleD, mCouleur)
sinon si mType = 4 alors
ptsCorneG est un tableau
ptsCorneG ajoute 280
ptsCorneG ajoute 90
ptsCorneG ajoute 265
ptsCorneG ajoute 65
ptsCorneG ajoute 290
ptsCorneG ajoute 85
polygone(graph, ptsCorneG, #991b1b)
ptsCorneD est un tableau
ptsCorneD ajoute 320
ptsCorneD ajoute 90
ptsCorneD ajoute 335
ptsCorneD ajoute 65
ptsCorneD ajoute 310
ptsCorneD ajoute 85
polygone(graph, ptsCorneD, #991b1b)
fin si
rectangle_arrondi(graph, 248, 140, 18, 40, 6, mCouleur)
rectangle_arrondi(graph, 334, 140, 18, 40, 6, mCouleur)
// --- 3. INFOS DU JOUEUR ---
rectangle_arrondi(graph, 20, 240, 560, 75, 8, #0f172a)
contour(graph, #38bdf8, 1)
label(graph, 35, 262, "VOTRE HÉROS", #ffffff, 12)
// PV Joueur
jHPNet est un nombre
jHPNet vaut maximum(0, jHP)
ratioHPj est un nombre
ratioHPj vaut jHPNet / jHPMax
label(graph, 35, 283, "PV", #4ade80, 10)
rectangle_arrondi(graph, 65, 272, 180, 14, 3, #1e293b)
rectangle_arrondi(graph, 65, 272, ratioHPj * 180, 14, 3, #22c55e)
label(graph, 115, 283, convertir_texte(arrondi(jHPNet)) + "/" + convertir_texte(jHPMax), #ffffff, 9)
// Mana Joueur
ratioMPj est un nombre
ratioMPj vaut jMana / jManaMax
label(graph, 35, 303, "MP", #38bdf8, 10)
rectangle_arrondi(graph, 65, 292, 180, 14, 3, #1e293b)
rectangle_arrondi(graph, 65, 292, ratioMPj * 180, 14, 3, #38bdf8)
label(graph, 115, 303, convertir_texte(arrondi(jMana)) + "/" + convertir_texte(jManaMax), #ffffff, 9)
// Stats
label(graph, 275, 283, "Attaque : " + convertir_texte(jAttaque), #fbbf24, 11)
label(graph, 275, 303, "Bouclier : " + convertir_texte(jBouclier), #38bdf8, 11)
// --- 4. JOURNAL ---
rectangle_arrondi(graph, 20, 322, 560, 32, 6, #1e293b)
contour(graph, #475569, 1)
label(graph, 30, 343, logTxt, #f1f5f9, 10)
// --- 5. BOUTONS D'ACTION ---
si etatJeu = 0 alors
rectangle_arrondi(graph, 20, 362, 270, 56, 6, #dc2626)
label(graph, 35, 387, "1. ATTAQUER À L'ÉPÉE", #ffffff, 12)
label(graph, 35, 405, "Gratuit | Regen +8 MP", #fca5a5, 9)
rectangle_arrondi(graph, 310, 362, 270, 56, 6, #d97706)
label(graph, 325, 387, "2. BOULE DE FEU", #ffffff, 12)
label(graph, 325, 405, "Coût: 20 MP | Gros dégâts", #fde68a, 9)
rectangle_arrondi(graph, 20, 426, 270, 56, 6, #16a34a)
label(graph, 35, 451, "3. SE SOIGNER", #ffffff, 12)
label(graph, 35, 469, "Coût: 15 MP | +30 PV & Shield", #86efac, 9)
rectangle_arrondi(graph, 310, 426, 270, 56, 6, #7c3aed)
label(graph, 325, 451, "4. FORGE (Amélioration)", #ffffff, 12)
label(graph, 325, 469, "Coût: 35 Or | +7 Atk, +20 PV", #c4b5fd, 9)
sinon si etatJeu = 1 alors
rectangle_arrondi(graph, 150, 375, 300, 65, 8, #16a34a)
label(graph, 212, 403, "MONSTRE SUIVANT ->", #ffffff, 15)
label(graph, 198, 424, "Clique ici pour continuer le donjon", #dcfce7, 10)
sinon si etatJeu = 2 alors
rectangle_arrondi(graph, 150, 375, 300, 65, 8, #dc2626)
label(graph, 205, 412, "RECOMMENCER", #ffffff, 16)
label(graph, 190, 430, "Tu as perdu ! Clique ici pour rejouer", #fee2e2, 10)
fin si
affiche graph
// --- DÉTECTION DES CLICS ET TOURS ---
tourJoue est un booléen
tourJoue vaut faux
cliquer graph dans clicX, clicY
si etatJeu = 0 alors
// CLIC SUR 1. ATTAQUE ÉPÉE
si clique(clicX, clicY, 20, 362, 270, 56) alors
degatsJ est un nombre
degatsJ vaut jAttaque + hasard(-2, 3)
mHP vaut mHP - degatsJ
jMana vaut minimum(jManaMax, jMana + 8)
logTxt vaut "Tu attaques à l'épée (-" + convertir_texte(degatsJ) + " PV au monstre) !"
tourJoue vaut vrai
// CLIC SUR 2. BOULE DE FEU
sinon si clique(clicX, clicY, 310, 362, 270, 56) alors
si jMana >= 20 alors
jMana vaut jMana - 20
degatsJ vaut (jAttaque * 2.2) + hasard(-1, 4)
mHP vaut mHP - degatsJ
logTxt vaut "Boule de feu magique ! (-" + convertir_texte(arrondi(degatsJ)) + " PV au monstre)"
tourJoue vaut vrai
sinon
logTxt vaut "Pas assez de Mana ! (20 MP requis)"
fin si
// CLIC SUR 3. SE SOIGNER
sinon si clique(clicX, clicY, 20, 426, 270, 56) alors
si jMana >= 15 alors
jMana vaut jMana - 15
jHP vaut minimum(jHPMax, jHP + 30)
jBouclier vaut jBouclier + 15
logTxt vaut "Sort de soin lancé ! +30 PV et +15 Bouclier."
tourJoue vaut vrai
sinon
logTxt vaut "Pas assez de Mana ! (15 MP requis)"
fin si
// CLIC SUR 4. FORGE
sinon si clique(clicX, clicY, 310, 426, 270, 56) alors
si jOr >= 35 alors
jOr vaut jOr - 35
jAttaque vaut jAttaque + 7
jHPMax vaut jHPMax + 20
jHP vaut jHP + 20
logTxt vaut "Forge : Attaque +7 et PV Max +20 !"
sinon
logTxt vaut "Pas assez d'or ! (35 Or requis)"
fin si
fin si
// TEST MORT DU MONSTRE
si mHP <= 0 alors
mHP vaut 0
gainOr est un nombre
gainOr vaut 15 + (mNiveau * 10)
jOr vaut jOr + gainOr
etatJeu vaut 1
logTxt vaut "VICTOIRE ! Monstre terrassé (+ " + convertir_texte(gainOr) + " Or) !"
tourJoue vaut faux
fin si
// RIPOSTE DU MONSTRE
si tourJoue et mHP > 0 alors
degatsM est un nombre
degatsM vaut mAttaque + hasard(-2, 2)
si jBouclier > 0 alors
si jBouclier >= degatsM alors
jBouclier vaut jBouclier - degatsM
degatsM vaut 0
sinon
degatsM vaut degatsM - jBouclier
jBouclier vaut 0
fin si
fin si
jHP vaut jHP - degatsM
logTxt vaut logTxt + " | Riposte : - " + convertir_texte(arrondi(degatsM)) + " PV"
si jHP <= 0 alors
jHP vaut 0
etatJeu vaut 2
logTxt vaut "DÉFAITE... Tu as perdu tous tes PV."
fin si
fin si
sinon si etatJeu = 1 alors
// CLIC MONSTRE SUIVANT
si clique(clicX, clicY, 150, 375, 300, 65) alors
appelle nouveauMonstre()
etatJeu vaut 0
jMana vaut jManaMax
logTxt vaut "Nouveau combat engagé ! À toi de jouer."
fin si
sinon si etatJeu = 2 alors
// CLIC RECOMMENCER
si clique(clicX, clicY, 150, 375, 300, 65) alors
jHPMax vaut 100
jHP vaut 100
jManaMax vaut 50
jMana vaut 50
jAttaque vaut 18
jBouclier vaut 0
jOr vaut 0
mNiveau vaut 0
appelle nouveauMonstre()
etatJeu vaut 0
logTxt vaut "Nouvelle partie commencée ! Bonne chance."
fin si
fin si
fin tant que