home · contact · privacy
Enforce sane create_unfound decisions.
[plomrogue2-experiments] / new / plomrogue / game.py
index 0e1ff9aecc6b22acfc0eafc230a0b6d3a19f2ca8..ffedbfaff14200e0af6a4f2335af5b5e0f66448b 100755 (executable)
@@ -8,7 +8,7 @@ from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE,
                                 cmd_GET_PICKABLE_ITEMS, cmd_MAP_SIZE,
                                 cmd_TERRAIN_LINE, cmd_PLAYER_ID,
                                 cmd_TURN, cmd_SWITCH_PLAYER, cmd_SAVE)
-from plomrogue.mapping import MapGeometryHex, Map, YX
+from plomrogue.mapping import MapGeometryHex, MapChunk, YX
 from plomrogue.parser import Parser
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
@@ -41,7 +41,9 @@ class GameBase:
         self.turn = 0
         self.things = []
 
-    def get_thing(self, id_, create_unfound=True):
+    def get_thing(self, id_, create_unfound):
+        # No default for create_unfound because every call to get_thing
+        # should be accompanied by serious consideration whether to use it.
         for thing in self.things:
             if id_ == thing.id_:
                 return thing
@@ -94,6 +96,7 @@ class Game(GameBase):
         self.player_id = 0
         self.player_is_alive = True
         self.maps = {}
+        self.max_map_awakeness = 100
         self.rand = PRNGod(0)
 
     def get_string_options(self, string_option_type):
@@ -132,7 +135,7 @@ class Game(GameBase):
         else:
             self.io.send('PLAYER_INVENTORY ,')
         for id_ in self.player.inventory:
-            thing = self.get_thing(id_)
+            thing = self.get_thing(id_, create_unfound=False)
             send_thing(thing)
         self.io.send('GAME_STATE_COMPLETE')
 
@@ -198,17 +201,24 @@ class Game(GameBase):
 
     @property
     def player(self):
-        return self.get_thing(self.player_id)
+        return self.get_thing(self.player_id, create_unfound=False)
 
     def new_thing_id(self):
         if len(self.things) == 0:
             return 0
+        # DANGEROUS – if anywhere we append a thing to the list of lower
+        # ID than the highest-value ID, this might lead to re-using an
+        # already active ID.  This condition /should/ not be fulfilled
+        # anywhere in the code, but if it does, trouble here is one of
+        # the more obvious indicators that it does – that's why there's
+        # no safeguard here against this.
         return self.things[-1].id_ + 1
 
     def get_map(self, map_pos):
         if not (map_pos in self.maps and
                 self.maps[map_pos].size == self.map_size):
-            self.maps[map_pos] = Map(self.map_size)
+            self.maps[map_pos] = MapChunk(self.map_size)
+            self.maps[map_pos].awake = self.max_map_awakeness
             for pos in self.maps[map_pos]:
                 self.maps[map_pos][pos] = '.'
         return self.maps[map_pos]
@@ -248,32 +258,41 @@ class Game(GameBase):
             self.player.proceed(is_AI=False)
 
         def reality_bubble():
-            import math
+
+            def regenerate_chunk_from_map_stats(map_):
+                import math
+                max_stat = self.max_map_awakeness
+                for t_type in map_.stats:
+                    stat = map_.stats[t_type]
+                    to_create = stat['population'] // max_stat
+                    mod_created = int(self.rand.randint(0, max_stat - 1) <
+                                      (stat['population'] % max_stat))
+                    to_create = (stat['population'] // max_stat) + mod_created
+                    if to_create == 0:
+                        continue
+                    average_health = None
+                    if stat['health'] > 0:
+                        average_health = math.ceil(stat['health'] /
+                                                   stat['population'])
+                    for i in range(to_create):
+                        t = self.add_thing_at_random(map_pos, t_type)
+                        if average_health:
+                            t.health = average_health
+                        #if hasattr(t, 'health'):
+                        #    print('DEBUG create', t.type_, t.health)
+
             for map_pos in self.maps:
                 m = self.maps[map_pos]
                 if map_pos in self.player.close_maps:
 
                     # Newly inside chunks are regenerated from .stats.
                     if not m.awake:
-                        for t_type in m.stats:
-                            stat = m.stats[t_type]
-                            to_create = stat['population'] // 100
-                            to_create = stat['population'] // 100 +\
-                                int(self.rand.randint(0, 99) < (stat['population'] % 100))
-                            if to_create == 0:
-                                continue
-                            average_health = None
-                            if stat['health'] > 0:
-                                average_health = math.ceil(stat['health'] /
-                                                           stat['population'])
-                            for i in range(to_create):
-                                t = self.add_thing_at_random(map_pos, t_type)
-                                if average_health:
-                                    t.health = average_health
+                        #print('DEBUG regen stats', map_pos, m.stats)
+                        regenerate_chunk_from_map_stats(m)
 
                     # Inside chunks are set to max .awake and don't collect
                     # stats.
-                    m.awake = 100
+                    m.awake = self.max_map_awakeness
                     m.stats = {}
 
                 # Outside chunks grow distant through .awake decremention.
@@ -281,7 +300,9 @@ class Game(GameBase):
                 # inside are disappeared.
                 elif m.awake > 0:
                     m.awake -= 1
-                    for t in self.things:
+                    # We iterate over a list comprehension of self.things,
+                    # since we might delete elements of self.things.
+                    for t in [t for t in self.things]:
                         if t.position[0] == map_pos:
                             if not t.type_ in m.stats:
                                 m.stats[t.type_] = {'population': 0,
@@ -289,9 +310,11 @@ class Game(GameBase):
                             m.stats[t.type_]['population'] += 1
                             if isinstance(t, ThingAnimate):
                                 m.stats[t.type_]['health'] += t.health
-                                if not m.awake:
                             if not m.awake:
+                                # TODO: Handle inventory.
                                 del self.things[self.things.index(t)]
+                    #if not m.awake:
+                    #    print('DEBUG sleep stats', map_pos, m.stats)
 
         while True:
             player_i = self.things.index(self.player)