home · contact · privacy
Refactor player session code.
[plomrogue2] / plomrogue / game.py
index 1ffbf1acfa186f900d96a8252bd5db58dd5fa0a8..23a0d7bbc21cd7763e9c58e7f2e7c49da584c3a9 100755 (executable)
@@ -4,6 +4,7 @@ from plomrogue.errors import GameError, PlayError
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, Map
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, Map
+import string
 
 
 
 
 
 
@@ -33,6 +34,19 @@ class GameBase:
 
 
 
 
 
 
+class SaveableMap(Map):
+    modified = False
+
+    def __setitem__(self, *args, **kwargs):
+        super().__setitem__(*args, **kwargs)
+        self.modified = True
+
+    def set_line(self, *args, **kwargs):
+        super().set_line(*args, **kwargs)
+        self.modified = True
+
+
+
 import os
 class Game(GameBase):
 
 import os
 class Game(GameBase):
 
@@ -43,11 +57,25 @@ 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 = {}
+        self.player_chars = string.digits + string.ascii_letters
+        self.player_char_i = -1
+        self.terrains = {
+            '.': 'floor',
+            'X': 'wall',
+            '=': 'window',
+            '#': 'bed',
+            'T': 'desk',
+            '8': 'cupboard',
+            '[': 'glass door',
+            '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')
@@ -67,8 +95,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:
@@ -76,7 +105,6 @@ class Game(GameBase):
         return True
 
     def get_string_options(self, string_option_type):
         return True
 
     def get_string_options(self, string_option_type):
-        import string
         if string_option_type == 'direction':
             return self.map_geometry.get_directions()
         elif string_option_type == 'char':
         if string_option_type == 'direction':
             return self.map_geometry.get_directions()
         elif string_option_type == 'char':
@@ -91,28 +119,45 @@ class Game(GameBase):
     def get_map_geometry_shape(self):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
 
     def get_map_geometry_shape(self):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
 
+    def get_player(self, connection_id):
+        if not connection_id in self.sessions:
+            return None
+        player = self.get_thing(self.sessions[connection_id]['thing_id'])
+        return player
+
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
         self.io.send('TURN ' + str(self.turn))
         for c_id in self.sessions:
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
         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)
+            player = self.get_player(c_id)
+            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(),
-                                           self.map_geometry.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_stencil[t.position] == '.']:
-                self.io.send('THING %s %s %s' % (t.position, t.type_, t.id_), c_id)
-                if hasattr(t, 'nickname'):
-                    self.io.send('THING_NAME %s %s' % (t.id_,
-                                                       quote(t.nickname)), c_id)
-            for yx in [yx for yx in self.portals
-                       if player.fov_stencil[yx] == '.']:
-                self.io.send('PORTAL %s %s' % (yx, quote(self.portals[yx])), 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)
+                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 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)
+            for big_yx in self.annotations:
+                for little_yx in [little_yx for little_yx in self.annotations[big_yx]
+                                  if player.fov_test(big_yx, little_yx)]:
+                    target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
+                    annotation = self.annotations[big_yx][little_yx]
+                    self.io.send('ANNOTATION_HINT %s' % (target_yx,), c_id)
         self.io.send('GAME_STATE_COMPLETE')
 
     def run_tick(self):
         self.io.send('GAME_STATE_COMPLETE')
 
     def run_tick(self):
@@ -124,9 +169,9 @@ class Game(GameBase):
                     connection_id_found = True
                     break
             if not connection_id_found:
                     connection_id_found = True
                     break
             if not connection_id_found:
-                t = self.get_thing(self.sessions[connection_id])
-                if hasattr(t, 'nickname'):
-                    self.io.send('CHAT ' + quote(t.nickname + ' left the map.'))
+                t = self.get_player(connection_id)
+                if hasattr(t, 'name'):
+                    self.io.send('CHAT ' + quote(t.name + ' left the map.'))
                 self.things.remove(t)
                 to_delete += [connection_id]
         for connection_id in to_delete:
                 self.things.remove(t)
                 to_delete += [connection_id]
         for connection_id in to_delete:
@@ -138,11 +183,11 @@ class Game(GameBase):
                     t.proceed()
                 except GameError as e:
                     for connection_id in [c_id for c_id in self.sessions
                     t.proceed()
                 except GameError as e:
                     for connection_id in [c_id for c_id in self.sessions
-                                          if self.sessions[c_id] == t.id_]:
+                                          if self.sessions[c_id]['thing_id'] == t.id_]:
                         self.io.send('GAME_ERROR ' + quote(str(e)), connection_id)
                 except PlayError as e:
                     for connection_id in [c_id for c_id in self.sessions
                         self.io.send('GAME_ERROR ' + quote(str(e)), connection_id)
                 except PlayError as e:
                     for connection_id in [c_id for c_id in self.sessions
-                                          if self.sessions[c_id] == t.id_]:
+                                          if self.sessions[c_id]['thing_id'] == t.id_]:
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
         if self.changed:
             self.turn += 1
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
         if self.changed:
             self.turn += 1
@@ -159,9 +204,9 @@ class Game(GameBase):
             return p
 
         def cmd_TASK_colon(task_name, game, *args, connection_id):
             return p
 
         def cmd_TASK_colon(task_name, game, *args, connection_id):
-            if connection_id not in game.sessions:
+            t = self.get_player(connection_id)
+            if not t:
                 raise GameError('Not registered as player.')
                 raise GameError('Not registered as player.')
-            t = game.get_thing(game.sessions[connection_id])
             t.set_next_task(task_name, args)
 
         def task_prefixed(command_name, task_prefix, task_command):
             t.set_next_task(task_name, args)
 
         def task_prefixed(command_name, task_prefix, task_command):
@@ -187,32 +232,56 @@ class Game(GameBase):
             return 1
         return max([t.id_ for t in self.things]) + 1
 
             return 1
         return max([t.id_ for t in self.things]) + 1
 
+    def get_next_player_char(self):
+        self.player_char_i += 1
+        if self.player_char_i >= len(self.player_chars):
+            self.player_char_i = 0
+        return self.player_chars[self.player_char_i]
+
     def save(self):
 
       def write(f, msg):
           f.write(msg + '\n')
 
       with open(self.io.save_file, 'w') as f:
     def save(self):
 
       def write(f, msg):
           f.write(msg + '\n')
 
       with open(self.io.save_file, 'w') as f:
-          # TODO: save tasks
           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 big_yx in [yx for yx in self.maps if self.maps[yx].modified]:
+              for y, line in self.maps[big_yx].lines():
+                  write(f, 'MAP_LINE %s %5s %s' % (big_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 big_yx in [yx for yx in self.map_controls
+                         if self.map_controls[yx].modified]:
+              for y, line in self.map_controls[big_yx].lines():
+                  write(f, 'MAP_CONTROL_LINE %s %5s %s' % (big_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)))
+
+    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] = SaveableMap(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)] = SaveableMap(self.map_geometry)
+        self.map_controls[YX(0,0)] = SaveableMap(self.map_geometry)
         self.annotations = {}
         self.annotations = {}