1 from random import choice
3 from naja.constants import(
4 BITS, DIRECTION_BITS, CONDITION_BITS, PLAYER_DEFAULTS,
6 from naja.player import Player
7 from naja import actions
10 class GameBoard(object):
12 A representation of the game board.
15 def __init__(self, state, player, board_locations):
16 self.max_health = state['max_health']
17 self.wins_required = state['wins_required']
18 self.health = state['health']
19 self.wins = state['wins']
20 self.locations = [item.copy() for item in state['locations']]
22 self.board_locations = board_locations
23 self.player_mode = EXAMINE
26 def new_game(cls, locations_definition,
27 initial_bits=PLAYER_DEFAULTS.INITIAL_BITS,
28 initial_pos=PLAYER_DEFAULTS.INITIAL_POS,
29 max_health=PLAYER_DEFAULTS.MAX_HEALTH,
30 wins_required=PLAYER_DEFAULTS.WINS_REQUIRED):
32 'max_health': max_health,
34 'wins_required': wins_required,
36 'locations': locations_definition,
38 player = Player(initial_bits, initial_pos)
39 board_locations = cls.import_board_locations(
40 cls.generate_board(locations_definition))
41 return cls(state, player, board_locations)
44 def import_game(cls, definition):
45 state = definition.copy()
46 player = Player.import_player(state.pop('player'))
47 board_locations = cls.import_board_locations(
48 state.pop('board_locations'))
49 return cls(state, player, board_locations)
53 'max_health': self.max_health,
54 'health': self.health,
55 'wins_required': self.wins_required,
57 'locations': [item.copy() for item in self.locations],
58 'player': self.player.export(),
59 'board_locations': self.export_board_locations(),
63 def import_locations(cls, locations_definition):
65 LocationCard.import_location(definition)
66 for definition in locations_definition]
68 def export_board_locations(self):
70 (position, location.export())
71 for position, location in self.board_locations.iteritems())
74 def import_board_locations(cls, board_locations_definition):
76 (position, LocationCard.import_location(definition))
77 for position, definition in board_locations_definition.iteritems())
80 def generate_board(cls, locations_definition):
84 board_location = LocationCard.new_location(
85 choice(locations_definition).copy())
86 board_locations[(x, y)] = board_location.export()
87 return board_locations
89 def lose_health(self):
92 self.end_game(win=False)
94 def gain_health(self):
95 if self.health < self.max_health:
98 def acquire_win_token(self):
100 if self.wins >= self.wins_required:
101 self.end_game(win=True)
103 def replace_card(self, position):
104 location = LocationCard.new_location(choice(self.locations).copy())
105 self.board_locations[position] = location
107 def shift_location_row(self, change, is_vertical):
108 px, py = self.player.position
109 shifted_locations = {}
110 mkpos = lambda i: (px, i) if is_vertical else (i, py)
113 if (px, py) == mkpos(i):
115 new_i = (i + change) % 5
116 if (px, py) == mkpos(new_i):
117 new_i = (new_i + change) % 5
118 shifted_locations[mkpos(new_i)] = self.board_locations[mkpos(i)]
120 print change, is_vertical, shifted_locations
121 self.board_locations.update(shifted_locations)
123 def shift_locations(self, direction):
124 if BITS[direction] == BITS.NORTH:
125 self.shift_location_row(-1, is_vertical=True)
126 elif BITS[direction] == BITS.SOUTH:
127 self.shift_location_row(1, is_vertical=True)
128 elif BITS[direction] == BITS.EAST:
129 self.shift_location_row(1, is_vertical=False)
130 elif BITS[direction] == BITS.WEST:
131 self.shift_location_row(-1, is_vertical=False)
133 def change_mode(self, new_mode):
134 """Advance to the next mode"""
135 if new_mode == self.player_mode:
136 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
138 elif new_mode in (ACT, EXAMINE):
139 self.player_mode = new_mode
141 raise RuntimeError("Illegal player mode %s" % self.player_mode)
143 def end_game(self, win):
144 # TODO: Find a way to not have UI stuff in game logic stuff.
145 from naja.events import SceneChangeEvent
146 from naja.scenes.lose import LoseScene
147 from naja.scenes.win import WinScene
149 SceneChangeEvent.post(WinScene)
151 SceneChangeEvent.post(LoseScene)
154 class LocationCard(object):
156 A particular set of options available on a location.
159 def __init__(self, bitwise_operand, location_actions):
160 self.bitwise_operand = bitwise_operand
161 self.actions = location_actions
165 def import_location(cls, state):
167 cls.build_action(definition) for definition in state['actions']]
168 return cls(state['bitwise_operand'], location_actions)
171 def build_action(cls, definition):
172 action_class = getattr(actions, definition['action_class'])
173 required_bits = cls.parse_bits(definition['required_bits'])
174 data = definition.get('data', {})
175 return action_class(required_bits, **data)
178 def new_location(cls, definition):
179 if 'bits' in definition:
180 bits = cls.parse_bits(definition['bits'])
182 bits = cls.generate_bitwise_operand()
183 return cls.import_location({
184 'bitwise_operand': bits,
185 'actions': definition['actions'],
189 def parse_bits(self, bit_list):
190 # Convert names to numbers if applicable.
191 return frozenset(BITS.get(bit, bit) for bit in bit_list)
195 'bitwise_operand': self.bitwise_operand,
196 'actions': [action.export() for action in self.actions],
199 def check_actions(self):
201 print "Warning: Location has no actions."
202 self.insert_default_default_action()
203 if self.actions[0].required_bits:
204 self.insert_default_default_action()
206 def insert_default_default_action(self):
207 self.actions.insert(0, self.build_action({
208 'action_class': 'DoNothing',
213 def generate_bitwise_operand():
215 Generate a set of two or three bits. At least one direction and one
216 condition bit will be included. There is a low probability of choosing
217 a third bit from the complete set.
220 bits.add(choice(DIRECTION_BITS.values()))
221 bits.add(choice(CONDITION_BITS.values()))
222 # One in three chance of adding a third bit, with a further one in four
223 # chance that it will match a bit already chosen.
224 if choice(range(3)) == 0:
225 bits.add(choice(BITS.values()))
226 return frozenset(bits)