From 267f0e9123ab1cccad9a3096050d0370bfc13385 Mon Sep 17 00:00:00 2001 From: Patrick Marsee Date: Wed, 15 May 2019 01:37:35 -0400 Subject: [PATCH] Things are now identified by unique integer IDs rather than by name, allowing multiple things to go by the same name. --- gamebase.py | 9 +- gamemap.py | 422 ++++++++++++++++------------------------------------ 2 files changed, 133 insertions(+), 298 deletions(-) diff --git a/gamebase.py b/gamebase.py index ff57ee7..06cc4bd 100644 --- a/gamebase.py +++ b/gamebase.py @@ -27,6 +27,7 @@ class GameBase(object): self.eventQueue = [] self.gameTime = 0.0 self.skipLoop = True + self.nextThing = 0 # player info self.playerx = -1 @@ -413,9 +414,9 @@ Object can be the name of the object, or its coordinates.""" # load the new level if len(args) == 2: - self.level = _gm.GameMap.read(args[0], int(args[1]), preLoaded) + self.level, self.nextThing = _gm.GameMap.read(args[0], int(args[1]), preLoaded, self.nextThing) else: - self.level = _gm.GameMap.read(args[0], preLoaded) + self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing) # get persistent things from it if args[0] in self.persist: @@ -451,7 +452,7 @@ Object can be the name of the object, or its coordinates.""" self.persist[self.level.name][i] = self.level.getThingByName(i) # build data object to be saved - data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.gameTime) + data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.gameTime, self.nextThing) # save it! fileName = 'saves/' + args[0].replace(' ', '_') + '.dat' @@ -482,7 +483,7 @@ Object can be the name of the object, or its coordinates.""" fileName = args[0] x, y, levelname = 1, 1, 'testing/test1.txt' with open(fileName, 'rb') as f: - self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime = _pi.load(f) + self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime, self.nextThing = _pi.load(f) #print(levelname, x, y, file = self.outstream) self.loadMap((levelname, x, y)) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) diff --git a/gamemap.py b/gamemap.py index 60937d9..27be546 100644 --- a/gamemap.py +++ b/gamemap.py @@ -25,6 +25,7 @@ class Thing(object): 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.""" @@ -361,8 +362,9 @@ class GameMap(object): self.mapGraph = graph self.mapMatrix = matrix self.dimensions = dimensions - self.thingPos = {} # int location : list of names - self.thingNames = {} # Things can be looked up by name. + 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 = [] @@ -470,7 +472,7 @@ class GameMap(object): yaml.dump(ret, f) @staticmethod - def read(infile = None, prevMap = None, preLoaded = False): + 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.""" @@ -516,9 +518,9 @@ Entering a map through stdin will be obsolete once testing is over.""" level = GameMap(infile, mapGraph, mapMatrix, dimensions) # Now, load other info - GameMap.loadThings(level, info, prevMap, preLoaded) + nextThing = GameMap.loadThings(level, info, prevMap, preLoaded, nextThing) - return level + return level, nextThing @staticmethod def parseMatrix(matrixStr): @@ -581,7 +583,7 @@ list of lists of tuples.""" return mat, graph, dim @staticmethod - def loadThings(level, info, prevMap = None, preLoaded = False): + 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'] @@ -604,303 +606,62 @@ list of lists of tuples.""" if 'loadOnce' in info and not preLoaded: for thing in info['loadOnce']: #print(type(thing)) - level.addThing(thing, True) + nextThing = level.addThing(thing, nextThing, True) if 'loadAlways' in info: for thing in info['loadAlways']: #print(type(thing)) - level.addThing(thing) + nextThing = level.addThing(thing, nextThing) if thing.thingType == 'x' and prevMap == thing.exitid: level.playerStart = (thing.x, thing.y) - - @staticmethod - def __loadExit(level, node, prevMap): - exitid = 0 - x, y = 0, 0 - destination = '' - name = '' - prefix = None - if 'name' in node.attrib: - name = node.attrib['name'] - else: - print("Nameless exit, ommitting.") - return None - if 'id' in node.attrib: - exitid = int(node.attrib['id']) - else: - print("Exit '{0}' with no id, ommitting.".format(name)) - return None - if 'location' in node.attrib: - loc = node.attrib['location'].split(',') - x, y = int(loc[0]), int(loc[1]) - else: - print("Exit '{0}' without a location, ommitting.".format(name)) - return None - if 'destination' in node.attrib: - destination = node.attrib['destination'] - else: - print("Exit '{0}' with no destination, ommitting.".format(name)) - return None - if 'prefix' in node.attrib: - prefix = node.attrib['prefix'] - graphic = GameMap.__loadGraphic(node) - #level.addThing(MapExit(name, x, y, exitid, destination, prefix)) - if prevMap == exitid: - level.playerStart = (x, y) - return MapExit(name, x, y, exitid, destination, prefix, graphic) - - @staticmethod - def __loadDoor(node): - x, y = 0, 0 - name = '' - locked = False - doorOpen = False - key = None - if 'name' in node.attrib: - name = node.attrib['name'] - else: - print("Nameless door, ommitting.") - return None - if 'location' in node.attrib: - pos = node.attrib['location'].split(',') - x, y = int(pos[0]), int(pos[1]) - else: - print("Door '{0}' without a location, ommitting.".format(name)) - return None - if 'locked' in node.attrib: - if node.attrib['locked'] == 'True': - locked = True - if 'open' in node.attrib: - if node.attrib['open'] == 'True': - doorOpen = True - if 'key' in node.attrib: - key = node.attrib['key'] - graphic = GameMap.__loadGraphic(node) - description = GameMap.__cleanStr(node.text, ' ') - return Door(name, x, y, locked, description, key, graphic) - - @staticmethod - def __loadUseable(node): - x, y = 0, 0 - playerx, playery = 0, 0 - name = '' - useFunc = '' - description = '' - if 'name' in node.attrib: - name = node.attrib['name'] - else: - print("Nameless useable, ommitting.") - return None - if 'location' in node.attrib: - pos = node.attrib['location'].split(',') - x, y = int(pos[0]), int(pos[1]) - else: - print("Useable '{0}' without a location, ommitting.".format(name)) - return None - if 'useLocation' in node.attrib: - loc = node.attrib['useLocation'].split(',') - playerx, playery = int(loc[0]), int(loc[1]) - else: - playerx, playery = x, y - if 'useFunc' in node.attrib: - useFunc = node.attrib['useFunc'] - else: - print("Unuseable useable '{0}', ommitting.".format(name)) - return None - graphic = GameMap.__loadGraphic(node) - description = GameMap.__cleanStr(node.text, ' ') - - # get custom values - customVals = {} - for val in node: - if val.tag == 'graphic': - continue # skip it - value = None - valName = '' - if 'name' in val.attrib: - valName = val.attrib['name'] - else: - print("Nameless custom value in {0}, ommitting.".format(name)) - continue - if 'value' in val.attrib: - if val.tag == 'int': - value = int(val.attrib['value']) - elif val.tag == 'float': - value = float(val.attrib['value']) - elif val.tag == 'str': - value = str(val.attrib['value']) - elif val.tag == 'item': - value = GameMap.__loadItem(val) - elif val.tag == 'intList': - value = [int(i) for i in val.attrib['value'].split(',')] - elif val.tag == 'floatList': - value = [float(i) for i in val.attrib['value'].split(',')] - elif val.tag == 'strList': - value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas. - else: - print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag)) - continue - elif val.tag == 'list': - value = [] - for i in val: - if 'value' in i.attrib: - if i.tag == 'int': - value.append(int(i.attrib['value'])) - elif i.tag == 'float': - value.append(float(i.attrib['value'])) - elif i.tag == 'str': - value.append(str(i.attrib['value'])) - elif i.tag == 'item': - value.append(GameMap.__loadItem(i)) - else: - print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag)) - continue - else: - print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name)) - continue - else: - print("Value {0} in {1} has no value, ommitting.".format(valName, name)) - continue - customVals[valName] = value - - return Useable(name, x, y, description, useFunc, customVals, playerx, playery, graphic) - - @staticmethod - def __loadItem(node): - x, y = 0, 0 - playerx, playery = 0, 0 - name = '' - useFunc = '' - useOnFunc = '' - description = '' - ranged = False - if 'name' in node.attrib: - name = node.attrib['name'] - else: - print("Nameless item, ommitting.") - return None - if 'location' in node.attrib: - pos = node.attrib['location'].split(',') - x, y = int(pos[0]), int(pos[1]) - else: - print("Item '{0}' without a location, ommitting.".format(name)) - return None - if 'useFunc' in node.attrib: - useFunc = node.attrib['useFunc'] - if 'useOnFunc' in node.attrib: - useOnFunc = node.attrib['useOnFunc'] - if 'ranged' in node.attrib: - ranged = (node.attrib['ranged'].casefold() == 'true') - graphic = GameMap.__loadGraphic(node) - description = GameMap.__cleanStr(node.text, ' ') - - # get custom values - customVals = {} - for val in node: - if val.tag == 'graphic': - continue # skip it - value = None - valName = '' - if 'name' in val.attrib: - valName = val.attrib['name'] - else: - print("Nameless custom value in {0}, ommitting.".format(name)) - continue - if 'value' in val.attrib: - if val.tag == 'int': - value = int(val.attrib['value']) - elif val.tag == 'float': - value = float(val.attrib['value']) - elif val.tag == 'str': - value = str(val.attrib['value']) - elif val.tag == 'item': - value = GameMap.__loadItem(val) - elif val.tag == 'intList': - value = [int(i) for i in val.attrib['value'].split(',')] - elif val.tag == 'floatList': - value = [float(i) for i in val.attrib['value'].split(',')] - elif val.tag == 'strList': - value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas. - else: - print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag)) - continue - elif val.tag == 'list': - value = [] - for i in val: - if 'value' in i.attrib: - if i.tag == 'int': - value.append(int(i.attrib['value'])) - elif i.tag == 'float': - value.append(float(i.attrib['value'])) - elif i.tag == 'str': - value.append(str(i.attrib['value'])) - elif i.tag == 'item': - value.append(GameMap.__loadItem(i)) - else: - print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag)) - continue - else: - print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name)) - continue - else: - print("Value {0} in {1} has no value, ommitting.".format(valName, name)) - continue - customVals[valName] = value - - return Item(name, x, y, description, useFunc, useOnFunc, customVals, ranged, graphic) - - @staticmethod - def __loadGraphic(node): - bgc = 'clear' - fgc = '#7F7F7F' - shape = '#' - graphicInDecl = False - if 'bgc' in node.attrib: - bgc = node.attrib['bgc'] - graphicInDecl = True - if 'fgc' in node.attrib: - fgc = node.attrib['fgc'] - graphicInDecl = True - if 'shape' in node.attrib: - shape = node.attrib['shape'] - graphicInDecl = True - - if not graphicInDecl: - #check for graphic child node - graphic = node.find('graphic') - if graphic != None: - if 'bgc' in graphic.attrib: - bgc = graphic.attrib['bgc'] - if 'fgc' in graphic.attrib: - fgc = graphic.attrib['fgc'] - if 'shape' in graphic.attrib: - shape = graphic.attrib['shape'] - return (bgc, fgc, shape) + return nextThing # stuff the gameshell itself might use - def addThing(self, thing, persist = False): + def addThing(self, thing, nextThing = 0, persist = False): if thing == None: - return - if thing.name in self.thingNames: - raise ValueError("Cannot have two objects named {0}.".format(thing.name)) + 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 pos = self.coordsToInt(thing.x, thing.y) if pos not in self.thingPos: - self.thingPos[pos] = [thing.name] + self.thingPos[pos] = [thing.thingID] else: - self.thingPos[pos].append(thing.name) - self.thingNames[thing.name] = thing + 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.name) + return nextThing - def removeThing(self, name): - thing = self.getThingByName(name) - 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] - del self.thingNames[name] + 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) @@ -975,7 +736,7 @@ The closeEnough parameter will create a path that lands beside the source if nec def getThingAtPos(self, pos): if pos in self.thingPos: - return self.thingNames[self.thingPos[pos][0]] + return self.things[self.thingPos[pos][0]] else: return None @@ -983,24 +744,97 @@ The closeEnough parameter will create a path that lands beside the source if nec if pos in self.thingPos: ret = [] for i in self.thingPos[pos]: - ret.append(self.thingNames[i]) + ret.append(self.things[i]) return ret else: return None def getThingByName(self, name): if name in self.thingNames: - return self.thingNames[name] + return self.things[self.thingNames[name][0]] else: return None - def moveThing(self, name, x, y = -1): + 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 None + + 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.thingPos: + self.thingNames[oldName].remove(thing.thingID) + if len(self.thingNames[oldName]) == 0: + del self.thingNames[oldName] + 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 None + + 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 None + + 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) - thing = self.getThingByName(name) if thing: oldPos = self.coordsToInt(thing.x, thing.y) if oldPos in self.thingPos: @@ -1013,5 +847,5 @@ The closeEnough parameter will create a path that lands beside the source if nec self.thingPos[newPos].append(name) thing.x, thing.y = x, y else: - raise RuntimeError("There is nothing by the name of {0}.".format(name)) + raise RuntimeError("There is nothing to move.".format(name))