added "info" and "wear" use functions, and fixed many bugs related to storing things by ID.

This commit is contained in:
Patrick Marsee 2019-05-28 14:23:15 -04:00
parent 1b41c46105
commit 8355dccb33
4 changed files with 165 additions and 44 deletions

View file

@ -25,7 +25,7 @@ class GameBase(object):
self.__behaviors = {} self.__behaviors = {}
self.__gameEvents = {} self.__gameEvents = {}
self.__IOCalls = {} # {str : function} self.__IOCalls = {} # {str : function}
self.customVals = {} # for setting flags and such self.customValues = {} # for setting flags and such
self.level = None self.level = None
self.persist = {} # {level : {thingName : thing}} self.persist = {} # {level : {thingName : thing}}
self.ps2 = '? ' self.ps2 = '? '
@ -58,6 +58,8 @@ class GameBase(object):
self.registerUseFunc('examine', self.examine) self.registerUseFunc('examine', self.examine)
self.registerUseFunc('key', self.key) self.registerUseFunc('key', self.key)
self.registerUseFunc('container', self.container) self.registerUseFunc('container', self.container)
self.registerUseFunc('info', self.info)
self.registerUseFunc('wear', self.wear)
self.registerBehavior('wander', self.wander) self.registerBehavior('wander', self.wander)
# Helper functions # Helper functions
@ -138,16 +140,16 @@ Returns (thing, x, y). "Thing" can be None."""
elif thing != None: elif thing != None:
return thing, thing.x, thing.y return thing, thing.x, thing.y
elif allowInventory: elif allowInventory:
if name in self.playerInv: for i in self.playerInv:
thing = self.playerInv[name] thingName = self.playerInv[i].name
if name == thingName:
thing = self.playerInv[i]
if thing != None: if thing != None:
return thing, -1, -1 return thing, -1, -1
else: else:
return None, -1, -1 return None, -1, -1
#raise RuntimeError("'None' item named '{0}' in player inventory.".format(name)) #raise RuntimeError("'None' item named '{0}' in player inventory.".format(name))
else:
return None, -1, -1 return None, -1, -1
#raise ValueError("{0} cannot be reached.".format(name))
else: # nothing else: # nothing
return None, -1, -1 return None, -1, -1
#raise ValueError("{0} cannot be reached.".format(name)) #raise ValueError("{0} cannot be reached.".format(name))
@ -295,6 +297,7 @@ say "use the lever" rather than just "use lever").
Object can be the name of the object, or its coordinates. It can also be Object can be the name of the object, or its coordinates. It can also be
the name of an item in the player's inventory.""" the name of an item in the player's inventory."""
speed = 0.6666667 speed = 0.6666667
useArgs = []
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run': if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333 speed = 0.3333333
args.pop(0) args.pop(0)
@ -303,19 +306,21 @@ the name of an item in the player's inventory."""
if 'on' in args: if 'on' in args:
self.useOn(args, speed) self.useOn(args, speed)
return return
if 'with' in args:
useArgs = args[args.index('with')+1:]
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.thingID 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.thingID in self.playerInv:
self.setEvent(0.125, _ge.UseEvent(thing)) self.setEvent(0.125, _ge.UseEvent(thing, useArgs))
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:
@ -335,7 +340,7 @@ the name of an item in the player's inventory."""
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)))
self.setEvent(t * speed + 0.125, _ge.UseEvent(thing)) self.setEvent(t * speed + 0.125, _ge.UseEvent(thing, useArgs))
return return
def useOn(self, args, speed): def useOn(self, args, speed):
@ -345,7 +350,10 @@ the name of an item in the player's inventory."""
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:])
if item == None or item.name not in self.playerInv: 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 {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
@ -356,7 +364,7 @@ the name of an item in the player's inventory."""
# 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):
self.setEvent(0.125, _ge.UseOnEvent(item, thing)) self.setEvent(0.125, _ge.UseOnEvent(item, thing, useArgs))
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:
@ -379,7 +387,7 @@ the name of an item in the player's inventory."""
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)))
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing)) self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing, useArgs))
return return
def take(self, args): def take(self, args):
@ -434,10 +442,12 @@ Object can be the name of the object, or its coordinates."""
"""drop [the] item""" """drop [the] item"""
if args[0] == 'the': if args[0] == 'the':
args.pop(0) args.pop(0)
if args[0] in self.playerInv: 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[args[0]]))
else: return
print('{0} do 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)
def loadMap(self, args): def loadMap(self, args):
# loadMap (fileName) # loadMap (fileName)
@ -602,26 +612,26 @@ 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
self.setEvent(self.__useFuncs[e.thing.useFunc](e.thing), _ge.NoOpEvent()) self.setEvent(self.__useFuncs[e.thing.useFunc](e.thing, e.args), _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
self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing), _ge.NoOpEvent()) self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing, e.args), _ge.NoOpEvent())
return False return False
def handleTake(self, e): def handleTake(self, e):
self.level.removeThingByName(e.item.name) self.level.removeThingByID(e.item.thingID)
self.playerInv[e.item.name] = e.item self.playerInv[e.item.thingID] = e.item
return True return True
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.level.addThing(e.item, True)
del self.playerInv[e.item.name] del self.playerInv[e.item.thingID]
return True return True
def handleBehave(self, e): def handleBehave(self, e):
@ -629,7 +639,7 @@ Object can be the name of the object, or its coordinates."""
# default useFuncs: take a list of arguments, return the time the use took # default useFuncs: take a list of arguments, return the time the use took
def examine(self, thing): def examine(self, thing, args):
"""Just prints the given text.""" """Just prints the given text."""
if 'pattern' not in thing.customValues or 'text' not in thing.customValues: if 'pattern' not in thing.customValues or 'text' not in thing.customValues:
raise ValueError("Non-examinable thing {0} examined.".format(thing.name)) raise ValueError("Non-examinable thing {0} examined.".format(thing.name))
@ -661,13 +671,50 @@ Object can be the name of the object, or its coordinates."""
self.customVals[thing.customValues['set']] = 0 self.customVals[thing.customValues['set']] = 0
return 0.0 return 0.0
def container(self, thing): def container(self, thing, args):
"""Acts as a container. Items can be traded between the container and the player's inventory.""" """Acts as a container. Items can be traded between the container and the player's inventory."""
items = list(thing.customValues['items']) items = list(thing.customValues['items'])
self.playerInv, thing.customValues['items'], timeOpen = self.getIO('container')(self.playerInv, items) self.playerInv, thing.customValues['items'], timeOpen = self.getIO('container')(self.playerInv, items)
return timeOpen return timeOpen
def key(self, item, thing): def info(self, thing, args):
"""Acts as a bookshelf, filing cabinet, bulletin board, or anything that contains raw info."""
items = dict(thing.customValues['items'])
return self.getIO('info')(items)
def wear(self, item, args):
"""Wear clotherg or otherwise equip a passive item."""
# An item must be in the player's inventory in order to wear it.
inInv = False
for i in self.playerInv:
if i == item.thingID:
inInv = True
break
if not inInv:
print("You cannot wear what you are not carrying.", file = self.outstream)
return 0.0
if 'wearing' not in self.customValues:
self.customValues['wearing'] = {}
if item.customValues['slot'] not in self.customValues['wearing']:
self.customValues['wearing'][item.customValues['slot']] = item
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
del self.playerInv[item.thingID]
print("{} put on the {}.".format(self.playerName, item.name), file = self.outstream)
else: # the player must be wearing something that will get in the way
# put the old clothes back in the inventory
oldItem = self.customValues['wearing'][item.customValues['slot']]
self.playerInv[oldItem.thingID] = oldItem
self.customValues['wearing'][item.customValues['slot']] = item
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
del self.playerInv[item.thingID]
print("{} took off the {} and put on the {}.".format(self.playerName, oldItem.name, item.name), file = self.outstream)
return 1.0
# item use-on functions
def key(self, item, thing, args):
"""Item is a key, which unlocks a door. This may be implemented for containers later.""" """Item is a key, which unlocks a door. This may be implemented for containers later."""
if isinstance(thing, tuple) or thing.thingType != 'd': if isinstance(thing, tuple) or thing.thingType != 'd':
print("That is not a door.", file = self.outstream) print("That is not a door.", file = self.outstream)

View file

@ -79,15 +79,17 @@ class ArriveEvent(GameEvent):
self.t = t self.t = t
class UseEvent(GameEvent): class UseEvent(GameEvent):
def __init__(self, thing): def __init__(self, thing, args):
super(UseEvent, self).__init__('use') super(UseEvent, self).__init__('use')
self.thing = thing self.thing = thing
self.args = args
class UseOnEvent(GameEvent): class UseOnEvent(GameEvent):
def __init__(self, item, thing): def __init__(self, item, thing, args):
super(UseOnEvent, self).__init__('useon') super(UseOnEvent, self).__init__('useon')
self.thing = thing # thing can be a coordinate pair? self.thing = thing # thing can be a coordinate pair?
self.item = item self.item = item
self.args = args
class TakeEvent(GameEvent): class TakeEvent(GameEvent):
def __init__(self, item): def __init__(self, item):

View file

@ -409,6 +409,29 @@ class MapExit(Thing):
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, graphic)
class MapEntrance(Thing):
yaml_flag = u'!MapEntrance'
# no graphic - should not be drawn
def __init__(self, x: int, y: int, exitid: int):
if prefix:
description = "{0} {1}".format(prefix, name)
super(MapEntrance, self).__init__('a', name, x, y, description, 1)
self.exitid = exitid
@classmethod
def to_yaml(cls, representer, node):
# save usual things
ret = {'location': (node.x, node.y), 'id': node.exitid}
return representer.represent_mapping(cls.yaml_flag, ret)
@classmethod
def from_yaml(cls, constructor, node):
parts = CommentedMap()
constructor.construct_mapping(node, parts, True)
# set default values for optional arguments
return cls(parts['location'][0], parts['location'][1], parts['id'])
class MapError(RuntimeError): class MapError(RuntimeError):
pass pass
@ -575,7 +598,6 @@ Entering a map through stdin will be obsolete once testing is over."""
# generate matrix and graph first # generate matrix and graph first
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat) mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
playerStart = (-1, -1)
level = GameMap(infile, mapGraph, mapMatrix, dimensions) level = GameMap(infile, mapGraph, mapMatrix, dimensions)
# Now, load other info # Now, load other info
@ -664,6 +686,7 @@ list of lists of tuples."""
level.wallColors.append('#7F3F0F') level.wallColors.append('#7F3F0F')
# get things # get things
hasKnownEntrance = False
if 'loadOnce' in info and not preLoaded: if 'loadOnce' in info and not preLoaded:
for thing in info['loadOnce']: for thing in info['loadOnce']:
#print(type(thing)) #print(type(thing))
@ -672,8 +695,9 @@ list of lists of tuples."""
for thing in info['loadAlways']: for thing in info['loadAlways']:
#print(type(thing)) #print(type(thing))
nextThing = level.addThing(thing, nextThing) nextThing = level.addThing(thing, nextThing)
if thing.thingType == 'x' and prevMap == thing.exitid: if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid:
level.playerStart = (thing.x, thing.y) level.playerStart = (thing.x, thing.y)
hasKnownEntrance = True
return nextThing return nextThing
# stuff the gameshell itself might use # stuff the gameshell itself might use
@ -686,6 +710,14 @@ list of lists of tuples."""
if thing.thingID == -1: # This is to ensure that we don't double up IDs. if thing.thingID == -1: # This is to ensure that we don't double up IDs.
thing.thingID = nextThing thing.thingID = nextThing
nextThing += 1 nextThing += 1
# Some things, like containers, have other things as custom values,
# so they need IDs as well.
if thing.thingType in 'iuc':
nextThing = self.addThingRecursive(thing.customValues, nextThing)
if thing.thingType == 'c':
for i in thing.inventory:
i.thingID = nextThing
nextThing += 1
pos = self.coordsToInt(thing.x, thing.y) pos = self.coordsToInt(thing.x, thing.y)
if pos not in self.thingPos: if pos not in self.thingPos:
self.thingPos[pos] = [thing.thingID] self.thingPos[pos] = [thing.thingID]
@ -700,6 +732,21 @@ list of lists of tuples."""
self.persistent.append(thing.thingID) self.persistent.append(thing.thingID)
return nextThing return nextThing
def addThingRecursive(self, container, nextThing = 0):
if isinstance(container, Thing):
container.thingID = nextThing
return nextThing + 1
elif isinstance(container, dict):
for i in container:
nextThing = self.addThingRecursive(container[i], nextThing)
return nextThing
elif isinstance(container, list):
for i in container:
nextThing = self.addThingRecursive(i, nextThing)
return nextThing
else:
return nextThing
def getThing(self, **kwargs): def getThing(self, **kwargs):
if 'name' in kwargs: if 'name' in kwargs:
return self.getThingByName(kwargs['name']) return self.getThingByName(kwargs['name'])
@ -885,7 +932,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
if len(self.thingPos[oldPos]) == 0: if len(self.thingPos[oldPos]) == 0:
del self.thingPos[oldPos] del self.thingPos[oldPos]
oldName = thing.name oldName = thing.name
if oldName in self.thingPos: if oldName in self.thingNames:
self.thingNames[oldName].remove(thing.thingID) self.thingNames[oldName].remove(thing.thingID)
if len(self.thingNames[oldName]) == 0: if len(self.thingNames[oldName]) == 0:
del self.thingNames[oldName] del self.thingNames[oldName]

View file

@ -56,6 +56,7 @@ class GameShell(Shell):
self.registerAlias('run', ['go', '-r']) self.registerAlias('run', ['go', '-r'])
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)
# Helper functions # Helper functions
@ -212,6 +213,9 @@ If -l is given, a map legend will be printed under the map."""
ret.append("Player name:{0:.>68}".format(self.gameBase.playerName)) ret.append("Player name:{0:.>68}".format(self.gameBase.playerName))
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)))
ret.append("Inventory:")
for i in self.gameBase.playerInv:
ret.append("{0:<8}: {1}".format(i, self.gameBase.playerInv[i].name))
ret.append("Things:\nID Name X Y") ret.append("Things:\nID Name X Y")
for i in self.gameBase.level.things: for i in self.gameBase.level.things:
j = self.gameBase.level.things[i] j = self.gameBase.level.things[i]
@ -224,7 +228,7 @@ If -l is given, a map legend will be printed under the map."""
return return
def inv(self, args): def inv(self, args):
print('\n'.join([i for i in self.gameBase.playerInv])) print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv]))
def container(self, inv, cont): def container(self, inv, cont):
"""container IO""" """container IO"""
@ -236,7 +240,7 @@ If -l is given, a map legend will be printed under the map."""
longestLen = len(i) longestLen = len(i)
longestStr = i longestStr = i
if longestLen > 0: if longestLen > 0:
print('{{0:<{0}}}{1}'.format(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):
@ -264,7 +268,7 @@ If -l is given, a map legend will be printed under the map."""
thing = ' '.join(instr[1:]) thing = ' '.join(instr[1:])
for i in range(len(cont)): for i in range(len(cont)):
if thing == cont[i].name: if thing == cont[i].name:
inv[cont[i].name] = cont[i] inv[cont[i].thingID] = cont[i]
del cont[i] del cont[i]
timeSpent += 0.5 timeSpent += 0.5
print("{0} taken.".format(thing)) print("{0} taken.".format(thing))
@ -273,15 +277,36 @@ If -l is given, a map legend will be printed under the map."""
# store something in the container # store something in the container
if instr[1] == "the": if instr[1] == "the":
del instr[1] del instr[1]
thing = ' '.join(instr[1:]) thingName = ' '.join(instr[1:])
if thing in inv: for i in inv:
cont.append(inv[thing]) thing = inv[i].name
del inv[thing] if thing == thingName:
cont.append(inv[i])
del inv[i]
print("{0} stored.".format(thing)) print("{0} stored.".format(thing))
timeSpent += 0.5 timeSpent += 0.5
break # so that all things with the same name don't get stored
instr = input("Take, store, or exit: ") instr = input("Take, store, or exit: ")
return inv, cont, timeSpent return inv, cont, timeSpent
def info(self, items):
"""IO for collections of information"""
charsRead = 0
for i in items:
print(' ', i)
instr = input("Choose an item to view, or exit: ")
while instr != 'exit':
if instr in items:
print(_tw.fill(items[instr], width = TERM_SIZE))
charsRead += len(items[instr])
else:
print('{} not here.'.format(instr))
input('<strike RETURN>')
for i in items:
print(' ', i)
instr = input("Choose an item to view, or exit: ")
return charsRead / 27 # based on average 250 words per minute, and word length of 5.5 + 1 for space.
def dialog(self, dialogObj): def dialog(self, dialogObj):
if 'opener' in dialogObj: if 'opener' in dialogObj:
print(_tw.fill(dialogObj['opener'], width = TERM_SIZE)) print(_tw.fill(dialogObj['opener'], width = TERM_SIZE))