Merge branch 'master' of git://ctpug.org.za/naja
authorDavid Sharpe <decoydavid@gmail.com>
Sat, 17 May 2014 21:48:59 +0000 (23:48 +0200)
committerDavid Sharpe <decoydavid@gmail.com>
Sat, 17 May 2014 21:48:59 +0000 (23:48 +0200)
data/location_decks/introduction.yaml
naja/scenes/credits.py
naja/scenes/dummygame.py [new file with mode: 0644]
naja/scenes/game.py
naja/widgets/board.py
naja/widgets/image_box.py
naja/widgets/info_area.py
naja/widgets/text.py

index e1011f08522d14ce4a169c4f2ce496fc33d847d1..89a67fe67b303f690b7d99f890f22c613e5c1fb8 100644 (file)
@@ -19,7 +19,7 @@ _action_definitions:
     action_class: 'DoNothing'
     required_bits: []
     data:
-      message: "Welcome to the game. You can only move to chequered squares, therefore you can stay here or move right."
+      message: "Welcome to the tutorial!\nYou can select any tile, but only move in directions with bits set. These are checquered. Now, only the current tile and 1 tile {EAST}.\nSelect {EAST} tile and press {RETURN}."
 
   - &STEP1-CARD
     card_name: 'step1'
@@ -32,17 +32,24 @@ _action_definitions:
     data:
       message: "Moving to a tile requires picking an action. Some tiles' actions set bits on your robot. Such as the {SOUTH} bit."
 
+  - &STEP2-ACTION-NULL
+    action_class: 'SetBits'
+    required_bits: []
+    data:
+      message: "So does this one. See, we gave you a choice!"
+
   - &STEP2-CARD
     card_name: 'step2'
     bits: [SOUTH]
     actions:
       - *STEP2-ACTION
+      - *STEP2-ACTION-NULL
 
   - &STEP3-ACTION
     action_class: 'ClearBits'
     required_bits: []
     data:
-      message: "Other tiles' actions clear bits. {SOUTH} or {EAST}. For the next part of the tutorial, we are  going to strictly control your movement bits."
+      message: "Other tiles' actions clear bits. This will clear {SOUTH} or {EAST}.\nFor the next part of the tutorial, we are going to control your movement bits strictly, so you don't get lost."
 
   - &STEP3-CARD-1
     card_name: 'step3-1'
@@ -60,7 +67,7 @@ _action_definitions:
     action_class: 'SetBits'
     required_bits: []
     data:
-      message: "There are key bits that can be set, such as {RED}. They may be required by actions. This action also sets some movement bits."
+      message: "There are key bits that can be set, such as {RED}. They may be required by actions.\nThis action also sets {EAST,SOUTH}."
 
   - &STEP4-CARD
     card_name: 'step4'
@@ -72,7 +79,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: [BLUE]
     data:
-      message: "You need to have the {BLUE} bit, to use this action and continue. Some actions have more complex requirements."
+      message: "You need to have the {BLUE} bit to use this action and continue.\nSome actions have more complex requirements."
       set: [EAST]
       clear: [NORTH, SOUTH, WEST]
 
@@ -80,7 +87,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: []
     data:
-      message: "Go back. Set {WEST}. Clear {NORTH,SOUTH,EAST}."
+      message: "Go back.\nSet {WEST}. Clear {NORTH,SOUTH,EAST}."
       set: [WEST]
       clear: [EAST, SOUTH, NORTH]
 
@@ -95,7 +102,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: [RED]
     data:
-      message: "You need to have the {RED} bit, to use this action to set the {BLUE} bit."
+      message: "You need to have the {RED} bit to use this action to set the {BLUE} bit."
       set: [NORTH, BLUE]
       clear: [SOUTH, EAST, WEST]
 
@@ -103,7 +110,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: []
     data:
-      message: "This action doesn't require anything. Go back. Set {NORTH}. Clear {SOUTH,EAST,WEST}."
+      message: "This action doesn't require anything. Go back.\nSet {NORTH}. Clear {SOUTH,EAST,WEST}."
       set: [NORTH]
       clear: [SOUTH, EAST, WEST]
 
@@ -118,7 +125,7 @@ _action_definitions:
     action_class: 'SetBits'
     required_bits: []
     data:
-      message: "Almost there. You have free reign on this column. Collect yourself a win bit {WINTOKEN} to finish the level. Good luck!"
+      message: "Almost there. You have free rein in this column.\nFinish the game by collecting enough {WINTOKEN}. 1 in this level.\nGood luck!"
 
   - &STEP7-CARD
     card_name: 'step7'
@@ -160,7 +167,7 @@ _action_definitions:
     required_bits: [GREEN]
     data:
       direction: EAST
-      message: "Some actions change the game board."
+      message: "All of the actions on this tile require {GREEN}. Without it, you can't even enter.\nActions like this one change the game board."
 
   - &STEP10-CARD
     card_name: 'step10'
@@ -171,7 +178,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: []
     data:
-      message: "We may have lied to youSet {WEST}. Clear {NORTH,SOUTH,EAST}."
+      message: "We may have lied to you about restricting to this column.\nSet {WEST}. Clear {NORTH,SOUTH,EAST}."
       set: [WEST]
       clear: [NORTH, SOUTH, EAST]
 
@@ -185,7 +192,7 @@ _action_definitions:
     action_class: 'GenericBits'
     required_bits: []
     data:
-      message: "In this board, tiles didn't change. In the random games, after taking an action (or the timer expires) the tile will be replaced by another one. Set {NORTH}. Clear {SOUTH,EAST,WEST}."
+      message: "In this board, tiles didn't change. In the random games, after taking an action (or a timer expires) the tile will be replaced.\nSet {NORTH}. Clear {SOUTH,EAST,WEST}."
       set: [NORTH]
       clear: [SOUTH, EAST, WEST]
 
index 75f1b3ce5ada6d6b13cd638770b869c732c1cf07..a522c986c1207ba6a97e7e84bac9bb56475eaabe 100644 (file)
@@ -35,10 +35,16 @@ class CreditsScene(Scene):
                 u'Adrianna PiƄska, David Sharpe, Jeremy Thurgood, '
                 u'Neil Muller, Simon Cross & Stefano Rivera',
                 '',
+                'Special thanks to:',
+                '',
+                'Desilu Crossman',
+                'for snacks and tutorial inspiration',
+                '',
                 'Music by Rolemusic:',
                 'http://rolemusic.sawsquarenoise.com/',
                 '',
-                'Press ESC to return to the menu.',
+                'Out of credits. Insert coin.',
+                '(Press ESC to return to the menu.)',
             ]),
             colour='white', padding=1,
             bg_colour=(0, 0, 0, 0), centre=True,
diff --git a/naja/scenes/dummygame.py b/naja/scenes/dummygame.py
new file mode 100644 (file)
index 0000000..5111d05
--- /dev/null
@@ -0,0 +1,40 @@
+"""
+Dummy scene that overlays a static rendering of a a game scene
+"""
+
+import pygame.locals as pgl
+import pygame
+
+from naja.constants import KEYS, PALETTE, SCREEN
+from naja.events import SceneChangeEvent, LoadGameEvent
+from naja.sound import sound
+from naja.scenes.scene import Scene
+from naja.scenes.game import GameScene
+from naja.gamestate import GameState
+from naja.widgets.image_box import PreRenderedImageBox
+from naja.widgets.text import TextWidget, TextBoxWidget
+
+
+class DummyGameScene(Scene):
+
+    def __init__(self, state=None):
+        super(DummyGameScene, self).__init__(state)
+        if not state:
+            game_state = GameState.new(max_health=4, wins_required=4)
+        else:
+            game_state = state
+        game = GameScene(game_state, play_sound=False)
+        game_surface = pygame.surface.Surface(SCREEN)
+        game.render(game_surface)
+        # Force tiles past the animation stage
+        game.board_widget.force_skip_animation()
+        game.render(game_surface)
+        self.add(PreRenderedImageBox((0, 0), game_surface))
+
+    def handle_scene_event(self, ev):
+        from naja.scenes.menu import MenuScene
+        if ev.type == pgl.KEYDOWN and ev.key in KEYS.QUIT:
+            # drop current state
+            LoadGameEvent.post(None)
+            SceneChangeEvent.post(MenuScene)
+            return
index c0b4b270ffd5cca556309d28406854e4508e11e4..82e08dbe3a7c6cabd332316a8c7fe0c689fbbdb6 100644 (file)
@@ -22,17 +22,19 @@ class GameScene(Scene):
     Gameboard scene.
     """
 
-    def __init__(self, state):
+    def __init__(self, state, play_sound=True):
         super(GameScene, self).__init__(state)
         self.add(PlayerBitsWidget((0, 0), state))
         info = InfoAreaWidget((480, 0), state)
-        self.add(BoardWidget((0, 60), state, info))
+        self.board_widget = BoardWidget((0, 60), state, info)
+        self.add(self.board_widget)
         self.add(GameBitsWidget((0, 540), state))
         self.add(info)
         self.add(RobotWidget(state))
-        sound.play_sound('startup.ogg')
-        background_track = random.choice(TUNES)
-        sound.play_music(background_track, 0.25)
+        if play_sound:
+            sound.play_sound('startup.ogg')
+            background_track = random.choice(TUNES)
+            sound.play_music(background_track, 0.25)
 
     def handle_scene_event(self, ev):
         from naja.scenes.menu import MenuScene
index 32fb5af8c52a3849e110c55a9eb90a5ee9caa24c..b2a93fa8e0baa6a5fffa5cc3ba9b1b62e9ccac7e 100644 (file)
@@ -62,6 +62,10 @@ class BoardWidget(Widget):
             idx = 0
         self.update_card_pos(moves[idx])
 
+    def force_skip_animation(self):
+        for tile in self._tiles:
+            tile.animation = 0
+
     def handle_event(self, ev):
         if self.state.gameboard.player_mode == ACT:
             return super(BoardWidget, self).handle_event(ev)
index 32787dfce793d9813a904e899ba55cf81be2c0bd..e9b4bf398d4e941dc731d795338e7e79324ca0b5 100644 (file)
@@ -22,3 +22,18 @@ class ImageBox(Widget):
 
     def draw(self, surface):
         surface.blit(self.surface, self.rect)
+
+
+class PreRenderedImageBox(Widget):
+    """Hold an image given as a surface"""
+
+    def __init__(self, pos, image):
+        super(PreRenderedImageBox, self).__init__(pos)
+        self.surface = image.copy()
+
+    def prepare(self):
+        self.size = self.surface.get_rect().size
+
+    def draw(self, surface):
+        surface.blit(self.surface, self.rect)
+
index 97c3daacc261411052e0e379b865c105c12074cc..1ff6bbbd92168e52ec12c4a489cf6f54638f95a7 100644 (file)
@@ -57,24 +57,21 @@ class InfoAreaWidget(Widget):
         y_offset = 0
         pos = lambda: (INFO_LEFT_PADDING, y_offset)
 
-        # Top title
-        title = TextWidget(
-            pos(), TITLES[self.state.gameboard.player_mode],
-            colour=PALETTE.WHITE)
-        title.render(self.surface)
-        y_offset += title.surface.get_rect().height - 4
-
         # Bits
+        y_offset += 12
         bits_text = ''.join('1' if bit in self.card.bitwise_operand else '0'
                             for bit in reversed(range(8)))
         if self.card.bitwise_operand:
             bits_text = '%s %s' % (
                 bits_text, bit_glyphs(self.card.bitwise_operand))
         card_bits = TextBoxWidget(
-            pos(), bits_text, box_width=box_width,
-            colour=PALETTE.LIGHT_TURQUOISE, bg_colour=PALETTE.BLACK)
-        card_bits.render(self.surface)
-        y_offset += card_bits.surface.get_rect().height + 4
+            (0, 0), bits_text, padding=4, centre=True,
+            colour=PALETTE.WHITE, border=2,
+            bg_colour=PALETTE.BLACK, border_colour=PALETTE.BLUE,
+            box_width=box_width)
+        card_bits.prepare()
+        self.surface.blit(card_bits.surface, pos())
+        y_offset += card_bits.surface.get_rect().height + 12
 
         # Actions
         for choice, action in enumerate(self.card.actions):
@@ -119,7 +116,7 @@ class InfoAreaWidget(Widget):
 
     def prepare_action(self, choice, action, y_offset, box_width):
         x_offset = INFO_LEFT_PADDING
-        glyphs_x_offset = 0
+        glyphs_x_offset = 2
         glyphs_y_offset = y_offset
         y_offset += ACTION_TEXT_OFFSET
         action_viable = action.check_available(self.state.player)
index 5dac4e622952b6f3407df7f95e622cf8db1498aa..af7f0ac55c11b898b103363aec27dcde9c855e12 100644 (file)
@@ -149,24 +149,25 @@ class TextBoxWidget(TextWidget):
 
         super(TextBoxWidget, self).__init__(*args, **kwargs)
 
-    def lines(self, image_map):
+    def lines(self):
         if self.box_width != 0:
-            return self._wrapped_lines(image_map)
+            return self._wrapped_lines()
         else:
-            return self.text.splitlines()
+            return ((line, []) for line in self.text.splitlines())
 
-    def _prepare_glyph(self, image_map, glyph, current_words, lines):
+    def _prepare_glyph(self, glyph, current_words):
+        glyphs = []
         size = self.font.size(' '.join(current_words[:-1] + ['']))
-        x = size[0] * EIGHT_BIT_SCALE + self.padding
-        y = size[1] * lines * EIGHT_BIT_SCALE + self.padding
+        x = size[0] * EIGHT_BIT_SCALE
         for glyph_key in glyph.glyph_keys:
             image_name, colour = MARKUP_MAP[glyph_key]
             if colour is None:
                 colour = self.colour
             image = resources.get_image(
                 image_name, transforms=(EIGHT_BIT, blender(colour)))
-            image_map[(x, y)] = image
+            glyphs.append(((x, 0), image))
             x += image.get_width()
+        return glyphs
 
     def _check_markup(self, word):
         suffix = ''
@@ -183,7 +184,7 @@ class TextBoxWidget(TextWidget):
 
         return None
 
-    def _wrapped_lines(self, image_map):
+    def _wrapped_lines(self):
         def words_fit(words):
             words_line = ' '.join(words)
             width = self.font.size(words_line)[0] * EIGHT_BIT_SCALE
@@ -198,9 +199,9 @@ class TextBoxWidget(TextWidget):
             line = line.strip()
             if not line:
                 line_count += 1
-                yield line
+                yield (line, [])
                 continue
-            current_words = []
+            current_words, glyphs = [], []
             remaining_words = line.split()
             while remaining_words:
                 word = remaining_words.pop(0)
@@ -210,30 +211,31 @@ class TextBoxWidget(TextWidget):
                 current_words.append(word)
                 if words_fit(current_words):
                     if glyph is not None:
-                        self._prepare_glyph(
-                            image_map, glyph, current_words, line_count)
+                        glyphs.extend(self._prepare_glyph(
+                            glyph, current_words))
                 else:
                     line_count += 1
-                    yield ' '.join(current_words[:-1])
-                    current_words = []
+                    yield (' '.join(current_words[:-1]), glyphs)
+                    current_words, glyphs = [], []
                     if glyph is not None:
                         word = glyph.markup_text
                     remaining_words.insert(0, word)
             if current_words and words_fit(current_words):
                 line_count += 1
-                yield ' '.join(current_words)
+                yield (' '.join(current_words), glyphs)
 
     def prepare(self):
         self.font = resources.get_font(self.fontname, self.fontsize)
-        image_map = {}
         rendered_lines = []
         width, height = self.padding * 2, self.padding * 2
-        for line in self.lines(image_map):
+        for line, glyphs in self.lines():
             line_surface = self.render_line(line)
             line_rect = line_surface.get_rect()
             rendered_lines.append(line_surface)
             width = max(width, line_rect.width + self.padding * 2)
             height += line_rect.height
+            for pos, img in glyphs:
+                line_surface.blit(img, pos)
 
         if self.full_width:
             width = max(width, self.box_width)
@@ -250,7 +252,5 @@ class TextBoxWidget(TextWidget):
                 x += self.padding
             self.surface.blit(line_surface, (x, y))
             y += line_surface.get_rect().height
-        for pos, img in image_map.items():
-            self.surface.blit(img, pos)
 
         self.render_border(self.surface)