Я пытаюсь создать боевую последовательность для моей текстовой игры, где пользователю предлагается ввести вход, а их вход используется для захвата функционального объекта из словаря. Мне трудно передать какие-либо позиционные аргументы объекту функции.
Здесь мой код, с которым я работаю:
def combat(player, enemy):
"""Our mechanism for combat between the player and the computer."""
while player.health >= 0 and enemy.health >= 0:
player_turn = True
while player_turn:
player_choice = input("It your turn! ")
player.moves.do(player_choice)(enemy)
player_turn = False
print("It the enemy turn.")
enemy.do('basic attack')
if player.health <= 0 and enemy.health > 0:
print("You lose.")
else:
print("You win.")
Вход пользователя захватывается методом do()
.
def do(self, ability):
"""Grabs the value of the key, determined by player input."""
self.moves.get(ability)
moves
- это словарь, который мы получаем через инициализацию. Он содержит все движения игроков.
class Combatant(object):
"""Base class that subclasses Human and Enemy classes."""
def __init__(self, health, stamina):
self.health = health
self.stamina = stamina
self.moves = {}
Здесь один из этих шагов для контекста. Я держу их довольно просто сейчас, пока не узнаю больше о проблеме, с которой я столкнулся. В конце концов, я планирую передать больше аргументов в пользу методов.
def basic_attack(self, enemy):
"""Attack that does a set amount of damage and costs no stamina."""
damage = 5
enemy.health -= damage
Класс Combatant
содержит только эти атрибуты и add_moves
do
и add_moves
(которые я использую для обновления содержимого словаря). Я использовал это для подкласса пары других классов, которые имеют свои собственные способности. Я пытаюсь передать позиционные аргументы объекту функции, вложенному в moves
, и я хочу избежать использования операторов if-else
(поэтому я могу поддерживать совместимость между моим кодом здесь и формами, которые я использую в Flask). Я попытался установить кортеж рядом с вызовом функции для do
, но он возвращает это:
File ".\combat_sequence_2.py", line 72, in combat
player.do(player_choice)(enemy)
TypeError: 'NoneType' object is not callable
Я пытаюсь основать свой подход на обратной связи, полученной OP в этой теме, и мне не повезло найти какие-либо возможные ответы в другом месте. Я также пытаюсь использовать более простой подход, если это возможно, прежде чем попытаться поиграть с модулем functools
.
Редактировать:
Мои moves
словарь будет выглядеть примерно так:
{'basic attack': basic_attack}
Вам просто нужно заполнить ходы именами функций в качестве ключей, а функция - значением, например:
self.moves['basic attack'] = basic_attack
Однако каждый раз, когда вы вводите пользовательский ввод, вы должны убедиться, что он действителен. Во- первых, вам нужно добавить оператор возврата к do
:
def do(self, ability):
"""Grabs the value of the key, determined by player input."""
return self.moves.get(ability)
Затем, когда вы вызываете do
, вам нужно убедиться, что тип возврата не является None
прежде чем пытаться вызвать его:
player_choice = input("It your turn! ")
move = player.moves.do(player_choice.strip())
if move is not None:
move(enemy)
player_turn = False
Также обратите внимание, что возвращаемое значение input
имеет символ новой строки в конце, поэтому я добавил вызов .strip()
get
когда в этом нет необходимости. используйте [ability]
и поймайте KeyError
. Гораздо более питонический.
get()
, однако, является частью языка и вполне допустима
В функции do
отсутствует оператор return
. Кроме того, не имеет смысла для функции с именем do
чтобы ничего не делать. Более того, это имя do
является информативным, даже если оно что-то do
.
Кроме того, я не думаю, что для каждого экземпляра класса Combatant
имеет смысл содержать реестр допустимых ходов. Это похоже на то, что нужно обрабатывать на уровне класса.
Следующий код обращается к этим точкам с добавленным декоратором define_move
и метаклассом для обработки перемещений с классом.
def define_move(function):
function._move = True
return function
class CombatantMeta(type):
def __new__(metacls, name, bases, namespace):
combatant_class = super().__new__(metacls, name, bases, namespace)
combatant_class.moves = {
name: function for name, function in namespace.items()
if hasattr(function, '_move')
}
return combatant_class
class Combatant(metaclass=CombatantMeta):
def __init__(self, health, stamina):
self.health = health
self.stamina = stamina
@classmethod
def is_valid_move(cls, name):
return name in cls.moves
def perform_move(self, name, enemy):
self.moves[name](self, enemy)
@define_move
def basic_attack(self, enemy):
enemy.health -= 5
Здесь имена ходов всегда будут идентичны имени функции - это должно помочь с удобочитаемостью. Использование метакласса может показаться чрезмерной инженерией, но это единственный способ динамически обновлять атрибут класса при создании.
Дополнительный метод is_valid_move
поможет упростить вашу основную функцию. Вместе с функцией сложения input_move
, combat
может быть потоком подкладки, чтобы содержать только заметную логику.
def input_move(msg):
move = ''
while not Combatant.is_valid_move(move):
move = input(msg)
return move
def combat(player, enemy):
while player.health > 0 and enemy.health > 0:
player_move = input_move("It your turn! ")
player.perform_move(player_move, enemy)
print("It the enemy turn.")
enemy.perform_move('basic_attack', player)
if player.health <= 0 and enemy.health > 0:
print("You lose.")
else:
print("You win.")
dict.get
имеет подписьget(key, default=None)
поkey
default
None
get(key, default=None)
согласно которому , еслиkey
не в словаре, поdefault
возвращается (None
по умолчанию). Таким образом,player_choice
вы передаете, отсутствует в словаре. Вместо этого я бы посоветовал использоватьself.moves[ability]
поскольку в этом случае вы получите более четкую ошибку. docs.python.org/3.8/library/stdtypes.html#dict.get . Вам нужно будет справиться, когда игрокplayer_choice
вplayer_choice
который не входит в вашиmoves
.