探索Python与设计模式:游戏循环模式
现在是时候来看我们的第一个设计模式了:游戏循环模式(Game Loop Pattern)!这个模式可以为我们提供许多很好的思路,以非常有效的方式重构我们的游戏
游戏循环模式
游戏循环模式有几个不同的版本。这里我介绍一个简单的情况,使用单线程。以下5个函数组成了这个模式:
- init()函数在启动时被调用,用于初始化游戏及其数据。在接下来的内容中,我将这些数据称为游戏状态(game state)
- processInput()函数在每次游戏迭代中被调用,用于处理控制输入(键盘、鼠标、手柄)。
update()函数用于改变游戏状态。processInput()的结果被用于更新游戏状态,同时也可以包含自动处理的过程。 - render()函数负责处理显示,它将游戏状态转化为可视内容。
run()函数包含了游戏循环。在许多情况下,这个循环看起来像下面这样:
def run():
init() while True: processInput() update() render()
游戏循环模式,就像其他模式一样,是一个为解决问题提供思路的方案。并没有唯一的使用方式:它取决于具体情况。
遵循模式的使用强迫你考虑可能没有想到的问题。例如,在创建“猜数字”游戏时,将用户输入、数据更新和渲染分开可能不是首先想到的。然而,根据经验丰富的开发者的说法,这种分离是至关重要的。所以,作为初学者,我们现在遵循这个建议,以后会理解它为什么重要。并且请相信我:当你理解了这些想法背后的所有精妙之处时,你会感到惊叹不已!
猜数字游戏: init()函数
从这个循环模式的init函数入手:
def init():
return None, random.randint(1,10)
这个函数返回一个初始游戏状态。我将游戏数据称为“游戏状态”,因为游戏可以被看作是有限状态机。对于这个游戏,状态包括:
游戏状态:表示游戏的整体状态的字符串:
- "win":玩家获胜并结束游戏;
- "end":玩家离开游戏;
- "lower":玩家仍在游戏中,并提供了一个比魔术数字小的数字;
- "higher":同样,但是为一个比魔术数字大的数字。
- 魔术数字:玩家需要猜测的数字。
将所有的游戏数据捆绑在一起是一个重要的任务;我们将在接下来的文章中详细讨论这个问题。
猜数字游戏:processInput()函数
def processInput():
while True:
word = input("What is the magic number? ")
if word == "quit":
return None
try:
playerNumber = int(word)
break
except ValueError:
print("Please type a number without decimals!")
continue
return playerNumber
这个函数要求玩家输入一个数字。它处理与用户输入相关的所有问题,比如检查输入的数字是否正确。它返回输入的数字,如果玩家想要停止游戏,则返回None。
对于使用这个函数的用户(比如run()函数),它就像一个神奇的盒子,返回玩家的指令。收集这些指令的方式并不重要。可以是通过键盘、鼠标、手柄、网络甚至是通过人工智能来获取。
猜数字游戏: update()函数
update()函数使用玩家的指令来更新游戏状态:
def update(gameStatus,magicNumber,playerNumber):
if playerNumber is None:
gameStatus = "end"
elif playerNumber == magicNumber:
gameStatus = "win"
elif magicNumber < playerNumber:
gameStatus = "lower"
elif magicNumber > playerNumber:
gameStatus = "higher"
return gameStatus, magicNumber
在我们的情况下,玩家的指令是playerNumber,而游戏状态和魔术数字形成了游戏状态。根据playerNumber的值,函数会更新游戏状态。
需要注意的是,我们没有将gameStatus作为输入,也从未更改magicNumber的值。所以,我们可以考虑从参数中移除gameStatus,以及从返回值中移除magicNumber。除非这是游戏的最终版本,并且必须减少计算复杂性,否则这并不是一个好主意。也许在游戏的未来改进中,我们需要根据gameStatus更新游戏,或者改变magicNumber的值。从设计的角度来看,当前的输入和输出定义是稳健的,没有改变的理由。
猜数字游戏: render()函数
render()函数用于显示当前的游戏状态。它应该无论发生什么都能正常工作,始终清晰地展示游戏的情况:
def render(gameStatus,magicNumber):
if gameStatus == "win":
print("This is correct! You win!")
elif gameStatus == "end":
print("Bye!")
elif gameStatus == "lower":
print("The magic number is lower")
elif gameStatus == "higher":
print("The magic number is higher")
else:
raise RuntimeError("Unexpected game status {}".format(gameStatus))
这个函数的输入是游戏状态,没有输出。过程很简单:根据gameStatus的值显示相应的消息。
需要注意的是,我们还处理了gameStatus具有意外值的情况。这是一个好习惯,它在你更新游戏并忘记更新某些部分时非常有帮助。
猜数字游戏:runGame()函数
def runGame():
gameStatus, magicNumber = init()
while gameStatus != "win" and gameStatus != "end":
playerNumber = processInput()
gameStatus, magicNumber = update(gameStatus,magicNumber,playerNumber)
render(gameStatus,magicNumber)
你可以看到流程如下:
- init()函数返回一个初始的游戏状态;
- processInput()函数从玩家那里收集指令;
- update()函数使用指令来更新游戏状态;
- render()函数显示游戏状态。
猜数字游戏:最终代码
# Import the random package
import random
def init():
"""
Initialize game
Outputs:
* gameStatus
* magicNumber
"""
# Generate a random Magic number
return None, random.randint(1,10)
def processInput():
"""
Handle player's input
Output:
* playerNumber: the number entered by the player, or None if the player wants to stop the game
"""
while True:
# Player input
word = input("What is the magic number? ")
# Quit if the player types "quit"
if word == "quit":
return None
# Int casting with exception handling
try:
playerNumber = int(word)
break
except ValueError:
print("Please type a number without decimals!")
continue
return playerNumber
def update(gameStatus,magicNumber,playerNumber):
"""
Update game state
Inputs:
* gameStatus: the status of the game
* magicNumber: the magic number to find
* playerNumber: the number entered by the player
Output:
* gameStatus: the status of the game
* magicNumber: the magic number to find
"""
if playerNumber is None:
gameStatus = "end"
elif playerNumber == magicNumber:
gameStatus = "win"
elif magicNumber < playerNumber:
gameStatus = "lower"
elif magicNumber > playerNumber:
gameStatus = "higher"
return gameStatus, magicNumber
def render(gameStatus,magicNumber):
"""
Render game state
Input:
* gameStatus: the status of the game, "win", "end", "lower" or "higher"
"""
# Cases
if gameStatus == "win":
print("This is correct! You win!")
elif gameStatus == "end":
print("Bye!")
elif gameStatus == "lower":
print("The magic number is lower")
elif gameStatus == "higher":
print("The magic number is higher")
else:
raise RuntimeError("Unexpected game status {}".format(gameStatus))
def runGame():
gameStatus, magicNumber = init()
while gameStatus != "win" and gameStatus != "end":
playerNumber = processInput()
gameStatus, magicNumber = update(gameStatus,magicNumber,playerNumber)
render(gameStatus,magicNumber)
# Launch the game
runGame()
游戏循环模式有助于帮助我们合理组织代码,适应更复杂的程序设计!!