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 allow_chess_move(self, chesspiece):
134 self.player.allow_chess_move(chesspiece)
136 def change_mode(self, new_mode):
137 """Advance to the next mode"""
138 if new_mode == self.player_mode:
139 raise RuntimeError("Inconsistent state. Setting mode %s to itself"
141 elif new_mode in (ACT, EXAMINE):
142 self.player_mode = new_mode
144 raise RuntimeError("Illegal player mode %s" % self.player_mode)
146 def end_game(self, win):
147 # TODO: Find a way to not have UI stuff in game logic stuff.
148 from naja.events import SceneChangeEvent
149 from naja.scenes.lose import LoseScene
150 from naja.scenes.win import WinScene
152 SceneChangeEvent.post(WinScene)
154 SceneChangeEvent.post(LoseScene)
157 class LocationCard(object):
159 A particular set of options available on a location.
162 def __init__(self, bitwise_operand, location_actions):
163 self.bitwise_operand = bitwise_operand
164 self.actions = location_actions
168 def import_location(cls, state):
170 cls.build_action(definition) for definition in state['actions']]
171 return cls(state['bitwise_operand'], location_actions)
174 def build_action(cls, definition):
175 action_class = getattr(actions, definition['action_class'])
176 required_bits = cls.parse_bits(definition['required_bits'])
177 data = definition.get('data', {})
178 return action_class(required_bits, **data)
181 def new_location(cls, definition):
182 if 'bits' in definition:
183 bits = cls.parse_bits(definition['bits'])
185 bits = cls.generate_bitwise_operand()
186 return cls.import_location({
187 'bitwise_operand': bits,
188 'actions': definition['actions'],
192 def parse_bits(self, bit_list):
193 # Convert names to numbers if applicable.
194 return frozenset(BITS.get(bit, bit) for bit in bit_list)
198 'bitwise_operand': self.bitwise_operand,
199 'actions': [action.export() for action in self.actions],
202 def check_actions(self):
204 print "Warning: Location has no actions."
205 self.insert_default_default_action()
206 if self.actions[0].required_bits:
207 self.insert_default_default_action()
209 def insert_default_default_action(self):
210 self.actions.insert(0, self.build_action({
211 'action_class': 'DoNothing',
216 def generate_bitwise_operand():
218 Generate a set of two or three bits. At least one direction and one
219 condition bit will be included. There is a low probability of choosing
220 a third bit from the complete set.
223 bits.add(choice(DIRECTION_BITS.values()))
224 bits.add(choice(CONDITION_BITS.values()))
225 # One in three chance of adding a third bit, with a further one in four
226 # chance that it will match a bit already chosen.
227 if choice(range(3)) == 0:
228 bits.add(choice(BITS.values()))
229 return frozenset(bits)