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):
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)
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])
@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()
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
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,
'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
'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 [
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):
@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
@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)
The current game state.
"""
-from naja.constants import BITS
from naja.gameboard import GameBoard
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):
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)
--- /dev/null
+"""
+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
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)
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()
Base Scene class.
"""
+from naja.widgets.base import Container
+
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
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
from unittest import TestCase
+from naja.constants import BITS
+from naja.gameboard import GameBoard, LocationCard
from naja.player import Player
from naja import actions
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)
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()
import pygame
+from naja.events import InvalidateTheWorld
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