我正在重新开始一个旧的个人项目,其中涉及用于 DnD 等 TTRPG 的骰子计算器。我希望使用 python Enum 对象作为预定义几种常见滚动分布的方法,例如直接 d20 滚动、优点、缺点、精灵精度等。我如何生成这些的确切细节并不重要,但我有一些代码看起来像这样:
class d20(Enum):
NORMAL = d(20)
ADVANTAGE = d(20).advantage()
DISADVANTAGE = d(20).disadvantage()
ELVEN_ACCURACY = intdist.max(d(20).advantage(), d(20))
所以我们有这个枚举,我将它用于关联的 AttackRoll 类。这是我的攻击掷骰类的一小部分的一些伪代码:
class AttackRoll:
def __init__(self, attack_modifier, crit_range = 20):
self.crit_chances = [] # some kind of container, this is related to what I'm asking about
for distribution in d20:
self.crit_chances.add(distribution, crit_chance(distribution, crit_range))
def get_crit_chance(self, d20_enum):
return self.crit_chances[d20_enum]
在这里,我本质上想做的是将致命一击机会与我的枚举中定义的每种 d20 骰子关联起来。但问题是,我很难想出一种在 get 函数中回忆这些信息的好方法,该方法既快速又感觉“Pythonic”,我将在稍后详细说明。
与它们所使用的方法相比,其中一些派生属性具有相当大的计算开销(不是暴击机会,但我需要的其他属性肯定会成为速度瓶颈),所以我确实想缓存这些常用的属性*。
我能做的一件事是使用一个字典,其中枚举对象作为键,值作为派生属性。这样,一旦创建了 AttackRoll 对象,调用暴击机会的开销应该是 O(1),这非常好。但现在,我失去了“鸭子类型”,因为这些函数原则上可以适用于任何类似 d20 的发行版,而不仅仅是我的 d20 枚举类中的发行版。以这种方式实现就等于说我的函数仅适用于我定义的 d20 发行版。想要使用不同的用户是 SOL。
作为新手,我认为一个很好的折衷方案是使用字典,但使用 try/ except 子句,如果提供的分布超出我的 d20 枚举范围,该子句将运行必要的更高开销的函数。所以也许像这样的伪代码:
class AttackRoll:
def __init__(self, attack_modifier, crit_range = 20):
self.crit_chances = dict()
for distribution in d20:
self.crit_chances[distribution] = crit_chance(distribution, crit_range)
def get_crit_chance(self, d20_enum):
try:
return self.crit_chances[d20_enum]
except KeyError:
if is_d20_roll_like(d20_enum):
return crit_chance(distribution, crit_range)
raise ValueError(...)
我是否遗漏了一些明显更好的东西?我非常喜欢这种方法,因为如果我想添加更多种类的 d20 卷,我只需将它们的整数分布对象添加到我的枚举中,一切都会按照我想要的方式工作。 (实际上,我一开始就忘记了精灵的准确性,而且我最初使用的方法不容易扩展......我在 AttackRoll 类中构建了不同类型的 d20 卷,并使用 d20 枚举作为人类可读数组索引的代理...这种认识是我写这个问题的动机)。它也以鸭子类型的方式工作,因为任何看起来像 d20 发行版的东西也可以工作。我天真地认为它很好,但想在围绕这种方法构建所有内容之前检查一下,并意识到有一些容易看到的问题将成为 PITA 稍后修复(我似乎遇到了很多)
*作为第二部分的问题,我听说过早的优化是万恶之源。我提到“设置”方法将成为该攻击类的许多应用程序的速度瓶颈。但归根结底,您真正要做的就是在最坏情况下(对于我要缓存的更复杂的位)将大约 200 微秒添加到 10 微秒的函数调用中。在实践中这些函数可能最多被调用几千次。我一开始就应该担心这个吗?
我不太清楚你想做的事情的逻辑。 通常,枚举保存标识符。 您对
d20
的实现看起来更像是一个查找表。 相反,枚举应该简单地保存任何骰子的枚举类型。 如果每个攻击检定都是一个事件,那么攻击检定应包含该检定的修正值。
from enum import Enum, auto
class RollModifier(Enum):
NORMAL = auto()
ADVANTAGE = auto()
DISADVANTAGE = auto()
ELVEN_ACCURACY = auto()
class Roll:
def __init__(
self,
die: int=20,
modifier: RollModifier=RollModifier.NORMAL,
crit_range: int=20
) -> None:
"""
Creates an die roll event.
"""
self.die = die
self.modifier = modifier
self.crit_range = crit_range
if crit_range > die:
raise ValueError('Crit range cannot be greater than the die')
def get_crit_chance(self, d20_enum):
# handle the logic for each enum here.
class AttackRoll(Roll):
...
class DefenseRoll(Roll):
...
class DamageRoll(Roll):
...
class SaveRoll(Roll):
...