Используйте состав, шаблон стратегии и словарь, чтобы лучше создать экземпляр класса, хранящийся в словаре
Я разрабатываю 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