Added loci, and made dialog processing internal to the engine.
This commit is contained in:
parent
5010042430
commit
9cda61a895
11 changed files with 1860 additions and 1269 deletions
336
gamelocus.py
Normal file
336
gamelocus.py
Normal file
|
@ -0,0 +1,336 @@
|
|||
# gamelocus.py
|
||||
|
||||
import math as _mt
|
||||
|
||||
class Locus(object):
|
||||
"""Abstract base class for locus objects."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Base locus constructor."""
|
||||
pass
|
||||
|
||||
def __contains__(self, item):
|
||||
"""Is this point within the locus?"""
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
return iter(())
|
||||
|
||||
def __repr__(self):
|
||||
return "Locus()"
|
||||
|
||||
class PointLocus(Locus):
|
||||
"""A locus that defines a single tile."""
|
||||
|
||||
def __init__(self, x, y, **kwargs):
|
||||
"""Take the coordinates for a point."""
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __contains__(self, item):
|
||||
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||
raise ValueError("Item must be a tuple or a list.")
|
||||
return item[0] == self.x and item[1] == self.y
|
||||
|
||||
def __iter__(self):
|
||||
return iter(((self.x, self.y),)) # just an iterator of the one tile
|
||||
|
||||
def __repr__(self):
|
||||
return "PointLocus({}, {})".format(self.x, self.y)
|
||||
|
||||
class LineLocus(Locus):
|
||||
"""A locus that defines all square tiles that intersect a given line segment."""
|
||||
|
||||
def __init__(self, x1, y1, x2, y2, thick = False, **kwargs):
|
||||
"""Take the coordinates for the two endpoints."""
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
self.thick = thick
|
||||
|
||||
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
|
||||
# Is it outside the bounding box for this line?
|
||||
if x > max(self.x1, self.x2) or x < min(self.x1, self.x2) or y > max(self.y1, self.y2) or y < min(self.y1, self.y2):
|
||||
return False
|
||||
# Is this line straight up and down, or left and right?
|
||||
elif self.x1 == self.x2 or self.y1 == self.y2:
|
||||
# In this case, the line takes up the whole bounding box, so it must
|
||||
# be true since it passed the first step to get here.
|
||||
return True
|
||||
else:
|
||||
slope = (self.y2 - self.y1) / (self.x2 - self.x1)
|
||||
intercept = (self.y1 + 0.5) - (self.x1 + 0.5) * slope
|
||||
f = lambda z: z * slope + intercept
|
||||
if self.thick:
|
||||
if slope > 0:
|
||||
return _mt.ceil(f(x)-1) <= y and _mt.floor(f(x+1)+1) > y
|
||||
else:
|
||||
return _mt.floor(f(x)+1) > y and _mt.ceil(f(x+1)-1) <= y
|
||||
else:
|
||||
if slope > 0:
|
||||
return _mt.floor(f(x)) <= y and _mt.ceil(f(x+1)) > y
|
||||
else:
|
||||
return _mt.ceil(f(x)) > y and _mt.floor(f(x+1)) <= y
|
||||
|
||||
def __iter__(self):
|
||||
# avoid infinite slope case
|
||||
if self.x1 == self.x2:
|
||||
miny, maxy = min(self.y1, self.y2), max(self.y1, self.y2) + 1
|
||||
ret = [(self.x1, y) for y in range(miny, maxy)]
|
||||
return iter(ret)
|
||||
# for convenience: it's easy to calculate if it's horizontal too.
|
||||
elif self.y1 == self.y2:
|
||||
minx, maxx = min(self.x1, self.x2), max(self.x1, self.x2) + 1
|
||||
ret = [(x, self.y1) for x in range(minx, maxx)]
|
||||
return iter(ret)
|
||||
else:
|
||||
slope = (self.y2 - self.y1) / (self.x2 - self.x1)
|
||||
intercept = (self.y1 + 0.5) - (self.x1 + 0.5) * slope
|
||||
f = lambda x: x * slope + intercept
|
||||
lx, ly = min((self.x1, self.y1), (self.x2, self.y2))
|
||||
rx, ry = max((self.x1, self.y1), (self.x2, self.y2))
|
||||
ret = []
|
||||
# edge case 1: the first half column
|
||||
if slope > 0:
|
||||
maxy = _mt.ceil(f(lx+1))
|
||||
if self.thick:
|
||||
maxy = _mt.floor(f(lx+1)+1)
|
||||
for y in range(ly, maxy):
|
||||
ret.append((lx, y))
|
||||
else:
|
||||
maxy = _mt.floor(f(lx+1))-1
|
||||
if self.thick:
|
||||
maxy = _mt.ceil(f(lx+1)-2)
|
||||
for y in range(ly, maxy, -1):
|
||||
ret.append((lx, y))
|
||||
# Usual case: the line between the end points
|
||||
for x in range(lx+1, rx):
|
||||
if slope > 0:
|
||||
miny = _mt.floor(f(x))
|
||||
maxy = _mt.ceil(f(x+1))
|
||||
if self.thick:
|
||||
miny = _mt.ceil(f(x)-1)
|
||||
maxy = _mt.floor(f(x+1)+1)
|
||||
for y in range(miny, maxy):
|
||||
ret.append((x, y))
|
||||
else:
|
||||
miny = _mt.ceil(f(x)-1)
|
||||
maxy = _mt.floor(f(x+1))-1
|
||||
if self.thick:
|
||||
miny = _mt.floor(f(x))
|
||||
maxy = _mt.ceil(f(x+1)-2)
|
||||
for y in range(miny, maxy, -1):
|
||||
ret.append((x, y))
|
||||
# edge case 2: the last half column
|
||||
if slope > 0:
|
||||
miny = _mt.floor(f(rx))
|
||||
if self.thick:
|
||||
miny = _mt.ceil(f(rx)-1)
|
||||
for y in range(miny, ry+1):
|
||||
ret.append((rx, y))
|
||||
else:
|
||||
miny = _mt.ceil(f(rx)-1)
|
||||
if self.thick:
|
||||
miny = _mt.floor(f(rx))
|
||||
for y in range(miny, ry-1, -1):
|
||||
ret.append((rx, y))
|
||||
return iter(ret)
|
||||
|
||||
def __repr__(self):
|
||||
return "LineLocus({}, {}, {}, {}, thick={})".format(self.x1, self.y1, self.x2, self.y2, self.thick)
|
||||
|
||||
class RectLocus(Locus):
|
||||
"""A locus that defines the outline of a rectangle."""
|
||||
|
||||
def __init__(self, x1, y1, x2, y2, **kwargs):
|
||||
self.lx = min(x1, x2)
|
||||
self.rx = max(x1, x2)
|
||||
self.ly = min(y1, y2)
|
||||
self.ry = max(y1, y2)
|
||||
|
||||
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
|
||||
if x < self.lx or x > self.rx or y < self.ly or y > self.ry:
|
||||
return False
|
||||
elif x == self.lx or x == self.rx or y == self.ry or y == self.ly:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
ret = [(x, self.ly) for x in range(self.lx, self.rx)]
|
||||
ret.extend([(self.rx, y) for y in range(self.ly, self.ry)])
|
||||
ret.extend([(x, self.ry) for x in range(self.rx, self.lx, -1)])
|
||||
ret.extend([(self.lx, y) for y in range(self.ry, self.ly, -1)])
|
||||
return iter(ret)
|
||||
|
||||
def __repr__(self):
|
||||
return "RectLocus({}, {}, {}, {})".format(self.lx, self.ly, self.rx, self.ry)
|
||||
|
||||
class FilledRectLocus(Locus):
|
||||
"""A locus that defines all points within a rectangle."""
|
||||
|
||||
def __init__(self, x1, y1, x2, y2, **kwargs):
|
||||
self.lx = min(x1, x2)
|
||||
self.rx = max(x1, x2)
|
||||
self.ly = min(y1, y2)
|
||||
self.ry = max(y1, y2)
|
||||
|
||||
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 x >= self.lx and x <= self.rx and y >= self.ly and y <= self.ry
|
||||
|
||||
def __iter__(self):
|
||||
return iter([(x, y) for y in range(self.ly, self.ry+1) for x in range(self.lx, self.rx+1)])
|
||||
|
||||
def __repr__(self):
|
||||
return "FilledRectLocus({}, {}, {}, {})".format(self.lx, self.ly, self.rx, self.ry)
|
||||
|
||||
class CircleLocus(Locus):
|
||||
"""A locus that defines the outline of a circle."""
|
||||
|
||||
def __init__(self, x, y, radius, **kwargs):
|
||||
self.x = x + 0.5
|
||||
self.y = y + 0.5
|
||||
self.r = radius
|
||||
|
||||
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
|
||||
if self.x == x + 0.5 and (y == _mt.floor(self.y + self.r) or y == _mt.floor(self.y - self.r)):
|
||||
return True
|
||||
elif self.y == y + 0.5 and (x == _mt.floor(self.x + self.r) or x == _mt.floor(self.x - self.r)):
|
||||
return True
|
||||
else:
|
||||
# Edge case: very small circle
|
||||
if self.r <= _mt.sqrt(2) * 0.5:
|
||||
# In this case, the circle is small enough that the previous
|
||||
# checks would have cought every possible tile other than the center.
|
||||
return x == _mt.floor(self.x) and y == _mt.floor(self.y)
|
||||
if y + 0.5 > self.y:
|
||||
if x + 0.5 > self.x:
|
||||
return _mt.sqrt((x - self.x)**2 + (y - self.y)**2) < self.r and _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) > self.r
|
||||
else:
|
||||
return _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) < self.r and _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) > self.r
|
||||
else:
|
||||
if x + 0.5 > self.x:
|
||||
return _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) < self.r and _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) > self.r
|
||||
else:
|
||||
return _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) < self.r and _mt.sqrt((x - self.x)**2 + (y - self.y)**2) > self.r
|
||||
|
||||
def __iter__(self):
|
||||
# Edge case: very small circle
|
||||
if self.r <= _mt.sqrt(2) * 0.5:
|
||||
if self.r > 0.5:
|
||||
ret = ((_mt.floor(self.x), _mt.floor(self.y)),
|
||||
(_mt.floor(self.x) + 1, _mt.floor(self.y)),
|
||||
(_mt.floor(self.x), _mt.floor(self.y) + 1),
|
||||
(_mt.floor(self.x) - 1, _mt.floor(self.y)),
|
||||
(_mt.floor(self.x), _mt.floor(self.y) - 1))
|
||||
return iter(ret)
|
||||
else:
|
||||
return iter(((_mt.floor(self.x), _mt.floor(self.y)),))
|
||||
else:
|
||||
f = lambda z: _mt.sqrt(self.r * self.r - (z - self.x)**2) + self.y
|
||||
# The topmost square: the 'keystone' if you will.
|
||||
ret = [(_mt.floor(self.x), _mt.floor(self.y + self.r))]
|
||||
# All squares between the keystone and the rightmost column.
|
||||
for x in range(_mt.ceil(self.x), _mt.floor(self.x + self.r)):
|
||||
ly = _mt.floor(f(x))
|
||||
ry = _mt.floor(f(x+1)) - 1
|
||||
for y in range(ly, ry, -1):
|
||||
ret.append((x, y))
|
||||
# The last column, minus the bottom square
|
||||
x = _mt.floor(self.x + self.r)
|
||||
for y in range(_mt.floor(f(x)), _mt.floor(self.y), -1):
|
||||
ret.append((x, y))
|
||||
# Finish the circle by copying the coordinates we already have.
|
||||
ret = ret + [(_mt.floor(self.x) + (y - _mt.floor(self.y)), _mt.floor(self.y) - (x - _mt.floor(self.x))) for x, y in ret]
|
||||
return iter(ret + [(_mt.floor(self.x) - (x - _mt.floor(self.x)), _mt.floor(self.y) - (y - _mt.floor(self.y))) for x, y in ret])
|
||||
|
||||
def __repr__(self):
|
||||
return "CircleLocus({}, {}, {})".format(_mt.floor(self.x), _mt.floor(self.y), self.r)
|
||||
|
||||
class FilledCircleLocus(Locus):
|
||||
"""A locus that defines all points within a circle."""
|
||||
|
||||
def __init__(self, x, y, radius, **kwargs):
|
||||
self.x = x + 0.5
|
||||
self.y = y + 0.5
|
||||
self.r = radius
|
||||
|
||||
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
|
||||
if self.x == x + 0.5 and (y <= _mt.floor(self.y + self.r) and y >= _mt.floor(self.y - self.r)):
|
||||
return True
|
||||
elif self.y == y + 0.5 and (x <= _mt.floor(self.x + self.r) and x >= _mt.floor(self.x - self.r)):
|
||||
return True
|
||||
else:
|
||||
# Edge case: very small circle
|
||||
if self.r <= _mt.sqrt(2) * 0.5:
|
||||
# In this case, the circle is small enough that the previous
|
||||
# checks would have cought every possible tile other than the center.
|
||||
return x == _mt.floor(self.x) and y == _mt.floor(self.y)
|
||||
if y + 0.5 > self.y:
|
||||
if x + 0.5 > self.x:
|
||||
return _mt.sqrt((x - self.x)**2 + (y - self.y)**2) < self.r
|
||||
else:
|
||||
return _mt.sqrt((x+1 - self.x)**2 + (y - self.y)**2) < self.r
|
||||
else:
|
||||
if x + 0.5 > self.x:
|
||||
return _mt.sqrt((x - self.x)**2 + (y+1 - self.y)**2) < self.r
|
||||
else:
|
||||
return _mt.sqrt((x+1 - self.x)**2 + (y+1 - self.y)**2) < self.r
|
||||
|
||||
def __iter__(self):
|
||||
# Edge case: very small circle
|
||||
if self.r <= _mt.sqrt(2) * 0.5:
|
||||
if self.r > 0.5:
|
||||
ret = ((_mt.floor(self.x), _mt.floor(self.y)),
|
||||
(_mt.floor(self.x) + 1, _mt.floor(self.y)),
|
||||
(_mt.floor(self.x), _mt.floor(self.y) + 1),
|
||||
(_mt.floor(self.x) - 1, _mt.floor(self.y)),
|
||||
(_mt.floor(self.x), _mt.floor(self.y) - 1))
|
||||
return iter(ret)
|
||||
else:
|
||||
return iter(((_mt.floor(self.x), _mt.floor(self.y)),))
|
||||
else:
|
||||
f = lambda z: _mt.sqrt(self.r * self.r - (z - self.x)**2) + self.y
|
||||
# The topmost square: the 'keystone' if you will.
|
||||
ret = [(_mt.floor(self.x), y) for y in range(_mt.floor(self.y + self.r), _mt.floor(self.y), -1)]
|
||||
# All squares between the keystone and the rightmost column.
|
||||
ry = _mt.floor(self.y)
|
||||
for x in range(_mt.ceil(self.x), _mt.floor(self.x + self.r)):
|
||||
ly = _mt.floor(f(x))
|
||||
for y in range(ly, ry, -1):
|
||||
ret.append((x, y))
|
||||
# The last column, minus the bottom square
|
||||
x = _mt.floor(self.x + self.r)
|
||||
for y in range(_mt.floor(f(x)), _mt.floor(self.y), -1):
|
||||
ret.append((x, y))
|
||||
# Finish the circle by copying the coordinates we already have.
|
||||
ret = ret + [(_mt.floor(self.x) + (y - _mt.floor(self.y)), _mt.floor(self.y) - (x - _mt.floor(self.x))) for x, y in ret]
|
||||
return iter(ret + [(_mt.floor(self.x) - (x - _mt.floor(self.x)), _mt.floor(self.y) - (y - _mt.floor(self.y))) for x, y in ret] + [(_mt.floor(self.x), _mt.floor(self.y))])
|
||||
|
||||
def __repr__(self):
|
||||
return "FilledCircleLocus({}, {}, {})".format(_mt.floor(self.x), _mt.floor(self.y), self.r)
|
||||
|
||||
class SetLocus(set, Locus):
|
||||
"""A locus that defines a set of given arbitrary points."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SetLocus, self).__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "SetLocus({})".format(tuple(self))
|
Loading…
Add table
Add a link
Reference in a new issue