20230616223234-header.png

现在是时候来看我们的第一个设计模式了:游戏循环模式(Game Loop Pattern)!这个模式可以为我们提供许多很好的思路,以非常有效的方式重构我们的游戏

游戏循环模式

游戏循环模式有几个不同的版本。这里我介绍一个简单的情况,使用单线程。以下5个函数组成了这个模式:
20230616235132-08_gameloop.png

  • 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()

游戏循环模式有助于帮助我们合理组织代码,适应更复杂的程序设计!!

标签: none