from plomrogue.errors import ArgError
+import collections
-class MapBase:
+class YX(collections.namedtuple('YX', ('y', 'x'))):
- def __init__(self, size=(0, 0)):
- self.size = size
- self.terrain = '?'*self.size_i
+ def __add__(self, other):
+ return YX(self.y + other.y, self.x + other.x)
- @property
- def size_i(self):
- return self.size[0] * self.size[1]
+ def __sub__(self, other):
+ return YX(self.y - other.y, self.x - other.x)
+
+ def __str__(self):
+ return 'Y:%s,X:%s' % (self.y, self.x)
- def set_line(self, y, line):
- height_map = self.size[0]
- width_map = self.size[1]
- if y >= height_map:
- raise ArgError('too large row number %s' % y)
- width_line = len(line)
- if width_line > width_map:
- raise ArgError('too large map line width %s' % width_line)
- self.terrain = self.terrain[:y * width_map] + line +\
- self.terrain[(y + 1) * width_map:]
- def get_position_index(self, yx):
- return yx[0] * self.size[1] + yx[1]
+class Map:
-class Map(MapBase):
+ def __init__(self, size=YX(0, 0)):
+ self.size = size
+ self.terrain = '?'*self.size_i
def __getitem__(self, yx):
return self.terrain[self.get_position_index(yx)]
def __iter__(self):
"""Iterate over YX position coordinates."""
- for y in range(self.size[0]):
- for x in range(self.size[1]):
- yield [y, x]
+ for y in range(self.size.y):
+ for x in range(self.size.x):
+ yield YX(y, x)
+
+ @property
+ def size_i(self):
+ return self.size.y * self.size.x
+
+ def set_line(self, y, line):
+ height_map = self.size.y
+ width_map = self.size.x
+ if y >= height_map:
+ raise ArgError('too large row number %s' % y)
+ width_line = len(line)
+ if width_line > width_map:
+ raise ArgError('too large map line width %s' % width_line)
+ self.terrain = self.terrain[:y * width_map] + line +\
+ self.terrain[(y + 1) * width_map:]
+
+ def get_position_index(self, yx):
+ return yx.y * self.size.x + yx.x
def lines(self):
- width = self.size[1]
- for y in range(self.size[0]):
+ width = self.size.x
+ for y in range(self.size.y):
yield (y, self.terrain[y * width:(y + 1) * width])
def get_fov_map(self, yx):
def get_neighbors(self, pos):
neighbors = {}
- pos = tuple(pos)
if not hasattr(self, 'neighbors_to'):
self.neighbors_to = {}
if pos in self.neighbors_to:
return self.neighbors_to[pos]
for direction in self.get_directions():
neighbors[direction] = None
- try:
- neighbors[direction] = self.move(pos, direction)
- except GameError:
- pass
+ neighbor_pos = self.move(pos, direction)
+ if neighbor_pos:
+ neighbors[direction] = neighbor_pos
self.neighbors_to[pos] = neighbors
return neighbors
def move(self, start_pos, direction):
mover = getattr(self, 'move_' + direction)
new_pos = mover(start_pos)
- if new_pos[0] < 0 or new_pos[1] < 0 or \
- new_pos[0] >= self.size[0] or new_pos[1] >= self.size[1]:
- raise GameError('would move outside map bounds')
+ if new_pos.y < 0 or new_pos.x < 0 or \
+ new_pos.y >= self.size.y or new_pos.x >= self.size.x:
+ return None
return new_pos
class MapWithLeftRightMoves(Map):
def move_LEFT(self, start_pos):
- return [start_pos[0], start_pos[1] - 1]
+ return YX(start_pos.y, start_pos.x - 1)
def move_RIGHT(self, start_pos):
- return [start_pos[0], start_pos[1] + 1]
+ return YX(start_pos.y, start_pos.x + 1)
class MapSquare(MapWithLeftRightMoves):
def move_UP(self, start_pos):
- return [start_pos[0] - 1, start_pos[1]]
+ return YX(start_pos.y - 1, start_pos.x)
def move_DOWN(self, start_pos):
- return [start_pos[0] + 1, start_pos[1]]
+ return YX(start_pos.y + 1, start_pos.x)
self.fov_map_type = FovMapHex
def move_UPLEFT(self, start_pos):
- if start_pos[0] % 2 == 1:
- return [start_pos[0] - 1, start_pos[1] - 1]
+ if start_pos.y % 2 == 1:
+ return YX(start_pos.y - 1, start_pos.x - 1)
else:
- return [start_pos[0] - 1, start_pos[1]]
+ return YX(start_pos.y - 1, start_pos.x)
def move_UPRIGHT(self, start_pos):
- if start_pos[0] % 2 == 1:
- return [start_pos[0] - 1, start_pos[1]]
+ if start_pos.y % 2 == 1:
+ return YX(start_pos.y - 1, start_pos.x)
else:
- return [start_pos[0] - 1, start_pos[1] + 1]
+ return YX(start_pos.y - 1, start_pos.x + 1)
def move_DOWNLEFT(self, start_pos):
- if start_pos[0] % 2 == 1:
- return [start_pos[0] + 1, start_pos[1] - 1]
+ if start_pos.y % 2 == 1:
+ return YX(start_pos.y + 1, start_pos.x - 1)
else:
- return [start_pos[0] + 1, start_pos[1]]
+ return YX(start_pos.y + 1, start_pos.x)
def move_DOWNRIGHT(self, start_pos):
- if start_pos[0] % 2 == 1:
- return [start_pos[0] + 1, start_pos[1]]
+ if start_pos.y % 2 == 1:
+ return YX(start_pos.y + 1, start_pos.x)
else:
- return [start_pos[0] + 1, start_pos[1] + 1]
+ return YX(start_pos.y + 1, start_pos.x + 1)
def __init__(self, source_map, yx):
self.source_map = source_map
self.size = self.source_map.size
+ self.fov_radius = (self.size.y / 2) - 0.5
self.terrain = '?' * self.size_i
self[yx] = '.'
self.shadow_cones = []
def basic_circle_out_move(self, pos, direction):
"""Move position pos into direction. Return whether still in map."""
mover = getattr(self, 'move_' + direction)
- pos[:] = mover(pos)
- if pos[0] < 0 or pos[1] < 0 or \
- pos[0] >= self.size[0] or pos[1] >= self.size[1]:
- return False
- return True
+ pos = mover(pos)
+ if pos.y < 0 or pos.x < 0 or \
+ pos.y >= self.size.y or pos.x >= self.size.x:
+ return pos, False
+ return pos, True
def circle_out(self, yx, f):
# Optimization potential: Precalculate movement positions. (How to check
# would lose shade growth through hexes at shade borders.)
# TODO: Start circling only in earliest obstacle distance.
+ # TODO: get rid of circle_in_map logic
circle_in_map = True
distance = 1
- yx = yx[:]
+ yx = YX(yx.y, yx.x)
#print('DEBUG CIRCLE_OUT', yx)
while circle_in_map:
+ if distance > self.fov_radius:
+ break
circle_in_map = False
- self.basic_circle_out_move(yx, 'RIGHT')
+ yx, _ = self.basic_circle_out_move(yx, 'RIGHT')
for dir_i in range(len(self.circle_out_directions)):
for dir_progress in range(distance):
direction = self.circle_out_directions[dir_i]
- if self.circle_out_move(yx, direction):
+ yx, test = self.circle_out_move(yx, direction)
+ if test:
f(yx, distance, dir_i, dir_progress)
circle_in_map = True
distance += 1