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 (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 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:
154 new_positions = [locations_to_rotate[-1]] + locations_to_rotate[:-1]
156 for old, new in zip(locations_to_rotate, new_positions):
157 rotated_locations[old] = self.board_locations[new]
159 self.board_locations.update(rotated_locations)
161 def allow_chess_move(self, chesspiece):
162 self.player.allow_chess_move(chesspiece)
164 def change_mode(self, new_mode):
165 """Advance to the next mode"""
166 if new_mode == self.player_mode:
167 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
169 elif new_mode in (ACT, EXAMINE):
170 self.player_mode = new_mode
172 raise RuntimeError("Illegal player mode %s" % self.player_mode)
174 def end_game(self, win):
175 # TODO: Find a way to not have UI stuff in game logic stuff.
176 from naja.events import SceneChangeEvent
177 from naja.scenes.lose import LoseScene
178 from naja.scenes.win import WinScene
180 SceneChangeEvent.post(WinScene)
182 SceneChangeEvent.post(LoseScene)
185 class LocationCard(object):
187 A particular set of options available on a location.
190 def __init__(self, bitwise_operand, location_actions):
191 self.bitwise_operand = bitwise_operand
192 self.actions = location_actions
196 def import_location(cls, state):
198 cls.build_action(definition) for definition in state['actions']]
199 return cls(state['bitwise_operand'], location_actions)
202 def build_action(cls, definition):
203 action_class = getattr(actions, definition['action_class'])
204 required_bits = cls.parse_bits(definition['required_bits'])
205 data = definition.get('data', {})
206 return action_class(required_bits, **data)
209 def new_location(cls, definition):
210 if 'bits' in definition:
211 bits = cls.parse_bits(definition['bits'])
213 bits = cls.generate_bitwise_operand()
214 return cls.import_location({
215 'bitwise_operand': bits,
216 'actions': definition['actions'],
220 def parse_bits(self, bit_list):
221 # Convert names to numbers if applicable.
222 return frozenset(BITS.get(bit, bit) for bit in bit_list)
226 'bitwise_operand': self.bitwise_operand,
227 'actions': [action.export() for action in self.actions],
230 def check_actions(self):
232 print "Warning: Location has no actions."
233 self.insert_default_default_action()
234 if self.actions[0].required_bits:
235 self.insert_default_default_action()
237 def insert_default_default_action(self):
238 self.actions.insert(0, self.build_action({
239 'action_class': 'DoNothing',
244 def generate_bitwise_operand():
246 Generate a set of two or three bits. At least one direction and one
247 condition bit will be included. There is a low probability of choosing
248 a third bit from the complete set.
251 bits.add(choice(DIRECTION_BITS.values()))
252 bits.add(choice(CONDITION_BITS.values()))
253 # One in three chance of adding a third bit, with a further one in four
254 # chance that it will match a bit already chosen.
255 if choice(range(3)) == 0:
256 bits.add(choice(BITS.values()))
257 return frozenset(bits)