#gamemap.py import re import heapq import xml.etree.ElementTree as ET import ruamel.yaml import math as _mt from ruamel.yaml.comments import CommentedMap # for loading classes class Thing(object): def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None): self.thingType = thingType self.name = name self.description = description self.x = x self.y = y self.playerx = x self.playery = y if playerx: self.playerx = playerx if playery: self.playery = playery self.passable = bool(flags & 1) self.talkable = bool(flags & 2) self.lookable = bool(flags & 4) self.takeable = bool(flags & 8) self.useable = bool(flags & 16) self.graphic = ('clear', '#7F7F7F', ' ') self.thingID = -1 # ID not assigned def __str__(self): """__str__ is used for look.""" return self.description def __eq__(self, other): if not isinstance(other, Thing): return False return self.name == other.name class Item(Thing): yaml_flag = u'!Item' defaultGraphic = ('clear', '#00BF00', '^') def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic): super(Item, self).__init__('i', name, x, y, description, 13) self.useFunc = useFunc self.useOnFunc = useOnFunc self.customValues = customValues self.ranged = ranged self.graphic = graphic def use(self): pass @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description} # save graphic graphic = {} if node.graphic[0] != Item.defaultGraphic[0]: graphic['bgc'] = node.graphic[0] if node.graphic[1] != Item.defaultGraphic[1]: graphic['fgc'] = node.graphic[1] if node.graphic[2] != Item.defaultGraphic[2]: graphic['shape'] = node.graphic[2] if len(graphic) > 0: ret['graphic'] = graphic # save use functions if node.useFunc != '': ret['useFunc'] = node.useFunc if node.useOnFunc != '': ret['useOnFunc'] = node.useOnFunc if len(node.customValues) > 0: ret['customValues'] = node.customValues if node.ranged: ret['ranged'] = node.ranged return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments useFunc = '' useOnFunc = '' customValues = {} ranged = False bgc = Item.defaultGraphic[0] fgc = Item.defaultGraphic[1] shape = Item.defaultGraphic[2] # load graphic if 'graphic' in parts: if 'bgc' in parts['graphic']: bgc = parts['graphic']['bgc'] if 'fgc' in parts['graphic']: fgc = parts['graphic']['fgc'] if 'shape' in parts['graphic']: shape = parts['graphic']['shape'] graphic = (bgc, fgc, shape) # load use functions if 'useFunc' in parts: useFunc = parts['useFunc'] if 'useOnFunc' in parts: useOnFunc = parts['useOnFunc'] if 'customValues' in parts: customValues = dict(parts['customValues']) for v in customValues: if isinstance(customValues[v], tuple): customValues[v] = list(customValues[v]) if 'ranged' in parts: useOnFunc = parts['ranged'] return cls(parts['name'], parts['location'][0], parts['location'][1], parts['description'], useFunc, useOnFunc, customValues, ranged, graphic) class Useable(Thing): yaml_flag = u'!Useable' defaultGraphic = ('clear', '#0000FF', '#') def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic): super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery) self.useFunc = useFunc self.customValues = customValues self.graphic = graphic @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description} # save graphic graphic = {} if node.graphic[0] != Useable.defaultGraphic[0]: graphic['bgc'] = node.graphic[0] if node.graphic[1] != Useable.defaultGraphic[1]: graphic['fgc'] = node.graphic[1] if node.graphic[2] != Useable.defaultGraphic[2]: graphic['shape'] = node.graphic[2] if len(graphic) > 0: ret['graphic'] = graphic # save use functions if node.useFunc != '': ret['useFunc'] = node.useFunc if len(node.customValues) > 0: ret['customValues'] = node.customValues if node.x != node.playerx or node.y != node.playery: ret['useLocation'] = (node.playerx, node.playery) return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments useFunc = '' customValues = {} playerx, playery = parts['location'] bgc = Useable.defaultGraphic[0] fgc = Useable.defaultGraphic[1] shape = Useable.defaultGraphic[2] # load graphic if 'graphic' in parts: if 'bgc' in parts['graphic']: bgc = parts['graphic']['bgc'] if 'fgc' in parts['graphic']: fgc = parts['graphic']['fgc'] if 'shape' in parts['graphic']: shape = parts['graphic']['shape'] graphic = (bgc, fgc, shape) # load use functions if 'useFunc' in parts: useFunc = parts['useFunc'] if 'customValues' in parts: customValues = dict(parts['customValues']) for v in customValues: if isinstance(customValues[v], tuple): customValues[v] = list(customValues[v]) if 'useLocation' in parts: playerx, playery = parts['useLocation'] return cls(parts['name'], parts['location'][0], parts['location'][1], parts['description'], useFunc, customValues, playerx, playery, graphic) class NPC(Thing): yaml_flag = u'!NPC' defaultGraphic = ('clear', '#000000', 'o') def __init__(self, name, x: int, y: int, description: str, behavior: str, inventory: list, customValues: dict, playerx = None, playery = None, following = False, graphic = defaultGraphic): super(NPC, self).__init__('c', name, x, y, description, 6, playerx, playery) self.behavior = behavior self.following = following self.inventory = inventory self.customValues = customValues self.graphic = graphic self.behaveEvent = None def give(self, item): self.inventory.append(item) def drop(self, index): return self.inventory.pop(index) def dialog(self): pass @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description, 'behavior': node.behavior} # save graphic graphic = {} if node.graphic[0] != NPC.defaultGraphic[0]: graphic['bgc'] = node.graphic[0] if node.graphic[1] != NPC.defaultGraphic[1]: graphic['fgc'] = node.graphic[1] if node.graphic[2] != NPC.defaultGraphic[2]: graphic['shape'] = node.graphic[2] if len(graphic) > 0: ret['graphic'] = graphic # save use functions if node.following: ret['following'] = node.following if len(node.inventory) > 0: ret['inventory'] = node.inventory if len(node.customValues) > 0: ret['customValues'] = node.customValues if node.x != node.playerx or node.y != node.playery: ret['useLocation'] = (node.playerx, node.playery) return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments following = False inventory = [] customValues = {} playerx, playery = parts['location'] bgc = NPC.defaultGraphic[0] fgc = NPC.defaultGraphic[1] shape = NPC.defaultGraphic[2] # load graphic if 'graphic' in parts: if 'bgc' in parts['graphic']: bgc = parts['graphic']['bgc'] if 'fgc' in parts['graphic']: fgc = parts['graphic']['fgc'] if 'shape' in parts['graphic']: shape = parts['graphic']['shape'] graphic = (bgc, fgc, shape) # load use functions if 'following' in parts: useFunc = parts['following'] if 'inventory' in parts: inventory = parts['inventory'] if 'customValues' in parts: customValues = dict(parts['customValues']) for v in customValues: if isinstance(customValues[v], tuple): customValues[v] = list(customValues[v]) if 'useLocation' in parts: playerx, playery = parts['useLocation'] return cls(parts['name'], parts['location'][0], parts['location'][1], parts['description'], parts['behavior'], inventory, customValues, playerx, playery, following, graphic) class Door(Thing): yaml_flag = u'!Door' defaultGraphic = ('clear', '#7F3F00', '#') def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic): self.descBase = description if description == None: if locked: description = "The {0} is locked.".format(name) else: description = "The {0} is unlocked.".format(name) else: if locked: description += " It is locked.".format(name) else: description += " It is unlocked.".format(name) super(Door, self).__init__('d', name, x, y, description, 1) self.passable = not locked self.key = key self.graphic = graphic def lock(self, key = None): if key == self.key: self.passable = not self.passable if self.descBase == None: if self.passable: self.description = "The {0} is unlocked.".format(self.name) else: self.description = "The {0} is locked.".format(self.name) else: if self.passable: self.description += " It is unlocked.".format(self.name) else: self.description += " It is locked.".format(self.name) return True return False @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'name': node.name, 'location': (node.x, node.y)} # save graphic graphic = {} if node.graphic[0] != Door.defaultGraphic[0]: graphic['bgc'] = node.graphic[0] if node.graphic[1] != Door.defaultGraphic[1]: graphic['fgc'] = node.graphic[1] if node.graphic[2] != Door.defaultGraphic[2]: graphic['shape'] = node.graphic[2] if len(graphic) > 0: ret['graphic'] = graphic # save door state if node.passable: ret['locked'] = not node.passable if node.descBase != None: ret['description'] = node.descBase if node.key != None: ret['key'] = node.key return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments description = None locked = False key = None bgc = Door.defaultGraphic[0] fgc = Door.defaultGraphic[1] shape = Door.defaultGraphic[2] # load graphic if 'graphic' in parts: if 'bgc' in parts['graphic']: bgc = parts['graphic']['bgc'] if 'fgc' in parts['graphic']: fgc = parts['graphic']['fgc'] if 'shape' in parts['graphic']: shape = parts['graphic']['shape'] graphic = (bgc, fgc, shape) # load door state if 'description' in parts: description = parts['description'] if 'locked' in parts: locked = parts['locked'] if 'key' in parts: key = parts['key'] return cls(parts['name'], parts['location'][0], parts['location'][1], locked, description, key, graphic) class MapExit(Thing): yaml_flag = u'!MapExit' defaultGraphic = ('clear', '#FF0000', 'x') def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix: None, graphic = defaultGraphic): description = name if prefix: description = "{0} {1}".format(prefix, name) super(MapExit, self).__init__('x', name, x, y, description, 5) self.exitid = exitid self.destination = destination self.prefix = prefix self.graphic = graphic @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination} # save graphic graphic = {} if node.graphic[0] != MapExit.defaultGraphic[0]: graphic['bgc'] = node.graphic[0] if node.graphic[1] != MapExit.defaultGraphic[1]: graphic['fgc'] = node.graphic[1] if node.graphic[2] != MapExit.defaultGraphic[2]: graphic['shape'] = node.graphic[2] if len(graphic) > 0: ret['graphic'] = graphic if node.prefix != None: ret['prefix'] = node.prefix return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments prefix = None bgc = MapExit.defaultGraphic[0] fgc = MapExit.defaultGraphic[1] shape = MapExit.defaultGraphic[2] # load graphic if 'graphic' in parts: if 'bgc' in parts['graphic']: bgc = parts['graphic']['bgc'] if 'fgc' in parts['graphic']: fgc = parts['graphic']['fgc'] if 'shape' in parts['graphic']: shape = parts['graphic']['shape'] graphic = (bgc, fgc, shape) if 'prefix' in parts: prefix = parts['prefix'] return cls(parts['name'], parts['location'][0], parts['location'][1], parts['id'], parts['destination'], prefix, graphic) class MapEntrance(Thing): yaml_flag = u'!MapEntrance' defaultGraphic = ('clear', 'clear', 'x') # no graphic - should not be drawn def __init__(self, x: int, y: int, exitid: int, name = None): if name == None: name = 'entrance {}'.format(exitid) super(MapEntrance, self).__init__('a', name, x, y, description, 1) self.exitid = exitid @classmethod def to_yaml(cls, representer, node): # save usual things ret = {'location': (node.x, node.y), 'id': node.exitid} return representer.represent_mapping(cls.yaml_flag, ret) @classmethod def from_yaml(cls, constructor, node): parts = CommentedMap() constructor.construct_mapping(node, parts, True) # set default values for optional arguments return cls(parts['location'][0], parts['location'][1], parts['id']) class MapError(RuntimeError): pass class GameMap(object): # Matrix tile codes: # e: empty (0) # w: wall (0) # regular expressions tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])') matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+') def __init__(self, name, graph, matrix, dimensions): self.name = name self.openingText = "" self.mapGraph = graph self.mapMatrix = matrix self.dimensions = dimensions self.things = {} # int thingID : thing self.thingPos = {} # int location : list of int thingIDs self.thingNames = {} # str name: list of int thingIDs self.playerStart = (1, 1) self.description = "The area is completely blank." self.floorColors = [] self.wallColors = [] self.persistent = [] self.enterScript = '' @staticmethod def __cleanStr(text: str, end = '\n'): if text == None: return text text = text.strip() i = 0 while True: i = text.find('\n', i) if i == -1: break j = i+1 if len(text) > j: while text[j] in ' \t': j += 1 # just find the first non-space chartacter text = text[:i+1] + text[j:] i += 1 else: break return text.replace('\n', end) @staticmethod def convert(infile: str): """Convert an XML map to a YAML one.""" data = None with open(infile, 'r') as f: data = f.read() info = ET.fromstring(data) layout = info.find('layout') ret = {} if layout == None: raise MapError('No layout in {0}.'.format(infile)) # "Layout" needs some work before we convert it. ret['layout'] = GameMap.__cleanStr(layout.text.strip()) # now the rest of the things if 'openingText' in info.attrib: ret['openingText'] = info.attrib['openingText'] else: raise MapError('No opening text in {0}.'.format(infile)) if 'playerStart' in info.attrib: ps = info.attrib['playerStart'].split(',') ret['playerStart'] = (int(ps[0]), int(ps[1])) else: raise MapError('No player start position in {0}.'.format(infile)) if info.text != None: ret['description'] = GameMap.__cleanStr(info.text, ' ') else: raise MapError('No description in {0}.'.format(infile)) # get map colors floorColors = ['#9F7F5F'] floorColorsStr = info.find('floorColors') if floorColorsStr != None: floorColors = floorColorsStr.text.lstrip().split() if len(floorColors) == 0: floorColors.append('#9F7F5F') ret['floorColors'] = floorColors wallColors = ['#7F3F0F'] wallColorsStr = info.find('wallColors') if wallColorsStr != None: wallColors = wallColorsStr.text.lstrip().split() if len(wallColors) == 0: wallColors.append('#7F3F0F') ret['wallColors'] = wallColors # get things ret['loadAlways'] = [] ret['loadOnce'] = [] for node in info: if node.tag == 'loadOnce': # Things in the load-once section are only loaded the first # time that the map is loaded, and saved in the player's # save file. for node1 in node: if node1.tag == 'door': ret['loadOnce'].append(GameMap.__loadDoor(node1)) elif node1.tag == 'useable': ret['loadOnce'].append(GameMap.__loadUseable(node1)) elif node1.tag == 'item': ret['loadOnce'].append(GameMap.__loadItem(node1)) elif node.tag == 'exit': ret['loadAlways'].append(GameMap.__loadExit(None, node, -1)) # weird arguments: there is no actual level elif node.tag == 'door': ret['loadAlways'].append(GameMap.__loadDoor(node)) elif node.tag == 'useable': ret['loadAlways'].append(GameMap.__loadUseable(node)) #start saving outfile = infile[:-3] + 'yml' yaml = ruamel.yaml.YAML() yaml.indent(mapping=4, sequence=4, offset=2) yaml.register_class(Item) yaml.register_class(Useable) yaml.register_class(Door) yaml.register_class(MapExit) with open(outfile, 'w') as f: f.write('%YAML 1.2\n---\n') yaml.dump(ret, f) @staticmethod def read(infile = None, prevMap = None, preLoaded = False, nextThing = 0): """Read map data and return a Map object. If infile is not provided, then it will read from stdin. Otherwise, it should be a valid file name. Entering a map through stdin will be obsolete once testing is over.""" info = None tryToRead = True yaml = ruamel.yaml.YAML() yaml.register_class(Item) yaml.register_class(Useable) yaml.register_class(NPC) yaml.register_class(Door) yaml.register_class(MapExit) if infile != None: try: with open(infile, 'r') as f: info = yaml.load(f) except OSError as e: print("The file could not be read.") return None, nextThing else: raise RuntimeError("No file was specified for loading.") # Now what we do with the data mat = None if 'layout' not in info: raise MapError('No layout in {0}.'.format(infile)) layout = info['layout'] if layout != None: #print(layout.text) match = GameMap.matrixRegex.match(layout.lstrip()) if match == None: raise RuntimeError('Map read a file without a map matrix.') mat = match.group() else: raise RuntimeError('Map read a file without a map matrix.') # generate matrix and graph first mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat) level = GameMap(infile, mapGraph, mapMatrix, dimensions) # Now, load other info nextThing = GameMap.loadThings(level, info, prevMap, preLoaded, nextThing) return level, nextThing @staticmethod def parseMatrix(matrixStr): """Returns a map graph as an adjacency list, as well as the matrix as a list of lists of tuples.""" # Make the matrix first mat = [[]] x = 0 y = 0 l = 0 while len(matrixStr) > 0: tile = GameMap.tileRegex.match(matrixStr) if tile != None: tileType = tile.group(1) tileNum = tile.group(2) if tileType == ' ': tileType = 'e' if tileNum == ' ': tileNum = '0' mat[l].append((tileType, int(tileNum))) #x += 1 matrixStr = matrixStr[len(tile.group()):] elif matrixStr[0] == '\n': if x == 0: x = len(mat[l]) elif x != len(mat[l]): raise RuntimeError("Map matrix has jagged edges.") l += 1 #x = 0 mat.append([]) i = 1 while i < len(matrixStr) and matrixStr[i] in ' \t\n': i += 1 if i == len(matrixStr): matrixStr = '' else: matrixStr = matrixStr[i:] else: # This should happen when it finishes? raise RuntimeError("Unexpected token in map matrix: '{0}'".format(matrixStr)) y = len(mat) - 1 # Now for the graph numTiles = x * y dim = (x, y) graph = [[] for j in range(numTiles)] passable = ('e', 'd', 'x', 'p', 'i') for j in range(y): for i in range(x): if mat[j][i][0] in passable: here = GameMap.__coordsToInt(i, j, x) if i > 0 and mat[j][i-1][0] in passable: there = GameMap.__coordsToInt(i-1, j, x) graph[here].append(there) graph[there].append(here) if j > 0 and mat[j-1][i][0] in passable: there = GameMap.__coordsToInt(i, j-1, x) graph[here].append(there) graph[there].append(here) return mat, graph, dim @staticmethod def loadThings(level, info, prevMap = None, preLoaded = False, nextThing = 0): """load the things from the xml part of the map file.""" if 'openingText' in info: level.openingText = info['openingText'] if 'playerStart' in info: level.playerStart = info['playerStart'] if 'description' in info: level.description = info['description'] if 'enterScript' in info: level.enterScript = info['enterScript'] # get map colors if 'floorColors' in info: level.floorColors = info['floorColors'] if 'wallColors' in info: level.wallColors = info['wallColors'] if len(level.floorColors) == 0: level.floorColors.append('#9F7F5F') if len(level.wallColors) == 0: level.wallColors.append('#7F3F0F') # get things hasKnownEntrance = False if 'loadOnce' in info and not preLoaded: for thing in info['loadOnce']: #print(type(thing)) nextThing = level.addThing(thing, nextThing, True) if 'loadAlways' in info: for thing in info['loadAlways']: #print(type(thing)) nextThing = level.addThing(thing, nextThing) if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid: level.playerStart = (thing.x, thing.y) hasKnownEntrance = True return nextThing # stuff the gameshell itself might use def addThing(self, thing, nextThing = 0, persist = False): if thing == None: return nextThing #if thing.name in self.thingNames: # resolved # raise ValueError("Cannot have two objects named {0}.".format(thing.name)) if thing.thingID == -1: # This is to ensure that we don't double up IDs. thing.thingID = nextThing nextThing += 1 # Some things, like containers, have other things as custom values, # so they need IDs as well. if thing.thingType in 'iuc': nextThing = self.addThingRecursive(thing.customValues, nextThing) if thing.thingType == 'c': for i in thing.inventory: i.thingID = nextThing nextThing += 1 pos = self.coordsToInt(thing.x, thing.y) if pos not in self.thingPos: self.thingPos[pos] = [thing.thingID] else: self.thingPos[pos].append(thing.thingID) if thing.name not in self.thingNames: self.thingNames[thing.name] = [thing.thingID] else: self.thingNames[thing.name].append(thing.thingID) self.things[thing.thingID] = thing if persist: self.persistent.append(thing.thingID) return nextThing def addThingRecursive(self, container, nextThing = 0): if isinstance(container, Thing): container.thingID = nextThing return nextThing + 1 elif isinstance(container, dict): for i in container: nextThing = self.addThingRecursive(container[i], nextThing) return nextThing elif isinstance(container, list): for i in container: nextThing = self.addThingRecursive(i, nextThing) return nextThing else: return nextThing def getThing(self, **kwargs): if 'name' in kwargs: return self.getThingByName(kwargs['name']) elif 'thingID' in kwargs: return self.getThingByID(kwargs['thingID']) elif 'pos' in kwargs: return self.getThingAtPos(kwargs['pos']) elif 'coords' in kwargs: return self.getThingAtCoords(kwargs['coords'][0], kwargs['coords'][1]) else: raise ValueError('Thing cannot be found by {}.'.format(str(kwargs))) def removeThing(self, **kwargs): if 'name' in kwargs: return self.removeThingByName(kwargs['name']) elif 'thingID' in kwargs: return self.removeThingByID(kwargs['thingID']) elif 'pos' in kwargs: return self.removeThingAtPos(kwargs['pos']) elif 'coords' in kwargs: return self.removeThingAtCoords(kwargs['coords'][0], kwargs['coords'][1]) else: raise ValueError('Thing cannot be found by {}.'.format(str(kwargs))) def path(self, x1, y1, x2, y2, closeEnough = True): startThing = self.getThingAtCoords(x1, y1) if not closeEnough: if startThing and not startThing.passable: return -1, [] # meaning you can't get there dist, prev = self.dijkstra(x1, y1, closeEnough) endPoint = self.coordsToInt(x2, y2) numVertex = self.dimensions[0] * self.dimensions[1] if dist[endPoint] < numVertex + 1: return dist[endPoint], prev else: return -1, [] # meaning you can't get there def dijkstra(self, x1, y1, closeEnough = True): """Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2) The closeEnough parameter will create a path that lands beside the source if necessary.""" # first test to see that the start point is passable startThing = self.getThingAtCoords(x1, y1) startPoint = self.coordsToInt(x1, y1) #endPoint = self.coordsToInt(x2, y2) numVertex = self.dimensions[0] * self.dimensions[1] dist = [numVertex + 1 for i in range(numVertex)] prev = [-1 for i in range(numVertex)] dist[startPoint] = 0 if closeEnough: if startThing and not startThing.passable: dist[startPoint] = -1 queue = [] heapq.heappush(queue, (dist[startPoint], startPoint)) while len(queue) > 0: u = heapq.heappop(queue)[1] for v in self.mapGraph[u]: thing = self.getThingAtPos(v) if thing and not thing.passable: continue tempDist = dist[u] + 1 if tempDist < dist[v]: dist[v] = tempDist if dist[u] != -1: prev[v] = u heapq.heappush(queue, (dist[v], v)) return dist, prev #if dist[endPoint] < numVertex + 1: # return dist[endPoint], prev #else: # return -1, [] # meaning you can't get there def lineOfSight(self, x1, y1, x2, y2): Dx = x2 - x1 Dy = y2 - y1 y = y1 + 0.5 x = x1 + 0.5 lst = [] if abs(Dx) >= abs(Dy): if Dx < 0: x = x2 + 0.5 y = y2 + 0.5 dy = Dy / Dx while(x < max(x2, x1) - 0.5): x += 1 lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y))) if _mt.floor(y) != _mt.floor(y + dy): lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y + dy))) y += dy elif abs(Dx) < abs(Dy): if Dy < 0: x = x2 + 0.5 y = y2 + 0.5 dx = Dx / Dy while(y < max(y2, y1) - 0.5): y += 1 lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y))) if _mt.floor(x) != _mt.floor(x + dx): lst.append(self.coordsToInt(_mt.floor(x + dx), _mt.floor(y))) x += dx # Here is where we actually check: for space in lst: if not self.isPassable(space): return False return True def isPassable(self, x, y = -1): pos = x if y == -1: x, y = self.intToCoords(x) else: pos = self.coordsToInt(x, y) if self.mapMatrix[y][x][0] == 'w': return False thingsInSpace = self.getThingsAtPos(pos) for thing in thingsInSpace: if not thing.passable: return False return True @staticmethod def __coordsToInt(x, y, width): return x + y * width def coordsToInt(self, x: int, y: int, width = -1): if width < 0: return x + y * self.dimensions[0] else: return x + y * width def intToCoords(self, pos: int): return pos % self.dimensions[0], int(pos / self.dimensions[0]) def getThingAtCoords(self, x, y): return self.getThingAtPos(self.coordsToInt(x, y)) def getThingsAtCoords(self, x, y): return self.getThingsAtPos(self.coordsToInt(x, y)) def getThingAtPos(self, pos): if pos in self.thingPos: return self.things[self.thingPos[pos][0]] else: return None def getThingsAtPos(self, pos): if pos in self.thingPos: ret = [] for i in self.thingPos[pos]: ret.append(self.things[i]) return ret else: return [] def getThingByName(self, name): if name in self.thingNames: return self.things[self.thingNames[name][0]] else: return None def getThingsByName(self, name): if name in self.thingNames: ret = [] for i in self.thingNames[name]: ret.append(self.things[i]) return ret else: return [] def getThingByID(self, thingID): if thingID in self.things: return self.things[thingID] else: return None def removeThingByThing(self, thing): if thing != None: oldPos = self.coordsToInt(thing.x, thing.y) if oldPos in self.thingPos: self.thingPos[oldPos].remove(thing.thingID) if len(self.thingPos[oldPos]) == 0: del self.thingPos[oldPos] oldName = thing.name if oldName in self.thingNames: self.thingNames[oldName].remove(thing.thingID) if len(self.thingNames[oldName]) == 0: del self.thingNames[oldName] if thing.thingID in self.persistent: self.persistent.remove(thing.thingID) del self.things[thing.thingID] return thing else: return None def removeThingAtCoords(self, x, y): return self.removeThingAtPos(self.coordsToInt(x, y)) def removeThingsAtCoords(self, x, y): return self.removeThingsAtPos(self.coordsToInt(x, y)) def removeThingAtPos(self, pos): if pos in self.thingPos: return self.removeThingByThing(self.getThingAtPos(pos)) else: return None def removeThingsAtPos(self, pos): if pos in self.thingPos: ret = [] for i in self.thingPos[pos]: ret.append(self.removeThingByThing(self.things[i])) return ret else: return [] def removeThingByName(self, name): if name in self.thingNames: return self.removeThingByThing(self.getThingByName(name)) else: return None def removeThingsByName(self, name): if name in self.thingNames: ret = [] for i in self.thingNames[name]: ret.append(self.removeThingByThing(self.things[i])) return ret else: return [] def removeThingByID(self, thingID): if thingID in self.things: return self.removeThingByThing(self.things[thingID]) else: return None def moveThing(self, thing, x, y = -1): newPos = x if y != -1: newPos = self.coordsToInt(x, y) else: x, y = self.intToCoords(x) if thing: oldPos = self.coordsToInt(thing.x, thing.y) if oldPos in self.thingPos: self.thingPos[oldPos].remove(name) if len(self.thingPos[oldPos]) == 0: del self.thingPos[oldPos] if newPos not in self.thingPos: self.thingPos[newPos] = [name] else: self.thingPos[newPos].append(name) relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y thing.x, thing.y = x, y thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery else: raise RuntimeError("There is nothing to move.".format(name))