Remove player_mode from game state.
[naja.git] / naja / tests / test_gameboard.py
1 from unittest import TestCase
2
3 from naja.constants import BITS, MOVES
4 from naja.gameboard import GameBoard, LocationCard
5 from naja.options import parse_args
6 from naja import actions
7
8
9 class TestGameBoard(TestCase):
10     def setUp(self):
11         parse_args([])
12
13     def make_deck(self, cards=None):
14         if cards is None:
15             cards = [{'card_name': 'card', 'actions': []}]
16         return {'cards': cards}
17
18     def assert_state(self, state1, state2, exclude=(), player_exclude=()):
19         def filter_dict(source, exclude_keys):
20             return dict((k, v) for k, v in source.items()
21                         if k not in exclude_keys)
22
23         state1 = filter_dict(state1, exclude)
24         if 'player' in state1:
25             state1['player'] = filter_dict(state1['player'], player_exclude)
26         state2 = filter_dict(state2, exclude)
27         if 'player' in state2:
28             state2['player'] = filter_dict(state2['player'], player_exclude)
29
30         self.assertEqual(state1, state2)
31
32     def test_export_new_board(self):
33         board = GameBoard.new_game({'cards': [
34             {'card_name': 'card1', 'actions': [
35                 {
36                     'action_class': 'LoseHealthOrMSB',
37                     'required_bits': [],
38                 }, {
39                     'action_class': 'GainHealth',
40                     'required_bits': [BITS.RED],
41                 },
42             ]}]})
43         exported_state = board.export()
44         board_locations = exported_state.pop('board_locations')
45         self.assertEqual(exported_state, {
46             'clock_count': 0,
47             'max_health': 4,
48             'health': 4,
49             'wins_required': 4,
50             'wins': 0,
51             'locations': [{'card_name': 'card1', 'actions': [
52                 {
53                     'action_class': 'LoseHealthOrMSB',
54                     'required_bits': [],
55                 }, {
56                     'action_class': 'GainHealth',
57                     'required_bits': [BITS.RED],
58                 },
59             ]}],
60             'player': board.player.export(),
61             'puzzle': False,
62             'replacement_params': None,
63         })
64         positions = []
65         for position, location_state in board_locations:
66             positions.append(position)
67             self.assertEqual(sorted(location_state.keys()), [
68                 'actions', 'bitwise_operand', 'card_name', 'max_number',
69                 'replacement_time'])
70             self.assertEqual(location_state['actions'], [
71                 {
72                     'action_class': 'LoseHealthOrMSB',
73                     'required_bits': [],
74                     'data': {},
75                 }, {
76                     'action_class': 'GainHealth',
77                     'required_bits': [BITS.RED],
78                     'data': {},
79                 },
80             ])
81             self.assertTrue(2 <= len(location_state['bitwise_operand']) <= 3)
82         self.assertEqual(
83             positions, sorted((x, y) for x in range(5) for y in range(5)))
84
85     def test_max_number(self):
86         def _check_counts(board):
87             counts = {}
88             exported_state = board.export()
89             board_locations = exported_state.pop('board_locations')
90             for _position, location_state in board_locations:
91                 counts.setdefault(
92                     location_state['actions'][0]['action_class'], 0)
93                 counts[location_state['actions'][0]['action_class']] += 1
94             self.assertTrue(counts.get('LoseHealthOrMSB', 0) <= 5)
95
96         board = GameBoard.new_game({'cards': [
97             {'card_name': 'card1', 'actions': [{
98                 'action_class': 'LoseHealthOrMSB',
99                 'required_bits': [], }],
100              'max_number': 5},
101             {'card_name': 'card2', 'actions': [{
102                 'action_class': 'DoNothing',
103                 'required_bits': [], }],
104              'max_number': 25}]})
105         # check creation constraints
106         _check_counts(board)
107         # Check replacement
108         # Replace center card 12 times and assert invariant still holds
109         for x in range(12):
110             board.replace_card((2, 2))
111         _check_counts(board)
112         # replace a diagonal of cards
113         for x in range(5):
114             board.replace_card((x, x))
115         _check_counts(board)
116
117     def test_max_number_complex(self):
118         def _check_counts(board):
119             counts = {}
120             exported_state = board.export()
121             board_locations = exported_state.pop('board_locations')
122             for _position, location_state in board_locations:
123                 counts.setdefault(
124                     location_state['actions'][0]['action_class'], 0)
125                 counts[location_state['actions'][0]['action_class']] += 1
126             self.assertTrue(counts.get('LoseHealthOrMSB', 0) <= 5)
127             self.assertTrue(counts.get('DoNothing', 0) <= 3)
128             self.assertTrue(counts.get('AcquireWinToken', 0) <= 4)
129             self.assertTrue(counts.get('GainHealth', 0) <= 3)
130
131         board = GameBoard.new_game({'cards': [
132             {'card_name': 'card1', 'actions': [{
133                 'action_class': 'LoseHealthOrMSB',
134                 'required_bits': [], }],
135              'max_number': 5},
136             {'card_name': 'card2', 'actions': [{
137                 'action_class': 'AcquireWinToken',
138                 'required_bits': [], }],
139              'max_number': 4},
140             {'card_name': 'card3', 'actions': [{
141                 'action_class': 'GainHealth',
142                 'required_bits': [], }],
143              'max_number': 3},
144             {'card_name': 'card4', 'actions': [{
145                 'action_class': 'RotateLocations',
146                 'required_bits': [], }],
147              'max_number': 25},
148             {'card_name': 'card5', 'actions': [{
149                 'action_class': 'AllowChessMove',
150                 'required_bits': [], }],
151              'max_number': 25},
152             {'card_name': 'card6', 'actions': [{
153                 'action_class': 'DoNothing',
154                 'required_bits': [], }],
155              'max_number': 3}]})
156         # check creation constraints
157         _check_counts(board)
158         # Check replacement
159         # Replace center card 12 times and assert invariant still holds
160         for x in range(12):
161             board.replace_card((2, 2))
162         _check_counts(board)
163         # replace a diagonal of cards
164         for x in range(5):
165             board.replace_card((x, x))
166         _check_counts(board)
167
168     def test_lose_health(self):
169         board = GameBoard.new_game(self.make_deck())
170         self.assertEqual(board.health, 4)
171         state_1 = board.export()
172
173         board.lose_health()
174         self.assertEqual(board.health, 3)
175         state_2 = board.export()
176
177         self.assert_state(state_1, state_2, exclude=['health'])
178
179     def test_gain_health(self):
180         board = GameBoard.new_game(self.make_deck())
181         board.health = 2
182         self.assertEqual(board.health, 2)
183         state_1 = board.export()
184
185         board.gain_health()
186         self.assertEqual(board.health, 3)
187         state_2 = board.export()
188
189         self.assert_state(state_1, state_2, exclude=['health'])
190
191     def test_gain_health_at_max(self):
192         board = GameBoard.new_game(self.make_deck())
193         self.assertEqual(board.health, 4)
194         state_1 = board.export()
195
196         board.gain_health()
197         self.assertEqual(board.health, 4)
198         state_2 = board.export()
199
200         self.assert_state(state_1, state_2)
201
202     def generate_locations(self, override_dict=None):
203         locations_dict = dict(((x, y), '%s%s' % (x, y))
204                               for x in range(5) for y in range(5))
205         if override_dict:
206             locations_dict.update(override_dict)
207         return locations_dict
208
209     def test_shift_locations_north(self):
210         board = GameBoard.new_game(self.make_deck())
211         board.board_locations = self.generate_locations()
212         board.shift_locations('NORTH')
213         self.assertEqual(board.board_locations, self.generate_locations({
214             (2, 0): '21', (2, 1): '23', (2, 3): '24', (2, 4): '20',
215         }))
216
217     def test_shift_locations_south(self):
218         board = GameBoard.new_game(self.make_deck())
219         board.board_locations = self.generate_locations()
220         board.shift_locations('SOUTH')
221         self.assertEqual(board.board_locations, self.generate_locations({
222             (2, 0): '24', (2, 1): '20', (2, 3): '21', (2, 4): '23',
223         }))
224
225     def test_shift_locations_east(self):
226         board = GameBoard.new_game(self.make_deck())
227         board.board_locations = self.generate_locations()
228         board.shift_locations('EAST')
229         self.assertEqual(board.board_locations, self.generate_locations({
230             (0, 2): '42', (1, 2): '02', (3, 2): '12', (4, 2): '32',
231         }))
232
233     def test_shift_locations_west(self):
234         board = GameBoard.new_game(self.make_deck())
235         board.board_locations = self.generate_locations()
236         board.shift_locations('WEST')
237         self.assertEqual(board.board_locations, self.generate_locations({
238             (0, 2): '12', (1, 2): '32', (3, 2): '42', (4, 2): '02',
239         }))
240
241     def test_rotate_locations_anticlockwise(self):
242         board = GameBoard.new_game(self.make_deck())
243         board.board_locations = self.generate_locations()
244         board.rotate_locations('ANTICLOCKWISE')
245         self.assertEqual(board.board_locations, self.generate_locations({
246             (1, 1): '21', (2, 1): '31', (3, 1): '32',
247             (1, 2): '11',               (3, 2): '33',
248             (1, 3): '12', (2, 3): '13', (3, 3): '23',
249         }))
250
251     def test_rotate_locations_anticlockwise_top(self):
252         board = GameBoard.new_game(self.make_deck(), initial_pos=(2, 0))
253         board.board_locations = self.generate_locations()
254         board.rotate_locations('ANTICLOCKWISE')
255         self.assertEqual(board.board_locations, self.generate_locations({
256             (1, 0): '30',               (3, 0): '31',
257             (1, 1): '10', (2, 1): '11', (3, 1): '21',
258         }))
259
260     def test_rotate_locations_anticlockwise_right(self):
261         board = GameBoard.new_game(self.make_deck(), initial_pos=(0, 2))
262         board.board_locations = self.generate_locations()
263         board.rotate_locations('ANTICLOCKWISE')
264         self.assertEqual(board.board_locations, self.generate_locations({
265             (0, 1): '11', (1, 1): '12',
266                           (1, 2): '13',
267             (0, 3): '01', (1, 3): '03',
268         }))
269
270     def test_rotate_locations_anticlockwise_corner(self):
271         board = GameBoard.new_game(self.make_deck(), initial_pos=(0, 4))
272         board.board_locations = self.generate_locations()
273         board.rotate_locations('ANTICLOCKWISE')
274         self.assertEqual(board.board_locations, self.generate_locations({
275             (0, 3): '13', (1, 3): '14',
276                           (1, 4): '03',
277         }))
278
279     def test_rotate_locations_clockwise(self):
280         board = GameBoard.new_game(self.make_deck())
281         board.board_locations = self.generate_locations()
282         board.rotate_locations('CLOCKWISE')
283         self.assertEqual(board.board_locations, self.generate_locations({
284             (1, 1): '12', (2, 1): '11', (3, 1): '21',
285             (1, 2): '13',               (3, 2): '31',
286             (1, 3): '23', (2, 3): '33', (3, 3): '32',
287         }))
288
289     def test_rotate_locations_clockwise_1_3(self):
290         board = GameBoard.new_game(self.make_deck(), initial_pos=(1, 3))
291         board.board_locations = self.generate_locations()
292         board.rotate_locations('CLOCKWISE')
293         self.assertEqual(board.board_locations, self.generate_locations({
294             (0, 2): '03', (1, 2): '02', (2, 2): '12',
295             (0, 3): '04',               (2, 3): '22',
296             (0, 4): '14', (1, 4): '24', (2, 4): '23',
297         }))
298
299     def test_allow_chess_move_knight(self):
300         board = GameBoard.new_game(self.make_deck())
301         board.allow_chess_move(MOVES.KNIGHT)
302         self.assertEqual(board.player.movement_mode, MOVES.KNIGHT)
303
304     def test_allow_chess_move_bishop(self):
305         board = GameBoard.new_game(self.make_deck())
306         board.allow_chess_move(MOVES.BISHOP)
307         self.assertEqual(board.player.movement_mode, MOVES.BISHOP)
308
309     def test_allow_chess_move_castle(self):
310         board = GameBoard.new_game(self.make_deck())
311         board.allow_chess_move(MOVES.CASTLE)
312         self.assertEqual(board.player.movement_mode, MOVES.CASTLE)
313
314
315 class TestLocationCard(TestCase):
316     def test_generate_bitwise_operand(self):
317         # This is testing a random process, so it may fail occasionally.
318         operand_sets = []
319         for _ in range(100):
320             operand_sets.append(LocationCard.generate_bitwise_operand())
321         sizes = {2: 0, 3: 0}
322         bits = set()
323         for operand_set in operand_sets:
324             sizes[len(operand_set)] += 1
325             bits.update(operand_set)
326             # TODO: Test that there's at least one condition and one direction.
327         self.assertTrue(sizes[2] > 0)
328         self.assertTrue(sizes[3] > 0)
329         self.assertTrue(sizes[2] > sizes[3])
330         self.assertEqual(bits, set(BITS.values()))
331
332     def test_new_location_no_actions(self):
333         location = LocationCard.new_location(
334             {'card_name': 'card', 'actions': []}, None)
335         [action] = location.actions
336         self.assertEqual(type(action), actions.DoNothing)
337         self.assertEqual(action.required_bits, set())
338         self.assertEqual(location.replacement_time, None)
339
340     def test_new_location_replacement_params(self):
341         location = LocationCard.new_location(
342             {'card_name': 'card', 'actions': []},
343             {'chance': 1, 'min': 2, 'max': 2})
344         [action] = location.actions
345         self.assertEqual(type(action), actions.DoNothing)
346         self.assertEqual(action.required_bits, set())
347         self.assertEqual(location.replacement_time, 2)
348
349     def test_new_location_one_action(self):
350         location = LocationCard.new_location({
351             'card_name': 'card1',
352             'actions': [
353                 {'required_bits': [], 'action_class': 'DoNothing'},
354             ]}, None)
355         [action] = location.actions
356         self.assertEqual(type(action), actions.DoNothing)
357         self.assertEqual(action.required_bits, set())
358         self.assertEqual(location.replacement_time, None)