Initial commit

This commit is contained in:
Patrick Marsee 2019-01-18 14:56:51 -05:00
commit 565b6ba903
12 changed files with 2284 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
maps
saves
__pycache__
winsh.py

628
gamebase.py Normal file
View file

@ -0,0 +1,628 @@
# gamebase.py
import re as _re
import heapq as _hq
import gamemap as _gm
import gameevents as _ge
import random as _ra
import sys as _sys
import pickle as _pi
class GameBase(object):
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"
#coordRegex2 = _re.compile(r'\(([0-9]+), ([0-9]+)\)') # "(2, 12)"
#coordRegex3 = _re.compile(r'(-?[0-9]+),? (-?[0-9]+)') # "2 12" or "2, 12"
def __init__(self):
self.outstream = _sys.stdout
self.__useFuncs = {}
self.__gameEvents = {}
self.customVals = {} # for setting flags and such
self.level = None
self.persist = {} # {level : {thingName : thing}}
self.ps2 = '? '
self.eventQueue = []
self.gameTime = 0.0
# player info
self.playerx = -1
self.playery = -1
self.prevx = -1 # prevx and prevy are for moving the player out of the way
self.prevy = -1 # when the tile they're standing on becomes blocked.
self.playerName = 'You'
self.playerInv = {}
# function deligates
self.onLevelLoad = None # str : level name? -> None
self.onContainer = None # list : contents -> list : newContents, float : timeOpen
# default events and useFuncs
self.registerEvent('noop', self.handleNoOp)
self.registerEvent('go', self.handleGo)
self.registerEvent('arrive', self.handleArrive)
self.registerEvent('use', self.handleUse)
self.registerEvent('useon', self.handleUseOn)
self.registerEvent('take', self.handleTake)
self.registerEvent('drop', self.handleDrop)
self.registerUseFunc('examine', self.examine)
self.registerUseFunc('key', self.key)
# Helper functions
def requestInput(self, prompt = ''):
"""Like input by default, but should be overridden if you don't want
to get input from stdin."""
return input(prompt)
def letterToNumber(self, letter):
if letter.isdecimal():
return int(letter)
ret = 0
sign = 1
start = 0
for i in range(len(letter)):
if letter[i] == '-':
sign = -sign
start -= 1
elif letter[i].isalpha():
if letter[i].isupper():
ret += (ord(letter[i]) - ord('A')) * (26**(i+start))
else:
ret += (ord(letter[i]) - ord('a')) * (26**(i+start))
else:
return ret * sign
return ret * sign
def numberToLetter(self, number):
if isinstance(number, str):
return number
ret = ''
sign = ''
if number == 0:
return 'A'
elif number < 0:
sign = '-'
number = -number
while number > 0:
ret += chr(ord('A') + number % 26)
number = int(number / 26)
return sign + ret
def parseCoords(self, args, usePlayerCoords = True, allowInventory = True):
"""Takes an argument list, and figures out what it's talking about.
Returns (thing, x, y). "Thing" can be None."""
coordStr = args[0]
if len(args) > 1:
coordStr += ' ' + args[1]
#print(coordStr)
x = -1
y = -1
match = GameBase.coordRegex.match(coordStr)
if match != None: # if coordinates were given
#print(match.group())
groups = match.groups()
ind = 0
for i in range(len(groups)):
if groups[i] == None or groups[i] == match.group():
continue
else:
ind = i
break
#print(groups[ind], groups[ind+1])
x = self.letterToNumber(groups[ind])
y = self.letterToNumber(groups[ind+1])
#print(x, y)
thing = self.level.getThingAtCoords(x, y)
if thing != None and usePlayerCoords:
return thing, thing.playerx, thing.playery
else:
return thing, x, y
else: # if a name was given
name = ' '.join(args)
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:
if name in self.playerInv:
thing = self.playerInv[name]
if thing != None:
return thing, -1, -1
else:
return None, -1, -1
#raise RuntimeError("'None' item named '{0}' in player inventory.".format(name))
else:
return None, -1, -1
#raise ValueError("{0} cannot be reached.".format(name))
else: # nothing
return None, -1, -1
#raise ValueError("{0} cannot be reached.".format(name))
return None, x, y
def justifyText(self, text, width = 80):
ret = []
text = text.lstrip().rstrip()
# start by making it all one long line.
text = _re.sub(r'\s{2,}', r' ', text)
# then add newlines as needed.
#i = 80
#i = get_terminal_size()[0]
i = width
while i < len(text):
while text[i] != ' ':
i -= 1
ret.append(text[:i])
text = text[i+1:]
ret.append(text)
return '\n'.join(ret)
def go(self, args):
"""go [-r] [to [the]] destination [additional arguments...]
Go to a location. "walk" and "move" are aliases of go.
-r: run to the location. "r" and "run" are aliases of -r.
The "run" command is an alias of "go -r".
"to" and "the" do nothing, and are intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "go to the balcony" rather than just "go balcony").
Destination can be a coordinate pair or object name. For instance, if one
wanted to go to D6, one could say "go to D6", "go to d 6", or "go to 3 6".
The letter is not case-sensitive."""
if self.level == None:
raise RuntimeError("Cannot move: No level has been loaded.")
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'to':
args.pop(0)
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args, allowInventory = False)
# Now we have a heading! Let's see if we can get there...
if (x, y) == (self.playerx, self.playery):
_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0)))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1:
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
space = path[pos]
if space == -1:
_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0)))
return
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
if space != -1:
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
else:
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed)))
break
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed)))
return
def look(self, args):
"""look [at [the]] object
Describe an object.
"at" and "the" do nothing, and are intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "look at the balcony" rather than just "look balcony").
Object can be the name of the object, or its coordinates."""
if len(args) == 0:
print(self.justifyText(self.level.description), file = self.outstream)
else:
if args[0] == 'at':
args.pop(0)
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args, usePlayerCoords = False)
if thing:
print(self.justifyText(str(thing)), file = self.outstream)
else:
print("There is nothing to see here.\n", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
def use(self, args):
"""use [-r] [the] object [on [the] object2]
Use an object. If the player is not already close to it, they will go to it.
use [-r] [the] item on [the] object
Use an item from the player's inventory on an object.
-r: run to the location. "r" and "run" are aliases of -r.
"the" does nothing, and is intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "use the lever" rather than just "use lever").
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."""
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'the':
args.pop(0)
if 'on' in args:
self.useOn(args, speed)
return
thing, x, y = self.parseCoords(args)
if thing == None:
print("There is nothing to use.", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
if thing.thingType != 'u' and thing.name not in self.playerInv:
print("The {0} cannot be used.".format(thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery) or thing.name in self.playerInv:
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.UseEvent(thing)))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
t += 1
#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 + 0.125, _ge.UseEvent(thing)))
return
def useOn(self, args, speed):
"""Called by use when there is an 'on' clause"""
onIndex = args.index('on')
item, x, y = self.parseCoords(args[:onIndex])
if args[onIndex+1] == 'the':
onIndex += 1
thing, x, y = self.parseCoords(args[onIndex+1:])
if item == None or item.name not in self.playerInv:
print("There is no {0} in the inventory.".format(item.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
if thing == None and x < 0 and y < 0:
print("Argument contains 'to' but with no real predicate.", file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery):
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.UseOnEvent(item, thing)))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1:
if thing != None:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
else:
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
t += 1
#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 + 0.125, _ge.UseOnEvent(item, thing)))
return
def take(self, args):
"""take [the] item
Take an item. 'get' is an alias of take.
If the player is not already close to it, they will go to it.
"the" does nothing, and is intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "take the flask" rather than just "take flask").
Object can be the name of the object, or its coordinates."""
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args)
if thing == None:
print("There is nothing to take.", file = self.outstream)
return
if thing.thingType != 'i':
print("The {0} cannot be taken.".format(thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery):
_hq.heappush(self.eventQueue, (self.gameTime + 0.125, _ge.TakeEvent(thing)))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
t += 1
#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 + 0.125, _ge.TakeEvent(thing)))
return
def drop(self, args):
"""drop [the] item"""
if args[0] == 'the':
args.pop(0)
if args[0] in self.playerInv:
_hq.heappush(self.eventQueue, (self.gameTime, _ge.DropEvent(self.playerInv[args[0]])))
else:
print('{0} do not have a {1}.'.format(self.playerName, args[0]), file = self.outstream)
def loadMap(self, args):
# loadMap (fileName)
# loadMap (fileName, entrance)
# loadMap (fileName, x, y)
# save persistent things in previous level
if self.level != None:
if self.level.name not in self.persist:
self.persist[self.level.name] = {}
for i in self.level.persistent:
self.persist[self.level.name][i] = self.level.getThingByName(i)
preLoaded = False
if args[0] in self.persist:
preLoaded = True
# load the new level
if len(args) == 2:
self.level = _gm.GameMap.read(args[0], int(args[1]), preLoaded)
else:
self.level = _gm.GameMap.read(args[0], preLoaded)
# get persistent things from it
if args[0] in self.persist:
persistedThings = tuple(self.persist[args[0]].keys())
for i in persistedThings:
self.level.addThing(self.persist[args[0]][i], True)
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.outstream.getvalue())
if len(args) <= 2:
self.playerx, self.playery = self.level.playerStart
else:
self.playerx, self.playery = int(args[1]), int(args[2])
self.prevx, self.prevy = self.playerx, self.playery
if self.onLevelLoad != None:
self.onLevelLoad()
def saveGame(self, args):
if len(args) < 1:
print("Save file must have a name!", file = self.outstream)
return
# choose pickle protocol depending on python version:
# 3 for Python 3.0.0 to 3.3.x, 4 for Python 3.4.0 to 3.7.x
prot = _pi.HIGHEST_PROTOCOL
# save persistent things so that the current level can be recalled as-is
if self.level != None:
if self.level.name not in self.persist:
self.persist[self.level.name] = {}
for i in self.level.persistent:
self.persist[self.level.name][i] = self.level.getThingByName(i)
# build data object to be saved
data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.gameTime)
# save it!
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
if args[0].endswith('.dat'): # This is really for absolute paths, but doesn't really check for that.
fileName = args[0]
with open(fileName, 'wb') as f:
_pi.dump(data, f, protocol=prot)
# delete things in the current map from the persist dict to prevent item duplication
persistedThings = tuple(self.persist[self.level.name].keys())
for i in persistedThings:
del self.persist[self.level.name][i]
# push a no-op event so that saving doesn't cost player characters time
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
def loadGame(self, args):
if len(args) < 1:
print("Save file must have a name!", file = self.outstream)
return
# choose pickle protocol depending on python version:
# 3 for Python 3.0.0 to 3.3.x, 4 for Python 3.4.0 to 3.7.x
prot = _pi.HIGHEST_PROTOCOL
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
if args[0].endswith('.dat'):
fileName = args[0]
x, y, levelname = 1, 1, 'testing/test1.txt'
with open(fileName, 'rb') as f:
self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime = _pi.load(f)
#print(levelname, x, y, file = self.outstream)
self.loadMap((levelname, x, y))
_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
def gameEventLoop(self):
while len(self.eventQueue) > 0:
ev = _hq.heappop(self.eventQueue)
self.gameTime = ev[0]
e = ev[1]
if self.__gameEvents[e.eventType](e):
break
if len(self.eventQueue) == 0:
self.gameTime = 0.0
_ge.resetEventNum()
# default event handlers
def handleNoOp(self, e):
return True
def handleGo(self, e):
if e.actor == self.playerName:
self.prevx, self.prevy = self.playerx, self.playery
self.playerx, self.playery = e.x, e.y
else:
self.level.moveThing(e.actor, e.x, e.y)
return False
def handleArrive(self, e):
if e.actor == self.playerName:
self.prevx, self.prevy = self.playerx, self.playery
self.playerx, self.playery = e.x, e.y
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(self.playerName, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
thing = self.level.getThingAtCoords(self.playerx, self.playery)
if thing:
if thing.thingType == 'x':
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
else:
actor = self.level.getThingByName(e.actor)
if actor:
self.level.moveThing(e.actor, e.x, e.y)
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
thing = self.level.getThingAtCoords(actor.x, actor.y)
if thing and thing != actor:
if thing.thingType == 'x':
print('{0} went {1}.'.format(actor.name, str(thing)))
self.level.removeThing(actor.name)
else:
print('There is nothing by the name of {0}.'.format(e.actor), file = self.outstream)
return False
def handleUse(self, e):
if e.thing.useFunc == '':
print('The {0} cannot be used by itself.'.format(e.thing.name), file = self.outstream)
return True
_hq.heappush(self.eventQueue, (self.gameTime + self.__useFuncs[e.thing.useFunc](e.thing), _ge.NoOpEvent()))
return False
def handleUseOn(self, e):
if e.item.useOnFunc == '':
print('The {0} cannot be used on other objects.'.format(e.item.name), file = self.outstream)
return True
_hq.heappush(self.eventQueue, (self.gameTime + self.__useFuncs[e.item.useOnFunc](e.item, e.thing), _ge.NoOpEvent()))
return False
def handleTake(self, e):
self.level.removeThing(e.item.name)
self.playerInv[e.item.name] = e.item
return True
def handleDrop(self, e):
e.item.x = self.playerx
e.item.y = self.playery
self.level.addThing(e.item, True)
del self.playerInv[e.item.name]
return True
# default useFuncs: take a list of arguments, return the time the use took
def examine(self, thing):
"""Just prints the given text."""
if 'pattern' not in thing.customValues or 'text' not in thing.customValues:
raise ValueError("Non-examinable thing {0} examined.".format(thing.name))
if thing.customValues['pattern'] == 'single':
print(self.justifyText(thing.customValues['text']), file = self.outstream)
elif thing.customValues['pattern'] == 'loop':
if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor']
print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream)
thing.customValues['cursor'] = (cursor + 1) % len(thing.customValues['text'])
elif thing.customValues['pattern'] == 'once':
if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor']
print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream)
if cursor < len(thing.customValues['text']) - 1:
thing.customValues['cursor'] += 1
elif thing.customValues['pattern'] == 'random':
cursor = _ra.randrange(len(thing.customValues['text']))
print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream)
thing.customValues['cursor'] = cursor
if 'set' in thing.customValues:
if 'cursor' in thing.customValues:
self.customVals[thing.customValues['set']] = thing.customValues[cursor]
else:
self.customVals[thing.customValues['set']] = 0
return 0.0
def container(self, thing):
"""Acts as a container. Items can be traded between the container and the player's inventory."""
items = thing.customValues['items']
thing.customValues['items'], timeOpen = self.onContainer(items)
return timeOpen
def key(self, item, thing):
"""Item is a key, which unlocks a door. This may be implemented for containers later."""
if isinstance(thing, tuple) or thing.thingType != 'd':
print("That is not a door.", file = self.outstream)
return 0.0
if thing.lock(item.name):
print("The key fits the lock.", file = self.outstream)
if not thing.passable and self.playerx == thing.x and self.playery == thing.y:
self.playerx, self.playery = self.prevx, self.prevy
else:
print("The key doesn't fit that lock.", file = self.outstream)
return 0.125
# stuff for extended classes to use
def registerUseFunc(self, name, func):
"""Registers a function for use by things in the map, but not directly
callable by the player."""
self.__useFuncs[name] = func
def registerEvent(self, name, func):
"""Registers a function to handle an event in the event loop.
They should take the event as an argument, and return True if it should
always give the player a turn, False otherwise."""
self.__gameEvents[name] = func

100
gameevents.py Normal file
View file

@ -0,0 +1,100 @@
#gameevents.py
# Experimental: events are ordered in the order that they were created,
# to resolve issues where two events happen at the same time.
_eventNum = 0
def resetEventNum():
global _eventNum
_eventNum = 0
class GameEvent(object):
def __init__(self, eventType: str):
global _eventNum
self.eventType = eventType
# Experimental, also NOT THREAD SAFE!
self.eventNum = _eventNum
_eventNum += 1
def __str__(self):
return "{0} ({1})".format(self.eventType, self.eventNum)
def __eq__(self, other):
if isinstance(other, GameEvent):
return self.eventNum == other.eventNum
else:
return self.eventNum == other
def __ne__(self, other):
if isinstance(other, GameEvent):
return self.eventNum != other.eventNum
else:
return self.eventNum != other
def __lt__(self, other):
if isinstance(other, GameEvent):
return self.eventNum < other.eventNum
else:
return self.eventNum < other
def __le__(self, other):
if isinstance(other, GameEvent):
return self.eventNum <= other.eventNum
else:
return self.eventNum <= other
def __gt__(self, other):
if isinstance(other, GameEvent):
return self.eventNum > other.eventNum
else:
return self.eventNum > other
def __ge__(self, other):
if isinstance(other, GameEvent):
return self.eventNum >= other.eventNum
else:
return self.eventNum >= other
class NoOpEvent(GameEvent):
def __init__(self):
super(NoOpEvent, self).__init__('noop')
class GoEvent(GameEvent):
def __init__(self, actor, x, y):
super(GoEvent, self).__init__('go')
self.actor = actor
self.x = x
self.y = y
class ArriveEvent(GameEvent):
def __init__(self, actor, x, y, t):
super(ArriveEvent, self).__init__('arrive')
self.actor = actor
self.x = x
self.y = y
self.t = t
class UseEvent(GameEvent):
def __init__(self, thing):
super(UseEvent, self).__init__('use')
self.thing = thing
class UseOnEvent(GameEvent):
def __init__(self, item, thing):
super(UseOnEvent, self).__init__('useon')
self.thing = thing # thing can be a coordinate pair?
self.item = item
class TakeEvent(GameEvent):
def __init__(self, item):
super(TakeEvent, self).__init__('take')
self.item = item
class DropEvent(GameEvent):
def __init__(self, item):
super(DropEvent, self).__init__('drop')
self.item = item

439
gamegui.py Normal file
View file

@ -0,0 +1,439 @@
# gamegui.py
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
from tile import Tile
from gamebase import GameBase
import io
class App(ttk.Frame):
def __init__(self, master, gameBase):
#print('Initializing...')
super(App, self).__init__(master)
self.gameBase = gameBase
self.gameBase.outstream = io.StringIO()
self.gameBase.requestInput = self.requestInput
self.gameBase.onLevelLoad = self.getGraphics
self.selected = (-1, -1)
self.parent = master
self.mapSize = (800, 450)
self.hasChanged = False
self.fileName = 'Untitled'
self.invTuple = tuple(self.gameBase.playerInv.keys())
self.invNames = StringVar(value=self.invTuple)
#self.project = Project()
self.tool = StringVar()
self.brush = StringVar()
self.history = []
self.redos = []
#self.selectedArea = (0, 0, self.project.x-1, self.project.y-1)
self.parent.clipboard_clear()
self.parent.clipboard_append('')
self.grid()
self.createWidgets()
self.makeTheDisplayWork()
self.bindKeys()
#print('Done.')
def createWidgets(self):
#print('Creating Widgets...')
self.initWidth, self.initHeight = 0, 0
# create menu bar
self.createMenuBar()
# create map
self.createMapView()
# create text out
self.createTextOut()
# create inventory list
self.createInventoryView()
#self.tool.set('setone')
#brushes = ttk.Combobox(self, textvariable = self.brush, values = tuple(sorted(Project.tileID.keys())))
#brushes.grid(column = 0, row = 6, sticky = (E, W))
#brushes.state(['readonly'])
#brushes.set('Dirt')
#ttk.Separator(self, orient = VERTICAL).grid(column = 1, row = 0, rowspan = 9, sticky = (N, S))
ttk.Sizegrip(self).grid(column = 1, row = 1, sticky = (S, E))
self.columnconfigure(0, weight = 1)
self.rowconfigure(0, weight = 1)
#print('Done.')
def createMenuBar(self):
#print('Creating the menubar...')
menubar = Menu(self.parent)
menuFile = Menu(menubar)
#menuEdit = Menu(menubar)
menuHelp = Menu(menubar)
menubar.add_cascade(menu = menuFile, label = 'File')
menuFile.add_command(label = 'New', command = lambda: self.loadLevel('maps/apartment.xml'), accelerator = 'Ctrl+N')
menuFile.add_command(label = 'Open...', command = self.fileOpen, accelerator = 'Ctrl+O')
menuFile.add_command(label = 'Save', command = self.fileSave, accelerator = 'Ctrl+S')
menuFile.add_command(label = 'Save As...', command = self.fileSaveAs)
menuFile.add_separator()
menuFile.add_command(label = 'Exit', command = self.onQuit, accelerator = 'Ctrl+Q')
#menubar.add_cascade(menu = menuEdit, label = 'Edit')
#menuEdit.add_command(label = 'Undo', command = self.undo, accelerator = 'Ctrl+Z')
#menuEdit.add_command(label = 'Redo', command = self.redo, accelerator = 'Ctrl+Y')
#menuEdit.add_separator()
#menuEdit.add_command(label = 'Cut', command = self.cut, accelerator = 'Ctrl+X')
#menuEdit.add_command(label = 'Copy', command = self.copy, accelerator = 'Ctrl+C')
#menuEdit.add_command(label = 'Paste', command = self.paste, accelerator = 'Ctrl+V')
menubar.add_cascade(menu = menuHelp, label = 'Help')
menuHelp.add_command(label = 'About', command = self.about)
menuHelp.add_separator()
menuHelp.add_command(label = 'Read Me', command = self.readme)
self.parent.configure(menu = menubar)
#print('Done.')
def createMapView(self):
mapFrame = ttk.LabelFrame(self, text = "map")
mapFrame.grid(column = 0, row = 0)
self.levelDisplay = Canvas(mapFrame, width = self.mapSize[0], height = self.mapSize[1], scrollregion = '0 0 2048 2048')
self.levelDisplay.grid(column = 0, row = 0)
vbar = ttk.Scrollbar(mapFrame, orient = VERTICAL, command = self.levelDisplay.yview)
self.levelDisplay.configure(yscrollcommand = vbar.set)
vbar.grid(column = 1, row = 0, sticky = (N, S))
hbar = ttk.Scrollbar(mapFrame, orient = HORIZONTAL, command = self.levelDisplay.xview)
self.levelDisplay.configure(xscrollcommand = hbar.set)
hbar.grid(column = 0, row = 1, sticky = (E, W))
self.bind('<Configure>', self.onResize)
def createTextOut(self):
textFrame = ttk.LabelFrame(self, text = "Info")
textFrame.grid(column = 0, row = 1)
self.infoDisplay = Text(textFrame, width = 80, height = 8, state = 'disabled')
self.infoDisplay.grid(column = 0, row = 0)
vbar = ttk.Scrollbar(textFrame, orient = VERTICAL, command = self.infoDisplay.yview)
self.infoDisplay.configure(yscrollcommand = vbar.set)
vbar.grid(column = 1, row = 0, sticky = (N, S))
self.infoCursor = 0
def createInventoryView(self):
invFrame = ttk.LabelFrame(self, text = "Inventory")
invFrame.grid(column = 1, row = 0)
self.invDisplay = Listbox(invFrame, height = 24, listvariable = self.invNames)
self.invDisplay.grid(column = 0, row = 0)
vbar = ttk.Scrollbar(invFrame, orient = VERTICAL, command = self.invDisplay.yview)
self.invDisplay.configure(yscrollcommand = vbar.set)
vbar.grid(column = 1, row = 0, sticky = (N, S))
def getGraphics(self):
self.tiles = {}
for i in range(len(self.gameBase.level.floorColors)):
self.tiles['e{0}'.format(i)] = Tile(self.gameBase.level.floorColors[i])
for i in range(len(self.gameBase.level.wallColors)):
self.tiles['w{0}'.format(i)] = Tile(self.gameBase.level.wallColors[i])
self.tiles['player'] = Tile('clear', 'blue', 'o')
for thing in self.gameBase.level.thingNames:
graphic = self.gameBase.level.thingNames[thing].graphic
self.tiles[thing] = Tile(graphic[0], graphic[1], graphic[2])
def makeTheDisplayWork(self):
#print('Making the display work...')
# level display
self.context = Menu(self.levelDisplay)
self.context.add_command(label = 'Load Map', command = lambda: self.loadLevel('maps/apartment.xml'))
self.context.add_command(label = 'Go', command = lambda: self.go(self.selected[0], self.selected[1]))
self.context.add_command(label = 'Run', command = lambda: self.go(self.selected[0], self.selected[1], True))
self.context.add_command(label = 'Look', command = lambda: self.look(self.selected[0], self.selected[1]))
self.context.add_command(label = 'Use', command = lambda: self.use(self.selected[0], self.selected[1]))
self.context.add_command(label = 'Take', command = lambda: self.take(self.selected[0], self.selected[1]))
#self.context.add_command(label = 'Cut', command = self.cut, accelerator = 'Ctrl+X')
#self.context.add_command(label = 'Goblin...')
#self.context.entryconfigure('Goblin...', command = self.configureGoblin, state = DISABLED)
#self.refreshDisplay()
self.levelDisplay.bind('<1>', self.handleClick)
self.levelDisplay.bind('<Double-1>', self.handleDoubleClick)
#self.levelDisplay.bind('<B1-Motion>', self.handleDragEvent)
#self.levelDisplay.bind('<ButtonRelease-1>', self.handleReleaseEvent)
self.levelDisplay.bind('<3>', self.handleRightClick)
# inventory list
self.invContext = Menu(self.invDisplay)
self.invContext.add_command(label = 'Look', command = lambda: self.look(self.invTuple[self.invDisplay.curselection()[0]]))
self.invContext.add_command(label = 'Use', command = lambda: self.use(self.invTuple[self.invDisplay.curselection()[0]]))
self.invContext.add_command(label = 'Drop', command = lambda: self.drop(self.invTuple[self.invDisplay.curselection()[0]]))
self.invDisplay.bind('<<ListboxSelect>>', lambda e: self.look(self.invTuple[self.invDisplay.curselection()[0]]))
self.invDisplay.bind('<3>', lambda e: self.invContext.post(e.x_root, e.y_root))
#print('Done.')
pass
def bindKeys(self):
self.parent.bind('<Control-n>', lambda e: self.fileNew())
self.parent.bind('<Control-o>', lambda e: self.fileOpen())
self.parent.bind('<Control-s>', lambda e: self.fileSave())
#self.parent.bind('<Control-z>', lambda e: self.undo())
#self.parent.bind('<Control-y>', lambda e: self.redo())
#self.parent.bind('<Control-x>', lambda e: self.cut())
#self.parent.bind('<Control-c>', lambda e: self.copy())
#self.parent.bind('<Control-v>', lambda e: self.paste())
self.parent.bind('<Control-q>', lambda e: self.onQuit())
def refreshDisplay(self):
#print('Refreshing the display...')
# refresh the map
self.levelDisplay.delete('all')
for y in range(self.gameBase.level.dimensions[1]):
for x in range(self.gameBase.level.dimensions[0]):
pos = self.gameBase.level.mapMatrix[y][x]
if pos[0] == 'w':
self.tiles['w{0}'.format(pos[1])].paint(self.levelDisplay, x, y)
else:
self.tiles['e{0}'.format(pos[1])].paint(self.levelDisplay, x, y)
for name in self.gameBase.level.thingNames:
thing = self.gameBase.level.getThingByName(name)
self.tiles[name].paint(self.levelDisplay, thing.x, thing.y)
self.tiles['player'].paint(self.levelDisplay, self.gameBase.playerx, self.gameBase.playery)
# refresh the info box
self.gameBase.outstream.seek(self.infoCursor)
#print(self.gameBase.outstream.tell())
#print(self.gameBase.outstream.read())
self.infoDisplay['state'] = 'normal'
self.infoDisplay.insert('end', self.gameBase.outstream.read())
self.infoDisplay.see('end -1 chars')
self.infoDisplay['state'] = 'disabled'
self.infoCursor = self.gameBase.outstream.tell()
#print(self.infoCursor)
#print('Done.')
def handleClick(self, event):
x = int(self.levelDisplay.canvasx(event.x) / 32)
y = int(self.levelDisplay.canvasy(event.y) / 32)
if (x, y) != self.selected:
self.selected = (x, y)
self.look(x, y)
def handleDoubleClick(self, event):
x = int(self.levelDisplay.canvasx(event.x) / 32)
y = int(self.levelDisplay.canvasy(event.y) / 32)
thing = self.gameBase.level.getThingAtCoords(x, y)
if thing != None and thing.useable:
self.use(x, y)
else:
self.go(x, y)
def handleRightClick(self, event):
x = int(self.levelDisplay.canvasx(event.x) / 32)
y = int(self.levelDisplay.canvasy(event.y) / 32)
self.selected = (x, y)
self.context.post(event.x_root, event.y_root)
#def openInvContext(self, event):
# self.invContext.post(event.x_root, event.y_root)
def handleDragEvent(self, event):
x = min(int(self.levelDisplay.canvasx(event.x) / 32), self.project.x-1)
y = min(int(self.levelDisplay.canvasy(event.y) / 32), self.project.y-1)
if self.tool.get() == 'select':
rect = self.levelDisplay.find_withtag('selectArea')
self.selectedArea = (min(x, self.firstX), min(y, self.firstY),
max(x, self.firstX), max(y, self.firstY))
self.levelDisplay.coords(rect,
(self.selectedArea[0]*32, self.selectedArea[1]*32,
self.selectedArea[2]*32+31, self.selectedArea[3]*32+31))
def handleReleaseEvent(self, event):
x = int(self.levelDisplay.canvasx(event.x) / 32)
y = int(self.levelDisplay.canvasy(event.y) / 32)
if self.tool.get() == 'select':
rect = self.levelDisplay.find_withtag('selectArea')
self.selectedArea = (min(x, self.firstX), min(y, self.firstY),
max(x, self.firstX), max(y, self.firstY))
self.levelDisplay.coords(rect,
(self.selectedArea[0]*32, self.selectedArea[1]*32,
self.selectedArea[2]*32+31, self.selectedArea[3]*32+31))
tiles = self.project.getTiles(self.selectedArea)
for y in range(len(tiles)):
for x in range(len(tiles[0])):
if type(tiles[y][x]) == Project.GoblinSettings:
self.context.entryconfigure('Goblin...', state = NORMAL)
break
break
else: pass
def loadLevel(self, fileName):
self.gameBase.loadMap([fileName])
#self.getGraphics()
self.refreshDisplay()
def go(self, x, y, running = False):
#print("go called")
#print(x, y)
if running:
self.gameBase.go(['-r', str(x), str(y)])
else:
self.gameBase.go([str(x), str(y)])
self.gameBase.gameEventLoop()
self.refreshDisplay() #inefficient, but will work for now.
def look(self, x, y = 1):
#print("look called")
#if x == self.gameBase.playerx and y == self.gameBase.playery:
# self.gameBase.look([
if isinstance(x, int):
thing = self.gameBase.level.getThingAtCoords(x, y)
if thing != None:
self.gameBase.look([thing.name])
else:
self.gameBase.look([])
else:
self.gameBase.look([x])
self.gameBase.gameEventLoop()
self.refreshDisplay() #inefficient, but will work for now.
def use(self, x, y = 1):
if isinstance(x, int):
self.gameBase.use([str(x), str(y)])
else:
self.gameBase.use([x])
self.gameBase.gameEventLoop()
self.refreshDisplay() #inefficient, but will work for now.
def take(self, x, y):
self.gameBase.take([str(x), str(y)])
self.gameBase.gameEventLoop()
self.refreshDisplay() #inefficient, but will work for now.
self.invTuple = tuple(self.gameBase.playerInv.keys())
self.invNames.set(self.invTuple)
def drop(self, item):
self.gameBase.drop([item])
self.gameBase.gameEventLoop()
self.refreshDisplay()
self.invTuple = tuple(self.gameBase.playerInv.keys())
self.invNames.set(self.invTuple)
def requestInput(self, prompt = ''):
answer = messagebox.askyesno(message=prompt, icon='question', title='Prompt')
if answer:
return 'y'
else:
return 'n'
def fileNew(self):
#print('Creating a new project...')
if self.askToSave():
self.newDialog = Toplevel(self.parent)
self.newDialog.title('New Project')
newFrame = Frame(self.newDialog)
newFrame.grid()
self.hei = StringVar()
self.wid = StringVar()
ttk.Label(newFrame, text = 'Width:').grid(column = 0, row = 0, sticky = W)
ttk.Label(newFrame, text = 'Height:').grid(column = 0, row = 1, sticky = W)
Spinbox(newFrame, from_ = 16, to = 255, increment = 1, textvariable = self.wid
).grid(column = 1, row = 0, sticky = W)
Spinbox(newFrame, from_ = 16, to = 255, increment = 1, textvariable = self.hei
).grid(column = 1, row = 1, sticky = W)
ttk.Button(newFrame, text = 'Create', command = self.__confirmNewDimensions,
default = 'active').grid(column = 0, row = 2)
ttk.Button(newFrame, text = 'Cancel', command = self.newDialog.destroy
).grid(column = 1, row = 2)
#print('Done.')
def fileOpen(self):
#print('Opening a project...')
if self.askToSave():
newName = filedialog.askopenfilename(defaultextension = '.dat', initialdir = 'saves')
if newName != '':
self.fileName = newName
self.gameBase.loadGame((self.fileName,))
self.gameBase.gameEventLoop()
self.refreshDisplay()
self.invTuple = tuple(self.gameBase.playerInv.keys())
self.invNames.set(self.invTuple)
#print('Done.')
def fileSave(self):
#print('Saving a project...')
newName = self.fileName
if self.fileName == 'Untitled':
self.fileSaveAs()
elif newName != '':
self.fileName = newName
self.gameBase.saveGame((self.fileName,))
self.hasChanged = False
#print('Done.')
def fileSaveAs(self):
newName = filedialog.asksaveasfilename(defaultextension = '.dat', initialdir = 'saves')
if newName != '':
self.fileName = newName
self.gameBase.saveGame((self.fileName,))
self.hasChanged = False
def onQuit(self):
if self.askToSave():
exit()
def askToSave(self):
if self.hasChanged:
insecure = messagebox.askyesnocancel(
message = 'Do you want to save ' + self.fileName + ' before continuing?',
icon = 'warning', title = 'New File')
print(type(insecure))
if insecure == None: return False
elif insecure == True:
self.fileSave()
return True
else: return True
else: return True
def about(self):
self.newDialog = Toplevel(self.parent)
self.newDialog.title('About')
newFrame = Frame(self.newDialog)
newFrame.grid()
ttk.Label(newFrame, text = 'I Am Gnome Level Editor v.0.9.0013').grid()
ttk.Button(newFrame, text = 'Okay', command = self.newDialog.destroy).grid()
def readme(self):
self.newDialog = Toplevel(self.parent)
self.newDialog.title('About')
newFrame = Frame(self.newDialog)
newFrame.grid()
text = Text(newFrame, width=80, height=40, wrap = 'word')
text.grid(column = 0, row = 0)
sbar = ttk.Scrollbar(newFrame, orient = VERTICAL, command = text.yview)
sbar.grid(column = 1, row = 0, sticky = (N, S, W))
text.configure(yscrollcommand = sbar.set)
text.state(['disabled'])
file = open('iag_readme.txt', 'r')
text.insert('1.0', file.read())
def onResize(self, event):
if self.initWidth == 0 and self.initHeight == 0:
self.initWidth = event.width
self.initHeight = event.height
else:
wDelta = event.width - int(self.initWidth)
hDelta = event.height - int(self.initHeight)
self.levelDisplay.configure(width = self.mapSize[0] + wDelta, height = self.mapSize[1] + hDelta)
# main
if __name__ == '__main__':
root = Tk()
root.title('Game Gui (debug)')
#root.geometry("1024x768")
#root.resizable(FALSE, FALSE)
root.option_add('*tearOff', FALSE)
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)
newApp = App(root, GameBase())
newApp.grid(sticky = (N, S, E, W))
root.mainloop()

688
gamemap.py Normal file
View file

@ -0,0 +1,688 @@
#gamemap.py
import re
import heapq
import xml.etree.ElementTree as ET
class Thing(object):
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None):
self.thingType = thingType
self.name = name
self.description = description
self.x = x
self.y = y
self.playerx = x
self.playery = y
if playerx:
self.playerx = playerx
if playery:
self.playery = playery
self.passable = bool(flags & 1)
self.talkable = bool(flags & 2)
self.lookable = bool(flags & 4)
self.takeable = bool(flags & 8)
self.useable = bool(flags & 16)
self.graphic = ('clear', '#7F7F7F', ' ')
def __str__(self):
"""__str__ is used for look."""
return self.description
def __eq__(self, other):
if not isinstance(other, Thing):
return False
return self.name == other.name
class Item(Thing):
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = ('clear', '#00BF00', '^')):
super(Item, self).__init__('i', name, x, y, description, 13)
self.useFunc = useFunc
self.useOnFunc = useOnFunc
self.customValues = customValues
self.ranged = ranged
self.graphic = graphic
def use(self):
pass
class Useable(Thing):
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = ('clear', '#0000FF', '#')):
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
self.useFunc = useFunc
self.customValues = customValues
self.graphic = graphic
def use(self):
pass
class NPC(Thing):
def __init__(self, name, x: int, y: int, description: str, friendly = True, following = False, graphic = ('clear', '#000000', 'o')):
super(NPC, self).__init__('c', name, x, y, description, 6)
self.following = following
self.friendly = friendly
self.inventory = []
self.graphic = graphic
def give(self, item):
self.inventory.append(item)
def drop(self, index):
return self.inventory.pop(index)
def dialog(self):
pass
class Door(Thing):
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = ('clear', '#7F3F00', '#')):
self.descBase = description
if description == None:
if locked:
description = "The {0} is locked.".format(name)
else:
description = "The {0} is unlocked.".format(name)
else:
if locked:
description += " It is locked.".format(name)
else:
description += " It is unlocked.".format(name)
super(Door, self).__init__('d', name, x, y, description, 1)
self.passable = not locked
self.key = key
self.graphic = graphic
def lock(self, key = None):
if key == self.key:
self.passable = not self.passable
if self.descBase == None:
if self.passable:
self.description = "The {0} is unlocked.".format(self.name)
else:
self.description = "The {0} is locked.".format(self.name)
else:
if self.passable:
self.description += " It is unlocked.".format(self.name)
else:
self.description += " It is locked.".format(self.name)
return True
return False
class MapExit(Thing):
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix: None, graphic = ('clear', '#FF0000', 'x')):
description = name
if prefix:
description = "{0} {1}".format(prefix, name)
super(MapExit, self).__init__('x', name, x, y, description, 5)
self.exitid = exitid
self.destination = destination
self.prefix = prefix
self.graphic = graphic
class GameMap(object):
# Matrix tile codes:
# e: empty (0)
# d: door
# x: map change
# w: wall (0)
# c: NPC (should this be a subclass of interactable?)
# i: item
# u: useable
# p: player start (up to one per level)
# regular expressions
matrixRegex = re.compile(r'([ \t]*([a-z][0-9]*)+(\n))+')
def __init__(self, name, graph, matrix, dimensions):
self.name = name
self.openingText = ""
self.mapGraph = graph
self.mapMatrix = matrix
self.dimensions = dimensions
self.thingPos = {} # int location : list of names
self.thingNames = {} # Things can be looked up by name.
self.playerStart = (1, 1)
self.description = "The area is completely blank."
self.floorColors = []
self.wallColors = []
self.persistent = []
@staticmethod
def read(infile = None, prevMap = None, preLoaded = False):
"""Read map data and return a Map object. If infile is not provided, then
it will read from stdin. Otherwise, it should be a valid file name.
Entering a map through stdin will be obsolete once testing is over."""
data = None
tryToRead = True
if infile != None:
try:
with open(infile, 'r') as f:
data = f.read()
except OSError as e:
print("The file could not be read. Falling back to stdin...")
else:
while tryToRead:
try:
data += input()
except EOFError as e:
tryToRead = False
# Now what we do with the data
info = ET.fromstring(data)
mat = None
layout = info.find('layout')
if layout != None:
#print(layout.text)
match = GameMap.matrixRegex.match(layout.text.lstrip())
if match == None:
raise RuntimeError('Map read a file without a map matrix.')
mat = match.group()
else:
raise RuntimeError('Map read a file without a map matrix.')
# generate matrix and graph first
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
playerStart = (-1, -1)
level = GameMap(infile, mapGraph, mapMatrix, dimensions)
# Now, load other info
GameMap.loadThings(level, info, prevMap, preLoaded)
return level
@staticmethod
def parseMatrix(matrixStr):
"""Returns a map graph as an adjacency list, as well as the matrix as a
list of lists of tuples."""
# Make the matrix first
mat = [[]]
x = 0
y = 0
i = 0
l = 0
while i < len(matrixStr):
if matrixStr[i].isalpha():
j = i+1
while j < len(matrixStr) and matrixStr[j].isdecimal():
j += 1
if j == i+1: # no number
mat[l].append((matrixStr[i], 0))
else:
mat[l].append((matrixStr[i], int(matrixStr[i+1:j])))
i = j
elif matrixStr[i] == '\n':
if x == 0:
x = len(mat[l])
elif x != len(mat[l]):
raise RuntimeError('Map matrix has jagged edges.')
l += 1
i += 1
mat.append([])
else: # assume it was a whitespace character.
i += 1
y = len(mat) - 1
# Now for the graph
numTiles = x * y
dim = (x, y)
graph = [[] for j in range(numTiles)]
passable = ('e', 'd', 'x', 'p', 'i')
for j in range(y):
for i in range(x):
if mat[j][i][0] in passable:
here = GameMap.__coordsToInt(i, j, x)
if i > 0 and mat[j][i-1][0] in passable:
there = GameMap.__coordsToInt(i-1, j, x)
graph[here].append(there)
graph[there].append(here)
if j > 0 and mat[j-1][i][0] in passable:
there = GameMap.__coordsToInt(i, j-1, x)
graph[here].append(there)
graph[there].append(here)
return mat, graph, dim
@staticmethod
def loadThings(level, info, prevMap = None, preLoaded = False):
"""load the things from the xml part of the map file."""
if 'openingText' in info.attrib:
level.openingText = info.attrib['openingText']
if 'playerStart' in info.attrib:
ps = info.attrib['playerStart'].split(',')
level.playerStart = (int(ps[0]), int(ps[1]))
if info.text != None:
level.description = info.text
# get map colors
floorColors = info.find('floorColors')
if floorColors != None:
level.floorColors = floorColors.text.lstrip().split()
if len(level.floorColors) == 0:
level.floorColors.append('#9F7F5F')
wallColors = info.find('wallColors')
if wallColors != None:
level.wallColors = wallColors.text.lstrip().split()
if len(level.wallColors) == 0:
level.wallColors.append('#7F3F0F')
# get things
for node in info:
if node.tag == 'loadOnce':
# Things in the load-once section are only loaded the first
# time that the map is loaded, and saved in the player's
# save file.
if preLoaded:
continue
for node1 in node:
if node1.tag == 'door':
level.addThing(GameMap.__loadDoor(node1), True)
elif node1.tag == 'useable':
level.addThing(GameMap.__loadUseable(node1), True)
elif node1.tag == 'item':
level.addThing(GameMap.__loadItem(node1), True)
elif node.tag == 'exit':
level.addThing(GameMap.__loadExit(level, node, prevMap))
elif node.tag == 'door':
level.addThing(GameMap.__loadDoor(node))
elif node.tag == 'useable':
level.addThing(GameMap.__loadUseable(node))
@staticmethod
def __loadExit(level, node, prevMap):
exitid = 0
x, y = 0, 0
destination = ''
name = ''
prefix = None
if 'name' in node.attrib:
name = node.attrib['name']
else:
print("Nameless exit, ommitting.")
return None
if 'id' in node.attrib:
exitid = int(node.attrib['id'])
else:
print("Exit '{0}' with no id, ommitting.".format(name))
return None
if 'location' in node.attrib:
loc = node.attrib['location'].split(',')
x, y = int(loc[0]), int(loc[1])
else:
print("Exit '{0}' without a location, ommitting.".format(name))
return None
if 'destination' in node.attrib:
destination = node.attrib['destination']
else:
print("Exit '{0}' with no destination, ommitting.".format(name))
return None
if 'prefix' in node.attrib:
prefix = node.attrib['prefix']
graphic = GameMap.__loadGraphic(node)
#level.addThing(MapExit(name, x, y, exitid, destination, prefix))
if prevMap == exitid:
level.playerStart = (x, y)
return MapExit(name, x, y, exitid, destination, prefix, graphic)
@staticmethod
def __loadDoor(node):
x, y = 0, 0
name = ''
locked = False
doorOpen = False
key = None
if 'name' in node.attrib:
name = node.attrib['name']
else:
print("Nameless door, ommitting.")
return None
if 'location' in node.attrib:
pos = node.attrib['location'].split(',')
x, y = int(pos[0]), int(pos[1])
else:
print("Door '{0}' without a location, ommitting.".format(name))
return None
if 'locked' in node.attrib:
if node.attrib['locked'] == 'True':
locked = True
if 'open' in node.attrib:
if node.attrib['open'] == 'True':
doorOpen = True
if 'key' in node.attrib:
key = node.attrib['key']
graphic = GameMap.__loadGraphic(node)
description = node.text
return Door(name, x, y, locked, description, key, graphic)
@staticmethod
def __loadUseable(node):
x, y = 0, 0
playerx, playery = 0, 0
name = ''
useFunc = ''
description = ''
if 'name' in node.attrib:
name = node.attrib['name']
else:
print("Nameless useable, ommitting.")
return None
if 'location' in node.attrib:
pos = node.attrib['location'].split(',')
x, y = int(pos[0]), int(pos[1])
else:
print("Useable '{0}' without a location, ommitting.".format(name))
return None
if 'useLocation' in node.attrib:
loc = node.attrib['useLocation'].split(',')
playerx, playery = int(loc[0]), int(loc[1])
else:
playerx, playery = x, y
if 'useFunc' in node.attrib:
useFunc = node.attrib['useFunc']
else:
print("Unuseable useable '{0}', ommitting.".format(name))
return None
graphic = GameMap.__loadGraphic(node)
description = node.text
# get custom values
customVals = {}
for val in node:
if val.tag == 'graphic':
continue # skip it
value = None
valName = ''
if 'name' in val.attrib:
valName = val.attrib['name']
else:
print("Nameless custom value in {0}, ommitting.".format(name))
continue
if 'value' in val.attrib:
if val.tag == 'int':
value = int(val.attrib['value'])
elif val.tag == 'float':
value = float(val.attrib['value'])
elif val.tag == 'str':
value = str(val.attrib['value'])
elif val.tag == 'item':
value = GameMap.__loadItem(val)
elif val.tag == 'intList':
value = [int(i) for i in val.attrib['value'].split(',')]
elif val.tag == 'floatList':
value = [float(i) for i in val.attrib['value'].split(',')]
elif val.tag == 'strList':
value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas.
else:
print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag))
continue
elif val.tag == 'list':
value = []
for i in val:
if 'value' in i.attrib:
if i.tag == 'int':
value.append(int(i.attrib['value']))
elif i.tag == 'float':
value.append(float(i.attrib['value']))
elif i.tag == 'str':
value.append(str(i.attrib['value']))
elif i.tag == 'item':
value.append(GameMap.__loadItem(i))
else:
print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag))
continue
else:
print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name))
continue
else:
print("Value {0} in {1} has no value, ommitting.".format(valName, name))
continue
customVals[valName] = value
return Useable(name, x, y, description, useFunc, customVals, playerx, playery, graphic)
@staticmethod
def __loadItem(node):
x, y = 0, 0
playerx, playery = 0, 0
name = ''
useFunc = ''
useOnFunc = ''
description = ''
ranged = False
if 'name' in node.attrib:
name = node.attrib['name']
else:
print("Nameless item, ommitting.")
return None
if 'location' in node.attrib:
pos = node.attrib['location'].split(',')
x, y = int(pos[0]), int(pos[1])
else:
print("Item '{0}' without a location, ommitting.".format(name))
return None
if 'useFunc' in node.attrib:
useFunc = node.attrib['useFunc']
if 'useOnFunc' in node.attrib:
useOnFunc = node.attrib['useOnFunc']
if 'ranged' in node.attrib:
ranged = (node.attrib['ranged'].casefold() == 'true')
graphic = GameMap.__loadGraphic(node)
description = node.text
# get custom values
customVals = {}
for val in node:
if val.tag == 'graphic':
continue # skip it
value = None
valName = ''
if 'name' in val.attrib:
valName = val.attrib['name']
else:
print("Nameless custom value in {0}, ommitting.".format(name))
continue
if 'value' in val.attrib:
if val.tag == 'int':
value = int(val.attrib['value'])
elif val.tag == 'float':
value = float(val.attrib['value'])
elif val.tag == 'str':
value = str(val.attrib['value'])
elif val.tag == 'item':
value = GameMap.__loadItem(val)
elif val.tag == 'intList':
value = [int(i) for i in val.attrib['value'].split(',')]
elif val.tag == 'floatList':
value = [float(i) for i in val.attrib['value'].split(',')]
elif val.tag == 'strList':
value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas.
else:
print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag))
continue
elif val.tag == 'list':
value = []
for i in val:
if 'value' in i.attrib:
if i.tag == 'int':
value.append(int(i.attrib['value']))
elif i.tag == 'float':
value.append(float(i.attrib['value']))
elif i.tag == 'str':
value.append(str(i.attrib['value']))
elif i.tag == 'item':
value.append(GameMap.__loadItem(i))
else:
print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag))
continue
else:
print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name))
continue
else:
print("Value {0} in {1} has no value, ommitting.".format(valName, name))
continue
customVals[valName] = value
return Item(name, x, y, description, useFunc, useOnFunc, customVals, ranged, graphic)
@staticmethod
def __loadGraphic(node):
bgc = 'clear'
fgc = '#7F7F7F'
shape = '#'
graphicInDecl = False
if 'bgc' in node.attrib:
bgc = node.attrib['bgc']
graphicInDecl = True
if 'fgc' in node.attrib:
fgc = node.attrib['fgc']
graphicInDecl = True
if 'shape' in node.attrib:
shape = node.attrib['shape']
graphicInDecl = True
if not graphicInDecl:
#check for graphic child node
graphic = node.find('graphic')
if graphic != None:
if 'bgc' in graphic.attrib:
bgc = graphic.attrib['bgc']
if 'fgc' in graphic.attrib:
fgc = graphic.attrib['fgc']
if 'shape' in graphic.attrib:
shape = graphic.attrib['shape']
return (bgc, fgc, shape)
# stuff the gameshell itself might use
def addThing(self, thing, persist = False):
if thing == None:
return
if thing.name in self.thingNames:
raise ValueError("Cannot have two objects named {0}.".format(thing.name))
pos = self.coordsToInt(thing.x, thing.y)
if pos not in self.thingPos:
self.thingPos[pos] = [thing.name]
else:
self.thingPos[pos].append(thing.name)
self.thingNames[thing.name] = thing
if persist:
self.persistent.append(thing.name)
def removeThing(self, name):
thing = self.getThingByName(name)
if thing:
oldPos = self.coordsToInt(thing.x, thing.y)
if oldPos in self.thingPos:
self.thingPos[oldPos].remove(name)
if len(self.thingPos[oldPos]) == 0:
del self.thingPos[oldPos]
del self.thingNames[name]
def path(self, x1, y1, x2, y2, closeEnough = True):
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
The closeEnough parameter will create a path that lands beside the source if necessary."""
# first test to see that the start point is passable
startThing = self.getThingAtCoords(x1, y1)
if not closeEnough:
if startThing and not startThing.passable:
return -1, [] # meaning you can't get there
startPoint = self.coordsToInt(x1, y1)
endPoint = self.coordsToInt(x2, y2)
numVertex = self.dimensions[0] * self.dimensions[1]
dist = [numVertex + 1 for i in range(numVertex)]
prev = [-1 for i in range(numVertex)]
dist[startPoint] = 0
if closeEnough:
if startThing and not startThing.passable:
dist[startPoint] = -1
queue = []
heapq.heappush(queue, (dist[startPoint], startPoint))
while len(queue) > 0:
u = heapq.heappop(queue)[1]
for v in self.mapGraph[u]:
thing = self.getThingAtPos(v)
if thing and not thing.passable:
continue
tempDist = dist[u] + 1
if tempDist < dist[v]:
dist[v] = tempDist
if dist[u] != -1:
prev[v] = u
heapq.heappush(queue, (dist[v], v))
if dist[endPoint] < numVertex + 1:
return dist[endPoint], prev
else:
return -1, [] # meaning you can't get there
def lineOfSight(self, x1, y1, x2, y2):
pass
@staticmethod
def __coordsToInt(x, y, width):
return x + y * width
def coordsToInt(self, x, y, width = -1):
if width < 0:
return x + y * self.dimensions[0]
else:
return x + y * width
def intToCoords(self, pos):
return pos % self.dimensions[0], int(pos / self.dimensions[0])
def getThingAtCoords(self, x, y):
return self.getThingAtPos(self.coordsToInt(x, y))
def getThingsAtCoords(self, x, y):
return self.getThingsAtPos(self.coordsToInt(x, y))
def getThingAtPos(self, pos):
if pos in self.thingPos:
return self.thingNames[self.thingPos[pos][0]]
else:
return None
def getThingsAtPos(self, pos):
if pos in self.thingPos:
ret = []
for i in self.thingPos[pos]:
ret.append(self.thingNames[i])
return ret
else:
return None
def getThingByName(self, name):
if name in self.thingNames:
return self.thingNames[name]
else:
return None
def moveThing(self, name, x, y = -1):
newPos = x
if y != -1:
newPos = self.coordsToInt(x, y)
else:
x, y = self.intToCoords(x)
thing = self.getThingByName(name)
if thing:
oldPos = self.coordsToInt(thing.x, thing.y)
if oldPos in self.thingPos:
self.thingPos[oldPos].remove(name)
if len(self.thingPos[oldPos]) == 0:
del self.thingPos[oldPos]
if newPos not in self.thingPos:
self.thingPos[newPos] = [name]
else:
self.thingPos[newPos].append(name)
thing.x, thing.y = x, y
else:
raise RuntimeError("There is nothing by the name of {0}.".format(name))

151
gameshell.py Normal file
View file

@ -0,0 +1,151 @@
# gameshell.py
from shell import Shell
from gamebase import GameBase
import sys as _sys
#import re
import heapq
#import gamemap
import gameevents
#from os import get_terminal_size
#import random
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
# 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('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.registerAlias('run', ['go', '-r'])
# Helper functions
def man(self, args):
super(GameShell, self).man(args)
heapq.heappush(self.gameBase.eventQueue, (self.gameBase.gameTime, gameevents.NoOpEvent()))
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 = {}
doors = {}
useables = {}
items = {}
for y in range(self.gameBase.level.dimensions[1]):
rows.append(['{0:2} '.format(y)])
for x in range(self.gameBase.level.dimensions[0]):
pos = self.gameBase.level.mapMatrix[y][x]
thing = self.gameBase.level.getThingAtPos(index)
if x == self.gameBase.playerx and y == self.gameBase.playery:
rows[-1].append('()')
elif thing:
if thing.thingType == 'x': # exit
rows[-1].append('X{0}'.format(thing.exitid))
exits[thing.exitid] = thing.name
elif thing.thingType == 'd': # door
doors[len(doors)+1] = thing.name
rows[-1].append('D{0}'.format(len(doors)))
elif thing.thingType == 'u': # useable
useables[len(useables)+1] = thing.name
rows[-1].append('U{0}'.format(len(useables)))
elif thing.thingType == 'i': # item
items[len(items)+1] = thing.name
rows[-1].append('I{0}'.format(len(items)))
elif pos[0] == 'w':
sides = 0
if y > 0 and self.gameBase.level.mapMatrix[y-1][x][0] == 'w':
sides += GameShell.UP
if x < self.gameBase.level.dimensions[0]-1 and self.gameBase.level.mapMatrix[y][x+1][0] == 'w':
sides += GameShell.RIGHT
if y < self.gameBase.level.dimensions[1]-1 and self.gameBase.level.mapMatrix[y+1][x][0] == 'w':
sides += GameShell.DOWN
if x > 0 and self.gameBase.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('\n'.join(rows))
if len(args) > 0:
if args[0] == '-l' or args[0] == 'l' or args[0] == 'legend':
legend = ["\n---Legend---\n", "() - {0}".format(self.gameBase.playerName),
"Xn - Exit to another area"]
for i in exits:
legend.append(' X{0} - {1}'.format(i, exits[i]))
legend.append("Un - Useable object")
for i in useables:
legend.append(' U{0} - {1}'.format(i, useables[i]))
legend.append("In - Item")
for i in items:
legend.append(' I{0} - {1}'.format(i, items[i]))
legend.append("Dn - Door")
for i in doors:
legend.append(' D{0} - {1}'.format(i, doors[i]))
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])))
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)))
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.playerInv]))
def update(self):
self.gameBase.gameEventLoop()
if __name__ == '__main__':
sh = GameShell(GameBase())
sh.run()

106
shell.py Normal file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# gameshell.py
#
# Copyright 2018 chees <chees@DESKTOP-0CA7MCF>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# Ver. 0.1.0017
import types as _types
import traceback as _tb
class Shell(object):
def __init__(self):
self.__commands = {'exit': self.exitShell}
self.__aliases = {}
self.ps1 = '> '
self.ps2 = '> '
self.__exit = False
def run(self):
"""The main game/shell loop"""
while not self.__exit:
print(self.ps1, end = '')
command = self.scanInput()
# we have to handle shell built-ins first (when we get some)
if len(command) == 0:
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()
def man(self, args):
help(self.__commands[args[0]])
def registerCommand(self, commandName: str, command: _types.FunctionType):
"""command must be a function that takes one argument: a list of strings,
conventionally called args or argv"""
self.__commands[commandName] = command
def registerAlias(self, a: str, original: list):
"""makes 'a' an alias for original.
'a' must be one token, but original can be multiple."""
self.__aliases[a] = original
def getAlias(self, a: str):
if a in self.__aliases:
return self.__aliases[a]
else:
return None
def exitShell(self, args):
"""The default exit command."""
self.__exit = True
# 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."""
ret = input()
if ret == '':
return []
ret = ret.split()
a = self.getAlias(ret[0])
if a:
ret = a[:] + ret[1:]
return ret
def handleUnknownCommand(self, command):
"""Handle commands that aren't registered. Override this if you want to do
something with those commands."""
print("Bad command.")
def update(self):
"""Runs at the end of each loop. Does nothing by default. Override this if
there is something you want the shell to do between every command."""
pass
def main(args):
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))

33
testing/test1.txt Normal file
View file

@ -0,0 +1,33 @@
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0
w0w0w0w0e0e0e0e0e0e0e0e0e0w0e0w0e0w0e0w0e0w0e0e0e0e0e0e0w0w0
w0w0w0w0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0w0
w0w0e0e0e0e0w0w0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0w0w0w0e0e0w0w0
w0w0w0e0e0e0w0w0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0w0w0
w0e0e0e0w0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0w0w0
w0e0e0e0w0e0e0e0e0w0e0e0e0w0w0w0e0e0w0w0w0w0w0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0w0w0w0w0e0w0e0e0e0e0e0e0w0w0w0e0w0w0e0e0w0w0
w0w0w0w0w0w0w0w0w0w0e0e0e0w0e0e0e0e0e0e0w0w0w0e0w0w0e0e0w0w0
w0e0e0w0e0x1e0e0w0e0e0e0e0w0e0e0w0e0w0e0e0w0w0e0e0e0e0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0w0e0w0e0e0w0w0e0e0e0e0w0w0w0
w0e0e0w0e0e0e0e0w0e0e0e0e0w0e0e0e0e0e0e0e0e0w0w0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0w0w0w0w0w0w0w0w0w0w0w0w0e0e0w0w0w0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0w0w0w0w0e0e0e0e0e0e0w0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0w0e0e0e0e0e0e0e0e0w0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0w0e0e0w0w0e0e0w0w0w0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0w0w0e0e0w0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0w0w0w0w0w0w0e0e0w0w0e0e0e0e0w0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0w0w0w0w0w0w0e0x2e0e0w0e0e0e0e0w0
w0e0e0w0e0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0w0w0e0e0w0w0
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
<map openingText="Floor 1 map loaded successfully. Normally, this would describe the environment." playerStart="5, 26">
<exit id="1" location="5, 10" destination="testing/test2.txt" name="upstairs"/>
<exit id="2" location="21, 23" destination="testing/test4.txt" name="downstairs"/>
</map>

35
testing/test2.txt Normal file
View file

@ -0,0 +1,35 @@
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0x3e0e0w0e0e0e0e0e0w0e0e0e0e0e0e0w0e0w0w0w0w0w0w0
w0w0w0w0e0e0e0e0e0w0e0e0e0e0e0w0e0e0w0w0e0e0w0e0e0e0w0w0w0w0
w0w0w0w0e0e0e0e0e0w0e0e0e0e0e0w0e0e0w0w0e0e0e0e0e0e0e0e0w0w0
w0w0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0w0w0e0e0e0e0w0
w0w0e0e0e0e0e0e0e0e0e0e0e0e0e0w0w0w0e0e0w0w0w0w0w0e0e0e0e0w0
w0e0e0e0w0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0w0w0w0w0w0e0w0
w0e0e0e0w0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0w0w0w0w0w0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0w0w0w0w0e0e0w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0w0w0w0w0e0e0e0w0w0w0e0e0e0e0e0e0w0w0e0e0w0w0w0w0
w0e0e0e0e0x1e0x2e0e0e0e0e0e0w0w0e0e0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0w0e0e0e0e0e0e0e0e0e0e0w0w0w0e0e0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0w0w0e0e0e0e0e0w0w0w0w0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0w0w0e0e0w0w0w0w0w0w0w0w0w0e0e0w0e0e0e0w0w0w0w0
w0w0w0w0w0e0w0w0w0e0e0w0w0w0w0w0w0w0e0e0e0e0w0w0e0x4e0e0e0w0
w0w0w0w0e0e0w0w0w0e0e0w0w0w0w0w0w0w0e0e0e0e0w0w0e0e0e0e0e0w0
w0e0e0e0e0w0w0w0w0w0e0w0e0e0w0e0e0e0w0w0w0w0w0w0w0w0w0w0w0w0
w0e0e0e0e0w0w0w0w0w0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0w0w0w0w0w0w0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0w0w0w0w0w0e0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0e0e0e0e0e0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0e0e0e0e0e0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0w0w0w0w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0w0w0w0w0e0e0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0e0e0e0e0e0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0e0e0e0e0e0w0e0e0e0w0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0e0e0e0e0e0e0w0e0e0e0w0w0w0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
w0w0w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
<map openingText="Floor 2" playerStart="6, 10">
<exit id="1" location="5, 10" destination="testing/test1.txt" name="downstairs"/>
<exit id="2" location="7, 10" destination="testing/test3.txt" name="upstairs"/>
<exit id="3" location="6, 1" destination="testing/test3.txt" name="north"/>
<exit id="4" location="25, 14" destination="testing/test3.txt" name="east"/>
</map>

24
testing/test3.txt Normal file
View file

@ -0,0 +1,24 @@
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0e0e0e0e0e0x3e0e0e0w0w0w0w0e0w0w0w0w0w0w0w0w0e0w0w0w0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0w0w0e0w0e0e0e0e0e0e0w0e0e0e0w0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0w0w0e0w0e0e0e0e0e0e0w0e0e0e0w0w0w0w0
w0w0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0w0e0e0e0e0e0e0e0w0w0w0
w0w0w0e0e0e0e0e0e0w0w0e0e0e0e0e0e0e0w0w0e0e0e0e0e0e0e0w0w0w0
w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0w0e0e0e0e0e0w0w0e0e0e0e0e0e0w0
w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0w0e0e0e0e0e0w0w0e0e0e0e0e0e0w0
w0w0w0w0w0e0e0e0w0w0w0w0e0e0e0w0e0e0w0w0w0w0w0w0e0e0e0e0e0w0
w0w0w0w0w0e0e0e0e0e0w0w0w0e0e0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0e0w0w0e0e0e0x2e0e0w0w0e0e0e0e0w0w0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0w0w0e0e0e0e0e0e0w0w0w0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0w0w0e0e0w0w0e0w0w0w0e0e0w0w0e0e0w0e0e0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0w0w0e0w0w0w0w0w0w0w0w0w0w0w0w0w0w0e0w0w0w0w0w0w0
w0w0w0w0e0e0e0e0w0w0w0w0e0e0e0e0e0e0e0e0e0e0w0e0e0x4e0e0e0w0
w0w0w0w0e0e0e0e0w0w0w0w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0
w0e0e0e0e0e0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0
w0w0w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0e0w0w0w0w0w0w0w0w0
<map openingText="Floor 3" playerStart="6, 10">
<exit id="2" location="7, 10" destination="testing/test2.txt" name="downstairs"/>
<exit id="3" location="6, 1" destination="testing/test2.txt" name="north"/>
<exit id="4" location="25, 14" destination="testing/test2.txt" name="east"/>
</map>

32
testing/test4.txt Normal file
View file

@ -0,0 +1,32 @@
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0w0w0e0w0w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0w0
w0w0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0w0e0e0w0e0w0e0w0e0e0w0
w0w0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0w0w0e0w0e0w0e0w0e0e0w0
w0e0e0e0w0w0e0e0e0w0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0w0
w0e0e0e0w0w0e0e0e0w0w0w0e0e0w0w0w0w0w0w0w0w0w0w0e0e0w0w0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0
w0w0w0w0e0e0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0w0w0e0e0w0w0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0w0w0e0e0w0w0e0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0w0
w0e0e0e0e0e0e0e0e0w0e0e0e0w0w0w0e0e0w0w0w0e0e0e0e0w0e0e0e0w0
w0w0w0w0w0w0w0w0w0w0e0e0e0w0w0w0e0e0w0w0w0e0e0e0e0w0e0e0w0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0w0w0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0w0w0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0x2e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0w0e0w0e0e0e0e0e0e0e0e0w0
w0e0e0e0e0e0e0e0e0e0e0e0e0w0e0e0e0e0e0e0w0e0e0e0e0e0e0e0e0w0
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
<map openingText="Basement" playerStart="22, 22">
<exit id="2" location="23, 22" destination="testing/test1.txt" name="upstairs"/>
</map>

44
tile.py Normal file
View file

@ -0,0 +1,44 @@
from tkinter import *
class Tile:
"""A representation of a tile on the display"""
shapes = {'circle' : 'o', 'cross' : 'x', 'triangle' : '^', 'none' : ' ',
'square' : '#', 'vertical' : '|', 'horizontal' : '-'}
def __init__(self, bgroundColor, fgroundColor = 'white', fgroundShape = ' '):
self.bgc = bgroundColor
self.fgc = fgroundColor
self.fgs = fgroundShape
def paint(self, display, x, y): #display being a canvas
if type(display) != Canvas:
raise TypeError('Display must be a tkinter.Canvas.')
else:
tag = '(' + str(int(x)) + ', ' + str(int(y)) + ')'
display.delete(tag) #delete the old tile before creating a new one.
if self.bgc != 'clear':
display.create_rectangle((x*32, y*32, x*32+32, y*32+32),
fill = self.bgc, width = 0, tags = (tag))
if self.fgs == Tile.shapes['triangle']:
display.create_polygon((x*32+15, y*32+2, x*32+2, y*32+30,
x*32+30, y*32+30, x*32+16, y*32+2),
fill = self.fgc, width = 0, tags = (tag))
elif self.fgs == Tile.shapes['circle']:
display.create_oval((x*32+2, y*32+2, x*32+30, y*32+30),
fill = self.fgc, width = 0, tags = (tag))
elif self.fgs == Tile.shapes['cross']:
display.create_line((x*32+2, y*32+2, x*32+30, y*32+30),
fill = self.fgc, width = 3, tags = (tag))
display.create_line((x*32+30, y*32+2, x*32+2, y*32+30),
fill = self.fgc, width = 3, tags = (tag))
elif self.fgs == Tile.shapes['square']:
display.create_rectangle((x*32+2, y*32+2, x*32+30, y*32+30),
fill = self.fgc, width = 0, tags = (tag))
elif self.fgs == Tile.shapes['vertical']:
display.create_line((x*32+16, y*32, x*32+16, y*32+32),
fill = self.fgc, width = 3, tags = (tag))
elif self.fgs == Tile.shapes['horizontal']:
display.create_line((x*32, y*32+16, x*32+32, y*32+16),
fill = self.fgc, width = 3, tags = (tag))
else: pass