Einführung
Dieses Projekt ist die Entwicklung des klassischen Spiels Sokoban mit der Programmiersprache Python und Pygame.
Die in diesem Projekt behandelten Kenntnisse umfassen:
- Die Grundlagen der Python-Syntax
- Die Grundlagen der Spielentwicklung mit Pygame
Dieser Kurs hat ein mittleres Schwierigkeitsniveau und eignet sich für Benutzer, die eine grundlegende Kenntnis von Python haben und ihre Kenntnisse erweitern möchten.
Der Quellcode sokoban.py.zip wird unter der GNU GPL v3 Lizenz veröffentlicht, und die Oberfläche wurde von Borgar erstellt.
👀 Vorschau

🎯 Aufgaben
In diesem Projekt lernen Sie:
- Wie man das Spiel mit Pygame initialisiert
- Wie man Spielereignisse und Tastatureingaben behandelt
- Wie man die Karte für das Spiel implementiert
- Wie man Bewegungsvorgänge für den Spieler und die Kisten implementiert
- Wie man Rückgängig- und Wiederholvorgänge implementiert
- Wie man die Spieloberfläche testet
🏆 Errungenschaften
Nach Abschluss dieses Projekts können Sie:
- Pygame initialisieren und das Spielfenster einrichten
- Spielereignisse und Tastatureingaben in Pygame behandeln
- Die Spielkarte implementieren und mit Pygame anzeigen
- Bewegungsvorgänge für den Spieler und die Kisten implementieren
- Rückgängig- und Wiederholvorgänge im Spiel implementieren
- Die Spieloberfläche testen und ausführen
Spieldeskription
Im Spiel Sokoban gibt es eine geschlossene Wand, die einen unregelmäßigen polygonalen Bereich bildet. Der Spieler und die Kisten können sich nur innerhalb dieses Bereichs bewegen. Innerhalb des Bereichs befindet sich eine Person, mehrere Kisten und Zielpunkte. Das Ziel des Spiels ist es, mit den Pfeiltasten die Bewegung der Person zu steuern und die Kisten auf die Zielpunkte zu schieben. Es kann nur eine Kiste gleichzeitig bewegt werden, und wenn eine Kiste in einer Ecke steckt, kann das Spiel nicht fortgesetzt werden.
Charaktere
Aus der obigen Beschreibung können wir die folgenden Charaktere im Spiel abstrahieren:
- Wände: Geschlossene Bereiche, die die Bewegungswege blockieren.
- Räume: Bereiche, in denen die Person laufen und Kisten schieben kann.
- Person: Der vom Spieler gesteuerte Charakter.
- Kisten
- Zielpunkte
Die Person, die Kisten und die Zielpunkte sollten alle innerhalb des Raumbereichs initialisiert werden, und keine anderen Charaktere sollten innerhalb des Wandbereichs erscheinen.
Bedienelemente
Im Spiel Sokoban ist der einzige Charakter, den wir steuern können, die Person. Wir verwenden die Pfeiltasten, um die Bewegung der Person zu steuern, sowohl um die Person zu bewegen als auch um Kisten zu schieben. Es gibt zwei Arten von Bewegungen für die Person, und wir müssen jede Situation separat behandeln:
- Das alleinige Bewegen der Person
- Das Bewegen der Person, während eine Kiste geschoben wird
Zusätzlich unterstützt das Spiel die folgenden beiden Operationen:
- Rückgängig: Der vorherige Zugang rückgängig machen, gesteuert durch die Backspace-Taste.
- Wiederholen: Den zuvor rückgängig gemachten Zugang wiederholen, gesteuert durch die Leertaste.
Zusammenfassend müssen wir die Tastaturevents für die vier Pfeiltasten, die Backspace-Taste für das Rückgängigmachen und die Leertaste für das Wiederholen unterstützen. Im nächsten Abschnitt zur Implementierung von Pygame müssen wir diese sechs Tastaturevents behandeln.
Entwicklungspräparation
Um in der Umgebung pygame verwenden zu können, öffnen Sie das Terminal in der experimentellen Umgebung und geben Sie den folgenden Befehl ein, um pygame zu installieren:
sudo pip install pygame
Es gibt viele Module in pygame, darunter Maus, Anzeigegeräte, Grafiken, Ereignisse, Schriftarten, Bilder, Tastaturen, Sound, Video, Audio usw. Im Sokoban-Spiel werden wir die folgenden Module verwenden:
pygame.display: Zugang zu Anzeigegeräten, um Bilder anzuzeigen.pygame.image: Bilder laden und speichern, um Spritesheets zu verarbeiten.pygame.key: Tastatureingaben lesen.pygame.event: Ereignisse verwalten, um Tastaturevents im Spiel zu behandeln.pygame.time: Zeit verwalten und Frameinformationen anzeigen.
In der obigen Einführung wurde von Spritesheets die Rede. Ein Spritesheet ist eine übliche Methode der Bildzusammenführung in der Spielentwicklung, bei der kleine Symbole und Hintergrundbilder zu einem Bild zusammengeführt werden und dann die Bildpositionierung in pygame verwendet wird, um den erforderlichen Teil des Bilds anzuzeigen.
Im Sokoban-Spiel verwenden wir ein fertiges Spritesheet. Ich werde hier nicht im Detail auf die Bildausschnitte und die Zusammenführung von Spritesheets eingehen, da es unzählige Methoden im Internet gibt.
Die Bildelemente im Sokoban-Spritesheet, das in diesem Projekt verwendet wird, stammen von borgar, und die Datei kann unter ~/project/borgar.png gefunden werden.
Die Spielbildelemente umfassen:
- Hintergrundfarbe der Spieloberfläche
- Spieler
- Normalbox
- Zielpunkt
- Überlappungseffekt zwischen Spieler und Zielpunkt
- Überlappungseffekt, wenn die Box den Zielpunkt erreicht
- Wand
Zwei Boxbilder im Spritesheet werden in unserer Implementierung nicht benötigt. Wir werden im späteren Implementierungsteil im Detail erklären, wie die blit-Methode in pygame verwendet wird, um den Inhalt des Spritesheets zu laden und anzuzeigen.
Spielentwicklung
Erstellen Sie zunächst eine Datei sokoban.py im Verzeichnis ~/project, und geben Sie dann den folgenden Inhalt in die Datei ein:
- Initialisieren von Pygame
import pygame, sys, os
from pygame.locals import *
from collections import deque
pygame.init()
- Festlegen des Anzeigeobjekts
## Setzen der Größe des Pygame-Anzeigefensters auf 400 Pixel breit und 300 Pixel hoch
screen = pygame.display.set_mode((400,300))
- Laden von Bildelementen
## Laden von Bildelementen aus einer einzelnen Datei
skinfilename = os.path.join('borgar.png')
try:
skin = pygame.image.load(skinfilename)
except pygame.error as msg:
print('cannot load skin')
raise SystemExit(msg)
skin = skin.convert()
## Setzen der Hintergrundfarbe des Fensters auf das Element an den Koordinaten (0,0) in der Skin-Datei
screen.fill(skin.get_at((0,0)))
- Festlegen der Uhr und der Wiederholzeit für Tastaturevents. Verwenden Sie
key.set_repeat, um das Zeitintervall für Wiederholereignisse mit den Parametern(delay, interval)festzulegen.
clock = pygame.time.Clock()
pygame.key.set_repeat(200,50)
- Starten der Hauptschleife
## Spielhauptschleife
while True:
clock.tick(60)
pass
- Behandeln von Spielereignissen und Tastatureingaben. In der Hauptschleife müssen wir Tastaturevents behandeln. Wie bereits erwähnt, müssen wir sechs Tasten unterstützen: nach oben, nach unten, nach links, nach rechts, Backspace und Leertaste.
## Abrufen von Spielereignissen
for event in pygame.event.get():
## Spiel beenden Ereignis
if event.type == QUIT:
pygame.quit()
sys.exit()
## Tastatureingabe
elif event.type == KEYDOWN:
## Links bewegen
if event.key == K_LEFT:
pass
## Hoch bewegen
elif event.key == K_UP:
pass
## Rechts bewegen
elif event.key == K_RIGHT:
pass
## Runter bewegen
elif event.key == K_DOWN:
pass
## Rückgängigmachen der Operation
elif event.key == K_BACKSPACE:
pass
## Wiederholen der Operation
elif event.key == K_SPACE:
pass
Jetzt haben wir das auf Pygame basierende Spielframework abgeschlossen. Lassen Sie uns mit der Implementierung der Spiellogik beginnen.
Implementierung der Karte
Zunächst müssen wir das Sokoban-Objekt definieren. Wir verwenden eine Klasse, um alle spielrelevanten Logik zu kapseln.
class Sokoban:
## Initialisieren des Sokoban-Spiels
def __init__(self):
pass
Das Sokoban-Spiel erfordert einen Arbeitsbereich, nämlich den Kartierungsbereich. Wir verwenden eine Zeichenliste, um die Karte darzustellen, wobei verschiedene Zeichen verschiedene Elemente im Spiel repräsentieren:
- Wand:
## Symbol - Raum:
- Symbol - Spieler:
@ Symbol - Kiste:
$ Symbol - Zielpunkt:
. Symbol - Spieler auf Zielpunkt:
+ Symbol - Kiste auf Zielpunkt:
* Symbol
Wenn das Spiel startet, müssen wir eine Standardzeichenliste für die Karte festlegen. Gleichzeitig müssen wir die Breite und Höhe der Karte kennen, um aus dieser eindimensionalen Liste eine 2D-Karte zu generieren.
Die Kartendarstellung ähnelt dem folgenden Code. Können Sie sich vorstellen, wie es danach aussehen wird, wenn es gestartet wird, basierend auf diesem Code?
class Sokoban:
## Initialisieren des Sokoban-Spiels
def __init__(self):
## Setzen der Karte
self.level = list(
"----#####----------"
"----#---#----------"
"----#$--#----------"
"--###--$##---------"
"--#--$-$-#---------"
"###-#-##-#---######"
"#---#-##-#####--..#"
"#-$--$----------..#"
"#####-###-#@##--..#"
"----#-----#########"
"----#######--------")
## Setzen der Breite und Höhe der Karte und der Position des Spielers in der Karte (Indexwert in der Karteliste)
## Insgesamt 19 Spalten
self.w = 19
## Insgesamt 11 Zeilen
self.h = 11
## Die Anfangsposition des Spielers befindet sich bei self.level[163]
self.man = 163
Die Karte wird durch das Scannen der Zeichenliste und das Anzeigen verschiedener Elemente an den entsprechenden Positionen basierend auf den Zeichen dargestellt.
Da die Anzeige 2D ist, werden die Breite und Höhe verwendet, um die Position jedes Zeichens im 2D-Anzeigebereich zu bestimmen. Wir müssen die in Pygame erwähnten screen und skin als Parameter an die Zeichnungsfunktion draw übergeben.
Es ist wichtig zu beachten, dass die von uns implementierte Zeichnungsfunktion blit aus Pygame verwendet, um das Bild aus dem Spritesheet zu extrahieren und es an der angegebenen Position anzuzeigen:
screen.blit(skin, (i*w, j*w), (0,0,w,w))
Die vollständige Implementierung der draw-Funktion lautet wie folgt. Zunächst wird der Scan durchgeführt, und dann wird das entsprechende Bild jedes Zeichens basierend auf dem Spritesheet angezeigt:
class Sokoban:
## Zeichnen der Karte auf das Pygame-Fenster basierend auf dem Kartierungslevel
def draw(self, screen, skin):
## Erhalten der Breite jedes Bildelements
w = skin.get_width() / 4
## Iterieren durch jedes Zeichenelement im Kartierungslevel
for i in range(0, self.w):
for j in range(0, self.h):
## Erhalten des Zeichens an der j-ten Zeile und i-ten Spalte in der Karte
item = self.level[j*self.w + i]
## Anzeigen als Wand(#) an dieser Position
if item == '#':
## Verwenden der blit-Methode aus Pygame, um das Bild an der angegebenen Position anzuzeigen,
## mit den Positionskoordinaten (i*w, j*w) und den Koordinaten und Länge-Breite des Bilds in der Skin als (0,2*w,w,w)
screen.blit(skin, (i*w, j*w), (0,2*w,w,w))
## Anzeigen als Raum(-) an dieser Position
elif item == '-':
screen.blit(skin, (i*w, j*w), (0,0,w,w))
## Anzeigen als Spieler(@) an dieser Position
elif item == '@':
screen.blit(skin, (i*w, j*w), (w,0,w,w))
## Anzeigen als Kiste($) an dieser Position
elif item == '$':
screen.blit(skin, (i*w, j*w), (2*w,0,w,w))
## Anzeigen als Zielpunkt(.) an dieser Position
elif item == '.':
screen.blit(skin, (i*w, j*w), (0,w,w,w))
## Anzeigen als Effekt des Spielers auf einem Zielpunkt
elif item == '+':
screen.blit(skin, (i*w, j*w), (w,w,w,w))
## Anzeigen als Effekt der auf einem Zielpunkt platzierten Kiste
elif item == '*':
screen.blit(skin, (i*w, j*w), (2*w,w,w,w))
Implementierung der Bewegungsoperation
Die Bewegungsoperation verwendet die Pfeiltasten, um in vier Richtungen zu bewegen: nach links, nach rechts, nach oben und nach unten. Wir verwenden die vier Zeichen 'l' (links), 'r' (rechts), 'u' (oben) und 'd' (unten), um die Bewegungsrichtung anzugeben.
Da der Vorgang für die Wiederholungsoperation und die Bewegungsoperation ähnlich ist, definieren wir eine interne Funktion, _move(), um die Bewegung in der Sokoban-Klasse zu behandeln:
class Sokoban:
## Interne Bewegungsfunktion: verwendet, um die Positionänderungen der Elemente in der Karte nach der Bewegungsoperation zu aktualisieren, wobei d die Bewegungsrichtung darstellt
def _move(self, d):
## Erhalten der Verschiebung in der Karte für die Bewegung
h = get_offset(d, self.w)
## Wenn das Zielfeld der Bewegung ein leerer Raum oder ein Zielpunkt ist, muss nur der Spieler bewegt werden
if self.level[self.man + h] == '-' or self.level[self.man + h] == '.':
## Bewegen Sie den Spieler an die Zielfunktion
move_man(self.level, self.man + h)
## Setzen Sie die ursprüngliche Position des Spielers nach der Bewegung
move_floor(self.level, self.man)
## Die neue Position des Spielers
self.man += h
## Fügen Sie die Bewegungsoperation zur Lösung hinzu
self.solution += d
## Wenn das Zielfeld der Bewegung eine Kiste ist, müssen sowohl die Kiste als auch der Spieler bewegt werden
elif self.level[self.man + h] == '*' or self.level[self.man + h] == '$':
## Die Verschiebung der Kiste und die Position des Spielers
h2 = h * 2
## Die Kiste kann nur bewegt werden, wenn die nächste Position ein leerer Raum oder ein Zielpunkt ist
if self.level[self.man + h2] == '-' or self.level[self.man + h2] == '.':
## Bewegen Sie die Kiste zum Zielpunkt
move_box(self.level, self.man + h2)
## Bewegen Sie den Spieler zum Zielpunkt
move_man(self.level, self.man + h)
## Setzen Sie die aktuelle Position des Spielers zurück
move_floor(self.level, self.man)
## Setzen Sie die neue Position des Spielers
self.man += h
## Markieren Sie die Bewegungsoperation als einen Großbuchstaben, um anzuzeigen, dass in diesem Schritt eine Kiste geschoben wurde
self.solution += d.upper()
## Inkrementieren Sie die Anzahl der Schritte zum Schieben der Kiste
self.push += 1
In der _move-Funktion müssen wir die folgenden Funktionen verwenden:
- get_offset(d, width): Erhalten der Verschiebung der Bewegung in der Karte.
dstellt die Bewegungsrichtung dar, undwidthstellt die Breite des Spielfensters dar. - move_man(level, i): Bewegen der Position des Spielers in der Karte.
levelist die Karteliste, undiist die Position des Spielers. - move_floor(level, i): Zurücksetzen der Position nach der Bewegung. Nachdem der Spieler von einer Position bewegt wurde, muss es als leerer Raum oder ein Zielpunkt zurückgesetzt werden.
- move_box(level, i): Bewegen der Position der Kiste in der Karte.
levelist die Karteliste, undiist die Position der Kiste.
Die Implementierung dieser Funktionen kann im vollständigen Code gesehen werden. Es ist wichtig, zu berücksichtigen, was das ursprüngliche Element an der Zielfunktion ist, wenn jedes Element bewegt wird, um zu bestimmen, was das Element nach der Bewegung gesetzt werden sollte.
Um die Bewegungsoperation durchzuführen, rufen Sie einfach _move auf und legen Sie todo[] auf leer (die Wiederholungsliste wird nur aktiviert, wenn die Rückgängigmachungsoperation durchgeführt wird).
Rückgängigmachung implementieren
Die Rückgängigmachung ist die umgekehrte Operation einer Bewegung. Sie holt den vorherigen Schritt aus solution und führt die umgekehrte Operation durch. Siehe den detaillierten Code:
class Sokoban:
## Rückgängigmachungsoperation: macht die vorherige Bewegung rückgängig
def undo(self):
## Überprüfen, ob es eine Bewegungserfassung gibt
if self.solution.__len__()>0:
## Speichert die Bewegungserfassung in der todo-Liste für die Wiederholungsoperation
self.todo.append(self.solution[-1])
## Löscht die Bewegungserfassung
self.solution.pop()
## Erhalten der Verschiebung, die für die Rückgängigmachungsoperation ausgeführt werden soll: das Negative der Verschiebung der letzten Bewegung
h = get_offset(self.todo[-1],self.w) * -1
## Überprüfen, ob diese Operation nur das Zeichen bewegt, ohne eine Kiste zu schieben
if self.todo[-1].islower():
## Bewegen Sie das Zeichen zurück an seine ursprüngliche Position
move_man(self.level, self.man + h)
## Setzen Sie die aktuelle Position des Zeichens
move_floor(self.level, self.man)
## Setzen Sie die Position des Zeichens auf der Karte
self.man += h
else:
## Wenn in diesem Schritt eine Kiste geschoben wird, bewegen Sie das Zeichen, die Kiste und führen Sie in _move die entsprechenden Operationen durch
move_floor(self.level, self.man - h)
move_box(self.level, self.man)
move_man(self.level, self.man + h)
self.man += h
self.push -= 1
Wiederholungsoperation
Wenn der Befehl zur Rückgängigmachung ausgeführt wird, wird der Inhalt von solution[] in todo[] verschoben, und wir müssen nur die _move-Funktion extrahieren und aufrufen.
## Wiederholungsoperation: Wenn die Rückgängigmachungsoperation ausgeführt und aktiviert wird, bewegen Sie sich zurück an die Position vor der Rückgängigmachung
def redo(self):
## Überprüfen, ob eine Rückgängigmachungsoperation aufgezeichnet ist
if self.todo.__len__() > 0:
## Bewegen Sie die zurückgemachten Schritte zurück
self._move(self.todo[-1].lower())
## Löschen Sie diese Aufzeichnung
self.todo.pop()
Mit den obigen Schritten ist der Hauptinhalt des Spiels abgeschlossen. Bitte vervollständigen Sie den vollständigen Spielcode unabhängig, testen Sie die Screenshots und stellen Sie bei Unklarheiten in der Frage- und Antwort-Sektion des Experimenterraums Fragen. Das Experimenterraum-Team und die Lehrer werden Ihre Fragen unverzüglich beantworten.
Zusätzliche Funktionen und Code-Refactoring
Wir haben jetzt ein grundlegendes Spiel, aber es ist noch nicht perfekt. Wir müssen einige zusätzliche Funktionen hinzufügen, um es spielbarer zu machen.
Wir müssen auch den Code umstrukturieren, um ihn lesbarer und wartbarer zu machen.
Klicken Sie, um den vollständigen Code anzuzeigen
import pygame, sys, os
from pygame.locals import *
from collections import deque
def to_box(level, index):
if level[index] == "-" or level[index] == "@":
level[index] = "$"
else:
level[index] = "*"
def to_man(level, i):
if level[i] == "-" or level[i] == "$":
level[i] = "@"
else:
level[i] = "+"
def to_floor(level, i):
if level[i] == "@" or level[i] == "$":
level[i] = "-"
else:
level[i] = "."
def to_offset(d, width):
d4 = [-1, -width, 1, width]
m4 = ["l", "u", "r", "d"]
return d4[m4.index(d.lower())]
def b_manto(level, width, b, m, t):
maze = list(level)
maze[b] = "#"
if m == t:
return 1
queue = deque([])
queue.append(m)
d4 = [-1, -width, 1, width]
m4 = ["l", "u", "r", "d"]
while len(queue) > 0:
pos = queue.popleft()
for i in range(4):
newpos = pos + d4[i]
if maze[newpos] in ["-", "."]:
if newpos == t:
return 1
maze[newpos] = i
queue.append(newpos)
return 0
def b_manto_2(level, width, b, m, t):
maze = list(level)
maze[b] = "#"
maze[m] = "@"
if m == t:
return []
queue = deque([])
queue.append(m)
d4 = [-1, -width, 1, width]
m4 = ["l", "u", "r", "d"]
while len(queue) > 0:
pos = queue.popleft()
for i in range(4):
newpos = pos + d4[i]
if maze[newpos] in ["-", "."]:
maze[newpos] = i
queue.append(newpos)
if newpos == t:
path = []
while maze[t]!= "@":
path.append(m4[maze[t]])
t = t - d4[maze[t]]
return path
return []
class Sokoban:
def __init__(self):
self.level = list(
"----#####--------------#---#--------------#$--#------------###--$##-----------#--$-$-#---------###-#-##-#---#######---#-##-#####--..##-$--$----------..######-###-#@##--..#----#-----#########----#######--------"
)
self.w = 19
self.h = 11
self.man = 163
self.hint = list(self.level)
self.solution = []
self.push = 0
self.todo = []
self.auto = 0
self.sbox = 0
self.queue = []
def draw(self, screen, skin):
w = skin.get_width() / 4
offset = (w - 4) / 2
for i in range(0, self.w):
for j in range(0, self.h):
if self.level[j * self.w + i] == "#":
screen.blit(skin, (i * w, j * w), (0, 2 * w, w, w))
elif self.level[j * self.w + i] == "-":
screen.blit(skin, (i * w, j * w), (0, 0, w, w))
elif self.level[j * self.w + i] == "@":
screen.blit(skin, (i * w, j * w), (w, 0, w, w))
elif self.level[j * self.w + i] == "$":
screen.blit(skin, (i * w, j * w), (2 * w, 0, w, w))
elif self.level[j * self.w + i] == ".":
screen.blit(skin, (i * w, j * w), (0, w, w, w))
elif self.level[j * self.w + i] == "+":
screen.blit(skin, (i * w, j * w), (w, w, w, w))
elif self.level[j * self.w + i] == "*":
screen.blit(skin, (i * w, j * w), (2 * w, w, w, w))
if self.sbox!= 0 and self.hint[j * self.w + i] == "1":
screen.blit(
skin, (i * w + offset, j * w + offset), (3 * w, 3 * w, 4, 4)
)
def move(self, d):
self._move(d)
self.todo = []
def _move(self, d):
self.sbox = 0
h = to_offset(d, self.w)
h2 = 2 * h
if self.level[self.man + h] == "-" or self.level[self.man + h] == ".":
## move
to_man(self.level, self.man + h)
to_floor(self.level, self.man)
self.man += h
self.solution += d
elif self.level[self.man + h] == "*" or self.level[self.man + h] == "$":
if self.level[self.man + h2] == "-" or self.level[self.man + h2] == ".":
## push
to_box(self.level, self.man + h2)
to_man(self.level, self.man + h)
to_floor(self.level, self.man)
self.man += h
self.solution += d.upper()
self.push += 1
def undo(self):
if self.solution.__len__() > 0:
self.todo.append(self.solution[-1])
self.solution.pop()
h = to_offset(self.todo[-1], self.w) * -1
if self.todo[-1].islower():
## undo a move
to_man(self.level, self.man + h)
to_floor(self.level, self.man)
self.man += h
else:
## undo a push
to_floor(self.level, self.man - h)
to_box(self.level, self.man)
to_man(self.level, self.man + h)
self.man += h
self.push -= 1
def redo(self):
if self.todo.__len__() > 0:
self._move(self.todo[-1].lower())
self.todo.pop()
def manto(self, x, y):
maze = list(self.level)
maze[self.man] = "@"
queue = deque([])
queue.append(self.man)
d4 = [-1, -self.w, 1, self.w]
m4 = ["l", "u", "r", "d"]
while len(queue) > 0:
pos = queue.popleft()
for i in range(4):
newpos = pos + d4[i]
if maze[newpos] in ["-", "."]:
maze[newpos] = i
queue.append(newpos)
t = y * self.w + x
if maze[t] in range(4):
self.todo = []
while maze[t]!= "@":
self.todo.append(m4[maze[t]])
t = t - d4[maze[t]]
self.auto = 1
def automove(self):
if self.auto == 1 and self.todo.__len__() > 0:
self._move(self.todo[-1].lower())
self.todo.pop()
else:
self.auto = 0
def boxhint(self, x, y):
d4 = [-1, -self.w, 1, self.w]
m4 = ["l", "u", "r", "d"]
b = y * self.w + x
maze = list(self.level)
to_floor(maze, b)
to_floor(maze, self.man)
mark = maze * 4
size = self.w * self.h
self.queue = []
head = 0
for i in range(4):
if b_manto(maze, self.w, b, self.man, b + d4[i]):
if len(self.queue) == 0:
self.queue.append((b, i, -1))
mark[i * size + b] = "1"
while head < len(self.queue):
pos = self.queue[head]
head += 1
for i in range(4):
if mark[pos[0] + i * size] == "1" and maze[pos[0] - d4[i]] in [
"-",
".",
]:
if mark[pos[0] - d4[i] + i * size]!= "1":
self.queue.append((pos[0] - d4[i], i, head - 1))
for j in range(4):
if b_manto(
maze,
self.w,
pos[0] - d4[i],
pos[0],
pos[0] - d4[i] + d4[j],
):
mark[j * size + pos[0] - d4[i]] = "1"
for i in range(size):
self.hint[i] = "0"
for j in range(4):
if mark[j * size + i] == "1":
self.hint[i] = "1"
def boxto(self, x, y):
d4 = [-1, -self.w, 1, self.w]
m4 = ["l", "u", "r", "d"]
om4 = ["r", "d", "l", "u"]
b = y * self.w + x
maze = list(self.level)
to_floor(maze, self.sbox)
to_floor(
maze, self.man
) ## make a copy of working maze by removing the selected box and the man
for i in range(len(self.queue)):
if self.queue[i][0] == b:
self.todo = []
j = i
while self.queue[j][2]!= -1:
self.todo.append(om4[self.queue[j][1]].upper())
k = self.queue[j][2]
if self.queue[k][2]!= -1:
self.todo += b_manto_2(
maze,
self.w,
self.queue[k][0],
self.queue[k][0] + d4[self.queue[k][1]],
self.queue[k][0] + d4[self.queue[j][1]],
)
else:
self.todo += b_manto_2(
maze,
self.w,
self.queue[k][0],
self.man,
self.queue[k][0] + d4[self.queue[j][1]],
)
j = k
self.auto = 1
return
print("not found!")
def mouse(self, x, y):
if x >= self.w or y >= self.h:
return
m = y * self.w + x
if self.level[m] in ["-", "."]:
if self.sbox == 0:
self.manto(x, y)
else:
self.boxto(x, y)
elif self.level[m] in ["$", "*"]:
if self.sbox == m:
self.sbox = 0
else:
self.sbox = m
self.boxhint(x, y)
elif self.level[m] in ["-", ".", "@", "+"]:
self.boxto(x, y)
## start pygame
pygame.init()
screen = pygame.display.set_mode((400, 300))
## load skin
skinfilename = os.path.join("borgar.png")
try:
skin = pygame.image.load(skinfilename)
except pygame.error as msg:
print("cannot load skin")
raise SystemExit(msg)
skin = skin.convert()
## screen.fill((255,255,255))
screen.fill(skin.get_at((0, 0)))
pygame.display.set_caption("sokoban.py")
## create Sokoban object
skb = Sokoban()
skb.draw(screen, skin)
clock = pygame.time.Clock()
pygame.key.set_repeat(200, 50)
## main game loop
while True:
clock.tick(60)
if skb.auto == 0:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_LEFT:
skb.move("l")
skb.draw(screen, skin)
elif event.key == K_UP:
skb.move("u")
skb.draw(screen, skin)
elif event.key == K_RIGHT:
skb.move("r")
skb.draw(screen, skin)
elif event.key == K_DOWN:
skb.move("d")
skb.draw(screen, skin)
elif event.key == K_BACKSPACE:
skb.undo()
skb.draw(screen, skin)
elif event.key == K_SPACE:
skb.redo()
skb.draw(screen, skin)
elif event.type == MOUSEBUTTONUP and event.button == 1:
mousex, mousey = event.pos
mousex /= skin.get_width() / 4
mousey /= skin.get_width() / 4
skb.mouse(mousex, mousey)
skb.draw(screen, skin)
else:
skb.automove()
skb.draw(screen, skin)
pygame.display.update()
pygame.display.set_caption(
skb.solution.__len__().__str__() + "/" + skb.push.__str__() + " - sokoban.py"
)
Ausführen und Testen
Um in der Konsole auszuführen:
cd ~/project
python sokoban.py
Wenn alles normal ist, werden Sie die folgende Spieloberfläche sehen:

Zusammenfassung
Dieses Projekt hat nur eine grundlegende Funktionalität eines Sokoban-Spiels implementiert. Basierend auf der Experimentation kann man überlegen, diesen Code zu erweitern, indem man:
- Erkennt, wie man die Kartendaten aus dem geschriebenen Code extrahiert und in eine Datei speichert.
- Maussteuerungen implementiert, um den Charakter schnell an eine bestimmte Position zu bewegen.
- Ein Algorithmus entwickelt, um automatisch zu bestimmen, ob eine Karte lösbar ist.



