# gameshell.py from shell import Shell from gamebase import GameBase import sys as _sys import os as _os #import re import heapq #import gamemap import gameevents import textwrap as _tw from shutil import get_terminal_size as _gts import gameutil as _gu #import random TERM_SIZE = _gts()[0] class GameShell(Shell): UP = 1 RIGHT = 2 DOWN = 4 LEFT = 8 WALLS = ('++', '++', ' +', '++', '++', '||', '++', '|+', '+ ', '++', '==', '++', '++', '+|', '++', '++') def __init__(self, gameBase, gameData = 'testing/testdata.yml'): super(GameShell, self).__init__() self.outstream = _sys.stdout self.gameBase = gameBase self.colorMode = 0 data = self.gameBase.loadGameData(gameData) self.gameTitle = 'Game Shell' # should be changed for actual games if 'title' in data: self.gameTitle = data['title'] self.startLevel = 'testing/test1.yml' # should be changed for actual games if 'startLevel' in data: self.startLevel = data['startLevel'] self.openingText = '{}\nIn Development' if 'openingText' in data: self.openingText = data['openingText'] if 'playerName' in data: self.gameBase.playerName = data['playerName'] if 'playerDescription' in data: self.gameBase.playerDescription = data['playerDescription'] self.ps2 = '?> ' self.__inGame = False # register functions self.registerCommand('load', self.loadGame) # should always be available self.registerCommand('flippetywick', self.devMode) self.registerCommand('options', self.options) self.registerCommand('colorTest', self.colorTest) self.registerCommand('man', self.man) self.registerCommand('help', self.man) self.registerCommand('new', self.newGame) self.gameBase.registerIO('container', self.container) self.gameBase.registerIO('startDialog', self.startDialog) self.gameBase.registerIO('openDialog', self.openDialog) self.gameBase.registerIO('respondDialog', self.respondDialog) self.gameBase.registerIO('endDialog', self.endDialog) self.gameBase.registerIO('info', self.info) self.gameBase.registerIO('playercmd', self.playercmd) # Helper functions def man(self, args): super(GameShell, self).man(args) def options(self, args): i = 0 while i < len(args): if args[i] == 'color': i += 1 if len(args) > i: if args[i] in ('0', '3', '4', '8', '24'): self.colorMode = int(args[i]) i += 1 else: print("Valid color depths are 0, 3, 4, 8, and 24.") else: print("Valid color depths are 0, 3, 4, 8, and 24.") elif args[i] == 'automove': i += 1 if len(args) > i: if args[i] in ('on', 'off'): self.gameBase.autoMove = args[i] == 'on' i += 1 else: print("Valid options are 'on' and 'off'.") else: print("Valid options are 'on' and 'off'.") else: print("Could not identify option {}".format(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 __getAdjFloors(self, level, x, y): """Get the floor colors of the 8 nearest tiles.""" adjFloors = [] for i in range(y-1, y+2): if i < 0 or i >= level.dimensions[1]: continue for j in range(x-1, x+2): if j < 0 or j >= level.dimensions[0]: continue if level.mapMatrix[i][j][0] == 'e': adjFloors.append(level.mapMatrix[i][j][1]) return adjFloors def __getUnderWallColor(self, level, x, y): adjFloors = self.__getAdjFloors(level, x, y) if len(adjFloors) > 0: counts = {} highestCount = 0 ret = -1 for i in adjFloors: if i in counts: counts[i] += 1 else: counts[i] = 1 if counts[i] > highestCount: ret = i highestCount = counts[i] elif counts[i] == highestCount and i < ret: ret = i return ret else: return -1 def showMap(self, args): """map [-l] 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([_gu.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] priorities = {'p': 1, 'n': 2, 'i': 3, 'u': 4, 'd': 5, 'x': 6, 'a': 7} 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] things = level.getThingsAtPos(index) if len(things) > 0: # Prioritize types: p, n, i, u, d, x, a thing = things[0] for i in things[1:]: if priorities[i.thingType] < priorities[thing.thingType]: thing = i if thing.graphic.foreground != textColor: textColor = thing.graphic.foreground #print(textColor) rows[-1].append(self.color(textColor[1:])) if thing.thingType == 'p': # player rows[-1].append('()') elif thing.thingType == 'x': # exit rows[-1].append('X{0}'.format(thing.exitid)) exits[thing.exitid] = (thing.name, thing.graphic.foreground) elif thing.thingType == 'n': # NPC characters[len(characters)+1] = (thing.name, thing.graphic.foreground) rows[-1].append('C{0}'.format(len(characters))) elif thing.thingType == 'd': # door doors[len(doors)+1] = (thing.name, thing.graphic.foreground) rows[-1].append('D{0}'.format(len(doors))) elif thing.thingType == 'u': # useable useables[len(useables)+1] = (thing.name, thing.graphic.foreground) rows[-1].append('U{0}'.format(len(useables))) elif thing.thingType == 'i': # item items[len(items)+1] = (thing.name, thing.graphic.foreground) rows[-1].append('I{0}'.format(len(items))) else: # 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 underWallColor = self.__getUnderWallColor(level, x, y) if level.floorColors[underWallColor] != floorColor and underWallColor != -1: floorColor = level.floorColors[underWallColor] rows[-1].append(self.color(floorColor[1:], fg = False)) rows[-1].append(GameShell.WALLS[sides]) else: if level.floorColors[level.mapMatrix[y][x][1]] != floorColor: floorColor = level.floorColors[level.mapMatrix[y][x][1]] rows[-1].append(self.color(floorColor[1:], fg = False)) rows[-1].append(' ') index += 1 rows[-1] = ''.join(rows[-1]) 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:.>71}".format(i[0], str(i[1]))) 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.player.name)) ret.append("Player position:{0:.>64}".format("{0}{1}".format(_gu.numberToLetter(self.gameBase.player.x), self.gameBase.player.y))) ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(_gu.numberToLetter(self.gameBase.player.prevx), self.gameBase.player.prevy))) ret.append("Inventory:") for i in self.gameBase.player.inventory: ret.append("{0:<8}: {1}".format(i.thingID, 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([i for i in self.gameBase.player.thingNames])) 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': pass elif response[0] in 'Xx': # cancel return else: sf = input('Save file: ') if len(sf) > 0: self.gameBase.saveGame([sf]) else: print('No save file given, cancelling exit.') else: sf = input('Save file: ') if len(sf) > 0: self.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 loadGame(self, args): # Check if there's a name in args. If not, just present a list of save files. if len(args) == 0: import pickle as _pi for fileName in _os.listdir('saves'): with open(f'saves/{fileName}', 'rb') as f: player, levelname, persist, eventQueue, customValues, gameTime, nextThing = _pi.load(f) print(f'{fileName[:-4]}\n - {levelname[5:-4]}') return if self.__inGame: response = input("Do you want to save before exiting (Y/n/x)? ") if len(response) > 0: if response[0] in 'Nn': pass elif response[0] in 'Xx': # cancel return else: sf = input('Save file: ') if len(sf) > 0: self.gameBase.saveGame([sf]) else: print('No save file given, cancelling exit.') return else: sf = input('Save file: ') if len(sf) > 0: self.gameBase.saveGame([sf]) else: print('No save file given, cancelling exit.') return try: self.gameBase.loadGame(args) 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('wait', self.gameBase.wait) 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('wait', self.gameBase.wait) 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.""" args = ['"' + a + '"' for a in args] self.gameBase.parseScript(' '.join(args) + '\n') # IO calls def container(self, player, cont: list): """container IO Player is modified through side-effect.""" # Pretty print: get length of the longest inventory item's name longestLen = 0 for i in player.thingNames: if len(i) > longestLen: longestLen = len(i) if longestLen > 0: inv = player.inventory # do this assignment because player.inventory is O(n) print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:")) i = 0 while i < len(inv) and i < len(cont): print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(inv[i].name)) i += 1 while i < len(inv): print(inv[i].name) 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("Look, take, store, or exit: ") while instr != "exit": instr = instr.split() if len(instr) != 0: if instr[0] == "take": # take something out of the container if len(instr) > 1 and instr[1] == "the": del instr[1] thingName = ' '.join(instr[1:]) if len(thingName) == 0: print(f"{self.gameBase.player.name} takes nothing.") else: for i in range(len(cont)): if thingName == cont[i].name: player.addThing(cont[i]) del cont[i] timeSpent += 0.5 print(f"{thingName} taken.") break else: # If it got here, it didn't find it. print(f"No {thingName} in container.") elif instr[0] == "store": # store something in the container if len(instr) > 1 and instr[1] == "the": del instr[1] thingName = ' '.join(instr[1:]) if len(thingName) == 0: print(f"{self.gameBase.player.name} stores nothing.") elif thingName in player.thingNames: cont.append(player.removeThingByName(thingName)) print(f"{thingName} stored.") timeSpent += 0.5 else: print(f"No {thingName} in inventory.") elif instr[0] == "look": # look at something in the container if len(instr) > 1 and instr[1] == "at": del instr[1] if len(instr) > 1 and instr[1] == "the": del instr[1] thingName = ' '.join(instr[1:]) if len(thingName) == 0: print(f"{self.gameBase.player.name} looks at nothing.") else: # Check the container first. for i in range(len(cont)): if thingName == cont[i].name: print(self.gameBase.justifyText(str(cont[i]))) break else: # If it wasn't there, try the player's inventory. if thingName in player.thingNames: print(self.gameBase.justifyText(str(player.getThingByName(thingName)))) else: # If we get here, it just isn't there. print(f"There is no {thingName} to look at.") instr = input("Look, take, store, or exit: ") return 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('') 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 startDialog(self): pass def openDialog(self, opener): for line in opener: print(_tw.fill(line, width = TERM_SIZE)) input("...") def respondDialog(self, options): for lineNo in range(len(options)): print(_tw.fill('{}: {}'.format(lineNo+1, options[lineNo]), width = TERM_SIZE)) answer = -1 while answer < 0 or answer >= len(options): answerString = input(self.ps2) if not answerString.isdigit(): # If the player inputs a non-integer, just prompt again. continue answer = int(answerString) - 1 return answer def endDialog(self): pass def playercmd(self, args): self.handleCommand(args) def update(self): self.gameBase.gameEventLoop() def exitShell(self, args): if self.__inGame: response = input("Do you want to save before exiting (Y/n/x)? ") if len(response) > 0: if response[0] in 'Nn': super(GameShell, self).exitShell(args) elif response[0] in 'Xx': # cancel return else: sf = input('Save file: ') if len(sf) > 0: self.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: self.gameBase.saveGame([sf]) super(GameShell, self).exitShell(args) else: print('No save file given, cancelling exit.') else: super(GameShell, self).exitShell(args) if __name__ == '__main__': sh = GameShell(GameBase()) sh.menuMode() sh.run() # |\_/| # /0 0\ # \o/