Petite mise à jour :
-Nombre de mouvements
-Nombre de poussées
-Chronomètre
Aucune complication dans le code mais ça ajoute un peu de confort !
Toute amélioration ou correction (il y a du boulot vu que je n'ai pas fait dans la finesse ) est bienvenue !
Rappel : ce code fonctionne également avec la version démo 4.41 !
Planche de sprites à charger (clic droit puis Enregistrer l'image sous...) et à mettre dans le même dossier que le code :
Version sans le son
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 et pb v4.50
;une partie des sprites est tirée du site ci-dessous
;http://www.brothersoft.com/games/soukoban.html
;*********************************
;-___________________________________
;-déclaration des variables et des constantes
;-___________________________________
;-L'énumération permet de travailler avec des noms plutôt qu'avec des chiffres
Enumeration
#spr_planche
#mur
#sol
#but
#caisse
#caisse_rouge
#soko1
#soko2
#eau
#spr_decor
#action_caisse_vers_sol
#action_caisse_vers_but
#action_caisserouge_vers_sol
#action_caisserouge_vers_but
EndEnumeration
;-déclaration des variables
niveau.b;numéro du niveau
musique.b
largeur_niveau.b
hauteur_niveau.b
y.b;variable utilisée pour les lignes du tableau
x.b;variable utilisée pour les colonnes du tableau
type_tuile.b;variable utilisée pour le contenu de la case du tableau
nbre_caisses.b
compteur.b;permet de vérifier si les caisses sont rangées
x_soko.b;colonne où se trouve le personnage
y_soko.b;ligne où se trouve le personnage
soko.b;sprite choisi pour le personnage, soit #soko1, soit #soko2
chrono_soko.i; variable utilisée pour temporiser l'animation du personnage
chrono_keyboard.i;variable utilisée pour temporiser la fonction KeyboardPushed()
largeur_ecran.i
hauteur_ecran.i
x_clip_eau.i;variable utilisée pour découper le sprite "planche" pixel par pixel au niveau de l'eau
chrono_eau.i; variable utilisée pour temporiser l'animation de l'eau
nombre_de_mouvements.i;variable utilisée pour comptabiliser le nombre de mouvements effectués par le joueur
nombre_de_poussees.i;variable utilisée pour comptabiliser le nombre le nombre de fois qu'une caisse a été poussée
chrono_niveau.i;variable utilisée pour mesurer les secondes qui s'écoulent
duree_niveau.i;variable utilisée compter le temps passé sur un niveau
;-_____________________________________________
;-structure utilisée pour la liste chaînée
Structure infos_action
varx.b
vary.b
action.i
EndStructure
;- 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.
Macro 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
;-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
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
;-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
EndIf
EndIf
EndMacro
;-on prévient le compilateur que l'on va utiliser les sprites et le clavier
InitSprite()
InitKeyboard()
;-on choisit le niveau de départ
niveau=1
MessageRequester("Infos !", ">Barre d'espace : annuler une action."+Chr(13)+">Return : réinitialiser le niveau")
;-"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
;-création d'une liste chaînée nommée "sauvegarde_actions()" pour pouvoir revenir en arrière dans le jeu
NewList sauvegarde_actions.infos_action()
AddElement(sauvegarde_actions())
sauvegarde_actions()\varx=0
sauvegarde_actions()\vary=0
sauvegarde_actions()\action=0
;-en fonction du niveau, on se place au bon endroit pour lire les informations correspondantes
Select niveau
Case 1
Restore niveau1
Case 2
Restore niveau2
Case 3
Restore niveau3
Case 4
Restore niveau4
EndSelect
;-lecture des dimensions (en nombre de cases) de la zone de jeu
Read.b largeur_niveau
Read.b hauteur_niveau
;-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
;-lecture du nombre de caisses
Read.b nbre_caisses
;-création du tableau pour y ranger toutes les informations
Dim carte.b(largeur_niveau,hauteur_niveau)
;-utilisation de deux boucles pour remplir le tableau
For y=0 To hauteur_niveau
For x=0 To largeur_niveau
;-lecture des data
Read.b type_tuile
carte(x,y)=type_tuile
;-récupération des coordonnées de départ du personnage
If type_tuile=#soko1
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)-1)*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-1)*32,0,32,32)
DisplaySprite(#spr_planche,x*32,y*32)
Else
ClipSprite(#spr_planche,(#sol-1)*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-1)*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)-1)*32,0,32,32)
DisplaySprite(#spr_planche,x*32,y*32)
EndIf
;-cas 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-1+3)*32
x_clip_eau=(#eau-1)*32
EndIf
EndIf
;-affichage de l'eau
ClipSprite(#spr_planche,x_clip_eau,0,32,32)
DisplayTranslucentSprite(#spr_planche,x*32,y*32,128)
EndIf
;-enlevez les points virgules et vous verrez !
; StartDrawing(ScreenOutput())
; DrawText(x*32,y*32,Str(carte(x,y)))
; StopDrawing()
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-1)*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 niveau=5
niveau=1
EndIf
;-retour au label "jeu:"
Goto jeu
EndIf
EndIf
Next x
Next y
;-gestion du clavier
If ElapsedMilliseconds()-chrono_keyboard>200
ExamineKeyboard()
;-appel de la macro clavier
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)
Goto jeu
EndIf
Delay(10)
Until WindowEvent() = #PB_Event_CloseWindow
End
;-____________________________
;-FIN DU PROGRAMME PRINCIPAL
;-____________________________
;-____________________________
;-DONNEES SUR LES NIVEAUX
;-____________________________
DataSection
;mur=1;sol=2;but=3;caisse=4;caisse_rouge=5;soko1=6;soko2=7;eau=8
niveau1:
Data.b 9,7,3;dimensions 10*8; 3 caisses
Data.b 1,1,1,1,1,0,0,0,0,0
Data.b 1,2,2,6,1,0,0,0,0,0
Data.b 1,2,2,2,1,1,0,0,0,0
Data.b 1,2,4,2,2,1,1,1,1,1
Data.b 1,2,4,2,4,2,2,8,8,1
Data.b 1,1,1,2,2,2,2,3,8,1
Data.b 8,8,1,2,2,2,3,3,2,1
Data.b 8,8,1,1,1,1,1,1,1,1
;mur=1;sol=2;but=3;caisse=4;caisse_rouge=5;soko1=6;soko2=7;eau=8
niveau2:
Data.b 8,7,7; dimensions 10*8; 3 caisses
Data.b 8,8,8,1,1,1,1,1,8
Data.b 1,1,1,1,3,2,2,1,1
Data.b 1,2,4,3,4,3,2,2,1
Data.b 1,6,4,1,2,1,4,2,1
Data.b 1,2,4,3,2,3,2,2,1
Data.b 1,1,1,1,4,1,4,2,1
Data.b 8,8,1,3,2,3,2,2,1
Data.b 8,8,1,1,1,1,1,1,1
;mur=1;sol=2;but=3;caisse=4;caisse_rouge=5;soko1=6;soko2=7;eau=8
niveau3:
Data.b 8,6,6; dimensions 9*7; 6 caisses
Data.b 8,1,1,1,1,1,8,8,8
Data.b 8,1,2,2,2,1,1,1,1
Data.b 1,1,2,1,4,2,2,2,1
Data.b 1,2,4,2,2,4,4,2,1
Data.b 1,2,1,4,1,3,5,3,1
Data.b 1,2,2,2,6,3,3,3,1
Data.b 1,1,1,1,1,1,1,1,1
;mur=1;sol=2;but=3;caisse=4;caisse_rouge=5;soko1=6;soko2=7;eau=8
niveau4:
Data.b 8,7,6; dimensions 9*8; 6 caisses
Data.b 8,8,8,1,1,1,1,1,8
Data.b 1,1,1,1,2,2,2,1,1
Data.b 1,2,4,2,4,2,2,2,1
Data.b 1,6,1,3,5,3,1,2,1
Data.b 1,2,1,3,5,3,1,2,1
Data.b 1,2,2,2,4,2,4,2,1
Data.b 1,1,2,2,2,1,1,1,1
Data.b 8,1,1,1,1,1,8,8,8
EndDataSection