home · contact · privacy
Prepare map logic extension, limit FOV distance.
authorChristian Heller <c.heller@plomlompom.de>
Thu, 25 Apr 2019 21:47:15 +0000 (23:47 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Thu, 25 Apr 2019 21:47:15 +0000 (23:47 +0200)
new/example_client.py
new/plomrogue/commands.py
new/plomrogue/game.py
new/plomrogue/mapping.py
new/plomrogue/parser.py
new/plomrogue/tasks.py
new/plomrogue/things.py

index 675d0a20ed01f4e1eb0d1a3a6aa9c9259c3167b2..6816f0eaf5a004885e569849b2e892387ed49ec3 100755 (executable)
@@ -58,9 +58,14 @@ class ClientMap(MapHex):
             return ''.join(map_view_chars).split('\n')
 
         map_lines = map_cells_to_lines(map_cells)
-        self.y_cut(map_lines, center[0], size[0])
+        if len(map_lines) % 2 == 0:
+            map_lines = map_lines[1:]
+        else:
+            for i in range(len(map_lines)):
+                map_lines[i] = '0' + map_lines[i]
+        self.y_cut(map_lines, center[1][0], size[0])
         map_width = self.size[1] * 2 + 1
-        self.x_cut(map_lines, center[1] * 2, size[1], map_width)
+        self.x_cut(map_lines, center[1][1] * 2, size[1], map_width)
         return map_lines
 
 
@@ -73,13 +78,13 @@ class World(WorldBase):
         on any update, even before we actually receive map data.
         """
         super().__init__(*args, **kwargs)
-        self.map_ = ClientMap()
+        self.maps = {(0,0): ClientMap()}
         self.player_inventory = []
         self.player_id = 0
         self.pickable_items = []
 
-    def new_map(self, yx):
-        self.map_ = ClientMap(yx)
+    def new_map(self, map_pos, size):
+        self.maps[map_pos] = ClientMap(size)
 
     @property
     def player(self):
@@ -107,7 +112,7 @@ cmd_TURN.argtypes = 'int:nonneg'
 
 
 def cmd_VISIBLE_MAP_LINE(game, y, terrain_line):
-    game.world.map_.set_line(y, terrain_line)
+    game.world.maps[(0,0)].set_line(y, terrain_line)
 cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string'
 
 
@@ -316,9 +321,9 @@ class DescriptorWidget(TextLinesWidget):
 
     def get_text_lines(self):
         lines = []
-        pos_i = self.tui.game.world.map_.\
-                get_position_index(self.tui.examiner_position)
-        terrain = self.tui.game.world.map_.terrain[pos_i]
+        pos_i = self.tui.game.world.maps[(0,0)].\
+                get_position_index(self.tui.examiner_position[1])
+        terrain = self.tui.game.world.maps[(0,0)].terrain[pos_i]
         lines = [terrain]
         for t in self.tui.game.world.things_at_pos(self.tui.examiner_position):
             lines += [t.type_]
@@ -381,9 +386,12 @@ class MapWidget(Widget):
     def draw(self):
 
         def annotated_terrain():
-            terrain_as_list = list(self.tui.game.world.map_.terrain[:])
+            terrain_as_list = list(self.tui.game.world.maps[(0,0)].terrain[:])
             for t in self.tui.game.world.things:
-                pos_i = self.tui.game.world.map_.get_position_index(t.position)
+                if t.id_ in self.tui.game.world.player_inventory:
+                    continue
+                pos_i = self.tui.game.world.maps[(0,0)].\
+                        get_position_index(t.position[1])
                 symbol = self.tui.game.symbol_for_type(t.type_)
                 if terrain_as_list[pos_i][0] in {'f', '@', 'm'}:
                     old_symbol = terrain_as_list[pos_i][0]
@@ -393,8 +401,8 @@ class MapWidget(Widget):
                 else:
                     terrain_as_list[pos_i] = symbol
             if self.tui.examiner_mode:
-                pos_i = self.tui.game.world.map_.\
-                        get_position_index(self.tui.examiner_position)
+                pos_i = self.tui.game.world.maps[(0,0)].\
+                        get_position_index(self.tui.examiner_position[1])
                 terrain_as_list[pos_i] = (terrain_as_list[pos_i][0], '?')
             return terrain_as_list
 
@@ -430,7 +438,7 @@ class MapWidget(Widget):
                     chars_with_attrs += [c]
             return chars_with_attrs
 
-        if self.tui.game.world.map_.terrain == '':
+        if self.tui.game.world.maps[(0,0)].terrain == '':
             lines = []
             pad_y(lines)
             self.safe_write(''.join(lines))
@@ -440,8 +448,8 @@ class MapWidget(Widget):
         center = self.tui.game.world.player.position
         if self.tui.examiner_mode:
             center = self.tui.examiner_position
-        lines = self.tui.game.world.map_.format_to_view(annotated_terrain,
-                                                        center, self.size)
+        lines = self.tui.game.world.maps[(0,0)].\
+                format_to_view(annotated_terrain, center, self.size)
         pad_or_cut_x(lines)
         pad_y(lines)
         self.safe_write(lines_to_colored_chars(lines))
@@ -481,7 +489,7 @@ class TUI:
         self.parser = Parser(self.game)
         self.to_update = {}
         self.item_pointer = 0
-        self.examiner_position = (0, 0)
+        self.examiner_position = ((0,0), (0, 0))
         self.examiner_mode = False
         self.popup_text = 'Hi bob'
         self.to_send = []
@@ -545,9 +553,10 @@ class TUI:
 
         def move_examiner(direction):
             start_pos = self.examiner_position
-            new_examine_pos = self.game.world.map_.move(start_pos, direction)
+            new_examine_pos = self.game.world.maps[(0,0)].\
+                              move(start_pos[0], direction)
             if new_examine_pos:
-                self.examiner_position = new_examine_pos
+                self.examiner_position[1] = new_examine_pos
             self.to_update['map'] = True
 
         def switch_to_pick_or_drop(target_widget):
index 9d93e6cb8871b384986a8577b646492a6f04b374..f9aa5fa1281d3e0a56d55ca797ad725ab61aa1f1 100644 (file)
@@ -12,7 +12,7 @@ def cmd_GET_GAMESTATE(game, connection_id):
 
 def cmd_MAP(game, yx):
     """Create new map of size yx and only '?' cells."""
-    game.world.new_map(yx)
+    game.world.new_map((0,0), yx)
 cmd_MAP.argtypes = 'yx_tuple:pos'
 
 def cmd_THING_TYPE(game, i, type_):
@@ -36,10 +36,10 @@ def cmd_THING_TYPE(game, i, type_):
     game.world.things[t_old_index] = t_new
 cmd_THING_TYPE.argtypes = 'int:nonneg string:thingtype'
 
-def cmd_THING_POS(game, i, yx):
+def cmd_THING_POS(game, i, big_yx, small_yx):
     t = game.world.get_thing(i)
-    t.position = tuple(yx)
-cmd_THING_POS.argtypes = 'int:nonneg yx_tuple:nonneg'
+    t.position = (big_yx, small_yx)
+cmd_THING_POS.argtypes = 'int:nonneg yx_tuple yx_tuple'
 
 def cmd_THING_INVENTORY(game, id_, ids):
     t = game.world.get_thing(id_)
@@ -60,7 +60,7 @@ def cmd_GET_PICKABLE_ITEMS(game, connection_id):
         game.io.send('PICKABLE_ITEMS ,')
 
 def cmd_TERRAIN_LINE(game, y, terrain_line):
-    game.world.map_.set_line(y, terrain_line)
+    game.world.maps[(0,0)].set_line(y, terrain_line)
 cmd_TERRAIN_LINE.argtypes = 'int:nonneg string'
 
 def cmd_PLAYER_ID(game, id_):
@@ -90,13 +90,14 @@ def cmd_SAVE(game):
     save_file_name = game.io.game_file_name + '.save'
     with open(save_file_name, 'w') as f:
         write(f, 'TURN %s' % game.world.turn)
-        write(f, 'MAP ' + stringify_yx(game.world.map_.size))
-        for y, line in game.world.map_.lines():
+        write(f, 'MAP ' + stringify_yx(game.world.maps[(0,0)].size))
+        for y, line in game.world.maps[(0,0)].lines():
             write(f, 'TERRAIN_LINE %5s %s' % (y, quote(line)))
         for thing in game.world.things:
             write(f, 'THING_TYPE %s %s' % (thing.id_, thing.type_))
-            write(f, 'THING_POS %s %s' % (thing.id_,
-                                          stringify_yx(thing.position)))
+            write(f, 'THING_POS %s %s %s' % (thing.id_,
+                                             stringify_yx(thing.position[0]),
+                                             stringify_yx(thing.position[1])))
             if hasattr(thing, 'health'):
                 write(f, 'THING_HEALTH %s %s' % (thing.id_, thing.health))
             if len(thing.inventory) > 0:
index 103d705b90287ed51a1ab32ddeadacfc743b638a..b1225eab8a625b7e5ee8efd1ebe3e1e8b7841d66 100755 (executable)
@@ -34,10 +34,10 @@ class WorldBase:
             return t
         return None
 
-    def things_at_pos(self, yx):
+    def things_at_pos(self, pos):
         things = []
         for t in self.things:
-            if t.position == yx:
+            if t.position == pos:
                 things += [t]
         return things
 
@@ -49,6 +49,7 @@ class World(WorldBase):
         super().__init__(*args, **kwargs)
         self.player_id = 0
         self.player_is_alive = True
+        self.maps = {}
 
     @property
     def player(self):
@@ -59,8 +60,8 @@ class World(WorldBase):
             return 0
         return self.things[-1].id_ + 1
 
-    def new_map(self, yx):
-        self.map_ = self.game.map_type(yx)
+    def new_map(self, map_pos, size):
+        self.maps[map_pos] = self.game.map_type(size)
 
     def proceed_to_next_player_turn(self):
         """Run game world turns until player can decide their next step.
@@ -81,11 +82,11 @@ class World(WorldBase):
             for thing in self.things[player_i+1:]:
                 thing.proceed()
             self.turn += 1
-            for pos in self.map_:
-                if self.map_[pos] == '.' and \
-                   len(self.things_at_pos(pos)) == 0 and \
+            for pos in self.maps[(0,0)]:
+                if self.maps[(0,0)][pos] == '.' and \
+                   len(self.things_at_pos(((0,0), pos))) == 0 and \
                    random.random() > 0.999:
-                    self.add_thing_at('food', pos)
+                    self.add_thing_at('food', ((0,0), pos))
             for thing in self.things[:player_i]:
                 thing.proceed()
             self.player.proceed(is_AI=False)
@@ -102,9 +103,10 @@ class World(WorldBase):
 
         def add_thing_at_random(type_):
             while True:
-                new_pos = (random.randint(0, yx[0] -1),
-                           random.randint(0, yx[1] - 1))
-                if self.map_[new_pos] != '.':
+                new_pos = ((0,0),
+                           (random.randint(0, yx[0] -1),
+                            random.randint(0, yx[1] -1)))
+                if self.maps[new_pos[0]][new_pos[1]] != '.':
                     continue
                 if len(self.things_at_pos(new_pos)) > 0:
                     continue
@@ -113,12 +115,21 @@ class World(WorldBase):
         self.things = []
         random.seed(seed)
         self.turn = 0
-        self.new_map(yx)
-        for pos in self.map_:
+        self.maps = {}
+        self.new_map((0,0), yx)
+        #self.new_map((0,1), yx)
+        #self.new_map((1,1), yx)
+        #self.new_map((1,0), yx)
+        #self.new_map((1,-1), yx)
+        #self.new_map((0,-1), yx)
+        #self.new_map((-1,-1), yx)
+        #self.new_map((-1,0), yx)
+        #self.new_map((-1,1), yx)
+        for pos in self.maps[(0,0)]:
             if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
-                self.map_[pos] = '#'
+                self.maps[(0,0)][pos] = '#'
                 continue
-            self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
+            self.maps[(0,0)][pos] = random.choice(('.', '.', '.', '.', 'x'))
 
         player = add_thing_at_random('human')
         self.player_id = player.id_
@@ -164,7 +175,7 @@ class Game:
 
     def get_string_options(self, string_option_type):
         if string_option_type == 'direction':
-            return self.world.map_.get_directions()
+            return self.world.maps[(0,0)].get_directions()
         elif string_option_type == 'thingtype':
             return list(self.thing_types.keys())
         return None
@@ -173,15 +184,18 @@ class Game:
         """Send out game state data relevant to clients."""
 
         self.io.send('TURN ' + str(self.world.turn))
-        self.io.send('MAP ' + stringify_yx(self.world.map_.size))
+        self.io.send('MAP ' + stringify_yx(visible_map.size))
         visible_map = self.world.player.get_visible_map()
         for y, line in visible_map.lines():
             self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
-        visible_things = self.world.player.get_visible_things()
+        visible_things, offset = self.world.player.get_visible_things()
         for thing in visible_things:
+            offset_pos = (thing.position[1][0] - offset[0],
+                          thing.position[1][1] - offset[1])
             self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
-            self.io.send('THING_POS %s %s' % (thing.id_,
-                                              stringify_yx(thing.position)))
+            self.io.send('THING_POS %s %s %s' % (thing.id_,
+                                                 stringify_yx(thing.position[0]),
+                                                 stringify_yx(offset_pos)))
             if hasattr(thing, 'health'):
                 self.io.send('THING_HEALTH %s %s' % (thing.id_,
                                                      thing.health))
@@ -193,8 +207,9 @@ class Game:
         for id_ in self.world.player.inventory:
             thing = self.world.get_thing(id_)
             self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
-            self.io.send('THING_POS %s %s' % (thing.id_,
-                                              stringify_yx(thing.position)))
+            self.io.send('THING_POS %s %s %s' % (thing.id_,
+                                                 stringify_yx(thing.position[0]),
+                                                 stringify_yx(thing.position[1])))
         self.io.send('GAME_STATE_COMPLETE')
 
     def proceed(self):
index c9398d47ef515f4d8e085005485b94fb82b450b9..0ec953d329befd0dd5e6ec93910e06e78ed69d7a 100644 (file)
@@ -146,6 +146,7 @@ class FovMap:
     def __init__(self, source_map, yx):
         self.source_map = source_map
         self.size = self.source_map.size
+        self.fov_radius = (self.size[0] / 2) - 0.5
         self.terrain = '?' * self.size_i
         self[yx] = '.'
         self.shadow_cones = []
@@ -236,11 +237,14 @@ class FovMap:
         # 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[:]
         #print('DEBUG CIRCLE_OUT', yx)
         while circle_in_map:
+            if distance > self.fov_radius:
+                break
             circle_in_map = False
             yx, _ = self.basic_circle_out_move(yx, 'RIGHT')
             for dir_i in range(len(self.circle_out_directions)):
index 84def03971e312613ab556aebbdd1a45b516bb95..a8a827f0e00ebd95bebf3eb2148370ef220ed695 100644 (file)
@@ -64,16 +64,17 @@ class Parser:
         args = self.argsparse(argtypes, args_candidates)
         return func, args
 
-    def parse_yx_tuple(self, yx_string, range_):
-        """Parse yx_string as yx_tuple:nonneg argtype, return result.
+    def parse_yx_tuple(self, yx_string, range_=None):
+        """Parse yx_string as yx_tuple, return result.
+
+        The range_ argument may be 'nonneg' (non-negative, including
+        0) or 'pos' (positive, excluding 0).
 
-        The range_ argument may be 'nonneg' (non-negative, including 0)
-        or 'pos' (positive, excluding 0).
         """
 
         def get_axis_position_from_argument(axis, token):
             if len(token) < 3 or token[:2] != axis + ':' or \
-                    not token[2:].isdigit():
+                    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':
@@ -93,7 +94,7 @@ class Parser:
         """Parse into / return args_tokens as args defined by signature.
 
         Expects signature to be a ' '-delimited sequence of any of the strings
-        'int:nonneg', 'yx_tuple:nonneg', 'yx_tuple:pos', 'string',
+        'int:nonneg', 'yx_tuple', 'yx_tuple:nonneg', 'yx_tuple:pos', 'string',
         'seq:int:nonneg', 'string:' + an option type string accepted by
         self.game.get_string_options, defining the respective argument types.
         """
@@ -115,6 +116,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)]
             elif tmpl == 'seq:int:nonneg':
                 if arg == ',':
                     args += [[]]
@@ -164,9 +167,9 @@ class TestParser(unittest.TestCase):
 
     def test_unhandled(self):
         p = Parser()
-        self.assertEqual(p.parse(''), None)
-        self.assertEqual(p.parse(' '), None)
-        self.assertEqual(p.parse('x'), None)
+        self.assertEqual(p.parse(''), (None, ()))
+        self.assertEqual(p.parse(' '), (None, ()))
+        #self.assertEqual(p.parse('x'), (None, ()))
 
     def test_argsparse(self):
         from functools import partial
@@ -175,30 +178,29 @@ class TestParser(unittest.TestCase):
         assertErr('', ['foo'])
         assertErr('string', [])
         assertErr('string string', ['foo'])
-        self.assertEqual(p.argsparse('string', ('foo',)),
-                         (['foo'], {}))
+        self.assertEqual(p.argsparse('string', ('foo',)), ['foo'])
         self.assertEqual(p.argsparse('string string', ('foo', 'bar')),
-                         (['foo', 'bar'], {}))
+                         ['foo', 'bar'])
         assertErr('int:nonneg', [''])
         assertErr('int:nonneg', ['x'])
         assertErr('int:nonneg', ['-1'])
         assertErr('int:nonneg', ['0.1'])
-        self.assertEqual(p.argsparse('int:nonneg', ('0',)),
-                         ([0], {}))
-        assertErr('yx_tuple:nonneg', ['x'])
+        self.assertEqual(p.argsparse('int:nonneg', ('0',)), [0])
+        assertErr('yx_tuple', ['x'])
+        assertErr('yx_tuple', ['Y:1.1,X:1'])
+        self.assertEqual(p.argsparse('yx_tuple', ('Y:1,X:-2',)), [(1, -2)])
         assertErr('yx_tuple:nonneg', ['Y:0,X:-1'])
         assertErr('yx_tuple:nonneg', ['Y:-1,X:0'])
-        assertErr('yx_tuple:nonneg', ['Y:1.1,X:1'])
         assertErr('yx_tuple:nonneg', ['Y:1,X:1.1'])
         self.assertEqual(p.argsparse('yx_tuple:nonneg', ('Y:1,X:2',)),
-                         ([(1, 2)], {}))
+                         [(1, 2)])
         assertErr('yx_tuple:pos', ['Y:0,X:1'])
         assertErr('yx_tuple:pos', ['Y:1,X:0'])
         assertErr('seq:int:nonneg', [''])
-        assertErr('seq:int:nonneg', [','])
+        self.assertEqual(p.argsparse('seq:int:nonneg', [',']), [[]])
         assertErr('seq:int:nonneg', ['a'])
         assertErr('seq:int:nonneg', ['a,1'])
         assertErr('seq:int:nonneg', [',1'])
         assertErr('seq:int:nonneg', ['1,'])
         self.assertEqual(p.argsparse('seq:int:nonneg', ('1,2,3',)),
-                         ([[1, 2, 3]], {}))
+                         [[1, 2, 3]])
index dfd22f7d50094d3732a327e370080e1085ef81ca..ae559800f5f931769f6861cae5a652a8ffd6987b 100644 (file)
@@ -19,6 +19,8 @@ class Task:
         for arg in self.args:
             if type(arg) == str:
                 stringed_args += [quote(arg)]
+            elif type(arg) == int:
+                stringed_args += [str(arg)]
             else:
                 raise GameError('stringifying arg type not implemented')
         return ' '.join(stringed_args)
@@ -36,18 +38,20 @@ class Task_MOVE(Task):
     argtypes = 'string:direction'
 
     def check(self):
-        test_pos = self.thing.world.map_.move(self.thing.position, self.args[0])
-        if test_pos is None:
+        test_pos = ((0,0),
+                    self.thing.world.maps[(0,0)].
+                    move(self.thing.position[1], self.args[0]))
+        if test_pos == ((0,0), None):
             raise GameError('would move outside map bounds')
-        if self.thing.world.map_[test_pos] != '.':
+        if self.thing.world.maps[test_pos[0]][test_pos[1]] != '.':
             raise GameError('%s would move into illegal terrain' % self.thing.id_)
         for t in self.thing.world.things_at_pos(test_pos):
             if t.blocking:
                 raise GameError('%s would move into other thing' % self.thing.id_)
 
     def do(self):
-        self.thing.position = self.thing.world.map_.move(self.thing.position,
-                                                         self.args[0])
+        self.thing.position = (0,0), self.thing.world.maps[(0,0)].\
+                                     move(self.thing.position[1], self.args[0])
         for id_ in self.thing.inventory:
             t = self.thing.world.get_thing(id_)
             t.position = self.thing.position
index c9e95d3cbf24dc70bf8ab05bb652435071dc19b2..408448e3bad13814b89baab733cb2907ee346a52 100644 (file)
@@ -5,7 +5,7 @@ from plomrogue.errors import GameError
 class ThingBase:
     type_ = '?'
 
-    def __init__(self, world, id_=None, position=(0,0)):
+    def __init__(self, world, id_=None, position=((0,0), (0,0))):
         self.world = world
         self.position = position
         if id_ is None:
@@ -45,16 +45,17 @@ class ThingAnimate(Thing):
         super().__init__(*args, **kwargs)
         self.set_task('WAIT')
         self._last_task_result = None
-        self._stencil = None
+        self._radius = 16
+        self.unset_surroundings()
 
-    def move_on_dijkstra_map(self, targets):
-        dijkstra_map = type(self.world.map_)(self.world.map_.size)
+    def move_on_dijkstra_map(self, own_pos, targets):
+        visible_map = self.get_visible_map()
+        dijkstra_map = self.world.game.map_type(visible_map.size)
         n_max = 256
         dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
         for target in targets:
             dijkstra_map[target] = 0
         shrunk = True
-        visible_map = self.get_visible_map()
         while shrunk:
             shrunk = False
             for pos in dijkstra_map:
@@ -66,7 +67,7 @@ class ThingAnimate(Thing):
                     if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
                         dijkstra_map[pos] = dijkstra_map[yx] + 1
                         shrunk = True
-        neighbors = dijkstra_map.get_neighbors(tuple(self.position))
+        neighbors = dijkstra_map.get_neighbors(own_pos)
         n = n_max
         target_direction = None
         for direction in sorted(neighbors.keys()):
@@ -79,15 +80,19 @@ class ThingAnimate(Thing):
         return target_direction
 
     def hunt_player(self):
-        visible_things = self.get_visible_things()
+        visible_things, offset = self.get_visible_things()
         target = None
         for t in visible_things:
             if t.type_ == 'human':
-                target = t.position
+                target = (t.position[1][0] - offset[0],
+                          t.position[1][1] - offset[1])
                 break
         if target is not None:
             try:
-                target_dir = self.move_on_dijkstra_map([target])
+                offset_self_pos = (self.position[1][0] - offset[0],
+                                   self.position[1][1] - offset[1])
+                target_dir = self.move_on_dijkstra_map(offset_self_pos,
+                                                       [target])
                 if target_dir is not None:
                     self.set_task('MOVE', (target_dir,))
                     return True
@@ -106,12 +111,16 @@ class ThingAnimate(Thing):
             if t.type_ == 'food':
                 self.set_task('PICKUP', (id_,))
                 return True
-        visible_things = self.get_visible_things()
+        visible_things, offset = self.get_visible_things()
         food_targets = []
         for t in visible_things:
             if t.type_ == 'food':
-                food_targets += [t.position]
-        target_dir = self.move_on_dijkstra_map(food_targets)
+                food_targets += [(t.position[1][0] - offset[0],
+                                  t.position[1][1] - offset[1])]
+        offset_self_pos = (self.position[1][0] - offset[0],
+                           self.position[1][1] - offset[1])
+        target_dir = self.move_on_dijkstra_map(offset_self_pos,
+                                               food_targets)
         if target_dir:
             try:
                 self.set_task('MOVE', (target_dir,))
@@ -144,7 +153,7 @@ class ThingAnimate(Thing):
         None. If is_AI, calls .decide_task to decide a self.task.
 
         """
-        self._stencil = None
+        self.unset_surroundings()
         self.health -= 1
         if self.health <= 0:
             if self is self.world.player:
@@ -173,35 +182,80 @@ class ThingAnimate(Thing):
             except GameError:
                 self.set_task('WAIT')
 
+    def unset_surroundings(self):
+        self._stencil = None
+        self._surrounding_map = None
+        self._surroundings_offset = None
+
+    def must_fix_indentation(self):
+        return self._radius % 2 != self.position[1][0] % 2
+
+    def get_surroundings_offset(self):
+        if self._surroundings_offset is not None:
+            return self._surroundings_offset
+        add_line = self.must_fix_indentation()
+        offset_y = self.position[1][0] - self._radius - int(add_line)
+        offset_x = self.position[1][1] - self._radius
+        self._surroundings_offset = (offset_y, offset_x)
+        return self._surroundings_offset
+
+    def get_surrounding_map(self):
+        if self._surrounding_map is not None:
+            return self._surrounding_map
+        offset = self.get_surroundings_offset()
+        add_line = self.must_fix_indentation()
+        self._surrounding_map = self.world.game.\
+                                map_type(size=(self._radius*2+1+int(add_line),
+                                               self._radius*2+1))
+        for pos in self._surrounding_map:
+            offset_pos = (pos[0] + offset[0], pos[1] + offset[1])
+            if offset_pos[0] >= 0 and \
+               offset_pos[0] < self.world.maps[(0,0)].size[0] and \
+               offset_pos[1] >= 0 and \
+               offset_pos[1] < self.world.maps[(0,0)].size[1]:
+                self._surrounding_map[pos] = self.world.maps[(0,0)][offset_pos]
+        return self._surrounding_map
+
     def get_stencil(self):
         if self._stencil is not None:
             return self._stencil
-        self._stencil = self.world.map_.get_fov_map(self.position)
+        m = self.get_surrounding_map()
+        offset = self.get_surroundings_offset()
+        fov_center = (self.position[1][0] - offset[0],
+                      self.position[1][1] - offset[1])
+        self._stencil = m.get_fov_map(fov_center)
         return self._stencil
 
     def get_visible_map(self):
         stencil = self.get_stencil()
-        m = self.world.map_.new_from_shape(' ')
+        m = self.get_surrounding_map().new_from_shape(' ')
         for pos in m:
             if stencil[pos] == '.':
-                m[pos] = self.world.map_[pos]
+                m[pos] = self._surrounding_map[pos]
         return m
 
     def get_visible_things(self):
         stencil = self.get_stencil()
+        offset = self.get_surroundings_offset()
         visible_things = []
         for thing in self.world.things:
-            if (not thing.in_inventory) and stencil[thing.position] == '.':
+            if abs(thing.position[1][0] - self.position[1][0]) > self._radius or\
+               abs(thing.position[1][1] - self.position[1][1]) > self._radius:
+                continue
+            offset_pos = (thing.position[1][0] - offset[0],
+                          thing.position[1][1] - offset[1])
+            if (not thing.in_inventory) and stencil[offset_pos] == '.':
                 visible_things += [thing]
-        return visible_things
+        return visible_things, offset
 
     def get_pickable_items(self):
         pickable_ids = []
-        for t in [t for t in self.get_visible_things() if
+        visible_things, _ = self.get_visible_things()
+        for t in [t for t in visible_things if
                   isinstance(t, ThingItem) and
                   (t.position == self.position or
-                   t.position in
-                   self.world.map_.get_neighbors(self.position).values())]:
+                   t.position[1] in
+                   self.world.maps[(0,0)].get_neighbors(self.position[1]).values())]:
             pickable_ids += [t.id_]
         return pickable_ids