1 from random import choice
3 from naja.constants import(
4 BITS, DIRECTION_BITS, CONDITION_BITS, PLAYER_DEFAULTS,
5 ACT, EXAMINE, ROTATION)
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 (tuple(position), LocationCard.import_location(definition))
77 for position, definition in board_locations_definition)
80 def generate_board(cls, locations_definition):
84 board_location = LocationCard.new_location(
85 choice(locations_definition).copy())
86 board_locations.append([(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 self.board_locations.update(shifted_locations)
122 def shift_locations(self, direction):
123 if BITS[direction] == BITS.NORTH:
124 self.shift_location_row(-1, is_vertical=True)
125 elif BITS[direction] == BITS.SOUTH:
126 self.shift_location_row(1, is_vertical=True)
127 elif BITS[direction] == BITS.EAST:
128 self.shift_location_row(1, is_vertical=False)
129 elif BITS[direction] == BITS.WEST:
130 self.shift_location_row(-1, is_vertical=False)
132 def rotate_locations(self, direction):
133 px, py = self.player.position
134 locations_to_rotate = []
135 rotated_locations = {}
138 for i in range(max(0, px - 1), min(5, px + 2)):
139 locations_to_rotate.append((i, py - 1))
142 locations_to_rotate.append((px + 1, py))
145 for i in reversed(range(max(0, px - 1), min(5, px + 2))):
146 locations_to_rotate.append((i, py + 1))
149 locations_to_rotate.append((px - 1, py))
151 if ROTATION[direction] == ROTATION.CLOCKWISE:
152 new_positions = locations_to_rotate[1:] + [locations_to_rotate[0]]
153 elif ROTATION[direction] == ROTATION.ANTICLOCKWISE:
155 [locations_to_rotate[-1]] + locations_to_rotate[:-1])
157 for old, new in zip(locations_to_rotate, new_positions):
158 rotated_locations[old] = self.board_locations[new]
160 self.board_locations.update(rotated_locations)
162 def allow_chess_move(self, chesspiece):
163 self.player.allow_chess_move(chesspiece)
165 def change_mode(self, new_mode):
166 """Advance to the next mode"""
167 if new_mode == self.player_mode:
168 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
170 elif new_mode in (ACT, EXAMINE):
171 self.player_mode = new_mode
173 raise RuntimeError("Illegal player mode %s" % self.player_mode)
175 def end_game(self, win):
176 # TODO: Find a way to not have UI stuff in game logic stuff.
177 from naja.events import SceneChangeEvent
178 from naja.scenes.lose import LoseScene
179 from naja.scenes.win import WinScene
181 SceneChangeEvent.post(WinScene)
183 SceneChangeEvent.post(LoseScene)
186 class LocationCard(object):
188 A particular set of options available on a location.
191 def __init__(self, bitwise_operand, location_actions):
192 self.bitwise_operand = bitwise_operand
193 self.actions = location_actions
197 def import_location(cls, state):
199 cls.build_action(definition) for definition in state['actions']]
200 return cls(state['bitwise_operand'], location_actions)
203 def build_action(cls, definition):
204 action_class = getattr(actions, definition['action_class'])
205 required_bits = cls.parse_bits(definition['required_bits'])
206 data = definition.get('data', {})
207 return action_class(required_bits, **data)
210 def new_location(cls, definition):
211 if 'bits' in definition:
212 bits = cls.parse_bits(definition['bits'])
214 bits = cls.generate_bitwise_operand()
215 return cls.import_location({
216 'bitwise_operand': bits,
217 'actions': definition['actions'],
221 def parse_bits(self, bit_list):
222 # Convert names to numbers if applicable.
223 return frozenset(BITS.get(bit, bit) for bit in bit_list)
227 'bitwise_operand': sorted(self.bitwise_operand),
228 'actions': [action.export() for action in self.actions],
231 def check_actions(self):
233 print "Warning: Location has no actions."
234 self.insert_default_default_action()
235 if self.actions[0].required_bits:
236 self.insert_default_default_action()
238 def insert_default_default_action(self):
239 self.actions.insert(0, self.build_action({
240 'action_class': 'DoNothing',
245 def generate_bitwise_operand():
247 Generate a set of two or three bits. At least one direction and one
248 condition bit will be included. There is a low probability of choosing
249 a third bit from the complete set.
252 bits.add(choice(DIRECTION_BITS.values()))
253 bits.add(choice(CONDITION_BITS.values()))
254 # One in three chance of adding a third bit, with a further one in four
255 # chance that it will match a bit already chosen.
256 if choice(range(3)) == 0:
257 bits.add(choice(BITS.values()))
258 return frozenset(bits)