From ed7d265b48b5288f8088420dbf1433784f3ed033 Mon Sep 17 00:00:00 2001 From: Patrick Marsee Date: Sat, 8 Jun 2019 13:42:00 -0400 Subject: [PATCH] various tweaks and fixes --- .gitignore | 2 + gamebase.py | 447 +++++++++++++++++++++++++++++++++------------------ gamemap.py | 39 +++-- gameshell.py | 202 +++++++++++++++++++---- shell.py | 46 ++++-- 5 files changed, 524 insertions(+), 212 deletions(-) diff --git a/.gitignore b/.gitignore index 7a2b14a..dceb081 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ maps saves +dialogs __pycache__ gameshell.geany gameexpr.py +game.py diff --git a/gamebase.py b/gamebase.py index 249b8cf..2d41c90 100644 --- a/gamebase.py +++ b/gamebase.py @@ -8,13 +8,19 @@ import random as _ra import sys as _sys import pickle as _pi import ruamel.yaml as _yaml +import textwrap as _tw class GameError(RuntimeError): pass +class _ScriptBreak(object): + + def __init__(self, value): + self.value = value + class GameBase(object): - coordRegex = _re.compile(r'((-?[a-zA-Z]+) ?(-?[0-9]+))|((-?[0-9]+),? (-?[0-9]+))|(\(([0-9]+), ([0-9]+)\))') + coordRegex = _re.compile(r'(?:(-?[a-zA-Z]+) ?(-?[0-9]+))|(?:(-?[0-9]+),? (-?[0-9]+))|(?:\(([0-9]+), ([0-9]+)\))') #coordRegex1 = _re.compile(r'(-?[a-zA-Z]+) ?(-?[0-9]+)') # "B12" or "B 12" #coordRegex2 = _re.compile(r'\(([0-9]+), ([0-9]+)\)') # "(2, 12)" #coordRegex3 = _re.compile(r'(-?[0-9]+),? (-?[0-9]+)') # "2 12" or "2, 12" @@ -61,12 +67,19 @@ class GameBase(object): self.registerUseFunc('container', self.container) self.registerUseFunc('info', self.info) self.registerUseFunc('wear', self.wear) + self.registerBehavior('stand', self.stand) self.registerBehavior('wander', self.wander) self.registerScript('if', self.ifScript) self.registerScript('printf', self.printfScript) + self.registerScript('get', self.getCustomValue) self.registerScript('set', self.setCustomValue) + self.registerScript('del', self.delCustomValue) self.registerScript('spawn', self.spawnThing) self.registerScript('give', self.giveToPlayer) + self.registerScript('move', self.moveThingScript) + self.registerScript('cvget', self.cvget) + self.registerScript('cvmod', self.cvmod) + self.registerScript('cvdel', self.cvdel) # Helper functions @@ -112,12 +125,30 @@ to get input from stdin.""" def parseCoords(self, args, usePlayerCoords = True, allowInventory = True): """Takes an argument list, and figures out what it's talking about. Returns (thing, x, y). "Thing" can be None.""" - coordStr = args[0] - if len(args) > 1: - coordStr += ' ' + args[1] #print(coordStr) x = -1 y = -1 + + # first, try by name for objects in the map + name = ' '.join(args) + thing = self.level.getThingByName(name) + if thing != None: + if usePlayerCoords: + return thing, thing.playerx, thing.playery + else: + return thing, thing.x, thing.y + + # Second, try by name for objects in the inventory + if allowInventory: + for i in self.playerInv: + thingName = self.playerInv[i].name + if name == thingName: + return self.playerInv[i], -1, -1 + + # Third, try by location + coordStr = args[0] + if len(args) > 1: + coordStr += ' ' + args[1] match = GameBase.coordRegex.match(coordStr) if match != None: # if coordinates were given #print(match.group()) @@ -139,27 +170,7 @@ Returns (thing, x, y). "Thing" can be None.""" else: return thing, x, y else: # if a name was given - name = ' '.join(args) - thing = self.level.getThingByName(name) - if thing != None and usePlayerCoords: - return thing, thing.playerx, thing.playery - elif thing != None: - return thing, thing.x, thing.y - elif allowInventory: - for i in self.playerInv: - thingName = self.playerInv[i].name - if name == thingName: - thing = self.playerInv[i] - if thing != None: - return thing, -1, -1 - else: - return None, -1, -1 - #raise RuntimeError("'None' item named '{0}' in player inventory.".format(name)) - return None, -1, -1 - else: # nothing - return None, -1, -1 - #raise ValueError("{0} cannot be reached.".format(name)) - return None, x, y + return None, -1, -1 def justifyText(self, text, width = 80): ret = [] @@ -181,10 +192,12 @@ Returns (thing, x, y). "Thing" can be None.""" return '\n'.join(ret) - def getValueFromString(self, arg: str): + def getValueFromString(self, arg: str, env = None): + if env == None: + env = self.customValues val = None validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg) - if arg[0] in '"\'': # assume it's a string + if arg[0] in '"\'' and arg[-1] == arg[0]: # assume it's a string val = arg[1:-1] elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None: if '.' in arg: @@ -204,51 +217,71 @@ Returns (thing, x, y). "Thing" can be None.""" elif arg.casefold() == 'playerinv': val = self.playerInv elif validIdent != None: - if 'scriptLocal' in self.customValues and validIdent.group() in self.customValues['scriptLocal']: - val = self.customValues['scriptLocal'][arg] - elif validIdent.group() in self.customValues: - val = self.customValues[arg] + group = validIdent.group() + if 'scriptLocal' in self.customValues and group in self.customValues['scriptLocal']: + val = self.customValues['scriptLocal'][group] + elif group in env: + val = env[group] else: return False # for if statements; if a variable doesn't exist, should evaluate to False # evaluate all values of all indecies - if validIdent.end() < len(arg): - if arg[validIdent.end()] == '[': - openBracket = validIdent.end() + openBracket = validIdent.end() + if openBracket < len(arg): + if arg[openBracket] == '[': ptr = openBracket depth = 0 while ptr < len(arg): - if ptr == '[': - depth += 1 - elif ptr == ']': - depth -= 1 - if depth == 0: - var = var[self.getValueFromString(arg[openBracket+1:ptr])] - openBracket = ptr + 1 - ptr += 1 if depth == 0 and arg[ptr] != '[': raise GameError('Invalid value syntax: {}'.format(arg)) + if arg[ptr] == '[': + depth += 1 + elif arg[ptr] == ']': + depth -= 1 + + if depth == 0: + index = self.getValueFromString(arg[openBracket+1:ptr]) + if index in val: + val = val[index] + else: + return False + openBracket = ptr + 1 + ptr += 1 else: raise GameError('Invalid value syntax: {}'.format(arg)) else: raise GameError('Invalid argument to getValueFromString: {}'.format(arg)) return val - def compareValues(self, operator: str, left: str, right: str): + def compareValues(self, args: list): """Generalized comparisons, may eventually be extended to other operators""" - lval = self.getValueFromString(left) - rval = self.getValueFromString(right) - if operator == '==': - return lval == rval - elif operator == '!=': - return lval != rval - elif operator == '<=': - return lval <= rval - elif operator == '>=': - return lval >= rval - elif operator == '<': - return lval < rval - elif operator == '>': - return lval > rval + if len(args) == 1: + return bool(self.getValueFromString(args[0])) + elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'): + lval = self.getValueFromString(args[0]) + operator = args[1] + rval = self.getValueFromString(args[2]) + if operator == '==': + return lval == rval + elif operator == '!=': + return lval != rval + elif operator == '<=': + return lval <= rval + elif operator == '>=': + return lval >= rval + elif operator == '<': + return lval < rval + elif operator == '>': + return lval > rval + elif operator == 'in': + if args[2].casefold() == 'playerinv': + for i in rval: + if rval[i].name == lval: + return True + return False + else: + return lval in rval + else: + raise GameError("Condition cannot be evaluated: {}".format(' '.join(args))) # commands @@ -278,6 +311,7 @@ The letter is not case-sensitive.""" # Now we have a heading! Let's see if we can get there... if (x, y) == (self.playerx, self.playery): #_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0))) + self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0)) return dist, path = self.level.path(x, y, self.playerx, self.playery) if dist == -1: @@ -343,7 +377,7 @@ Character can be the name of the character, or their coordinates.""" args.pop(0) if args[0] == 'the': args.pop(0) - thing, x, y = self.parseCoords(args, usePlayerCoords = False) + thing, x, y = self.parseCoords(args, usePlayerCoords = False, allowInventory = False) if not self.level.lineOfSight(self.playerx, self.playery, x, y): print("{} cannot talk to {}.".format(self.playerName, thing.name), file = self.outstream) elif thing == None: @@ -423,15 +457,15 @@ the name of an item in the player's inventory.""" def useOn(self, args, speed): """Called by use when there is an 'on' clause""" onIndex = args.index('on') - item, x, y = self.parseCoords(args[:onIndex]) + item, x, y = self.parseCoords(args[:onIndex], usePlayerCoords = False, allowInventory = True) if args[onIndex+1] == 'the': onIndex += 1 - thing, x, y = self.parseCoords(args[onIndex+1:]) + thing, x, y = self.parseCoords(args[onIndex+1:], usePlayerCoords = True, allowInventory = True) useArgs = [] if 'with' in args: useArgs = args[args.index('with')+1:] if item == None or item.thingID not in self.playerInv: - print("There is no {0} in the inventory.".format(item.name), file = self.outstream) + print("There is no such item in the inventory.", file = self.outstream) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) return if thing == None and x < 0 and y < 0: @@ -481,7 +515,7 @@ Object can be the name of the object, or its coordinates.""" args.pop(0) if args[0] == 'the': args.pop(0) - thing, x, y = self.parseCoords(args) + thing, x, y = self.parseCoords(args, allowInventory = False) if thing == None: print("There is nothing to take.", file = self.outstream) return @@ -522,7 +556,7 @@ Object can be the name of the object, or its coordinates.""" for i in self.playerInv: thingName = self.playerInv[i].name if ' '.join(args) == thingName: - self.setEvent(0.0, _ge.DropEvent(self.playerInv[args[0]])) + self.setEvent(0.0, _ge.DropEvent(self.playerInv[i])) return print('{0} does not have a {1}.'.format(self.playerName, args[0]), file = self.outstream) @@ -547,11 +581,14 @@ Object can be the name of the object, or its coordinates.""" else: self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing) + if self.level == None: + raise GameError("Map could not be loaded.") + # get persistent things from it if args[0] in self.persist: persistedThings = tuple(self.persist[args[0]].keys()) for i in persistedThings: - self.level.addThing(self.persist[args[0]][i], True) + self.nextThing = self.level.addThing(self.persist[args[0]][i], self.nextThing, True) #nextThing shouldn't change del self.persist[args[0]][i] # delete them from the persist dict to prevent item duplication print(self.level.openingText, file = self.outstream) @@ -579,10 +616,10 @@ Object can be the name of the object, or its coordinates.""" if self.level.name not in self.persist: self.persist[self.level.name] = {} for i in self.level.persistent: - self.persist[self.level.name][i] = self.level.getThingByName(i) + self.persist[self.level.name][i] = self.level.getThingByID(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, self.nextThing) + data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing) # save it! fileName = 'saves/' + args[0].replace(' ', '_') + '.dat' @@ -613,7 +650,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, self.nextThing = _pi.load(f) + self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.customValues, 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())) @@ -659,45 +696,70 @@ Object can be the name of the object, or its coordinates.""" del literalStr[l] l -= 1 elif instr[c] in ' \t\n;}{': - if argStart != l: - ret.append(''.join(literalStr[argStart:l])) - if instr[c] == ';': - script.append(ret) - ret = [] - elif instr[c] == '{': # block - stack.append(script) - script.append(ret) - script = [] - ret.append(script) - ret = [] - del literalStr[l] + if l > 0 and instr[c-1] == '\\': + del literalStr[l-1] l -= 1 - elif instr[c] == '}': # close block - if len(ret) > 0: + else: + if argStart != l: + ret.append(''.join(literalStr[argStart:l])) + if instr[c] == ';': + if len(ret) > 0: + script.append(ret) + ret = [] + elif instr[c] == '{': # block + stack.append(script) script.append(ret) - script = stack.pop() - ret = [] - del literalStr[l] - l -= 1 - argStart = l + 1 + script = [] + ret.append(script) + ret = [] + del literalStr[l] + l -= 1 + elif instr[c] == '}': # close block + if len(ret) > 0: + script.append(ret) + ret = [] + script = stack.pop() + del literalStr[l] + l -= 1 + argStart = l + 1 c += 1 l += 1 if argStart != l: ret.append(''.join(literalStr[argStart:l])) - script.append(ret) + if len(ret) > 0: + script.append(ret) #print('After parsing: {}'.format(script)) self.customValues['scriptLocal'] = {} - self.runScript(script) - del self.customValues['scriptLocal'] + ret = self.runScript(script) + if 'scriptLocal' in self.customValues: + del self.customValues['scriptLocal'] + if isinstance(ret, _ScriptBreak): + ret = ret.value + return ret def runScript(self, script: list): """run a script""" + ret = False for line in script: - if line[0].casefold() == 'playercmd': + if len(line) == 0: + # empty line + continue + elif line[0].casefold() == 'playercmd': # run a player command self.getIO('playercmd')(line[1:]) + elif line[0].casefold() == 'break': + # exit early + return _ScriptBreak(ret) + elif line[0] in self.__scripts: + # run a script + ret = self.__scripts[line[0]](line[1:]) + if isinstance(ret, _ScriptBreak): + return ret else: - self.__scripts[line[0]](line[1:]) + # conditional evaluation + ret = self.compareValues(line) + # We specifically want the return value of the last line executed. + return ret def gameEventLoop(self): #print(self.skipLoop) @@ -730,7 +792,8 @@ Object can be the name of the object, or its coordinates.""" self.prevx, self.prevy = self.playerx, self.playery self.playerx, self.playery = e.x, e.y else: - self.level.moveThing(e.actor, e.x, e.y) + actor = self.level.getThingByName(e.actor) + self.level.moveThing(actor, e.x, e.y) return False def handleArrive(self, e): @@ -741,9 +804,11 @@ Object can be the name of the object, or its coordinates.""" thing = self.level.getThingAtCoords(self.playerx, self.playery) if thing: if thing.thingType == 'x': - a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing))) - if a != 'n' and a != 'N': - self.loadMap((thing.destination, thing.exitid)) + self.parseScript(thing.onUse) + if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)): + a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing))) + if a != 'n' and a != 'N': + self.loadMap((thing.destination, thing.exitid)) return True else: actor = self.level.getThingByName(e.actor) @@ -781,7 +846,7 @@ Object can be the name of the object, or its coordinates.""" def handleDrop(self, e): e.item.x = self.playerx e.item.y = self.playery - self.level.addThing(e.item, True) + self.nextThing = self.level.addThing(e.item, self.nextThing, True) # nextThing shouldn't change del self.playerInv[e.item.thingID] return True @@ -796,30 +861,30 @@ Object can be the name of the object, or its coordinates.""" raise ValueError("Non-examinable thing {0} examined.".format(thing.name)) if thing.customValues['pattern'] == 'single': - print(self.justifyText(thing.customValues['text']), file = self.outstream) + print(self.justifyText(str(thing.customValues['text'])), file = self.outstream) elif thing.customValues['pattern'] == 'loop': if not 'cursor' in thing.customValues: thing.customValues['cursor'] = 0 cursor = thing.customValues['cursor'] - print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream) + print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream) thing.customValues['cursor'] = (cursor + 1) % len(thing.customValues['text']) elif thing.customValues['pattern'] == 'once': if not 'cursor' in thing.customValues: thing.customValues['cursor'] = 0 cursor = thing.customValues['cursor'] - print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream) + print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream) if cursor < len(thing.customValues['text']) - 1: thing.customValues['cursor'] += 1 elif thing.customValues['pattern'] == 'random': cursor = _ra.randrange(len(thing.customValues['text'])) - print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream) + print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream) thing.customValues['cursor'] = cursor if 'set' in thing.customValues: if 'cursor' in thing.customValues: - self.customVals[thing.customValues['set']] = thing.customValues[cursor] + self.customValues[thing.customValues['set']] = thing.customValues[cursor] else: - self.customVals[thing.customValues['set']] = 0 + self.customValues[thing.customValues['set']] = True return 0.0 def container(self, thing, args): @@ -893,12 +958,13 @@ Object can be the name of the object, or its coordinates.""" raise GameError('Incomplete If statement: if {}'.format(' '.join(args))) # evaluate condition - if args[1] in ('==', '!=', '<=', '>=', '<', '>'): + if len(args) > 1 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'): if len(args) < 4: raise GameError('Incomplete If statement: if {}'.format(' '.join(args))) - ret = self.compareValues(args[1], args[0], args[2]) + ret = self.compareValues(args[:3]) + args = args[3:] else: - ret = bool(self.parseValue(args[0])) + ret = bool(self.getValueFromString(args[0])) args = args[1:] if inverse: ret = not ret @@ -906,17 +972,34 @@ Object can be the name of the object, or its coordinates.""" # if condition is true, evaluate further if ret: if isinstance(args[-1], list): - self.runScript(args[-1]) + return self.runScript(args[-1]) else: - self.runScript([args]) + return self.runScript([args]) def printfScript(self, args): """Assume the first argument is a stringable object, and format all others.""" - print(args[0].format(*[self.getValueFromString(i) for i in args[1:]]), file = self.outstream) + ret = args[0].format(*[self.getValueFromString(i) for i in args[1:]]) + print(ret, file = self.outstream) + return ret - def setCustomValue(self, args): + def getCustomValue(self, args, env = None): + norm = True + if env == None: + env = self.customValues + else: + norm = False + if len(args) > 0 and args[0] == 'local': + if 'scriptLocal' in self.customValues: + env = self.customValues['scriptLocal'] + else: + raise GameError('Attempted to set a local variable without local scope.') + args.pop(0) + return self.getValueFromString(args[0], env) + + def setCustomValue(self, args, env = None): """takes [customValue, op, value]""" - env = self.customValues + if env == None: + env = self.customValues if len(args) > 0 and args[0] == 'local': if 'scriptLocal' in self.customValues: env = self.customValues['scriptLocal'] @@ -925,7 +1008,9 @@ Object can be the name of the object, or its coordinates.""" args.pop(0) if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='): raise GameError('Arguments are not fit for the setCustomValue script.') - val = self.getValueFromString(args[2]) + # This next line allows cvmod to use values in the thing's customValues dict, + # but doing this means that accessing a game CV requires setting it to a local var. + val = self.getValueFromString(args[2], env) # set the value to a default value (0, 0.0, '', etc.) if not yet assigned if args[0] not in env and args[1] != '=': @@ -975,6 +1060,15 @@ Object can be the name of the object, or its coordinates.""" env[args[0]] &= val elif args[1] == '^=': env[args[0]] ^= val + return env[args[0]] + + def delCustomValue(self, args, env = None): + """To clean up after a map.""" + if env == None: + env = self.customValues + for i in args: + if i in env: + del(env[i]) def spawnThing(self, args): """Spawns a thing. [type name=... location=... etc]""" @@ -989,35 +1083,33 @@ Object can be the name of the object, or its coordinates.""" for i in args[1:]: if i[0:5].casefold() == 'name=': name = i[5:] - elif i[0:10].casefold() == 'location=': - _, x, y = self.parseCoords(i[10:].split()) + elif i[0:9].casefold() == 'location=': + _, x, y = self.parseCoords(i[9:].split(), usePlayerCoords = False, allowInventory = False) elif i[0:4].casefold() == 'fgc=': - if len(i[4:]) != 7 or i[0] != '#': + if len(i[4:]) != 7 or i[4] != '#': raise GameError("Invalid color: {}".format(i[4:])) - for j in i[4:]: + for j in i[5:]: if j not in '0123456789abcdefABCDEF': raise GameError("Invalid color: {}".format(i[4:])) fgc = i[4:] elif i[0:4].casefold() == 'bgc=': - if len(i[4:]) != 7 or i[0] != '#': + if len(i[4:]) != 7 or i[4] != '#': raise GameError("Invalid color: {}".format(i[4:])) - for j in i[4:]: + for j in i[5:]: if j not in '0123456789abcdefABCDEF': raise GameError("Invalid color: {}".format(i[4:])) bgc = i[4:] elif i[0:6].casefold() == 'shape=': - if len(i[6:]) != 7 or i[0] != '#': - raise GameError("Invalid color: {}".format(i[6:])) - for j in i[6:]: - if j not in '0123456789abcdefABCDEF': - raise GameError("Invalid color: {}".format(i[6:])) - shape = i[6:] + if i[6] not in 'ox^ #|-': + raise GameError("Invalid shape: {}".format(i[6])) + shape = i[6] elif i == 'persist': persist = True if x < 0 or y < 0: raise GameError("Cannot spawn thing: bad location") # Unfortunately, there needs to be a case for each type. + thing = None if args[0].casefold() == 'item': # spawn an item description = 'A nondescript item.' @@ -1027,12 +1119,12 @@ Object can be the name of the object, or its coordinates.""" ranged = False if name == None: name = 'item {}'.format(self.nextThing) - if fgc == None: - fgc = _gm.Item.defaultGraphic['fgc'] if bgc == None: - bgc = _gm.Item.defaultGraphic['bgc'] + bgc = _gm.Item.defaultGraphic[0] + if fgc == None: + fgc = _gm.Item.defaultGraphic[1] if shape == None: - shape = _gm.Item.defaultGraphic['shape'] + shape = _gm.Item.defaultGraphic[2] for i in args[1:]: if i[0:12].casefold() == 'description=': description = i[12:] @@ -1045,9 +1137,8 @@ Object can be the name of the object, or its coordinates.""" elif i[0:3].casefold() == 'cv:': equalsign = i.index('=') cv = i[3:equalsign] - customValues[cv] = getValueFromString(i[equalsign+1:]) + customValues[cv] = self.getValueFromString(i[equalsign+1:]) thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) elif args[0].casefold() == 'useable': # spawn a useable thing description = 'A nondescript useable thing.' @@ -1056,12 +1147,12 @@ Object can be the name of the object, or its coordinates.""" playerx, playery = x, y if name == None: name = 'useable {}'.format(self.nextThing) - if fgc == None: - fgc = _gm.Item.defaultGraphic['fgc'] if bgc == None: - bgc = _gm.Item.defaultGraphic['bgc'] + bgc = _gm.Useable.defaultGraphic[0] + if fgc == None: + fgc = _gm.Useable.defaultGraphic[1] if shape == None: - shape = _gm.Item.defaultGraphic['shape'] + shape = _gm.Useable.defaultGraphic[2] for i in args[1:]: if i[0:12].casefold() == 'description=': description = i[12:] @@ -1072,24 +1163,23 @@ Object can be the name of the object, or its coordinates.""" elif i[0:3].casefold() == 'cv:': equalsign = i.index('=') cv = i[3:equalsign] - customValues[cv] = getValueFromString(i[equalsign+1:]) + customValues[cv] = self.getValueFromString(i[equalsign+1:]) thing = _gm.Useable(name, x, y, description, useFunc, customValues, playerx, playery, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) elif args[0].casefold() == 'npc': # spawn an NPC description = 'A nondescript character.' - behavior = '' + behavior = 'stand' inv = [] customValues = {} playerx, playery = x, y if name == None: name = 'character {}'.format(self.nextThing) - if fgc == None: - fgc = _gm.Item.defaultGraphic['fgc'] if bgc == None: - bgc = _gm.Item.defaultGraphic['bgc'] + bgc = _gm.NPC.defaultGraphic[0] + if fgc == None: + fgc = _gm.NPC.defaultGraphic[1] if shape == None: - shape = _gm.Item.defaultGraphic['shape'] + shape = _gm.NPC.defaultGraphic[2] for i in args[1:]: if i[0:12].casefold() == 'description=': description = i[12:] @@ -1100,9 +1190,8 @@ Object can be the name of the object, or its coordinates.""" elif i[0:3].casefold() == 'cv:': equalsign = i.index('=') cv = i[3:equalsign] - customValues[cv] = getValueFromString(i[equalsign+1:]) + customValues[cv] = self.getValueFromString(i[equalsign+1:]) thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, False, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) elif args[0].casefold() == 'door': # spawn a door description = 'a nondescript door.' @@ -1110,32 +1199,33 @@ Object can be the name of the object, or its coordinates.""" key = None if name == None: name = 'door {}'.format(self.nextThing) - if fgc == None: - fgc = _gm.Item.defaultGraphic['fgc'] if bgc == None: - bgc = _gm.Item.defaultGraphic['bgc'] + bgc = _gm.Door.defaultGraphic[0] + if fgc == None: + fgc = _gm.Door.defaultGraphic[1] if shape == None: - shape = _gm.Item.defaultGraphic['shape'] + shape = _gm.Door.defaultGraphic[2] for i in args[1:]: if i[0:12].casefold() == 'description=': description = i[12:] elif i.casefold() == 'locked': locked = True thing = _gm.Door(name, x, y, locked, description, key, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) elif args[0].casefold() == 'mapexit': # spawn an exit to another map (use with EXTREME caution!) destination = '' prefix = None exitid = 0 + onUse = '' + key = True if name == None: name = 'somewhere' - if fgc == None: - fgc = _gm.Item.defaultGraphic['fgc'] if bgc == None: - bgc = _gm.Item.defaultGraphic['bgc'] + bgc = _gm.MapExit.defaultGraphic[0] + if fgc == None: + fgc = _gm.MapExit.defaultGraphic[1] if shape == None: - shape = _gm.Item.defaultGraphic['shape'] + shape = _gm.MapExit.defaultGraphic[2] for i in args[1:]: if i[0:12].casefold() == 'destination=': destination = i[12:] @@ -1143,8 +1233,11 @@ Object can be the name of the object, or its coordinates.""" prefix = i[7:] elif i[0:7].casefold() == 'exitid=': exitid = int(i[7:]) - thing = _gm.MapExit(name, x, y, exitid, destination, prefix, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) + elif i[0:6].casefold() == 'onuse=': + onUse = i[6:] + elif i[0:4].casefold() == 'key=': + key = i[4:] + thing = _gm.MapExit(name, x, y, exitid, destination, prefix, onUse, key, (bgc, fgc, shape)) elif args[0].casefold() == 'mapentrance': # spawn a map entrance exitid = 0 @@ -1152,7 +1245,10 @@ Object can be the name of the object, or its coordinates.""" if i[0:7].casefold() == 'exitid=': exitid = int(i[7:]) thing = _gm.MapEntrance(x, y, exitid, name) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) + else: + raise GameError("{} not a valid thing type.".format(args[0])) + self.nextThing = self.level.addThing(thing, self.nextThing, persist) + return thing def giveToPlayer(self, args): """We get to assume it's an item.""" @@ -1169,7 +1265,7 @@ Object can be the name of the object, or its coordinates.""" if i[0:5].casefold() == 'name=': name = i[5:] elif i[0:10].casefold() == 'location=': - _, x, y = self.parseCoords(i[10:].split()) + _, x, y = self.parseCoords(i[10:].split(), usePlayerCoords = False, allowInventory = False) elif i[0:4].casefold() == 'fgc=': if len(i[4:]) != 7 or i[0] != '#': raise GameError("Invalid color: {}".format(i[4:])) @@ -1206,10 +1302,49 @@ Object can be the name of the object, or its coordinates.""" cv = i[3:equalsign] customValues[cv] = getValueFromString(i[equalsign+1:]) thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape)) - self.nextThing = self.level.addThing(thing, self.nextThing, persist) + thing.thingID = self.nextThing + self.playerInv[thing.thingID] = thing + self.nextThing += 1 + return thing + + def moveThingScript(self, args): + colon = args.index(':') + thing, x, y = self.parseCoords(args[0:colon], usePlayerCoords = False, allowInventory = False) + if thing == None: + return False + _, newX, newY = self.parseCoords(args[colon+1:], usePlayerCoords = True, allowInventory = False) + self.level.moveThing(thing, newX, newY) + + def cvget(self, args): + thing, x, y = self.parseCoords(args[0:-1], usePlayerCoords = False, allowInventory = True) + if thing != None and thing.thingType in 'ciu': + return self.getCustomValue(args[-1:], env = thing.customValues) + else: + raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.") + + def cvmod(self, args): + """Modify a custom value in a thing. + args = thing identifier operator value""" + thing, x, y = self.parseCoords(args[0:-3], usePlayerCoords = False, allowInventory = True) + if thing != None and thing.thingType in 'ciu': + return self.setCustomValue(args[-3:], env = thing.customValues) + else: + raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.") + + def cvdel(self, args): + """Delete custom values.""" + colon = args.index(':') + thing, x, y = self.parseCoords(args[0:colon], usePlayerCoords = False, allowInventory = True) + if thing != None and thing.thingType in 'ciu': + return self.setCustomValue(args[colon+1:], env = thing.customValues) + else: + raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.") # behaviors + def stand(self, actor): + pass + def wander(self, actor): pass diff --git a/gamemap.py b/gamemap.py index 3d442e1..e7cd443 100644 --- a/gamemap.py +++ b/gamemap.py @@ -358,7 +358,7 @@ 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): + def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix = None, onUse = '', key = True, graphic = defaultGraphic): description = name if prefix: description = "{0} {1}".format(prefix, name) @@ -366,6 +366,8 @@ class MapExit(Thing): self.exitid = exitid self.destination = destination self.prefix = prefix + self.onUse = onUse + self.key = key self.graphic = graphic @classmethod @@ -384,6 +386,10 @@ class MapExit(Thing): ret['graphic'] = graphic if node.prefix != None: ret['prefix'] = node.prefix + if node.onUse != '': + ret['onUse'] = node.onUse + if node.key != True: + ret['key'] = node.key return representer.represent_mapping(cls.yaml_flag, ret) @classmethod @@ -392,6 +398,8 @@ class MapExit(Thing): constructor.construct_mapping(node, parts, True) # set default values for optional arguments prefix = None + onUse = '' + key = True bgc = MapExit.defaultGraphic[0] fgc = MapExit.defaultGraphic[1] shape = MapExit.defaultGraphic[2] @@ -406,8 +414,12 @@ class MapExit(Thing): graphic = (bgc, fgc, shape) if 'prefix' in parts: prefix = parts['prefix'] + if 'onUse' in parts: + onUse = parts['onUse'] + if 'key' in parts: + key = parts['key'] return cls(parts['name'], parts['location'][0], parts['location'][1], - parts['id'], parts['destination'], prefix, graphic) + parts['id'], parts['destination'], prefix, onUse, key, graphic) class MapEntrance(Thing): yaml_flag = u'!MapEntrance' @@ -417,7 +429,7 @@ class MapEntrance(Thing): 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) + super(MapEntrance, self).__init__('a', name, x, y, '', 1) self.exitid = exitid @classmethod @@ -574,6 +586,7 @@ Entering a map through stdin will be obsolete once testing is over.""" yaml.register_class(NPC) yaml.register_class(Door) yaml.register_class(MapExit) + yaml.register_class(MapEntrance) if infile != None: try: with open(infile, 'r') as f: @@ -582,7 +595,7 @@ Entering a map through stdin will be obsolete once testing is over.""" print("The file could not be read.") return None, nextThing else: - raise RuntimeError("No file was specified for loading.") + raise MapError("No file was specified for loading.") # Now what we do with the data mat = None @@ -593,10 +606,10 @@ Entering a map through stdin will be obsolete once testing is over.""" #print(layout.text) match = GameMap.matrixRegex.match(layout.lstrip()) if match == None: - raise RuntimeError('Map read a file without a map matrix.') + raise MapError('Map read a file without a map matrix.') mat = match.group() else: - raise RuntimeError('Map read a file without a map matrix.') + raise MapError('Map read a file without a map matrix.') # generate matrix and graph first mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat) @@ -632,7 +645,7 @@ list of lists of tuples.""" if x == 0: x = len(mat[l]) elif x != len(mat[l]): - raise RuntimeError("Map matrix has jagged edges.") + raise MapError("Map matrix has jagged edges.") l += 1 #x = 0 mat.append([]) @@ -644,7 +657,7 @@ list of lists of tuples.""" else: matrixStr = matrixStr[i:] else: # This should happen when it finishes? - raise RuntimeError("Unexpected token in map matrix: '{0}'".format(matrixStr)) + raise MapError("Unexpected token in map matrix: '{0}'".format(matrixStr)) y = len(mat) - 1 # Now for the graph @@ -995,19 +1008,19 @@ The closeEnough parameter will create a path that lands beside the source if nec newPos = self.coordsToInt(x, y) else: x, y = self.intToCoords(x) - if thing: + if thing != None: oldPos = self.coordsToInt(thing.x, thing.y) if oldPos in self.thingPos: - self.thingPos[oldPos].remove(name) + self.thingPos[oldPos].remove(thing.thingID) if len(self.thingPos[oldPos]) == 0: del self.thingPos[oldPos] if newPos not in self.thingPos: - self.thingPos[newPos] = [name] + self.thingPos[newPos] = [thing.thingID] else: - self.thingPos[newPos].append(name) + self.thingPos[newPos].append(thing.thingID) 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)) + raise MapError("There is nothing to move.") diff --git a/gameshell.py b/gameshell.py index 3cc323e..43a7969 100644 --- a/gameshell.py +++ b/gameshell.py @@ -27,36 +27,26 @@ class GameShell(Shell): self.outstream = _sys.stdout self.gameBase = gameBase self.colorMode = 0 + self.gameTitle = 'Game Shell' # should be changed for actual games + self.startLevel = 'testing/test1.yml' # should be changed for actual games + self.openingText = '{}\nIn Development' self.ps2 = '?> ' + self.__inGame = False # register functions - self.registerCommand('map', self.showMap) - self.registerCommand('ls', self.showMap) - self.registerCommand('go', self.gameBase.go) - self.registerCommand('move', self.gameBase.go) - self.registerCommand('walk', self.gameBase.go) - self.registerCommand('look', self.gameBase.look) - self.registerCommand('talk', self.gameBase.talk) - self.registerCommand('use', self.gameBase.use) - self.registerCommand('loadMap', self.gameBase.loadMap) - self.registerCommand('man', self.man) - self.registerCommand('help', self.man) - self.registerCommand('save', self.gameBase.saveGame) - self.registerCommand('load', self.gameBase.loadGame) - self.registerCommand('take', self.gameBase.take) - self.registerCommand('get', self.gameBase.take) - self.registerCommand('drop', self.gameBase.drop) - self.registerCommand('inv', self.inv) - self.registerCommand('bag', self.inv) - self.registerCommand('items', self.inv) - self.registerCommand('status', self.status) + self.registerCommand('load', self.gameBase.loadGame) # should always be available + self.registerCommand('flippetywick', self.devMode) self.registerCommand('options', self.options) self.registerCommand('colorTest', self.colorTest) - self.registerAlias('run', ['go', '-r']) + self.registerCommand('man', self.man) + self.registerCommand('help', self.man) + self.registerCommand('new', self.newGame) self.gameBase.registerIO('container', self.container) self.gameBase.registerIO('dialog', self.dialog) self.gameBase.registerIO('info', self.info) + self.gameBase.registerIO('playercmd', self.playercmd) + self.menuMode() # Helper functions @@ -108,6 +98,39 @@ class GameShell(Shell): self.__colorTestRoutine(0, 128) # dark self.__colorTestRoutine(0, 256) # medium self.__colorTestRoutine(128, 256) # light + + def __getAdjFloors(self, level, x, y): + """Get the floor colors of the 8 nearest tiles.""" + adjFloors = [] + for i in range(y-1, y+2): + if i < 0 or i >= level.dimensions[1]: + continue + for j in range(x-1, x+2): + if j < 0 or j >= level.dimensions[0]: + continue + if level.mapMatrix[i][j][0] == 'e': + adjFloors.append(level.mapMatrix[i][j][1]) + return adjFloors + + def __getUnderWallColor(self, level, x, y): + adjFloors = self.__getAdjFloors(level, x, y) + if len(adjFloors) > 0: + counts = {} + highestCount = 0 + ret = -1 + for i in adjFloors: + if i in counts: + counts[i] += 1 + else: + counts[i] = 1 + if counts[i] > highestCount: + ret = i + highestCount = counts[i] + elif counts[i] == highestCount and i < ret: + ret = i + return ret + else: + return -1 def showMap(self, args): """map [-l] @@ -170,8 +193,15 @@ If -l is given, a map legend will be printed under the map.""" sides += GameShell.DOWN if x > 0 and level.mapMatrix[y][x-1][0] == 'w': sides += GameShell.LEFT + underWallColor = self.__getUnderWallColor(level, x, y) + if level.floorColors[underWallColor] != floorColor and underWallColor != -1: + floorColor = level.floorColors[underWallColor] + rows[-1].append(self.color(floorColor[1:], fg = False)) rows[-1].append(GameShell.WALLS[sides]) else: + if level.floorColors[level.mapMatrix[y][x][1]] != floorColor: + floorColor = level.floorColors[level.mapMatrix[y][x][1]] + rows[-1].append(self.color(floorColor[1:], fg = False)) rows[-1].append(' ') index += 1 rows[-1] = ''.join(rows[-1]) @@ -235,26 +265,108 @@ If -l is given, a map legend will be printed under the map.""" def inv(self, args): print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv])) + def newGame(self, args): + if self.__inGame: + response = input("Do you want to save before exiting (Y/n/x)? ") + if len(response) > 0: + if response[0] in 'Nn': + super(GameShell, self).exitShell(args) + elif response[0] in 'Xx': # cancel + return + else: + sf = input('Save file: ') + if len(sf) > 0: + gameBase.saveGame([sf]) + else: + print('No save file given, cancelling exit.') + else: + sf = input('Save file: ') + if len(sf) > 0: + gameBase.saveGame([sf]) + else: + print('No save file given, cancelling exit.') + try: + self.gameBase.loadMap([self.startLevel]) + except RuntimeError as e: + print(e) + return + self.gameMode() + + def gameMode(self): + """Mode for in-game.""" + if not self.__inGame: + self.registerCommand('map', self.showMap) + self.registerCommand('ls', self.showMap) + self.registerCommand('go', self.gameBase.go) + self.registerCommand('move', self.gameBase.go) + self.registerCommand('walk', self.gameBase.go) + self.registerCommand('look', self.gameBase.look) + self.registerCommand('talk', self.gameBase.talk) + self.registerCommand('use', self.gameBase.use) + self.registerCommand('save', self.gameBase.saveGame) + self.registerCommand('take', self.gameBase.take) + self.registerCommand('get', self.gameBase.take) + self.registerCommand('drop', self.gameBase.drop) + self.registerCommand('inv', self.inv) + self.registerCommand('bag', self.inv) + self.registerCommand('items', self.inv) + self.registerAlias('run', ['go', '-r']) + self.__inGame = True + + def menuMode(self, text = None): + """Mode for main menus and the like.""" + if self.__inGame: + self.registerCommand('map', self.showMap) + self.unRegisterCommand('ls', self.showMap) + self.unRegisterCommand('go', self.gameBase.go) + self.unRegisterCommand('move', self.gameBase.go) + self.unRegisterCommand('walk', self.gameBase.go) + self.unRegisterCommand('look', self.gameBase.look) + self.unRegisterCommand('talk', self.gameBase.talk) + self.unRegisterCommand('use', self.gameBase.use) + self.unRegisterCommand('save', self.gameBase.saveGame) + self.unRegisterCommand('take', self.gameBase.take) + self.unRegisterCommand('get', self.gameBase.take) + self.unRegisterCommand('drop', self.gameBase.drop) + self.unRegisterCommand('inv', self.inv) + self.unRegisterCommand('bag', self.inv) + self.unRegisterCommand('items', self.inv) + self.unRegisterAlias('run', ['go', '-r']) + self.__inGame = False + if text == None: + print(self.openingText.format(self.gameTitle)) + else: + print(text.format(self.gameTitle)) + + def devMode(self, args): + print('Dev mode activated. Please dev responsibly.') + self.gameMode() + self.registerCommand('dev', self.runScript) + self.registerCommand('status', self.status) + self.registerCommand('loadMap', self.gameBase.loadMap) + + def runScript(self, args): + """simple wrapper for gameBase.runscript.""" + self.gameBase.parseScript(' '.join(args)) + # IO calls def container(self, inv, cont): """container IO""" # Pretty print: get length of the longest inventory item's name longestLen = 0 - longestStr = "" for i in inv: - if len(i) > longestLen: - longestLen = len(i) - longestStr = i + if len(inv[i].name) > longestLen: + longestLen = len(inv[i].name) if longestLen > 0: print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:")) i = 0 invKeys = tuple(inv.keys()) while i < len(invKeys) and i < len(cont): - print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(invKeys[i])) + print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(inv[invKeys[i]].name)) i += 1 while i < len(invKeys): - print(invKeys[i]) + print(inv[invKeys[i]].name) i += 1 while i < len(cont): print(' '*(longestLen+2) + cont[i].name) @@ -308,7 +420,7 @@ If -l is given, a map legend will be printed under the map.""" charsRead += len(items[instr]) else: print('{} not here.'.format(instr)) - input('') + input('') for i in items: print(' ', i) instr = input("Choose an item to view, or exit: ") @@ -336,7 +448,10 @@ If -l is given, a map legend will be printed under the map.""" return ret # if ret == 1 or ret == 0, go back again. elif 'opener' in dialogObj: - print(_tw.fill(dialogObj['opener'], width = TERM_SIZE)) + toPrint = dialogObj['opener'].split('\n') # split by lines so that fill doesn't erase newlines + for line in toPrint: + print(_tw.fill(line, width = TERM_SIZE)) + input('') while isinstance(dialogObj, dict): if 'action' in dialogObj: action = dialogObj['action'] @@ -350,8 +465,8 @@ If -l is given, a map legend will be printed under the map.""" if ans[0] == '?': condEnd = ans.index(':') cond = ans[1:condEnd].strip().split() - if self.gameBase.compareValues(cond[1], cond[0], cond[2]): - print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:]), width = TERM_SIZE)) + if self.gameBase.compareValues(cond): + print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:].strip()), width = TERM_SIZE)) j += 1 else: skips.append(i) @@ -389,6 +504,31 @@ If -l is given, a map legend will be printed under the map.""" def update(self): self.gameBase.gameEventLoop() + + def exitShell(self, args): + if self.__inGame: + response = input("Do you want to save before exiting (Y/n/x)? ") + if len(response) > 0: + if response[0] in 'Nn': + super(GameShell, self).exitShell(args) + elif response[0] in 'Xx': # cancel + return + else: + sf = input('Save file: ') + if len(sf) > 0: + gameBase.saveGame([sf]) + super(GameShell, self).exitShell(args) + else: + print('No save file given, cancelling exit.') + else: + sf = input('Save file: ') + if len(sf) > 0: + gameBase.saveGame([sf]) + super(GameShell, self).exitShell(args) + else: + print('No save file given, cancelling exit.') + else: + super(GameShell, self).exitShell(args) if __name__ == '__main__': sh = GameShell(GameBase()) diff --git a/shell.py b/shell.py index c414002..aca4dd0 100644 --- a/shell.py +++ b/shell.py @@ -140,13 +140,25 @@ class Shell(object): conventionally called args or argv""" self.__commands[commandName] = command + def unRegisterCommand(self, commandName: str): + """Remove a command. May be useful for inheriting classes and the like.""" + del self.__commands[commandName] + def registerAlias(self, a: str, original: list): """makes 'a' an alias for original. 'a' must be one token, but original can be multiple.""" self.__aliases[a] = original + + def unRegisterAlias(self, commandName: str): + """Remove an alias. May be useful for inheriting classes and the like.""" + del self.__aliases[commandName] def registerBatch(self, name: str, commands: list): self.__batches[name] = commands + + def unRegisterBatch(self, commandName: str): + """Remove a command. May be useful for inheriting classes and the like.""" + del self.__batches[commandName] def getAlias(self, a: str): if a in self.__aliases: @@ -164,29 +176,39 @@ conventionally called args or argv""" """Take a line of text and turn it into an argument list""" if instr == '': return [] + literalStr = list(instr) inQuotes = False ret = [] argStart = 0 c = 0 + l = 0 while c < len(instr): if inQuotes: if instr[c] == '"': - inQuotes = False - ret.append(instr[argStart:c]) - argStart = c+1 + if instr[c-1] == '\\': + del literalStr[l-1] + l -= 1 + else: + inQuotes = False + del literalStr[l] + l -= 1 else: if instr[c] == '"': - inQuotes = True - if argStart != c: - ret.append(instr[argStart:c]) - argStart = c+1 + if l > 0 and instr[c-1] == '\\': + del literalStr[l-1] + l -= 1 + else: + inQuotes = True + del literalStr[l] + l -= 1 elif instr[c] in ' \t\n': - if argStart != c: - ret.append(instr[argStart:c]) - argStart = c + 1 + if argStart != l: + ret.append(''.join(literalStr[argStart:l])) + argStart = l + 1 c += 1 - if argStart != c: - ret.append(instr[argStart:c]) + l += 1 + if argStart != l: + ret.append(''.join(literalStr[argStart:l])) a = self.getAlias(ret[0]) if a: ret = a + ret[1:]