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 = MOVE
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_locations(self, direction):
108 px, py = self.player.position
109 shifted_locations = {}
110 # TODO: Make this less horrible. Also test it.
111 if BITS[direction] == BITS.NORTH:
119 shifted_locations[(px, new_y)] = self.board_locations[(px, y)]
120 elif BITS[direction] == BITS.SOUTH:
128 shifted_locations[(px, new_y)] = self.board_locations[(px, y)]
129 elif BITS[direction] == BITS.EAST:
137 shifted_locations[(new_x, py)] = self.board_locations[(x, py)]
138 elif BITS[direction] == BITS.WEST:
146 shifted_locations[(new_x, py)] = self.board_locations[(x, py)]
148 self.board_locations.update(shifted_locations)
150 def change_mode(self, new_mode):
151 """Advance to the next mode"""
152 if new_mode == self.player_mode:
153 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
155 elif new_mode in (MOVE, ACT, EXAMINE):
156 self.player_mode = new_mode
158 raise RuntimeError("Illegal player mode %s" % self.player_mode)
160 def end_game(self, win):
161 # TODO: Find a way to not have UI stuff in game logic stuff.
162 from naja.events import SceneChangeEvent
163 from naja.scenes.lose import LoseScene
164 from naja.scenes.win import WinScene
166 SceneChangeEvent.post(WinScene)
168 SceneChangeEvent.post(LoseScene)
171 class LocationCard(object):
173 A particular set of options available on a location.
176 def __init__(self, bitwise_operand, location_actions):
177 self.bitwise_operand = bitwise_operand
178 self.actions = location_actions
182 def import_location(cls, state):
184 cls.build_action(definition) for definition in state['actions']]
185 return cls(state['bitwise_operand'], location_actions)
188 def build_action(cls, definition):
189 action_class = getattr(actions, definition['action_class'])
190 required_bits = definition['required_bits']
191 data = definition.get('data', {})
192 return action_class(required_bits, **data)
195 def new_location(cls, definition):
196 return cls.import_location({
197 'bitwise_operand': cls.generate_bitwise_operand(),
198 'actions': definition['actions'],
203 'bitwise_operand': self.bitwise_operand,
204 'actions': [action.export() for action in self.actions],
207 def check_actions(self):
209 print "Warning: Location has no actions."
210 self.insert_default_default_action()
211 if self.actions[0].required_bits:
212 self.insert_default_default_action()
214 def insert_default_default_action(self):
215 self.actions.insert(0, self.build_action({
216 'action_class': 'DoNothing',
221 def generate_bitwise_operand():
223 Generate a set of two or three bits. At least one direction and one
224 condition bit will be included. There is a low probability of choosing
225 a third bit from the complete set.
228 bits.add(choice(DIRECTION_BITS.values()))
229 bits.add(choice(CONDITION_BITS.values()))
230 # One in three chance of adding a third bit, with a further one in four
231 # chance that it will match a bit already chosen.
232 if choice(range(3)) == 0:
233 bits.add(choice(BITS.values()))
234 return frozenset(bits)