gameshell/gameshell.py

396 lines
18 KiB
Python
Raw Normal View History

# gameshell.py
from shell import Shell
from gamebase import GameBase
import sys as _sys
#import re
import heapq
#import gamemap
import gameevents
import textwrap as _tw
from shutil import get_terminal_size as _gts
#import random
TERM_SIZE = _gts()[0]
class GameShell(Shell):
UP = 1
RIGHT = 2
DOWN = 4
LEFT = 8
WALLS = ('++', '++', ' +', '++', '++', '||', '++', '|+', '+ ', '++',
'==', '++', '++', '+|', '++', '++')
def __init__(self, gameBase):
super(GameShell, self).__init__()
self.outstream = _sys.stdout
self.gameBase = gameBase
self.colorMode = 0
self.ps2 = '?> '
# register functions
self.registerCommand('map', self.showMap)
self.registerCommand('ls', self.showMap)
self.registerCommand('go', self.gameBase.go)
self.registerCommand('move', self.gameBase.go)
self.registerCommand('walk', self.gameBase.go)
self.registerCommand('look', self.gameBase.look)
self.registerCommand('talk', self.gameBase.talk)
self.registerCommand('use', self.gameBase.use)
self.registerCommand('loadMap', self.gameBase.loadMap)
self.registerCommand('man', self.man)
self.registerCommand('help', self.man)
self.registerCommand('save', self.gameBase.saveGame)
self.registerCommand('load', self.gameBase.loadGame)
self.registerCommand('take', self.gameBase.take)
self.registerCommand('get', self.gameBase.take)
self.registerCommand('drop', self.gameBase.drop)
self.registerCommand('inv', self.inv)
self.registerCommand('bag', self.inv)
self.registerCommand('items', self.inv)
self.registerCommand('status', self.status)
self.registerCommand('options', self.options)
self.registerCommand('colorTest', self.colorTest)
self.registerAlias('run', ['go', '-r'])
self.gameBase.registerIO('container', self.container)
self.gameBase.registerIO('dialog', self.dialog)
self.gameBase.registerIO('info', self.info)
# Helper functions
def man(self, args):
super(GameShell, self).man(args)
#heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
def options(self, args):
i = 0
while i < len(args):
if args[i] == 'color':
i += 1
if args[i] in ('0', '3', '4', '8', '24'):
self.colorMode = int(args[i])
i += 1
def __colorTestRoutine(self, lower, upper):
colors = []
step = int((upper - lower) / 8)
#print(step)
for g in range(lower, upper, step):
colors.append(self.color(upper-1, g, lower, False))
#print(len(colors))
colors.append(self.color(upper-1, upper-1, lower, False))
for r in range(upper-step, lower-1, -step):
colors.append(self.color(r, upper-1, lower, False))
#print(len(colors))
for b in range(lower+step, upper, step):
colors.append(self.color(lower, upper-1, b, False))
#print(len(colors))
colors.append(self.color(lower, upper-1, upper-1, False))
for g in range(upper-step, lower-1, -step):
colors.append(self.color(lower, g, upper-1, False))
#print(len(colors))
for r in range(lower+step, upper, step):
colors.append(self.color(r, lower, upper-1, False))
colors.append(self.color(upper-1, lower, upper-1, False))
#print(len(colors))
for b in range(upper-step, lower, -step):
colors.append(self.color(upper-1, lower, b, False))
#print(len(colors))
colors.append('\x1b[0m')
print(' '.join(colors))
def colorTest(self, args):
for i in (3, 4, 8, 24):
print(i)
self.colorMode = i
self.__colorTestRoutine(0, 128) # dark
self.__colorTestRoutine(0, 256) # medium
self.__colorTestRoutine(128, 256) # light
def showMap(self, args):
"""map [-l]
See a map of the local area. "ls" is an alias of map.
"l" and "legend" are aliases of -l.
If -l is given, a map legend will be printed under the map."""
xAxis = ' ' + ''.join([self.gameBase.numberToLetter(i).ljust(2) for i in range(self.gameBase.level.dimensions[0])]) + '\n'
rows = []
index = 0
exits = {}
characters = {}
doors = {}
useables = {}
items = {}
level = self.gameBase.level
textColor = level.wallColors[0]
floorColor = level.floorColors[0]
for y in range(level.dimensions[1]):
rows.append(['{0}{1:2} {2}{3}'.format(self.clearColor(), y, self.color(textColor[1:]), self.color(floorColor[1:], fg = False))])
rows[-1].append(self.color(textColor[1:]))
for x in range(level.dimensions[0]):
pos = level.mapMatrix[y][x]
thing = level.getThingAtPos(index)
if x == self.gameBase.playerx and y == self.gameBase.playery:
if '#0000FF' != textColor:
textColor = '#0000FF'
rows[-1].append(self.color(textColor[1:]))
rows[-1].append('()')
elif thing:
if thing.graphic[1] != textColor:
textColor = thing.graphic[1]
rows[-1].append(self.color(textColor[1:]))
if thing.thingType == 'x': # exit
rows[-1].append('X{0}'.format(thing.exitid))
exits[thing.exitid] = (thing.name, thing.graphic[1])
elif thing.thingType == 'c': # useable
characters[len(characters)+1] = (thing.name, thing.graphic[1])
rows[-1].append('C{0}'.format(len(characters)))
elif thing.thingType == 'd': # door
doors[len(doors)+1] = (thing.name, thing.graphic[1])
rows[-1].append('D{0}'.format(len(doors)))
elif thing.thingType == 'u': # useable
useables[len(useables)+1] = (thing.name, thing.graphic[1])
rows[-1].append('U{0}'.format(len(useables)))
elif thing.thingType == 'i': # item
items[len(items)+1] = (thing.name, thing.graphic[1])
rows[-1].append('I{0}'.format(len(items)))
2019-05-30 15:44:29 -04:00
elif thing.thingType == 'a': # entrance
rows[-1].append(' ')
elif pos[0] == 'w':
if level.wallColors[level.mapMatrix[y][x][1]] != textColor:
textColor = level.wallColors[level.mapMatrix[y][x][1]]
rows[-1].append(self.color(textColor[1:]))
sides = 0
if y > 0 and level.mapMatrix[y-1][x][0] == 'w':
sides += GameShell.UP
if x < level.dimensions[0]-1 and level.mapMatrix[y][x+1][0] == 'w':
sides += GameShell.RIGHT
if y < level.dimensions[1]-1 and level.mapMatrix[y+1][x][0] == 'w':
sides += GameShell.DOWN
if x > 0 and level.mapMatrix[y][x-1][0] == 'w':
sides += GameShell.LEFT
rows[-1].append(GameShell.WALLS[sides])
else:
rows[-1].append(' ')
index += 1
rows[-1] = ''.join(rows[-1])
print(xAxis)
print('{0}\n'.format(self.clearColor(False)).join(rows) + self.clearColor())
self.setClearColor()
if len(args) > 0:
if args[0] == '-l' or args[0] == 'l' or args[0] == 'legend':
legend = ["\n---Legend---\n", "{0}(){1} - {2}".format(self.color('0000FF'), self.clearColor(), self.gameBase.playerName),
"Xn - Exit to another area"]
for i in exits:
legend.append(' {0}X{1}{2} - {3}'.format(self.color(exits[i][1][1:]), i, self.clearColor(), exits[i][0]))
legend.append("Cn - Character")
for i in characters:
legend.append(' {0}U{1}{2} - {3}'.format(self.color(characters[i][1][1:]), i, self.clearColor(), characters[i][0]))
legend.append("Un - Useable object")
for i in useables:
legend.append(' {0}U{1}{2} - {3}'.format(self.color(useables[i][1][1:]), i, self.clearColor(), useables[i][0]))
legend.append("In - Item")
for i in items:
legend.append(' {0}I{1}{2} - {3}'.format(self.color(items[i][1][1:]), i, self.clearColor(), items[i][0]))
legend.append("Dn - Door")
for i in doors:
legend.append(' {0}D{1}{2} - {3}'.format(self.color(doors[i][1][1:]), i, self.clearColor(), doors[i][0]))
print('\n'.join(legend))
#heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
return
def status(self, args):
ret = []
if self.gameBase.level != None:
ret.append("Level:{0:.>74}".format(self.gameBase.level.name))
else:
ret.append("Level:{0:.>74}".format("None"))
ret.append("Gametime:{0:.>71.3}".format(self.gameBase.gameTime))
ret.append("Event queue:")
for i in sorted(self.gameBase.eventQueue):
ret.append("{0:.<8.3}:{1:.>72}".format(i[0], str(i[1])))
2019-05-30 15:44:29 -04:00
ret.append("custom values:")
for i in self.gameBase.customValues:
ret.append("{0:<22}: {1}".format(i, self.gameBase.customValues[i]))
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("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")
for i in self.gameBase.level.things:
j = self.gameBase.level.things[i]
ret.append("{0:<7} {1:<31} {2:<3} {3:<3}".format(i, j.name, j.x, j.y))
ret.append("Persistent:")
for i in self.gameBase.level.persistent:
ret.append(str(i))
print('\n'.join(ret))
#heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
return
def inv(self, args):
print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv]))
2019-05-30 15:44:29 -04:00
# IO calls
def container(self, inv, cont):
"""container IO"""
# Pretty print: get length of the longest inventory item's name
longestLen = 0
longestStr = ""
for i in inv:
if len(i) > longestLen:
longestLen = len(i)
longestStr = i
if longestLen > 0:
print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:"))
i = 0
invKeys = tuple(inv.keys())
while i < len(invKeys) and i < len(cont):
print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(invKeys[i]))
i += 1
while i < len(invKeys):
print(invKeys[i])
i += 1
while i < len(cont):
print(' '*(longestLen+2) + cont[i].name)
i += 1
else:
print('Container:')
for i in cont:
print(i.name)
# Now, actually interacting with the container
timeSpent = 0.5 # using a container always takes at least 1/2 second, even just opening and closing it again.
instr = input("Take, store, or exit: ")
while instr != "exit":
instr = instr.split()
if instr[0] == "take":
# take something out of the container
if instr[1] == "the":
del instr[1]
thing = ' '.join(instr[1:])
for i in range(len(cont)):
if thing == cont[i].name:
inv[cont[i].thingID] = cont[i]
del cont[i]
timeSpent += 0.5
print("{0} taken.".format(thing))
break
elif instr[0] == "store":
# store something in the container
if instr[1] == "the":
del instr[1]
thingName = ' '.join(instr[1:])
for i in inv:
thing = inv[i].name
if thing == thingName:
cont.append(inv[i])
del inv[i]
print("{0} stored.".format(thing))
timeSpent += 0.5
break # so that all things with the same name don't get stored
instr = input("Take, store, or exit: ")
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):
2019-05-30 15:44:29 -04:00
if 'script' in dialogObj:
self.gameBase.parseScript(dialogObj['script'])
if 'cond' in dialogObj:
cases = dialogObj['cond']
ret = 0
for case in cases:
cond = case['case'].split() # should be list like ["value", "==", 1]
if len(cond) == 1 and (cond[0] == 'else' or self.gameBase.getValueFromString(cond[0])):
ret = self.dialog(case)
break
elif len(cond) == 3 and self.gameBase.compareValues(cond[1], cond[0], cond[2]):
ret = self.dialog(case)
break
else:
raise RuntimeError("All routes are false: {}".format(testValue))
if ret > 1:
return ret - 1
else:
return ret
# if ret == 1 or ret == 0, go back again.
elif 'opener' in dialogObj:
print(_tw.fill(dialogObj['opener'], width = TERM_SIZE))
while isinstance(dialogObj, dict):
if 'action' in dialogObj:
action = dialogObj['action']
if action == 'answer':
answer = 0
skips = []
j = 0 # follower to i
if 'answers' in dialogObj and isinstance(dialogObj['answers'], list):
for i in range(len(dialogObj['answers'])):
ans = dialogObj['answers'][i]
if ans[0] == '?':
condEnd = ans.index(':')
cond = ans[1:condEnd].strip().split()
if self.gameBase.compareValues(cond[1], cond[0], cond[2]):
print(_tw.fill('{}: {}'.format(j+1, ans[condEnd+1:]), width = TERM_SIZE))
j += 1
else:
skips.append(i)
else:
print(_tw.fill('{}: {}'.format(j+1, ans), width = TERM_SIZE))
j += 1
answer = int(input(self.ps2)) - 1
# account for offset if there were answer options that didn't meet conditions
for i in skips:
if i <= answer:
answer += 1
else:
break
if 'replies' in dialogObj and isinstance(dialogObj['replies'], list):
ret = self.dialog(dialogObj['replies'][answer])
if ret > 1:
return ret - 1
elif ret == 0:
return 0
# if ret == 1, then do this dialog again
elif len(action) >= 4 and action[:4] == 'back':
if len(action) == 4:
return 1
return int(action[4:])
elif action == 'exit':
return 0
else:
raise RuntimeError('Malformed action: {}'.format(action))
else:
raise RuntimeError("Dialog branch with neither switch nor openner.")
2019-05-30 15:44:29 -04:00
def playercmd(self, args):
self.handleCommand(args)
def update(self):
self.gameBase.gameEventLoop()
if __name__ == '__main__':
sh = GameShell(GameBase())
sh.run()