原生Python完成迷宫冒险游戏
功能介绍
这是一个迷宫冒险类游戏,玩家走到终点即为胜利。在通往终点的道路上可能会有火焰,玩家需要绕过这些火焰或者用手中的水瓶去浇灭它。玩家可以用过 w,a,s,d 来移动,碰到不同格子会有不同的效果:
Cell | Display | Function |
---|---|---|
入口 | X | 迷宫的入口。当游戏开始时,玩家出生在出口位置。 |
出口 | Y | 迷宫的出口。玩家到达出口即为胜利,显示出玩家用的步数。 |
玩家 | A | 玩家。初始没有水。 |
墙 | * | 玩家撞到墙时不能移动。 |
空气 | 玩家可移动到的格子。 | |
火焰 | F | 如果玩家手中有水,可以消耗一瓶水救火。如果玩家没有水,玩家被火烧死,游戏结束。 |
水 | W | 玩家捡起水。 |
传送门 | 数字 1-9 | 玩家可以传送到相对应的数字上。玩家按 e 可以再传送原来的传送门。) |
功能实现
- 用read file的知识去读取迷宫,同时创建cell instance。
- 玩家的移动本质就是交换格子,但由于有的格子有特殊效果,我们可以先step到那个格子,看有没有特殊效果,然后再交换格子。
- run.py控制玩家的输入,并把命令传给game.py。game.py直接控制游戏的状态。
处理人物以及格子
玩家和格子都需要有display的属性,方便我们把他画出来。在player里,需要记录玩家的行和列,以及移动的动作。
# player.py
class Player:
def __init__(self):
"""
display -- the display of the player
num_water_buckets -- the number of water the player have
row, col -- the position of the player
moves -- store the move made by the player
action -- store the user's input (record how the player move to the teleport)
"""
self.display = 'A'
self.num_water_buckets = 0
self.row = 0
self.col = 0
self.moves = []
self.action = ''
def move(self, move):
# Move the player
if move =='w':
self.row -= 1
self.moves.append('w')
if move =='s':
self.row += 1
self.moves.append('s')
if move =='d':
self.col += 1
self.moves.append('d')
if move =='a':
self.col -= 1
self.moves.append('a')
if move == 'e':
self.moves.append('e')
def moves_made(self):
"""
Returns:
A string contains how many moves the player made and their moves
"""
message = ''
num_of_move = len(self.moves)
moves = ', '.join(self.moves)
if len(self.moves) == 1:
message += 'You made {} move.\n'.format(num_of_move)
message += 'Your move: {}\n'.format(moves)
else:
message += 'You made {} moves.\n'.format(num_of_move)
message += 'Your moves: {}\n'.format(moves)
return message
def win(self):
# Print the winning message and the moves
print()
print('\nYou conquer the treacherous maze set up by the Fire Nation and reclaim the Honourable \
Furious Forest Throne, restoring your hometown back to its former glory of rainbow and sunshine! Peace reigns over the lands.\n')
print(self.moves_made())
print('''=====================\n====== YOU WIN! =====\n=====================''')
exit()
def lose(self):
# Print the winning message and the moves
print()
print('\nYou step into the fires and watch your dreams disappear :(.')
print('\nThe Fire Nation triumphs! The Honourable Furious Forest is reduced to a pile of \
ash and is scattered to the winds by the next storm... You have been roasted.\n')
print(self.moves_made())
print('''=====================\n===== GAME OVER =====\n=====================''')
exit()
step()记录了每个格子都有特殊的效果,能直接改变game的状态
class Start:
def __init__(self):
self.display = 'X'
def step(self, game):
"""
If current position is not a teleport, the current position become an Air block and the player is free to move
If it is a teleport, it stays the same
"""
if not isinstance(game.grid[game.player.row][game.player.col], Teleport):
game.grid[game.player.row][game.player.col]=Air()
class End:
def __init__(self):
self.display = 'Y'
def step(self, game):
"""
If current position is not a teleport, the current position become an Air block and the player is free to move
If it is a teleport, it stays the same
Give a game message 'win'
"""
if not isinstance(game.grid[game.player.row][game.player.col], Teleport):
game.grid[game.player.row][game.player.col]=Air()
game.message='win'
class Air:
def __init__(self):
self.display = ' '
def step(self, game):
"""
If current position is a teleport, current position is the same teleport
Otherwise, current position become an Air block
"""
if isinstance(game.grid[game.player.row][game.player.col], Teleport):
game.grid[game.player.row][game.player.col]=Teleport(game.grid[game.player.row][game.player.col].display)
else:
game.grid[game.player.row][game.player.col]=Air()
class Wall:
def __init__(self):
self.display = '*'
def step(self, game):
"""
The player will not move
Give a game message of step on a wall
"""
game.is_player_move = False
game.message = '\nYou walked into a wall. Oof!\n'
class Fire:
def __init__(self):
self.display = 'F'
def step(self, game):
"""
If player have more than one water, one water is used and give a message of extinguish fire and current position become an Air block
If player donnot have water, give a message of lose and current position become an Air block
"""
if game.player.num_water_buckets >= 1:
game.player.num_water_buckets -= 1
game.message = '\nWith your strong acorn arms, you throw a water bucket at the fire. You acorn roll your way through the extinguished flames!\n'
game.grid[game.player.row][game.player.col]=Air()
else:
game.message = 'lose'
game.grid[game.player.row][game.player.col]=Air()
class Water:
def __init__(self):
self.display = 'W'
def step(self, game):
"""
player gain one water and give a message of gaining water and current position become an Air block
"""
game.player.num_water_buckets += 1
game.message = "\nThank the Honourable Furious Forest, you\'ve found a bucket of water!\n"
game.grid[game.player.row][game.player.col]=Air()
class Teleport:
def __init__(self,cell):
self.display = cell
def step(self, game):
"""
Update how the player came to current teleport
Find the corresponding teleport and move player to there, the player are not allowed to move then
Give a game message of teleport
"""
game.player.moves.append(game.player.action)
display = game.grid[game.next_row][game.next_col].display
game.player.row, game.player.col = game.find(display, game.next_row, game.next_col)
game.is_player_move = False
game.message = '\nWhoosh! The magical gates break Physics as we know it and opens a wormhole through space and time.\n'
处理迷宫
生成迷宫。读取一个txt文件,把每个cell变成各自的对象,输出一个二维列表,里面为对象
# game_parser.py
from cells import (
Start,
End,
Air,
Wall,
Fire,
Water,
Teleport
)
def read_lines(filename):
"""
Read in a file, process them using parse(),
and return the contents as a list of list of cells.
"""
try:
f = open(filename)
except:
print('{} does not exist!'.format(filename))
exit()
lines = f.readlines()
f.close()
return parse(lines)
def parse(lines):
"""Transform the input into a grid.
Arguments:
lines -- list of strings representing the grid
Returns:
list -- contains list of lists of Cells
"""
#只能有一个入口,一个出口, 传送门必须=对应
count_x = 0
count_y = 0
count_tele = {'1':0,'2':0,'3':0,'4':0,'5':0,'6':0,'7':0,'8':0,'9':0}
grid = []
for line in lines:
str_cells = list(line.rstrip())
obj_cells = []
for cell in str_cells:
if cell == 'X':
obj_cells.append(Start())
count_x+=1
elif cell == 'Y':
obj_cells.append(End())
count_y+=1
elif cell == ' ':
obj_cells.append(Air())
elif cell == '*':
obj_cells.append(Wall())
elif cell == 'F':
obj_cells.append(Fire())
elif cell == 'W':
obj_cells.append(Water())
elif cell.isdigit():
if int(cell) in [i for i in range(1,10)]:
obj_cells.append(Teleport(cell))
count_tele[cell] += 1
else:
raise ValueError('Bad letter in configuration file: {}.'.format(cell))
else:
raise ValueError('Bad letter in configuration file: {}.'.format(cell))
grid.append(obj_cells)
# Check there is one start, end, teleport in pairs
if count_x != 1:
raise ValueError('Expected 1 starting position, got {}.'.format(count_x))
if count_y != 1:
raise ValueError('Expected 1 ending position, got {}.'.format(count_y))
for [key,value] in count_tele.items():
if value == 1 or value > 2:
raise ValueError('Teleport pad {} does not have an exclusively matching pad.'.format(key))
return grid
第二步是把他们画出来,输出是一个字符串
## grid.py
def grid_to_string(grid, player):
"""Turns a grid and player into a string
Arguments:
grid -- list of list of Cells
player -- a Player with water buckets
Returns:
string: A string representation of the grid and player.
"""
strn = ''
row = 0
while row < len(grid):
col = 0
while col < len(grid[row]):
if row == player.row and col == player.col:
strn += player.display
else:
strn += grid[row][col].display
col += 1
strn+='\n'
row += 1
if player.num_water_buckets == 1:
strn += '\nYou have {} water bucket.'.format(player.num_water_buckets)
else:
strn += '\nYou have {} water buckets.'.format(player.num_water_buckets)
return strn
游戏的引擎
# game.py
from game_parser import read_lines
from grid import grid_to_string
from player import Player
from cells import (
Start,
Wall,
Teleport
)
class Game:
player = Player()
def __init__(self, filename):
"""
Arguments:
filename -- the name of board going to play
Instance variable:
grid -- returns a two dimension list of cells
next_row, next_col -- the player's next position in the grid
start.row, start.col -- the start position of the game
player.row, player.col -- set the player to the start
message : store any messages from the game
is_player_move : controlling whether to move the player's position. If True, the player can move one step. If Flase, the player cannot move one step.
"""
self.grid = read_lines(filename)
self.next_row , self.next_col = 0 , 0
self.start_row, self.start_col = self.find('X', -1, -1)
self.player.row, self.player.col = self.start_row, self.start_col
self.message = ''
self.is_player_move = True
def find(self, display, current_row, current_col):
"""
find the position of a cell
Arguments:
display -- the display of the cell want to find
current_row, current_col -- the current position of player
Returns:
a list contains row and column of the cell
"""
valid_display = ['X', 'Y', ' ', '*', 'F', 'W', 'A', '1', '2', '3', '4', '5', '6', '7', '8', '9']
if display not in valid_display:
raise ValueError('Please enter a valid display')
row = 0
result = []
while row < len(self.grid):
col = 0
while col < len(self.grid[row]):
if self.grid[row][col].display == display \
and (row != current_row or col != current_col):
result.append(row)
result.append(col)
col += 1
row += 1
return result
def draw(self):
# draw the grid
return grid_to_string(self.grid,self.player)
def in_bound(self,row,col):
"""
Check if the position is in the bound of grid
Arguments:
row, col -- the position going to check
Returns:
True if the position is in bound
False if the position is out of bound, then player act as step on a wall
"""
if type(row) != int or type(col) != int:
raise ValueError('Please enter an integer row and col')
if row <0 or row > len(self.grid)-1 or col < 0 or col > len(self.grid[0])-1:
wall = Wall()
wall.step(self)
self.is_player_move = False
return False
else:
return True
def game_move(self,move):
"""
Before move the player, check any special effect if move the player
Arguments:
move -- input from user (one of w, s, d, a, e, q, otherwise raise warning)
"""
cr = self.player.row
cl = self.player.col
self.player.action = move
if move == 'w':
#If the next position is in the bound, step the next position to see if there is any special effect.
if self.in_bound(cr-1,cl):
self.next_row, self.next_col = cr-1, cl
self.grid[cr-1][cl].step(self)
elif move == 's':
if self.in_bound(cr+1,cl):
self.next_row, self.next_col = cr+1, cl
self.grid[cr+1][cl].step(self)
elif move == 'd':
if self.in_bound(cr,cl+1):
self.next_row, self.next_col = cr, cl+1
self.grid[cr][cl+1].step(self)
elif move == 'a':
if self.in_bound(cr,cl-1):
self.next_row, self.next_col = cr, cl-1
self.grid[cr][cl-1].step(self)
elif move == 'q':
print('\nBye!')
exit()
elif move == 'e':
# if player is on a teleport now and user input 'e', find the position of the other teleport and transit the player to there\
# the player will not move one step since it is transit by the teleport
if isinstance(self.grid[self.player.row][self.player.col], Teleport):
display = self.grid[self.player.row][self.player.col].display
self.player.row, self.player.col = self.find(display, self.player.row, self.player.col)
self.is_player_move = False
self.player.moves.append('e')
self.message = '\nWhoosh! The magical gates break Physics as we know it and opens a wormhole through space and time.\n'
else:
self.message = '\nPlease enter a valid move (w, a, s, d, e, q).\n'
self.is_player_move = False
# Set the display of start position to X
self.grid[self.start_row][self.start_col]=Start()
def message_parse(self):
# parse the message from the game
if self.message =='win':
self.player.win()
elif self.message == 'lose':
self.player.lose()
else:
print(self.message)
self.message = ''
# run.py
from game import Game
import os
import sys
if len(sys.argv) < 2 or len(sys.argv) > 3:
print('Usage: python3 run.py <filename> [play]')
exit()
def play_mode():
# If the mode is play, the system will clean screen automatic
if len(sys.argv) == 3 and sys.argv[2] == 'play':
return os.system('cls')
else:
return None
filename = sys.argv[1]
game = Game(filename)
while True:
game.is_player_move = True
# draw the grid and print any message
play_mode()
print(game.draw())
game.message_parse()
# ask for input and check if there is any special effect
move = input('Input a move: ').lower()
game.game_move(move)
# if player is free to move, move the player
if game.is_player_move:
game.player.move(move)