Added ability to make batch functions in the shell, and did some reworking of the event system in gamebase to allow it

This commit is contained in:
Patrick Marsee 2019-02-06 18:29:43 -05:00
parent 9055d8b257
commit bc70bad1c7
3 changed files with 130 additions and 54 deletions

View file

@ -25,6 +25,7 @@ class GameBase(object):
self.ps2 = '? ' self.ps2 = '? '
self.eventQueue = [] self.eventQueue = []
self.gameTime = 0.0 self.gameTime = 0.0
self.skipLoop = True
# player info # player info
self.playerx = -1 self.playerx = -1
@ -187,18 +188,18 @@ 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)))
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:
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream) print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
else: else:
pos = self.level.coordsToInt(self.playerx, self.playery) pos = self.level.coordsToInt(self.playerx, self.playery)
space = path[pos] space = path[pos]
if space == -1: if space == -1:
_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0))) self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0))
return return
#target = self.level.coordsToInt(x, y) #target = self.level.coordsToInt(x, y)
t = 1 t = 1
@ -207,12 +208,11 @@ The letter is not case-sensitive."""
newx, newy = self.level.intToCoords(space) newx, newy = self.level.intToCoords(space)
space = path[space] space = path[space]
if space != -1: if space != -1:
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
else: else:
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed))) self.setEvent(t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed))
break break
t += 1 t += 1
#newx, newy = self.level.intToCoords(space) #newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed))) #_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed)))
return return
@ -236,7 +236,7 @@ Object can be the name of the object, or its coordinates."""
print(self.justifyText(str(thing)), file = self.outstream) print(self.justifyText(str(thing)), file = self.outstream)
else: else:
print("There is nothing to see here.\n", file = self.outstream) print("There is nothing to see here.\n", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
def use(self, args): def use(self, args):
"""use [-r] [the] object [on [the] object2] """use [-r] [the] object [on [the] object2]
@ -261,21 +261,21 @@ the name of an item in the player's inventory."""
thing, x, y = self.parseCoords(args) thing, x, y = self.parseCoords(args)
if thing == None: if thing == None:
print("There is nothing to use.", file = self.outstream) print("There is nothing to use.", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
if thing.thingType != 'u' and thing.name not in self.playerInv: if thing.thingType != 'u' and thing.name not in self.playerInv:
print("The {0} cannot be used.".format(thing.name), file = self.outstream) print("The {0} cannot be used.".format(thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
# Similar to go, but not quite the same. # Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery) or thing.name in self.playerInv: if (x, y) == (self.playerx, self.playery) or thing.name in self.playerInv:
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.UseEvent(thing))) self.setEvent(0.125, _ge.UseEvent(thing))
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:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream) print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
else: else:
pos = self.level.coordsToInt(self.playerx, self.playery) pos = self.level.coordsToInt(self.playerx, self.playery)
@ -286,11 +286,11 @@ the name of an item in the player's inventory."""
while space != -1: while space != -1:
newx, newy = self.level.intToCoords(space) newx, newy = self.level.intToCoords(space)
space = path[space] space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
t += 1 t += 1
#newx, newy = self.level.intToCoords(space) #newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) #_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
_hq.heappush(self.eventQueue, (self.gameTime + t * speed + 0.125, _ge.UseEvent(thing))) self.setEvent(t * speed + 0.125, _ge.UseEvent(thing))
return return
def useOn(self, args, speed): def useOn(self, args, speed):
@ -302,16 +302,16 @@ the name of an item in the player's inventory."""
thing, x, y = self.parseCoords(args[onIndex+1:]) thing, x, y = self.parseCoords(args[onIndex+1:])
if item == None or item.name not in self.playerInv: if item == None or item.name not in self.playerInv:
print("There is no {0} in the inventory.".format(item.name), file = self.outstream) print("There is no {0} in the inventory.".format(item.name), 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:
print("Argument contains 'to' but with no real predicate.", file = self.outstream) print("Argument contains 'to' but with no real predicate.", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
# Similar to go, but not quite the same. # Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery): if (x, y) == (self.playerx, self.playery):
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.UseOnEvent(item, thing))) self.setEvent(0.125, _ge.UseOnEvent(item, thing))
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:
@ -319,7 +319,7 @@ the name of an item in the player's inventory."""
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream) print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
else: else:
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream) print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
else: else:
pos = self.level.coordsToInt(self.playerx, self.playery) pos = self.level.coordsToInt(self.playerx, self.playery)
@ -330,11 +330,11 @@ the name of an item in the player's inventory."""
while space != -1: while space != -1:
newx, newy = self.level.intToCoords(space) newx, newy = self.level.intToCoords(space)
space = path[space] space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
t += 1 t += 1
#newx, newy = self.level.intToCoords(space) #newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) #_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
_hq.heappush(self.eventQueue, (self.gameTime + t * speed + 0.125, _ge.UseOnEvent(item, thing))) self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing))
return return
def take(self, args): def take(self, args):
@ -357,17 +357,17 @@ Object can be the name of the object, or its coordinates."""
return return
if thing.thingType != 'i': if thing.thingType != 'i':
print("The {0} cannot be taken.".format(thing.name), file = self.outstream) print("The {0} cannot be taken.".format(thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
# Similar to go, but not quite the same. # Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery): if (x, y) == (self.playerx, self.playery):
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.TakeEvent(thing))) self.setEvent(0.125, _ge.TakeEvent(thing))
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:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream) print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
else: else:
pos = self.level.coordsToInt(self.playerx, self.playery) pos = self.level.coordsToInt(self.playerx, self.playery)
@ -378,11 +378,11 @@ Object can be the name of the object, or its coordinates."""
while space != -1: while space != -1:
newx, newy = self.level.intToCoords(space) newx, newy = self.level.intToCoords(space)
space = path[space] space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
t += 1 t += 1
#newx, newy = self.level.intToCoords(space) #newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy))) #_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
_hq.heappush(self.eventQueue, (self.gameTime + t * speed + 0.125, _ge.TakeEvent(thing))) self.setEvent(t * speed + 0.125, _ge.TakeEvent(thing))
return return
def drop(self, args): def drop(self, args):
@ -390,7 +390,7 @@ Object can be the name of the object, or its coordinates."""
if args[0] == 'the': if args[0] == 'the':
args.pop(0) args.pop(0)
if args[0] in self.playerInv: if args[0] in self.playerInv:
_hq.heappush(self.eventQueue, (self.gameTime, _ge.DropEvent(self.playerInv[args[0]]))) self.setEvent(0.0, _ge.DropEvent(self.playerInv[args[0]]))
else: else:
print('{0} do not have a {1}.'.format(self.playerName, args[0]), file = self.outstream) print('{0} do not have a {1}.'.format(self.playerName, args[0]), file = self.outstream)
@ -464,7 +464,7 @@ Object can be the name of the object, or its coordinates."""
del self.persist[self.level.name][i] del self.persist[self.level.name][i]
# push a no-op event so that saving doesn't cost player characters time # push a no-op event so that saving doesn't cost player characters time
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent())) #_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return return
def loadGame(self, args): def loadGame(self, args):
@ -483,19 +483,29 @@ Object can be the name of the object, or its coordinates."""
self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime = _pi.load(f) self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime = _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()))
return return
def gameEventLoop(self): def gameEventLoop(self):
#print(self.skipLoop)
if self.skipLoop:
return
#print(self.skipLoop)
while len(self.eventQueue) > 0: while len(self.eventQueue) > 0:
ev = _hq.heappop(self.eventQueue) ev = _hq.heappop(self.eventQueue)
self.gameTime = ev[0] self.gameTime = ev[0]
e = ev[1] e = ev[1]
if self.__gameEvents[e.eventType](e): if self.__gameEvents[e.eventType](e):
#print('break loop')
break break
if len(self.eventQueue) == 0: if len(self.eventQueue) == 0:
self.gameTime = 0.0 self.gameTime = 0.0
_ge.resetEventNum() _ge.resetEventNum()
self.skipLoop = True
def setEvent(self, t, e, skip = False):
_hq.heappush(self.eventQueue, (self.gameTime + t, e))
self.skipLoop = skip
# default event handlers # default event handlers
@ -540,14 +550,14 @@ Object can be the name of the object, or its coordinates."""
if e.thing.useFunc == '': if e.thing.useFunc == '':
print('The {0} cannot be used by itself.'.format(e.thing.name), file = self.outstream) print('The {0} cannot be used by itself.'.format(e.thing.name), file = self.outstream)
return True return True
_hq.heappush(self.eventQueue, (self.gameTime + self.__useFuncs[e.thing.useFunc](e.thing), _ge.NoOpEvent())) self.setEvent(self.__useFuncs[e.thing.useFunc](e.thing), _ge.NoOpEvent())
return False return False
def handleUseOn(self, e): def handleUseOn(self, e):
if e.item.useOnFunc == '': if e.item.useOnFunc == '':
print('The {0} cannot be used on other objects.'.format(e.item.name), file = self.outstream) print('The {0} cannot be used on other objects.'.format(e.item.name), file = self.outstream)
return True return True
_hq.heappush(self.eventQueue, (self.gameTime + self.__useFuncs[e.item.useOnFunc](e.item, e.thing), _ge.NoOpEvent())) self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing), _ge.NoOpEvent())
return False return False
def handleTake(self, e): def handleTake(self, e):

View file

@ -54,7 +54,7 @@ class GameShell(Shell):
def man(self, args): def man(self, args):
super(GameShell, self).man(args) super(GameShell, self).man(args)
heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent())) #heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
def options(self, args): def options(self, args):
i = 0 i = 0
@ -182,7 +182,7 @@ If -l is given, a map legend will be printed under the map."""
for i in doors: for i in doors:
legend.append(' {0}D{1}{2} - {3}'.format(self.color(doors[i][1][1:]), i, self.clearColor(), doors[i][0])) legend.append(' {0}D{1}{2} - {3}'.format(self.color(doors[i][1][1:]), i, self.clearColor(), doors[i][0]))
print('\n'.join(legend)) print('\n'.join(legend))
heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent())) #heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
return return
def status(self, args): def status(self, args):
@ -199,7 +199,7 @@ If -l is given, a map legend will be printed under the map."""
ret.append("Player position:{0:.>64}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.playerx), self.gameBase.playery))) ret.append("Player position:{0:.>64}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.playerx), self.gameBase.playery)))
ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.prevx), self.gameBase.prevy))) ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.prevx), self.gameBase.prevy)))
print('\n'.join(ret)) print('\n'.join(ret))
heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent())) #heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
return return
def inv(self, args): def inv(self, args):

108
shell.py
View file

@ -20,22 +20,24 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA. # MA 02110-1301, USA.
# #
# Ver. 0.1.0029 # Ver. 0.1.0032
import types as _types import types as _types
import traceback as _tb import traceback as _tb
import math as _math import math as _math
import re as _re
_CONV24TO8 = 6 / 256 _CONV24TO8 = 6 / 256
class Shell(object): class Shell(object):
def __init__(self): def __init__(self):
self.__commands = {'exit': self.exitShell} self.__commands = {'exit': self.exitShell, 'batch': self.batch, 'alias': self.makeAlias, 'def': self.defineBatch}
self.__aliases = {} self.__aliases = {}
self.__batches = {}
self.ps1 = '> ' self.ps1 = '> '
self.ps2 = '> ' self.ps2 = '. '
self.colorMode = 0 # bits of color depth. Supports: 0, 3, 4, 8, 24 self.colorMode = 0 # bits of color depth. Supports: 0, 3, 4, 8, 24
self.prevColor = '\x1b[0m' self.prevColor = '\x1b[0m'
self.__exit = False self.__exit = False
@ -126,16 +128,7 @@ class Shell(object):
print(self.ps1, end = '') print(self.ps1, end = '')
command = self.scanInput() command = self.scanInput()
# we have to handle shell built-ins first (when we get some) # we have to handle shell built-ins first (when we get some)
if len(command) == 0: self.handleCommand(command)
continue
if command[0] in self.__commands:
try:
self.__commands[command[0]](command[1:])
except Exception as e:
_tb.print_exc()
print(e)
else:
self.handleUnknownCommand(command)
self.update() self.update()
self.__exit = False self.__exit = False
@ -152,20 +145,23 @@ conventionally called args or argv"""
'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 registerBatch(self, name: str, commands: list):
self.__batches[name] = commands
def getAlias(self, a: str): def getAlias(self, a: str):
if a in self.__aliases: if a in self.__aliases:
return self.__aliases[a] return self.__aliases[a]
else: else:
return None return None
def exitShell(self, args): def getBatch(self, name: str):
"""The default exit command.""" if name in self.__batches:
self.__exit = True return ['batch']
else:
return None
# Beyond this point are functions that are called within the main loop. def scanLine(self, instr):
def scanInput(self): """Take a line of text and turn it into an argument list"""
"""Parses input. Override this for custom input parsing, or input source."""
instr = input()
if instr == '': if instr == '':
return [] return []
inQuotes = False inQuotes = False
@ -193,9 +189,79 @@ conventionally called args or argv"""
ret.append(instr[argStart:c]) ret.append(instr[argStart:c])
a = self.getAlias(ret[0]) a = self.getAlias(ret[0])
if a: if a:
ret = a[:] + ret[1:] ret = a + ret[1:]
b = self.getBatch(ret[0])
if b:
ret = b + ret
return ret return ret
# Default functions
def exitShell(self, args):
"""The default exit command."""
self.__exit = True
def makeAlias(self, args):
"""Make an alias."""
self.registerAlias(args[0], args[1:])
def defineBatch(self, args):
"""Define a batch file."""
if len(args) != 1:
raise ValueError('def takes only one argument')
ret = []
command = input(self.ps2)
while command != 'end':
ret.append(command)
command = input(self.ps2)
self.registerBatch(args[0], ret)
def batch(self, args):
"""Run commands in batch mode
$0 is the name of the batch
$1 - $n are individual arguments
$* is all the arguments except 0
$>0 - $>n is all arguments after the nth
$>=0 - $>=n is the nth and later arguments"""
script = self.__batches[args[0]]
var = _re.compile(r'\$((?:[>][=]?)?[0-9]+)')
u = False
for line in script:
if u:
self.update()
else:
u = True
newLine = line.replace(r'$*', ' '.join(args[1:]))
matches = var.finditer(newLine)
for m in matches:
n = m.group(1)
if n[0] == '>':
if n[1] == '=':
num = int(n[2:])
else:
num = int(n[1:])+1
newLine = newLine.replace(m.group(), ' '.join(args[num:]))
else:
newLine = newLine.replace(m.group(), args[int(n)])
self.handleCommand(self.scanLine(newLine))
# Beyond this point are functions that are called within the main loop.
def scanInput(self):
"""Parses input. Override this for custom input parsing, or input source."""
return self.scanLine(input())
def handleCommand(self, command):
if len(command) == 0:
return
if command[0] in self.__commands:
try:
self.__commands[command[0]](command[1:])
except Exception as e:
_tb.print_exc()
print(e)
else:
self.handleUnknownCommand(command)
def handleUnknownCommand(self, command): def handleUnknownCommand(self, command):
"""Handle commands that aren't registered. Override this if you want to do """Handle commands that aren't registered. Override this if you want to do
something with those commands.""" something with those commands."""