home · contact · privacy
Re-write mapping system to accomodate infinite map growth.
authorChristian Heller <c.heller@plomlompom.de>
Wed, 18 Nov 2020 02:37:32 +0000 (03:37 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Wed, 18 Nov 2020 02:37:32 +0000 (03:37 +0100)
plomrogue/commands.py
plomrogue/game.py
plomrogue/mapping.py
plomrogue/parser.py
plomrogue/tasks.py
plomrogue/things.py

index ed7d3ddeb2b041181a14fdd3ef29f09d50d9b3f1..249e1ab5e91d50a91a51ff940ba572936baf1cef 100644 (file)
@@ -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
         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])
     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]
         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)
     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_
     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])
 
 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')
         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 == ' ':
         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:
     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])
     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')
         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 == ' ':
         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:
     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'
 
     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
     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
     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])
 
 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)';
     annotation = '(unknown)';
-    if player.fov_test(source_yx):
+    if player.fov_test(big_yx, little_yx):
         annotation = '(none)';
         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'
 
     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(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_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 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_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
     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)
 
 def cmd_THING_NAME(game, thing_id, name):
     t = game.get_thing(thing_id)
index 0a33a56ef2e5bbd1426084fec67d4cb38351c9ff..3a33660da10dcae09b6a82fe95c24ba252e1668e 100755 (executable)
@@ -44,8 +44,8 @@ class Game(GameBase):
         self.tasks = {}
         self.thing_types = {}
         self.sessions = {}
         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 = {}
         self.map_control_passwords = {}
         self.annotations = {}
         self.portals = {}
@@ -62,6 +62,7 @@ class Game(GameBase):
             'o': 'sink',
             'O': 'toilet'
         }
             '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')
         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)
 
                 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:
         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])
         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(),
             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)
                                            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)
             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)
                 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):
         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,))
           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']:
           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)))
 
               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
     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 = {}
         self.annotations = {}
index 00b8b1d7a6771bd1ed6fcef34b835dd061ee38da..99924bc599bdf396c68115463fafbe4fba23eb99 100644 (file)
@@ -24,22 +24,29 @@ class MapGeometry():
 
     def get_directions(self):
         directions = []
 
     def get_directions(self):
         directions = []
+        prefix = 'move__'
         for name in dir(self):
         for name in dir(self):
-            if name[:5] == 'move_':
-                directions += [name[5:]]
+            if name[:len(prefix)] == prefix:
+                directions += [name[len(prefix):]]
         return directions
 
         return directions
 
-    def get_neighbors(self, pos):
+    def get_neighbors_yxyx(self, yxyx):
         neighbors = {}
         for direction in self.get_directions():
         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)
         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]
         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]
 
         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 \
         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
 
             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):
 
 
 
 class MapGeometryWithLeftRightMoves(MapGeometry):
 
-    def move_LEFT(self, start_pos):
+    def move__LEFT(self, start_pos):
         return YX(start_pos.y, start_pos.x - 1)
 
         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)
 
 
         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
     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):
 
     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
 
         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)
 
         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)
 
 
         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
     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):
 
     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
 
         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)
 
         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)
 
         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)
 
         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)
         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():
 
 
 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):
         self.terrain = '.' * self.size_i
 
     def __getitem__(self, yx):
@@ -152,23 +178,17 @@ class Map():
 
     def __iter__(self):
         """Iterate over YX position coordinates."""
 
     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)
 
                 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):
     @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):
 
     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)
         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):
                        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):
 
     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])
 
 
             yield (y, self.terrain[y * width:(y + 1) * width])
 
 
-
 class SourcedMap(Map):
 
 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.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
 
         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):
 
 
 class DijkstraMap(SourcedMap):
@@ -219,11 +248,8 @@ class DijkstraMap(SourcedMap):
         shrunk = True
         source_map_segment = ''
         for yx in self:
         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):
         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)
 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)
 
         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.
         # 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 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
                 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):
             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):
         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)
                 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
 
 
             distance += 1
 
 
@@ -363,7 +377,6 @@ class FovMap(SourcedMap):
 class FovMapHex(FovMap):
     circle_out_directions = ('DOWNLEFT', 'LEFT', 'UPLEFT',
                              'UPRIGHT', 'RIGHT', 'DOWNRIGHT')
 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)
 
     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'))
 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])
 
     def circle_out_move(self, yx, direction):
         yx = self.basic_circle_out_move(yx, direction[0])
index a1b56b571b650431d297badf3f0e4573da731359..d1307a97701d3ef5c5eee6ecc2c16cfcb8d94282 100644 (file)
@@ -45,16 +45,23 @@ class Parser:
         """Parse yx_string as yx_tuple, return result.
 
         The range_ argument may be 'nonneg' (non-negative, including
         """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):
 
         """
 
         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.')
                 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')]
                 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 + ':':
             elif tmpl == string_string:
                 args += [arg]
             elif tmpl[:len(string_string) + 1] == string_string + ':':
index ff1cc7ea613d721eec2ffa7c7368b48fa62117fc..9335aeddb0df450953a4ffb01bf4710142c9c150 100644 (file)
@@ -29,17 +29,15 @@ class Task_MOVE(Task):
     argtypes = 'string:direction'
 
     def get_move_target(self):
     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):
 
     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')
             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):
             raise PlayError('blocked by impassable tile')
 
     def do(self):
@@ -54,12 +52,14 @@ class Task_WRITE(Task):
     argtypes = 'string:char string'
 
     def check(self):
     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.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):
         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]] = '.'
 
 
 
 
 
 
index dfb536c27f8e6f9acb49b8e5ad2d60d16c66e27e..cedee68266583f9486dea676a0d67bd5bacb2378 100644 (file)
@@ -6,7 +6,7 @@ from plomrogue.mapping import YX
 class ThingBase:
     type_ = '?'
 
 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()
         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
         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
 
         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
 
         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 = ''
         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
             else:
                 visible_terrain += ' '
         return visible_terrain