home · contact · privacy
Allow player movement beyond central map. Lots of mapping rewrite.
[plomrogue2-experiments] / new / plomrogue / game.py
index e8e80c0f1e8785f00771a3700fce11e5fb0a194e..db71f21e21df5adf7412f7e2279f3a48177a1c5d 100755 (executable)
@@ -1,15 +1,36 @@
-from plomrogue.tasks import Task_WAIT, Task_MOVE, Task_PICKUP, Task_DROP
-from plomrogue.errors import ArgError
-from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE, cmd_MAP,
-                                cmd_MAP, cmd_THING_TYPE, cmd_THING_POS,
-                                cmd_THING_INVENTORY, cmd_GET_PICKABLE_ITEMS,
-                                cmd_TERRAIN_LINE, cmd_PLAYER_ID, cmd_TURN,
-                                cmd_SWITCH_PLAYER, cmd_SAVE)
-from plomrogue.mapping import MapHex
+from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_PICKUP,
+                             Task_DROP, Task_EAT)
+from plomrogue.errors import ArgError, GameError
+from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE,
+                                cmd_MAP, cmd_MAP, cmd_THING_TYPE,
+                                cmd_THING_POS, cmd_THING_INVENTORY,
+                                cmd_THING_HEALTH, cmd_SEED,
+                                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.parser import Parser
 from plomrogue.io import GameIO
 from plomrogue.parser import Parser
 from plomrogue.io import GameIO
-from plomrogue.misc import quote, stringify_yx
-from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingItem
+from plomrogue.misc import quote
+from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingFood
+import random
+
+
+
+class PRNGod(random.Random):
+
+    def seed(self, seed):
+        self.prngod_seed = seed
+
+    def getstate(self):
+        return self.prngod_seed
+
+    def setstate(seed):
+        self.seed(seed)
+
+    def random(self):
+        self.prngod_seed = ((self.prngod_seed * 1103515245) + 12345) % 2**32
+        return (self.prngod_seed >> 16) / (2**16 - 1)
 
 
 
 
 
 
@@ -30,6 +51,13 @@ class WorldBase:
             return t
         return None
 
             return t
         return None
 
+    def things_at_pos(self, pos):
+        things = []
+        for t in self.things:
+            if t.position == pos:
+                things += [t]
+        return things
+
 
 
 class World(WorldBase):
 
 
 class World(WorldBase):
@@ -37,6 +65,9 @@ class World(WorldBase):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.player_id = 0
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.player_id = 0
+        self.player_is_alive = True
+        self.maps = {}
+        self.rand = PRNGod(0)
 
     @property
     def player(self):
 
     @property
     def player(self):
@@ -47,8 +78,16 @@ class World(WorldBase):
             return 0
         return self.things[-1].id_ + 1
 
             return 0
         return self.things[-1].id_ + 1
 
-    def new_map(self, yx):
-        self.map_ = self.game.map_type(yx)
+    def get_map(self, map_pos, create_unfound=True):
+        if not (map_pos in self.maps and
+                self.maps[map_pos].size == self.game.map_size):
+            if create_unfound:
+                self.maps[map_pos] = Map(self.game.map_size)
+                for pos in self.maps[map_pos]:
+                    self.maps[map_pos][pos] = '.'
+            else:
+                return None
+        return self.maps[map_pos]
 
     def proceed_to_next_player_turn(self):
         """Run game world turns until player can decide their next step.
 
     def proceed_to_next_player_turn(self):
         """Run game world turns until player can decide their next step.
@@ -60,45 +99,61 @@ class World(WorldBase):
         (after incrementing the world turn) all that come before the
         player; then the player's .proceed() is run, and if it does
         not finish his task, the loop starts at the beginning. Once
         (after incrementing the world turn) all that come before the
         player; then the player's .proceed() is run, and if it does
         not finish his task, the loop starts at the beginning. Once
-        the player's task is finished, the loop breaks.
+        the player's task is finished, or the player is dead, the loop
+        breaks.
+
         """
         while True:
             player_i = self.things.index(self.player)
             for thing in self.things[player_i+1:]:
                 thing.proceed()
             self.turn += 1
         """
         while True:
             player_i = self.things.index(self.player)
             for thing in self.things[player_i+1:]:
                 thing.proceed()
             self.turn += 1
+            for pos in self.maps[YX(0,0)]:
+                if self.maps[YX(0,0)][pos] == '.' and \
+                   len(self.things_at_pos((YX(0,0), pos))) == 0 and \
+                   self.rand.random() > 0.999:
+                    self.add_thing_at('food', (YX(0,0), pos))
             for thing in self.things[:player_i]:
                 thing.proceed()
             self.player.proceed(is_AI=False)
             for thing in self.things[:player_i]:
                 thing.proceed()
             self.player.proceed(is_AI=False)
-            if self.player.task is None:
+            if self.player.task is None or not self.player_is_alive:
                 break
 
                 break
 
+    def add_thing_at(self, type_, pos):
+        t = self.game.thing_types[type_](self)
+        t.position = pos
+        self.things += [t]
+        return t
+
     def make_new(self, yx, seed):
     def make_new(self, yx, seed):
-        import random
 
 
-        def add_thing(type_):
-            t = self.game.thing_types[type_](self)
-            t.position = (random.randint(0, yx[0] -1),
-                          random.randint(0, yx[1] - 1))
-            self.things += [t]
-            return t
+        def add_thing_at_random(type_):
+            while True:
+                new_pos = (YX(0,0),
+                           YX(self.rand.randint(0, yx.y - 1),
+                              self.rand.randint(0, yx.x - 1)))
+                if self.maps[new_pos[0]][new_pos[1]] != '.':
+                    continue
+                if len(self.things_at_pos(new_pos)) > 0:
+                    continue
+                return self.add_thing_at(type_, new_pos)
 
         self.things = []
 
         self.things = []
-        random.seed(seed)
+        self.rand.seed(seed)
         self.turn = 0
         self.turn = 0
-        self.new_map(yx)
-        for pos in self.map_:
-            if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
-                self.map_[pos] = '#'
-                continue
-            self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
-
-        player = add_thing('human')
+        self.maps = {}
+        self.game.map_size = yx
+        map_ = self.get_map(YX(0,0))
+        for pos in map_:
+            map_[pos] = self.rand.choice(('.', '.', '.', '~', 'x'))
+        player = add_thing_at_random('human')
         self.player_id = player.id_
         self.player_id = player.id_
-        add_thing('monster')
-        add_thing('monster')
-        add_thing('item')
-        add_thing('item')
+        add_thing_at_random('monster')
+        add_thing_at_random('monster')
+        add_thing_at_random('food')
+        add_thing_at_random('food')
+        add_thing_at_random('food')
+        add_thing_at_random('food')
         return 'success'
 
 
         return 'success'
 
 
@@ -107,16 +162,21 @@ class Game:
 
     def __init__(self, game_file_name):
         self.io = GameIO(game_file_name, self)
 
     def __init__(self, game_file_name):
         self.io = GameIO(game_file_name, self)
-        self.map_type = MapHex
+        self.map_size = None
+        self.map_geometry = MapGeometryHex()
         self.tasks = {'WAIT': Task_WAIT,
                       'MOVE': Task_MOVE,
                       'PICKUP': Task_PICKUP,
         self.tasks = {'WAIT': Task_WAIT,
                       'MOVE': Task_MOVE,
                       'PICKUP': Task_PICKUP,
+                      'EAT': Task_EAT,
                       'DROP': Task_DROP}
         self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
                          'GET_GAMESTATE': cmd_GET_GAMESTATE,
                       'DROP': Task_DROP}
         self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
                          'GET_GAMESTATE': cmd_GET_GAMESTATE,
+                         'SEED': cmd_SEED,
+                         'MAP_SIZE': cmd_MAP_SIZE,
                          'MAP': cmd_MAP,
                          'THING_TYPE': cmd_THING_TYPE,
                          'THING_POS': cmd_THING_POS,
                          'MAP': cmd_MAP,
                          'THING_TYPE': cmd_THING_TYPE,
                          'THING_POS': cmd_THING_POS,
+                         'THING_HEALTH': cmd_THING_HEALTH,
                          'THING_INVENTORY': cmd_THING_INVENTORY,
                          'TERRAIN_LINE': cmd_TERRAIN_LINE,
                          'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
                          'THING_INVENTORY': cmd_THING_INVENTORY,
                          'TERRAIN_LINE': cmd_TERRAIN_LINE,
                          'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
@@ -129,11 +189,11 @@ class Game:
         self.thing_type = Thing
         self.thing_types = {'human': ThingHuman,
                             'monster': ThingMonster,
         self.thing_type = Thing
         self.thing_types = {'human': ThingHuman,
                             'monster': ThingMonster,
-                            'item': ThingItem}
+                            'food': ThingFood}
 
     def get_string_options(self, string_option_type):
         if string_option_type == 'direction':
 
     def get_string_options(self, string_option_type):
         if string_option_type == 'direction':
-            return self.world.map_.get_directions()
+            return self.map_geometry.get_directions()
         elif string_option_type == 'thingtype':
             return list(self.thing_types.keys())
         return None
         elif string_option_type == 'thingtype':
             return list(self.thing_types.keys())
         return None
@@ -141,16 +201,25 @@ class Game:
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
+        def send_thing(offset, thing):
+            offset_pos = self.map_geometry.pos_in_projection(thing.position,
+                                                             offset,
+                                                             self.map_size)
+            self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
+            self.io.send('THING_POS %s %s' % (thing.id_, offset_pos))
+
         self.io.send('TURN ' + str(self.world.turn))
         self.io.send('TURN ' + str(self.world.turn))
-        self.io.send('MAP ' + stringify_yx(self.world.map_.size))
         visible_map = self.world.player.get_visible_map()
         visible_map = self.world.player.get_visible_map()
+        offset = self.world.player.get_surroundings_offset()
+        self.io.send('VISIBLE_MAP %s %s' % (offset, visible_map.size))
         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()
         for thing in visible_things:
         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()
         for thing in visible_things:
-            self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
-            self.io.send('THING_POS %s %s' % (thing.id_,
-                                              stringify_yx(thing.position)))
+            send_thing(offset, thing)
+            if hasattr(thing, 'health'):
+                self.io.send('THING_HEALTH %s %s' % (thing.id_,
+                                                     thing.health))
         if len(self.world.player.inventory) > 0:
             self.io.send('PLAYER_INVENTORY %s' %
                          ','.join([str(i) for i in self.world.player.inventory]))
         if len(self.world.player.inventory) > 0:
             self.io.send('PLAYER_INVENTORY %s' %
                          ','.join([str(i) for i in self.world.player.inventory]))
@@ -158,9 +227,7 @@ class Game:
             self.io.send('PLAYER_INVENTORY ,')
         for id_ in self.world.player.inventory:
             thing = self.world.get_thing(id_)
             self.io.send('PLAYER_INVENTORY ,')
         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)))
+            send_thing(offset, thing)
         self.io.send('GAME_STATE_COMPLETE')
 
     def proceed(self):
         self.io.send('GAME_STATE_COMPLETE')
 
     def proceed(self):
@@ -184,6 +251,8 @@ class Game:
             return p
 
         def cmd_TASK_colon(task_name, game, *args):
             return p
 
         def cmd_TASK_colon(task_name, game, *args):
+            if not game.world.player_is_alive:
+                raise GameError('You are dead.')
             game.world.player.set_task(task_name, args)
             game.proceed()
 
             game.world.player.set_task(task_name, args)
             game.proceed()