为玩家创造效果?

Creating effects for a player?

我会尽量保持简短:

这是我的第一印象:

class Player(game.PlayerEntity):
    def __init__(self):
        super().__init__()
        self._effects = []

    def add_effect(self, effect, duration=None):
        self.effects.append(effect)
        if duration is not None:
            delayed(duration, self.remove_effect, effect)
        self._apply_effects()

    def remove_effect(self, effect):
        if effect in self.effects:
            self.effects.remove(effect)
        self._apply_effects()

    def _apply_effects(self):
        if 'freeze' in self.effects:
            self.move_type = MoveTypes.NONE
        elif 'fly' in self.effects:
            self.move_type = MoveTypes.FLY
        else:
            self.move_type = MoveTypes.WALK
        if 'burn' in self.effects:
            self.ignite()
        else:
            self.extinguish()

    freeze = lambda self, duration: self.add_effect('freeze', duration)
    fly = lambda self, duration: self.add_effect('fly', duration)
    burn = lambda self, duration: self.add_effect('burn', duration)

不过,我想让它更灵活,例如有一个 Effect class.

我遇到的几个问题:

  1. Player_effects 列表并调用 self._effects[0].apply(self, duration),或者 Effect class 有 player 属性并调用它自己的属性apply(duration)?
  2. 我将如何与多种效果互动? Player只能飞行被冻结,不能同时飞行(由于move_type)。然而burn是完全独立的效果。
  1. 我会有一个 Effect class,然后将 Effect 个对象的列表存储在 Player 对象中。调用 Player 中的方法将 Effect 个对象添加到此列表。浏览每个 turn/tick 上的 Effect 列表以检查堆栈、更新剩余持续时间、删除任何没有剩余持续时间的列表,等等。

  2. 您可以通过创建自定义 Effect 对象与多种效果进行交互。例如,如果您想要一个允许移动的冻结状态并且只是增加 Player 获得暴击的机会,请创建一个新的 Effect 来定义您正在寻找的行为。

以下代码提供了使用 activate/deactivate 定义新效果并创建 "categories" 的可能性。如果两种效果属于同一类别,则优先级较高的效果会影响玩家。不同类别的效果是独立的。

实现中有一个"flaw",试想如果出现以下情况:

  • 应用效果a
  • 效果a被移除没有因为超时
  • 效果a再次应用第一次管理超时
  • 之前
  • 效应 a 被移除,因为达到了第一个给药持续时间

如果 Effect 变成 class/metaclass,这可能很容易解决。

这里的效果真的应该是class/metaclass,刚开始就这么写,想写完。为什么是 metaclass?解决上述问题的一种方法是让 Effect 成为一个元 class,这样 Freeze 就是一个 class 并且一个管理部门将是这个 class,与此实例相关的延迟。

from collections import namedtuple, defaultdict

Effect = namedtuple("Effect", ["activate", "deactivate", "category", "priority", "name"])

class Player(game.PlayerEntity):
    def __init__(self):
        super().__init__()
        # Instead of list a priority list could/should be used
        self._effects = defaultdict(list)

    def add_effect(self, effect, duration=None):
        # Check if player allready suffers from effect
        if effect not in self._effects[effect.category]:

            # If the new effect has a higher priority than all others, activate it
            max_priority_effect = max(self._effects, key = lambda effect: effect.priority): 
            if effect.priority > max_priority_effect
                # Deactivate the old effect to savely change status
                max_priority_effect.deactivate(self)
                effect.activate(self)

            self._effects[effect.category].append(effect)

        if duration is not None:
            delayed(duration, remove_effect, effect)

    def remove_effect(self, effect):
        if effect.name in self._effects[effect.category]:
            priority_element = max(self._effects, key = lambda effect: effect.priority)

            # Might not be optimal?
            # If Effect is implemented as class do proper __eq__
            if effect.name == priority_element.name:
                # If the effect is the currently active effect deactive it and activate
                # the effect with the secondmost priority
                self._effects[effect.category][effect.name].deactivate()
                max(self._effects, key = lambda effect: effect.priority).activate()
                # If Effect is implemented as class do proper max() handling

            del self._effects[effect.category][effect.name]

#Effects:
freeze = Effect(
        activate   = lambda player: player.move_type = MoveTypes.None,
        deactivate = lambda player: player.move_type = MoveTypes.WALK,
        category   = "movement",
        priority   = 2,
        name       = "freeze")
fly = Effect(
        activate   = lambda player: player.move_type = MoveTypes.FLY,
        deactivate = lambda player: player.move_type = MoveTypes.WALK,
        category   = "movement",
        priority   = 1,
        name       = "fly")
burn = Effect(
        activate   = lambda player: player.ignite(),
        deactivate = lambda player: player.extinguish(),
        category   = "other",
        priority   = 0,
        name       = "burn")

编辑: 看来我错了,这里不需要 metaclasses。以下是 Effect 为 class 的实现。未经测试。

from collections import namedtuple, defaultdict
from functools import total_ordering

@total_ordering
class Effect:

    def __init__(self):
        super().__init__(self)
        self.deactivate = self.if_fresh(deactivate)

    def if_fresh(self, f):
        if self.fresh:
            f()
            self.fresh = False

    def activate(self, player):
        # Overwrite me!
        pass

    def deactivate(self, player):
        # Overwrite me!
        pass

    def __eq__(self, other):
        return(self is other)

    def __lt__(self, other):
        return(self.priority < other.priority)

class Burn(Effect):

    category = "other"
    priority = "0"

    def activate(self, player):
        player.ignite()

    def deactivate(self, player):
        player.extinguish()

[... other effects ... ]


class Player(game.PlayerEntity):
    def __init__(self):
        super().__init__()
        # Instead of list a priority list could/should be used
        self._effects = defaultdict(list)

    def add_effect(self, effect, duration=None):
        # Check if player allready suffers from effect
        if effect not in self._effects[effect.category]:

            # If the new effect has a higher priority than all others, activate it
            max_priority_effect = max(self._effects[effect.category])
            if effect > max_priority_effect:
                # Deactivate the old effect to savely change status
                max_priority_effect.deactivate(self)
                effect.activate(self)

            self._effects[effect.category].append(effect)

        if duration is not None:
            delayed(duration, remove_effect, effect)

    def remove_effect(self, effect):
        if effect in self._effects[effect.category]:
            priority_element = max(self._effects)

            if effect is priority_element:
                # If the effect is the currently active effect deactive it and activate
                # the effect with the secondmost priority
                self._effects[effect.category][effect].deactivate()
                max(self._effects.activate())

            del self._effects[effect.category][effect]