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 = definition['required_bits']
174 data = definition.get('data', {})
175 return action_class(required_bits, **data)
178 def new_location(cls, definition):
179 return cls.import_location({
180 'bitwise_operand': cls.generate_bitwise_operand(),
181 'actions': definition['actions'],
186 'bitwise_operand': self.bitwise_operand,
187 'actions': [action.export() for action in self.actions],
190 def check_actions(self):
192 print "Warning: Location has no actions."
193 self.insert_default_default_action()
194 if self.actions[0].required_bits:
195 self.insert_default_default_action()
197 def insert_default_default_action(self):
198 self.actions.insert(0, self.build_action({
199 'action_class': 'DoNothing',
204 def generate_bitwise_operand():
206 Generate a set of two or three bits. At least one direction and one
207 condition bit will be included. There is a low probability of choosing
208 a third bit from the complete set.
211 bits.add(choice(DIRECTION_BITS.values()))
212 bits.add(choice(CONDITION_BITS.values()))
213 # One in three chance of adding a third bit, with a further one in four
214 # chance that it will match a bit already chosen.
215 if choice(range(3)) == 0:
216 bits.add(choice(BITS.values()))
217 return frozenset(bits)