PB : 6.20
Os : Windows
Bonjour,
Je vous propose aujourd'hui un éditeur de niveau simple d'emploi.

Les dimensions du tableau peuvent être supérieur à la taille de l'écran.
Les ascenseurs ou les touches fléchées du clavier vous aideront à naviguer dans le tableau.
Des boutons flêché sont là pour recentrer le tableau.
Sauvegarde des niveaux au format TXT.
Le manuel seul disponible ICI :https://1drv.ms/b/c/1ac3401a3244fd29/EW ... A?e=QWMQLY
Le code à été regroupé dans un fichier ZIP.
Il contient les sources suivants :
- L'éditeur.
- L'objet "BIDULE LT" : Un utilitaire d'affichage créé par mes soins. ( Il continue d'évoluer et fera l'objet d'un article par la suite. )
- Les images des boutons.
- Le manuel.
Disponible ICI :https://1drv.ms/u/c/1ac3401a3244fd29/Ef ... g?e=oH5Wzm
Le jeu programmé par HuitBit est utilisé pour la démo.
Je remercie "HuitBit" qui m'a aimablement autorisé à utiliser son jeu et a apporter les modifications nécessaire pour le rendre compatible à la lecture des fichiers de niveaux.
Le jeu complet contient les musiques et sons d'origine, le code source modifié.
Des niveaux supplémentaire à créer sont inclue dans le document PDF .
Le tout est disponible ICI :https://1drv.ms/u/c/1ac3401a3244fd29/ER ... w?e=UyP5XH
Suite à la remarque de Falsam, voici le code du jeu corrigé.
Désolé pour le désagrément.
Le code à également été mise à jour dans le zip ci dessus.
Code : Tout sélectionner
; sokoban avec annulation des actions,reset et statistiques
; auteur Huitbit
; amélioration des déplacements par Le Soldat Inconnu
; pb v4.41 démo et pb v4.50
; une partie des sprites est tirée du site ci-dessous
; http://www.brothersoft.com/games/soukoban.html
; MAJ Octobre 2014
;
; pb v6.20
; Retravaillé par Plabouro472
; Les levels son chargé depuis le disque
; MAJ Mars 2025
; *********************************
;- ___________________________________
;- déclaration des variables et des constantes
;- ___________________________________
;- L'énumération permet de travailler avec des noms plutôt qu'avec des chiffres
; Enumeration a été réorganisé par Plabouro472
; l'image "planche.PNG" a été modifier pour correspondre à l'énumération suivante (Plabouro472)
Global JouerMusique .b = #False ; par Plabouro472
Enumeration Sprite
#vide
#mur
#sol
#but
#caisse
#caisse_rouge
#soko1
#soko2
#eau
EndEnumeration
Enumeration action 1 ; Corrigé le 03/05/2025 18:55
#action_caisse_vers_sol
#action_caisse_vers_but
#action_caisserouge_vers_sol
#action_caisserouge_vers_but
EndEnumeration
Enumeration Autre
#spr_planche
#spr_decor
#son_caisse_poussee
EndEnumeration
;- structure utilisée pour la liste chaînée
Structure infos_action
varx.b
vary.b
action.i
EndStructure
Global NewList sauvegarde_actions .infos_action ( )
Global Dim carte .b (1,1)
;- déclaration des variables
; Rendu Global pour un accés depuis les procédures par Plabouro472
Global niveau .b ; numéro du niveau
Global musique .b
Global largeur_niveau .b
Global hauteur_niveau .b
Global y .b ; variable utilisée pour les lignes du tableau
Global x .b ; variable utilisée pour les colonnes du tableau
Global type_tuile .b ; variable utilisée pour le contenu de la case du tableau
Global nbre_caisses .b
Global compteur .b ; permet de vérifier si les caisses sont rangées
Global x_soko .b ; colonne où se trouve le personnage
Global y_soko .b ; ligne où se trouve le personnage
Global soko .b ; sprite choisi pour le personnage, soit #soko1, soit #soko2
Global chrono_soko .i ; variable utilisée pour temporiser l'animation du personnage
Global chrono_keyboard .i ; variable utilisée pour temporiser la fonction KeyboardPushed()
Global largeur_ecran .i
Global hauteur_ecran .i
Global x_clip_eau .i ; variable utilisée pour découper le sprite "planche" pixel par pixel au niveau de l'eau
Global chrono_eau .i ; variable utilisée pour temporiser l'animation de l'eau
Global nombre_de_mouvements .i ; variable utilisée pour comptabiliser le nombre de mouvements effectués par le joueur
Global nombre_de_poussees .i ; variable utilisée pour comptabiliser le nombre de fois qu'une caisse a été poussée
Global chrono_niveau .i ; variable utilisée pour mesurer les secondes qui s'écoulent
Global duree_niveau .i ; variable utilisée compter le temps passé sur un niveau
;- _____________________________________________
;- Cette macro "clavier", permet en une seule fois de gérer les quatre directions,
;- il suffit de l'appeler en changeant les valeurs des paramètres direction, dx et dy.
; Remplacé par une procédure par Plabouro472
Procedure clavier(dx, dy)
;- si la place est libre devant, on avance dans la direction choisie
If carte(x_soko + dx, y_soko + dy) = #sol Or carte(x_soko + dx, y_soko + dy) = #but
x_soko = x_soko + dx
y_soko = y_soko + dy
nombre_de_mouvements = nombre_de_mouvements + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = dx
sauvegarde_actions()\vary = dy
sauvegarde_actions()\action = 0
;- s'il y a une caisse
ElseIf carte(x_soko + dx, y_soko + dy) = #caisse
;- si la place est libre devant la caisse, on peut la pousser
If carte(x_soko + 2 * dx, y_soko + 2 * dy) = #sol
carte(x_soko + dx, y_soko + dy) = #sol
carte(x_soko + 2 * dx, y_soko + 2 * dy) = #caisse
x_soko = x_soko + dx
y_soko = y_soko + dy
nombre_de_mouvements = nombre_de_mouvements + 1
nombre_de_poussees = nombre_de_poussees + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = dx
sauvegarde_actions()\vary = dy
sauvegarde_actions()\action = #action_caisse_vers_sol
PlaySound(#son_caisse_poussee)
;- si la place libre devant la caisse est un point d'arrivée, on bouge la caisse et on l'affiche en rouge
ElseIf carte(x_soko + 2 * dx, y_soko + 2 * dy) = #but
carte(x_soko + dx, y_soko + dy) = #sol
carte(x_soko + 2 * dx, y_soko + 2 * dy) = #caisse_rouge
x_soko = x_soko + dx
y_soko = y_soko + dy
nombre_de_mouvements = nombre_de_mouvements + 1
nombre_de_poussees = nombre_de_poussees + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = dx
sauvegarde_actions()\vary = dy
sauvegarde_actions()\action = #action_caisse_vers_but
PlaySound(#son_caisse_poussee)
EndIf
;- si la caisse poussée est rouge
ElseIf carte(x_soko + dx, y_soko + dy) = #caisse_rouge
;- si on la déplace, on fera apparaître une case arrivée et elle redeviendra de couleur normale
If carte(x_soko + 2 * dx, y_soko + 2 * dy) = #sol
carte(x_soko + dx, y_soko + dy) = #but
carte(x_soko + 2 * dx, y_soko + 2 * dy) = #caisse
x_soko = x_soko + dx
y_soko = y_soko + dy
nombre_de_mouvements = nombre_de_mouvements + 1
nombre_de_poussees = nombre_de_poussees + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = dx
sauvegarde_actions()\vary = dy
sauvegarde_actions()\action = #action_caisserouge_vers_sol
PlaySound(#son_caisse_poussee)
;- si on la déplace on fera apparaître une case arrivée et elle restera rouge
ElseIf carte(x_soko + 2 * dx, y_soko + 2 * dy) = #but
carte(x_soko + dx, y_soko + dy) = #but
carte(x_soko + 2 * dx, y_soko + 2 * dy) = #caisse_rouge
x_soko = x_soko + dx
y_soko = y_soko + dy
nombre_de_mouvements = nombre_de_mouvements + 1
nombre_de_poussees = nombre_de_poussees + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = dx
sauvegarde_actions()\vary = dy
sauvegarde_actions()\action = #action_caisserouge_vers_but
PlaySound(#son_caisse_poussee)
EndIf
EndIf
EndProcedure
;- on prévient le compilateur que l'on va utiliser les sprites et le clavier
InitSprite()
InitKeyboard()
InitSound()
; Corrigé le 03/05/2025 18:55
UseOGGSoundDecoder()
LoadSound(#son_caisse_poussee, "son_caisse_poussee.wav")
A$ = "> Barre d'espace : annuler une action." + Chr(13) + Chr(10) +
"> Return : réinitialiser le niveau." + Chr(13) + Chr(10) +
"> + : Niveau suivant." + Chr(13) + Chr(10) +
"Souhaitez-vous jouer la musique ?"
If MessageRequester("Infos !", A$ , #PB_MessageRequester_YesNo ) = #PB_MessageRequester_Yes
JouerMusique = #True
EndIf
If JouerMusique
;- on charge le son et les musiques
LoadSound(1, "musique1.ogg")
LoadSound(2, "musique2.ogg")
LoadSound(3, "musique3.ogg")
LoadSound(4, "musique4.ogg")
EndIf
;- on choisit le niveau de départ
niveau = 1
;- "jeu:" s'appelle un label, c'est une adresse, un numéro de ligne où l'on peut renvoyer le programme
jeu:
nombre_de_mouvements = 0
nombre_de_poussees = 0
duree_niveau = 0
ClearList(sauvegarde_actions())
;- création d'une liste chaînée nommée "sauvegarde_actions()" pour pouvoir revenir en arrière dans le jeu
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx = 0
sauvegarde_actions()\vary = 0
sauvegarde_actions()\action = 0
;- ______________________
;- Chargement du niveau
; Retravaillé par Plabouro472
;- ______________________
; La DataSection a été supprimée
; On charge maintenant depuis un fichier sur disque
; Format d'un fichier :"Soko001.lev" : Le nom du niveau
;20,16,-1 : Largeur , Hauteur , Skin ( Skin n'est Pas utilisé ici )
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 : Données du tableau
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,1,2,2,6,1,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,1,2,2,2,1,1,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,1,2,4,2,2,1,1,1,1,1,0,0,0,0,0
;0,0,0,0,0,1,2,4,2,4,2,2,8,8,1,0,0,0,0,0
;0,0,0,0,0,1,1,1,2,2,2,2,3,8,1,0,0,0,0,0
;0,0,0,0,0,0,0,1,2,2,2,3,3,2,1,0,0,0,0,0
;0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;- en fonction du niveau, on charge le fichier correspondant par Plabouro472
Fichier$ = "Soko" + RSet( Str( niveau ) , 3, "0") + ".lev"
If FileSize( Fichier$ ) < 0
niveau = 1
Fichier$ = "Soko" + RSet( Str( niveau ) , 3, "0") + ".lev"
EndIf
;- lecture et lancement de la musique de fond
If JouerMusique
musique = ( niveau - 1 ) % 4 + 1
PlaySound(musique, #PB_Sound_Loop)
SoundVolume(musique, 70)
EndIf
;- lecture des dimensions (en nombre de cases) de la zone de jeu
; Retravaillé par Plabouro472
ReadFile( 0 , Fichier$ , #PB_Ascii )
Entete$ = ReadString(0)
largeur_niveau = Val( StringField( Entete$ , 1 , ",") ) : largeur_niveau - 1
hauteur_niveau = Val( StringField( Entete$ , 2 , ",") ) : hauteur_niveau - 1
; Le troisième paramètre n'est pas utilisé ici, on le passe.
;- transformation des dimensions en nombre de pixels, sachant que chaque image fait 32*32 pixels
largeur_ecran = (largeur_niveau + 1) * 32
hauteur_ecran = (hauteur_niveau + 1) * 32
nbre_caisses = 0
;- création du tableau pour y ranger toutes les informations
FreeArray(carte())
Dim carte.b(largeur_niveau, hauteur_niveau)
;- Remplir le tableau
For y = 0 To hauteur_niveau
;- lecture des données sur disque par Plabouro472
LigneDeData$ = ReadString(0)
For x = 0 To largeur_niveau
type_tuile = Val( StringField( LigneDeData$ , x + 1 , ",") )
carte(x, y) = type_tuile
; On compte le nombre de but à atteindre
If type_tuile = #but Or type_tuile = #caisse_rouge
nbre_caisses + 1
EndIf
;- récupération des coordonnées de départ du personnage
If type_tuile = #soko1 Or type_tuile = #soko2
x_soko = x
y_soko = y
carte(x, y) = #sol
EndIf
Next x
Next y
;- ______________________
;- PROGRAMME PRINCIPAL
;- ______________________
OpenWindow(0, 0, 0, largeur_ecran, hauteur_ecran, "Sokoban niveau " + Str(niveau), #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, largeur_ecran, hauteur_ecran, 0, 0, 0)
;- on précise le type d'image utilisée
UsePNGImageDecoder()
;- on charge l'image "planche" de taille 352*32 pixels
;- Remarque : j'ai choisi une image où tous les sprites sont présents, dans la suite du programme, je la "découpe" avec clipsprite() en fonction de mes besoins.
;- j'aurais pu charger plusieurs images pour ne pas avoir à utiliser ClipSprite() !
LoadSprite(#spr_planche, "planche.png")
;- on dessine le décor (c'est dire, tout ce qui est immobile) en utilisant les sprites et les informations du tableau carte(x,y)
ClearScreen(RGB(0, 0, 0))
For y = 0 To hauteur_niveau
For x = 0 To largeur_niveau
If carte(x, y) > 0 ; quand carte(x,y)=0, on affiche rien
If carte(x, y) < 4 ; affichage des murs et du sol, caisses, soko,eau non affichés
ClipSprite ( #spr_planche , carte(x, y) * 32 , 0 , 32 , 32 )
DisplaySprite ( #spr_planche , x * 32 , y * 32 )
Else ; si carte(x,y) supérieur ou égal à 4 on affiche le sol à la place
If carte(x, y) = 5 ; caisse rouge, on affiche donc une case arrivée à la place
ClipSprite ( #spr_planche , #but * 32 , 0 , 32 , 32 )
DisplaySprite ( #spr_planche , x * 32 , y * 32)
Else
ClipSprite ( #spr_planche , #sol * 32 , 0 , 32 , 32 )
DisplaySprite ( #spr_planche , x * 32 , y * 32)
EndIf
EndIf
EndIf
Next x
Next y
;- on prend une "photo" du sprite crée et on l'appelle #spr_decor
GrabSprite(#spr_decor, 0, 0, largeur_ecran, hauteur_ecran)
;- choix de l'image de départ du personnage "soko"
soko = #soko1
;- choix de l'abscisse de départ de l'image "eau"
x_clip_eau = (#eau ) * 32
;- _________________________________
;- BOUCLE PRINCIPALE
;- _________________________________
Repeat
FlipBuffers() ; voir l'aide pour cette fonction
;- on affiche le décor
DisplaySprite(#spr_decor, 0, 0)
;- on affiche les éléments restants
For y = 0 To hauteur_niveau
For x = 0 To largeur_niveau
;- affichage des caisses
If carte(x, y) = #caisse Or carte(x, y) = #caisse_rouge
ClipSprite ( #spr_planche , carte(x, y) * 32 , 0 , 32 , 32 )
DisplaySprite ( #spr_planche , x * 32 , y * 32)
EndIf
;- affichage de l'eau
If carte(x, y) = #eau
;- animation de l'eau
If ElapsedMilliseconds() - chrono_eau > 60
chrono_eau = ElapsedMilliseconds()
x_clip_eau = x_clip_eau + 1
If x_clip_eau > (#eau + 3) * 32
x_clip_eau = #eau * 32
EndIf
EndIf
;- affichage de l'eau
ClipSprite ( #spr_planche , x_clip_eau , 0 , 32 , 32 )
DisplayTransparentSprite ( #spr_planche , x * 32 , y * 32 , 128 )
EndIf
Next x
Next y
;- animation du personnage
If ElapsedMilliseconds() - chrono_soko > 500
chrono_soko = ElapsedMilliseconds()
soko = soko + 1
;- test pour revenir à la première image
If soko > #soko2
soko = #soko1
EndIf
EndIf
;- affichage du personnage
ClipSprite(#spr_planche,(soko ) * 32, 0, 32, 32)
DisplaySprite(#spr_planche, x_soko * 32, y_soko * 32)
;- comptage des secondes passées sur le niveau
If nombre_de_mouvements <> 0
If ElapsedMilliseconds() - chrono_niveau > 1000
chrono_niveau = ElapsedMilliseconds()
duree_niveau = duree_niveau + 1
SetWindowTitle(0, "Niveau " + Str(niveau) + " | M=" + Str(nombre_de_mouvements) + " | P=" + Str(nombre_de_poussees) + " | t=" + Str(duree_niveau) + " s")
EndIf
Else
duree_niveau = 0
SetWindowTitle( 0 , "Sokoban niveau " + Str(niveau) )
EndIf
;- vérification du nombre de caisses placées
compteur = 0
For y = 0 To hauteur_niveau
For x = 0 To largeur_niveau
If carte(x, y) = #caisse_rouge
compteur = compteur + 1
If compteur = nbre_caisses
; le niveau va changer avant de repasser par le Flipbuffers() présent en début de boucle
; il faut donc faire un dernier rendu "à la main"
FlipBuffers()
MessageRequester("Victoire !", Str(nbre_caisses) + " caisses rangées")
;- passage au niveau suivant
niveau = niveau + 1
If JouerMusique
StopSound ( #PB_All ) ;(musique)
EndIf
;- retour au label "jeu:"
Goto jeu
EndIf
EndIf
Next x
Next y
;- gestion du clavier
ExamineKeyboard()
If ElapsedMilliseconds() - chrono_keyboard > 200
;ExamineKeyboard()
If KeyboardPushed(#PB_Key_Right)
clavier(1, 0)
chrono_keyboard = ElapsedMilliseconds()
EndIf
If KeyboardPushed(#PB_Key_Left)
clavier(-1, 0)
chrono_keyboard = ElapsedMilliseconds()
EndIf
If KeyboardPushed(#PB_Key_Down)
clavier(0, 1)
chrono_keyboard = ElapsedMilliseconds()
EndIf
If KeyboardPushed(#PB_Key_Up)
clavier(0, -1)
chrono_keyboard = ElapsedMilliseconds()
EndIf
EndIf
;- annulation des actions effectuées
If KeyboardReleased(#PB_Key_Space)
If ListIndex(sauvegarde_actions()) <> 0
x_soko = x_soko - sauvegarde_actions()\varx
y_soko = y_soko - sauvegarde_actions()\vary
nombre_de_mouvements = nombre_de_mouvements - 1
Select sauvegarde_actions()\action
Case #action_caisse_vers_sol
carte(x_soko + sauvegarde_actions()\varx, y_soko + sauvegarde_actions()\vary) = #caisse
carte(x_soko + 2 * sauvegarde_actions()\varx, y_soko + 2 * sauvegarde_actions()\vary) = #sol
nombre_de_poussees = nombre_de_poussees - 1
Case #action_caisse_vers_but
carte(x_soko + sauvegarde_actions()\varx, y_soko + sauvegarde_actions()\vary) = #caisse
carte(x_soko + 2 * sauvegarde_actions()\varx, y_soko + 2 * sauvegarde_actions()\vary) = #but
nombre_de_poussees = nombre_de_poussees - 1
Case #action_caisserouge_vers_sol
carte(x_soko + sauvegarde_actions()\varx, y_soko + sauvegarde_actions()\vary) = #caisse_rouge
carte(x_soko + 2 * sauvegarde_actions()\varx, y_soko + 2 * sauvegarde_actions()\vary) = #sol
nombre_de_poussees = nombre_de_poussees - 1
Case #action_caisserouge_vers_but
carte(x_soko + sauvegarde_actions()\varx, y_soko + sauvegarde_actions()\vary) = #caisse_rouge
carte(x_soko + 2 * sauvegarde_actions()\varx, y_soko + 2 * sauvegarde_actions()\vary) = #but
nombre_de_poussees = nombre_de_poussees - 1
EndSelect
SetWindowTitle(0, "Niveau " + Str(niveau) + "|M=" + Str(nombre_de_mouvements) + "|P=" + Str(nombre_de_poussees) + "|t=" + Str(duree_niveau) + " s")
DeleteElement(sauvegarde_actions())
EndIf
EndIf
;- recommencer le niveau
If KeyboardReleased(#PB_Key_Return)
If JouerMusique
StopSound(musique)
EndIf
Goto jeu
EndIf
;- Passe au niveau suivant
If KeyboardReleased(#PB_Key_Add) ; par Plabouro472
If JouerMusique
StopSound(musique)
EndIf
niveau = niveau + 1
Goto jeu
EndIf
Delay(10)
Until WindowEvent() = #PB_Event_CloseWindow
End