Python как сделать набор правил для каждого класса в игре - PullRequest
2 голосов
/ 18 марта 2019

в C # мы должны получить / установить правила, но я не знаю, как это сделать в Python.

Пример: орки могут экипировать только Оси, другое оружие не подходитЛюди могут экипировать только мечи, другие виды оружия имеют право.

Как я могу сказать Python, что Орк не может сделать что-то подобное в примере выше?

Спасибо за ответы заранее, надеюсьребята, это имело какой-то смысл.

Ответы [ 3 ]

3 голосов
/ 19 марта 2019

в 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 Эрика Липперта, в которой рассказывается именно об этой проблеме (и мой ответ вдохновлен этой серией), и даже используются похожие примеры(фэнтезийные классы и оружие), но ИМХО в ОО-языках это распространенная ошибка - моделировать неправильные вещи с объектами и пытаться навязать бизнес-логику в систему типов языков.

0 голосов
/ 18 марта 2019

Если вы хотите, чтобы у класса Orc были определенные члены, а у класса Human нет, и вы все еще хотите, чтобы эти классы были связаны (поскольку оба являются символами), это именно то, для чего наследуется.Вот пример иерархии наследования:

Inheritance hierarchy

Вот пример реализации:

class Weapon:
    def __init__(self, damage):
        self.damage = damage

class Gun(Weapon):
    def __init__(self):
        Weapon.__init__(self, 300)

class Grenade(Weapon):
    def __init__(self):
        Weapon.__init__(self, 1000)

class Knife(Weapon):
    def __init__(self):
        Weapon.__init__(self, 50)

class Hammer(Weapon):
    def __init__(self):
        Weapon.__init__(self, 100)


class Character:
    def __init__(self, name, default_weapon, additional):
        self.name = name
        self.default_weapon = default_weapon
        self.additional = additional

    def attack(self):
        pass # some implementation

class Heavily_armed(Character):
    def __init__(self, name):
        Character.__init__(self, name, Gun(), Grenade())


class Lightly_armed(Character):
    def __init__(self, name):
        Character.__init__(self, name, Knife(), Hammer())

class Human(Lightly_armed):
    def __init__(self, name, age, height):
        Lightly_armed.__init__(self, name)
        self.height = height
        self.age = age

class Orc(Heavily_armed):
    def __init__(self, name, danger):
        Heavily_armed.__init__(self, name)
        self.danger = danger

Как вы можете видеть каждый Character есть оружие, но это разные виды оружия.Чтобы расширить это, вы можете создать набор «доступного оружия» для каждого типа и создать экземпляр определенного оружия, только если оно есть в наборе.Это может или не может быть жизнеспособным выбором дизайна для вашей реализации игры.Выбор бесконечен

0 голосов
/ 18 марта 2019

В языке Python отсутствует эффективный механизм ограничения доступа к экземпляру или методу. Существует соглашение, заключающееся в том, что префикс имени поля / метода с подчеркиванием для имитации «защищенного» или «частного» поведения.

Но все члены класса Python по умолчанию являются публичными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...