2019-03-09 19:08:46 -05:00
|
|
|
#gamemap.py
|
|
|
|
import re
|
|
|
|
import heapq
|
|
|
|
import ruamel.yaml
|
2019-05-15 17:56:16 -04:00
|
|
|
import math as _mt
|
2019-10-11 14:35:39 -04:00
|
|
|
import gamethings as _gt
|
|
|
|
import gamelocus as _gl
|
2019-10-15 18:40:50 -04:00
|
|
|
from ruamel.yaml.comments import CommentedMap
|
|
|
|
|
|
|
|
class Singleton(object):
|
|
|
|
"""This is a super basic class (would be a struct in other languages)
|
|
|
|
that represents where a singleton Thing should be in a map."""
|
|
|
|
yaml_flag = u'!Singleton'
|
|
|
|
|
|
|
|
def __init__(self, name: str, x: int, y: int):
|
|
|
|
self.name = name
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def to_yaml(cls, representer, node):
|
|
|
|
representer.represent_mapping({
|
|
|
|
'name': self.name,
|
|
|
|
'location': (self.x, self.y)
|
|
|
|
})
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_yaml(cls, constructor, node):
|
|
|
|
parts = CommentedMap()
|
|
|
|
constructor.construct_mapping(node, parts, True)
|
|
|
|
# since all parts are necessary, I won't bother with if statements
|
|
|
|
# and let it crash.
|
|
|
|
if not isinstance(parts['name'], str):
|
|
|
|
raise RuntimeError("Name must be a string.")
|
|
|
|
if not isinstance(parts['location'], list):
|
|
|
|
raise RuntimeError("Location must be a list.")
|
|
|
|
if not (isinstance(parts['location'][0], int) and isinstance(parts['location'][1], int)):
|
|
|
|
raise RuntimeError("Coordinates must be integers.")
|
|
|
|
return cls(parts['name'], parts['location'][0], parts['location'][1])
|
2019-05-28 14:23:15 -04:00
|
|
|
|
2019-03-09 19:08:46 -05:00
|
|
|
class MapError(RuntimeError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class GameMap(object):
|
|
|
|
# Matrix tile codes:
|
|
|
|
# e: empty (0)
|
|
|
|
# w: wall (0)
|
|
|
|
|
|
|
|
# regular expressions
|
|
|
|
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
|
|
|
|
matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+')
|
2019-10-11 14:35:39 -04:00
|
|
|
yaml = ruamel.yaml.YAML()
|
|
|
|
yaml.register_class(_gt.Item)
|
|
|
|
yaml.register_class(_gt.Useable)
|
|
|
|
yaml.register_class(_gt.NPC)
|
|
|
|
yaml.register_class(_gt.Door)
|
|
|
|
yaml.register_class(_gt.MapExit)
|
|
|
|
yaml.register_class(_gt.MapEntrance)
|
2019-10-15 18:40:50 -04:00
|
|
|
yaml.register_class(Singleton)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
def __init__(self, name, graph, matrix, dimensions):
|
|
|
|
self.name = name
|
|
|
|
self.openingText = ""
|
|
|
|
self.mapGraph = graph
|
|
|
|
self.mapMatrix = matrix
|
|
|
|
self.dimensions = dimensions
|
2019-05-15 01:37:35 -04:00
|
|
|
self.things = {} # int thingID : thing
|
|
|
|
self.thingPos = {} # int location : list of int thingIDs
|
|
|
|
self.thingNames = {} # str name: list of int thingIDs
|
2019-03-09 19:08:46 -05:00
|
|
|
self.playerStart = (1, 1)
|
|
|
|
self.description = "The area is completely blank."
|
|
|
|
self.floorColors = []
|
|
|
|
self.wallColors = []
|
|
|
|
self.persistent = []
|
2019-05-30 15:44:29 -04:00
|
|
|
self.enterScript = ''
|
2019-10-11 14:35:39 -04:00
|
|
|
self.version = 'square 1'
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __cleanStr(text: str, end = '\n'):
|
|
|
|
if text == None:
|
|
|
|
return text
|
|
|
|
text = text.strip()
|
|
|
|
i = 0
|
|
|
|
while True:
|
|
|
|
i = text.find('\n', i)
|
|
|
|
if i == -1:
|
|
|
|
break
|
|
|
|
j = i+1
|
|
|
|
if len(text) > j:
|
|
|
|
while text[j] in ' \t':
|
|
|
|
j += 1 # just find the first non-space chartacter
|
|
|
|
text = text[:i+1] + text[j:]
|
|
|
|
i += 1
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
return text.replace('\n', end)
|
|
|
|
|
|
|
|
@staticmethod
|
2019-10-15 18:40:50 -04:00
|
|
|
def read(infile = None, prevMap = None, singletons = None, preLoaded = False, nextThing = 0):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""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."""
|
2019-03-09 19:50:31 -05:00
|
|
|
info = None
|
2019-03-09 19:08:46 -05:00
|
|
|
tryToRead = True
|
|
|
|
if infile != None:
|
|
|
|
try:
|
|
|
|
with open(infile, 'r') as f:
|
2019-10-11 14:35:39 -04:00
|
|
|
info = GameMap.yaml.load(f)
|
2019-03-09 19:08:46 -05:00
|
|
|
except OSError as e:
|
|
|
|
print("The file could not be read.")
|
2019-05-20 21:13:56 -04:00
|
|
|
return None, nextThing
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError("No file was specified for loading.")
|
2019-10-11 14:35:39 -04:00
|
|
|
|
|
|
|
# Future feature: different map standards, with cool special features
|
|
|
|
# like hex tiles, or walls in-between tiles.
|
|
|
|
# For now just 'square 1': square tiles, version 1.
|
|
|
|
version = 'square 1'
|
|
|
|
if 'version' in info:
|
|
|
|
if info['version'] in ('square 1'):
|
|
|
|
version = info['version']
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# Now what we do with the data
|
|
|
|
mat = None
|
2019-03-09 19:50:31 -05:00
|
|
|
if 'layout' not in info:
|
|
|
|
raise MapError('No layout in {0}.'.format(infile))
|
|
|
|
layout = info['layout']
|
2019-03-09 19:08:46 -05:00
|
|
|
if layout != None:
|
|
|
|
#print(layout.text)
|
2019-03-09 19:50:31 -05:00
|
|
|
match = GameMap.matrixRegex.match(layout.lstrip())
|
2019-03-09 19:08:46 -05:00
|
|
|
if match == None:
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError('Map read a file without a map matrix.')
|
2019-03-09 19:08:46 -05:00
|
|
|
mat = match.group()
|
|
|
|
else:
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError('Map read a file without a map matrix.')
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# generate matrix and graph first
|
|
|
|
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
|
|
|
|
level = GameMap(infile, mapGraph, mapMatrix, dimensions)
|
|
|
|
|
|
|
|
# Now, load other info
|
2019-10-15 18:40:50 -04:00
|
|
|
nextThing = GameMap.loadThings(level, info, prevMap, singletons, preLoaded, nextThing)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-05-15 01:37:35 -04:00
|
|
|
return level, nextThing
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
@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
|
|
|
|
l = 0
|
|
|
|
while len(matrixStr) > 0:
|
|
|
|
tile = GameMap.tileRegex.match(matrixStr)
|
|
|
|
if tile != None:
|
|
|
|
tileType = tile.group(1)
|
|
|
|
tileNum = tile.group(2)
|
|
|
|
if tileType == ' ':
|
|
|
|
tileType = 'e'
|
|
|
|
if tileNum == ' ':
|
|
|
|
tileNum = '0'
|
|
|
|
mat[l].append((tileType, int(tileNum)))
|
|
|
|
#x += 1
|
|
|
|
matrixStr = matrixStr[len(tile.group()):]
|
|
|
|
elif matrixStr[0] == '\n':
|
|
|
|
if x == 0:
|
|
|
|
x = len(mat[l])
|
|
|
|
elif x != len(mat[l]):
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError("Map matrix has jagged edges.")
|
2019-03-09 19:08:46 -05:00
|
|
|
l += 1
|
|
|
|
#x = 0
|
|
|
|
mat.append([])
|
|
|
|
i = 1
|
|
|
|
while i < len(matrixStr) and matrixStr[i] in ' \t\n':
|
|
|
|
i += 1
|
|
|
|
if i == len(matrixStr):
|
|
|
|
matrixStr = ''
|
|
|
|
else:
|
|
|
|
matrixStr = matrixStr[i:]
|
2019-03-09 19:50:31 -05:00
|
|
|
else: # This should happen when it finishes?
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError("Unexpected token in map matrix: '{0}'".format(matrixStr))
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-10-15 18:40:50 -04:00
|
|
|
def loadThings(level, info, prevMap = None, singletons = None, preLoaded = False, nextThing = 0):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""load the things from the xml part of the map file."""
|
2019-03-09 19:50:31 -05:00
|
|
|
if 'openingText' in info:
|
|
|
|
level.openingText = info['openingText']
|
|
|
|
if 'playerStart' in info:
|
|
|
|
level.playerStart = info['playerStart']
|
|
|
|
if 'description' in info:
|
|
|
|
level.description = info['description']
|
2019-05-30 15:44:29 -04:00
|
|
|
if 'enterScript' in info:
|
|
|
|
level.enterScript = info['enterScript']
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# get map colors
|
2019-03-09 19:50:31 -05:00
|
|
|
if 'floorColors' in info:
|
|
|
|
level.floorColors = info['floorColors']
|
|
|
|
if 'wallColors' in info:
|
|
|
|
level.wallColors = info['wallColors']
|
2019-03-09 19:08:46 -05:00
|
|
|
if len(level.floorColors) == 0:
|
|
|
|
level.floorColors.append('#9F7F5F')
|
|
|
|
if len(level.wallColors) == 0:
|
|
|
|
level.wallColors.append('#7F3F0F')
|
|
|
|
|
|
|
|
# get things
|
2019-05-28 14:23:15 -04:00
|
|
|
hasKnownEntrance = False
|
2019-03-09 19:50:31 -05:00
|
|
|
if 'loadOnce' in info and not preLoaded:
|
|
|
|
for thing in info['loadOnce']:
|
|
|
|
#print(type(thing))
|
2019-10-15 18:40:50 -04:00
|
|
|
if isinstance(thing, _gt.Thing):
|
|
|
|
nextThing = level.addThing(thing, nextThing, True)
|
|
|
|
else:
|
|
|
|
raise MapError("Non-thing loaded as a thing:\n{}".format(thing))
|
2019-03-09 19:50:31 -05:00
|
|
|
if 'loadAlways' in info:
|
|
|
|
for thing in info['loadAlways']:
|
|
|
|
#print(type(thing))
|
2019-10-15 18:40:50 -04:00
|
|
|
if isinstance(thing, _gt.Thing):
|
|
|
|
nextThing = level.addThing(thing, nextThing)
|
|
|
|
if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid:
|
|
|
|
level.playerStart = (thing.x, thing.y)
|
|
|
|
hasKnownEntrance = True
|
|
|
|
elif isinstance(thing, Singleton):
|
|
|
|
if singletons != None:
|
|
|
|
single = singletons[thing.name]
|
|
|
|
single.x, single.y = thing.x, thing.y
|
|
|
|
single.prevx, single.prevy = thing.x, thing.y
|
|
|
|
nextThing = level.addThing(single, nextThing)
|
|
|
|
else:
|
|
|
|
raise MapError("Non-thing loaded as a thing:\n{}".format(thing))
|
2019-05-15 01:37:35 -04:00
|
|
|
return nextThing
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# stuff the gameshell itself might use
|
|
|
|
|
2019-05-15 01:37:35 -04:00
|
|
|
def addThing(self, thing, nextThing = 0, persist = False):
|
2019-10-15 18:40:50 -04:00
|
|
|
if thing == None: # it must be a singleton
|
2019-05-15 01:37:35 -04:00
|
|
|
return nextThing
|
|
|
|
#if thing.name in self.thingNames: # resolved
|
|
|
|
# raise ValueError("Cannot have two objects named {0}.".format(thing.name))
|
|
|
|
if thing.thingID == -1: # This is to ensure that we don't double up IDs.
|
|
|
|
thing.thingID = nextThing
|
|
|
|
nextThing += 1
|
2019-10-15 18:40:50 -04:00
|
|
|
# Some things, like containers, have other things as custom values,
|
|
|
|
# so they need IDs as well.
|
|
|
|
# Let's only add them if they weren't already loaded.
|
|
|
|
if thing.thingType in 'iun':
|
|
|
|
nextThing = GameMap.addThingRecursive(thing.customValues, nextThing)
|
|
|
|
if thing.thingType == 'n':
|
|
|
|
for i in thing.tempInventory:
|
|
|
|
if i.thingID == -1:
|
|
|
|
i.thingID = nextThing
|
|
|
|
nextThing = GameMap.addThingRecursive(i.customValues, nextThing + 1)
|
|
|
|
thing.addThing(i)
|
|
|
|
del thing.tempInventory
|
2019-03-09 19:08:46 -05:00
|
|
|
pos = self.coordsToInt(thing.x, thing.y)
|
|
|
|
if pos not in self.thingPos:
|
2019-05-15 01:37:35 -04:00
|
|
|
self.thingPos[pos] = [thing.thingID]
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-05-15 01:37:35 -04:00
|
|
|
self.thingPos[pos].append(thing.thingID)
|
|
|
|
if thing.name not in self.thingNames:
|
|
|
|
self.thingNames[thing.name] = [thing.thingID]
|
|
|
|
else:
|
|
|
|
self.thingNames[thing.name].append(thing.thingID)
|
|
|
|
self.things[thing.thingID] = thing
|
2019-03-09 19:08:46 -05:00
|
|
|
if persist:
|
2019-05-24 14:06:34 -04:00
|
|
|
self.persistent.append(thing.thingID)
|
2019-05-15 01:37:35 -04:00
|
|
|
return nextThing
|
|
|
|
|
2019-10-14 12:02:13 -04:00
|
|
|
@staticmethod
|
|
|
|
def addThingRecursive(container, nextThing = 0):
|
2019-10-11 14:35:39 -04:00
|
|
|
if isinstance(container, _gt.Thing):
|
2019-07-04 18:48:47 -04:00
|
|
|
if container.thingID == -1:
|
|
|
|
container.thingID = nextThing
|
2019-10-14 12:02:13 -04:00
|
|
|
nextThing = GameMap.addThingRecursive(container.customValues, nextThing)
|
2019-07-04 18:48:47 -04:00
|
|
|
return nextThing + 1
|
|
|
|
else:
|
|
|
|
return nextThing
|
2019-05-28 14:23:15 -04:00
|
|
|
elif isinstance(container, dict):
|
|
|
|
for i in container:
|
2019-10-14 12:02:13 -04:00
|
|
|
nextThing = GameMap.addThingRecursive(container[i], nextThing)
|
2019-05-28 14:23:15 -04:00
|
|
|
return nextThing
|
|
|
|
elif isinstance(container, list):
|
|
|
|
for i in container:
|
2019-10-14 12:02:13 -04:00
|
|
|
nextThing = GameMap.addThingRecursive(i, nextThing)
|
2019-05-28 14:23:15 -04:00
|
|
|
return nextThing
|
|
|
|
else:
|
|
|
|
return nextThing
|
|
|
|
|
2019-05-15 01:37:35 -04:00
|
|
|
def getThing(self, **kwargs):
|
|
|
|
if 'name' in kwargs:
|
|
|
|
return self.getThingByName(kwargs['name'])
|
|
|
|
elif 'thingID' in kwargs:
|
|
|
|
return self.getThingByID(kwargs['thingID'])
|
|
|
|
elif 'pos' in kwargs:
|
|
|
|
return self.getThingAtPos(kwargs['pos'])
|
|
|
|
elif 'coords' in kwargs:
|
|
|
|
return self.getThingAtCoords(kwargs['coords'][0], kwargs['coords'][1])
|
|
|
|
else:
|
|
|
|
raise ValueError('Thing cannot be found by {}.'.format(str(kwargs)))
|
|
|
|
|
|
|
|
def removeThing(self, **kwargs):
|
|
|
|
if 'name' in kwargs:
|
|
|
|
return self.removeThingByName(kwargs['name'])
|
|
|
|
elif 'thingID' in kwargs:
|
|
|
|
return self.removeThingByID(kwargs['thingID'])
|
|
|
|
elif 'pos' in kwargs:
|
|
|
|
return self.removeThingAtPos(kwargs['pos'])
|
|
|
|
elif 'coords' in kwargs:
|
|
|
|
return self.removeThingAtCoords(kwargs['coords'][0], kwargs['coords'][1])
|
|
|
|
else:
|
|
|
|
raise ValueError('Thing cannot be found by {}.'.format(str(kwargs)))
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-10-11 14:35:39 -04:00
|
|
|
def path(self, x1, y1, loc, closeEnough = True):
|
2019-03-09 19:08:46 -05:00
|
|
|
startThing = self.getThingAtCoords(x1, y1)
|
2019-10-11 14:35:39 -04:00
|
|
|
#if not closeEnough:
|
|
|
|
# if startThing and not startThing.passable:
|
|
|
|
# return -1, [], -1 # meaning you can't get there
|
|
|
|
dist, prev, endPoint = self.dijkstra(x1, y1, loc, closeEnough)
|
|
|
|
#endPoint = self.coordsToInt(x2, y2)
|
2019-03-09 19:08:46 -05:00
|
|
|
numVertex = self.dimensions[0] * self.dimensions[1]
|
2019-10-11 14:35:39 -04:00
|
|
|
if endPoint > -1 and dist[endPoint] < numVertex + 1:
|
|
|
|
pathList = [endPoint]
|
|
|
|
nextPoint = prev[endPoint]
|
|
|
|
while nextPoint != -1:
|
|
|
|
pathList.append(nextPoint)
|
|
|
|
nextPoint = prev[nextPoint]
|
|
|
|
pathList.reverse()
|
|
|
|
return dist[endPoint], pathList[1:], endPoint
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-10-11 14:35:39 -04:00
|
|
|
return -1, [], -1 # meaning you can't get there
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-10-11 14:35:39 -04:00
|
|
|
def dijkstra(self, x1, y1, loc = None, closeEnough = True):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
|
2019-10-11 14:35:39 -04:00
|
|
|
The closeEnough parameter will create a path that lands beside the source if
|
|
|
|
necessary. The loc parameter is an optional locus which will cause the function
|
|
|
|
to return once it finds a point that's in the locus."""
|
2019-03-09 19:08:46 -05:00
|
|
|
# first test to see that the start point is passable
|
|
|
|
startThing = self.getThingAtCoords(x1, y1)
|
|
|
|
startPoint = self.coordsToInt(x1, y1)
|
2019-10-11 14:35:39 -04:00
|
|
|
endPoint = -1 # until one matches the locus, which it might not.
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-10-11 14:35:39 -04:00
|
|
|
#if closeEnough:
|
|
|
|
# if startThing and not startThing.passable:
|
|
|
|
# dist[startPoint] = -1 # This is so it doesn't path into a non-passable end point.
|
2019-03-09 19:08:46 -05:00
|
|
|
queue = []
|
|
|
|
heapq.heappush(queue, (dist[startPoint], startPoint))
|
|
|
|
|
|
|
|
while len(queue) > 0:
|
|
|
|
u = heapq.heappop(queue)[1]
|
2019-10-11 14:35:39 -04:00
|
|
|
if loc != None and self.intToCoords(u) in loc:
|
|
|
|
return dist, prev, u
|
2019-03-09 19:08:46 -05:00
|
|
|
for v in self.mapGraph[u]:
|
|
|
|
thing = self.getThingAtPos(v)
|
|
|
|
if thing and not thing.passable:
|
2019-10-11 14:35:39 -04:00
|
|
|
if closeEnough and self.intToCoords(v) in loc:
|
|
|
|
return dist, prev, u
|
|
|
|
else:
|
|
|
|
continue
|
2019-03-09 19:08:46 -05:00
|
|
|
tempDist = dist[u] + 1
|
|
|
|
if tempDist < dist[v]:
|
|
|
|
dist[v] = tempDist
|
|
|
|
if dist[u] != -1:
|
|
|
|
prev[v] = u
|
|
|
|
heapq.heappush(queue, (dist[v], v))
|
|
|
|
|
2019-10-11 14:35:39 -04:00
|
|
|
return dist, prev, endPoint
|
2019-03-09 19:08:46 -05:00
|
|
|
#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):
|
2019-07-04 18:48:47 -04:00
|
|
|
"""Test for line of signt from one tile to another."""
|
|
|
|
# Trivial case first:
|
|
|
|
if abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1:
|
|
|
|
return True
|
2019-10-11 14:35:39 -04:00
|
|
|
|
|
|
|
# Common case second:
|
|
|
|
lst = list(_gl.LineLocus(x1, y1, x2, y2, False))[1:-1]
|
2019-05-15 17:56:16 -04:00
|
|
|
|
|
|
|
# Here is where we actually check:
|
|
|
|
for space in lst:
|
2019-10-11 14:35:39 -04:00
|
|
|
if not self.isPassable(self.coordsToInt(*space)):
|
2019-05-15 17:56:16 -04:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def isPassable(self, x, y = -1):
|
|
|
|
pos = x
|
|
|
|
if y == -1:
|
|
|
|
x, y = self.intToCoords(x)
|
|
|
|
else:
|
|
|
|
pos = self.coordsToInt(x, y)
|
|
|
|
if self.mapMatrix[y][x][0] == 'w':
|
|
|
|
return False
|
|
|
|
thingsInSpace = self.getThingsAtPos(pos)
|
|
|
|
for thing in thingsInSpace:
|
|
|
|
if not thing.passable:
|
|
|
|
return False
|
|
|
|
return True
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __coordsToInt(x, y, width):
|
|
|
|
return x + y * width
|
|
|
|
|
2019-05-15 17:56:16 -04:00
|
|
|
def coordsToInt(self, x: int, y: int, width = -1):
|
2019-03-09 19:08:46 -05:00
|
|
|
if width < 0:
|
|
|
|
return x + y * self.dimensions[0]
|
|
|
|
else:
|
|
|
|
return x + y * width
|
|
|
|
|
2019-05-15 17:56:16 -04:00
|
|
|
def intToCoords(self, pos: int):
|
2019-03-09 19:08:46 -05:00
|
|
|
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:
|
2019-05-15 01:37:35 -04:00
|
|
|
return self.things[self.thingPos[pos][0]]
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getThingsAtPos(self, pos):
|
|
|
|
if pos in self.thingPos:
|
2019-07-04 18:48:47 -04:00
|
|
|
return [self.things[i] for i in self.thingPos[pos]]
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-05-15 17:56:16 -04:00
|
|
|
return []
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
def getThingByName(self, name):
|
|
|
|
if name in self.thingNames:
|
2019-05-15 01:37:35 -04:00
|
|
|
return self.things[self.thingNames[name][0]]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getThingsByName(self, name):
|
|
|
|
if name in self.thingNames:
|
|
|
|
ret = []
|
|
|
|
for i in self.thingNames[name]:
|
|
|
|
ret.append(self.things[i])
|
|
|
|
return ret
|
|
|
|
else:
|
2019-05-15 17:56:16 -04:00
|
|
|
return []
|
2019-05-15 01:37:35 -04:00
|
|
|
|
|
|
|
def getThingByID(self, thingID):
|
|
|
|
if thingID in self.things:
|
|
|
|
return self.things[thingID]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def removeThingByThing(self, thing):
|
|
|
|
if thing != None:
|
|
|
|
oldPos = self.coordsToInt(thing.x, thing.y)
|
|
|
|
if oldPos in self.thingPos:
|
|
|
|
self.thingPos[oldPos].remove(thing.thingID)
|
|
|
|
if len(self.thingPos[oldPos]) == 0:
|
|
|
|
del self.thingPos[oldPos]
|
|
|
|
oldName = thing.name
|
2019-05-28 14:23:15 -04:00
|
|
|
if oldName in self.thingNames:
|
2019-05-15 01:37:35 -04:00
|
|
|
self.thingNames[oldName].remove(thing.thingID)
|
|
|
|
if len(self.thingNames[oldName]) == 0:
|
|
|
|
del self.thingNames[oldName]
|
2019-05-24 14:06:34 -04:00
|
|
|
if thing.thingID in self.persistent:
|
|
|
|
self.persistent.remove(thing.thingID)
|
2019-05-15 01:37:35 -04:00
|
|
|
del self.things[thing.thingID]
|
|
|
|
return thing
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def removeThingAtCoords(self, x, y):
|
|
|
|
return self.removeThingAtPos(self.coordsToInt(x, y))
|
|
|
|
|
|
|
|
def removeThingsAtCoords(self, x, y):
|
|
|
|
return self.removeThingsAtPos(self.coordsToInt(x, y))
|
|
|
|
|
|
|
|
def removeThingAtPos(self, pos):
|
|
|
|
if pos in self.thingPos:
|
|
|
|
return self.removeThingByThing(self.getThingAtPos(pos))
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def removeThingsAtPos(self, pos):
|
|
|
|
if pos in self.thingPos:
|
|
|
|
ret = []
|
|
|
|
for i in self.thingPos[pos]:
|
|
|
|
ret.append(self.removeThingByThing(self.things[i]))
|
|
|
|
return ret
|
|
|
|
else:
|
2019-05-15 17:56:16 -04:00
|
|
|
return []
|
2019-05-15 01:37:35 -04:00
|
|
|
|
|
|
|
def removeThingByName(self, name):
|
|
|
|
if name in self.thingNames:
|
|
|
|
return self.removeThingByThing(self.getThingByName(name))
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def removeThingsByName(self, name):
|
|
|
|
if name in self.thingNames:
|
|
|
|
ret = []
|
|
|
|
for i in self.thingNames[name]:
|
|
|
|
ret.append(self.removeThingByThing(self.things[i]))
|
|
|
|
return ret
|
|
|
|
else:
|
2019-05-15 17:56:16 -04:00
|
|
|
return []
|
2019-05-15 01:37:35 -04:00
|
|
|
|
|
|
|
def removeThingByID(self, thingID):
|
|
|
|
if thingID in self.things:
|
|
|
|
return self.removeThingByThing(self.things[thingID])
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2019-05-15 01:37:35 -04:00
|
|
|
def moveThing(self, thing, x, y = -1):
|
2019-03-09 19:08:46 -05:00
|
|
|
newPos = x
|
|
|
|
if y != -1:
|
|
|
|
newPos = self.coordsToInt(x, y)
|
|
|
|
else:
|
|
|
|
x, y = self.intToCoords(x)
|
2019-06-08 13:42:00 -04:00
|
|
|
if thing != None:
|
2019-10-11 14:35:39 -04:00
|
|
|
if thing.x == x and thing.y == y:
|
|
|
|
return # it's already there, so don't do anything.
|
2019-03-09 19:08:46 -05:00
|
|
|
oldPos = self.coordsToInt(thing.x, thing.y)
|
|
|
|
if oldPos in self.thingPos:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.thingPos[oldPos].remove(thing.thingID)
|
2019-03-09 19:08:46 -05:00
|
|
|
if len(self.thingPos[oldPos]) == 0:
|
|
|
|
del self.thingPos[oldPos]
|
|
|
|
if newPos not in self.thingPos:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.thingPos[newPos] = [thing.thingID]
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.thingPos[newPos].append(thing.thingID)
|
2019-05-15 17:56:16 -04:00
|
|
|
relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y
|
2019-07-04 18:48:47 -04:00
|
|
|
thing.prevx, thing.prevy = thing.x, thing.y
|
2019-03-09 19:08:46 -05:00
|
|
|
thing.x, thing.y = x, y
|
2019-05-15 17:56:16 -04:00
|
|
|
thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-06-08 13:42:00 -04:00
|
|
|
raise MapError("There is nothing to move.")
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-10-11 14:35:39 -04:00
|
|
|
class LoSLocus(_gl.Locus):
|
|
|
|
"""A locus that defines all points within line-of-sight of a given points."""
|
|
|
|
|
|
|
|
def __init__(self, x, y, level):
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
|
|
|
self.level = level
|
|
|
|
|
|
|
|
def __contains__(self, item):
|
|
|
|
if not isinstance(item, tuple) and not isinstance(item, list):
|
|
|
|
raise ValueError("Item must be a tuple or a list.")
|
|
|
|
x, y = item
|
|
|
|
return self.level.lineOfSight(x, y, self.x, self.y)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
ret = [(self.x, self.y)]
|
|
|
|
mat = [[False for i in range(self.level.y)] for j in range(self.level.x)]
|
|
|
|
mat[self.x][self.y] = True
|
|
|
|
fringe = [(self.x, self.y + 1), (self.x + 1, self.y), (self.x, self.y - 1), (self.x - 1, self.y),
|
|
|
|
(self.x + 1, self.y + 1), (self.x + 1, self.y - 1), (self.x - 1, self.y - 1), (self.x - 1, self.y + 1)]
|
|
|
|
while len(fringe) > 0:
|
|
|
|
point = fringe.pop(0)
|
|
|
|
check = []
|
|
|
|
if abs(point[0] - self.x) > abs(point[1] - self.y):
|
|
|
|
if point[0] > self.x:
|
|
|
|
check.append((point[0] - 1, point[1]))
|
|
|
|
if point[1] > self.y:
|
|
|
|
check.append((point[0] - 1, point[1] - 1))
|
|
|
|
elif point[1] < self.y:
|
|
|
|
check.append((point[0] - 1, point[1] + 1))
|
|
|
|
else:
|
|
|
|
check.append((point[0] + 1, point[1]))
|
|
|
|
if point[1] > self.y:
|
|
|
|
check.append((point[0] + 1, point[1] - 1))
|
|
|
|
elif point[1] < self.y:
|
|
|
|
check.append((point[0] + 1, point[1] + 1))
|
|
|
|
elif abs(point[0] - self.x) < abs(point[1] - self.y):
|
|
|
|
if point[1] > self.y:
|
|
|
|
check.append((point[0], point[1] - 1))
|
|
|
|
if point[0] > self.x:
|
|
|
|
check.append((point[0] - 1, point[1] - 1))
|
|
|
|
elif point[0] < self.x:
|
|
|
|
check.append((point[0] + 1, point[1] - 1))
|
|
|
|
else:
|
|
|
|
check.append((point[0], point[1] + 1))
|
|
|
|
if point[0] > self.x:
|
|
|
|
check.append((point[0] - 1, point[1] + 1))
|
|
|
|
elif point[0] < self.x:
|
|
|
|
check.append((point[0] + 1, point[1] + 1))
|
|
|
|
else:
|
|
|
|
if point[0] > self.x:
|
|
|
|
if point[1] > self.y:
|
|
|
|
check.append((point[0] - 1, point[1] - 1))
|
|
|
|
else:
|
|
|
|
check.append((point[0] - 1, point[1] - 1))
|
|
|
|
else:
|
|
|
|
if point[1] > self.y:
|
|
|
|
check.append((point[0] + 1, point[1] - 1))
|
|
|
|
else:
|
|
|
|
check.append((point[0] + 1, point[1] - 1))
|
|
|
|
status = [mat[i[0]][i[1]] for i in check]
|
|
|
|
addIf = False
|
|
|
|
if True in status:
|
|
|
|
if False in status:
|
|
|
|
addIf = self.level.lineOfSight(point[0], point[1], self.x, self.y)
|
|
|
|
else:
|
|
|
|
addIf = True
|
|
|
|
if addIf:
|
|
|
|
mat[point[0]][point[1]] = self.level.isPassable(*point)
|
|
|
|
ret.append(point)
|
|
|
|
return iter(ret)
|
|
|
|
|