はじめに
このプロジェクトは、Python 言語と Pygame を使って、クラシックゲームのソコバンを開発するものです。
このプロジェクトでカバーされる知識ポイントは以下の通りです。
- Python の基本構文
- Pygame を使った基本的なゲーム開発
このコースは難易度が適度で、Python の基本的な理解を持ち、さらに知識を深めたいユーザーに適しています。
ソースコードsokoban.py.zipは GNU GPL v3 ライセンスの下で公開されており、スキンは Borgar によって作成されました。
👀 プレビュー

🎯 タスク
このプロジェクトで学ぶことは以下の通りです。
- Pygame を使ってゲームを初期化する方法
- ゲームイベントとキーボード操作を処理する方法
- ゲーム用のマップを実装する方法
- プレイヤーとボックスの移動操作を実装する方法
- アンドゥとリドゥ操作を実装する方法
- ゲームインターフェイスをテストする方法
🏆 成果
このプロジェクトを完了すると、以下のことができるようになります。
- Pygame を初期化し、ゲームウィンドウをセットアップする
- Pygame でゲームイベントとキーボード入力を処理する
- ゲームマップを実装し、Pygame を使って表示する
- プレイヤーとボックスの移動操作を実装する
- ゲーム内でアンドゥとリドゥ操作を実装する
- ゲームインターフェイスをテストして実行する
ゲームの説明
ソコバンゲームでは、不規則な多角形領域を形成する囲いの壁があります。プレイヤーとボックスはこの領域内でのみ移動できます。領域内には、1 人の人物といくつかのボックスと目標地点があります。ゲームの目的は、矢印キーを使って人物の移動を制御し、ボックスを目標地点に押し付けることです。一度に 1 つのボックスのみを移動でき、ボックスが隅に引っかかった場合、ゲームは続行できません。
キャラクター
上記の説明から、このゲームにおける以下のキャラクターを抽象化できます。
- 壁:移動経路をブロックする囲まれた領域
- スペース:人物が歩き、ボックスを押すことができる領域
- 人物:プレイヤーが制御するキャラクター
- ボックス
- 目標地点
人物、ボックス、目標地点はすべてスペース領域内で初期化する必要があり、壁領域内には他のキャラクターが出現してはいけません。
操作方法
ソコバンゲームでは、操作できる唯一のキャラクターは人物です。矢印キーを使って人物の移動を制御し、人物の移動とボックスの押し付けの両方に使用します。人物の移動には 2 種類あり、それぞれのケースを個別に処理する必要があります。
- 人物のみを移動させる
- ボックスを押しながら人物を移動させる
また、このゲームでは以下の 2 つの操作がサポートされています。
- アンドゥ:前の移動を取り消します。バックスペースキーで制御します。
- リドゥ:以前取り消された移動をやり直します。スペースバーで制御します。
要するに、4 つの矢印キー、アンドゥ用のバックスペースキー、リドゥ用のスペースバーのキーボードイベントをサポートする必要があります。次の Pygame を実装するセクションでは、これら 6 つのキーボードイベントを処理する必要があります。
開発の準備
環境で pygame を使用できるようにするには、実験環境のターミナルを開き、次のコマンドを入力して pygame をインストールします。
sudo pip install pygame
pygame には、マウス、表示デバイス、グラフィック、イベント、フォント、画像、キーボード、サウンド、ビデオ、オーディオなど、多くのモジュールがあります。ソコバンゲームでは、以下のモジュールを使用します。
pygame.display:画像を表示するための表示デバイスにアクセスします。pygame.image:画像を読み込んで保存し、スプライトシートを処理するために使用します。pygame.key:キーボード入力を読み取ります。pygame.event:イベントを管理し、ゲーム内のキーボードイベントを処理します。pygame.time:時間を管理し、フレーム情報を表示します。
上記の説明ではスプライトシートについて言及しました。スプライトシートは、ゲーム開発における一般的な画像マージ方法であり、小さなアイコンと背景画像を 1 つの画像にマージし、その後、pygame の画像位置指定を使用して画像の必要な部分を表示します。
ソコバンゲームでは、既製のスプライトシートを使用しています。ここでは、画像の切り抜き方法やスプライトシートのマージ方法について詳しく説明しません。オンライン上には数え切れない方法があります。
このプロジェクトで使用するソコバンのスプライトシートの画像要素はborgarからのもので、ファイルは~/project/borgar.pngにあります。
ゲーム画像要素には、以下が含まれます。
- ゲームインターフェイスの背景色
- プレイヤー
- 通常のボックス
- 目標地点
- プレイヤーと目標地点の重なり効果
- ボックスが目標地点に到達した重なり効果
- 壁
スプライトシート内の 2 つのボックス画像は、この実装では必要ありません。後続の実装部分で、pygame のblitメソッドを使用してスプライトシートのコンテンツを読み込んで表示する方法について詳しく説明します。
ゲーム開発
まず、~/project ディレクトリに sokoban.py ファイルを作成し、次の内容をファイルに入力します。
- pygame を初期化する
import pygame, sys, os
from pygame.locals import *
from collections import deque
pygame.init()
- 表示オブジェクトを設定する
## pygame の表示ウィンドウのサイズを横幅 400 ピクセル、高さ 300 ピクセルに設定する
screen = pygame.display.set_mode((400,300))
- 画像要素を読み込む
## 単一のファイルから画像要素を読み込む
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()
## ウィンドウの背景色を、skin ファイルの座標 (0,0) の要素に設定する
screen.fill(skin.get_at((0,0)))
- クロックとキーボードイベントの繰り返し時間を設定する。
key.set_repeatを使用して、パラメータ(delay, interval)で繰り返しイベントの時間間隔を設定する。
clock = pygame.time.Clock()
pygame.key.set_repeat(200,50)
- メインループを開始する
## ゲームのメインループ
while True:
clock.tick(60)
pass
- ゲームイベントとキーボード操作を処理する。メインループでは、キーボードイベントを処理する必要があり、前述の通り、上、下、左、右、バックスペース、スペースの 6 つのキーをサポートする必要がある。
## ゲームイベントを取得する
for event in pygame.event.get():
## ゲーム終了イベント
if event.type == QUIT:
pygame.quit()
sys.exit()
## キーボード操作
elif event.type == KEYDOWN:
## 左に移動
if event.key == K_LEFT:
pass
## 上に移動
elif event.key == K_UP:
pass
## 右に移動
elif event.key == K_RIGHT:
pass
## 下に移動
elif event.key == K_DOWN:
pass
## アンドゥ操作
elif event.key == K_BACKSPACE:
pass
## リドゥ操作
elif event.key == K_SPACE:
pass
これで、pygame をベースとしたゲームフレームワークが完成しました。次に、ゲームロジックの実装に移りましょう。
マップの実装
まず、ソコバンオブジェクトを定義する必要があります。すべてのゲーム関連のロジックを含むクラスを使用します。
class Sokoban:
## ソコバンゲームを初期化する
def __init__(self):
pass
ソコバンゲームには操作領域が必要で、それがマップ領域です。マップを表すために文字のリストを使用し、異なる文字がゲーム内の異なる要素を表します。
- 壁:
## 記号 - スペース:
- 記号 - プレイヤー:
@ 記号 - ボックス:
$ 記号 - 目標地点:
. 記号 - 目標地点にいるプレイヤー:
+ 記号 - 目標地点に置かれたボックス:
* 記号
ゲームが始まるとき、マップにデフォルトの文字リストを設定する必要があります。同時に、マップの幅と高さを知る必要があり、この 1 次元のリストから 2 次元のマップを生成するためです。
マップの表現は以下のコードに似ています。このコードを元にゲームを開始したときの様子が想像できますか?
class Sokoban:
## ソコバンゲームを初期化する
def __init__(self):
## マップを設定する
self.level = list(
"----#####----------"
"----#---#----------"
"----#$--#----------"
"--###--$##---------"
"--#--$-$-#---------"
"###-#-##-#---######"
"#---#-##-#####--..#"
"#-$--$----------..#"
"#####-###-#@##--..#"
"----#-----#########"
"----#######--------")
## マップの幅と高さと、マップ内のプレイヤーの位置(マップリスト内のインデックス値)を設定する
## 合計 19 列
self.w = 19
## 合計 11 行
self.h = 11
## プレイヤーの初期位置は self.level[163] にある
self.man = 163
マップは文字リストを走査し、文字に基づいて対応する位置に異なる要素を表示することで表示されます。
表示は 2 次元であるため、幅と高さを使用して 2 次元表示領域内の各文字の位置を決定します。pygame で言及したscreenとskinを引数として描画関数drawに渡す必要があります。
重要なことは、私たちが実装した描画関数が pygame のblitを使用しており、これはスプライトシートから画像を抽出し、指定された位置に表示することです。
screen.blit(skin, (i*w, j*w), (0,0,w,w))
draw関数の完全な実装は以下の通りです。まず走査を行い、その後、スプライトシートに基づいて各文字に対応する画像を表示します。
class Sokoban:
## pygame のウィンドウにマップレベルに基づいてマップを描画する
def draw(self, screen, skin):
## 各画像要素の幅を取得する
w = skin.get_width() / 4
## マップレベルの各文字要素を反復処理する
for i in range(0, self.w):
for j in range(0, self.h):
## マップの j 行目と i 列目の文字を取得する
item = self.level[j*self.w + i]
## この位置に壁 (#) として表示する
if item == '#':
## pygame の blit メソッドを使用して指定された位置に画像を表示し、
## 位置座標は (i*w, j*w) で、skin 内の画像の座標と長さ幅は (0,2*w,w,w)
screen.blit(skin, (i*w, j*w), (0,2*w,w,w))
## この位置にスペース (-) として表示する
elif item == '-':
screen.blit(skin, (i*w, j*w), (0,0,w,w))
## この位置にプレイヤー(@) として表示する
elif item == '@':
screen.blit(skin, (i*w, j*w), (w,0,w,w))
## この位置にボックス ($) として表示する
elif item == '$':
screen.blit(skin, (i*w, j*w), (2*w,0,w,w))
## この位置に目標地点 (.) として表示する
elif item == '.':
screen.blit(skin, (i*w, j*w), (0,w,w,w))
## 目標地点にいるプレイヤーの効果として表示する
elif item == '+':
screen.blit(skin, (i*w, j*w), (w,w,w,w))
## 目標地点に置かれたボックスの効果として表示する
elif item == '*':
screen.blit(skin, (i*w, j*w), (2*w,w,w,w))
移動操作の実装
移動操作では、矢印キーを使って左、右、上、下の 4 方向に移動を制御します。移動方向を指定するために、4 つの文字 'l'(左)、'r'(右)、'u'(上)、'd'(下)を使用します。
やり直し操作と移動操作に必要な処理は似ているため、ソコバンクラス内で内部関数 _move() を定義して移動を処理します。
class Sokoban:
## 内部の移動関数:移動操作後のマップ内の要素の位置変化を更新するために使用され、d は移動方向を表す
def _move(self, d):
## 移動に対するマップ内の変位を取得する
h = get_offset(d, self.w)
## 移動の目標領域が空きスペースまたは目標地点の場合、プレイヤーだけが移動する必要がある
if self.level[self.man + h] == '-' or self.level[self.man + h] == '.':
## プレイヤーを目標位置に移動する
move_man(self.level, self.man + h)
## 移動後のプレイヤーの元の位置を設定する
move_floor(self.level, self.man)
## プレイヤーの新しい位置
self.man += h
## 移動操作をソリューションに追加する
self.solution += d
## 移動の目標領域がボックスの場合、ボックスとプレイヤーの両方が移動する必要がある
elif self.level[self.man + h] == '*' or self.level[self.man + h] == '$':
## ボックスとプレイヤーの位置の変位
h2 = h * 2
## 次の位置が空きスペースまたは目標地点の場合のみ、ボックスを移動できる
if self.level[self.man + h2] == '-' or self.level[self.man + h2] == '.':
## ボックスを目標地点に移動する
move_box(self.level, self.man + h2)
## プレイヤーを目標地点に移動する
move_man(self.level, self.man + h)
## プレイヤーの現在位置をリセットする
move_floor(self.level, self.man)
## プレイヤーの新しい位置を設定する
self.man += h
## 移動操作を大文字の文字としてマークし、このステップでボックスが押されたことを示す
self.solution += d.upper()
## ボックスを押すステップ数を増やす
self.push += 1
_move 関数では、以下の関数を使用する必要があります。
- get_offset(d, width):マップ内の移動の変位を取得する。
dは移動方向を表し、widthはゲームウィンドウの幅を表す。 - move_man(level, i):マップ内のプレイヤーの位置を移動する。
levelはマップリストで、iはプレイヤーの位置である。 - move_floor(level, i):移動後の位置をリセットする。プレイヤーがある位置から移動した後、その位置は空きスペースまたは目標地点にリセットされる必要がある。
- move_box(level, i):マップ内のボックスの位置を移動する。
levelはマップリストで、iはボックスの位置である。
これらの関数の実装は、完全なコードで確認できます。各要素を移動する際に、目標位置の元の要素が何であるかを考慮して、移動後に何の要素に設定するかを決定することが重要です。
移動操作を実行するには、単に _move を呼び出し、todo[] を空に設定します(やり直しリストはアンドゥ操作を実行する際にのみアクティブになります)。
アンドゥの実装
アンドゥは移動の逆操作です。solution から前のステップを取得し、逆操作を行います。詳細なコードを見てください。
class Sokoban:
## アンドゥ操作:前の移動を元に戻す
def undo(self):
## 移動記録があるかどうかを確認する
if self.solution.__len__()>0:
## やり直し操作のために、移動記録を todo リストに保存する
self.todo.append(self.solution[-1])
## 移動記録を削除する
self.solution.pop()
## アンドゥ操作における移動するオフセットを取得する:最後の移動のオフセットの負数
h = get_offset(self.todo[-1],self.w) * -1
## この操作がボックスを押すことなく文字のみを移動させるかどうかを確認する
if self.todo[-1].islower():
## 文字を元の位置に戻す
move_man(self.level, self.man + h)
## 文字の現在位置を設定する
move_floor(self.level, self.man)
## マップ上の文字の位置を設定する
self.man += h
else:
## このステップがボックスを押す場合、文字とボックスを移動させ、_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
リドゥ操作
アンドゥコマンドが実行されると、内容が solution[] から todo[] に移動し、私たちはただ抽出して _move 関数を呼び出すだけです。
## リドゥ操作:アンドゥ操作が実行されて活性化されたとき、アンドゥ前の位置に戻る
def redo(self):
## アンドゥ操作が記録されているかどうかを確認する
if self.todo.__len__() > 0:
## 元に戻されたステップを戻す
self._move(self.todo[-1].lower())
## この記録を削除する
self.todo.pop()
上記の手順で、ゲームの主な内容は完了しました。引き続き、完全なゲームコードを独自に完成させ、スクリーンショットをテストしてください。不明点があれば、実験室の Q&A セクションで質問してください。実験室チームと教師は、あなたの質問に迅速に回答します。
追加機能とコードのリファクタリング
今、基本的なゲームができましたが、まだ不完全です。もっと遊びやすくするために、追加機能をいくつか追加する必要があります。
また、コードをリファクタリングして、読みやすく保守しやすくする必要があります。
コード全体を表示するにはここをクリック
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"
)
実行とテスト
ターミナルで実行するには:
cd ~/project
python sokoban.py
すべてが正常であれば、次のゲームインターフェイスが表示されます:

まとめ
このプロジェクトでは、倉庫番ゲームの基本機能のみを実装しました。この実験を基に、以下のようにコードを拡張することができます。
- 書かれたコードからマップデータを抽出し、ファイルに保存する方法を考える。
- マウス操作を実装して、キャラクターを迅速に特定の位置に移動させる。
- マップが解けるかどうかを自動的に判断するアルゴリズムを開発する。



