home · contact · privacy
Enforce sane create_unfound decisions.
[plomrogue2-experiments] / new / plomrogue / things.py
index c9e95d3cbf24dc70bf8ab05bb652435071dc19b2..8900d9a983d751490b7b0d84f0802d9b2efae1ec 100644 (file)
@@ -1,17 +1,38 @@
 from plomrogue.errors import GameError
 from plomrogue.errors import GameError
+from plomrogue.mapping import YX, Map, FovMapHex
 
 
 
 class ThingBase:
     type_ = '?'
 
 
 
 
 class ThingBase:
     type_ = '?'
 
-    def __init__(self, world, id_=None, position=(0,0)):
-        self.world = world
-        self.position = position
+    def __init__(self, game, id_=None, position=(YX(0,0), YX(0,0))):
+        self.game = game
         if id_ is None:
         if id_ is None:
-            self.id_ = self.world.new_thing_id()
+            self.id_ = self.game.new_thing_id()
         else:
             self.id_ = id_
         else:
             self.id_ = id_
+        self.position = position
+
+    @property
+    def position(self):
+        return self._position
+
+    def _position_set(self, pos):
+        """Set self._position to pos.
+
+        We use this setter as core to the @position.setter property
+        method due to property setter subclassing not yet working
+        properly, see <https://bugs.python.org/issue14965>. We will
+        therefore super() _position_set instead of @position.setter in
+        subclasses.
+
+        """
+        self._position = pos
+
+    @position.setter
+    def position(self, pos):
+        self._position_set(pos)
 
 
 
 
 
 
@@ -20,12 +41,55 @@ class Thing(ThingBase):
     in_inventory = False
 
     def __init__(self, *args, **kwargs):
     in_inventory = False
 
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
         self.inventory = []
         self.inventory = []
+        self._radius = 8
+        super().__init__(*args, **kwargs)
 
     def proceed(self):
         pass
 
 
     def proceed(self):
         pass
 
+    def _position_set(self, pos):
+        super()._position_set(pos)
+        for t_id in self.inventory:
+            t = self.game.get_thing(t_id, create_unfound=False)
+            t.position = self.position
+        if not self.id_ == self.game.player_id:
+            return
+        edge_left = self.position[1].x - self._radius
+        edge_right = self.position[1].x + self._radius
+        edge_up = self.position[1].y - self._radius
+        edge_down = self.position[1].y + self._radius
+        if edge_left < 0:
+            self.game.get_map(self.position[0] + YX(1,-1))
+            self.game.get_map(self.position[0] + YX(0,-1))
+            self.game.get_map(self.position[0] + YX(-1,-1))
+        if edge_right >= self.game.map_size.x:
+            self.game.get_map(self.position[0] + YX(1,1))
+            self.game.get_map(self.position[0] + YX(0,1))
+            self.game.get_map(self.position[0] + YX(-1,1))
+        if edge_up < 0:
+            self.game.get_map(self.position[0] + YX(-1,1))
+            self.game.get_map(self.position[0] + YX(-1,0))
+            self.game.get_map(self.position[0] + YX(-1,-1))
+        if edge_down >= self.game.map_size.y:
+            self.game.get_map(self.position[0] + YX(1,1))
+            self.game.get_map(self.position[0] + YX(1,0))
+            self.game.get_map(self.position[0] + YX(1,-1))
+        #alternative
+        #if self.position[1].x < self._radius:
+        #    self.game.get_map(self.position[0] - YX(0,1))
+        #if self.position[1].y < self._radius:
+        #    self.game.get_map(self.position[0] - YX(1,0))
+        #if self.position[1].x > self.game.map_size.x - self._radius:
+        #    self.game.get_map(self.position[0] + YX(0,1))
+        #if self.position[1].y > self.game.map_size.y - self._radius:
+        #    self.game.get_map(self.position[0] + YX(1,0))
+        #if self.position[1].y < self._radius and \
+        #   self.position[1].x <= [pos for pos in
+        #                          diagonal_distance_edge
+        #                          if pos.y == self.position[1].y][0].x:
+        #    self.game.get_map(self.position[0] - YX(1,1))
+
 
 
 class ThingItem(Thing):
 
 
 class ThingItem(Thing):
@@ -45,64 +109,90 @@ class ThingAnimate(Thing):
         super().__init__(*args, **kwargs)
         self.set_task('WAIT')
         self._last_task_result = None
         super().__init__(*args, **kwargs)
         self.set_task('WAIT')
         self._last_task_result = None
-        self._stencil = None
+        self.unset_surroundings()
+        self.close_maps = ()
+
+    def _position_set(self, pos):
+        """For player we need to update .close_maps on every move via the
+           self.surroundings property method, to keep their reality
+           bubble in sync with their movement.
 
 
-    def move_on_dijkstra_map(self, targets):
-        dijkstra_map = type(self.world.map_)(self.world.map_.size)
+        """
+        super()._position_set(pos)
+        if self.id_ == self.game.player_id:
+            if not hasattr(self, '_surroundings'):
+                self._surroundings = None
+            self.surroundings
+
+    def move_on_dijkstra_map(self, own_pos, targets):
+        visible_map = self.get_visible_map()
+        dijkstra_map = Map(visible_map.size,
+                           start_indented=visible_map.start_indented)
         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
         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()
+        get_neighbors = self.game.map_geometry.get_neighbors
         while shrunk:
             shrunk = False
             for pos in dijkstra_map:
                 if visible_map[pos] != '.':
                     continue
         while shrunk:
             shrunk = False
             for pos in dijkstra_map:
                 if visible_map[pos] != '.':
                     continue
-                neighbors = dijkstra_map.get_neighbors(tuple(pos))
+                neighbors = get_neighbors((YX(0,0), pos), dijkstra_map.size,
+                                          dijkstra_map.start_indented)
                 for direction in neighbors:
                 for direction in neighbors:
-                    yx = neighbors[direction]
-                    if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
-                        dijkstra_map[pos] = dijkstra_map[yx] + 1
+                    big_yx, small_yx = neighbors[direction]
+                    if big_yx == YX(0,0) and \
+                       dijkstra_map[small_yx] < dijkstra_map[pos] - 1:
+                        dijkstra_map[pos] = dijkstra_map[small_yx] + 1
                         shrunk = True
                         shrunk = True
-        neighbors = dijkstra_map.get_neighbors(tuple(self.position))
+        #print('DEBUG DIJKSTRA ---------------------', self.id_, self.position)
+        #for y, line in dijkstra_map.lines():
+        #    line_to_print = []
+        #    for x in line:
+        #        line_to_print += ['%3s' % x]
+        #    print(' '.join(line_to_print))
+        neighbors = get_neighbors((YX(0,0), own_pos), dijkstra_map.size,
+                                  dijkstra_map.start_indented)
         n = n_max
         target_direction = None
         for direction in sorted(neighbors.keys()):
         n = n_max
         target_direction = None
         for direction in sorted(neighbors.keys()):
-            yx = neighbors[direction]
-            if yx is not None:
-                n_new = dijkstra_map[yx]
+            big_yx, small_yx = neighbors[direction]
+            if big_yx == (0,0):
+                n_new = dijkstra_map[small_yx]
                 if n_new < n:
                     n = n_new
                     target_direction = direction
         return target_direction
 
                 if n_new < n:
                     n = n_new
                     target_direction = direction
         return target_direction
 
-    def hunt_player(self):
-        visible_things = self.get_visible_things()
-        target = None
-        for t in visible_things:
-            if t.type_ == 'human':
-                target = t.position
-                break
-        if target is not None:
-            try:
-                target_dir = self.move_on_dijkstra_map([target])
-                if target_dir is not None:
-                    self.set_task('MOVE', (target_dir,))
-                    return True
-            except GameError:
-                pass
-        return False
+    #def hunt_player(self):
+    #    visible_things = self.get_visible_things()
+    #    target = None
+    #    for t in visible_things:
+    #        if t.type_ == 'human':
+    #            target = t.position[1] - self.view_offset
+    #            break
+    #    if target is not None:
+    #        try:
+    #            offset_self_pos = self.position[1] - self.view_offset
+    #            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
+    #        except GameError:
+    #            pass
+    #    return False
 
     def hunt_food_satisfaction(self):
         for id_ in self.inventory:
 
     def hunt_food_satisfaction(self):
         for id_ in self.inventory:
-            t = self.world.get_thing(id_)
+            t = self.game.get_thing(id_, create_unfound=False)
             if t.type_ == 'food':
                 self.set_task('EAT', (id_,))
                 return True
         for id_ in self.get_pickable_items():
             if t.type_ == 'food':
                 self.set_task('EAT', (id_,))
                 return True
         for id_ in self.get_pickable_items():
-            t = self.world.get_thing(id_)
+            t = self.game.get_thing(id_, create_unfound=False)
             if t.type_ == 'food':
                 self.set_task('PICKUP', (id_,))
                 return True
             if t.type_ == 'food':
                 self.set_task('PICKUP', (id_,))
                 return True
@@ -110,8 +200,14 @@ class ThingAnimate(Thing):
         food_targets = []
         for t in visible_things:
             if t.type_ == 'food':
         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 += [self.game.map_geometry.pos_in_view(t.position,
+                                                                    self.view_offset,
+                                                                    self.game.map_size)]
+        offset_self_pos = self.game.map_geometry.pos_in_view(self.position,
+                                                             self.view_offset,
+                                                             self.game.map_size)
+        target_dir = self.move_on_dijkstra_map(offset_self_pos,
+                                               food_targets)
         if target_dir:
             try:
                 self.set_task('MOVE', (target_dir,))
         if target_dir:
             try:
                 self.set_task('MOVE', (target_dir,))
@@ -126,7 +222,7 @@ class ThingAnimate(Thing):
             self.set_task('WAIT')
 
     def set_task(self, task_name, args=()):
             self.set_task('WAIT')
 
     def set_task(self, task_name, args=()):
-        task_class = self.world.game.tasks[task_name]
+        task_class = self.game.tasks[task_name]
         self.task = task_class(self, args)
         self.task.check()  # will throw GameError if necessary
 
         self.task = task_class(self, args)
         self.task.check()  # will throw GameError if necessary
 
@@ -134,8 +230,8 @@ class ThingAnimate(Thing):
         """Further the thing in its tasks, decrease its health.
 
         First, ensures an empty map, decrements .health and kills
         """Further the thing in its tasks, decrease its health.
 
         First, ensures an empty map, decrements .health and kills
-        thing if crossing zero (removes from self.world.things for AI
-        thing, or unsets self.world.player_is_alive for player thing);
+        thing if crossing zero (removes from self.game.things for AI
+        thing, or unsets self.game.player_is_alive for player thing);
         then checks that self.task is still possible and aborts if
         otherwise (for AI things, decides a new task).
 
         then checks that self.task is still possible and aborts if
         otherwise (for AI things, decides a new task).
 
@@ -144,13 +240,14 @@ class ThingAnimate(Thing):
         None. If is_AI, calls .decide_task to decide a self.task.
 
         """
         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:
         self.health -= 1
         if self.health <= 0:
-            if self is self.world.player:
-                self.world.player_is_alive = False
+            if self is self.game.player:
+                self.game.player_is_alive = False
             else:
             else:
-                del self.world.things[self.world.things.index(self)]
+                # TODO: Handle inventory.
+                del self.game.things[self.game.things.index(self)]
             return
         try:
             self.task.check()
             return
         try:
             self.task.check()
@@ -173,35 +270,71 @@ class ThingAnimate(Thing):
             except GameError:
                 self.set_task('WAIT')
 
             except GameError:
                 self.set_task('WAIT')
 
+    def unset_surroundings(self):
+        self._stencil = None
+        self._surroundings = None
+
+    @property
+    def view_offset(self):
+        return self.game.map_geometry.get_view_offset(self.game.map_size,
+                                                      self.position,
+                                                      self._radius)
+
+    @property
+    def surroundings(self):
+        if self._surroundings is not None:
+            return self._surroundings
+        s, close_maps = self.\
+            game.map_geometry.get_view_and_seen_maps(self.game.map_size,
+                                                     self.game.get_map,
+                                                     self._radius,
+                                                     self.view_offset)
+        self.close_maps = close_maps
+        self._surroundings = s
+        return self._surroundings
+
     def get_stencil(self):
         if self._stencil is not None:
             return self._stencil
     def get_stencil(self):
         if self._stencil is not None:
             return self._stencil
-        self._stencil = self.world.map_.get_fov_map(self.position)
+        m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
+        for pos in self.surroundings:
+            if self.surroundings[pos] in {'.', '~'}:
+                m[pos] = '.'
+        fov_center = YX((m.size.y) // 2, m.size.x // 2)
+        self._stencil = FovMapHex(m, fov_center)
         return self._stencil
 
     def get_visible_map(self):
         stencil = self.get_stencil()
         return self._stencil
 
     def get_visible_map(self):
         stencil = self.get_stencil()
-        m = self.world.map_.new_from_shape(' ')
+        m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
         for pos in m:
             if stencil[pos] == '.':
         for pos in m:
             if stencil[pos] == '.':
-                m[pos] = self.world.map_[pos]
+                m[pos] = self.surroundings[pos]
         return m
 
     def get_visible_things(self):
         stencil = self.get_stencil()
         visible_things = []
         return m
 
     def get_visible_things(self):
         stencil = self.get_stencil()
         visible_things = []
-        for thing in self.world.things:
-            if (not thing.in_inventory) and stencil[thing.position] == '.':
+        for thing in self.game.things:
+            pos = self.game.map_geometry.pos_in_view(thing.position,
+                                                     self.view_offset,
+                                                     self.game.map_size)
+            if pos.y < 0 or pos.x < 0 or\
+               pos.y >= stencil.size.y or pos.x >= stencil.size.x:
+                continue
+            if (not thing.in_inventory) and stencil[pos] == '.':
                 visible_things += [thing]
         return visible_things
 
     def get_pickable_items(self):
         pickable_ids = []
                 visible_things += [thing]
         return visible_things
 
     def get_pickable_items(self):
         pickable_ids = []
-        for t in [t for t in self.get_visible_things() if
-                  isinstance(t, ThingItem) and
+        visible_things = self.get_visible_things()
+        neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
+                                                               self.game.map_size)
+        for t in [t for t in visible_things
+                  if isinstance(t, ThingItem) and
                   (t.position == self.position or
                   (t.position == self.position or
-                   t.position in
-                   self.world.map_.get_neighbors(self.position).values())]:
+                   t.position in neighbor_fields.values())]:
             pickable_ids += [t.id_]
         return pickable_ids
 
             pickable_ids += [t.id_]
         return pickable_ids