changeset 145:0c49627920eb

Load game objects from level.
author Jeremy Thurgood <firxen@gmail.com>
date Mon, 02 Sep 2013 23:05:25 +0200
parents 829c8b6e142d
children fff8e14858b4
files data/levels/level1 nagslang/game_object.py nagslang/level.py nagslang/screens/area.py nagslang/tests/test_game_object.py nagslang/tests/test_level.py
diffstat 6 files changed, 164 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/data/levels/level1	Mon Sep 02 22:40:14 2013 +0200
+++ b/data/levels/level1	Mon Sep 02 23:05:25 2013 +0200
@@ -17,3 +17,30 @@
   - [60, 470]
   - [60, 780]
 size: [1200, 900]
+game_objects:
+  - classname: Box
+    args:
+      - [250, 350]
+  - classname: FloorSwitch
+    args:
+      - [300, 400]
+    name: switch
+  - classname: FloorSwitch
+    args:
+      - [300, 600]
+    name: switch2
+  - classname: FloorLight
+    args:
+      - [300, 500]
+      - switch
+    name: light
+  - classname: FloorLight
+    args:
+      - [250, 500]
+      - both_switches
+    name: light2
+  - classname: StateLogicalAndPuzzler
+    args:
+      - switch
+      - switch2
+    name: both_switches
--- a/nagslang/game_object.py	Mon Sep 02 22:40:14 2013 +0200
+++ b/nagslang/game_object.py	Mon Sep 02 23:05:25 2013 +0200
@@ -16,6 +16,8 @@
         self._components = {}
 
     def add_component(self, name, puzzler):
+        if not isinstance(puzzler, Puzzler):
+            puzzler = puzzler.puzzler
         self._components[name] = puzzler
         puzzler.set_glue(self)
 
@@ -276,7 +278,7 @@
 
 def make_body(mass, moment, position, damping=None):
     body = pymunk.Body(mass, moment)
-    body.position = position
+    body.position = tuple(position)
     if damping is not None:
         body.damping = damping
         body.velocity_func = damping_velocity_func
@@ -321,8 +323,7 @@
 
 class FloorSwitch(GameObject):
     def __init__(self, space, position):
-        body = pymunk.Body()
-        body.position = position
+        body = make_body(None, None, position)
         self.shape = pymunk.Circle(body, 30)
         self.shape.collision_type = COLLISION_TYPE_SWITCH
         self.shape.sensor = True
@@ -335,8 +336,7 @@
 
 class FloorLight(GameObject):
     def __init__(self, space, position, state_source):
-        body = pymunk.Body()
-        body.position = position
+        body = make_body(None, None, position)
         self.shape = pymunk.Circle(body, 10)
         self.shape.collision_type = COLLISION_TYPE_SWITCH
         self.shape.sensor = True
--- a/nagslang/level.py	Mon Sep 02 22:40:14 2013 +0200
+++ b/nagslang/level.py	Mon Sep 02 23:05:25 2013 +0200
@@ -1,6 +1,7 @@
 import pygame
 import pygame.locals as pgl
 
+from nagslang import game_object as go
 from nagslang.resources import resources
 from nagslang.yamlish import load, dump
 
@@ -26,16 +27,40 @@
         self._tile_image = None
         self._surface = None
         self._exterior = False
+        self._glue = go.PuzzleGlue()
+        self._drawables = []
 
-    def load(self):
+    def _get_data(self):
+        # For overriding in tests.
         with resources.get_file('levels', self.name) as f:
-            data = load(f)
+            return load(f)
+
+    def load(self, space):
+        data = self._get_data()
         self.x, self.y = data['size']
         self.base_tile = data['base_tile']
         for i, points in data['polygons'].iteritems():
             self.polygons[i] = []
             for point in points:
                 self.polygons[i].append(tuple(point))
+        for game_object_dict in data.get('game_objects', []):
+            self._create_game_object(space, **game_object_dict)
+
+    def _create_game_object(self, space, classname, args, name=None):
+        # We should probably build a registry of game objects or something.
+        # At least this is better than just calling `eval`, right?
+        cls = getattr(go, classname)
+        if issubclass(cls, go.Puzzler):
+            gobj = cls(*args)
+        elif issubclass(cls, go.GameObject):
+            gobj = cls(space, *args)
+            self._drawables.append(gobj)
+        else:
+            raise TypeError(
+                "Expected a subclass of Puzzler or GameObject, got %s" % (
+                    classname))
+        if name is not None:
+            self._glue.add_component(name, gobj)
 
     def all_closed(self):
         """Check if all the polygons are closed"""
@@ -80,6 +105,9 @@
     def get_walls(self):
         return self.polygons.values()
 
+    def get_drawables(self):
+        return self._drawables
+
     def _draw_walls(self):
         for index, polygon in self.polygons.items():
             color = POLY_COLORS[index]
--- a/nagslang/screens/area.py	Mon Sep 02 22:40:14 2013 +0200
+++ b/nagslang/screens/area.py	Mon Sep 02 23:05:25 2013 +0200
@@ -68,11 +68,12 @@
     def setup(self):
         self.keys = ControlKeys()
         self._level = Level(self.name)
-        self._level.load()
+        self._level.load(self.space)
         self._drawables = Drawables()
         self.add_walls()
         self.add_protagonist()
-        self._setup_demo_objects()
+        self.add_game_objects()
+        # self._setup_demo_objects()
 
     def _setup_demo_objects(self):
         # TODO: Put this in a level instead
@@ -82,10 +83,10 @@
         light = FloorLight(self.space, (300, 500), 'switch')
         light2 = FloorLight(self.space, (250, 500), 'both_switches')
         light2.zorder = ZORDER_HIGH
-        glue.add_component('switch', switch.puzzler)
-        glue.add_component('switch2', switch2.puzzler)
-        glue.add_component('light', light.puzzler)
-        glue.add_component('light2', light2.puzzler)
+        glue.add_component('switch', switch)
+        glue.add_component('switch2', switch2)
+        glue.add_component('light', light)
+        glue.add_component('light2', light2)
         glue.add_component(
             'both_switches', StateLogicalAndPuzzler('switch', 'switch2'))
         self._drawables.add(switch)
@@ -110,6 +111,10 @@
                 corner = next_corner
         self.space.add(*self.walls)
 
+    def add_game_objects(self):
+        for drawable in self._level.get_drawables():
+            self._drawables.add(drawable)
+
     def add_protagonist(self):
         self.protagonist = Protagonist(self.space, (350, 300))
         self._drawables.add(self.protagonist)
--- a/nagslang/tests/test_game_object.py	Mon Sep 02 22:40:14 2013 +0200
+++ b/nagslang/tests/test_game_object.py	Mon Sep 02 23:05:25 2013 +0200
@@ -67,3 +67,16 @@
         self.assertEqual('foo', puzzler.get_state())
         faker.fake_state = 'bar'
         self.assertEqual('bar', puzzler.get_state())
+
+    def test_glue_add_component(self):
+        glue = game_object.PuzzleGlue()
+        puzzler = FakePuzzler('foo')
+        gobj = FakeGameObject(None, None)
+        gobj.puzzler = FakePuzzler('bar')
+
+        self.assertEqual({}, glue._components)
+        glue.add_component('foo', puzzler)
+        self.assertEqual({'foo': puzzler}, glue._components)
+        glue.add_component('bar', gobj)
+        self.assertEqual(
+            {'foo': puzzler, 'bar': gobj.puzzler}, glue._components)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/tests/test_level.py	Mon Sep 02 23:05:25 2013 +0200
@@ -0,0 +1,78 @@
+from unittest import TestCase
+
+from nagslang import game_object as go
+from nagslang.level import Level
+
+
+class FakeSpace(object):
+    def add(self, *objs):
+        pass
+
+
+class TestLevel(TestCase):
+    def make_level(self, name, data):
+        level = Level(name)
+        level._get_data = lambda: data
+        return level
+
+    def test_empty_level(self):
+        level = self.make_level('foo', {
+            'size': [5, 10],
+            'base_tile': 'tiles/floor.png',
+            'polygons': {},
+        })
+        level.load(FakeSpace())
+        self.assertEqual((5, 10), level.get_size())
+        self.assertEqual([], level.get_walls())
+        self.assertEqual([], level.get_drawables())
+
+    def test_level_walls(self):
+        level = self.make_level('foo', {
+            'size': [5, 10],
+            'base_tile': 'tiles/floor.png',
+            'polygons': {
+                1: [[1, 1], [2, 1], [1, 2]],
+            },
+        })
+        level.load(FakeSpace())
+        self.assertEqual((5, 10), level.get_size())
+        self.assertEqual([[(1, 1), (2, 1), (1, 2)]], level.get_walls())
+        self.assertEqual([], level.get_drawables())
+
+    def test_level_game_objects(self):
+        level = self.make_level('foo', {
+            'size': [5, 10],
+            'base_tile': 'tiles/floor.png',
+            'polygons': {},
+            'game_objects': [
+                {
+                    'classname': 'Box',
+                    'args': [[3, 3]],
+                },
+                {
+                    'name': 'foo',
+                    'classname': 'FloorSwitch',
+                    'args': [[4, 4]],
+                },
+                {
+                    'name': 'foo_proxy',
+                    'classname': 'StateProxyPuzzler',
+                    'args': ['foo'],
+                },
+            ],
+        })
+        level.load(FakeSpace())
+        self.assertEqual((5, 10), level.get_size())
+        self.assertEqual([], level.get_walls())
+        [box, switch] = level.get_drawables()
+        self.assertTrue(isinstance(box, go.Box))
+        self.assertEqual(box.shape.body.position, (3, 3))
+        self.assertTrue(isinstance(switch, go.FloorSwitch))
+        self.assertEqual(switch.shape.body.position, (4, 4))
+
+        puzzle_bits = level._glue._components
+        self.assertEqual(['foo', 'foo_proxy'], sorted(puzzle_bits.keys()))
+        self.assertTrue(
+            isinstance(puzzle_bits['foo_proxy'], go.StateProxyPuzzler))
+        self.assertEqual('foo', puzzle_bits['foo_proxy']._state_source)
+        self.assertTrue(isinstance(puzzle_bits['foo'], go.FloorSwitchPuzzler))