Bastard merge
authorDavid Sharpe <decoydavid@gmail.com>
Sun, 11 May 2014 19:59:04 +0000 (21:59 +0200)
committerDavid Sharpe <decoydavid@gmail.com>
Sun, 11 May 2014 19:59:04 +0000 (21:59 +0200)
18 files changed:
data/images/board/robot.png
data/images/board/robot_cyan.png [new file with mode: 0644]
data/images/board/robot_magenta.png [new file with mode: 0644]
data/images/board/robot_msb.png [new file with mode: 0644]
data/images/board/robot_yellow.png [new file with mode: 0644]
naja/actions.py
naja/constants.py
naja/events.py
naja/gameboard.py
naja/gamestate.py
naja/scenes/credits.py
naja/scenes/credits.py.orig [new file with mode: 0644]
naja/scenes/game.py
naja/scenes/menu.py
naja/scenes/scene.py
naja/tests/test_actions.py
naja/tests/test_gameboard.py
naja/widgets/base.py

index 80416cc1441e2dc88f961c3b1dacde8d4ea1e2f6..bf3e096c5d3c658121a8e88df28b3d658578b5ca 100644 (file)
Binary files a/data/images/board/robot.png and b/data/images/board/robot.png differ
diff --git a/data/images/board/robot_cyan.png b/data/images/board/robot_cyan.png
new file mode 100644 (file)
index 0000000..247fbfd
Binary files /dev/null and b/data/images/board/robot_cyan.png differ
diff --git a/data/images/board/robot_magenta.png b/data/images/board/robot_magenta.png
new file mode 100644 (file)
index 0000000..3ec6fec
Binary files /dev/null and b/data/images/board/robot_magenta.png differ
diff --git a/data/images/board/robot_msb.png b/data/images/board/robot_msb.png
new file mode 100644 (file)
index 0000000..2cd6cc1
Binary files /dev/null and b/data/images/board/robot_msb.png differ
diff --git a/data/images/board/robot_yellow.png b/data/images/board/robot_yellow.png
new file mode 100644 (file)
index 0000000..a4e5413
Binary files /dev/null and b/data/images/board/robot_yellow.png differ
index c49f7c61b909ba01f27778b5265b742cfab63821..5599bd73b2d724e6986bc8a58d3ecf7e3ba2da16 100644 (file)
@@ -15,7 +15,7 @@ class LocationAction(object):
     def check_available(self, player):
         return player.bits.check_bits(self.required_bits)
 
-    def perform_action(self, player, board):
+    def perform_action(self, board, location):
         raise NotImplementedError("TODO")
 
     def check_and_clear_MSB(self, player):
@@ -29,13 +29,27 @@ class LocationAction(object):
 class DoNothing(LocationAction):
     TEXT = "No effect."
 
-    def perform_action(self, player, board):
+    def perform_action(self, board, location):
         pass
 
 
 class LoseHeathOrMSB(LocationAction):
     TEXT = "Lose health. If MSB is set, it will be cleared instead."
 
-    def perform_action(self, player, board):
-        if not self.check_and_clear_MSB(player):
+    def perform_action(self, board, location):
+        if not self.check_and_clear_MSB(board.player):
             board.lose_health()
+
+
+class SetBits(LocationAction):
+    TEXT = "Set bits specified by this location."
+
+    def perform_action(self, board, location):
+        board.player.bits.set_bits(location.bitwise_operand)
+
+
+class ToggleBits(LocationAction):
+    TEXT = "Toggle bits specified by this location."
+
+    def perform_action(self, board, location):
+        board.player.bits.toggle_bits(location.bitwise_operand)
index d7e023a557700404c32bd63fa156bd6e75afac69..99dd99c97928caa730eec83829d5825c9c56051d 100644 (file)
@@ -36,6 +36,14 @@ BITS = AttrDict({
 DIRECTION_BITS = AttrDict((k, v) for k, v in BITS.items() if v < 4)
 CONDITION_BITS = AttrDict((k, v) for k, v in BITS.items() if v >= 4)
 
+# Player defaults
+PLAYER_DEFAULTS = AttrDict({
+    'INITIAL_BITS': BITS.NORTH | BITS.SOUTH | BITS.EAST | BITS.WEST,
+    'INITIAL_POS': (2, 2),
+    'MAX_HEALTH': 4,
+    'WINS_REQUIRED': 4,
+})
+
 # Game size constants
 TILE_SIZE = (96, 96)
 BOARD_SIZE = (5 * TILE_SIZE[0], 5 * TILE_SIZE[1])
index 4d3fb07f80a94bc5502edfa048b3e0e017c80393..0e102edf9229d0de4fbdc327514e0b08046144c8 100644 (file)
@@ -35,3 +35,13 @@ class QuitGameEvent(NajaEvent):
     @classmethod
     def post(cls):
         super(QuitGameEvent, cls).post()
+
+
+class InvalidateTheWorld(NajaEvent):
+    # This is used to signal to all the widgets that the world has changed
+    # and cached state may need to be recreated
+    TYPE = "INVALIDATE"
+
+    @classmethod
+    def post(cls):
+        super(InvalidateTheWorld, cls).post()
index 5ec61507c7e31a5b10b7c28780b427414e97c462..de0bc1e3f31c5b4b196d41a9345747a065900595 100644 (file)
@@ -1,6 +1,7 @@
 from random import choice
 
-from naja.constants import BITS, DIRECTION_BITS, CONDITION_BITS
+from naja.constants import(
+    BITS, DIRECTION_BITS, CONDITION_BITS, PLAYER_DEFAULTS)
 from naja.player import Player
 from naja import actions
 
@@ -15,13 +16,16 @@ class GameBoard(object):
         self.wins_required = state['wins_required']
         self.health = state['health']
         self.wins = state['wins']
-        self.locations = self.import_locations(state['locations'])
+        self.locations = [item.copy() for item in state['locations']]
         self.player = player
         self.board_locations = board_locations
 
     @classmethod
-    def new_game(cls, initial_bits, initial_pos, max_health, wins_required,
-                 locations_definition):
+    def new_game(cls, locations_definition,
+                 initial_bits=PLAYER_DEFAULTS.INITIAL_BITS,
+                 initial_pos=PLAYER_DEFAULTS.INITIAL_POS,
+                 max_health=PLAYER_DEFAULTS.MAX_HEALTH,
+                 wins_required=PLAYER_DEFAULTS.WINS_REQUIRED):
         state = {
             'max_health': max_health,
             'health': max_health,
@@ -30,7 +34,8 @@ class GameBoard(object):
             'locations': locations_definition,
         }
         player = Player(initial_bits, initial_pos)
-        board_locations = cls.generate_board(locations_definition)
+        board_locations = cls.import_board_locations(
+            cls.generate_board(locations_definition))
         return cls(state, player, board_locations)
 
     @classmethod
@@ -47,14 +52,11 @@ class GameBoard(object):
             'health': self.health,
             'wins_required': self.wins_required,
             'wins': self.wins,
-            'locations': self.export_locations(),
+            'locations': [item.copy() for item in self.locations],
             'player': self.player.export(),
             'board_locations': self.export_board_locations(),
         }
 
-    def export_locations(self):
-        return [location.export() for location in self.locations]
-
     @classmethod
     def import_locations(cls, locations_definition):
         return [
@@ -64,7 +66,7 @@ class GameBoard(object):
     def export_board_locations(self):
         return dict(
             (position, location.export())
-            for position, location in self.board_locations)
+            for position, location in self.board_locations.iteritems())
 
     @classmethod
     def import_board_locations(cls, board_locations_definition):
@@ -74,8 +76,13 @@ class GameBoard(object):
 
     @classmethod
     def generate_board(cls, locations_definition):
-        # TODO: Choose some locations.
-        return {}
+        board_locations = {}
+        for x in range(5):
+            for y in range(5):
+                board_location = LocationCard.new_location(
+                    choice(locations_definition).copy())
+                board_locations[(x, y)] = board_location.export()
+        return board_locations
 
     def lose_health(self):
         self.health -= 1
@@ -93,7 +100,6 @@ class LocationCard(object):
 
     @classmethod
     def import_location(cls, state):
-        # TODO: Import real locations.
         location_actions = [
             cls.build_action(definition) for definition in state['actions']]
         return cls(state['bitwise_operand'], location_actions)
index dabf34a9c23311c6302d06242dc2688dce5d50b8..a82cec8e6f5de9f9229f63a950b4c12d5d9acb65 100644 (file)
@@ -2,7 +2,6 @@
 The current game state.
 """
 
-from naja.constants import BITS
 from naja.gameboard import GameBoard
 
 
@@ -11,20 +10,9 @@ class GameState(object):
     Naja game state.
     """
 
-    INITIAL_BITS = (
-        BITS.NORTH | BITS.SOUTH |
-        BITS.EAST | BITS.WEST
-    )
-    MAX_HEALTH = 4
-    WINS_REQUIRED = 4
-
     def __init__(self):
         self.gameboard = GameBoard.new_game(
-            initial_bits=self.INITIAL_BITS,
-            initial_pos=(2, 2),
-            max_health=self.MAX_HEALTH,
-            wins_required=self.WINS_REQUIRED,
-            locations_definition=[])  # TODO: we will need some of these :)
+            locations_definition=[{'actions': []}])  # TODO: we will need some of these :)
 
     @property
     def player(self):
index 05e2f7d3ddf94fba577258a3a294617c47d4986e..920684ca0fb09041eb8f26e34fd0cfdde72e2a28 100644 (file)
@@ -14,16 +14,20 @@ class CreditsScene(Scene):
 
     def __init__(self, state):
         super(CreditsScene, self).__init__(state)
-        self.widgets.append(TextWidget((120, 120), 'Credits', fontsize=32,
+        self.add(TextWidget((360, 160), 'Credits', fontsize=32,
                             colour='white'))
-        self.widgets.append(TextBoxWidget((120, 30),
+        self.add(TextBoxWidget((120, 30),
                                        'Your mom '
-                                       'A stranger \nA \nbag full of snakes', fontsize=32,
+                                       'A stranger \n'
+                                       'A \n'
+                                       'bag full of snakes\n'
+                                       'A host of really bored bronies\n'
+                                       'And FIVE GOLDEN RIIIIINGS!', fontsize=32,
                             colour='white', padding=1, border=1,
                             bg_colour='black', border_colour='black',
                             box_width=100))
 
-    def handle_event(self, ev):
+    def handle_scene_event(self, ev):
         if ev.type == pgl.KEYUP and ev.key in (pgl.K_q, pgl.K_ESCAPE):
             from naja.scenes.menu import MenuScene
             SceneChangeEvent.post(MenuScene)
diff --git a/naja/scenes/credits.py.orig b/naja/scenes/credits.py.orig
new file mode 100644 (file)
index 0000000..116bcfd
--- /dev/null
@@ -0,0 +1,36 @@
+"""
+Main menu scene.
+"""
+
+import pygame.locals as pgl
+
+from naja.scenes.scene import Scene
+from naja.widgets.text import TextWidget, TextBoxWidget
+from naja.events import SceneChangeEvent
+
+class CreditsScene(Scene):
+
+    base_menu = None
+
+    def __init__(self, state):
+        super(CreditsScene, self).__init__(state)
+<<<<<<< HEAD
+        self.widgets.append(TextWidget((120, 120), 'Credits', fontsize=32,
+                            colour='white'))
+        self.widgets.append(TextBoxWidget((120, 30),
+                                       'Your mom '
+                                       'A stranger \nA \nbag full of snakes', fontsize=32,
+                            colour='white', padding=1, border=1,
+                            bg_colour='black', border_colour='black',
+                            box_width=100))
+=======
+        self.add(TextWidget((60, 10), 'Credits', fontsize=32, colour='white'))
+        self.add(TextWidget((60, 30), 'Your mom\nA stranger', fontsize=32,
+                 colour='white'))
+>>>>>>> 4e27b4e10d7fd4f66097ece17a8c51b80233a363
+
+    def handle_scene_event(self, ev):
+        if ev.type == pgl.KEYUP and ev.key in (pgl.K_q, pgl.K_ESCAPE):
+            from naja.scenes.menu import MenuScene
+            SceneChangeEvent.post(MenuScene)
+            return
index 9d8b29046a5c8b7ee81f79b5702a5b994973519c..32285cd4dedbb02f0538478b1cbda34e06d4cc0a 100644 (file)
@@ -19,12 +19,12 @@ class GameScene(Scene):
 
     def __init__(self, state):
         super(GameScene, self).__init__(state)
-        self.widgets.append(PlayerBitsWidget((0, 0)))
-        self.widgets.append(BoardWidget((0, 60)))
-        self.widgets.append(GameBitsWidget((0, 540)))
-        self.widgets.append(InfoAreaWidget((480, 0)))
+        self.add(PlayerBitsWidget((0, 0)))
+        self.add(BoardWidget((0, 60)))
+        self.add(GameBitsWidget((0, 540)))
+        self.add(InfoAreaWidget((480, 0)))
 
-    def handle_event(self, ev):
+    def handle_scene_event(self, ev):
         from naja.scenes.menu import MenuScene
         if ev.type == pgl.KEYUP and ev.key in (pgl.K_q, pgl.K_ESCAPE):
             SceneChangeEvent.post(MenuScene)
index a73c2ecb498ff94d5d76a2e9bd7d06a08ff3fa82..46b2b3b13eab19a19e18ea17d8a9f8ea4c1b2998 100644 (file)
@@ -16,15 +16,12 @@ class MenuScene(Scene):
 
     def __init__(self, state):
         super(MenuScene, self).__init__(state)
-        self.widgets.append(TextWidget((10, 10), 'Game', fontsize=32,
-                            colour='white'))
-        self.widgets.append(TextWidget((10, 40), 'Credits', fontsize=32,
-                            colour='white'))
-        self.widgets.append(TextWidget((10, 70), 'Quit', fontsize=32,
-                            colour='white'))
+        self.add(TextWidget((10, 10), 'Game', fontsize=32, colour='white'))
+        self.add(TextWidget((10, 40), 'Credits', fontsize=32, colour='white'))
+        self.add(TextWidget((10, 70), 'Quit', fontsize=32, colour='white'))
         self.pos = 0
 
-    def handle_event(self, ev):
+    def handle_scene_event(self, ev):
         if ev.type == pgl.KEYDOWN:
             if ev.key in (pgl.K_q, pgl.K_ESCAPE):
                 QuitGameEvent.post()
index ce0b68488eb1a0dc6369fa37c3dd4902cdb8d3e8..4841ab5a628cb3a318ab0c947d1d537f9f0ee03b 100644 (file)
@@ -2,6 +2,8 @@
 Base Scene class.
 """
 
+from naja.widgets.base import Container
+
 
 class Scene(object):
     """
@@ -11,7 +13,10 @@ class Scene(object):
     """
     def __init__(self, state):
         self.state = state
-        self.widgets = []
+        self.container = Container()
+
+    def add(self, widget):
+        self.container.add(widget)
 
     def enter(self):
         pass
@@ -19,12 +24,17 @@ class Scene(object):
     def exit(self):
         pass
 
-    def render(self, surface):
-        self.render_widgets(surface)
+    def render_scene(self, surface):
+        pass
 
-    def render_widgets(self, surface):
-        for widget in self.widgets:
-            widget.render(surface)
+    def render(self, surface):
+        self.render_scene(surface)
+        self.container.render(surface)
 
     def handle_event(self, ev):
+        if self.container.handle_event(ev):
+            return
+        self.handle_scene_event(ev)
+
+    def handle_scene_event(ev):
         pass
index 8896b8d2607e72a6bc0ac034fba8b7f2a2a75f66..d2c723a6b4082c0135da73565a44d0862afab361 100644 (file)
@@ -1,5 +1,7 @@
 from unittest import TestCase
 
+from naja.constants import BITS
+from naja.gameboard import GameBoard, LocationCard
 from naja.player import Player
 from naja import actions
 
@@ -8,12 +10,76 @@ class TestActions(TestCase):
     def make_player(self, *bits):
         return Player(sum(1 << bit for bit in bits), None)
 
-    def test_DoNothing_check_available(self):
-        player = self.make_player()
-        action = actions.DoNothing(set())
-        self.assertEqual(action.check_available(player), True)
+    def make_board(self, player_bits=None, locations=None):
+        if locations is None:
+            locations = [{'actions': []}]
+        board = GameBoard.new_game(locations)
+        if player_bits is not None:
+            board.player.bits.set_bits(player_bits)
+        return board
 
-    def test_LoseHealthOrMSB_check_available(self):
-        player = self.make_player()
-        action = actions.LoseHeathOrMSB(set())
-        self.assertEqual(action.check_available(player), True)
+    def test_check_available(self):
+        def check_available(action_bits, player_bits, expected_result):
+            action = actions.LocationAction(action_bits)
+            player = self.make_player(*player_bits)
+            self.assertEqual(action.check_available(player), expected_result)
+
+        check_available(set(), [], True)
+        check_available(set(), [BITS.MSB], True)
+        check_available(set([BITS.MSB]), [], False)
+        check_available(set([BITS.MSB]), [BITS.MSB], True)
+
+    def test_DoNothing(self):
+        board = self.make_board()
+        state_before = board.export()
+        actions.DoNothing(set()).perform_action(board, None)
+        state_after = board.export()
+        self.assertEqual(state_before, state_after)
+
+    def test_LoseHealthOrMSB_MSB_clear(self):
+        board = self.make_board()
+        state_before = board.export()
+        actions.LoseHeathOrMSB(set()).perform_action(board, None)
+        state_after = board.export()
+        self.assertEqual(state_after['health'], state_before['health'] - 1)
+
+        state_before.pop('health')
+        state_after.pop('health')
+        self.assertEqual(state_before, state_after)
+
+    def test_LoseHealthOrMSB_MSB_set(self):
+        board = self.make_board(player_bits=[BITS.MSB])
+        state_before = board.export()
+        actions.LoseHeathOrMSB(set()).perform_action(board, None)
+        state_after = board.export()
+        self.assertEqual(board.player.bits.check_bit(BITS.MSB), False)
+
+        state_before['player'].pop('bits')
+        state_after['player'].pop('bits')
+        self.assertEqual(state_before, state_after)
+
+    def test_SetBits(self):
+        board = self.make_board()
+        state_before = board.export()
+        location = LocationCard(set([BITS.MSB, BITS.NORTH]), [])
+        actions.SetBits(set()).perform_action(board, location)
+        state_after = board.export()
+        self.assertEqual(
+            board.player.bits.check_bits([BITS.MSB, BITS.NORTH]), True)
+
+        state_before['player'].pop('bits')
+        state_after['player'].pop('bits')
+        self.assertEqual(state_before, state_after)
+
+    def test_ToggleBits(self):
+        board = self.make_board(player_bits=[BITS.NORTH])
+        state_before = board.export()
+        location = LocationCard(set([BITS.MSB, BITS.NORTH]), [])
+        actions.ToggleBits(set()).perform_action(board, location)
+        state_after = board.export()
+        self.assertEqual(board.player.bits.check_bit(BITS.MSB), True)
+        self.assertEqual(board.player.bits.check_bit(BITS.NORTH), False)
+
+        state_before['player'].pop('bits')
+        state_after['player'].pop('bits')
+        self.assertEqual(state_before, state_after)
index bd1a16c1c080c3ea95b5a075ed8f1cc2f6345ef2..9d39fe85a4e34a2fcb7a5ad3290ce4104ad5ed7f 100644 (file)
@@ -7,19 +7,28 @@ from naja import actions
 
 class TestGameBoard(TestCase):
     def test_export_new_board(self):
-        board = GameBoard.new_game(4, 4, [])
-        self.assertEqual(board.export(), {
+        board = GameBoard.new_game([{'actions': []}])
+        exported_state = board.export()
+        board_locations = exported_state.pop('board_locations')
+        self.assertEqual(exported_state, {
             'max_health': 4,
             'health': 4,
             'wins_required': 4,
             'wins': 0,
-            'locations': [],
-            'board_locations': {},
+            'locations': [{'actions': []}],
             'player': board.player.export(),
         })
+        self.assertEqual(
+            set(board_locations.keys()),
+            set((x, y) for x in range(5) for y in range(5)))
+        for location_state in board_locations.values():
+            self.assertEqual(
+                sorted(location_state.keys()), ['actions', 'bitwise_operand'])
+            self.assertEqual(location_state['actions'], [])
+            self.assertTrue(2 <= len(location_state['bitwise_operand']) <= 3)
 
     def test_lose_health(self):
-        board = GameBoard.new_game(4, 4, [])
+        board = GameBoard.new_game([{'actions': []}])
         self.assertEqual(board.health, 4)
         state_1 = board.export()
 
index 32ff46fa21811e3b99b61d84e6fbac8325cc2433..133cc532c2ae8fb0886117255decbba65f1e374c 100644 (file)
@@ -1,4 +1,5 @@
 import pygame
+from naja.events import InvalidateTheWorld
 
 
 class Widget(object):
@@ -12,13 +13,44 @@ class Widget(object):
         return pygame.Rect(self.pos, self.size)
 
     def render(self, surface):
+        '''Draw the widget onto surface'''
         if not self._prepared:
             self.prepare()
             self._prepared = True
         self.draw(surface)
 
     def draw(self, surface):
+        '''The overrideable bit of widget drawing'''
         raise NotImplemented()
 
     def prepare(self):
-        raise NotImplemented()
+        '''Prepare the widget for drawing. This usually caches a surface.'''
+
+    def handle_event(self, ev):
+        '''Return True if the event has been handled'''
+        if InvalidateTheWorld.matches(ev):
+            # Invalidate has special handling. Widgets should never return
+            # True for for this event
+            self._prepared = False
+            return False
+        return False
+
+
+class Container(object):
+    def __init__(self, *widgets):
+        self.widgets = []
+        for widget in widgets:
+            self.add(widget)
+
+    def add(self, widget):
+        self.widgets.append(widget)
+
+    def render(self, surface):
+        for widget in self.widgets:
+            widget.render(surface)
+
+    def handle_event(self, ev):
+        for widget in self.widgets:
+            if widget.handle_event(ev):
+                return True
+        return False