Initial commit
This commit is contained in:
commit
565b6ba903
12 changed files with 2284 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
maps
|
||||||
|
saves
|
||||||
|
__pycache__
|
||||||
|
winsh.py
|
628
gamebase.py
Normal file
628
gamebase.py
Normal 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
100
gameevents.py
Normal 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
439
gamegui.py
Normal 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
688
gamemap.py
Normal 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
151
gameshell.py
Normal 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
106
shell.py
Normal 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
33
testing/test1.txt
Normal 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
35
testing/test2.txt
Normal 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
24
testing/test3.txt
Normal 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
32
testing/test4.txt
Normal 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
44
tile.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue