Introdução
Este projeto é o desenvolvimento do jogo clássico Sokoban utilizando a linguagem Python e a biblioteca Pygame.
Os pontos de conhecimento abordados neste projeto incluem:
- Sintaxe básica do Python
- Desenvolvimento básico de jogos com Pygame
Este curso tem um nível de dificuldade moderado e é adequado para usuários que possuem um conhecimento básico de Python e desejam aprofundar seus conhecimentos.
O código fonte sokoban.py.zip é lançado sob a licença GNU GPL v3, e a skin foi criada por Borgar.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, você aprenderá:
- Como inicializar o jogo usando Pygame
- Como lidar com eventos do jogo e operações de teclado
- Como implementar o mapa do jogo
- Como implementar operações de movimento para o jogador e as caixas
- Como implementar operações de desfazer e refazer
- Como testar a interface do jogo
🏆 Conquistas
Após concluir este projeto, você será capaz de:
- Inicializar o Pygame e configurar a janela do jogo
- Lidar com eventos do jogo e entradas de teclado no Pygame
- Implementar o mapa do jogo e exibi-lo usando Pygame
- Implementar operações de movimento para o jogador e as caixas
- Implementar operações de desfazer e refazer no jogo
- Testar e executar a interface do jogo
Descrição do Jogo
No jogo Sokoban, existe uma parede fechada que forma uma área poligonal irregular. O jogador e as caixas só podem se mover dentro desta área. Dentro da área, há uma pessoa, várias caixas e pontos de destino. O objetivo do jogo é usar as setas do teclado para controlar o movimento da pessoa e empurrar as caixas para os pontos de destino. Apenas uma caixa pode ser movida por vez, e se uma caixa ficar presa em um canto, o jogo não poderá continuar.
Personagens
A partir da descrição acima, podemos abstrair os seguintes personagens no jogo:
- Paredes: Áreas fechadas que bloqueiam os caminhos de movimento.
- Espaços: Áreas onde a pessoa pode andar e empurrar caixas.
- Pessoa: O personagem controlado pelo jogador.
- Caixas
- Pontos de destino
A pessoa, as caixas e os pontos de destino devem ser inicializados dentro da área de espaço, e outros personagens não devem aparecer dentro da área da parede.
Controles
No jogo Sokoban, o único personagem que podemos controlar é a pessoa. Usamos as setas do teclado para controlar o movimento da pessoa, tanto para mover a pessoa quanto para empurrar as caixas. Existem dois tipos de movimentos para a pessoa, e precisamos lidar com cada caso separadamente:
- Mover a pessoa sozinha
- Mover a pessoa enquanto empurra uma caixa
Além disso, o jogo suporta as seguintes duas operações:
- Desfazer (Undo): Desfazer o movimento anterior, controlado pela tecla Backspace.
- Refazer (Redo): Refazer o movimento desfeito anteriormente, controlado pela barra de espaço.
Em resumo, precisamos suportar os eventos de teclado para as quatro setas, a tecla Backspace para desfazer e a barra de espaço para refazer. Na próxima seção de implementação do Pygame, precisaremos lidar com esses seis eventos de teclado.
Preparação para o Desenvolvimento
Para poder usar o Pygame no ambiente, abra o terminal no ambiente experimental e digite o seguinte comando para instalar o Pygame:
sudo pip install pygame
Existem muitos módulos no Pygame, incluindo mouse, dispositivos de exibição, gráficos, eventos, fontes, imagens, teclados, som, vídeo, áudio, etc. No jogo Sokoban, usaremos os seguintes módulos:
pygame.display: Acessa dispositivos de exibição para exibir imagens.pygame.image: Carrega e armazena imagens, usado para lidar com sprite sheets.pygame.key: Lê as entradas do teclado.pygame.event: Gerencia eventos, lida com eventos de teclado no jogo.pygame.time: Gerencia o tempo e exibe informações de quadros.
A introdução acima mencionou sprite sheets. Sprite sheet é um método comum de mesclagem de imagens no desenvolvimento de jogos, que mescla pequenos ícones e imagens de fundo em uma única imagem e, em seguida, usa o posicionamento de imagem do Pygame para exibir a parte necessária da imagem.
No jogo Sokoban, usamos um sprite sheet pronto. Não entrarei em detalhes sobre como recortar imagens e mesclar sprite sheets aqui, pois existem inúmeros métodos disponíveis online.
Os elementos de imagem no sprite sheet do Sokoban usados neste projeto são de borgar, e o arquivo pode ser encontrado em ~/project/borgar.png.
Os elementos de imagem do jogo incluem:
- Cor de fundo da interface do jogo
- Jogador
- Caixa normal
- Ponto de destino
- Efeito de sobreposição do jogador e ponto de destino
- Efeito de sobreposição da caixa atingindo o ponto de destino
- Parede
Duas imagens de caixa no sprite sheet não são necessárias em nossa implementação. Explicaremos em detalhes como usar o método blit no Pygame para carregar e exibir o conteúdo do sprite sheet na parte de implementação subsequente.
Desenvolvimento do Jogo
Primeiro, crie um arquivo sokoban.py no diretório ~/project e, em seguida, insira o seguinte conteúdo no arquivo:
- Inicialize o Pygame
import pygame, sys, os
from pygame.locals import *
from collections import deque
pygame.init()
- Defina o objeto de exibição
## Defina o tamanho da janela de exibição do pygame para 400 pixels de largura e 300 pixels de altura
screen = pygame.display.set_mode((400,300))
- Carregue os elementos da imagem
## Carregue os elementos da imagem de um único arquivo
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()
## Defina a cor de fundo da janela para o elemento nas coordenadas (0,0) no arquivo skin
screen.fill(skin.get_at((0,0)))
- Defina o relógio e o tempo de repetição para eventos de teclado. Use
key.set_repeatpara definir o intervalo de tempo para eventos de repetição com os parâmetros(delay, interval).
clock = pygame.time.Clock()
pygame.key.set_repeat(200,50)
- Inicie o loop principal
## Loop principal do jogo
while True:
clock.tick(60)
pass
- Lide com eventos do jogo e operações do teclado. No loop principal, precisamos lidar com eventos de teclado, como mencionado anteriormente, precisamos suportar seis teclas: cima, baixo, esquerda, direita, backspace e espaço.
## Obtenha eventos do jogo
for event in pygame.event.get():
## Evento de sair do jogo
if event.type == QUIT:
pygame.quit()
sys.exit()
## Operação do teclado
elif event.type == KEYDOWN:
## Mover para a esquerda
if event.key == K_LEFT:
pass
## Mover para cima
elif event.key == K_UP:
pass
## Mover para a direita
elif event.key == K_RIGHT:
pass
## Mover para baixo
elif event.key == K_DOWN:
pass
## Operação desfazer (Undo)
elif event.key == K_BACKSPACE:
pass
## Operação refazer (Redo)
elif event.key == K_SPACE:
pass
Agora, concluímos a estrutura do jogo baseada em Pygame. Vamos começar a implementar a lógica do jogo.
Implementação do Mapa
Primeiro, precisamos definir o objeto Sokoban. Usamos uma classe para conter toda a lógica relacionada ao jogo.
class Sokoban:
## Inicializa o jogo Sokoban
def __init__(self):
pass
O jogo Sokoban requer uma área operacional, que é a área do mapa. Usamos uma lista de caracteres para representar o mapa, onde diferentes caracteres representam diferentes elementos no jogo:
- Parede:
## símbolo - Espaço:
- símbolo - Jogador:
@ símbolo - Caixa:
$ símbolo - Ponto de destino:
.símbolo` - Jogador no ponto de destino:
+ símbolo - Caixa no ponto de destino:
* símbolo
Quando o jogo começa, precisamos definir uma lista de caracteres padrão para o mapa. Ao mesmo tempo, precisamos saber a largura e a altura do mapa para gerar um mapa 2D a partir desta lista unidimensional.
A representação do mapa é semelhante ao código a seguir. Você consegue imaginar como seria depois de iniciar com base neste código?
class Sokoban:
## Inicializa o jogo Sokoban
def __init__(self):
## Define o mapa
self.level = list(
"----#####----------"
"----#---#----------"
"----#$--#----------"
"--###--$##---------"
"--#--$-$-#---------"
"###-#-##-#---######"
"#---#-##-#####--..#"
"#-$--$----------..#"
"#####-###-#@##--..#"
"----#-----#########"
"----#######--------")
## Define a largura e a altura do mapa e a posição do jogador no mapa (valor do índice na lista do mapa)
## Total de 19 colunas
self.w = 19
## Total de 11 linhas
self.h = 11
## A posição inicial do jogador está em self.level[163]
self.man = 163
O mapa é exibido digitalizando a lista de caracteres e exibindo diferentes elementos nas posições correspondentes com base nos caracteres.
Como a exibição é 2D, a largura e a altura são usadas para determinar a posição de cada caractere na área de exibição 2D. Precisamos passar screen e skin mencionados no Pygame como parâmetros para a função de desenho draw.
É importante notar que a função de desenho que implementamos usa blit do Pygame, que extrai a imagem do sprite sheet e a exibe na posição especificada:
screen.blit(skin, (i*w, j*w), (0,0,w,w))
A implementação completa da função draw é a seguinte. Primeiro, a varredura é realizada e, em seguida, a imagem correspondente a cada caractere é exibida com base no sprite sheet:
class Sokoban:
## Desenha o mapa na janela do pygame com base no nível do mapa
def draw(self, screen, skin):
## Obtém a largura de cada elemento da imagem
w = skin.get_width() / 4
## Itera por cada elemento de caractere no nível do mapa
for i in range(0, self.w):
for j in range(0, self.h):
## Obtém o caractere na j-ésima linha e i-ésima coluna no mapa
item = self.level[j*self.w + i]
## Exibe como uma parede(#) nesta posição
if item == '#':
## Use o método blit do pygame para exibir a imagem na posição especificada,
## com as coordenadas da posição (i*w, j*w) e as coordenadas e comprimento-largura da imagem na skin como (0,2*w,w,w)
screen.blit(skin, (i*w, j*w), (0,2*w,w,w))
## Exibe como um espaço(-) nesta posição
elif item == '-':
screen.blit(skin, (i*w, j*w), (0,0,w,w))
## Exibe como um jogador(@) nesta posição
elif item == '@':
screen.blit(skin, (i*w, j*w), (w,0,w,w))
## Exibe como uma caixa($) nesta posição
elif item == '$':
screen.blit(skin, (i*w, j*w), (2*w,0,w,w))
## Exibe como um ponto de destino(.) nesta posição
elif item == '.':
screen.blit(skin, (i*w, j*w), (0,w,w,w))
## Exibe como o jogador em um efeito de ponto de destino
elif item == '+':
screen.blit(skin, (i*w, j*w), (w,w,w,w))
## Exibe como a caixa colocada em um efeito de ponto de destino
elif item == '*':
screen.blit(skin, (i*w, j*w), (2*w,w,w,w))
Implementando a Operação de Movimento
A operação de movimento usa as setas do teclado para controlar o movimento em quatro direções: esquerda, direita, cima e baixo. Usamos quatro caracteres 'l' (esquerda), 'r' (direita), 'u' (cima) e 'd' (baixo) para especificar a direção do movimento.
Como o processo necessário para a operação de refazer (redo) e a operação de movimento é semelhante, definimos uma função interna, _move(), para lidar com o movimento na classe Sokoban:
class Sokoban:
## Função interna de movimento: usada para atualizar as mudanças de posição dos elementos no mapa após a operação de movimento, onde d representa a direção do movimento
def _move(self, d):
## Obtém o deslocamento no mapa para o movimento
h = get_offset(d, self.w)
## Se a área de destino do movimento for espaço vazio ou um ponto de destino, apenas o jogador precisa se mover
if self.level[self.man + h] == '-' or self.level[self.man + h] == '.':
## Move o jogador para a posição de destino
move_man(self.level, self.man + h)
## Define a posição original do jogador após o movimento
move_floor(self.level, self.man)
## A nova posição do jogador
self.man += h
## Adiciona a operação de movimento à solução
self.solution += d
## Se a área de destino do movimento for uma caixa, tanto a caixa quanto o jogador precisam se mover
elif self.level[self.man + h] == '*' or self.level[self.man + h] == '$':
## O deslocamento da caixa e a posição do jogador
h2 = h * 2
## A caixa só pode ser movida se a próxima posição for espaço vazio ou um ponto de destino
if self.level[self.man + h2] == '-' or self.level[self.man + h2] == '.':
## Move a caixa para o ponto de destino
move_box(self.level, self.man + h2)
## Move o jogador para o ponto de destino
move_man(self.level, self.man + h)
## Redefine a posição atual do jogador
move_floor(self.level, self.man)
## Define a nova posição do jogador
self.man += h
## Marca a operação de movimento como um caractere maiúsculo para indicar que uma caixa foi empurrada nesta etapa
self.solution += d.upper()
## Incrementa o número de etapas para empurrar a caixa
self.push += 1
Na função _move, precisamos usar as seguintes funções:
- get_offset(d, width): Obtém o deslocamento do movimento no mapa.
drepresenta a direção do movimento ewidthrepresenta a largura da janela do jogo. - move_man(level, i): Move a posição do jogador no mapa.
levelé a lista do mapa eié a posição do jogador. - move_floor(level, i): Redefine a posição após o movimento. Depois que o jogador se move de uma posição, ela precisa ser redefinida como espaço vazio ou um ponto de destino.
- move_box(level, i): Move a posição da caixa no mapa.
levelé a lista do mapa eié a posição da caixa.
A implementação dessas funções pode ser vista no código completo. É importante considerar qual é o elemento original na posição de destino ao mover cada elemento para determinar qual elemento deve ser definido após o movimento.
Para realizar a operação de movimento, basta chamar _move e definir todo[] como vazio (a lista de refazer é ativada apenas ao realizar operações de desfazer).
Implementar Desfazer (Undo)
Desfazer (Undo) é a operação reversa de um movimento. Ele recupera a etapa anterior de solution e realiza a operação reversa. Veja o código detalhado:
class Sokoban:
## Operação de desfazer: desfaz o movimento anterior
def undo(self):
## Verifica se há um registro de movimento
if self.solution.__len__()>0:
## Armazena o registro de movimento na lista todo para a operação de refazer (redo)
self.todo.append(self.solution[-1])
## Exclui o registro de movimento
self.solution.pop()
## Obtém o deslocamento a ser movido para a operação de desfazer: o negativo do deslocamento do último movimento
h = get_offset(self.todo[-1],self.w) * -1
## Verifica se esta operação move apenas o personagem sem empurrar uma caixa
if self.todo[-1].islower():
## Move o personagem de volta para sua posição original
move_man(self.level, self.man + h)
## Define a posição atual do personagem
move_floor(self.level, self.man)
## Define a posição do personagem no mapa
self.man += h
else:
## Se esta etapa empurra uma caixa, move o personagem, a caixa e realiza operações relacionadas em _move
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
Operação Refazer (Redo)
Quando o comando desfazer (undo) é executado, o conteúdo é movido de solution[] para todo[], e só precisamos extrair e chamar a função _move.
## Operação refazer: Quando a operação desfazer é executada e ativada, move de volta para a posição antes do desfazer
def redo(self):
## Verifica se há uma operação desfazer registrada
if self.todo.__len__() > 0:
## Move de volta as etapas desfeitas
self._move(self.todo[-1].lower())
## Exclui este registro
self.todo.pop()
Com as etapas acima, o conteúdo principal do jogo foi concluído. Por favor, continue a completar independentemente o código completo do jogo, teste as capturas de tela e faça quaisquer perguntas na seção de Perguntas e Respostas da Sala de Experimentos (Experiment Room) se tiver algum ponto obscuro. A equipe da Sala de Experimentos e os professores responderão prontamente a quaisquer perguntas que você possa ter.
Funções Adicionais e Refatoração de Código
Agora temos um jogo básico, mas ele não é perfeito. Precisamos adicionar algumas funções adicionais para torná-lo mais jogável.
Também precisamos refatorar o código para torná-lo mais legível e fácil de manter.
Clique para ver o código completo
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"
)
Execução e Testes
Para executar no terminal:
cd ~/project
python sokoban.py
Se tudo estiver normal, você verá a seguinte interface do jogo:

Resumo
Este projeto implementou apenas uma funcionalidade básica de um jogo Sokoban. Com base no experimento, pode-se considerar a expansão deste código por meio de:
- Descobrir como extrair os dados do mapa do código escrito e salvá-los em um arquivo.
- Implementar controles do mouse para mover rapidamente o personagem para uma posição específica.
- Desenvolver um algoritmo para determinar automaticamente se um mapa é solucionável.



