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.options import options
7 from naja.player import Player
8 from naja import actions
11 class GameBoard(object):
13 A representation of the game board.
16 def __init__(self, state, player, board_locations):
17 self.max_health = state['max_health']
18 self.wins_required = state['wins_required']
19 self.health = state['health']
20 self.wins = state['wins']
21 self.locations = [item.copy() for item in state['locations']]
23 self.board_locations = board_locations
24 self.player_mode = EXAMINE
27 def new_game(cls, locations_definition,
28 initial_bits=PLAYER_DEFAULTS.INITIAL_BITS,
29 initial_pos=PLAYER_DEFAULTS.INITIAL_POS,
30 max_health=PLAYER_DEFAULTS.MAX_HEALTH,
31 wins_required=PLAYER_DEFAULTS.WINS_REQUIRED):
32 if options.initial_bits:
33 initial_bits = options.initial_bits
35 'max_health': max_health,
37 'wins_required': wins_required,
39 'locations': locations_definition,
41 player = Player(initial_bits, initial_pos)
42 board_locations = cls.import_board_locations(
43 cls.generate_board(locations_definition))
44 return cls(state, player, board_locations)
47 def import_game(cls, definition):
48 state = definition.copy()
49 player = Player.import_player(state.pop('player'))
50 board_locations = cls.import_board_locations(
51 state.pop('board_locations'))
52 return cls(state, player, board_locations)
56 'max_health': self.max_health,
57 'health': self.health,
58 'wins_required': self.wins_required,
60 'locations': [item.copy() for item in self.locations],
61 'player': self.player.export(),
62 'board_locations': self.export_board_locations(),
66 def import_locations(cls, locations_definition):
68 LocationCard.import_location(definition)
69 for definition in locations_definition]
71 def export_board_locations(self):
73 (position, location.export())
74 for position, location in self.board_locations.iteritems())
77 def import_board_locations(cls, board_locations_definition):
79 (tuple(position), LocationCard.import_location(definition))
80 for position, definition in board_locations_definition)
83 def generate_board(cls, locations_definition):
87 board_location = LocationCard.new_location(
88 choice(locations_definition).copy())
89 board_locations.append([(x, y), board_location.export()])
90 return board_locations
92 def lose_health(self):
95 self.end_game(win=False)
97 def gain_health(self):
98 if self.health < self.max_health:
101 def acquire_win_token(self):
103 if self.wins >= self.wins_required:
104 self.end_game(win=True)
106 def replace_card(self, position):
107 location = LocationCard.new_location(choice(self.locations).copy())
108 self.board_locations[position] = location
110 def shift_location_row(self, change, is_vertical):
111 px, py = self.player.position
112 shifted_locations = {}
113 mkpos = lambda i: (px, i) if is_vertical else (i, py)
116 if (px, py) == mkpos(i):
118 new_i = (i + change) % 5
119 if (px, py) == mkpos(new_i):
120 new_i = (new_i + change) % 5
121 shifted_locations[mkpos(new_i)] = self.board_locations[mkpos(i)]
123 self.board_locations.update(shifted_locations)
125 def shift_locations(self, direction):
126 if BITS[direction] == BITS.NORTH:
127 self.shift_location_row(-1, is_vertical=True)
128 elif BITS[direction] == BITS.SOUTH:
129 self.shift_location_row(1, is_vertical=True)
130 elif BITS[direction] == BITS.EAST:
131 self.shift_location_row(1, is_vertical=False)
132 elif BITS[direction] == BITS.WEST:
133 self.shift_location_row(-1, is_vertical=False)
135 def rotate_locations(self, direction):
136 px, py = self.player.position
137 locations_to_rotate = []
138 rotated_locations = {}
141 for i in range(max(0, px - 1), min(5, px + 2)):
142 locations_to_rotate.append((i, py - 1))
145 locations_to_rotate.append((px + 1, py))
148 for i in reversed(range(max(0, px - 1), min(5, px + 2))):
149 locations_to_rotate.append((i, py + 1))
152 locations_to_rotate.append((px - 1, py))
154 if ROTATION[direction] == ROTATION.CLOCKWISE:
155 new_positions = locations_to_rotate[1:] + [locations_to_rotate[0]]
156 elif ROTATION[direction] == ROTATION.ANTICLOCKWISE:
158 [locations_to_rotate[-1]] + locations_to_rotate[:-1])
160 for old, new in zip(locations_to_rotate, new_positions):
161 rotated_locations[old] = self.board_locations[new]
163 self.board_locations.update(rotated_locations)
165 def allow_chess_move(self, chesspiece):
166 self.player.allow_chess_move(chesspiece)
168 def change_mode(self, new_mode):
169 """Advance to the next mode"""
170 if new_mode == self.player_mode:
171 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
173 elif new_mode in (ACT, EXAMINE):
174 self.player_mode = new_mode
176 raise RuntimeError("Illegal player mode %s" % self.player_mode)
178 def end_game(self, win):
179 # TODO: Find a way to not have UI stuff in game logic stuff.
180 from naja.events import SceneChangeEvent
181 from naja.scenes.lose import LoseScene
182 from naja.scenes.win import WinScene
184 SceneChangeEvent.post(WinScene)
186 SceneChangeEvent.post(LoseScene)
189 class LocationCard(object):
191 A particular set of options available on a location.
194 def __init__(self, bitwise_operand, location_actions):
195 self.bitwise_operand = bitwise_operand
196 self.actions = location_actions
200 def import_location(cls, state):
202 cls.build_action(definition) for definition in state['actions']]
203 return cls(state['bitwise_operand'], location_actions)
206 def build_action(cls, definition):
207 action_class = getattr(actions, definition['action_class'])
208 required_bits = cls.parse_bits(definition['required_bits'])
209 data = definition.get('data', {})
210 return action_class(required_bits, **data)
213 def new_location(cls, definition):
214 if 'bits' in definition:
215 bits = cls.parse_bits(definition['bits'])
217 bits = cls.generate_bitwise_operand()
218 return cls.import_location({
219 'bitwise_operand': bits,
220 'actions': definition['actions'],
224 def parse_bits(self, bit_list):
225 # Convert names to numbers if applicable.
226 return frozenset(BITS.get(bit, bit) for bit in bit_list)
230 'bitwise_operand': sorted(self.bitwise_operand),
231 'actions': [action.export() for action in self.actions],
234 def check_actions(self):
236 print "Warning: Location has no actions."
237 self.insert_default_default_action()
238 if self.actions[0].required_bits:
239 self.insert_default_default_action()
241 def insert_default_default_action(self):
242 self.actions.insert(0, self.build_action({
243 'action_class': 'DoNothing',
248 def generate_bitwise_operand():
250 Generate a set of two or three bits. At least one direction and one
251 condition bit will be included. There is a low probability of choosing
252 a third bit from the complete set.
255 bits.add(choice(DIRECTION_BITS.values()))
256 bits.add(choice(CONDITION_BITS.values()))
257 # One in three chance of adding a third bit, with a further one in four
258 # chance that it will match a bit already chosen.
259 if choice(range(3)) == 0:
260 bits.add(choice(BITS.values()))
261 return frozenset(bits)