以易于理解的方式利用Python的“枚举”作为标志的正确方法

问题描述 投票:0回答:1

我正在重新开始一个旧的个人项目,其中涉及用于 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 微秒的函数调用中。在实践中这些函数可能最多被调用几千次。我一开始就应该担心这个吗?

python enums
1个回答
0
投票

我不太清楚你想做的事情的逻辑。 通常,枚举保存标识符。 您对

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):
    ...
© www.soinside.com 2019 - 2024. All rights reserved.