bastard merge
authorDavid Sharpe <decoydavid@gmail.com>
Sat, 17 May 2014 12:50:42 +0000 (14:50 +0200)
committerDavid Sharpe <decoydavid@gmail.com>
Sat, 17 May 2014 12:50:42 +0000 (14:50 +0200)
36 files changed:
LICENSE.txt
README.txt
data/icons/Makefile [new file with mode: 0644]
data/icons/robolock.ico
data/images/board/tile_2_puzzle.png [new file with mode: 0644]
data/images/board/tile_available_puzzle.png [new file with mode: 0644]
data/location_decks/standard.yaml
data/sounds/__init__.py
data/sounds/aha.ogg [new file with mode: 0644]
data/sounds/awwww.ogg [new file with mode: 0644]
data/sounds/chirp.ogg
data/sounds/error.ogg
data/sounds/grind.ogg
data/sounds/shutdown.ogg
data/sounds/silence.ogg
data/sounds/startup.ogg
data/sounds/yipee.ogg [new file with mode: 0644]
data/sounds/zoop.ogg
data/sounds/zzzzz.ogg
debian/copyright
naja/actions.py
naja/gameboard.py
naja/gamestate.py
naja/gen_sound.py
naja/options.py
naja/resources/__init__.py
naja/resources/loader.py
naja/scenes/credits.py
naja/scenes/menu.py
naja/sound.py
naja/tests/test_gameboard.py
naja/widgets/text.py
naja/widgets/tile.py
setup.py
sources/images/square.xcf
tools/gen_sound.py

index 597becbfc56f0fe876bbadde01f35d2df0c8dc3d..bbe9ad808509f2986635dd80e6542a990a55b068 100644 (file)
@@ -1,4 +1,4 @@
-Copyright (c) 2014, Adrianna Pinska, David Sharpe, Jeremy Thurgood,
+Copyright (c) 2014, Adrianna Pińska, David Sharpe, Jeremy Thurgood,
                     Neil Muller, Simon Cross, Stefano Rivera
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
index 86ac8ef0147ba3d64df58037f1b549aa0d6bed2a..23d788cdb0f7eee65ae89ee734b5c99dc709c499 100644 (file)
@@ -10,7 +10,7 @@ Team:
 Members:
     Simon Cross
     Neil Muller
-    Adrianna Pinska
+    Adrianna Pińska
     Stefano Rivera
     David Sharpe
     Jeremy Thurgood
diff --git a/data/icons/Makefile b/data/icons/Makefile
new file mode 100644 (file)
index 0000000..fa114b6
--- /dev/null
@@ -0,0 +1,27 @@
+TARGETS=robolock.icns robolock.ico
+
+PNGS=$(wildcard *.png)
+RGBA_PNGS=$(patsubst %,rgba/%,$(PNGS))
+
+.PHONY: all
+all: $(TARGETS)
+       rm -rf rgba
+
+.PHONY: clean
+clean:
+       rm -rf rgba
+       rm -f $(TARGETS)
+
+.PHONY: install-tools
+install-tools:
+       echo apt-get install icnsutils icoutils
+
+rgba/%.png: %.png
+       mkdir -p rgba
+       convert $^ PNG32:$@
+
+robolock.icns: $(filter-out %_24.png,$(RGBA_PNGS))
+       png2icns $@ $^
+
+robolock.ico: $(RGBA_PNGS)
+       icotool --create --output $@ $^
index eab34e6864cf39681e0c1ed9ece529a5fd1e8e78..2083c428c87ab9b31db7afdc4caddf52ad3bc2b3 100644 (file)
Binary files a/data/icons/robolock.ico and b/data/icons/robolock.ico differ
diff --git a/data/images/board/tile_2_puzzle.png b/data/images/board/tile_2_puzzle.png
new file mode 100644 (file)
index 0000000..4245bc3
Binary files /dev/null and b/data/images/board/tile_2_puzzle.png differ
diff --git a/data/images/board/tile_available_puzzle.png b/data/images/board/tile_available_puzzle.png
new file mode 100644 (file)
index 0000000..ff4ed9c
Binary files /dev/null and b/data/images/board/tile_available_puzzle.png differ
index 92f13e5de04df4b368c7553f28da39b5a84825b8..14b2fbc8f186d7c8f0f6c6fc3dc7ca11a34a34d2 100644 (file)
@@ -100,15 +100,18 @@ _action_definitions:
 _card_definitions:
   - &WIN-CARD-1
     card_name: 'win1'
+    max_number: 1
     actions:
       - *ACQUIRE-WIN-TOKEN
   - &WIN-CARD-2
     card_name: 'win2'
+    max_number: 2
     actions:
       - *BAD-DEFAULT
       - *ACQUIRE-WIN-TOKEN
   - &WIN-CARD-3
     card_name: 'win3'
+    max_number: 1
     actions:
       - *SET-BITS-DEFAULT
       - *ACQUIRE-WIN-TOKEN
index 77940938d9ea7d762948d07a7ed85031fe572284..5773cb3970515c5763472e2143228cdb095ec46f 100644 (file)
@@ -1,25 +1,38 @@
+import itertools
 import random
 
 from naja.gen_sound import Chunk, scale
 
 
 def grind():
-    yield Chunk('sine', freq=100, length=0.01, volume=25)
+    yield Chunk('sine', freq=200, length=0.01, volume=25)
     tones = []
-    for freq in (100, 150, 200, 350, 120, 170, 300):
-        tones.append(Chunk('sine', freq=freq, length=0.01, volume=25))
+    for freq in (200, 300, 400, 700, 240, 340, 600):
+        tones.append(Chunk('sine', freq=freq, length=0.005, volume=25))
     for i in range(75):
         yield random.choice(tones)
-    yield Chunk('sine', freq=100, length=0.01, volume=25)
+    yield Chunk('sine', freq=200, length=0.01, volume=25)
 
 
 SOUNDS = {
     'chirp': scale(1650, 1449, -200, length=0.05, volume=50),
     'error': Chunk('sine', freq=1000, length=0.25),
     'grind': grind(),
-    'shutdown': scale(800, 199, -200),
+    'shutdown': scale(1600, 399, -400, length=0.1),
     'silence': Chunk('silence', length=2),
-    'startup': scale(200, 801, 200),
-    'zoop': scale(500, 800, 20, length=0.01, volume=50),
-    'zzzzz': Chunk('sine', freq=100, length=0.5, volume=50),
+    'startup': scale(400, 1601, 400, length=0.1),
+    'zoop': scale(1000, 1600, 40, length=0.005, volume=50),
+    'zzzzz': Chunk('sine', freq=200, length=0.25, volume=50),
+    'yipee': itertools.chain(
+        scale(1400, 1801, 100, length=0.05, volume=50),
+        scale(1600, 1801, 100, length=0.05, volume=50),
+    ),
+    'awwww': (
+        Chunk('sine', freq=800, length=0.1, volume=50),
+        Chunk('sine', freq=500, length=0.2, volume=50),
+    ),
+    'aha': (
+        Chunk('sine', freq=2000, length=0.1, volume=50),
+        Chunk('sine', freq=2200, length=0.05, volume=50),
+    ),
 }
diff --git a/data/sounds/aha.ogg b/data/sounds/aha.ogg
new file mode 100644 (file)
index 0000000..49589e0
Binary files /dev/null and b/data/sounds/aha.ogg differ
diff --git a/data/sounds/awwww.ogg b/data/sounds/awwww.ogg
new file mode 100644 (file)
index 0000000..2ab41d9
Binary files /dev/null and b/data/sounds/awwww.ogg differ
index da5ef18a5f52eda40d8ca8e15834bb3e976ad3ee..249d00417c4aedf07599fa7ce130eab7ba9a98e3 100644 (file)
Binary files a/data/sounds/chirp.ogg and b/data/sounds/chirp.ogg differ
index ac62c6bdd36d4f335520a87e3a1bcf3f4206812a..32004281da2fe138a6685b71fb649b0a01d48391 100644 (file)
Binary files a/data/sounds/error.ogg and b/data/sounds/error.ogg differ
index 7fc162686b28df2a2745a000f3694a2f30fec961..800e7b50b08713894dba64f6091353065e619111 100644 (file)
Binary files a/data/sounds/grind.ogg and b/data/sounds/grind.ogg differ
index c7048900120a16c8878ef7477a68d37d3915a240..6bd99a868da6fb854983881a60fd065363a4644c 100644 (file)
Binary files a/data/sounds/shutdown.ogg and b/data/sounds/shutdown.ogg differ
index 967551903598c9f53dbf8764394fba9394b991d3..93bfcc8ae29cc9d6fe8b586e43ec8d434dd94a03 100644 (file)
Binary files a/data/sounds/silence.ogg and b/data/sounds/silence.ogg differ
index ad2f39b0d1aa757daec45d53457bf9976f892c63..0a4ca4f1baab4f0db21e5c35062b70132c51daa6 100644 (file)
Binary files a/data/sounds/startup.ogg and b/data/sounds/startup.ogg differ
diff --git a/data/sounds/yipee.ogg b/data/sounds/yipee.ogg
new file mode 100644 (file)
index 0000000..d4930a7
Binary files /dev/null and b/data/sounds/yipee.ogg differ
index bee8aeb41679cc60e9868b0ca984e082fea40e10..eb28c59c90521dfc1290868e52fe91035ed30c26 100644 (file)
Binary files a/data/sounds/zoop.ogg and b/data/sounds/zoop.ogg differ
index 2a185c1494d4ec8d64c4a2a233d8282a6a76372a..cf61dc8e8f32b1ee50bb3d6ec98e92bc2788c1eb 100644 (file)
Binary files a/data/sounds/zzzzz.ogg and b/data/sounds/zzzzz.ogg differ
index c0b4c90b1c2d86c367106820613d1b17bac70be3..7635ec158a6dc3afe06cf1c2a44f182726651953 100644 (file)
@@ -5,7 +5,7 @@ Source: https://ctpug.org.za/git/naja/
 
 Files: *
 Copyright:
- 2014, Adrianna Pinska,
+ 2014, Adrianna Pińska,
  2014, David Sharpe,
  2014, Jeremy Thurgood,
  2014, Neil Muller,
index d73a66aae4ba0dfede3cbd60e1961531f9299146..9f3b221b3af18cf033093465d0306a9ec1ddab93 100644 (file)
@@ -82,6 +82,7 @@ class LoseHealthOrMSB(LocationAction):
 
     def perform_action(self, board, location):
         if not self.check_and_clear_MSB(board.player):
+            sound.play_sound('awwww.ogg')
             board.lose_health()
 
 
@@ -120,6 +121,7 @@ class LoseHealthOrMSBAndSetBits(LocationAction):
 
     def perform_action(self, board, location):
         if not self.check_and_clear_MSB(board.player):
+            sound.play_sound('awwww.ogg')
             board.lose_health()
         board.player.bits.set_bits(location.bitwise_operand)
 
@@ -129,6 +131,7 @@ class AcquireWinToken(LocationAction):
     GLYPHS = (ACTION_GLYPHS.WINTOKEN,)
 
     def perform_action(self, board, location):
+        sound.play_sound('yipee.ogg')
         board.acquire_win_token()
         board.player.bits.clear_bits(set([
             BITS.RED, BITS.GREEN, BITS.BLUE,
@@ -140,6 +143,7 @@ class GainHealth(LocationAction):
     GLYPHS = (ACTION_GLYPHS.HEAL,)
 
     def perform_action(self, board, location):
+        sound.play_sound('aha.ogg')
         board.gain_health()
 
 
@@ -149,6 +153,7 @@ class GainHealthAndClearBitsOrMSB(LocationAction):
     MSB_GLYPH = ACTION_GLYPHS.CLEAR_BITS
 
     def perform_action(self, board, location):
+        sound.play_sound('aha.ogg')
         board.gain_health()
         if not self.check_and_clear_MSB(board.player):
             board.player.bits.clear_bits(location.bitwise_operand)
index ed8d74f4657974079b3978ece51edbb0a30b3499..ad692e716e8ee414371c72152f04ea0672233403 100644 (file)
@@ -120,8 +120,9 @@ class GameBoard(object):
         replacement_params = deck.get('replacement_params', None)
         for x in range(5):
             for y in range(5):
+                new_choice = cls.choose_card(deck['cards'], board_locations)
                 board_location = LocationCard.new_location(
-                    choice(deck['cards']).copy(), replacement_params)
+                    new_choice.copy(), replacement_params)
                 board_locations.append([(x, y), board_location.export()])
         return board_locations
 
@@ -144,10 +145,36 @@ class GameBoard(object):
             self.replace_card(position)
 
     def replace_card(self, position):
-        location = LocationCard.new_location(choice(self.locations).copy(),
+        new_choice = self.choose_card(self.locations,
+                                      self.board_locations.items(),
+                                      position)
+        location = LocationCard.new_location(new_choice.copy(),
                                              self.replacement_params)
         self.board_locations[position] = location
 
+    @classmethod
+    def choose_card(cls, cards, board_locations, position=None):
+        # Find which cards are at their maximum and exclude them from
+        # the choice list
+        counts = {}
+        choices = {card['card_name']: card for card in cards}
+        for pos, card in board_locations:
+            if pos == position:
+                # skip the card we're replacing if appropriate
+                continue
+            if isinstance(card, LocationCard):
+                key = card.card_name
+                max_num = card.max_number
+            else:
+                key = card['card_name']
+                max_num = card.get('max_number', 25)
+            counts.setdefault(key, 0)
+            counts[key] += 1
+            if counts[key] >= max_num:
+                if key in choices:
+                    del choices[key]
+        return choice(choices.values())
+
     def shift_location_row(self, change, is_vertical):
         px, py = self.player.position
         shifted_locations = {}
@@ -240,10 +267,11 @@ class LocationCard(object):
     """
 
     def __init__(self, card_name, bitwise_operand, location_actions,
-                 replacement_time):
+                 replacement_time, max_number=25):
         self.card_name = card_name
         self.bitwise_operand = bitwise_operand
         self.actions = location_actions
+        self.max_number = max_number
         self.check_actions()
         self.replacement_time = replacement_time
 
@@ -252,7 +280,8 @@ class LocationCard(object):
         location_actions = [
             cls.build_action(definition) for definition in state['actions']]
         return cls(state['card_name'], state['bitwise_operand'],
-                   location_actions, state['replacement_time'])
+                   location_actions, state['replacement_time'],
+                   state['max_number'])
 
     @classmethod
     def build_action(cls, definition):
@@ -274,10 +303,12 @@ class LocationCard(object):
             replacement_time = cls.generate_replacement_time(
                 replacement_params)
 
+        max_number = definition.get('max_number', 25)
         card_name = definition['card_name']
         return cls.import_location({
             'bitwise_operand': bits,
             'actions': definition['actions'],
+            'max_number': max_number,
             'card_name': card_name,
             'replacement_time': replacement_time,
         })
@@ -291,6 +322,7 @@ class LocationCard(object):
         return {
             'bitwise_operand': sorted(self.bitwise_operand),
             'actions': [action.export() for action in self.actions],
+            'max_number': self.max_number,
             'card_name': self.card_name,
             'replacement_time': self.replacement_time,
         }
index a32438170e3c5663366850cde744027507b57ceb..d00c7fc182b8af7887b4f1fab472b38f25ebf35f 100644 (file)
@@ -2,23 +2,15 @@
 The current game state.
 """
 
-try:
-    import yaml
-except ImportError:
-    yaml = None
-    import json
-
 from naja.gameboard import GameBoard
-from naja.resources import resources
+from naja.resources import resources, ResourceNotFound
 
 
 def load_location_deck(name):
-    if yaml:
-        with resources.get_file('location_decks', '%s.yaml' % name) as deck_fp:
-            return yaml.safe_load(deck_fp)
-    else:
-        with resources.get_file('location_decks', '%s.json' % name) as deck_fp:
-            return json.load(deck_fp)
+    try:
+        return resources.get_yaml('location_decks', '%s.yaml' % name)
+    except ResourceNotFound:
+        return resources.get_json('location_decks', '%s.json' % name)
 
 
 class GameState(object):
index 8ad04795ec0036d37c4e9e9080a53d5112b5fdd0..f324c8c7212ce6bfeecd36781bbbe84c4971c071 100644 (file)
@@ -36,7 +36,7 @@ class Chunk(object):
 
     def raw(self):
         if self.type == 'silence':
-            for i in xrange(int(176400 * self.length)):
+            for i in xrange(int(88200 * self.length)):
                 yield '\x00'
 
         if self.type == 'sine':
index 19d3e741aec5ae02b552af786b949567b6ac7697..73129490cf7903e4796543968f13248495c97353 100644 (file)
@@ -32,6 +32,8 @@ def load_deck(parser, deck):
     try:
         state = GameState.new(deck=deck, max_health=4, wins_required=4)
     except:
+        if options.debug:
+            raise
         parser.error("Could not load deck %r" % (deck,))
     options.game_state = state
 
index 191cbd4170fe20399dcb6acd8bd764b0c9933c94..b080786609851f3953531ef5f47e682e7ef6256e 100644 (file)
@@ -1,3 +1,5 @@
-from naja.resources.loader import Loader
+from naja.resources.loader import Loader, ResourceNotFound
+
+__all__ = ['resources', 'ResourceNotFound']
 
 resources = Loader('data')
index 28a3c7a3fdf0f39cb85918d3f97f0dbb964b69e0..69eff2fed3fafa891fb9e8c32dbbfd090d24f360 100644 (file)
@@ -1,5 +1,6 @@
 import os
 import sys
+import json
 
 try:
     from pkg_resources import resource_filename
@@ -11,6 +12,12 @@ except ImportError:
         # time going down this rabbithole already
         return os.path.join(os.path.dirname(__file__), '..', '..', 'data',
                             path)
+
+try:
+    import yaml
+except ImportError:
+    yaml = None
+
 import pygame
 
 
@@ -99,3 +106,15 @@ class Loader(object):
             self._cache[key] = pygame.font.Font(fn, font_size)
 
         return self._cache[key]
+
+    def get_yaml(self, *path_fragments):
+        if yaml is None:
+            raise ResourceNotFound("%s - %s" % (
+                os.path.join(*path_fragments),
+                "YAML module not available"))
+        with self.get_file(*path_fragments) as yaml_file:
+            return yaml.safe_load(yaml_file)
+
+    def get_json(self, *path_fragments):
+        with self.get_file(*path_fragments) as json_file:
+            return json.load(json_file)
index a6ae05d2b6ce1de404332d50fe48cc7a7753b67d..75f1b3ce5ada6d6b13cd638770b869c732c1cf07 100644 (file)
@@ -1,3 +1,4 @@
+# coding: UTF-8
 """
 Credits scene.
 """
@@ -31,8 +32,8 @@ class CreditsScene(Scene):
                 '',
                 'by',
                 '',
-                'Adrianna Pinska, David Sharpe, Jeremy Thurgood, '
-                'Neil Muller, Simon Cross & Stefano Rivera',
+                u'Adrianna Pińska, David Sharpe, Jeremy Thurgood, '
+                u'Neil Muller, Simon Cross & Stefano Rivera',
                 '',
                 'Music by Rolemusic:',
                 'http://rolemusic.sawsquarenoise.com/',
index d1812c5b88c52e6d7e957245c35ae8497bf475fb..4dde093a9a12ecff4faf807cb10c4e967115a5ca 100644 (file)
@@ -16,6 +16,7 @@ from naja.scenes.new_game import NewGameScene
 from naja.widgets.selector import SelectorWidget
 from naja.widgets.text import TextWidget
 from naja.widgets.image_box import ImageBox
+from naja.sound import sound
 
 
 class MenuScene(Scene):
@@ -81,6 +82,8 @@ class MenuScene(Scene):
         selector.add(quit)
 
         self.konami = []
+        if not sound.playing_music:
+            sound.play_music('scape.ogg', 0.25)
 
     def scene_callback(self, scene_class):
         return lambda event: SceneChangeEvent.post(scene_class)
index 2d61974a9bbc10dac235b41b236bc3a2a1238945..f0ef91c8224c7675050bfb22183b33af9cf20572 100644 (file)
@@ -13,7 +13,7 @@ from naja.constants import (
 
 class DummySound(object):
     def init(self):
-        pass
+        self.playing_music = False
 
     def play_sound(self, name, volume=DEFAULT_SOUND_VOLUME, foreground=False):
         pass
@@ -34,6 +34,7 @@ class DummySound(object):
 class PygameSound(object):
     def __init__(self):
         self._sounds = {}
+        self.playing_music = False
 
     def init(self):
         mixer.init(FREQ, BITSIZE, CHANNELS, BUFFER)
@@ -64,21 +65,26 @@ class PygameSound(object):
         mixer.music.load(track_name)
         mixer.music.set_volume(volume)
         mixer.music.play(-1)  # loop sound
+        self.playing_music = True
 
     def pause_music(self):
         mixer.music.pause()
+        self.playing_music = False
 
     def unpause_music(self):
         mixer.music.unpause()
+        self.playing_music = True
 
     def stop(self):
         mixer.fadeout(1000)
         mixer.music.stop()
+        self.playing_music = False
 
 
 class SoundProxy(object):
     def __init__(self):
         self._sound = DummySound()
+        self._sound.init()
 
     def init(self):
         """Attempt to initialize the sound system."""
index 5b383991273e9d20f919a54cfec4104913ffcbae..a72505a5d415efa0abd130c2bc5b3294db52d6c4 100644 (file)
@@ -31,7 +31,7 @@ class TestGameBoard(TestCase):
 
     def test_export_new_board(self):
         board = GameBoard.new_game({'cards': [
-            {'card_name' : 'card1', 'actions': [
+            {'card_name': 'card1', 'actions': [
             {
                 'action_class': 'LoseHealthOrMSB',
                 'required_bits': [],
@@ -47,7 +47,7 @@ class TestGameBoard(TestCase):
             'health': 4,
             'wins_required': 4,
             'wins': 0,
-            'locations': [{'card_name' : 'card1', 'actions': [
+            'locations': [{'card_name': 'card1', 'actions': [
                 {
                     'action_class': 'LoseHealthOrMSB',
                     'required_bits': [],
@@ -65,7 +65,7 @@ class TestGameBoard(TestCase):
             positions.append(position)
             self.assertEqual(
                 sorted(location_state.keys()), ['actions', 'bitwise_operand',
-                                                'card_name'])
+                                                'card_name', 'max_number'])
             self.assertEqual(location_state['actions'], [
                 {
                     'action_class': 'LoseHealthOrMSB',
@@ -81,6 +81,89 @@ class TestGameBoard(TestCase):
         self.assertEqual(
             positions, sorted((x, y) for x in range(5) for y in range(5)))
 
+    def test_max_number(self):
+        def _check_counts(board):
+            counts = {}
+            exported_state = board.export()
+            board_locations = exported_state.pop('board_locations')
+            for _position, location_state in board_locations:
+                counts.setdefault(
+                    location_state['actions'][0]['action_class'], 0)
+                counts[location_state['actions'][0]['action_class']] += 1
+            self.assertTrue(counts.get('LoseHealthOrMSB', 0) <= 5)
+
+        board = GameBoard.new_game({'cards': [
+            {'card_name': 'card1', 'actions': [{
+                'action_class': 'LoseHealthOrMSB',
+                'required_bits': [], }],
+             'max_number': 5},
+            {'card_name': 'card2', 'actions': [{
+                'action_class': 'DoNothing',
+                'required_bits': [], }],
+             'max_number': 25}]})
+        # check creation constraints
+        _check_counts(board)
+        # Check replacement
+        # Replace center card 12 times and assert invariant still holds
+        for x in range(12):
+            board.replace_card((2, 2))
+        _check_counts(board)
+        # replace a diagonal of cards
+        for x in range(5):
+            board.replace_card((x, x))
+        _check_counts(board)
+
+    def test_max_number_complex(self):
+        def _check_counts(board):
+            counts = {}
+            exported_state = board.export()
+            board_locations = exported_state.pop('board_locations')
+            for _position, location_state in board_locations:
+                counts.setdefault(
+                    location_state['actions'][0]['action_class'], 0)
+                counts[location_state['actions'][0]['action_class']] += 1
+            self.assertTrue(counts.get('LoseHealthOrMSB', 0) <= 5)
+            self.assertTrue(counts.get('DoNothing', 0) <= 3)
+            self.assertTrue(counts.get('AcquireWinToken', 0) <= 4)
+            self.assertTrue(counts.get('GainHealth', 0) <= 3)
+
+        board = GameBoard.new_game({'cards': [
+            {'card_name': 'card1', 'actions': [{
+                'action_class': 'LoseHealthOrMSB',
+                'required_bits': [], }],
+             'max_number': 5},
+            {'card_name': 'card2', 'actions': [{
+                'action_class': 'AcquireWinToken',
+                'required_bits': [], }],
+             'max_number': 4},
+            {'card_name': 'card3', 'actions': [{
+                'action_class': 'GainHealth',
+                'required_bits': [], }],
+             'max_number': 3},
+            {'card_name': 'card4', 'actions': [{
+                'action_class': 'RotateLocations',
+                'required_bits': [], }],
+             'max_number': 25},
+            {'card_name': 'card5', 'actions': [{
+                'action_class': 'AllowChessMove',
+                'required_bits': [], }],
+             'max_number': 25},
+            {'card_name': 'card6', 'actions': [{
+                'action_class': 'DoNothing',
+                'required_bits': [], }],
+             'max_number': 3}]})
+        # check creation constraints
+        _check_counts(board)
+        # Check replacement
+        # Replace center card 12 times and assert invariant still holds
+        for x in range(12):
+            board.replace_card((2, 2))
+        _check_counts(board)
+        # replace a diagonal of cards
+        for x in range(5):
+            board.replace_card((x, x))
+        _check_counts(board)
+
     def test_lose_health(self):
         board = GameBoard.new_game(self.make_deck())
         self.assertEqual(board.health, 4)
index 6501fd730e6dd3912f581282181ee0250f516a0d..f0115550f80eef65ede91b8c17f10b8f3f6a4bff 100644 (file)
@@ -59,7 +59,7 @@ class TextWidget(Widget):
         self.centre = centre
         self.centre_pos = pos
         self.border = border
-        self.border_colour = border_colour
+        self.border_colour = convert_colour(border_colour)
         self.padding = padding
 
     def render_line(self, text):
index c9326670251d2988704aa2e35d0a7829048682f9..5417dcf3ba3e4e8960c829d76dc8e7c4353a09cf 100644 (file)
@@ -34,10 +34,19 @@ class TileWidget(Widget):
         # Draw background
         x, y = abs(self.board_pos[0] - 2), abs(self.board_pos[1] - 2)
 
+        if self.state.gameboard.puzzle:
+            tile_1_name = 'board/tile_1.png'
+            tile_2_name = 'board/tile_2_puzzle.png'
+            tile_available_name = 'board/tile_available_puzzle.png'
+        else:
+            tile_1_name = 'board/tile_1.png'
+            tile_2_name = 'board/tile_2.png'
+            tile_available_name = 'board/tile_available.png'
+
         if (x + y) % 2 == 0:
-            bg_name = 'board/tile_2.png'
+            bg_name = tile_2_name
         else:
-            bg_name = 'board/tile_1.png'
+            bg_name = tile_1_name
         bg = resources.get_image(bg_name, transforms=(EIGHT_BIT,))
         overlays = []
 
@@ -45,7 +54,7 @@ class TileWidget(Widget):
 
         if self.state.gameboard.player_mode == EXAMINE and legal_move:
             overlays.append(resources.get_image(
-                'board/tile_available.png', transforms=(EIGHT_BIT,)))
+                tile_available_name, transforms=(EIGHT_BIT,)))
         if self.highlighted:
             overlays.append(resources.get_image(
                 'board/tile_selected.png',
index f39ff02542efe5d89ebfbb3b2f7eccc7c9a0940e..49e8fffbda9d9f1acced73b9fd20c32bbf7621c4 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ setup(
     author=(", ".join([
         "Simon Cross",
         "Neil Muller",
-        "Adrianna Pinska",
+        "Adrianna Pińska",
         "Stefano Rivera",
         "David Sharpe",
         "Jeremy Thurgood",
index 64dbd9f13e655c602c97293a91cf2335b2c598c1..05a1503ff6156877ba339ec812b1f16289bad9b4 100644 (file)
Binary files a/sources/images/square.xcf and b/sources/images/square.xcf differ
index a85211961824401572aa98a4dd897f1d1c3501c9..94bf3d12cd59d7891ece2dc2674a901737eaf982 100755 (executable)
@@ -13,7 +13,8 @@ def gen_raw(description):
 
 def encode(filename, data):
     print "Writing %s" % filename
-    p = Popen(('oggenc', '-o', filename, '--raw', '--quiet', '-'),
+    p = Popen(('oggenc', '-o', filename, '--raw', '--raw-chan=1',
+               '--quiet', '-'),
               stdin=PIPE, cwd='data/sounds')
     for blob in data:
         p.stdin.write(blob)