第五章 中期实战 项目练习 二
题目
情景介绍
推箱子游戏(英文一般叫sokoban
)
实现一个控制台版本的推箱子游戏。
推箱子游戏地图信息存在sokoban.txt
中,具体如下
#######
#.....#
#...+.#
##..+.#
#--.#P#
#######
其中
-
P
代表玩家,可以自由移动。 -
+
代表箱子,可以被玩家推动。 -
-
代表目的地,要把箱子移动到目的地。
-
无法移动,被箱子覆盖后,该处变成O
。
(箱子被移开后,原地再次显示-
) -
.
代表路,玩家可以在路与路之间上下左右移动(无法斜着移动) -
#
代表墙,无法穿越。
补充说明:
玩家移动时,前面有箱子则可以向前推动箱子。
如果箱子前面有墙或者其他箱子,则无法推动。
主界面
展示欢迎语,展示游戏面板,读取用户输入。
英文版本
---------------
Welcome to Sokoban!
#######
#.....#
#...+.#
##..+.#
#--.#P#
#######
Enter command:
游戏中有移动命令和退出游戏命令
移动命令
移动命令的语法为:
方位
+ 步数
:尝试向指定方向移动指定步数,移动中前方有墙则停止。
方位值及意义如下:
w
: 向上移动a
: 向左移动s
: 向下移动d
: 向右移动
步数是一个正整数
举例:
d2
: 尝试向右移动2格。
w3
: 尝试向上移动3格。
退出命令
退出命令是e
输入该命令,
游戏界面展示结束语并结束
效果如下
Enter command: e
Bye!
找到终点
对于上面展示的情况。
找到终点的一系列命令为
w2
a2
w1
a1
s2
d1
s1
a1
w2
d3
s1
a2
w1
a1
s1
找到终点的时候,再次展示游戏面板,
并输出成功信息。
#######
#.....#
#.....#
##P...#
#OO.#.#
#######
Win!
整体运行效果
使用上一部分的正确命令,运行效果如下
Welcome to Sokoban!
#######
#.....#
#...+.#
##..+.#
#--.#P#
#######
Enter command: w2
#######
#.....#
#...+P#
##..+.#
#--.#.#
#######
Enter command: a2
#######
#.....#
#.+P..#
##..+.#
#--.#.#
#######
Enter command: w1
#######
#..P..#
#.+...#
##..+.#
#--.#.#
#######
Enter command: a1
#######
#.P...#
#.+...#
##..+.#
#--.#.#
#######
Enter command: s2
#######
#.....#
#.....#
##P.+.#
#-O.#.#
#######
Enter command: d1
#######
#.....#
#.....#
##.P+.#
#-O.#.#
#######
Enter command: s1
#######
#.....#
#.....#
##..+.#
#-OP#.#
#######
Enter command: a1
#######
#.....#
#.....#
##..+.#
#OP.#.#
#######
Enter command: w2
#######
#.....#
#.P...#
##..+.#
#O-.#.#
#######
Enter command: d3
#######
#.....#
#....P#
##..+.#
#O-.#.#
#######
Enter command: s1
#######
#.....#
#.....#
##..+P#
#O-.#.#
#######
Enter command: a2
#######
#.....#
#.....#
##+P..#
#O-.#.#
#######
Enter command: w1
#######
#.....#
#..P..#
##+...#
#O-.#.#
#######
Enter command: a1
#######
#.....#
#.P...#
##+...#
#O-.#.#
#######
Enter command: s1
#######
#.....#
#.....#
##P...#
#OO.#.#
#######
Win!
分割线
本小段没有实际意义,
仅用于分隔题目和答案。
防止学生无意中直接看到答案,
影响思路。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
答案
WELCOME = "Welcome to Sokoban!"
ENTER = "Enter command: "
WIN = "Win!"
BYE = "Bye!"
INVALID = "Invalid command"
DIRECTION = {
# direction: (dr, dc)
"w": (-1, 0),
"a": (0, -1),
"s": (1, 0),
"d": (0, 1),
}
def read_txt(filename):
print(filename)
with open(filename, "r") as f:
fr = f.read()
lines = fr.split("\n")
board = []
for line in lines:
line = line.strip()
if line:
board.append(list(line))
return board
def get_info(board):
info = {
"player": [],
"boxes": [],
}
r, c = len(board), len(board[0])
for ri in range(r):
for ci in range(c):
cell = board[ri][ci]
if cell == "+":
info["boxes"].append([ri, ci])
board[ri][ci] = "."
elif cell == "P":
info["player"] = [ri, ci]
board[ri][ci] = "."
return info
def show_board(board, info):
r, c = len(board), len(board[0])
player = tuple(info["player"])
boxes = info["boxes"]
for ri in range(r):
for ci in range(c):
cell = board[ri][ci]
if (ri, ci) == player:
cell = "P"
elif [ri, ci] in boxes:
if cell == "-":
cell = "O"
else:
cell = "+"
print(cell, end="")
print()
def check_win(board, info):
for box in info["boxes"]:
br, bc = box
if board[br][bc] != "-":
return False
return True
def move(board, info, drc, step):
r, c = len(board), len(board[1])
pr, pc = info["player"]
dr, dc = drc
for i in range(step):
new_r = pr + dr
new_c = pc + dc
if board[new_r][new_c] == "#":
break
if new_r < 0 or new_r >= r or new_c < 0 or new_c >= c:
break
new_rc = [new_r, new_c]
if new_rc in info["boxes"]:
index = info["boxes"].index(new_rc)
next_r, next_c = new_r + dr, new_c + dc
if board[next_r][next_c] == "#":
break
if next_r < 0 or next_r >= r or next_c < 0 or next_c >= c:
break
if [next_r, next_c] in info["boxes"]:
break
info["boxes"][index] = [next_r, next_c]
pr = new_r
pc = new_c
info["player"] = [pr, pc]
def main(txt_file):
board = read_txt(txt_file) # 1. 获取推箱子文件对应的二维列表
info = get_info(board) # 2. 解析出关键信息: 玩家位置,箱子位置
print(WELCOME) # 3. 展示欢迎语
# 4. 使用循环
while True:
# 4-a. 循环中展示面板
show_board(board, info)
if check_win(board, info): # 4-b. 所有箱子都到达目标点,胜利并结束游戏
print(WIN)
return
cmd = input(ENTER)
cmd = cmd.lower()
# 4-d. 处理用户输入
if len(cmd) > 0:
if cmd[0] == "e": # 4-e. 直接退出游戏
print(BYE)
return
# 4-f. 尝试解析移动命令
direction = cmd[0]
step = cmd[1:]
if direction in DIRECTION:
drc = DIRECTION[direction]
if step.isdigit(): #
# 4-g. 解析成功,移动并直接进入下一轮循环
step = int(step)
move(board, info, drc, step)
continue
# 4-h. 处理中失败了,提醒输入不符合规范。
print(INVALID)
txt_file = "sokoban.txt"
main(txt_file)