From d9c9b5b7d5cac2469ac075010c4d729e1adf0cc4 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 18 Nov 2020 03:37:32 +0100 Subject: [PATCH 1/1] Re-write mapping system to accomodate infinite map growth. --- plomrogue/commands.py | 89 +++++++++++++---------- plomrogue/game.py | 67 +++++++++++------ plomrogue/mapping.py | 162 +++++++++++++++++++++++------------------- plomrogue/parser.py | 21 ++++-- plomrogue/tasks.py | 31 ++++---- plomrogue/things.py | 15 ++-- 6 files changed, 221 insertions(+), 164 deletions(-) diff --git a/plomrogue/commands.py b/plomrogue/commands.py index ed7d3dd..249e1ab 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -45,12 +45,11 @@ def cmd_ALL(game, msg, connection_id): raise GameError('need to be logged in for this') speaker = game.get_thing(game.sessions[connection_id]) largest_audible_distance = 20 - dijkstra_map_class = game.map_geometry.dijkstra_map_class - dijkstra_map = dijkstra_map_class(game.map, speaker.position, - largest_audible_distance) + dijkstra_map = DijkstraMap(game.maps, speaker.position, + largest_audible_distance, game.get_map) for c_id in game.sessions: listener = game.get_thing(game.sessions[c_id]) - target_yx = dijkstra_map.target_yx(listener.position, True) + target_yx = dijkstra_map.target_yx(*listener.position, True) if not target_yx: continue listener_distance = dijkstra_map[target_yx] @@ -72,7 +71,8 @@ def cmd_LOGIN(game, nick, connection_id): if connection_id in game.sessions: raise GameError('cannot log in twice') t = game.thing_types['Player'](game) - t.position = YX(game.map.size.y // 2, game.map.size.x // 2) + t.position = (YX(0,0), + YX(game.map_geometry.size.y // 2, game.map_geometry.size.x // 2)) game.things += [t] # TODO refactor into Thing.__init__? t.player_char = game.get_next_player_char() game.sessions[connection_id] = t.id_ @@ -125,87 +125,100 @@ cmd_TURN.argtypes = 'int:nonneg' def cmd_ANNOTATE(game, yx, msg, pw, connection_id): player = game.get_thing(game.sessions[connection_id]) - source_yx = player.fov_stencil.source_yx(yx) - if not player.fov_test(source_yx): + big_yx, little_yx = player.fov_stencil.source_yxyx(yx) + if not player.fov_test(big_yx, little_yx): raise GameError('cannot annotate tile outside field of view') - if not game.can_do_tile_with_pw(source_yx, pw): + if not game.can_do_tile_with_pw(big_yx, little_yx, pw): raise GameError('wrong password for tile') if msg == ' ': - if source_yx in game.annotations: - del game.annotations[source_yx] + if big_yx in game.annotations: + if little_yx in game.annotations[big_yx]: + del game.annotations[big_yx][little_yx] else: - game.annotations[source_yx] = msg + if not big_yx in game.annotations: + game.annotations[big_yx] = {} + game.annotations[big_yx][little_yx] = msg game.changed = True cmd_ANNOTATE.argtypes = 'yx_tuple:nonneg string string' def cmd_PORTAL(game, yx, msg, pw, connection_id): player = game.get_thing(game.sessions[connection_id]) - source_yx = player.fov_stencil.source_yx(yx) - if not player.fov_test(source_yx): + big_yx, little_yx = player.fov_stencil.source_yxyx(yx) + if not player.fov_test(big_yx, little_yx): raise GameError('cannot edit portal on tile outside field of view') - if not game.can_do_tile_with_pw(source_yx, pw): + if not game.can_do_tile_with_pw(big_yx, little_yx, pw): raise GameError('wrong password for tile') if msg == ' ': - if source_yx in game.portals: - del game.portals[source_yx] + if big_yx in game.portals: + if little_yx in game.portals[big_yx]: + del game.portals[big_yx][little_xy] else: - game.portals[source_yx] = msg + if not big_yx in game.portals: + game.portals[big_yx] = {} + game.portals[big_yx][little_yx] = msg game.changed = True cmd_PORTAL.argtypes = 'yx_tuple:nonneg string string' -def cmd_GOD_ANNOTATE(game, yx, msg): - game.annotations[yx] = msg +def cmd_GOD_ANNOTATE(game, big_yx, little_yx, msg): + if not big_yx in game.annotations: + game.annotations[big_yx] = {} + game.annotations[big_yx][little_yx] = msg game.changed = True -cmd_GOD_ANNOTATE.argtypes = 'yx_tuple:nonneg string' +cmd_GOD_ANNOTATE.argtypes = 'yx_tuple yx_tuple:nonneg string' -def cmd_GOD_PORTAL(game, yx, msg): - game.portals[yx] = msg +def cmd_GOD_PORTAL(game, big_yx, little_yx, msg): + if not big_yx in game.portals: + game.portals[big_yx] = {} + game.portals[big_yx][little_yx] = msg game.changed = True -cmd_GOD_PORTAL.argtypes = 'yx_tuple:nonneg string' +cmd_GOD_PORTAL.argtypes = 'yx_tuple yx_tuple:nonneg string' def cmd_GET_ANNOTATION(game, yx, connection_id): player = game.get_thing(game.sessions[connection_id]) - source_yx = player.fov_stencil.source_yx(yx) + big_yx, little_yx = player.fov_stencil.source_yxyx(yx) annotation = '(unknown)'; - if player.fov_test(source_yx): + if player.fov_test(big_yx, little_yx): annotation = '(none)'; - if source_yx in game.annotations: - annotation = game.annotations[source_yx] + if big_yx in game.annotations: + if little_yx in game.annotations[big_yx]: + annotation = game.annotations[big_yx][little_yx] game.io.send('ANNOTATION %s %s' % (yx, quote(annotation))) cmd_GET_ANNOTATION.argtypes = 'yx_tuple:nonneg' -def cmd_MAP_LINE(game, y, line): - game.map.set_line(y, line) -cmd_MAP_LINE.argtypes = 'int:nonneg string' +def cmd_MAP_LINE(game, big_yx, y, line): + map_ = game.get_map(big_yx) + map_.set_line(y, line) +cmd_MAP_LINE.argtypes = 'yx_tuple int:nonneg string' def cmd_MAP(game, geometry, size): map_geometry_class = globals()['MapGeometry' + geometry] game.new_world(map_geometry_class(size)) cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos' -def cmd_MAP_CONTROL_LINE(game, y, line): - game.map_control.set_line(y, line) -cmd_MAP_CONTROL_LINE.argtypes = 'int:nonneg string' +def cmd_MAP_CONTROL_LINE(game, big_yx, y, line): + map_control = game.get_map(big_yx, 'control') + map_control.set_line(y, line) +cmd_MAP_CONTROL_LINE.argtypes = 'yx_tuple int:nonneg string' def cmd_MAP_CONTROL_PW(game, tile_class, password): game.map_control_passwords[tile_class] = password cmd_MAP_CONTROL_PW.argtypes = 'char string' -def cmd_THING(game, yx, thing_type, thing_id): +def cmd_THING(game, big_yx, little_yx, thing_type, thing_id): if not thing_type in game.thing_types: raise GameError('illegal thing type %s' % thing_type) - if not game.map.inside(yx): - raise GameError('illegal position %s' % yx) + map_ = game.get_map(big_yx) t_old = None if thing_id > 0: t_old = game.get_thing(thing_id) - t_new = game.thing_types[thing_type](game, id_=thing_id, position=yx) + t_new = game.thing_types[thing_type](game, id_=thing_id, position=(big_yx, + little_yx)) if t_old: game.things[game.things.index(t_old)] = t_new else: game.things += [t_new] game.changed = True -cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg' +cmd_THING.argtypes = 'yx_tuple yx_tuple:nonneg string:thing_type int:nonneg' def cmd_THING_NAME(game, thing_id, name): t = game.get_thing(thing_id) diff --git a/plomrogue/game.py b/plomrogue/game.py index 0a33a56..3a33660 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -44,8 +44,8 @@ class Game(GameBase): self.tasks = {} self.thing_types = {} self.sessions = {} - self.map = Map(self.map_geometry.size) - self.map_control = Map(self.map_geometry.size) + self.maps = {} + self.map_controls = {} self.map_control_passwords = {} self.annotations = {} self.portals = {} @@ -62,6 +62,7 @@ class Game(GameBase): 'o': 'sink', 'O': 'toilet' } + self.new_world(self.map_geometry) if os.path.exists(self.io.save_file): if not os.path.isfile(self.io.save_file): raise GameError('save file path refers to non-file') @@ -81,8 +82,9 @@ class Game(GameBase): print("FILE INPUT LINE %5s: %s" % (i, line), end='') self.io.handle_input(line, god_mode=True) - def can_do_tile_with_pw(self, yx, pw): - tile_class = self.map_control[yx] + def can_do_tile_with_pw(self, big_yx, little_yx, pw): + map_control = self.get_map(big_yx) + tile_class = map_control[little_yx] if tile_class in self.map_control_passwords: tile_pw = self.map_control_passwords[tile_class] if pw != tile_pw: @@ -110,24 +112,27 @@ class Game(GameBase): self.io.send('TURN ' + str(self.turn)) for c_id in self.sessions: player = self.get_thing(self.sessions[c_id]) - visible_terrain = player.fov_stencil_map(self.map) + visible_terrain = player.fov_stencil_map() self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id) self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(), - player.fov_stencil.size, + player.fov_stencil.geometry.size, quote(visible_terrain)), c_id) - visible_control = player.fov_stencil_map(self.map_control) + visible_control = player.fov_stencil_map('control') self.io.send('MAP_CONTROL %s' % quote(visible_control), c_id) - for t in [t for t in self.things if player.fov_test(t.position)]: - target_yx = player.fov_stencil.target_yx(t.position) + for t in [t for t in self.things if player.fov_test(*t.position)]: + target_yx = player.fov_stencil.target_yx(*t.position) self.io.send('THING %s %s %s' % (target_yx, t.type_, t.id_), c_id) if hasattr(t, 'name'): self.io.send('THING_NAME %s %s' % (t.id_, quote(t.name)), c_id) if hasattr(t, 'player_char'): self.io.send('THING_CHAR %s %s' % (t.id_, quote(t.player_char)), c_id) - for yx in [yx for yx in self.portals if player.fov_test(yx)]: - self.io.send('PORTAL %s %s' % (player.fov_stencil.target_yx(yx), - quote(self.portals[yx])), c_id) + for big_yx in self.portals: + for little_yx in [little_yx for little_yx in self.portals[big_yx] + if player.fov_test(big_yx, little_yx)]: + target_yx = player.fov_stencil.target_yx(big_yx, little_yx) + portal = self.portals[big_yx][little_yx] + self.io.send('PORTAL %s %s' % (target_yx, quote(portal)), c_id) self.io.send('GAME_STATE_COMPLETE') def run_tick(self): @@ -218,24 +223,40 @@ class Game(GameBase): write(f, 'TURN %s' % self.turn) map_geometry_shape = self.get_map_geometry_shape() write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,)) - for y, line in self.map.lines(): - write(f, 'MAP_LINE %5s %s' % (y, quote(line))) - for yx in self.annotations: - write(f, 'GOD_ANNOTATE %s %s' % (yx, quote(self.annotations[yx]))) - for yx in self.portals: - write(f, 'GOD_PORTAL %s %s' % (yx, quote(self.portals[yx]))) - for y, line in self.map_control.lines(): - write(f, 'MAP_CONTROL_LINE %5s %s' % (y, quote(line))) + for yx in self.maps: + for y, line in self.maps[yx].lines(): + write(f, 'MAP_LINE %s %5s %s' % (yx, y, quote(line))) + for big_yx in self.annotations: + for little_yx in self.annotations[big_yx]: + write(f, 'GOD_ANNOTATE %s %s %s' % + (big_yx, little_yx, quote(self.annotations[big_yx][little_yx]))) + for big_yx in self.portals: + for little_yx in self.portals[big_yx]: + write(f, 'GOD_PORTAL %s %s %s' % (big_yx, little_yx, + quote(self.portals[big_yx][little_yx]))) + for yx in self.map_controls: + for y, line in self.map_controls[yx].lines(): + write(f, 'MAP_CONTROL_LINE %s %5s %s' % (yx, y, quote(line))) for tile_class in self.map_control_passwords: write(f, 'MAP_CONTROL_PW %s %s' % (tile_class, self.map_control_passwords[tile_class])) for t in [t for t in self.things if not t.type_ == 'Player']: - write(f, 'THING %s %s %s' % (t.position, t.type_, t.id_)) + write(f, 'THING %s %s %s %s' % (t.position[0], + t.position[1], t.type_, t.id_)) if hasattr(t, 'name'): write(f, 'THING_NAME %s %s' % (t.id_, quote(t.name))) + def get_map(self, big_yx, type_='normal'): + if type_ == 'normal': + maps = self.maps + elif type_ == 'control': + maps = self.map_controls + if not big_yx in maps: + maps[big_yx] = Map(self.map_geometry) + return maps[big_yx] + def new_world(self, map_geometry): self.map_geometry = map_geometry - self.map = Map(self.map_geometry.size) - self.map_control = Map(self.map_geometry.size) + self.maps[YX(0,0)] = Map(self.map_geometry) + self.map_controls[YX(0,0)] = Map(self.map_geometry) self.annotations = {} diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index 00b8b1d..99924bc 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -24,22 +24,29 @@ class MapGeometry(): def get_directions(self): directions = [] + prefix = 'move__' for name in dir(self): - if name[:5] == 'move_': - directions += [name[5:]] + if name[:len(prefix)] == prefix: + directions += [name[len(prefix):]] return directions - def get_neighbors(self, pos): + def get_neighbors_yxyx(self, yxyx): neighbors = {} for direction in self.get_directions(): - neighbors[direction] = self.move(pos, direction) + neighbors[direction] = self.move_yxyx(yxyx, direction) + return neighbors + + def get_neighbors_yx(self, pos): + neighbors = {} + for direction in self.get_directions(): + neighbors[direction] = self.move_yx(pos, direction) return neighbors def get_neighbors_i(self, i): if i in self.neighbors_i: return self.neighbors_i[i] pos = YX(i // self.size.x, i % self.size.x) - neighbors_pos = self.get_neighbors(pos) + neighbors_pos = self.get_neighbors_yx(pos) neighbors_i = {} for direction in neighbors_pos: pos = neighbors_pos[direction] @@ -50,22 +57,41 @@ class MapGeometry(): self.neighbors_i[i] = neighbors_i return self.neighbors_i[i] - def move(self, start_pos, direction): - mover = getattr(self, 'move_' + direction) - target = mover(start_pos) + def move_yx(self, start_yx, direction, check=True): + mover = getattr(self, 'move__' + direction) + target = mover(start_yx) + # TODO refactor with SourcedMap.inside? if target.y < 0 or target.x < 0 or \ - target.y >= self.size.y or target.x >= self.size.x: + target.y >= self.size.y or target.x >= self.size.x: return None return target + def move_yxyx(self, start_yxyx, direction): + mover = getattr(self, 'move__' + direction) + start_yx = self.undouble_yxyx(*start_yxyx) + target_yx = mover(start_yx) + return self.double_yx(target_yx) + + def double_yx(self, absolute_yx): + big_y = absolute_yx.y // self.size.y + little_y = absolute_yx.y % self.size.y + big_x = absolute_yx.x // self.size.x + little_x = absolute_yx.x % self.size.x + return YX(big_y, big_x), YX(little_y, little_x) + + def undouble_yxyx(self, big_yx, little_yx): + y = big_yx.y * self.size.y + little_yx.y + x = big_yx.x * self.size.x + little_yx.x + return YX(y, x) + class MapGeometryWithLeftRightMoves(MapGeometry): - def move_LEFT(self, start_pos): + def move__LEFT(self, start_pos): return YX(start_pos.y, start_pos.x - 1) - def move_RIGHT(self, start_pos): + def move__RIGHT(self, start_pos): return YX(start_pos.y, start_pos.x + 1) @@ -75,18 +101,18 @@ class MapGeometrySquare(MapGeometryWithLeftRightMoves): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fov_map_class = FovMapSquare - self.dijkstra_map_class = DijkstraMapSquare def define_segment(self, source_center, radius): + source_center = self.undouble_yxyx(*source_center) size = YX(2 * radius + 1, 2 * radius + 1) offset = YX(source_center.y - radius, source_center.x - radius) center = YX(radius, radius) return size, offset, center - def move_UP(self, start_pos): + def move__UP(self, start_pos): return YX(start_pos.y - 1, start_pos.x) - def move_DOWN(self, start_pos): + def move__DOWN(self, start_pos): return YX(start_pos.y + 1, start_pos.x) @@ -95,37 +121,37 @@ class MapGeometryHex(MapGeometryWithLeftRightMoves): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fov_map_class = FovMapHex - self.dijkstra_map_class = DijkstraMapHex def define_segment(self, source_center, radius): + source_center = self.undouble_yxyx(*source_center) indent = 1 if (source_center.y % 2) else 0 size = YX(2 * radius + 1 + indent, 2 * radius + 1) offset = YX(source_center.y - radius - indent, source_center.x - radius) center = YX(radius + indent, radius) return size, offset, center - def move_UPLEFT(self, start_pos): + def move__UPLEFT(self, start_pos): start_indented = start_pos.y % 2 if start_indented: return YX(start_pos.y - 1, start_pos.x) else: return YX(start_pos.y - 1, start_pos.x - 1) - def move_UPRIGHT(self, start_pos): + def move__UPRIGHT(self, start_pos): start_indented = start_pos.y % 2 if start_indented: return YX(start_pos.y - 1, start_pos.x + 1) else: return YX(start_pos.y - 1, start_pos.x) - def move_DOWNLEFT(self, start_pos): + def move__DOWNLEFT(self, start_pos): start_indented = start_pos.y % 2 if start_indented: return YX(start_pos.y + 1, start_pos.x) else: return YX(start_pos.y + 1, start_pos.x - 1) - def move_DOWNRIGHT(self, start_pos): + def move__DOWNRIGHT(self, start_pos): start_indented = start_pos.y % 2 if start_indented: return YX(start_pos.y + 1, start_pos.x + 1) @@ -136,8 +162,8 @@ class MapGeometryHex(MapGeometryWithLeftRightMoves): class Map(): - def __init__(self, map_size): - self.size = map_size + def __init__(self, map_geometry): + self.geometry = map_geometry self.terrain = '.' * self.size_i def __getitem__(self, yx): @@ -152,23 +178,17 @@ class Map(): def __iter__(self): """Iterate over YX position coordinates.""" - for y in range(self.size.y): - for x in range(self.size.x): + for y in range(self.geometry.size.y): + for x in range(self.geometry.size.x): yield YX(y, x) - # TODO: use this for more refactoring - def inside(self, yx): - if yx.y < 0 or yx.x < 0 or yx.y >= self.size.y or yx.x >= self.size.x: - return False - return True - @property def size_i(self): - return self.size.y * self.size.x + return self.geometry.size.y * self.geometry.size.x def set_line(self, y, line): - height_map = self.size.y - width_map = self.size.x + height_map = self.geometry.size.y + width_map = self.geometry.size.x if y >= height_map: raise ArgError('too large row number %s' % y) width_line = len(line) @@ -178,36 +198,45 @@ class Map(): self.terrain[(y + 1) * width_map:] def get_position_index(self, yx): - return yx.y * self.size.x + yx.x + return yx.y * self.geometry.size.x + yx.x def lines(self): - width = self.size.x - for y in range(self.size.y): + width = self.geometry.size.x + for y in range(self.geometry.size.y): yield (y, self.terrain[y * width:(y + 1) * width]) - class SourcedMap(Map): - def __init__(self, source_map, source_center, radius): - self.source_map = source_map + def __init__(self, source_maps, source_center, radius, get_map): + self.source_maps = source_maps self.radius = radius - self.size, self.offset, self.center = \ - self.geometry_class.define_segment(None, source_center, radius) - self.geometry = self.geometry_class(self.size) + example_map = get_map(YX(0,0)) + self.source_geometry = example_map.geometry + size, self.offset, self.center = \ + self.source_geometry.define_segment(source_center, radius) + self.geometry = self.source_geometry.__class__(size) + for yx in self: + big_yx, _ = self.source_yxyx(yx) + get_map(big_yx) - def source_yx(self, yx, check=False): - source_yx = yx + self.offset - if check and not self.source_map.inside(source_yx): - return False - return source_yx + def source_yxyx(self, yx): + absolute_yx = yx + self.offset + big_yx, little_yx = self.source_geometry.double_yx(absolute_yx) + return big_yx, little_yx - def target_yx(self, yx, check=False): - target_yx = yx - self.offset + def target_yx(self, big_yx, little_yx, check=False): + target_yx = self.source_geometry.undouble_yxyx(big_yx, little_yx) - self.offset if check and not self.inside(target_yx): return False return target_yx + def inside(self, yx): + if yx.y < 0 or yx.x < 0 or \ + yx.y >= self.geometry.size.y or yx.x >= self.geometry.size.x: + return False + return True + class DijkstraMap(SourcedMap): @@ -219,11 +248,8 @@ class DijkstraMap(SourcedMap): shrunk = True source_map_segment = '' for yx in self: - yx_in_source = self.source_yx(yx, True) - if yx_in_source: - source_map_segment += self.source_map[yx_in_source] - else: - source_map_segment += 'X' + big_yx, little_yx = self.source_yxyx(yx) + source_map_segment += self.source_maps[big_yx][little_yx] while shrunk: shrunk = False for i in range(self.size_i): @@ -248,31 +274,21 @@ class DijkstraMap(SourcedMap): -class DijkstraMapHex(DijkstraMap): - geometry_class = MapGeometryHex - - - -class DijkstraMapSquare(DijkstraMap): - geometry_class = MapGeometrySquare - - - class FovMap(SourcedMap): # TODO: player visibility asymmetrical (A can see B when B can't see A): # does this make sense, or not? def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.terrain = '?' * self.size.y * self.size.x + self.terrain = '?' * self.size_i #self.size.y * self.size.x self[self.center] = '.' self.shadow_cones = [] self.circle_out(self.center, self.shadow_process) - def throws_shadow(self, source_yx): - return self.source_map[source_yx] == 'X' + def throws_shadow(self, big_yx, little_yx): + return self.source_maps[big_yx][little_yx] == 'X' - def shadow_process(self, yx, source_yx, distance_to_center, dir_i, dir_progress): + def shadow_process(self, yx, source_yxyx, distance_to_center, dir_i, dir_progress): # Possible optimization: If no shadow_cones yet and self[yx] == '.', # skip all. CIRCLE = 360 # Since we'll float anyways, number is actually arbitrary. @@ -312,7 +328,7 @@ class FovMap(SourcedMap): if in_shadow_cone(cone): return self[yx] = '.' - if self.throws_shadow(source_yx): + if self.throws_shadow(*source_yxyx): unmerged = True while merge_cone(cone): unmerged = False @@ -333,8 +349,7 @@ class FovMap(SourcedMap): eval_cone([left_arm, right_arm]) def basic_circle_out_move(self, pos, direction): - #"""Move position pos into direction. Return whether still in map.""" - mover = getattr(self.geometry, 'move_' + direction) + mover = getattr(self.geometry, 'move__' + direction) return mover(pos) def circle_out(self, yx, f): @@ -353,9 +368,8 @@ class FovMap(SourcedMap): for dir_progress in range(distance): direction = self.circle_out_directions[dir_i] yx = self.circle_out_move(yx, direction) - source_yx = self.source_yx(yx, True) - if source_yx: - f(yx, source_yx, distance, dir_i, dir_progress) + source_yxyx = self.source_yxyx(yx) + f(yx, source_yxyx, distance, dir_i, dir_progress) distance += 1 @@ -363,7 +377,6 @@ class FovMap(SourcedMap): class FovMapHex(FovMap): circle_out_directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', 'UPRIGHT', 'RIGHT', 'DOWNRIGHT') - geometry_class = MapGeometryHex def circle_out_move(self, yx, direction): return self.basic_circle_out_move(yx, direction) @@ -373,7 +386,6 @@ class FovMapHex(FovMap): class FovMapSquare(FovMap): circle_out_directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'), ('UP', 'RIGHT'), ('RIGHT', 'DOWN')) - geometry_class = MapGeometrySquare def circle_out_move(self, yx, direction): yx = self.basic_circle_out_move(yx, direction[0]) diff --git a/plomrogue/parser.py b/plomrogue/parser.py index a1b56b5..d1307a9 100644 --- a/plomrogue/parser.py +++ b/plomrogue/parser.py @@ -45,16 +45,23 @@ class Parser: """Parse yx_string as yx_tuple, return result. The range_ argument may be 'nonneg' (non-negative, including - 0) or 'pos' (positive, excluding 0). + 0) or 'pos' (positive, excluding 0) or 'all'. """ def get_axis_position_from_argument(axis, token): - if len(token) < 3 or token[:2] != axis + ':' or \ - not (token[2:].isdigit() or token[2] == '-'): - raise ArgError('Non-int arg for ' + axis + ' position.') - n = int(token[2:]) - if n < 1 and range_ == 'pos': + if token[:2] != axis + ':': + raise ArgError('invalid YX tuple formatting') + n_string = token[2:] + if n_string.strip() != n_string: + raise ArgError('invalid YX tuple formatting') + try: + n = int(n_string) + except ValueError: + raise ArgError('non-int value for ' + axis + ' position') + if range_ == 'all': + return n + if n < 1 and range == 'pos': raise ArgError('Arg for ' + axis + ' position < 1.') elif n < 0 and range_ == 'nonneg': raise ArgError('Arg for ' + axis + ' position < 0.') @@ -120,6 +127,8 @@ class Parser: args += [self.parse_yx_tuple(arg, 'nonneg')] elif tmpl == 'yx_tuple:pos': args += [self.parse_yx_tuple(arg, 'pos')] + elif tmpl == 'yx_tuple': + args += [self.parse_yx_tuple(arg, 'all')] elif tmpl == string_string: args += [arg] elif tmpl[:len(string_string) + 1] == string_string + ':': diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index ff1cc7e..9335aed 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -29,17 +29,15 @@ class Task_MOVE(Task): argtypes = 'string:direction' def get_move_target(self): - return self.thing.game.map_geometry.move(self.thing.position, - self.args[0]) + return self.thing.game.map_geometry.move_yxyx(self.thing.position, + self.args[0]) def check(self): - test_pos = self.get_move_target() - if test_pos is None: - raise PlayError('would move out of map') - elif test_pos in [t.position for t in self.thing.game.things - if t.blocking]: + test_yxyx = self.get_move_target() + if test_yxyx in [t.position for t in self.thing.game.things + if t.blocking]: raise PlayError('blocked by other thing') - elif self.thing.game.map[test_pos] != '.': + elif self.thing.game.maps[test_yxyx[0]][test_yxyx[1]] != '.': raise PlayError('blocked by impassable tile') def do(self): @@ -54,12 +52,14 @@ class Task_WRITE(Task): argtypes = 'string:char string' def check(self): - if not self.thing.game.can_do_tile_with_pw(self.thing.position, + if not self.thing.game.can_do_tile_with_pw(*self.thing.position, self.args[1]): raise GameError('wrong password for tile') def do(self): - self.thing.game.map[self.thing.position] = self.args[0] + big_yx = self.thing.position[0] + little_yx = self.thing.position[1] + self.thing.game.maps[big_yx][little_yx] = self.args[0] @@ -71,12 +71,11 @@ class Task_FLATTEN_SURROUNDINGS(Task): pass def do(self): - for yx in[self.thing.position] + \ - list(self.thing.game.map_geometry.get_neighbors(self.thing.position).values()): - if yx is not None: - if not self.thing.game.can_do_tile_with_pw(yx, self.args[0]): - continue - self.thing.game.map[yx] = '.' + for yxyx in[self.thing.position] + \ + list(self.thing.game.map_geometry.get_neighbors_yxyx(self.thing.position).values()): + if not self.thing.game.can_do_tile_with_pw(*yxyx, self.args[0]): + continue + self.thing.game.maps[yxyx[0]][yxyx[1]] = '.' diff --git a/plomrogue/things.py b/plomrogue/things.py index dfb536c..cedee68 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -6,7 +6,7 @@ from plomrogue.mapping import YX class ThingBase: type_ = '?' - def __init__(self, game, id_=0, position=(YX(0,0))): + def __init__(self, game, id_=0, position=(YX(0,0),YX(0,0))): self.game = game if id_ == 0: self.id_ = self.game.new_thing_id() @@ -94,21 +94,24 @@ class ThingAnimate(Thing): if self._fov: return self._fov fov_map_class = self.game.map_geometry.fov_map_class - self._fov = fov_map_class(self.game.map, self.position, 12) + self._fov = fov_map_class(self.game.maps, self.position, 12, + self.game.get_map) return self._fov - def fov_test(self, yx): - test_position = self.fov_stencil.target_yx(yx) + def fov_test(self, big_yx, little_yx): + test_position = self.fov_stencil.target_yx(big_yx, little_yx) if self.fov_stencil.inside(test_position): if self.fov_stencil[test_position] == '.': return True return False - def fov_stencil_map(self, map): + def fov_stencil_map(self, map_type='normal'): visible_terrain = '' for yx in self.fov_stencil: if self.fov_stencil[yx] == '.': - visible_terrain += map[self.fov_stencil.source_yx(yx)] + big_yx, little_yx = self.fov_stencil.source_yxyx(yx) + map_ = self.game.get_map(big_yx, map_type) + visible_terrain += map_[little_yx] else: visible_terrain += ' ' return visible_terrain -- 2.30.2