home · contact · privacy
Flatten game->world hierarchy.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 30 Apr 2019 17:34:35 +0000 (19:34 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 30 Apr 2019 17:34:35 +0000 (19:34 +0200)
new/example_client.py
new/plomrogue/commands.py
new/plomrogue/game.py
new/plomrogue/tasks.py
new/plomrogue/things.py

index a88c85b3c2d642f2a7a9cce5dc3be95f46b6e010..2a4b80cc6e03fe722033790a310f84e90c830f64 100755 (executable)
@@ -4,7 +4,7 @@ import socket
 import threading
 from plomrogue.parser import ArgError, Parser
 from plomrogue.commands import cmd_PLAYER_ID, cmd_THING_HEALTH
-from plomrogue.game import Game, WorldBase
+from plomrogue.game import GameBase
 from plomrogue.mapping import Map, MapGeometryHex, YX
 from plomrogue.io import PlomSocket
 from plomrogue.things import ThingBase
@@ -65,30 +65,6 @@ class ClientMap(Map):
         return map_lines
 
 
-class World(WorldBase):
-
-    def __init__(self, *args, **kwargs):
-        """Extend original with local classes and empty default map.
-
-        We need the empty default map because we draw the map widget
-        on any update, even before we actually receive map data.
-        """
-        super().__init__(*args, **kwargs)
-        self.map_ = ClientMap()
-        self.offset = YX(0,0)
-        self.player_inventory = []
-        self.player_id = 0
-        self.pickable_items = []
-
-    def new_map(self, offset, size):
-        self.map_ = ClientMap(size)
-        self.offset = offset
-
-    @property
-    def player(self):
-        return self.get_thing(self.player_id)
-
-
 def cmd_LAST_PLAYER_TASK_RESULT(game, msg):
     if msg != "success":
         game.log(msg)
@@ -103,19 +79,19 @@ cmd_TURN_FINISHED.argtypes = 'int:nonneg'
 
 def cmd_TURN(game, n):
     """Set game.turn to n, empty game.things."""
-    game.world.turn = n
-    game.world.things = []
-    game.world.pickable_items[:] = []
+    game.turn = n
+    game.things = []
+    game.pickable_items[:] = []
 cmd_TURN.argtypes = 'int:nonneg'
 
 
 def cmd_VISIBLE_MAP(game, offset, size):
-    game.world.new_map(offset, size)
+    game.new_map(offset, size)
 cmd_VISIBLE_MAP.argtypes = 'yx_tuple yx_tuple:pos'
 
 
 def cmd_VISIBLE_MAP_LINE(game, y, terrain_line):
-    game.world.map_.set_line(y, terrain_line)
+    game.map_.set_line(y, terrain_line)
 cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string'
 
 
@@ -126,34 +102,39 @@ def cmd_GAME_STATE_COMPLETE(game):
 
 
 def cmd_THING_TYPE(game, i, type_):
-    t = game.world.get_thing(i)
+    t = game.get_thing(i)
     t.type_ = type_
 cmd_THING_TYPE.argtypes = 'int:nonneg string'
 
 
 def cmd_THING_POS(game, i, yx):
-    t = game.world.get_thing(i)
+    t = game.get_thing(i)
     t.position = YX(0,0), yx
 cmd_THING_POS.argtypes = 'int:nonneg yx_tuple:nonneg'
 
 
 def cmd_PLAYER_INVENTORY(game, ids):
-    game.world.player_inventory[:] = ids  # TODO: test whether valid IDs
+    game.player_inventory[:] = ids  # TODO: test whether valid IDs
     game.tui.to_update['inventory'] = True
 cmd_PLAYER_INVENTORY.argtypes = 'seq:int:nonneg'
 
 
 def cmd_PICKABLE_ITEMS(game, ids):
-    game.world.pickable_items[:] = ids
+    game.pickable_items[:] = ids
     game.tui.to_update['pickable_items'] = True
 cmd_PICKABLE_ITEMS.argtypes = 'seq:int:nonneg'
 
 
-class Game:
+class Game(GameBase):
 
-    def __init__(self):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.map_ = ClientMap()  # we need an empty default map cause we draw
+        self.offset = YX(0,0)    # the map widget even before we get a real one
+        self.player_inventory = []
+        self.player_id = 0
+        self.pickable_items = []
         self.parser = Parser(self)
-        self.world = World(self)
         self.map_geometry = MapGeometryHex()
         self.thing_type = ThingBase
         self.commands = {'LAST_PLAYER_TASK_RESULT': cmd_LAST_PLAYER_TASK_RESULT,
@@ -172,6 +153,14 @@ class Game:
         self.do_quit = False
         self.tui = None
 
+    def new_map(self, offset, size):
+        self.map_ = ClientMap(size)
+        self.offset = offset
+
+    @property
+    def player(self):
+        return self.get_thing(self.player_id)
+
     def get_command(self, command_name):
         from functools import partial
         if command_name in self.commands:
@@ -333,11 +322,11 @@ class DescriptorWidget(TextLinesWidget):
 
     def get_text_lines(self):
         lines = []
-        pos_i = self.tui.game.world.map_.\
+        pos_i = self.tui.game.map_.\
                 get_position_index(self.tui.examiner_position[1])
-        terrain = self.tui.game.world.map_.terrain[pos_i]
+        terrain = self.tui.game.map_.terrain[pos_i]
         lines = [terrain]
-        for t in self.tui.game.world.things_at_pos(self.tui.examiner_position):
+        for t in self.tui.game.things_at_pos(self.tui.examiner_position):
             lines += [t.type_]
         return lines
 
@@ -381,7 +370,7 @@ class ItemsSelectorWidget(Widget):
         counter = 0
         for id_ in self.selection:
             pointer = '*' if counter == self.tui.item_pointer else ' '
-            t = self.tui.game.world.get_thing(id_)
+            t = self.tui.game.get_thing(id_)
             lines += ['%s %s' % (pointer, t.type_)]
             counter += 1
         line_width = self.size.x
@@ -399,12 +388,11 @@ class MapWidget(Widget):
     def draw(self):
 
         def annotated_terrain():
-            terrain_as_list = list(self.tui.game.world.map_.terrain[:])
-            for t in self.tui.game.world.things:
-                if t.id_ in self.tui.game.world.player_inventory:
+            terrain_as_list = list(self.tui.game.map_.terrain[:])
+            for t in self.tui.game.things:
+                if t.id_ in self.tui.game.player_inventory:
                     continue
-                pos_i = self.tui.game.world.map_.\
-                        get_position_index(t.position[1])
+                pos_i = self.tui.game.map_.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]
@@ -414,7 +402,7 @@ class MapWidget(Widget):
                 else:
                     terrain_as_list[pos_i] = symbol
             if self.tui.examiner_mode:
-                pos_i = self.tui.game.world.map_.\
+                pos_i = self.tui.game.map_.\
                         get_position_index(self.tui.examiner_position[1])
                 terrain_as_list[pos_i] = (terrain_as_list[pos_i][0], '?')
             return terrain_as_list
@@ -451,18 +439,18 @@ class MapWidget(Widget):
                     chars_with_attrs += [c]
             return chars_with_attrs
 
-        if self.tui.game.world.map_.terrain == '':
+        if self.tui.game.map_.terrain == '':
             lines = []
             pad_y(lines)
             self.safe_write(''.join(lines))
             return
 
         annotated_terrain = annotated_terrain()
-        center = self.tui.game.world.player.position
+        center = self.tui.game.player.position
         if self.tui.examiner_mode:
             center = self.tui.examiner_position
-        indent_first_line = not bool(self.tui.game.world.offset.y % 2)
-        lines = self.tui.game.world.map_.\
+        indent_first_line = not bool(self.tui.game.offset.y % 2)
+        lines = self.tui.game.map_.\
                 format_to_view(annotated_terrain, center, self.size,
                                indent_first_line)
         pad_or_cut_x(lines)
@@ -473,14 +461,14 @@ class MapWidget(Widget):
 class TurnWidget(Widget):
 
     def draw(self):
-        self.safe_write((str(self.tui.game.world.turn), curses.color_pair(2)))
+        self.safe_write((str(self.tui.game.turn), curses.color_pair(2)))
 
 
 class HealthWidget(Widget):
 
     def draw(self):
-        if hasattr(self.tui.game.world.player, 'health'):
-            self.safe_write((str(self.tui.game.world.player.health),
+        if hasattr(self.tui.game.player, 'health'):
+            self.safe_write((str(self.tui.game.player.health),
                              curses.color_pair(2)))
 
 
@@ -548,7 +536,7 @@ class TUI:
                 return True
 
             selectables_menu(key, pickable_items_widget,
-                             self.game.world.pickable_items, f)
+                             self.game.pickable_items, f)
 
         def inventory_menu(key):
 
@@ -564,12 +552,12 @@ class TUI:
                 return True
 
             selectables_menu(key, inventory_widget,
-                             self.game.world.player_inventory, f)
+                             self.game.player_inventory, f)
 
         def move_examiner(direction):
             start_pos = self.examiner_position
             new_examine_pos = self.game.map_geometry.move(start_pos, direction,
-                                                          self.game.world.map_.size)
+                                                          self.game.map_.size)
             if new_examine_pos[0] == (0,0):
                 self.examiner_position = new_examine_pos
             self.to_update['map'] = True
@@ -587,7 +575,7 @@ class TUI:
                 switch_widgets(descriptor_widget, log_widget)
             else:
                 self.examiner_mode = True
-                self.examiner_position = self.game.world.player.position
+                self.examiner_position = self.game.player.position
                 switch_widgets(log_widget, descriptor_widget)
             self.to_update['map'] = True
 
@@ -668,11 +656,11 @@ class TUI:
                                              ['map'], False)
         map_widget = MapWidget(self, YX(0, 21), YX(None, None), ['map'])
         inventory_widget = ItemsSelectorWidget('INVENTORY:',
-                                               self.game.world.player_inventory,
+                                               self.game.player_inventory,
                                                self, YX(0, 21), YX(None, None),
                                                ['inventory'], False)
         pickable_items_widget = ItemsSelectorWidget('PICKABLE:',
-                                                    self.game.world.pickable_items,
+                                                    self.game.pickable_items,
                                                     self, YX(0, 21),
                                                     YX(None, None),
                                                     ['pickable_items'],
index bcab2d10702c13bda99a2fee684e210b5e7fa979..744d471fac100bc5e326813899d93466a353566e 100644 (file)
@@ -3,7 +3,7 @@ from plomrogue.misc import quote
 
 
 def cmd_GEN_WORLD(game, yx, seed):
-    game.world.make_new(yx, seed)
+    game.make_new_world(yx, seed)
 cmd_GEN_WORLD.argtypes = 'yx_tuple:pos int:nonneg'
 
 def cmd_GET_GAMESTATE(game, connection_id):
@@ -11,7 +11,7 @@ def cmd_GET_GAMESTATE(game, connection_id):
     game.send_gamestate(connection_id)
 
 def cmd_SEED(game, seed):
-    game.world.rand.prngod_seed = seed
+    game.rand.prngod_seed = seed
 cmd_SEED.argtypes = 'int:nonneg'
 
 def cmd_MAP_SIZE(game, size):
@@ -20,12 +20,12 @@ cmd_MAP_SIZE.argtypes = 'yx_tuple:pos'
 
 def cmd_MAP(game, map_pos):
     """Ensure (possibly empty/'?'-filled) map at position map_pos."""
-    game.world.get_map(map_pos)
+    game.get_map(map_pos)
 cmd_MAP.argtypes = 'yx_tuple'
 
 def cmd_THING_TYPE(game, i, type_):
-    t_old = game.world.get_thing(i)
-    t_new = game.thing_types[type_](game.world, i)
+    t_old = game.get_thing(i)
+    t_new = game.thing_types[type_](game, i)
     #attr_names_of_old = [name for name in dir(t_old) where name[:2] != '__']
     #attr_names_of_new = [name for name in dir(t_new) where name[:2] != '__']
     #class_new = type(t_new)
@@ -41,31 +41,31 @@ def cmd_THING_TYPE(game, i, type_):
     #    setattr(t_new, attr_name, attr_old)
     t_new.position = t_old.position
     t_new.in_inventory = t_old.in_inventory
-    t_old_index = game.world.things.index(t_old)
-    game.world.things[t_old_index] = t_new
+    t_old_index = game.things.index(t_old)
+    game.things[t_old_index] = t_new
 cmd_THING_TYPE.argtypes = 'int:nonneg string:thingtype'
 
 def cmd_THING_POS(game, i, big_yx, small_yx):
-    t = game.world.get_thing(i)
+    t = game.get_thing(i)
     t.position = (big_yx, small_yx)
 cmd_THING_POS.argtypes = 'int:nonneg yx_tuple yx_tuple:nonneg'
 
 def cmd_THING_INVENTORY(game, id_, ids):
-    carrier = game.world.get_thing(id_)
+    carrier = game.get_thing(id_)
     carrier.inventory = ids
     for id_ in ids:
-        t = game.world.get_thing(id_)
+        t = game.get_thing(id_)
         t.in_inventory = True
         t.position = carrier.position
 cmd_THING_INVENTORY.argtypes = 'int:nonneg seq:int:nonneg'
 
 def cmd_THING_HEALTH(game, id_, health):
-    t = game.world.get_thing(id_)
+    t = game.get_thing(id_)
     t.health = health
 cmd_THING_HEALTH.argtypes = 'int:nonneg int:nonneg'
 
 def cmd_GET_PICKABLE_ITEMS(game, connection_id):
-    pickable_ids = game.world.player.get_pickable_items()
+    pickable_ids = game.player.get_pickable_items()
     if len(pickable_ids) > 0:
         game.io.send('PICKABLE_ITEMS %s' %
                      ','.join([str(id_) for id_ in pickable_ids]))
@@ -73,26 +73,26 @@ def cmd_GET_PICKABLE_ITEMS(game, connection_id):
         game.io.send('PICKABLE_ITEMS ,')
 
 def cmd_TERRAIN_LINE(game, big_yx, y, terrain_line):
-    game.world.maps[big_yx].set_line(y, terrain_line)
+    game.maps[big_yx].set_line(y, terrain_line)
 cmd_TERRAIN_LINE.argtypes = 'yx_tuple int:nonneg string'
 
 def cmd_PLAYER_ID(game, id_):
     # TODO: test whether valid thing ID
-    game.world.player_id = id_
+    game.player_id = id_
 cmd_PLAYER_ID.argtypes = 'int:nonneg'
 
 def cmd_TURN(game, n):
-    game.world.turn = n
+    game.turn = n
 cmd_TURN.argtypes = 'int:nonneg'
 
 def cmd_SWITCH_PLAYER(game):
-    game.world.player.set_task('WAIT')
-    thing_ids = [t.id_ for t in game.world.things]
-    player_index = thing_ids.index(game.world.player.id_)
+    game.player.set_task('WAIT')
+    thing_ids = [t.id_ for t in game.things]
+    player_index = thing_ids.index(game.player.id_)
     if player_index == len(thing_ids) - 1:
-        game.world.player_id = thing_ids[0]
+        game.player_id = thing_ids[0]
     else:
-        game.world.player_id = thing_ids[player_index + 1]
+        game.player_id = thing_ids[player_index + 1]
     game.proceed()
 
 def cmd_SAVE(game):
@@ -102,15 +102,15 @@ 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, 'SEED %s' % game.world.rand.prngod_seed)
+        write(f, 'TURN %s' % game.turn)
+        write(f, 'SEED %s' % game.rand.prngod_seed)
         write(f, 'MAP_SIZE %s' % (game.map_size,))
-        for map_pos in game.world.maps:
+        for map_pos in game.maps:
             write(f, 'MAP %s' % (map_pos,))
-        for map_pos in game.world.maps:
-            for y, line in game.world.maps[map_pos].lines():
+        for map_pos in game.maps:
+            for y, line in game.maps[map_pos].lines():
                  write(f, 'TERRAIN_LINE %s %5s %s' % (map_pos, y, quote(line)))
-        for thing in game.world.things:
+        for thing in game.things:
             write(f, 'THING_TYPE %s %s' % (thing.id_, thing.type_))
             write(f, 'THING_POS %s %s %s' % (thing.id_, thing.position[0],
                                              thing.position[1]))
@@ -129,5 +129,5 @@ def cmd_SAVE(game):
                                  if game.tasks[k] == task.__class__][0]
                     write(f, 'SET_TASK:%s %s %s %s' % (task_name, thing.id_,
                                                        task.todo, task_args))
-        write(f, 'PLAYER_ID %s' % game.world.player_id)
+        write(f, 'PLAYER_ID %s' % game.player_id)
 cmd_SAVE.dont_save = True
index db71f21e21df5adf7412f7e2279f3a48177a1c5d..6b2c74b62f7427e9ae4daa9e705e0f740f16745e 100755 (executable)
@@ -34,19 +34,18 @@ class PRNGod(random.Random):
 
 
 
-class WorldBase:
+class GameBase:
 
-    def __init__(self, game):
+    def __init__(self):
         self.turn = 0
         self.things = []
-        self.game = game
 
     def get_thing(self, id_, create_unfound=True):
         for thing in self.things:
             if id_ == thing.id_:
                 return thing
         if create_unfound:
-            t = self.game.thing_type(self, id_)
+            t = self.thing_type(self, id_)
             self.things += [t]
             return t
         return None
@@ -60,107 +59,10 @@ class WorldBase:
 
 
 
-class World(WorldBase):
+class Game(GameBase):
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, game_file_name, *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):
-        return self.get_thing(self.player_id)
-
-    def new_thing_id(self):
-        if len(self.things) == 0:
-            return 0
-        return self.things[-1].id_ + 1
-
-    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.
-
-        Iterates through all non-player things, on each step
-        furthering them in their tasks (and letting them decide new
-        ones if they finish). The iteration order is: first all things
-        that come after the player in the world things list, then
-        (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, 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
-            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)
-            if self.player.task is None or not self.player_is_alive:
-                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 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.rand.seed(seed)
-        self.turn = 0
-        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_
-        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'
-
-
-
-class Game:
-
-    def __init__(self, game_file_name):
         self.io = GameIO(game_file_name, self)
         self.map_size = None
         self.map_geometry = MapGeometryHex()
@@ -184,12 +86,14 @@ class Game:
                          'TURN': cmd_TURN,
                          'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
                          'SAVE': cmd_SAVE}
-        self.world_type = World
-        self.world = self.world_type(self)
         self.thing_type = Thing
         self.thing_types = {'human': ThingHuman,
                             'monster': ThingMonster,
                             'food': ThingFood}
+        self.player_id = 0
+        self.player_is_alive = True
+        self.maps = {}
+        self.rand = PRNGod(0)
 
     def get_string_options(self, string_option_type):
         if string_option_type == 'direction':
@@ -208,25 +112,25 @@ class Game:
             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))
-        visible_map = self.world.player.get_visible_map()
-        offset = self.world.player.get_surroundings_offset()
+        self.io.send('TURN ' + str(self.turn))
+        visible_map = self.player.get_visible_map()
+        offset = self.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()
+        visible_things = self.player.get_visible_things()
         for thing in visible_things:
             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:
+        if len(self.player.inventory) > 0:
             self.io.send('PLAYER_INVENTORY %s' %
-                         ','.join([str(i) for i in self.world.player.inventory]))
+                         ','.join([str(i) for i in self.player.inventory]))
         else:
             self.io.send('PLAYER_INVENTORY ,')
-        for id_ in self.world.player.inventory:
-            thing = self.world.get_thing(id_)
+        for id_ in self.player.inventory:
+            thing = self.get_thing(id_)
             send_thing(offset, thing)
         self.io.send('GAME_STATE_COMPLETE')
 
@@ -236,9 +140,9 @@ class Game:
         First sends 'TURN_FINISHED' message, then runs game world
         until new player input is needed, then sends game state.
         """
-        self.io.send('TURN_FINISHED ' + str(self.world.turn))
-        self.world.proceed_to_next_player_turn()
-        msg = str(self.world.player._last_task_result)
+        self.io.send('TURN_FINISHED ' + str(self.turn))
+        self.proceed_to_next_player_turn()
+        msg = str(self.player._last_task_result)
         self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
         self.send_gamestate()
 
@@ -251,13 +155,13 @@ class Game:
             return p
 
         def cmd_TASK_colon(task_name, game, *args):
-            if not game.world.player_is_alive:
+            if not game.player_is_alive:
                 raise GameError('You are dead.')
-            game.world.player.set_task(task_name, args)
+            game.player.set_task(task_name, args)
             game.proceed()
 
         def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
-            t = game.world.get_thing(thing_id, False)
+            t = game.get_thing(thing_id, False)
             if t is None:
                 raise ArgError('No such Thing.')
             task_class = game.tasks[task_name]
@@ -289,3 +193,91 @@ class Game:
             f = partial_with_attrs(self.commands[command_name], self)
             return f
         return None
+
+    @property
+    def player(self):
+        return self.get_thing(self.player_id)
+
+    def new_thing_id(self):
+        if len(self.things) == 0:
+            return 0
+        return self.things[-1].id_ + 1
+
+    def get_map(self, map_pos, create_unfound=True):
+        if not (map_pos in self.maps and
+                self.maps[map_pos].size == self.map_size):
+            if create_unfound:
+                self.maps[map_pos] = Map(self.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.
+
+        Iterates through all non-player things, on each step
+        furthering them in their tasks (and letting them decide new
+        ones if they finish). The iteration order is: first all things
+        that come after the player in the world things list, then
+        (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, 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
+            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)
+            if self.player.task is None or not self.player_is_alive:
+                break
+
+    def add_thing_at(self, type_, pos):
+        t = self.thing_types[type_](self)
+        t.position = pos
+        self.things += [t]
+        return t
+
+    def make_new_world(self, yx, seed):
+
+        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.rand.seed(seed)
+        self.turn = 0
+        self.maps = {}
+        self.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_
+        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'
+
index 6d67c35b1b088b0bf5b617525530668d8c1acbac..11d4ba33ca69b125366f4262f4a65bfc8816617f 100644 (file)
@@ -39,15 +39,15 @@ class Task_MOVE(Task):
     argtypes = 'string:direction'
 
     def get_move_target(self):
-        return self.thing.world.game.map_geometry.move(self.thing.position,
-                                                       self.args[0],
-                                                       self.thing.world.game.map_size)
+        return self.thing.game.map_geometry.move(self.thing.position,
+                                                 self.args[0],
+                                                 self.thing.game.map_size)
 
     def check(self):
         test_pos = self.get_move_target()
-        if self.thing.world.maps[test_pos[0]][test_pos[1]] != '.':
+        if self.thing.game.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):
+        for t in self.thing.game.things_at_pos(test_pos):
             if t.blocking:
                 raise GameError('%s would move into other thing' % self.thing.id_)
 
@@ -60,15 +60,15 @@ class Task_PICKUP(Task):
     argtypes = 'int:nonneg'
 
     def check(self):
-        to_pick_up = self.thing.world.get_thing(self.args[0],
-                                                create_unfound=False)
+        to_pick_up = self.thing.game.get_thing(self.args[0],
+                                               create_unfound=False)
         if to_pick_up is None or \
            to_pick_up.id_ not in self.thing.get_pickable_items():
             raise GameError('thing of ID %s not in reach to pick up'
                             % self.args[0])
 
     def do(self):
-        to_pick_up = self.thing.world.get_thing(self.args[0])
+        to_pick_up = self.thing.game.get_thing(self.args[0])
         self.thing.inventory += [self.args[0]]
         to_pick_up.in_inventory = True
         to_pick_up.position = self.thing.position
@@ -79,7 +79,7 @@ class TaskOnInventoryItem(Task):
     argtypes = 'int:nonneg'
 
     def _basic_inventory_item_check(self):
-        item = self.thing.world.get_thing(self.args[0], create_unfound=False)
+        item = self.thing.game.get_thing(self.args[0], create_unfound=False)
         if item is None:
             raise GameError('no thing of ID %s' % self.args[0])
         if item.id_ not in self.thing.inventory:
@@ -87,7 +87,7 @@ class TaskOnInventoryItem(Task):
         return item
 
     def _eliminate_from_inventory(self):
-        item = self.thing.world.get_thing(self.args[0])
+        item = self.thing.game.get_thing(self.args[0])
         del self.thing.inventory[self.thing.inventory.index(item.id_)]
         item.in_inventory = False
         return item
@@ -115,5 +115,5 @@ class Task_EAT(TaskOnInventoryItem):
 
     def do(self):
         to_eat = self._eliminate_from_inventory()
-        del self.thing.world.things[self.thing.world.things.index(to_eat)]
+        del self.thing.game.things[self.thing.game.things.index(to_eat)]
         self.thing.health += 50
index 4f35880b7689644a1e3f1c941f02a93898130077..2e97541a8cb2416eaa3ead7bad023c4b9d75ce3d 100644 (file)
@@ -6,10 +6,10 @@ from plomrogue.mapping import YX, Map, FovMapHex
 class ThingBase:
     type_ = '?'
 
-    def __init__(self, world, id_=None, position=(YX(0,0), YX(0,0))):
-        self.world = world
+    def __init__(self, game, id_=None, position=(YX(0,0), YX(0,0))):
+        self.game = game
         if id_ is None:
-            self.id_ = self.world.new_thing_id()
+            self.id_ = self.game.new_thing_id()
         else:
             self.id_ = id_
         self.position = position
@@ -51,30 +51,44 @@ class Thing(ThingBase):
     def _position_set(self, pos):
         super()._position_set(pos)
         for t_id in self.inventory:
-            t = self.world.get_thing(t_id)
+            t = self.game.get_thing(t_id)
             t.position = self.position
-        if not self.id_ == self.world.player_id:
+        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.world.get_map(self.position[0] + YX(1,-1))
-            self.world.get_map(self.position[0] + YX(0,-1))
-            self.world.get_map(self.position[0] + YX(-1,-1))
-        if edge_right >= self.world.game.map_size.x:
-            self.world.get_map(self.position[0] + YX(1,1))
-            self.world.get_map(self.position[0] + YX(0,1))
-            self.world.get_map(self.position[0] + YX(-1,1))
+            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.world.get_map(self.position[0] + YX(-1,1))
-            self.world.get_map(self.position[0] + YX(-1,0))
-            self.world.get_map(self.position[0] + YX(-1,-1))
-        if edge_down >= self.world.game.map_size.y:
-            self.world.get_map(self.position[0] + YX(1,1))
-            self.world.get_map(self.position[0] + YX(1,0))
-            self.world.get_map(self.position[0] + YX(1,-1))
+            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))
 
 
 
@@ -110,16 +124,16 @@ class ThingAnimate(Thing):
             for pos in dijkstra_map:
                 if visible_map[pos] != '.':
                     continue
-                neighbors = self.world.game.map_geometry.get_neighbors((YX(0,0), pos),
-                                                                       dijkstra_map.size)
+                neighbors = self.game.map_geometry.get_neighbors((YX(0,0), pos),
+                                                                 dijkstra_map.size)
                 for direction in neighbors:
                     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
-        neighbors = self.world.game.map_geometry.get_neighbors((YX(0,0), own_pos),
-                                                               dijkstra_map.size)
+        neighbors = self.game.map_geometry.get_neighbors((YX(0,0), own_pos),
+                                                         dijkstra_map.size)
         n = n_max
         target_direction = None
         for direction in sorted(neighbors.keys()):
@@ -153,12 +167,12 @@ class ThingAnimate(Thing):
 
     def hunt_food_satisfaction(self):
         for id_ in self.inventory:
-            t = self.world.get_thing(id_)
+            t = self.game.get_thing(id_)
             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_)
             if t.type_ == 'food':
                 self.set_task('PICKUP', (id_,))
                 return True
@@ -185,7 +199,7 @@ class ThingAnimate(Thing):
             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
 
@@ -193,8 +207,8 @@ class ThingAnimate(Thing):
         """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).
 
@@ -206,10 +220,10 @@ class ThingAnimate(Thing):
         self.unset_surroundings()
         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:
-                del self.world.things[self.world.things.index(self)]
+                del self.game.things[self.game.things.index(self)]
             return
         try:
             self.task.check()
@@ -240,9 +254,9 @@ class ThingAnimate(Thing):
     def get_surroundings_offset(self):
         if self._surroundings_offset is not None:
             return self._surroundings_offset
-        offset = YX(self.position[0].y * self.world.game.map_size.y +
+        offset = YX(self.position[0].y * self.game.map_size.y +
                     self.position[1].y - self._radius,
-                    self.position[0].x * self.world.game.map_size.x +
+                    self.position[0].x * self.game.map_size.x +
                     self.position[1].x - self._radius)
         self._surroundings_offset = offset
         return self._surroundings_offset
@@ -254,10 +268,11 @@ class ThingAnimate(Thing):
         offset = self.get_surroundings_offset()
         for pos in self._surrounding_map:
             offset_pos = pos + offset
-            big_yx, small_yx = self.world.game.map_geometry.absolutize_coordinate(self.world.game.map_size, (0,0), offset_pos)
-            map_ = self.world.get_map(big_yx, False)
+            absolutize = self.game.map_geometry.absolutize_coordinate
+            big_yx, small_yx = absolutize(self.game.map_size, (0,0), offset_pos)
+            map_ = self.game.get_map(big_yx, False)
             if map_ is None:
-                map_ = Map(size=self.world.game.map_size)
+                map_ = Map(size=self.game.map_size)
             self._surrounding_map[pos] = map_[small_yx]
         return self._surrounding_map
 
@@ -285,10 +300,10 @@ class ThingAnimate(Thing):
         stencil = self.get_stencil()
         offset = self.get_surroundings_offset()
         visible_things = []
-        for thing in self.world.things:
-            pos = self.world.game.map_geometry.pos_in_projection(thing.position,
-                                                                 offset,
-                                                                 self.world.game.map_size)
+        for thing in self.game.things:
+            pos = self.game.map_geometry.pos_in_projection(thing.position,
+                                                           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
@@ -299,8 +314,8 @@ class ThingAnimate(Thing):
     def get_pickable_items(self):
         pickable_ids = []
         visible_things = self.get_visible_things()
-        neighbor_fields = self.world.game.map_geometry.get_neighbors(self.position,
-                                                                     self.world.game.map_size)
+        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