Ce programme illustre comment l’algorithmie, la logique de jeu et le rendu visuel peuvent s’intégrer pour créer une expérience complète inspirée du célèbre Sokoban.
L’ensemble repose sur une architecture qui combine génération procédurale, gestion des interactions et affichage isométrique 3D.
Génération procédurale d’un niveau jouable
Le système construit automatiquement une salle fermée, y place des caisses, des cibles et un joueur, puis applique une série de mouvements aléatoires pour obtenir une configuration réaliste et jouable.
Points clés :
- Création d’une grille 10×10 avec murs et zones libres.
- Placement des caisses uniquement sur des cibles, jamais sur les bords.
- Mélange contrôlé via des déplacements simulés.
Validation stricte du niveau :
- bon nombre de caisses,
- aucune caisse bloquée,
- au moins une caisse hors cible pour garantir un vrai défi.
Extrait du programme :
« si caisseSurBord() alors valide vaut faux »
« si caissesHorsCible() = 0 alors valide vaut faux »
Chaque partie démarre ainsi avec un puzzle cohérent et intéressant.
Logique de déplacement et gestion des collisions
Le déplacement du joueur suit les règles classiques du Sokoban :
- impossibilité de traverser les murs,
- poussée des caisses uniquement si l’espace derrière est libre,
- distinction entre caisse sur cible (*) et caisse hors cible (b).
Extrait du programme :
« si estCible(al, ac) alors poser(al, ac, "") sinon poser(al, ac, "b") »
Cette mécanique garantit une fidélité totale aux principes du jeu original.
Rendu isométrique 3D : immersion et lisibilité
La partie graphique repose sur un rendu isométrique détaillé :
- sols en losange,
- murs en volume,
- caisses en 3D avec variations visuelles,
- personnage stylisé avec ombre, tronc, tête et yeux.
Chaque élément est dessiné via des polygones, ce qui crée une ambiance visuelle cohérente et agréable.
Extrait du programme :
« polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c) »
Le résultat est une carte isométrique claire, immersive et parfaitement adaptée à un jeu de réflexion.
Une architecture complète de mini-jeu
Ce projet combine :
- génération procédurale,
- gestion des états,
- interactions clavier,
- rendu graphique avancé,
- boucle de jeu complète,
- détection de victoire.
Un exemple concret de la manière dont l’algorithmie peut servir la créativité, et comment un ensemble de fonctions peut devenir une expérience ludique cohérente.
Perspectives
Ce type de moteur ouvre la voie à :
- la création automatique de puzzles,
- l’ajout d’animations,
- l’intégration dans un moteur plus large,
- l’export vers le web ou le mobile,
- l’expérimentation IA autour de la résolution de niveaux.
Programme :
// ============================================================
// PARAMETRES
// ============================================================
lignes est un nombre
lignes vaut 10
colonnes est un nombre
colonnes vaut 10
nbCaisses est un nombre
nbCaisses vaut 5
melange est un nombre
melange vaut 60
// ============================================================
// GRILLE
// ============================================================
niveau est un tableau
cibles est un tableau
jl est un nombre
jc est un nombre
jl vaut 0
jc vaut 0
fonction caseAt(li, co)
ln est un texte
ln vaut niveau[li]
retourne ln[co]
fin fonction
fonction cibleAt(li, co)
ln est un texte
ln vaut cibles[li]
retourne ln[co]
fin fonction
procedure poser(li, co, nc)
ln est un texte
ln vaut niveau[li]
nouvelle est un texte
nouvelle vaut ""
k est un nombre
k vaut 1
tant que k <= longueur(ln)
si k = co alors
nouvelle vaut nouvelle + nc
sinon
nouvelle vaut nouvelle + ln[k]
fin si
k vaut k + 1
fin tant que
niveau[li] vaut nouvelle
fin procedure
procedure poserCible(li, co)
ln est un texte
ln vaut cibles[li]
nouvelle est un texte
nouvelle vaut ""
k est un nombre
k vaut 1
tant que k <= longueur(ln)
si k = co alors
nouvelle vaut nouvelle + "c"
sinon
nouvelle vaut nouvelle + ln[k]
fin si
k vaut k + 1
fin tant que
cibles[li] vaut nouvelle
fin procedure
fonction estCible(li, co)
si cibleAt(li, co) = "c" alors
retourne vrai
fin si
retourne faux
fin fonction
fonction estLibre(li, co)
ca est un texte
ca vaut caseAt(li, co)
si (ca = "#") ou (ca = "b") ou (ca = "*") alors
retourne faux
fin si
retourne vrai
fin fonction
// ============================================================
// GENERATION DU NIVEAU
// ============================================================
procedure construireSalle()
vide niveau
vide cibles
li est un nombre
li vaut 0
tant que li < lignes
ligneN est un texte
ligneN vaut ""
ligneC est un texte
ligneC vaut ""
co est un nombre
co vaut 1
tant que co <= colonnes
bord est un booleen
bord vaut faux
si (li = 0) ou (li = lignes - 1) alors
bord vaut vrai
fin si
si (co = 1) ou (co = colonnes) alors
bord vaut vrai
fin si
si bord alors
ligneN vaut ligneN + "#"
sinon
ligneN vaut ligneN + "."
fin si
ligneC vaut ligneC + "."
co vaut co + 1
fin tant que
niveau ajoute ligneN
cibles ajoute ligneC
li vaut li + 1
fin tant que
fin procedure
procedure placerCaisses()
poses est un nombre
poses vaut 0
essais est un nombre
essais vaut 0
tant que (poses < nbCaisses) et (essais < 800)
essais vaut essais + 1
rl est un nombre
rl vaut hasard(2, lignes - 3)
rc est un nombre
rc vaut hasard(3, colonnes - 2)
si caseAt(rl, rc) = "." alors
appelle poser(rl, rc, "*")
appelle poserCible(rl, rc)
poses vaut poses + 1
fin si
fin tant que
fin procedure
procedure placerJoueur()
ok est un booleen
ok vaut faux
essais est un nombre
essais vaut 0
tant que (non ok) et (essais < 800)
essais vaut essais + 1
rl est un nombre
rl vaut hasard(1, lignes - 2)
rc est un nombre
rc vaut hasard(2, colonnes - 1)
si caseAt(rl, rc) = "." alors
appelle poser(rl, rc, "j")
jl vaut rl
jc vaut rc
ok vaut vrai
fin si
fin tant que
fin procedure
procedure tirerUnCoup()
dl est un nombre
dc est un nombre
choix est un nombre
choix vaut hasard(1, 4)
dl vaut 0
dc vaut 0
si choix = 1 alors
dl vaut - 1
sinon si choix = 2 alors
dl vaut 1
sinon si choix = 3 alors
dc vaut - 1
sinon
dc vaut 1
fin si
reculL est un nombre
reculL vaut jl + dl
reculC est un nombre
reculC vaut jc + dc
caisseL est un nombre
caisseL vaut jl - dl
caisseC est un nombre
caisseC vaut jc - dc
si estLibre(reculL, reculC) alors
derriere est un texte
derriere vaut caseAt(caisseL, caisseC)
tire est un booleen
tire vaut faux
si (derriere = "b") ou (derriere = "*") alors
surBord est un booleen
surBord vaut faux
si (jl = 1) ou (jl = lignes - 2) alors
surBord vaut vrai
fin si
si (jc = 2) ou (jc = colonnes - 1) alors
surBord vaut vrai
fin si
si (non surBord) et (hasard(0, 1) = 1) alors
tire vaut vrai
fin si
fin si
si estCible(jl, jc) alors
appelle poser(jl, jc, "c")
sinon
appelle poser(jl, jc, ".")
fin si
si tire alors
si estCible(caisseL, caisseC) alors
appelle poser(caisseL, caisseC, "c")
sinon
appelle poser(caisseL, caisseC, ".")
fin si
si estCible(jl, jc) alors
appelle poser(jl, jc, "*")
sinon
appelle poser(jl, jc, "b")
fin si
fin si
appelle poser(reculL, reculC, "j")
jl vaut reculL
jc vaut reculC
fin si
fin procedure
fonction caisseSurBord()
li est un nombre
li vaut 1
tant que li < lignes - 1
co est un nombre
co vaut 2
tant que co < colonnes
car est un texte
car vaut caseAt(li, co)
si (car = "b") ou (car = "*") alors
si (li = 1) ou (li = lignes - 2) ou (co = 2) ou (co = colonnes - 1) alors
retourne vrai
fin si
fin si
co vaut co + 1
fin tant que
li vaut li + 1
fin tant que
retourne faux
fin fonction
fonction compteCaisses()
n est un nombre
n vaut 0
li est un nombre
li vaut 0
tant que li < lignes
co est un nombre
co vaut 1
tant que co <= colonnes
car est un texte
car vaut caseAt(li, co)
si (car = "b") ou (car = "*") alors
n vaut n + 1
fin si
co vaut co + 1
fin tant que
li vaut li + 1
fin tant que
retourne n
fin fonction
fonction caissesHorsCible()
n est un nombre
n vaut 0
li est un nombre
li vaut 0
tant que li < lignes
co est un nombre
co vaut 1
tant que co <= colonnes
si caseAt(li, co) = "b" alors
n vaut n + 1
fin si
co vaut co + 1
fin tant que
li vaut li + 1
fin tant que
retourne n
fin fonction
procedure genererNiveau()
valide est un booleen
valide vaut faux
tentatives est un nombre
tentatives vaut 0
tant que (non valide) et (tentatives < 60)
tentatives vaut tentatives + 1
appelle construireSalle()
appelle placerCaisses()
appelle placerJoueur()
n est un nombre
n vaut 0
tant que n < melange
appelle tirerUnCoup()
n vaut n + 1
fin tant que
valide vaut vrai
si compteCaisses() <> nbCaisses alors
valide vaut faux
fin si
si caisseSurBord() alors
valide vaut faux
fin si
si caissesHorsCible() = 0 alors
valide vaut faux
fin si
fin tant que
fin procedure
// ============================================================
// DESSIN ISOMETRIQUE 3D
// ============================================================
tw est un nombre
tw vaut 56
th est un nombre
th vaut 28
g est une toile
dimension(g, 650, 400)
procedure dessiner()
dégradé(g, [#1e2a38, #2c3e50])
ox est un nombre
ox vaut 300
oy est un nombre
oy vaut 45 // <-- Ajusté pour centrer parfaitement la map 10x10 en hauteur
somm est un nombre
somm vaut 1
tant que somm <= lignes + colonnes
li est un nombre
li vaut 0
tant que li < lignes
co est un nombre
co vaut 1
tant que co <= colonnes
si (li + co) = somm alors
car est un texte
car vaut caseAt(li, co)
cx est un nombre
cx vaut ox + (co - li) * (tw / 2)
cy est un nombre
cy vaut oy + (co + li) * (th / 2)
// 1. Sol
si car <> "#" alors
polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c)
ligne(g, cx, cy - th/2, cx + tw/2, cy, #2c3e50, 1)
ligne(g, cx + tw/2, cy, cx, cy + th/2, #2c3e50, 1)
ligne(g, cx, cy + th/2, cx - tw/2, cy, #2c3e50, 1)
ligne(g, cx - tw/2, cy, cx, cy - th/2, #2c3e50, 1)
// Cible : caisse 3D rouge transparente
si estCible(li, co) alors
si car <> "*" alors
hcib est un nombre
hcib vaut 24
polygone(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.35))
polygone(g, cx - tw/2, cy - hcib, cx, cy + th/2 - hcib, cx, cy + th/2, cx - tw/2, cy, rgba(192, 57, 43, 0.45))
polygone(g, cx, cy + th/2 - hcib, cx + tw/2, cy - hcib, cx + tw/2, cy, cx, cy + th/2, rgba(146, 43, 33, 0.45))
ligne(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
ligne(g, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
ligne(g, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
ligne(g, cx - tw/2, cy - hcib, cx, cy - th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
ligne(g, cx, cy + th/2 - hcib, cx, cy + th/2, rgba(231, 76, 60, 0.8), 1)
fin si
fin si
fin si
// 2. Mur
si car = "#" alors
h est un nombre
h vaut 28
polygone(g, cx, cy - th/2 - h, cx + tw/2, cy - h, cx, cy + th/2 - h, cx - tw/2, cy - h, #243342)
polygone(g, cx - tw/2, cy - h, cx, cy + th/2 - h, cx, cy + th/2, cx - tw/2, cy, #141c26)
polygone(g, cx, cy + th/2 - h, cx + tw/2, cy - h, cx + tw/2, cy, cx, cy + th/2, #1a2531)
ligne(g, cx, cy + th/2 - h, cx, cy + th/2, #101820, 1)
ligne(g, cx, cy + th/2 - h, cx - tw/2, cy - h, #101820, 1)
ligne(g, cx, cy + th/2 - h, cx + tw/2, cy - h, #101820, 1)
fin si
// 3. Caisse
si (car = "b") ou (car = "*") alors
cTop est un texte
cTop vaut #f5b041
cLeft est un texte
cLeft vaut #d68910
cRight est un texte
cRight vaut #b9770e
si car = "*" alors
cTop vaut #2ecc71
cLeft vaut #27ae60
cRight vaut #1e8449
fin si
hc est un nombre
hc vaut 24
polygone(g, cx, cy - th/2 - hc, cx + tw/2, cy - hc, cx, cy + th/2 - hc, cx - tw/2, cy - hc, cTop)
polygone(g, cx - tw/2, cy - hc, cx, cy + th/2 - hc, cx, cy + th/2, cx - tw/2, cy, cLeft)
polygone(g, cx, cy + th/2 - hc, cx + tw/2, cy - hc, cx + tw/2, cy, cx, cy + th/2, cRight)
ligne(g, cx, cy + th/2 - hc, cx, cy + th/2, #875c06, 1)
fin si
// 4. Joueur (Personnage amélioré)
si car = "j" alors
// Ombre au sol
ellipse(g, cx, cy, 18, 9, rgba(0, 0, 0, 0.4))
// Dimensions du corps
pw est un nombre
pw vaut 20
ph est un nombre
ph vaut 10
pj est un nombre
pj vaut 18 // hauteur du tronc
// Tronc (Pull rouge)
polygone(g, cx, cy - ph/2 - pj, cx + pw/2, cy - pj, cx, cy + ph/2 - pj, cx - pw/2, cy - pj, #e74c3c)
polygone(g, cx - pw/2, cy - pj, cx, cy + ph/2 - pj, cx, cy + ph/2, cx - pw/2, cy, #c0392b)
polygone(g, cx, cy + ph/2 - pj, cx + pw/2, cy - pj, cx + pw/2, cy, cx, cy + ph/2, #922b21)
// Tête (Cercle couleur peau)
cTop est un nombre
cTop vaut cy - pj - 10
cercle(g, cx, cTop, 11, #f5cba7)
// Yeux
cercle(g, cx - 4, cTop - 2, 2, #2c3e50)
cercle(g, cx + 4, cTop - 2, 2, #2c3e50)
fin si
fin si
co vaut co + 1
fin tant que
li vaut li + 1
fin tant que
somm vaut somm + 1
fin tant que
affiche g
fin procedure
// ============================================================
// DEPLACEMENT
// ============================================================
procedure deplacer(dl, dc)
nl est un nombre
nl vaut jl + dl
nc est un nombre
nc vaut jc + dc
dest est un texte
dest vaut caseAt(nl, nc)
bloque est un booleen
bloque vaut faux
si dest = "#" alors
bloque vaut vrai
fin si
si (non bloque) et ((dest = "b") ou (dest = "*")) alors
al est un nombre
al vaut nl + dl
ac est un nombre
ac vaut nc + dc
apres est un texte
apres vaut caseAt(al, ac)
si (apres = "#") ou (apres = "b") ou (apres = "*") alors
bloque vaut vrai
sinon
si estCible(al, ac) alors
appelle poser(al, ac, "*")
sinon
appelle poser(al, ac, "b")
fin si
si estCible(nl, nc) alors
appelle poser(nl, nc, "c")
sinon
appelle poser(nl, nc, ".")
fin si
fin si
fin si
si non bloque alors
si estCible(jl, jc) alors
appelle poser(jl, jc, "c")
sinon
appelle poser(jl, jc, ".")
fin si
appelle poser(nl, nc, "j")
jl vaut nl
jc vaut nc
fin si
fin procedure
fonction gagne()
li est un nombre
li vaut 0
tant que li < lignes
co est un nombre
co vaut 1
tant que co <= colonnes
si caseAt(li, co) = "b" alors
retourne faux
fin si
co vaut co + 1
fin tant que
li vaut li + 1
fin tant que
retourne vrai
fin fonction
// ============================================================
// PARTIE
// ============================================================
appelle genererNiveau()
fini est un booleen
fini vaut faux
action est un nombre
action vaut 0
coups est un nombre
coups vaut 0
tant que non fini
appelle dessiner()
// Attente de la frappe d'une touche au clavier (z, s, q, d, r, x)
appuyer ["z", "s", "q", "d", "r", "x"] dans action
// action = 1 (z), 2 (s), 3 (q), 4 (d), 5 (r), 6 (x)
si action = 1 alors
appelle deplacer(-1, 0)
coups vaut coups + 1
sinon si action = 2 alors
appelle deplacer(1, 0)
coups vaut coups + 1
sinon si action = 3 alors
appelle deplacer(0, -1)
coups vaut coups + 1
sinon si action = 4 alors
appelle deplacer(0, 1)
coups vaut coups + 1
sinon si action = 5 alors
appelle genererNiveau()
coups vaut 0
sinon si action = 6 alors
fini vaut vrai
fin si
si non fini alors
si gagne() alors
fini vaut vrai
fin si
fin si
fin tant que
effacer(g)
appelle dessiner()
si gagne() alors
couleur(#2ecc71, "GAGNE en " + convertir_texte(coups) + " coups ! Bravo !")
sinon
couleur(#e74c3c, "Partie terminee. A bientot !")
fin si