Используйте состав, шаблон стратегии и словарь, чтобы лучше создать экземпляр класса, хранящийся в словаре

Я разрабатываю RogueLike на Python и стараюсь изо всех сил с ООП и своими маленькими знаниями для создания курса Python для студента.

mapRogue = ['~~~~~~~~~~',
            '~~~~.....Y',
            'YYYYY+YYYY',
            'YYYY....YY']

Я хочу преобразовать эту строковую карту в 2D-список, содержащий объект, определяющий природу моей плитки в RogueLike. Для этого я решил использовать словарь для сопоставления символьного ключа и класса для создания экземпляра, когда я читаю эту переменную mapRogue,

Я нахожу решение с использованием наследования, но imho этот код на самом деле не так элегантен, как я хочу, и, вероятно, не очень гибок, если позже я хочу добавить другой тип поведения плиток.

DOOR класс с использованием наследования

class Tile(object):
    #a tile of the map and its properties
    def __init__(self, name, char, position, blocked, color=(255, 255, 255), bgcolor=(0, 0, 0)):
        self.name = name
        self.blocked = blocked
        self.char = char
        self.color = color
        self.bgcolor = bgcolor
        self.position = position

class Door(Tile):
    def __init__(self,  name, char, position, blocked, bgcolor, key,color=(255, 255, 255), open=False ):
        Tile.__init__( self,name, char, position, blocked, color, bgcolor)
        self.state = open
        self.key = key

    def opening(self, key):
        if self.key == key:
            self.state = True

tilesObject = {".": {"name": 'floor', "obj": Tile, "bgcolor": (233, 207, 177), "block": False},
               "Y": {"name": 'forest', "obj": Tile, "bgcolor": (25, 150, 64), "block": True},
               "~": {"name": 'water', "obj": Tile, "bgcolor": (10, 21, 35), "block": False},
               "+": {"name": 'doors', "obj": Door, "bgcolor": (10, 10, 25), "block": False}}
import types
def load(mymap):
    tileMap = []
    x, y = (0,0)
    for line in mymap:
        tileLine = []
        for value in line:
            try:
                tile = tilesObject[value]
            except KeyError:
                return "Error on key"
            if tile["obj"].__name__ ==  "Door":
                obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"], key="42isTheKey", open=False)
            else:
                obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"])

            x += 1
            tileLine.append(obj)
        x = 0
        y += 1
        tileMap.append(tileLine)
    return tileMap


for line in load(mapRogue):
    for obj in line:
        print obj , "\n"

ДВЕРИ класс с использованием композиции

Я подозреваю, что есть другой ответ, использующий композицию и / или шаблон стратегии, поэтому я пытаюсь украсить объект Tile поведением Door, но я заблокирован этим словарем...

На самом деле, я пробую множественное решение без успеха, у вас есть предложение, чтобы помочь мне решить эту проблему зачатия, используя элегантный oop и python?

class Tile(object):
    #a tile of the map and its properties
    def __init__(self, name, char, position, blocked, color=(255, 255, 255), bgcolor=(0, 0, 0), door=None):
        self.name = name
        self.blocked = blocked
        self.char = char
        self.color = color
        self.bgcolor = bgcolor
        self.door = door
        self.position = position

# Door decorate the Tile object using composition
class Door(object):
    def __init__(self, key, open=False):
        self.state = open
        self.key = key

    def opening(self, key):
        if self.key == key:
            self.state = True

tilesObject = {".": {"name": 'floor', "obj": Tile, "bgcolor": (233, 207, 177), "block": False},
               "Y": {"name": 'forest', "obj": Tile, "bgcolor": (25, 150, 64), "block": True},
               "~": {"name": 'water', "obj": Tile, "bgcolor": (10, 21, 35), "block": False},
               "+": {"name": 'doors', "obj": Door, "bgcolor": (10, 10, 25), "block": False}}

def load(mymap):
    tileMap = []
    x, y = (0,0)
    for line in mymap:
        tileLine = []
        for value in line:
            try:
                tile = tilesObject[value]
            except KeyError:
                return "Error on key"

            # Here i need to detect when obj is Door 
                    # because i need to define a special Tile 
                    # decorated by Door behavior, 
                    # so it seems this is not a good solution :/

            x += 1
            tileLine.append(obj)
        x = 0
        y += 1
        tileMap.append(tileLine)
    return tileMap

Обновление с некоторыми сведениями:

Спасибо за ответ @User и @Hyperborreus, вы правы, я упростил мой пример здесь, и в моем коде у меня есть два слоя:

  • Tile которые не двигаются,
  • и GameObjects которые могут перемещаться, атаковать, защищать и многие другие функции, оформленные с использованием composition как в этом уроке здесь

С помощью pygameЯ показываю все свои Tiles объект с помощью draw_tile() функция.

Так что на данный момент мне нужна связь между Door а также Tile класс для правильного вычисления FOV для игрока позже, потому что Door иметь поведение и ограничить зрение моего персонажа (с заблокированными атрибутами или fovState). После этого я нарисовал все gameObjectповерх них уже нарисовано Tile поверхностей. Дверь - это часть вычислений, относящихся только к Tile, и другим вещам, похожим на roguelike, так что я могу объяснить Door Надеюсь, что так.

Так что, вероятно, вы правы в своем предложении по словарю определения игры, мне нужно изменить способ создания экземпляра объекта, определение oop для Door / Tiles остается прежним, но когда я читаю исходную карту строк, содержащую item, door, а также статический объект, я отделяю gameObject создание и Tile конкретизация..

Идея словаря для создания экземпляра элемента на карте rogueLike, определенной в списке строк, основана на идее, основанной здесь: https://bitbucket.org/BigYellowCactus/dropout/

Возможно, создатель этого кода @dominic-kexel также может помочь нам в этом вопросе?

2 ответа

Решение

ИМХО, вы должны различать "плитки" (базовая базовая карта) и "объекты" (вещи, с которыми игрок может взаимодействовать, например, открывающиеся двери, атакующие драконы или убивающие ямы).

Если вы хотите сравнить это с 3D-видеоигрой, то "плитки" - это среда, с которой вы не можете взаимодействовать, а "объекты" - это интерактивные объекты.

Простые тайлы могут быть экземплярами одного отдельного класса и содержать информацию, относящуюся и общую для всех тайлов, такую ​​как подсказки рендеринга (какой символ какого цвета) или аспекты движения (могут быть пройдены, скорость движения и т. Д.).

Затем объекты помещаются поверх плиток.

Представьте, что у вас есть карта с большим количеством этажей и стен, и в двух местах у вас есть две двери. Все "плитки" ведут себя одинаково (вы можете ходить по полу, независимо от того, какая плитка для пола), но вы будете прислоняться головой к стене (независимо от того, где стена). Но двери разные: для одной двери требуется "Зеленый ключ", а для другой - "Вышитый ключ Диссидента Пикси".

Эта разница, где ваш ifпроблема возникает. Дверь нуждается в дополнительной информации. Чтобы определить карту, вам нужны все плитки (идентичные внутри каждого класса) и другие списки объектов, размещенных на определенных плитках (каждый объект отличается).

Двери, Уэллс, Драконы, Коммутаторы и т. Д. Могут наследовать от общего базового класса, который реализует стандартные действия, такие как "проверка", "взаимодействие", "атака", "кричать на" и, возможно, специальные интерфейсы для специальных действий.

Таким образом, полное определение игры может выглядеть так:

game = {'baseMap': '#here comes your 2D array of tiles',
'objects': [ {'class': Door, 'position': (x, y), 'other Door arguments': ...}, 
{'class': Door, 'position': (x2, y2), 'other Door arguments': ...},
{'class': Dragon, 'position': (x3, y3), 'dragon arguments': ...}, ] }

Затем для создания экземпляров реальных объектов (объектов в ОО-смысле, а не в игровом смысле) просмотрите это определение и вызовите c'tor каждого объекта с элементами словаря в качестве аргументов-ключевых слов (двойная звездочка). Это только один из многих возможных подходов.

Для визуализации отобразите представление плитки, если плитка пуста, или представление объекта, если на плитке есть объект.


Вот что я имею в виду с двойной звездочкой:

class Door:
    def __init__ (self, position, colour, creaking = True):
        print (position, colour, creaking)

objDefs = [...,
          {'class': Door, 'kwargs': {'position': (2, 3), 'colour': 'black'} },
          ...]

#Here you actually iterate over objDefs
objDef = objDefs [1]
obj = objDef ['class'] (**objDef ['kwargs'] )

Большой Редактировать:

Это идея того, как можно реализовать рендеринг карты с использованием плиток и объектов. (Только мои два цента)

#! /usr/bin/python3.2

colours = {'white': 7, 'green': 2, 'blue': 4, 'black': 0, 'yellow': 3}

class Tile:
    data = {'.': ('Floor', 'white', True),
        'Y': ('Forest', 'green', False),
        '~': ('Water', 'blue', False) }

    def __init__ (self, code, position):
        self.code = code
        self.position = position
        self.name, self.colour, self.passable = Tile.data [code]

    def __str__ (self):
        return '\x1b[{}m{}'.format (30 + colours [self.colour], self.code)

class GameObject:
    #here got he general interfaces common to all game objects
    def __str__ (self):
        return '\x1b[{}m{}'.format (30 + colours [self.colour], self.code)

class Door (GameObject):
    def __init__ (self, code, position, colour, key):
        self.code = code
        self.position = position
        self.colour = colour
        self.key = key

    def close (self): pass
        #door specific interface

class Dragon (GameObject):
    def __init__ (self, code, position, colour, stats):
        self.code = code
        self.position = position
        self.colour = colour
        self.stats = stats

    def bugger (self): pass
        #dragon specific interface

class Map:
    def __init__ (self, codeMap, objects):
        self.tiles = [ [Tile (c, (x, y) ) for x, c in enumerate (line) ] for y, line in enumerate (codeMap) ]
        self.objects = {obj ['args'] ['position']: obj ['cls'] (**obj ['args'] ) for obj in objects}

    def __str__ (self):
        return '\n'.join (
            ''.join (str (self.objects [ (x, y) ] if (x, y) in self.objects else tile)
                for x, tile in enumerate (line) )
            for y, line in enumerate (self.tiles)
            ) + '\n\x1b[0m'

mapRouge = ['~~~~~~~~~~',
            '~~~~.....Y',
            'YYYYY.YYYY',
            'YYYY....YY']

objects = [ {'cls': Door,
        'args': {'code': '.', 'position': (5, 2), 'colour': 'black',
        'key': 'Ancient Key of Constipation'} },
    {'cls': Dragon,
        'args': {'code': '@',  'position': (7, 3), 'colour': 'yellow',
        'stats': {'ATK': 20, 'DEF': 20} } } ]

theMap = Map (mapRouge, objects)
print (theMap)

И вот результат:

Вот простое решение вашей проблемы:

kw = tile.copy()
cls = kw.pop('obj')
obj = cls(**kw)

делает так же, как

        if tile["obj"].__name__ ==  "Door":
            obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"], key="42isTheKey", open=False)
        else:
            obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"])

И это потому, что я ценю то, что вы делаете:

Я согласен с Hyperboreus, что вы делаете различие между положением = плиткой и тем, что расположено сверху, если плитка.

Что действительно хорошо работает в другой игре, так это то, что плитки связаны:

class Tile:
    def __init__(self):
        self.left_tile = None
        self.right_tile = None
        ...
        self.content = [FreeSpaceToWalk()]

    def can_I_walk_there(self, person):
        for content in self.content:
            if not content.can_I_walk_there(person): return False
        return True

Таким образом, вы можете создавать порталы, соединяя тайлы, которые не являются соседями по позиции.

Вот некоторые содержания:

class Door:
    password = '42'
    def can_I_walk_there(self, person):
        return person.what_is_the_password_for(self) == self.password

class FreeSpaceToWalk:
    def can_I_walk_there(self, person):
        return True

class Wall:
    def can_I_walk_there(self, person):
        return False
Другие вопросы по тегам