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