简体   繁体   English

使用组合,策略模式和字典来更好地实例化存储在字典中的类

[英]Use composition, strategy pattern and dictionary to better instantiate class stored in dictionary

I develop a RogueLike in python, and I try to make my best with OOP and my little knowledge to construct a python course for student. 我用python开发了一个RogueLike,然后尝试用OOP尽力而为,并以自己的一点知识为学生构建python课程。

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

I want to transform this string map into 2D list containing object defining the nature of my tile in the RogueLike. 我想将此字符串映射转换为2D列表,其中包含定义RogueLike中图块性质的对象。 For that I decide to use a dictionary to map character key and class to instantiate when I read this variable mapRogue . 为此,我决定使用字典来映射字符键,并在读取此变量mapRogue时实例化类。

I find a solution using inheritance, but imho this code is not really as elegant as i want, and probably not very flexible if I want to add other type of tile behavior later. 我找到了一个使用继承的解决方案,但是恕我直言,这段代码并不像我想要的那样优雅,而且如果以后要添加其他类型的平铺行为,可能也不是很灵活。

DOOR class using inheritance 使用继承的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"

DOOR class using composition 使用合成的DOOR类

I suspect there is an other answer using composition and/or strategy pattern, so i try to decorate the Tile object with Door behavior, but i'm blocked with this dictionnary ... 我怀疑还有其他使用构图和/或策略模式的答案,所以我尝试用Door行为装饰Tile对象,但是我被此词典所阻塞...

Actually i try multiple solution without success, do you have a proposition to help me to solve this problem of conception using elegant oop and python ? 实际上,我尝试多种解决方案均未成功,您是否有命题可以帮助我使用优雅的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

An update with some informations: 有关一些信息的更新:

Thanks for answer @User and @Hyperborreus, you're right, I simplify my example here, and in my code, I have two layers: 感谢您的回答@User和@Hyperborreus,您是对的,我在这里简化了示例,在我的代码中,我分为两层:

  • the Tile which don't move, 不动的Tile
  • and the GameObjects which can move, attack, defend, and a lot of other function decorated using composition like in this tutorial here GameObjects可以移动,攻击,防御,并使用装饰了很多其他功能composition在本教程中像这里

Using pygame , I display all my Tiles object using a draw_tile() function. 使用pygame ,我使用draw_tile()函数显示所有Tiles对象。

So at this point I need a link between Door and Tile class to compute correctly a fov for player later, because Door have behavior and limit the vision of my character (with an attributes blocked or fovState). 因此,在这一点上,我需要在DoorTile类之间建立链接,以便稍后为玩家正确计算fov,因为Door具有行为并限制了我角色的视野(属性为block或fovState)。 After that, I drawn all gameObject , on top of these already drawed Tile surfaces. 之后,我在这些已经绘制的Tile曲面的顶部绘制了所有gameObject Door is part of computation only specific to Tile, and other things in roguelike so that explain why I define the Door like that I hope. 门是计算的一部分,仅特定于图块,以及其他类似流氓的东西,因此可以解释为什么我希望如此定义Door

So probably you're right with your proposition of game definition dictionary, I need to change the way i instantiate object, the oop definition of Door / Tiles rest the same, but when I read the initial string map which contain item, door and also static object, I separate gameObject instantiation and Tile instantiation.. 所以可能您对游戏定义字典的主张是正确的,我需要更改实例化对象的方式,Door / Tiles的oop定义保持不变,但是当我读取包含项目,door和静态对象,我将gameObject实例化和Tile实例化分开。

The idea of dictionary to instantiate element on a rogueLike map defined in string list is based on the idea founded here: https://bitbucket.org/BigYellowCactus/dropout/ 字典实例化字符串列表中定义的rogueLike映射上的元素的想法是基于以下想法: https ://bitbucket.org/BigYellowCactus/dropout/

Perhaps the creator of this code, @dominic-kexel can also help us to on this point? 也许这段代码的创建者@ dominic-kexel可以在这一点上帮助我们吗?

IMHO, you should make a distinction between "tiles" (the underlying basemap) and "objects" (things the player can interact with, like doors that open, dragons that attack, or pits that kill). 恕我直言,您应该区分“砖块”(基础底图)和“物体”(玩家可以与之互动的事物,例如打开的门,攻击的龙或杀死的坑)。

If you want to compare this with a 3D-video game, the "tiles" would be the environment you cannot interact with, and the "objects" would be the clickable things. 如果要将其与3D视频游戏进行比较,则“平铺”将是您无法与之交互的环境,而“对象”将是可单击的事物。

The mere tiles can be instances of one single class and hold the information relevant and common to all tiles, like rendering hints (which character in which colour) or movement aspects (can be passed, movement speed, etc). 单纯的图块可以是一个单一类的实例,并保存与所有图块相关且相同的信息,例如渲染提示(哪种颜色的字符)或运动方面(可以通过,运动速度等)。

The objects are then placed on top of the tiles. 然后将对象放在图块的顶部。

Imagine you have a map with a lot of floor and walls, and at two positions you have two doors. 假设您有一张地图,上面有很多地板和墙壁,在两个位置上有两个门。 All "tiles" behave the same (you can walk on floor, no matter which floor tile) but you will butt your head against a wall (no matter where the wall is). 所有“瓷砖”的行为都相同(无论在哪个地板砖上,您都可以在地板上行走),但是您将头靠在墙壁上(无论墙壁在哪里)。 But the doors are different: One door requires the "Green Key" and the other door requires the "Embroidered Key of the Dissident Pixie". 但是门是不同的:一扇门需要“绿色钥匙”,另一扇门需要“异己小精灵的绣花钥匙”。

This difference is where your if -issue arises. 这种差异就是您的if issue出现的地方。 The door needs extra information. 门需要更多信息。 In order to define a map, you need all tiles (identical within each class) and another lists of objects placed on certain tiles (each object different). 为了定义地图,您需要所有图块(在每个类中相同)和放置在某些图块上的另一对象列表(每个对象都不同)。

Doors, Wells, Dragons, Switches etc could inherit from a common base class which implements standard actions like "inspect", "interact", "attack", "yell at", and maybe special interfaces for special actions. 门,井,龙,开关等可以从通用的基类继承,该基类实现标准的操作,例如“检查”,“交互”,“攻击”,“大吼大叫”,以及特殊操作的特殊接口。

So a complete game definition could look like this: 因此,完整的游戏定义应如下所示:

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': ...}, ] }

Then for instantiating the actual objects (object in the OO sense, not in the game sense), walk throught this definition, and call the c'tor of each object with the dictionary items as keyword-arguments (double-asterisk). 然后,要实例化实际对象(面向对象的对象,而不是游戏对象的对象),遍历此定义,并使用字典项作为关键字参数(双星号)调用每个对象的索引。 This is only one possible approach of many. 这只是许多方法中的一种。

For rendering, display the tile presentation if the tile is empty, or the object representation if there is an object on the tile. 为了进行渲染,如果图块为空,则显示图块表示;如果图块上有一个对象,则显示对象表示。


This is what I mean with double-asterisk: 这就是双星号的意思:

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'] )

Big Edit: 大修改:

This is an idea of how one could implement the rendering of the map with both tiles and objects. 这是关于如何使用图块和对象实现地图渲染的一种想法。 (Just my two cents): (只需我的两分钱):

#! /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)

And this is the result: 结果如下:

在此处输入图片说明

Here a simple solution to your problem: 这里是您问题的简单解决方案:

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

does the same as 与...相同

        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"])

And this is because I value what you do: 这是因为我重视您的工作:

I would agree with Hyperboreus that you make a difference between the position = the tile and what is placed on top if the tile. 我同意Hyperboreus的观点,即您在position = tile和在tile上方放置的内容之间有所不同。

What worked really well in another game is that tiles are connected: 在另一款游戏中真正有效的方法是将图块连接起来:

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

This way you can make portals by connecting tiles that are not neighbors by position. 这样,您可以通过按位置连接不相邻的图块来创建门户。

Here are some contents: 以下是一些内容:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM