Tech, Bio & Photography

Python TRPG制作实录 - EP.2 游戏大厅界面

果然换manjaro之后开发效率高多了,心无杂念。


这一期我们准备建设比较粗犷的部分——游戏大厅界面,这部分的代码对应源代码中 run.py 的部分。为了不让以前的代 (shi) 码 (shan) 影响我们的思路,现在笔者将旧的代码全部删去。

照例先起个头,装作“万事开头难”的“开头”已经结束了 (自欺欺人+1)

# -*- coding: utf-8 -*-


"""
    Project: Continuous Infinity
    File: src/run.py
    Author: Little_Ye233
    Created: 2021-07-05
    Description: Main run shell.
"""

Part I. 欢迎界面

除去初始化的等待界面,玩家游玩游戏见到的第一个界面多半就是游戏大厅界面了,这也很大程度上决定了玩家的第一印象。不过我们这里只是简单的制作一个可以称得上 TRPG 的东西,所以也没必要搞那么多花里胡哨的,直接上一个简朴的界面:

# -*- coding: utf-8 -*-


"""
    Project: Continuous Infinity
    File: src/game/system/scenes/lobby.py
    Author: Little_Ye233
    Created: 2021-07-05
    Description: Game algorithms of lobby scene.
"""


from ..constants import *


class Lobby(object):
    @staticmethod
    def show():
        print(SYSTEM_SCENES_LOBBY_WELCOME)
# -*- coding: utf-8 -*-


"""
    Project: Continuous Infinity
    File: src/game/system/constants.py
    Author: Little_Ye233
    Created: 2021-07-05
    Description: Definitions of game constants.
"""


# System
version = '0.1.0'

 
# Literal strings
SYSTEM_SCENES_LOBBY_WELCOME = f'''\
-*-*- Continuous Infinity -*-*-
-*- Ver {version} -*-
-*- Author: Little_Ye233 -*-'''

嗯嗯嗯?!为什么多了个 lobby.pyconstants.py ?还有原来的 system.py 为什么变成了一个同名的文件夹?

其实这个是笔者先前开发项目时的常有情况——每每做出了一定的规划,最后总会在某个地方需要修改原先的架构。这里也是模仿当时的情况做出的——在实际情况下考虑到单个文件 system.py 放置所有的逻辑可能会导致文件过于冗杂,故分离出多个子文件 (夹) 。其实这里我还埋伏了一手,就是 constants.py 。我们看到这里定义常量的时候我的命名方式是“源文件名 (下划线作文件路径分隔符) +其他标识符”,这也是一种冗长的写法,但笔者先暂时这么写,如果后期发现其他方式命名起来更容易,就尝试进一步修改。

先看这里的 src/game/system/constants.py 定义的内容。第一段 System 下定义的是版本号,变量名定义采用了许多著名模块中的 version (这里注意到变量名称是全部小写) 模块级变量定义方式,其中的字符串遵循“Semantic Versioning” (语义化版本) 中的规定。第二段下定义了一个游戏大厅的欢迎界面,注意到这里利用了 Python 3.6 下引入的字符串格式化输出方式“f-string”。

Part II. 选项

有了欢迎界面自然也要有选择的部分。选项的设计可以有简单的两种:第一种是给出所有可选项并标号,玩家自行输入标号;第二种是给出所有可选项,玩家通过方向键控制高亮选项。当然这两种也可以继续细分,实现选项的方式也不止这两种,甚至可以几种方式结合。我们这里为了简便,就采用第一种方式——不过看起来可能比较复杂就是了:

from ..widgets import Options

class Lobby:

    @staticmethod
    def show():
        print(LOBBY_WELCOME)
        cmd = Options(0, [
            START_GAME,
            SELECT_LEVEL,
            SETTINGS,
            CREDITS,
            EXIT_GAME
        ]).show()

这是 src/game/system/scenes/lobby.py 的一部分定义。明显发现原来很长的常量名变成了直接表义的名字 LOBBY_WELCOME ,一方面是因为实在太长打起来很累 (终于悔改了) ,另一方面是因为使用只表义但不包含文件路径信息的记法便于在不同位置使用相同的字符串 (比如可能很多地方都会用到“开始游戏”之内的字符串) 。而这里我们又引入了一个新的概念 (坑) ——组件 (Widget) ,以将游戏设计中出现的各种小程序片段统一化。这里我们构造出了第一种组件——选项 (Options) ,来为玩家提供一个简单的选择系统。对于实现的细节我们先按下不表,只需能大致了解它实现出的效果即可:

In [1]: from src.game.system.widgets import Options

In [2]: cmd = Options('0', ['Option 1', 'Option 2', 'Option 3']).show()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-6f1c48425fd6> in <module>
----> 1 cmd = Options('0', ['Option 1', 'Option 2', 'Option 3']).show()

~/Documents/code/Python/rpg/ContinuousInfinity/src/game/system/widgets/options/main.py in __init__(self, style, *params)
     38         # CAUTIONS: now `style` can be 0 only
     39         if style not in self._avail_style:
---> 40             raise ValueError(f'style ID {style} is unavailable')
     41
     42         self.style = style

ValueError: style ID 0 is unavailable

In [3]: cmd = Options(0, ['Option 1', 'Option 2', 'Option 3']).show()
Choices:
(1) Option 1
(2) Option 2
(3) Option 3
Please choose: a
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-3-c0fc1f068dc0> in <module>
----> 1 cmd = Options(0, ['Option 1', 'Option 2', 'Option 3']).show()

~/Documents/code/Python/rpg/ContinuousInfinity/src/game/system/widgets/options/main.py in show(self)
     51                     'src.game.system.widgets.options.styles'
     52                 )
---> 53         return style.Style.show(*self.params)

~/Documents/code/Python/rpg/ContinuousInfinity/src/game/system/widgets/options/styles/style0.py in show(*params)
     22         s = input('Please choose: ')
     23         if not s.isdigit() or int(s) == 0 or int(s) > len(choices):
---> 24             raise ValueError(f'invalid input value: {s}')
     25         return int(s)

ValueError: invalid input value: a

In [4]: cmd = Options(0, ['Option 1', 'Option 2', 'Option 3']).show()
Choices:
(1) Option 1
(2) Option 2
(3) Option 3
Please choose: 0
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-c0fc1f068dc0> in <module>
----> 1 cmd = Options(0, ['Option 1', 'Option 2', 'Option 3']).show()

~/Documents/code/Python/rpg/ContinuousInfinity/src/game/system/widgets/options/main.py in show(self)
     51                     'src.game.system.widgets.options.styles'
     52                 )
---> 53         return style.Style.show(*self.params)

~/Documents/code/Python/rpg/ContinuousInfinity/src/game/system/widgets/options/styles/style0.py in show(*params)
     22         s = input('Please choose: ')
     23         if not s.isdigit() or int(s) == 0 or int(s) > len(choices):
---> 24             raise ValueError(f'invalid input value: {s}')
     25         return int(s)

ValueError: invalid input value: 0

In [5]: cmd = Options(0, ['Option 1', 'Option 2', 'Option 3']).show()
Choices:
(1) Option 1
(2) Option 2
(3) Option 3
Please choose: 1

In [6]: cmd
Out[6]: 1

这里我们可以看到, Options 类能够根据给定参数产生一个实例,而这个实例的 show 方法则能在默认输出设备——也就是终端 (terminal) ——其实这里我预感没有加上 IO 设备的参数后期会出麻烦,但现在暂时先这么写着。然后如果交互过程没有异常的话,这个方法会将玩家选择的选项的编号 (在当前的 style 中,必定为一个正整数) 作为返回值。依此我们可以编写出如下的游戏大厅页面场景 (scene) 代码:

from ..constants import *
from ..widgets import Options


def start_game():
    print('start_game: Not implemented yet!')

def select_level():
    print('select_level: Not implemented yet!')

def settings():
    print('settings: Not implemented yet!')

def show_credits():
    print(CREDITS_MSG)

def exit_game():
    print(LOBBY_EXIT)
    exit(0)


actions = [None, start_game, select_level, settings, show_credits, exit_game]


class Lobby:
    
    @staticmethod
    def show():
        print(LOBBY_WELCOME)

        while True:
            try:
                cmd = Options(0, [
                    START_GAME,
                    SELECT_LEVEL,
                    SETTINGS,
                    CREDITS,
                    EXIT_GAME
                ]).show()
            except ValueError:  # wrong `cmd`
                print()
                print(WRONG_CMD)
            else:
                print()
                actions[cmd]()
            print()

一些常量的定义暂时忽略。最终能得到如下的测试结果:

$ python src/run.py
-*-*- Continuous Infinity -*-*-
-*- Ver 0.1.0 -*-
-*- Author: Little_Ye233 -*-
Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 0

Got a wrong choice. Please try again.

Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 1

start_game: Not implemented yet!

Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 2

select_level: Not implemented yet!

Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 3

settings: Not implemented yet!

Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 4

-*-*- Credits -*-*-
Author: Little_Ye233

Choices:
(1) Start Game
(2) Select a Level
(3) Settings
(4) Credits
(5) Exit Game
Please choose: 5

Goodbye!

【完】

1 Like