various tweaks and fixes

This commit is contained in:
Patrick Marsee 2019-06-08 13:42:00 -04:00
parent ed7fe60a6d
commit ed7d265b48
5 changed files with 524 additions and 212 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
maps maps
saves saves
dialogs
__pycache__ __pycache__
gameshell.geany gameshell.geany
gameexpr.py gameexpr.py
game.py

View file

@ -8,13 +8,19 @@ import random as _ra
import sys as _sys import sys as _sys
import pickle as _pi import pickle as _pi
import ruamel.yaml as _yaml import ruamel.yaml as _yaml
import textwrap as _tw
class GameError(RuntimeError): class GameError(RuntimeError):
pass pass
class _ScriptBreak(object):
def __init__(self, value):
self.value = value
class GameBase(object): 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" #coordRegex1 = _re.compile(r'(-?[a-zA-Z]+) ?(-?[0-9]+)') # "B12" or "B 12"
#coordRegex2 = _re.compile(r'\(([0-9]+), ([0-9]+)\)') # "(2, 12)" #coordRegex2 = _re.compile(r'\(([0-9]+), ([0-9]+)\)') # "(2, 12)"
#coordRegex3 = _re.compile(r'(-?[0-9]+),? (-?[0-9]+)') # "2 12" or "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('container', self.container)
self.registerUseFunc('info', self.info) self.registerUseFunc('info', self.info)
self.registerUseFunc('wear', self.wear) self.registerUseFunc('wear', self.wear)
self.registerBehavior('stand', self.stand)
self.registerBehavior('wander', self.wander) self.registerBehavior('wander', self.wander)
self.registerScript('if', self.ifScript) self.registerScript('if', self.ifScript)
self.registerScript('printf', self.printfScript) self.registerScript('printf', self.printfScript)
self.registerScript('get', self.getCustomValue)
self.registerScript('set', self.setCustomValue) self.registerScript('set', self.setCustomValue)
self.registerScript('del', self.delCustomValue)
self.registerScript('spawn', self.spawnThing) self.registerScript('spawn', self.spawnThing)
self.registerScript('give', self.giveToPlayer) 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 # Helper functions
@ -112,12 +125,30 @@ to get input from stdin."""
def parseCoords(self, args, usePlayerCoords = True, allowInventory = True): def parseCoords(self, args, usePlayerCoords = True, allowInventory = True):
"""Takes an argument list, and figures out what it's talking about. """Takes an argument list, and figures out what it's talking about.
Returns (thing, x, y). "Thing" can be None.""" Returns (thing, x, y). "Thing" can be None."""
coordStr = args[0]
if len(args) > 1:
coordStr += ' ' + args[1]
#print(coordStr) #print(coordStr)
x = -1 x = -1
y = -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) match = GameBase.coordRegex.match(coordStr)
if match != None: # if coordinates were given if match != None: # if coordinates were given
#print(match.group()) #print(match.group())
@ -139,27 +170,7 @@ Returns (thing, x, y). "Thing" can be None."""
else: else:
return thing, x, y return thing, x, y
else: # if a name was given else: # if a name was given
name = ' '.join(args) return None, -1, -1
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
def justifyText(self, text, width = 80): def justifyText(self, text, width = 80):
ret = [] ret = []
@ -181,10 +192,12 @@ Returns (thing, x, y). "Thing" can be None."""
return '\n'.join(ret) return '\n'.join(ret)
def getValueFromString(self, arg: str): def getValueFromString(self, arg: str, env = None):
if env == None:
env = self.customValues
val = None val = None
validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg) 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] val = arg[1:-1]
elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None: elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None:
if '.' in arg: if '.' in arg:
@ -204,51 +217,71 @@ Returns (thing, x, y). "Thing" can be None."""
elif arg.casefold() == 'playerinv': elif arg.casefold() == 'playerinv':
val = self.playerInv val = self.playerInv
elif validIdent != None: elif validIdent != None:
if 'scriptLocal' in self.customValues and validIdent.group() in self.customValues['scriptLocal']: group = validIdent.group()
val = self.customValues['scriptLocal'][arg] if 'scriptLocal' in self.customValues and group in self.customValues['scriptLocal']:
elif validIdent.group() in self.customValues: val = self.customValues['scriptLocal'][group]
val = self.customValues[arg] elif group in env:
val = env[group]
else: else:
return False # for if statements; if a variable doesn't exist, should evaluate to False return False # for if statements; if a variable doesn't exist, should evaluate to False
# evaluate all values of all indecies # evaluate all values of all indecies
if validIdent.end() < len(arg): openBracket = validIdent.end()
if arg[validIdent.end()] == '[': if openBracket < len(arg):
openBracket = validIdent.end() if arg[openBracket] == '[':
ptr = openBracket ptr = openBracket
depth = 0 depth = 0
while ptr < len(arg): 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] != '[': if depth == 0 and arg[ptr] != '[':
raise GameError('Invalid value syntax: {}'.format(arg)) 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: else:
raise GameError('Invalid value syntax: {}'.format(arg)) raise GameError('Invalid value syntax: {}'.format(arg))
else: else:
raise GameError('Invalid argument to getValueFromString: {}'.format(arg)) raise GameError('Invalid argument to getValueFromString: {}'.format(arg))
return val 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""" """Generalized comparisons, may eventually be extended to other operators"""
lval = self.getValueFromString(left) if len(args) == 1:
rval = self.getValueFromString(right) return bool(self.getValueFromString(args[0]))
if operator == '==': elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
return lval == rval lval = self.getValueFromString(args[0])
elif operator == '!=': operator = args[1]
return lval != rval rval = self.getValueFromString(args[2])
elif operator == '<=': if operator == '==':
return lval <= rval return lval == rval
elif operator == '>=': elif operator == '!=':
return lval >= rval return lval != rval
elif operator == '<': elif operator == '<=':
return lval < rval return lval <= rval
elif operator == '>': elif operator == '>=':
return lval > rval 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 # commands
@ -278,6 +311,7 @@ The letter is not case-sensitive."""
# Now we have a heading! Let's see if we can get there... # Now we have a heading! Let's see if we can get there...
if (x, y) == (self.playerx, self.playery): if (x, y) == (self.playerx, self.playery):
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0))) #_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 return
dist, path = self.level.path(x, y, self.playerx, self.playery) dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1: if dist == -1:
@ -343,7 +377,7 @@ Character can be the name of the character, or their coordinates."""
args.pop(0) args.pop(0)
if args[0] == 'the': if args[0] == 'the':
args.pop(0) 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): if not self.level.lineOfSight(self.playerx, self.playery, x, y):
print("{} cannot talk to {}.".format(self.playerName, thing.name), file = self.outstream) print("{} cannot talk to {}.".format(self.playerName, thing.name), file = self.outstream)
elif thing == None: elif thing == None:
@ -423,15 +457,15 @@ the name of an item in the player's inventory."""
def useOn(self, args, speed): def useOn(self, args, speed):
"""Called by use when there is an 'on' clause""" """Called by use when there is an 'on' clause"""
onIndex = args.index('on') 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': if args[onIndex+1] == 'the':
onIndex += 1 onIndex += 1
thing, x, y = self.parseCoords(args[onIndex+1:]) thing, x, y = self.parseCoords(args[onIndex+1:], usePlayerCoords = True, allowInventory = True)
useArgs = [] useArgs = []
if 'with' in args: if 'with' in args:
useArgs = args[args.index('with')+1:] useArgs = args[args.index('with')+1:]
if item == None or item.thingID not in self.playerInv: 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())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
if thing == None and x < 0 and y < 0: 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) args.pop(0)
if args[0] == 'the': if args[0] == 'the':
args.pop(0) args.pop(0)
thing, x, y = self.parseCoords(args) thing, x, y = self.parseCoords(args, allowInventory = False)
if thing == None: if thing == None:
print("There is nothing to take.", file = self.outstream) print("There is nothing to take.", file = self.outstream)
return return
@ -522,7 +556,7 @@ Object can be the name of the object, or its coordinates."""
for i in self.playerInv: for i in self.playerInv:
thingName = self.playerInv[i].name thingName = self.playerInv[i].name
if ' '.join(args) == thingName: if ' '.join(args) == thingName:
self.setEvent(0.0, _ge.DropEvent(self.playerInv[args[0]])) self.setEvent(0.0, _ge.DropEvent(self.playerInv[i]))
return return
print('{0} does not have a {1}.'.format(self.playerName, args[0]), file = self.outstream) 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: else:
self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing) 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 # get persistent things from it
if args[0] in self.persist: if args[0] in self.persist:
persistedThings = tuple(self.persist[args[0]].keys()) persistedThings = tuple(self.persist[args[0]].keys())
for i in persistedThings: 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 del self.persist[args[0]][i] # delete them from the persist dict to prevent item duplication
print(self.level.openingText, file = self.outstream) 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: if self.level.name not in self.persist:
self.persist[self.level.name] = {} self.persist[self.level.name] = {}
for i in self.level.persistent: 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 # 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! # save it!
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat' fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
@ -613,7 +650,7 @@ Object can be the name of the object, or its coordinates."""
fileName = args[0] fileName = args[0]
x, y, levelname = 1, 1, 'testing/test1.txt' x, y, levelname = 1, 1, 'testing/test1.txt'
with open(fileName, 'rb') as f: 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) #print(levelname, x, y, file = self.outstream)
self.loadMap((levelname, x, y)) self.loadMap((levelname, x, y))
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_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] del literalStr[l]
l -= 1 l -= 1
elif instr[c] in ' \t\n;}{': elif instr[c] in ' \t\n;}{':
if argStart != l: if l > 0 and instr[c-1] == '\\':
ret.append(''.join(literalStr[argStart:l])) del literalStr[l-1]
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]
l -= 1 l -= 1
elif instr[c] == '}': # close block else:
if len(ret) > 0: 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.append(ret)
script = stack.pop() script = []
ret = [] ret.append(script)
del literalStr[l] ret = []
l -= 1 del literalStr[l]
argStart = l + 1 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 c += 1
l += 1 l += 1
if argStart != l: if argStart != l:
ret.append(''.join(literalStr[argStart:l])) ret.append(''.join(literalStr[argStart:l]))
script.append(ret) if len(ret) > 0:
script.append(ret)
#print('After parsing: {}'.format(script)) #print('After parsing: {}'.format(script))
self.customValues['scriptLocal'] = {} self.customValues['scriptLocal'] = {}
self.runScript(script) ret = self.runScript(script)
del self.customValues['scriptLocal'] if 'scriptLocal' in self.customValues:
del self.customValues['scriptLocal']
if isinstance(ret, _ScriptBreak):
ret = ret.value
return ret
def runScript(self, script: list): def runScript(self, script: list):
"""run a script""" """run a script"""
ret = False
for line in script: 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 # run a player command
self.getIO('playercmd')(line[1:]) 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: 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): def gameEventLoop(self):
#print(self.skipLoop) #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.prevx, self.prevy = self.playerx, self.playery
self.playerx, self.playery = e.x, e.y self.playerx, self.playery = e.x, e.y
else: 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 return False
def handleArrive(self, e): 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) thing = self.level.getThingAtCoords(self.playerx, self.playery)
if thing: if thing:
if thing.thingType == 'x': if thing.thingType == 'x':
a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing))) self.parseScript(thing.onUse)
if a != 'n' and a != 'N': if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)):
self.loadMap((thing.destination, thing.exitid)) 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 return True
else: else:
actor = self.level.getThingByName(e.actor) 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): def handleDrop(self, e):
e.item.x = self.playerx e.item.x = self.playerx
e.item.y = self.playery 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] del self.playerInv[e.item.thingID]
return True 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)) raise ValueError("Non-examinable thing {0} examined.".format(thing.name))
if thing.customValues['pattern'] == 'single': 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': elif thing.customValues['pattern'] == 'loop':
if not 'cursor' in thing.customValues: if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0 thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor'] 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']) thing.customValues['cursor'] = (cursor + 1) % len(thing.customValues['text'])
elif thing.customValues['pattern'] == 'once': elif thing.customValues['pattern'] == 'once':
if not 'cursor' in thing.customValues: if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0 thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor'] 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: if cursor < len(thing.customValues['text']) - 1:
thing.customValues['cursor'] += 1 thing.customValues['cursor'] += 1
elif thing.customValues['pattern'] == 'random': elif thing.customValues['pattern'] == 'random':
cursor = _ra.randrange(len(thing.customValues['text'])) 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 thing.customValues['cursor'] = cursor
if 'set' in thing.customValues: if 'set' in thing.customValues:
if 'cursor' 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: else:
self.customVals[thing.customValues['set']] = 0 self.customValues[thing.customValues['set']] = True
return 0.0 return 0.0
def container(self, thing, args): 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))) raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
# evaluate condition # evaluate condition
if args[1] in ('==', '!=', '<=', '>=', '<', '>'): if len(args) > 1 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
if len(args) < 4: if len(args) < 4:
raise GameError('Incomplete If statement: if {}'.format(' '.join(args))) 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: else:
ret = bool(self.parseValue(args[0])) ret = bool(self.getValueFromString(args[0]))
args = args[1:] args = args[1:]
if inverse: if inverse:
ret = not ret 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 condition is true, evaluate further
if ret: if ret:
if isinstance(args[-1], list): if isinstance(args[-1], list):
self.runScript(args[-1]) return self.runScript(args[-1])
else: else:
self.runScript([args]) return self.runScript([args])
def printfScript(self, args): def printfScript(self, args):
"""Assume the first argument is a stringable object, and format all others.""" """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]""" """takes [customValue, op, value]"""
env = self.customValues if env == None:
env = self.customValues
if len(args) > 0 and args[0] == 'local': if len(args) > 0 and args[0] == 'local':
if 'scriptLocal' in self.customValues: if 'scriptLocal' in self.customValues:
env = self.customValues['scriptLocal'] env = self.customValues['scriptLocal']
@ -925,7 +1008,9 @@ Object can be the name of the object, or its coordinates."""
args.pop(0) args.pop(0)
if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='): if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='):
raise GameError('Arguments are not fit for the setCustomValue script.') 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 # set the value to a default value (0, 0.0, '', etc.) if not yet assigned
if args[0] not in env and args[1] != '=': 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 env[args[0]] &= val
elif args[1] == '^=': elif args[1] == '^=':
env[args[0]] ^= val 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): def spawnThing(self, args):
"""Spawns a thing. [type name=... location=... etc]""" """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:]: for i in args[1:]:
if i[0:5].casefold() == 'name=': if i[0:5].casefold() == 'name=':
name = i[5:] name = i[5:]
elif i[0:10].casefold() == 'location=': elif i[0:9].casefold() == 'location=':
_, x, y = self.parseCoords(i[10:].split()) _, x, y = self.parseCoords(i[9:].split(), usePlayerCoords = False, allowInventory = False)
elif i[0:4].casefold() == 'fgc=': 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:])) raise GameError("Invalid color: {}".format(i[4:]))
for j in i[4:]: for j in i[5:]:
if j not in '0123456789abcdefABCDEF': if j not in '0123456789abcdefABCDEF':
raise GameError("Invalid color: {}".format(i[4:])) raise GameError("Invalid color: {}".format(i[4:]))
fgc = i[4:] fgc = i[4:]
elif i[0:4].casefold() == 'bgc=': 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:])) raise GameError("Invalid color: {}".format(i[4:]))
for j in i[4:]: for j in i[5:]:
if j not in '0123456789abcdefABCDEF': if j not in '0123456789abcdefABCDEF':
raise GameError("Invalid color: {}".format(i[4:])) raise GameError("Invalid color: {}".format(i[4:]))
bgc = i[4:] bgc = i[4:]
elif i[0:6].casefold() == 'shape=': elif i[0:6].casefold() == 'shape=':
if len(i[6:]) != 7 or i[0] != '#': if i[6] not in 'ox^ #|-':
raise GameError("Invalid color: {}".format(i[6:])) raise GameError("Invalid shape: {}".format(i[6]))
for j in i[6:]: shape = i[6]
if j not in '0123456789abcdefABCDEF':
raise GameError("Invalid color: {}".format(i[6:]))
shape = i[6:]
elif i == 'persist': elif i == 'persist':
persist = True persist = True
if x < 0 or y < 0: if x < 0 or y < 0:
raise GameError("Cannot spawn thing: bad location") raise GameError("Cannot spawn thing: bad location")
# Unfortunately, there needs to be a case for each type. # Unfortunately, there needs to be a case for each type.
thing = None
if args[0].casefold() == 'item': if args[0].casefold() == 'item':
# spawn an item # spawn an item
description = 'A nondescript item.' description = 'A nondescript item.'
@ -1027,12 +1119,12 @@ Object can be the name of the object, or its coordinates."""
ranged = False ranged = False
if name == None: if name == None:
name = 'item {}'.format(self.nextThing) name = 'item {}'.format(self.nextThing)
if fgc == None:
fgc = _gm.Item.defaultGraphic['fgc']
if bgc == None: if bgc == None:
bgc = _gm.Item.defaultGraphic['bgc'] bgc = _gm.Item.defaultGraphic[0]
if fgc == None:
fgc = _gm.Item.defaultGraphic[1]
if shape == None: if shape == None:
shape = _gm.Item.defaultGraphic['shape'] shape = _gm.Item.defaultGraphic[2]
for i in args[1:]: for i in args[1:]:
if i[0:12].casefold() == 'description=': if i[0:12].casefold() == 'description=':
description = i[12:] description = i[12:]
@ -1045,9 +1137,8 @@ Object can be the name of the object, or its coordinates."""
elif i[0:3].casefold() == 'cv:': elif i[0:3].casefold() == 'cv:':
equalsign = i.index('=') equalsign = i.index('=')
cv = i[3:equalsign] 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)) 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': elif args[0].casefold() == 'useable':
# spawn a useable thing # spawn a useable thing
description = 'A nondescript 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 playerx, playery = x, y
if name == None: if name == None:
name = 'useable {}'.format(self.nextThing) name = 'useable {}'.format(self.nextThing)
if fgc == None:
fgc = _gm.Item.defaultGraphic['fgc']
if bgc == None: if bgc == None:
bgc = _gm.Item.defaultGraphic['bgc'] bgc = _gm.Useable.defaultGraphic[0]
if fgc == None:
fgc = _gm.Useable.defaultGraphic[1]
if shape == None: if shape == None:
shape = _gm.Item.defaultGraphic['shape'] shape = _gm.Useable.defaultGraphic[2]
for i in args[1:]: for i in args[1:]:
if i[0:12].casefold() == 'description=': if i[0:12].casefold() == 'description=':
description = i[12:] description = i[12:]
@ -1072,24 +1163,23 @@ Object can be the name of the object, or its coordinates."""
elif i[0:3].casefold() == 'cv:': elif i[0:3].casefold() == 'cv:':
equalsign = i.index('=') equalsign = i.index('=')
cv = i[3:equalsign] 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)) 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': elif args[0].casefold() == 'npc':
# spawn an NPC # spawn an NPC
description = 'A nondescript character.' description = 'A nondescript character.'
behavior = '' behavior = 'stand'
inv = [] inv = []
customValues = {} customValues = {}
playerx, playery = x, y playerx, playery = x, y
if name == None: if name == None:
name = 'character {}'.format(self.nextThing) name = 'character {}'.format(self.nextThing)
if fgc == None:
fgc = _gm.Item.defaultGraphic['fgc']
if bgc == None: if bgc == None:
bgc = _gm.Item.defaultGraphic['bgc'] bgc = _gm.NPC.defaultGraphic[0]
if fgc == None:
fgc = _gm.NPC.defaultGraphic[1]
if shape == None: if shape == None:
shape = _gm.Item.defaultGraphic['shape'] shape = _gm.NPC.defaultGraphic[2]
for i in args[1:]: for i in args[1:]:
if i[0:12].casefold() == 'description=': if i[0:12].casefold() == 'description=':
description = i[12:] description = i[12:]
@ -1100,9 +1190,8 @@ Object can be the name of the object, or its coordinates."""
elif i[0:3].casefold() == 'cv:': elif i[0:3].casefold() == 'cv:':
equalsign = i.index('=') equalsign = i.index('=')
cv = i[3:equalsign] 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)) 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': elif args[0].casefold() == 'door':
# spawn a door # spawn a door
description = 'a nondescript door.' description = 'a nondescript door.'
@ -1110,32 +1199,33 @@ Object can be the name of the object, or its coordinates."""
key = None key = None
if name == None: if name == None:
name = 'door {}'.format(self.nextThing) name = 'door {}'.format(self.nextThing)
if fgc == None:
fgc = _gm.Item.defaultGraphic['fgc']
if bgc == None: if bgc == None:
bgc = _gm.Item.defaultGraphic['bgc'] bgc = _gm.Door.defaultGraphic[0]
if fgc == None:
fgc = _gm.Door.defaultGraphic[1]
if shape == None: if shape == None:
shape = _gm.Item.defaultGraphic['shape'] shape = _gm.Door.defaultGraphic[2]
for i in args[1:]: for i in args[1:]:
if i[0:12].casefold() == 'description=': if i[0:12].casefold() == 'description=':
description = i[12:] description = i[12:]
elif i.casefold() == 'locked': elif i.casefold() == 'locked':
locked = True locked = True
thing = _gm.Door(name, x, y, locked, description, key, (bgc, fgc, shape)) 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': elif args[0].casefold() == 'mapexit':
# spawn an exit to another map (use with EXTREME caution!) # spawn an exit to another map (use with EXTREME caution!)
destination = '' destination = ''
prefix = None prefix = None
exitid = 0 exitid = 0
onUse = ''
key = True
if name == None: if name == None:
name = 'somewhere' name = 'somewhere'
if fgc == None:
fgc = _gm.Item.defaultGraphic['fgc']
if bgc == None: if bgc == None:
bgc = _gm.Item.defaultGraphic['bgc'] bgc = _gm.MapExit.defaultGraphic[0]
if fgc == None:
fgc = _gm.MapExit.defaultGraphic[1]
if shape == None: if shape == None:
shape = _gm.Item.defaultGraphic['shape'] shape = _gm.MapExit.defaultGraphic[2]
for i in args[1:]: for i in args[1:]:
if i[0:12].casefold() == 'destination=': if i[0:12].casefold() == 'destination=':
destination = i[12:] destination = i[12:]
@ -1143,8 +1233,11 @@ Object can be the name of the object, or its coordinates."""
prefix = i[7:] prefix = i[7:]
elif i[0:7].casefold() == 'exitid=': elif i[0:7].casefold() == 'exitid=':
exitid = int(i[7:]) exitid = int(i[7:])
thing = _gm.MapExit(name, x, y, exitid, destination, prefix, (bgc, fgc, shape)) elif i[0:6].casefold() == 'onuse=':
self.nextThing = self.level.addThing(thing, self.nextThing, persist) 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': elif args[0].casefold() == 'mapentrance':
# spawn a map entrance # spawn a map entrance
exitid = 0 exitid = 0
@ -1152,7 +1245,10 @@ Object can be the name of the object, or its coordinates."""
if i[0:7].casefold() == 'exitid=': if i[0:7].casefold() == 'exitid=':
exitid = int(i[7:]) exitid = int(i[7:])
thing = _gm.MapEntrance(x, y, exitid, name) 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): def giveToPlayer(self, args):
"""We get to assume it's an item.""" """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=': if i[0:5].casefold() == 'name=':
name = i[5:] name = i[5:]
elif i[0:10].casefold() == 'location=': 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=': elif i[0:4].casefold() == 'fgc=':
if len(i[4:]) != 7 or i[0] != '#': if len(i[4:]) != 7 or i[0] != '#':
raise GameError("Invalid color: {}".format(i[4:])) 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] cv = i[3:equalsign]
customValues[cv] = getValueFromString(i[equalsign+1:]) customValues[cv] = getValueFromString(i[equalsign+1:])
thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape)) 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 # behaviors
def stand(self, actor):
pass
def wander(self, actor): def wander(self, actor):
pass pass

View file

@ -358,7 +358,7 @@ class MapExit(Thing):
yaml_flag = u'!MapExit' yaml_flag = u'!MapExit'
defaultGraphic = ('clear', '#FF0000', 'x') 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 description = name
if prefix: if prefix:
description = "{0} {1}".format(prefix, name) description = "{0} {1}".format(prefix, name)
@ -366,6 +366,8 @@ class MapExit(Thing):
self.exitid = exitid self.exitid = exitid
self.destination = destination self.destination = destination
self.prefix = prefix self.prefix = prefix
self.onUse = onUse
self.key = key
self.graphic = graphic self.graphic = graphic
@classmethod @classmethod
@ -384,6 +386,10 @@ class MapExit(Thing):
ret['graphic'] = graphic ret['graphic'] = graphic
if node.prefix != None: if node.prefix != None:
ret['prefix'] = node.prefix 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) return representer.represent_mapping(cls.yaml_flag, ret)
@classmethod @classmethod
@ -392,6 +398,8 @@ class MapExit(Thing):
constructor.construct_mapping(node, parts, True) constructor.construct_mapping(node, parts, True)
# set default values for optional arguments # set default values for optional arguments
prefix = None prefix = None
onUse = ''
key = True
bgc = MapExit.defaultGraphic[0] bgc = MapExit.defaultGraphic[0]
fgc = MapExit.defaultGraphic[1] fgc = MapExit.defaultGraphic[1]
shape = MapExit.defaultGraphic[2] shape = MapExit.defaultGraphic[2]
@ -406,8 +414,12 @@ class MapExit(Thing):
graphic = (bgc, fgc, shape) graphic = (bgc, fgc, shape)
if 'prefix' in parts: if 'prefix' in parts:
prefix = parts['prefix'] 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], 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): class MapEntrance(Thing):
yaml_flag = u'!MapEntrance' yaml_flag = u'!MapEntrance'
@ -417,7 +429,7 @@ class MapEntrance(Thing):
def __init__(self, x: int, y: int, exitid: int, name = None): def __init__(self, x: int, y: int, exitid: int, name = None):
if name == None: if name == None:
name = 'entrance {}'.format(exitid) 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 self.exitid = exitid
@classmethod @classmethod
@ -574,6 +586,7 @@ Entering a map through stdin will be obsolete once testing is over."""
yaml.register_class(NPC) yaml.register_class(NPC)
yaml.register_class(Door) yaml.register_class(Door)
yaml.register_class(MapExit) yaml.register_class(MapExit)
yaml.register_class(MapEntrance)
if infile != None: if infile != None:
try: try:
with open(infile, 'r') as f: 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.") print("The file could not be read.")
return None, nextThing return None, nextThing
else: else:
raise RuntimeError("No file was specified for loading.") raise MapError("No file was specified for loading.")
# Now what we do with the data # Now what we do with the data
mat = None mat = None
@ -593,10 +606,10 @@ Entering a map through stdin will be obsolete once testing is over."""
#print(layout.text) #print(layout.text)
match = GameMap.matrixRegex.match(layout.lstrip()) match = GameMap.matrixRegex.match(layout.lstrip())
if match == None: 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() mat = match.group()
else: 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 # generate matrix and graph first
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat) mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
@ -632,7 +645,7 @@ list of lists of tuples."""
if x == 0: if x == 0:
x = len(mat[l]) x = len(mat[l])
elif 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 l += 1
#x = 0 #x = 0
mat.append([]) mat.append([])
@ -644,7 +657,7 @@ list of lists of tuples."""
else: else:
matrixStr = matrixStr[i:] matrixStr = matrixStr[i:]
else: # This should happen when it finishes? 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 y = len(mat) - 1
# Now for the graph # 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) newPos = self.coordsToInt(x, y)
else: else:
x, y = self.intToCoords(x) x, y = self.intToCoords(x)
if thing: if thing != None:
oldPos = self.coordsToInt(thing.x, thing.y) oldPos = self.coordsToInt(thing.x, thing.y)
if oldPos in self.thingPos: if oldPos in self.thingPos:
self.thingPos[oldPos].remove(name) self.thingPos[oldPos].remove(thing.thingID)
if len(self.thingPos[oldPos]) == 0: if len(self.thingPos[oldPos]) == 0:
del self.thingPos[oldPos] del self.thingPos[oldPos]
if newPos not in self.thingPos: if newPos not in self.thingPos:
self.thingPos[newPos] = [name] self.thingPos[newPos] = [thing.thingID]
else: else:
self.thingPos[newPos].append(name) self.thingPos[newPos].append(thing.thingID)
relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y
thing.x, thing.y = x, y thing.x, thing.y = x, y
thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery
else: else:
raise RuntimeError("There is nothing to move.".format(name)) raise MapError("There is nothing to move.")

View file

@ -27,36 +27,26 @@ class GameShell(Shell):
self.outstream = _sys.stdout self.outstream = _sys.stdout
self.gameBase = gameBase self.gameBase = gameBase
self.colorMode = 0 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.ps2 = '?> '
self.__inGame = False
# register functions # register functions
self.registerCommand('map', self.showMap) self.registerCommand('load', self.gameBase.loadGame) # should always be available
self.registerCommand('ls', self.showMap) self.registerCommand('flippetywick', self.devMode)
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('options', self.options) self.registerCommand('options', self.options)
self.registerCommand('colorTest', self.colorTest) 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('container', self.container)
self.gameBase.registerIO('dialog', self.dialog) self.gameBase.registerIO('dialog', self.dialog)
self.gameBase.registerIO('info', self.info) self.gameBase.registerIO('info', self.info)
self.gameBase.registerIO('playercmd', self.playercmd)
self.menuMode()
# Helper functions # Helper functions
@ -108,6 +98,39 @@ class GameShell(Shell):
self.__colorTestRoutine(0, 128) # dark self.__colorTestRoutine(0, 128) # dark
self.__colorTestRoutine(0, 256) # medium self.__colorTestRoutine(0, 256) # medium
self.__colorTestRoutine(128, 256) # light 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): def showMap(self, args):
"""map [-l] """map [-l]
@ -170,8 +193,15 @@ If -l is given, a map legend will be printed under the map."""
sides += GameShell.DOWN sides += GameShell.DOWN
if x > 0 and level.mapMatrix[y][x-1][0] == 'w': if x > 0 and level.mapMatrix[y][x-1][0] == 'w':
sides += GameShell.LEFT 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]) rows[-1].append(GameShell.WALLS[sides])
else: 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(' ') rows[-1].append(' ')
index += 1 index += 1
rows[-1] = ''.join(rows[-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): def inv(self, args):
print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv])) 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 # IO calls
def container(self, inv, cont): def container(self, inv, cont):
"""container IO""" """container IO"""
# Pretty print: get length of the longest inventory item's name # Pretty print: get length of the longest inventory item's name
longestLen = 0 longestLen = 0
longestStr = ""
for i in inv: for i in inv:
if len(i) > longestLen: if len(inv[i].name) > longestLen:
longestLen = len(i) longestLen = len(inv[i].name)
longestStr = i
if longestLen > 0: if longestLen > 0:
print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:")) print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:"))
i = 0 i = 0
invKeys = tuple(inv.keys()) invKeys = tuple(inv.keys())
while i < len(invKeys) and i < len(cont): 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 i += 1
while i < len(invKeys): while i < len(invKeys):
print(invKeys[i]) print(inv[invKeys[i]].name)
i += 1 i += 1
while i < len(cont): while i < len(cont):
print(' '*(longestLen+2) + cont[i].name) 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]) charsRead += len(items[instr])
else: else:
print('{} not here.'.format(instr)) print('{} not here.'.format(instr))
input('<strike RETURN>') input('<ENTER>')
for i in items: for i in items:
print(' ', i) print(' ', i)
instr = input("Choose an item to view, or exit: ") 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 return ret
# if ret == 1 or ret == 0, go back again. # if ret == 1 or ret == 0, go back again.
elif 'opener' in dialogObj: 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('<ENTER>')
while isinstance(dialogObj, dict): while isinstance(dialogObj, dict):
if 'action' in dialogObj: if 'action' in dialogObj:
action = dialogObj['action'] action = dialogObj['action']
@ -350,8 +465,8 @@ If -l is given, a map legend will be printed under the map."""
if ans[0] == '?': if ans[0] == '?':
condEnd = ans.index(':') condEnd = ans.index(':')
cond = ans[1:condEnd].strip().split() cond = ans[1:condEnd].strip().split()
if self.gameBase.compareValues(cond[1], cond[0], cond[2]): if self.gameBase.compareValues(cond):
print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:]), width = TERM_SIZE)) print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:].strip()), width = TERM_SIZE))
j += 1 j += 1
else: else:
skips.append(i) skips.append(i)
@ -389,6 +504,31 @@ If -l is given, a map legend will be printed under the map."""
def update(self): def update(self):
self.gameBase.gameEventLoop() 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__': if __name__ == '__main__':
sh = GameShell(GameBase()) sh = GameShell(GameBase())

View file

@ -140,13 +140,25 @@ class Shell(object):
conventionally called args or argv""" conventionally called args or argv"""
self.__commands[commandName] = command 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): def registerAlias(self, a: str, original: list):
"""makes 'a' an alias for original. """makes 'a' an alias for original.
'a' must be one token, but original can be multiple.""" 'a' must be one token, but original can be multiple."""
self.__aliases[a] = original 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): def registerBatch(self, name: str, commands: list):
self.__batches[name] = commands 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): def getAlias(self, a: str):
if a in self.__aliases: 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""" """Take a line of text and turn it into an argument list"""
if instr == '': if instr == '':
return [] return []
literalStr = list(instr)
inQuotes = False inQuotes = False
ret = [] ret = []
argStart = 0 argStart = 0
c = 0 c = 0
l = 0
while c < len(instr): while c < len(instr):
if inQuotes: if inQuotes:
if instr[c] == '"': if instr[c] == '"':
inQuotes = False if instr[c-1] == '\\':
ret.append(instr[argStart:c]) del literalStr[l-1]
argStart = c+1 l -= 1
else:
inQuotes = False
del literalStr[l]
l -= 1
else: else:
if instr[c] == '"': if instr[c] == '"':
inQuotes = True if l > 0 and instr[c-1] == '\\':
if argStart != c: del literalStr[l-1]
ret.append(instr[argStart:c]) l -= 1
argStart = c+1 else:
inQuotes = True
del literalStr[l]
l -= 1
elif instr[c] in ' \t\n': elif instr[c] in ' \t\n':
if argStart != c: if argStart != l:
ret.append(instr[argStart:c]) ret.append(''.join(literalStr[argStart:l]))
argStart = c + 1 argStart = l + 1
c += 1 c += 1
if argStart != c: l += 1
ret.append(instr[argStart:c]) if argStart != l:
ret.append(''.join(literalStr[argStart:l]))
a = self.getAlias(ret[0]) a = self.getAlias(ret[0])
if a: if a:
ret = a + ret[1:] ret = a + ret[1:]