в C # мы должны получить / установить правила, но я не знаю, как это сделать в Python.
Нет.Геттеры и сеттеры вам здесь не помогут.Обратите внимание, что Python также имеет геттеры / сеттеры и дундер (что-то вроде self.__foo
), но давайте не будем следовать этому пути.
Вместо этого давайте посмотрим, что у вас есть:
- куча вещей (например, Орки и Люди и Мечи и прочее)
- куча действий (хорошо, в настоящее время это только одно действие, владение , но, возможно, завтра вы решите, что вампир может пить кровь, но не человек)
- и куча правил (топор - это оружие,меч - это оружие, орки могут использовать только топор, человек может использовать другое оружие, ...).
Итак, давайте попробуем смоделировать нашу игру следующим образом: с Things
, Actions
и Rules
.
Поскольку мы классные дети, давайте начнем с записи наших правил в виде текста:
rules =[
"Human is Person",
"Orc is Person",
"Person may wield Weapon",
"Human may not wield Axe",
"Orc may only wield Axe",
"Sword is Weapon",
"Bow is Weapon",
"Axe is Weapon",
"Cow is Animal",
"Animal may eat Grass"
]
Как вы видите, я говорю о коровах и животныхи трава тоже, так что мы можем видеть, что мы собираемся сделать очень общий подход.
Мы знаем, что наши «вещи» имеют разные типы, имя и способ вызова«действие», так вот наш Thing
класс:
class Thing:
def __init__(self, name, *type):
self.name = name
self.type = ['Thing', *type]
def action(self, action_class, *args):
action_class(self, *args)()
A Thing
имеет тип 'Thing'
и все остальное, что мы передаем __init__
, и мы можем вызвать action
функция с классом Action
(мы создаем его в ближайшее время) и некоторыми аргументами, которые мы передаем этой функции.
Пока все очень просто.
Теперь, вот какой универсальный Action
может выглядеть так:
class Action:
def __init__(self, name, a, b):
self.name = name
self.a = a
self.b = b
def invoke(self):
print('You feel a strange sensation...')
def forbidden(self):
print(f"{self.a.name} tries to {self.name} {self.b.name}, but that is not possible")
def __call__(self):
if Rules.allowed(self):
self.invoke()
else:
self.forbidden()
print('----------')
Просто имя и две вещи (a
и b
).Он может быть вызван (например, Thing.action
) и может быть вызван (и затем вызывать invoke
), или нет (и затем вызывать fobidden
).
Давайте пока проигнорируем Rules.allowed
и создадим некоторые действия, которые делают что-то:
class Wield(Action):
def __init__(self, thing, weapon):
super().__init__('wield', thing, weapon)
def invoke(self):
if hasattr(self.a, 'weapon'):
print(f'{self.a.name} drops {self.a.weapon.name}')
self.a.weapon = self.b
print(f'{self.a.name} now wields {self.a.weapon.name}')
class Eat(Action):
def __init__(self, thing, food):
super().__init__('eat', thing, food)
def forbidden(self):
print(f'{self.a.name} tried to eat {self.b.name}, but did not like it very much...')
def invoke(self):
print(f'{self.a.name} eats {self.b.name}')
Действие Wield
установит weapon
вызывающего, но только если это разрешено,Действие Eat
, ну, на данный момент, просто печатает сообщение ...
Итак, единственное, что нам остается сделать сейчас - это реализовать Rules.allowed
, что означает парсинг правил, которые мы создали всначала и действуй по нему.
Вот класс Rules
:
class Rules:
alias_list = []
prohibition_list = []
permission_list = []
exclusive_list = []
def parse_rules(rules):
for rule in rules:
if ' is ' in rule:
type, alias = rule.split(' is ')
Rules.alias_list.append((type, alias))
elif ' may only ' in rule:
obj, rest = rule.split(' may only ')
action, second = rest.split(' ')
Rules.exclusive_list.append((obj, action, second))
elif ' may not ' in rule:
obj, rest = rule.split(' may not ')
action, second = rest.split(' ')
Rules.prohibition_list.append((obj, action, second))
elif ' may ' in rule:
obj, rest = rule.split(' may ')
action, second = rest.split(' ')
Rules.permission_list.append((obj, action, second))
def resolve_types_inner(types, aliases):
for (source_type, alias_type) in aliases[:]:
if source_type in types:
types.add(alias_type)
aliases.remove((source_type, alias_type))
return Rules.resolve_types_inner(types, aliases)
return types
def resolve_types(thing):
types = set(thing.type)
return Rules.resolve_types_inner(types, Rules.alias_list[:])
def allowed(action_to_test):
a_types = Rules.resolve_types(action_to_test.a)
b_types = Rules.resolve_types(action_to_test.b)
for (a, action, b) in Rules.exclusive_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
print ('-- allowed by exclusive_list')
return True
for (a, action, b) in Rules.prohibition_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
print ('-- forbidden')
return False
for (a, action, b) in Rules.permission_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
if not action in (x for (a2,x,b2) in Rules.exclusive_list if x == action and a2 in a_types):
print ('-- allowed')
return True
else:
print ('-- forbidden by exclusive_list')
return False
print ('-- no rules match')
Конечно, это просто очень базовый и не полноценный механизм правил или язык логического программирования, но этосделаем пока.
Мы уже поддерживаем 4 функции:
- Псевдонимы.Мы можем сказать, что A - это что-то B, и все правила B применяются к A
- Разрешить что-то
- Запретить что-то
- Разрешить A что-то только для определенного B
Функция parse_rules
просто разбивает строки и добавляет части в разные списки, а в функции allowed
мы повторяем эти списки, чтобы определить, разрешено ли что-то или нет.
Почувствуйтеможете улучшить это или добавить новые функции.
Итак, теперь мы готовы к работе.
Давайте запустим следующее:
# prepare our simple rule engine
Rules.parse_rules(rules)
# Let some things exist in the world
Carl_the_Human = Thing('Carl', 'Human')
Grump_the_Orc = Thing('Grump', 'Orc')
Sandy_the_Cow = Thing('Sandy', 'Cow')
Carls_sword = Thing("Carl's Sword of Justice", 'Sword')
Grumps_axe = Thing("Grump's rusty Axe", 'Axe')
Old_bow = Thing("An old bow", 'Bow')
# Sandy is hungry
Sandy_the_Cow.action(Wield, Grumps_axe)
Sandy_the_Cow.action(Eat, Grumps_axe)
Sandy_the_Cow.action(Eat, Thing("a bunch of grass", "Grass"))
# Carl wants to try some weapons
Carl_the_Human.action(Wield, Carls_sword)
Carl_the_Human.action(Wield, Grumps_axe)
Carl_the_Human.action(Wield, Old_bow)
# Grump wants to try some weapons
Grump_the_Orc.action(Wield, Grumps_axe)
Grump_the_Orc.action(Wield, Carls_sword)
мыполучить следующий результат:
-- no rules match
Sandy tries to wield Grump's rusty Axe, but that is not possible
----------
-- no rules match
Sandy tried to eat Grump's rusty Axe, but did not like it very much...
----------
-- allowed
Sandy eats a bunch of grass
----------
-- allowed
Carl now wields Carl's Sword of Justice
----------
-- forbidden
Carl tries to wield Grump's rusty Axe, but that is not possible
----------
-- allowed
Carl drops Carl's Sword of Justice
Carl now wields An old bow
----------
-- allowed by exclusive_list
Grump now wields Grump's rusty Axe
----------
-- forbidden by exclusive_list
Grump tries to wield Carl's Sword of Justice, but that is not possible
----------
Всякий раз, когда нам нужно новое «Правило» в нашем игровом мире, мы можем просто добавить его в наш список правил в виде простого текста и позволить нашему простому механизму правил решить, разрешено ли что-либо(или даже как что-то должно произойти, если мы расширим наш двигатель).
Так что, возможно, у нас есть оружие дальнего и ближнего боя, и мечники также могут использовать копья, но не луки, а лучники могут использовать луки и копья, но не ближний бойоружие?
Нет проблем,просто напишите это в правилах:
"Ranged is Weapon",
"Melee is Weapon",
"Bow is Ranged",
"Spear is Ranged",
"Sword is Melee",
"Human is Person",
"Archer is Human",
"Swordman is Human",
"Person may wield Weapon",
"Archer may not wield Melee",
"Swordman may not wield Bow"
Пример:
Swordman = Thing('the old Guy', 'Swordman')
Archer = Thing('the Archer', 'Archer')
Carls_sword = Thing("Carl's Sword of Justice", 'Sword')
Old_bow = Thing("An old bow", 'Bow')
Spear = Thing("A golden Spear", 'Spear')
Archer.action(Wield, Carls_sword)
Archer.action(Wield, Old_bow)
Archer.action(Wield, Spear)
Swordman.action(Wield, Carls_sword)
Swordman.action(Wield, Old_bow)
Swordman.action(Wield, Spear)
Результат:
-- forbidden
the Archer tries to wield Carl's Sword of Justice, but that is not possible
----------
-- allowed
the Archer now wields An old bow
----------
-- allowed
the Archer drops An old bow
the Archer now wields A golden Spear
----------
-- allowed
the old Guy now wields Carl's Sword of Justice
----------
-- forbidden
the old Guy tries to wield An old bow, but that is not possible
----------
-- allowed
the old Guy drops Carl's Sword of Justice
the old Guy now wields A golden Spear
----------
Вот полный, запускаемый код, который вы можете попробовать сами:
rules =[
"Human is Person",
"Orc is Person",
"Person may wield Weapon",
"Human may not wield Axe",
"Orc may only wield Axe",
"Sword is Weapon",
"Bow is Weapon",
"Axe is Weapon",
"Cow is Animal",
"Animal may eat Grass"
]
class Rules:
alias_list = []
prohibition_list = []
permission_list = []
exclusive_list = []
def parse_rules(rules):
for rule in rules:
if ' is ' in rule:
type, alias = rule.split(' is ')
Rules.alias_list.append((type, alias))
elif ' may only ' in rule:
obj, rest = rule.split(' may only ')
action, second = rest.split(' ')
Rules.exclusive_list.append((obj, action, second))
elif ' may not ' in rule:
obj, rest = rule.split(' may not ')
action, second = rest.split(' ')
Rules.prohibition_list.append((obj, action, second))
elif ' may ' in rule:
obj, rest = rule.split(' may ')
action, second = rest.split(' ')
Rules.permission_list.append((obj, action, second))
def resolve_types_inner(types, aliases):
for (source_type, alias_type) in aliases[:]:
if source_type in types:
types.add(alias_type)
aliases.remove((source_type, alias_type))
return Rules.resolve_types_inner(types, aliases)
return types
def resolve_types(thing):
types = set(thing.type)
return Rules.resolve_types_inner(types, Rules.alias_list[:])
def allowed(action_to_test):
a_types = Rules.resolve_types(action_to_test.a)
b_types = Rules.resolve_types(action_to_test.b)
for (a, action, b) in Rules.exclusive_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
print ('-- allowed by exclusive_list')
return True
for (a, action, b) in Rules.prohibition_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
print ('-- forbidden')
return False
for (a, action, b) in Rules.permission_list:
if action == action_to_test.name:
if a in a_types and b in b_types:
if not action in (x for (a2,x,b2) in Rules.exclusive_list if x == action and a2 in a_types):
print ('-- allowed')
return True
else:
print ('-- forbidden by exclusive_list')
return False
print ('-- no rules match')
class Action:
def __init__(self, name, a, b):
self.name = name
self.a = a
self.b = b
def invoke(self):
print('You feel a strange sensation...')
def forbidden(self):
print(f"{self.a.name} tries to {self.name} {self.b.name}, but that is not possible")
def __call__(self):
if Rules.allowed(self):
self.invoke()
else:
self.forbidden()
print('----------')
class Wield(Action):
def __init__(self, thing, weapon):
super().__init__('wield', thing, weapon)
def invoke(self):
if hasattr(self.a, 'weapon'):
print(f'{self.a.name} drops {self.a.weapon.name}')
self.a.weapon = self.b
print(f'{self.a.name} now wields {self.a.weapon.name}')
class Eat(Action):
def __init__(self, thing, food):
super().__init__('eat', thing, food)
def forbidden(self):
print(f'{self.a.name} tried to eat {self.b.name}, but did not like it very much...')
def invoke(self):
print(f'{self.a.name} eats {self.b.name}')
class Thing:
def __init__(self, name, *type):
self.name = name
self.type = ['Thing', *type]
def action(self, action_class, *args):
action_class(self, *args)()
if __name__ == '__main__':
Rules.parse_rules(rules)
Carl_the_Human = Thing('Carl', 'Human')
Grump_the_Orc = Thing('Grump', 'Orc')
Sandy_the_Cow = Thing('Sandy', 'Cow')
Carls_sword = Thing("Carl's Sword of Justice", 'Sword')
Grumps_axe = Thing("Grump's rusty Axe", 'Axe')
Old_bow = Thing("An old bow", 'Bow')
Sandy_the_Cow.action(Wield, Grumps_axe)
Sandy_the_Cow.action(Eat, Grumps_axe)
Sandy_the_Cow.action(Eat, Thing("a bunch of grass", "Grass"))
Carl_the_Human.action(Wield, Carls_sword)
Carl_the_Human.action(Wield, Grumps_axe)
Carl_the_Human.action(Wield, Old_bow)
Grump_the_Orc.action(Wield, Grumps_axe)
Grump_the_Orc.action(Wield, Carls_sword)
Обратите внимание, что для этого существуют языки программирования, например Inform7 .
Если вы хотите узнать больше, я предлагаю прочитать серию Wizards and warriors Эрика Липперта, в которой рассказывается именно об этой проблеме (и мой ответ вдохновлен этой серией), и даже используются похожие примеры(фэнтезийные классы и оружие), но ИМХО в ОО-языках это распространенная ошибка - моделировать неправильные вещи с объектами и пытаться навязать бизнес-логику в систему типов языков.