首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >角色扮演游戏的项实现

角色扮演游戏的项实现
EN

Code Review用户
提问于 2018-01-07 15:43:17
回答 2查看 1.2K关注 0票数 9

我正在开发一个跨平台、独立于实现的Python库,以使RPG风格的游戏开发更容易,或者至少更容易访问。

此代码设计为在Python3.6及更高版本上运行,并且已经用mypy和pytest进行了测试。由于我不知道如何用typing.NewType实现这些类,所以它目前缺乏任何自定义类型。我不打算支持早期版本的Python。我不打算将这个库附加到任何特定的游戏库中,比如Pygame或PySDL2,尽管这可能会发生变化。

所有数据目前都存储在JSON文件中;我最初使用的是XML,然后是TOML,但随着库的不断增长,我决定只使用JSON,因为很明显,我实现这些项目的方式变得非常困难,很难以一种简洁的方式表示为这些格式中的任何一种。

示例项目如下所示:

代码语言:javascript
复制
"0": {
  "name": "Coins",
  "type": "item",
  "stackable": true,
  "examine": [
    "Lovely money!",
    "A stack of {} coins."
  ]
},
"1": {
  "name": "Wooden sword",
  "type": "weapon",
  "examine": "A dull wooden sword.",
  "atk": 5,
  "def": 3,
  "combine": [
    [2, 5]
  ]
},

其中,项ID用作每个项的密钥。name表示项目在游戏中的名称,type (如果有的话),examine是一个字符串,提供有关项目的信息(如果不是堆栈的话)或包含两个字符串的列表,一个字符串包含信息,另一个字符串具有当前的数量(如果是可堆叠的),检查取决于堆栈上的项目数量而改变。

还有一些可选的参数,如atkdef,这些参数可以在配备时提高统计量。combine是将项与其他项组合以创建新项的可能方法列表;所使用的数字都是其他所有项is,最后一个项是最终项。

我只想要关于我的实现的反馈,例如;它足够可读性吗?有足够的评论吗?从这样的代码中分离项目数据是个好主意吗?用混音器是不是个坏主意?

以下是我的实现:

代码语言:javascript
复制
class Item(ReprMixin, DataFileMixin):

    """ Class for generating item objects; used by Inventory and Player """

    EQUIPMENT = ('weapon',
                 'head',
                 'chest',
                 'legs',
                 'off-hand',)

    def __init__(self, id_num: int, **kwargs) -> None:

        """
        Initiates an Item object

        Arguments:
        - id_num: a unique integer representing the item to be created. Required.

        Optional keyword arguments:
        - file: name of, or path to, a file from which the item data is gathered.
                Defaults to the ITEM_FILE constant.
        - count: for a stackable item, sets how many items are on the stack
        - meta: metadata describing the item, defaults to None
        """

        # Get item data with DataFileMixin.get_item_by_ID()
        item_data = self.get_item_by_ID(
            id_num,
            file=kwargs.get('file', ITEM_FILE)
        )

        # Basic attributes every item has defined
        self.ID = int(id_num)
        self.name = item_data['name']
        self.slot = item_data['type']
        self.descriptions = item_data['examine']
        #NOTE: The item's actual description
        #is defined in the Item.description property!
        #This is due to the distinction between
        #normal and stackable items.

        # Attributes exclusive to wearable items
        if self.slot in self.EQUIPMENT:
            self.attack = item_data.get('atk', None)
            self.defence = item_data.get('def', None)
            self.specialAttack = item_data.get('specialAttack', None)

        # Miscellaneous optional attributes
        self.stackable = item_data.get('stackable', False)
        self.combinations = item_data.get('combine', None)

        self.metadata = kwargs.get('meta', None)
        if self.stackable:
            self._count = kwargs.get('count', 1)

    def __eq__(self, item):
        """ Compares the ID and metadata values of two items """
        return self.ID == item.ID and self.metadata == item.metadata

    def __lt__(self, item):
        try:
            return int(self.ID) < int(item.ID)
        except ValueError:
            if self.ID.isdigit() and not item.ID.isdigit():
                return True
            elif self.ID.isdigit():
                return self.ID < item.ID
            return False

    def __gt__(self, item):
        try:
            return int(self.ID) > int(item.ID)
        except ValueError:
            if self.ID.isdigit() and not item.ID.isdigit():
                return False
            elif self.ID.isdigit():
                return self.ID > item.ID
            return True

    @property
    def description(self):
        if not self.stackable:
            return self.descriptions
        examine = self.descriptions[0]
        if self._count >= ITEM_MAX_COUNT:
            examine = self.descriptions[1].format(self._count)
        return examine

由于代码中引用了它们,下面是DataFileMixinReprMixin的实现:

代码语言:javascript
复制
from abc import ABCMeta

class DataFileMixin(metaclass=ABCMeta):
    """ Contains methods for getting game data from files """

    @staticmethod
    def _get_by_ID(ID: int, obj_type: str, file: str, file_format=DATA_FORMAT) -> dict:
        """ 'Low-level' access to filedata """
        with open(file) as f:
            if file_format == "json":
                data = json.load(f, parse_int=int, parse_float=float)
            elif file_format == "toml":
                data = toml.load(f)
            else:
                raise NotImplementedError(f"Missing support for opening files of type: {file_format}")
        return data[obj_type][str(ID)]

    def get_item_by_ID(self, ID: int, file=ITEM_FILE) -> dict:
        """ Returns a dictionary representation of a given item ID """
        return self._get_by_ID(ID, 'items', file)

    def get_enemy_by_ID(self, ID: int, file=ENEMY_FILE) -> dict:
        """ Returns a dictionary representation of a given enemy ID """
        return self._get_by_ID(ID, 'enemies', file)

    def get_npc_by_ID(self, ID: int, file=NPC_FILE) -> dict:
        """ Returns a dictionary representation of a given NPC ID """
        return self._get_by_ID(ID, 'NPCs', file)

    def get_entity_by_ID(self, ID: int, file=ENTITY_FILE) -> dict:
        """ Returns a dictionary representation of a given entity ID """
        return self._get_by_ID(ID, 'entities', file)


class ReprMixin(metaclass=ABCMeta):
    """ Automatically generates a __repr__-method for any class """

    def __repr__(self):
        """ Automatically generated __repr__-method """

        attributes = [f"{key}={value}" if type(value) != str
                     else f'{key}="{value}"'
                     for key, value in vars(self).items()]
        v_string = ", ".join(attributes)
        class_name = self.__class__.__name__
        return f"{class_name}({v_string})"

下面是在一个单独的配置文件中定义的全局常量:

代码语言:javascript
复制
from pathlib import Path

DATA_DIR = Path(__file__).parent / "data"
DATA_FORMAT = "json"
ITEM_MAX_COUNT = 10**5 #Used to change item description

ITEM_FILE = str(DATA_DIR / f"items.{DATA_FORMAT}")
ENTITY_FILE = str(DATA_DIR / f"entities.{DATA_FORMAT}")
ENEMY_FILE = str(DATA_DIR / f"enemies.{DATA_FORMAT}")
NPC_FILE = str(DATA_DIR / f"npcs.{DATA_FORMAT}")
QUEST_FILE = str(DATA_DIR / f"quests.{DATA_FORMAT}")
OBJECT_FILE = str(DATA_DIR / f"objects.{DATA_FORMAT}")
EN

回答 2

Code Review用户

回答已采纳

发布于 2018-01-07 21:24:28

使用混合器是好的的想法吗?

是的,它有助于单一的责任。它让混音类做好一件事。

将数据与代码分离是件好事,JSON非常方便。但它不支持评论。考虑与https://pypi.python.org/pypi/jsoncomment/0.2.2集成

项目0(硬币)似乎缺少一个数量属性。在其格式字符串中,{}可能是{quantity}。在第1项中,+5攻击和+3剑防御是足够清晰的,但我希望能对“合并”一词发表评论。你随同的散文描述很有帮助。在name中对非专有名词项使用大写或混合大小写似乎很奇怪。

代码语言:javascript
复制
EQUIPMENT = ('weapon',
             'head',
             'chest',
             'legs',
             'off-hand',)

后面的逗号是好的,但不要紧跟在后面,把帕伦放在自己的线上。尾随逗号的目的是尽量减少源更新的差异。

看来设备想要成为Enum

代码语言:javascript
复制
    """
    Initiates an Item object

不,这不是一个有用的评论。从签名上我们已经知道我们在一辆警车里。在一行中,您应该告诉我们一个项目是什么秘密,它承担了什么单独的责任,这样其他代码就不必这样做了。

代码语言:javascript
复制
    Arguments:
    - id_num: a unique integer representing the item to be created. Required.

这个评论是好的,但考虑删除它,因为它没有说除了签名已经告诉我们。标识符id_num是一个深思熟虑的标识符,它试图为Gentle拼写它是数字的。但这是通常的情况,签名有助于说明它是int的。考虑将名称重命名为id

好的,我在上面建议了数量,我看你已经决定了更短的数量,这很酷。

代码语言:javascript
复制
    Optional keyword arguments:
    - file: name of, or path to, a file from which the item data is gathered.
            Defaults to the ITEM_FILE constant.
    - count: for a stackable item, sets how many items are on the stack
    - meta: metadata describing the item, defaults to None

文件评论是曲折的,而且太长了。说它是项目数据的文件,并完成它。或者具体一点,并将其确定为JSON或其他什么。这句默认的话很奇怪。为什么,为什么我们不在签名中声明file,并给出默认值呢?

代码语言:javascript
复制
        file=kwargs.get('file', ITEM_FILE)

如果签名使用了标准的file成语,那么这个表达式与简单的def __init__(self, id: int, file=ITEM_FILE, **kwargs)是多余的。

我发现堆叠的评论有点令人惊讶,因为我想到“可堆叠”是用来制作硬币项目和钞票(或珠宝)项目。这一评论表明,可堆叠与描述和玩家动词相关,这些动词对某些项目有效。

元注释是没有帮助的,因为“描述项的元数据”使我无法更好地理解什么是有效的,它将意味着什么。考虑使用更详细的metadata。当然可以考虑在签名中添加metadata=None

代码语言:javascript
复制
    item_data = self.get_item_by_ID(

"_data“部分含糊不清,毫无帮助。请叫它item

代码语言:javascript
复制
    self.ID = int(id_num)

这不是一个显式常量,所以pep8要求您拼写它self.id

代码语言:javascript
复制
    self.slot = item_data['type']

slot是一个非常好的标识符,但我不明白为什么我们要无缘无故地重命名。如果type不正确的话,那就应该是slot了。在这里,self.type_似乎是更自然的标识符(因为python已经定义了type)。

代码语言:javascript
复制
    #NOTE: The item's actual description
    #is defined in the Item.description property!
    #This is due to the distinction between
    #normal and stackable items.

你把我弄丢了。“描述”的语义学看起来很简单,但现在我们已经把水弄混了。

代码语言:javascript
复制
    # Attributes exclusive to wearable items

这意味着当前类可能被称为BaseItem,而WearableItem可能继承它,并保存攻击/防御代码。StackableItem默认为count=1。

代码语言:javascript
复制
        self.specialAttack = item_data.get('specialAttack', None)

可以任意定义JSON属性拼写,但pep8要求将其分配给self.special_attack。请运行flake8并遵循它的建议。

item_data.get('stackable', False)item_data.get('combine', None)表达式表明stackable=False, combine=None属于签名。

代码语言:javascript
复制
def __eq__(self, item):
    """ Compares the ID and metadata values of two items """
    return self.ID == item.ID and self.metadata == item.metadata

我一点也不明白。我们可以有一个带有空元数据的项目7,也可以有一个带有foo=bar的项目7?这太疯狂了。一个id应该唯一地标识这个东西。也许我们所拥有的更像是一个SKU或一个part_number?

我也不明白它和gt,因为一个格式良好的项目总是有一个数字id。也许这并不总是正确的,而且我们看到了一些旧的代码库的残余物应该被编辑?考虑将其添加到项ctor:assert 0 + id == id中,这样尝试使用字符串id就会抛出。

代码语言:javascript
复制
    if self._count >= ITEM_MAX_COUNT:

最大计数表示对格式良好的项的限制。考虑定义一个单行谓词助手,可能是is_a_lot()is_large_amount(),以控制使用哪种替代描述。

代码语言:javascript
复制
""" Contains methods for getting game data from files """

耶!这是一个有益的评论。

代码语言:javascript
复制
        if file_format == "json":

考虑删除该参数,只需询问if file.endswith('.json')

使用lru_缓存对每个解析的JSON文件进行回忆录

各种获取器看起来都是多余的。调用者可以简单地传递一个字符串或Enum。您需要一个从Enum映射到文件名的dict,或者一个本身可以返回正确文件名的Enum。文档字符串可能会被省略,因为它们只告诉我们签名所表示的内容。

票数 3
EN

Code Review用户

发布于 2018-01-07 21:15:19

我不太清楚您实现__gt____lt__方法的目的是什么。这仅仅是为了对项目列表进行排序(按某个任意数字,ID)吗?

我认为更有帮助的地方(或者是除此之外,或者是它本身)是能够比较相同项的堆栈大小。这样的话,player1.gold > player2.gold就有意义了(两个播放器都有gold项,但堆栈大小不同)。

您还应该考虑一下,等式是否也应该测试这一点。换句话说,一枚金币和一千枚金币是否相同?无论您对这个问题的答案是什么,您都应该向类的用户提供这两种比较方法(并为__eq__选择一种)。

你的DataFileMixin上的另一张便条。当您访问一个元素时,您当前正在读取整个数据文件。它将更快(特别是当文件增长,它会,如果你真的用这个实现一个游戏)缓存文件内容和服务的元素。当然,这需要将数据放入内存中,但是当前的实现也是如此。

您可能需要添加一个检查,查看自上次读取后文件是否已被修改(当然,在这种情况下,您必须再次读取它),可能需要使用修改时间。例如,如果用户更改系统时间或在时区或其他类似区域之间进行更改,但应该足够好,这可能是不可靠的。

如果该文件不再适合内存,则必须按顺序读取该文件,直到找到要查找的元素为止,该元素将再次返回到每次访问时读取该文件(但仍然应该缓存已查找过一次的元素)。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/184514

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档