Пример #1
0
 def __init__(self, width = 0, height = 0, attrib = {}):
     self.width = width
     self.height = height
     
     self.clear()
     
     self.attrib = attrib
     self.exist = False
     self.alive = False
     self.queue = EntityQueue()
     self.life = self.live()
Пример #2
0
class TiledMap:
    def __init__(self, width = 0, height = 0, attrib = {}):
        self.width = width
        self.height = height
        
        self.clear()
        
        self.attrib = attrib
        self.exist = False
        self.alive = False
        self.queue = EntityQueue()
        self.life = self.live()
    
    def check(self):
        '''Compatibility with entity.Entity'''
        pass
    
    def setAlive(self, val=True):
        self.alive = val
    
    def attr(self, name):
        # TODO
        return self.attrib[name]
    
    def setAttr(self, name, value):
        self.attrib[name] = value
    
    def fromXml(xmlRoot):
        self = TiledMap()
        self.loadXml(xmlRoot)
        return self
    
    # TODO: separate to loader/saver?..
    # TODO: other formats?
    def saveXml(self):
        xmlRoot = ET.Element('tiledmap', {'width':str(self.width), 'height':str(self.height)})
        for y in range(self.height):
            for x in range(self.width):
                aTile = self.getTile(x, y)
                if not aTile.empty():
                    xmlRoot.append(aTile.saveXml(x, y))
        return xmlRoot
    
    def loadXml(self, xmlRoot):
        if xmlRoot.tag != 'tiledmap':
            raise XmlLoadError(xmlRoot)
        
        try:
            self.width = int(xmlRoot.get('width'))
            self.height = int(xmlRoot.get('height'))
        except TypeError:
            raise XmlLoadError(xmlRoot)
        
        self.clear()
        
        for xmlTile in xmlRoot:
            try:
                x = int(xmlTile.attrib['x'])
                y = int(xmlTile.attrib['y'])
            except TypeError:
                raise XmlLoadError(xmlTile)
            
            self.getTile(x, y).loadXml(xmlTile)
    
    def resize(self, w, h):
        '''Set new map size & clear'''
        self.width = w
        self.height = h
        self.clear()
    
    def clear(self):
        '''Fill map with empty tiles'''
        def t(x, y):
            return tile.Tile(worldregistry.world.layers, worldregistry.world.layerOrder)
        self.content = self.genMap(t)
    
    def setExist(self, value):
        self.exist = value
    
    
    def genMap(self, func):
        '''Generate 2D array generated by func applied to every x, y'''
        if self.width < 0 or self.height < 0:
            raise TiledMapSizeError(self)
        return [[func(x, y) for x in range(self.width)] for y in range(self.height)]
    
    def floodfill(self, x0, y0, func, worked=None):
        '''Apply func to floodfill area'''
        if not worked:
            worked = self.genMap(lambda x, y: False)
        
        todo = [(x0, y0)]
        while todo:
            x, y = todo.pop(0)
            if x < 0 or y < 0:
                continue
            try:
                worked[y][x]
            except IndexError:
                continue
            
            if not worked[y][x]:
                worked[y][x] = True
                if func(x, y):
                    # TODO: optimize
                    for dx in range(-1, 2):
                        for dy in range(-1, 2):
                            if dx or dy:
                                todo.append((x+dx, y+dy))
        return worked
    
    def raytrace(self, x0, y0, func, direct=None, sdir=None, applyToSelf=True, worked=None):
        '''Apply func to raytraced coords'''
        
        # TODO: optimize, it's very slow now
        
        if not worked:
            worked = self.genMap(lambda x, y: False)
        
        try:
            self.getTile(x0, y0)
        except TiledMapSizeError:
            return worked
        
        if worked[y0][x0]:
            return worked
        
        worked[y0][x0] = True
        
        if not applyToSelf or func(x0, y0):
            directs = []
            if not direct:
                for dx in range(-1, 2):
                    for dy in range(-1, 2):
                        if dx or dy:
                            directs.append((dx, dy))
            else:
                dx, dy = direct
                directs.append((dx, dy))
                if dx and dy:               # corner
                    directs.append((0, dy))
                    directs.append((dx, 0))
                elif not dx:
                    directs.append((dy, dy))
                    directs.append((-dy, dy))
                elif not dy:
                    directs.append((dx, dx))
                    directs.append((dx, -dx))
                else:
                    raise RuntimeError('(0, 0) direction')
            
            for d in directs:
                nsdir = sdir
                dx, dy = d
                dn = direct or (dx, dy)
                if direct and direct != (dx, dy):
                    if sdir and sdir != (dx, dy):
                        continue
                    nsdir = (dx, dy)
                    if sdir:
                        direct = sdir
                worked = self.raytrace(x0+dx, y0+dy, func, direct=dn, sdir=nsdir, worked=worked)
        return worked
    
    
    def getTile(self, x, y):
        '''Get tile from position; raise TiledMapSizeError if not on map'''
        if x < 0 or y < 0:
            raise TiledMapSizeError(self)
        try:
            return self.content[y][x]
        except IndexError:
            raise TiledMapSizeError(self)
        except TypeError:
            raise TypeError('getTile() arguments should be integers, got {0}'.format((x, y)))
    
    def getContent(self, x, y, position):
        '''Get content from (x,y)->position'''
        return self.getTile(x, y).get(position)
    
    def putOn(self, x, y, position, anEntity, queueChange = True):
        '''Put anEntity on specified position, including setting this position for entity'''
        if queueChange and anEntity.alive:
            self.queue.push(anEntity)
        
        anEntity.placeOn(self, x, y, position)
        aTile = self.getTile(x, y)
        aTile.put(position, anEntity)
    
    def setContent(self, x, y, position, anEntity):
        '''Force content into position; for use in map generators'''
        anEntity.placeOn(self, x, y, position)
        self.getTile(x, y).set(position, anEntity)
    
    def removeFromMap(self, anEntity, queueChange = True):
        '''Remove entity from map if it's on it'''
        if queueChange and anEntity.alive:
            self.queue.remove(anEntity)
        
        x = anEntity.x
        y = anEntity.y
        
        if x == None or y == None:
            raise entity.EntityCoordError(anEntity)
        
        position = anEntity.position
        anEntity.removeFrom(self, x, y, position)
        aTile = self.getTile(x, y)
        try:
            aTile.remove(anEntity, position)
        except PositionEntityError:
            logging.warning('trying to remove entity from aTile which doesn\'t contain it')
            logging.debug('aTile: {0}, entity: {1}, position: {2}'.format(aTile, anEntity, position))
    
    def moveTo(self, anEntity, x, y, position):
        try:
            self.removeFromMap(anEntity, queueChange=False)
        except entity.EntityCoordError:
            logging.warning('moveTo() called when entity had no position')
        self.putOn(x, y, position, anEntity, queueChange=False)
    
    def createEntity(self, attrib, x, y, position):
        anEntity = entity.Entity(attrib)
        self.putOn(x, y, position, anEntity)
        return anEntity
    
    def step(self):
        '''Do one step of map live'''
        if not self.exist:
            self.notifyEmpty()
            return False
        if self.alive:
            return next(self.life)
        else:
            return False
    
    def notifyEmpty(self):
        '''Notify about map being empty, not generated yet'''
        for watcher in worldregistry.world.mapWatchers:
            watcher.notify(self, 'empty')
    
    def live(self):
        '''Generator-style function yilding entity.live() or False in case of queue reloading'''
        while True:
            try:
                anEntity = self.queue.pop()
                anEntity.check()
            except EmptyQueueError:
                # reload queue
                self.queue.reset()
                yield False
            except BaseEntityDeadError:
                self.removeFromMap(anEntity)
            except Exception as err:
                logging.error('Error in TiledMap.live():')
                logging.debug(traceback.format_exc())
            else:
                try:
                    yield anEntity.live()
                except Exception as err:
                    logging.error('Error in TiledMap.live -> anEntity.live():')
                    logging.debug(traceback.format_exc())