-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
Members:
Simon Cross
Neil Muller
- Adrianna Pinska
+ Adrianna Pińska
Stefano Rivera
David Sharpe
Jeremy Thurgood
--- /dev/null
+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 $@ $^
_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
+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),
+ ),
}
Files: *
Copyright:
- 2014, Adrianna Pinska,
+ 2014, Adrianna Pińska,
2014, David Sharpe,
2014, Jeremy Thurgood,
2014, Neil Muller,
def perform_action(self, board, location):
if not self.check_and_clear_MSB(board.player):
+ sound.play_sound('awwww.ogg')
board.lose_health()
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)
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,
GLYPHS = (ACTION_GLYPHS.HEAL,)
def perform_action(self, board, location):
+ sound.play_sound('aha.ogg')
board.gain_health()
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)
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
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 = {}
"""
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
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):
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,
})
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,
}
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):
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':
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
-from naja.resources.loader import Loader
+from naja.resources.loader import Loader, ResourceNotFound
+
+__all__ = ['resources', 'ResourceNotFound']
resources = Loader('data')
import os
import sys
+import json
try:
from pkg_resources import resource_filename
# 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
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)
+# coding: UTF-8
"""
Credits 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/',
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):
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)
class DummySound(object):
def init(self):
- pass
+ self.playing_music = False
def play_sound(self, name, volume=DEFAULT_SOUND_VOLUME, foreground=False):
pass
class PygameSound(object):
def __init__(self):
self._sounds = {}
+ self.playing_music = False
def init(self):
mixer.init(FREQ, BITSIZE, CHANNELS, BUFFER)
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."""
def test_export_new_board(self):
board = GameBoard.new_game({'cards': [
- {'card_name' : 'card1', 'actions': [
+ {'card_name': 'card1', 'actions': [
{
'action_class': 'LoseHealthOrMSB',
'required_bits': [],
'health': 4,
'wins_required': 4,
'wins': 0,
- 'locations': [{'card_name' : 'card1', 'actions': [
+ 'locations': [{'card_name': 'card1', 'actions': [
{
'action_class': 'LoseHealthOrMSB',
'required_bits': [],
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',
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)
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):
# 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 = []
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',
author=(", ".join([
"Simon Cross",
"Neil Muller",
- "Adrianna Pinska",
+ "Adrianna Pińska",
"Stefano Rivera",
"David Sharpe",
"Jeremy Thurgood",
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)