我正在开发一个跨平台、独立于实现的Python库,以使RPG风格的游戏开发更容易,或者至少更容易访问。
此代码设计为在Python3.6及更高版本上运行,并且已经用mypy和pytest进行了测试。由于我不知道如何用typing.NewType实现这些类,所以它目前缺乏任何自定义类型。我不打算支持早期版本的Python。我不打算将这个库附加到任何特定的游戏库中,比如Pygame或PySDL2,尽管这可能会发生变化。
所有数据目前都存储在JSON文件中;我最初使用的是XML,然后是TOML,但随着库的不断增长,我决定只使用JSON,因为很明显,我实现这些项目的方式变得非常困难,很难以一种简洁的方式表示为这些格式中的任何一种。
示例项目如下所示:
"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是一个字符串,提供有关项目的信息(如果不是堆栈的话)或包含两个字符串的列表,一个字符串包含信息,另一个字符串具有当前的数量(如果是可堆叠的),检查取决于堆栈上的项目数量而改变。
还有一些可选的参数,如atk和def,这些参数可以在配备时提高统计量。combine是将项与其他项组合以创建新项的可能方法列表;所使用的数字都是其他所有项is,最后一个项是最终项。
我只想要关于我的实现的反馈,例如;它足够可读性吗?有足够的评论吗?从这样的代码中分离项目数据是个好主意吗?用混音器是不是个坏主意?
以下是我的实现:
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由于代码中引用了它们,下面是DataFileMixin和ReprMixin的实现:
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})"下面是在一个单独的配置文件中定义的全局常量:
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}")发布于 2018-01-07 21:24:28
使用混合器是好的的想法吗?
是的,它有助于单一的责任。它让混音类做好一件事。
将数据与代码分离是件好事,JSON非常方便。但它不支持评论。考虑与https://pypi.python.org/pypi/jsoncomment/0.2.2集成
项目0(硬币)似乎缺少一个数量属性。在其格式字符串中,{}可能是{quantity}。在第1项中,+5攻击和+3剑防御是足够清晰的,但我希望能对“合并”一词发表评论。你随同的散文描述很有帮助。在name中对非专有名词项使用大写或混合大小写似乎很奇怪。
EQUIPMENT = ('weapon',
'head',
'chest',
'legs',
'off-hand',)后面的逗号是好的,但不要紧跟在后面,把帕伦放在自己的线上。尾随逗号的目的是尽量减少源更新的差异。
看来设备想要成为Enum。
"""
Initiates an Item object不,这不是一个有用的评论。从签名上我们已经知道我们在一辆警车里。在一行中,您应该告诉我们一个项目是什么秘密,它承担了什么单独的责任,这样其他代码就不必这样做了。
Arguments:
- id_num: a unique integer representing the item to be created. Required.这个评论是好的,但考虑删除它,因为它没有说除了签名已经告诉我们。标识符id_num是一个深思熟虑的标识符,它试图为Gentle拼写它是数字的。但这是通常的情况,签名有助于说明它是int的。考虑将名称重命名为id。
好的,我在上面建议了数量,我看你已经决定了更短的数量,这很酷。
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,并给出默认值呢?
file=kwargs.get('file', ITEM_FILE)如果签名使用了标准的file成语,那么这个表达式与简单的def __init__(self, id: int, file=ITEM_FILE, **kwargs)是多余的。
我发现堆叠的评论有点令人惊讶,因为我想到“可堆叠”是用来制作硬币项目和钞票(或珠宝)项目。这一评论表明,可堆叠与描述和玩家动词相关,这些动词对某些项目有效。
元注释是没有帮助的,因为“描述项的元数据”使我无法更好地理解什么是有效的,它将意味着什么。考虑使用更详细的metadata。当然可以考虑在签名中添加metadata=None。
item_data = self.get_item_by_ID("_data“部分含糊不清,毫无帮助。请叫它item。
self.ID = int(id_num)这不是一个显式常量,所以pep8要求您拼写它self.id。
self.slot = item_data['type']slot是一个非常好的标识符,但我不明白为什么我们要无缘无故地重命名。如果type不正确的话,那就应该是slot了。在这里,self.type_似乎是更自然的标识符(因为python已经定义了type)。
#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这意味着当前类可能被称为BaseItem,而WearableItem可能继承它,并保存攻击/防御代码。StackableItem默认为count=1。
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属于签名。
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就会抛出。
if self._count >= ITEM_MAX_COUNT:最大计数表示对格式良好的项的限制。考虑定义一个单行谓词助手,可能是is_a_lot()或is_large_amount(),以控制使用哪种替代描述。
""" Contains methods for getting game data from files """耶!这是一个有益的评论。
if file_format == "json":考虑删除该参数,只需询问if file.endswith('.json')。
各种获取器看起来都是多余的。调用者可以简单地传递一个字符串或Enum。您需要一个从Enum映射到文件名的dict,或者一个本身可以返回正确文件名的Enum。文档字符串可能会被省略,因为它们只告诉我们签名所表示的内容。
发布于 2018-01-07 21:15:19
我不太清楚您实现__gt__和__lt__方法的目的是什么。这仅仅是为了对项目列表进行排序(按某个任意数字,ID)吗?
我认为更有帮助的地方(或者是除此之外,或者是它本身)是能够比较相同项的堆栈大小。这样的话,player1.gold > player2.gold就有意义了(两个播放器都有gold项,但堆栈大小不同)。
您还应该考虑一下,等式是否也应该测试这一点。换句话说,一枚金币和一千枚金币是否相同?无论您对这个问题的答案是什么,您都应该向类的用户提供这两种比较方法(并为__eq__选择一种)。
你的DataFileMixin上的另一张便条。当您访问一个元素时,您当前正在读取整个数据文件。它将更快(特别是当文件增长,它会,如果你真的用这个实现一个游戏)缓存文件内容和服务的元素。当然,这需要将数据放入内存中,但是当前的实现也是如此。
您可能需要添加一个检查,查看自上次读取后文件是否已被修改(当然,在这种情况下,您必须再次读取它),可能需要使用修改时间。例如,如果用户更改系统时间或在时区或其他类似区域之间进行更改,但应该足够好,这可能是不可靠的。
如果该文件不再适合内存,则必须按顺序读取该文件,直到找到要查找的元素为止,该元素将再次返回到每次访问时读取该文件(但仍然应该缓存已查找过一次的元素)。
https://codereview.stackexchange.com/questions/184514
复制相似问题