def test_is_playable_discard(self): """move.is_playable returns true so long as there's a card in that player's hand at that index.""" mock_gamestate = create_autospec(GameState) mock_gamestate.player_hands = [['card0', 'card1', 'card2', 'card3'], ['card4', 'card5', 'card6']] move = Move(move_type='discard', player_index=0, card_index=3) assert move.is_playable(mock_gamestate) is True
def test_is_playable_discard_bad_index_fails(self): """move.is_playable returns false when there's no card in that player's hand at that index.""" mock_gamestate = create_autospec(GameState) mock_gamestate.player_hands = [['card0', 'card1', 'card2'], ['card3', 'card4', 'card5', 'card6']] move = Move(move_type='discard', player_index=0, card_index=-1) assert move.is_playable(mock_gamestate) is False
def construct_all_legal_moves(game_state): moves = [] my_hand = game_state.get_my_hand() # all discards and all plays for card_index in range(len(my_hand)): moves.append(Move('discard', game_state.player_id, card_index=card_index)) moves.append(Move('play', game_state.player_id, card_index=card_index)) # all information that could be given # Ensure info tokens exist before we bother constructing info moves if game_state.board.clock_tokens > 0: for pid in range(len(game_state.player_hands)): if pid != game_state.player_id: card_colors = set([]) card_numbers = set([]) hand = game_state.player_hands[pid] for card_index in range(len(hand)): card_colors.add(hand[card_index].color) card_numbers.add(hand[card_index].number) for color in card_colors: moves.append(Move('give_information', game_state.player_id, information={'player_id': pid, 'information_type': 'color', 'information': color})) for number in card_numbers: moves.append(Move('give_information', game_state.player_id, information={'player_id': pid, 'information_type': 'number', 'information': number})) return moves
def test_apply_give_information_number_multiple_cards(self): """Give information (number) should: call make_public('number') on all cards with that number in a hand call board.use_clock_token() """ mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 1 mock_card = create_autospec(Card) mock_card.number = 2 mock_card_other_number = create_autospec(Card) mock_card_other_number.number = 4 mock_gamestate.player_hands = [[ mock_card_other_number, mock_card, mock_card_other_number ], [mock_card, mock_card_other_number, mock_card]] info_dict = { 'player_id': 1, 'information_type': 'number', 'information': 2 } move = Move(move_type='give_information', player_index=0, information=info_dict) mock_gamestate = move.apply(game_state=mock_gamestate) mock_card.make_public.assert_called_with('number') assert mock_card.make_public.call_count == 2 mock_gamestate.board.use_clock_token.assert_called_once()
def test_apply_give_information_color_once(self): """Give information (color) should: call make_public('color') on all cards with that color in a hand call board.use_clock_token() """ mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 2 mock_card = create_autospec(Card) mock_card.color = 'blue' mock_card_other_colors = create_autospec(Card) mock_card_other_colors.color = 'red' mock_gamestate.player_hands = [[ mock_card_other_colors, mock_card_other_colors, mock_card, mock_card ], [mock_card, mock_card_other_colors]] info_dict = { 'player_id': 1, 'information_type': 'color', 'information': 'blue' } move = Move(move_type='give_information', player_index=0, information=info_dict) mock_gamestate = move.apply(game_state=mock_gamestate) mock_card.make_public.assert_called_once_with('color') mock_gamestate.board.use_clock_token.assert_called_once()
def construct_move(self, game_state): move_type = None while move_type not in ('play', 'discard', 'give_information'): move_type = raw_input( "Please enter 'play', 'discard', or 'give_information':") if move_type in ('play', 'discard'): card_index = None while card_index not in [ str(i) for i in range( len(game_state.player_hands[game_state.get_my_id()])) ]: print("Your current hand: " + str(game_state.player_hands[game_state.get_my_id()])) card_index = raw_input( "Please specify the index of the card you'd like to " + move_type + ":") return Move(move_type, game_state.get_my_id(), card_index=int(card_index)) else: player_id = False information_type = False information = False while player_id not in [ str(i) for i in range(len(game_state.player_hands)) ] or player_id == str(game_state.get_my_id()): player_id = raw_input( "Which player would you like to give information?") print("Player {pid}'s hand: {hand}".format( pid=player_id, hand=str(game_state.player_hands[int(player_id)]))) while information_type not in ('number', 'color'): information_type = raw_input( "Would you like to share a number or a color?") if information_type == 'number': while information not in ['1', '2', '3', '4', '5']: information = raw_input( "Enter the number you would like to reveal.") return Move(move_type, game_state.get_my_id(), information={ 'player_id': int(player_id), 'information_type': information_type, 'information': int(information) }) else: while information not in game_state.board.deck_colors: information = raw_input("Enter a color from {c}".format( c=game_state.board.deck_colors)) return Move(move_type, game_state.get_my_id(), information={ 'player_id': int(player_id), 'information_type': information_type, 'information': information })
def test_apply_bad_move_fails(self): mock_gamestate = create_autospec(GameState) mock_gamestate.player_hands = [['card0', 'card1', 'card2'], ['card3', 'card4', 'card5', 'card6']] move = Move(move_type='discard', player_index=0, card_index=-1) with pytest.raises(AssertionError) as excinfo: move.apply(mock_gamestate) assert str( excinfo.value ) == 'Cannot apply move discard card index -1 in their hand, not playable.'
def get_valid_moves(self, board): # Prevents future calls appending to the same array self.valid_moves = [] m = self._magnitude() if (board.get_piece_at(self.x, self.y + m) == None): self._append_valid_move(board, Move(self, T(0, m, 1))) if (not self.has_moved): self._append_valid_move(board, Move(self, T(0, m, 2))) if (board.get_piece_at(self.x + 1, self.y + m) != None): self._append_valid_move(board, Move(self, T(1, m, 1))) if (board.get_piece_at(self.x - 1, self.y + m) != None): self._append_valid_move(board, Move(self, T(-1, m, 1))) return self.valid_moves
def test_apply_discard(self): """Discard should remove a card from the player's hand, add it to the discard pile and add back a clock token.""" mock_gamestate = create_autospec(GameState) mock_card = create_autospec(Card) mock_card_for_discard = create_autospec(Card) mock_gamestate.player_hands = [[mock_card], [mock_card, mock_card_for_discard]] mock_gamestate.board = create_autospec(Board) move = Move(move_type='discard', player_index=1, card_index=1) mock_gamestate = move.apply(game_state=mock_gamestate) assert len(mock_gamestate.player_hands[1]) == 1 mock_gamestate.board.discard_card.assert_called_with( mock_card_for_discard) mock_gamestate.board.add_clock_token.assert_called_once()
def test_is_playable_information_give_self_information_fails(self): mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 1 mock_card = create_autospec(Card) mock_card.number = 2 mock_gamestate.player_hands = [[mock_card, mock_card, mock_card], [mock_card, mock_card, mock_card]] info_dict = { 'player_id': 0, 'information_type': 'number', 'information': 2 } move = Move(move_type='give_information', player_index=0, information=info_dict) assert move.is_playable(mock_gamestate) is False
def test_is_playable_information_bad_information_type_fails(self): mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 1 mock_card = create_autospec(Card) mock_card.number = 2 mock_gamestate.player_hands = [[ 'is_playable should', 'not look at players cards' ], [mock_card, mock_card, mock_card]] info_dict = { 'player_id': 1, 'information_type': 'board talk is for cheaters', 'information': 2 } move = Move(move_type='give_information', player_index=0, information=info_dict) assert move.is_playable(mock_gamestate) is False
def test_is_playable_information_no_clock_tokens(self): mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_card = create_autospec(Card) mock_card.number = 2 mock_gamestate.player_hands = [[ 'is_playable should', 'not look at players cards' ], [mock_card, mock_card, mock_card]] mock_gamestate.board.clock_tokens = 0 info_dict = { 'player_id': 0, 'information_type': 'number', 'information': 2 } move = Move(move_type='give_information', player_index=1, information=info_dict) assert move.is_playable(mock_gamestate) is False
def test_apply_play_blow_fuse(self): """Play (blow fuse) should discard the card marked for play and call board.use_fuse_token""" mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_card = create_autospec(Card) mock_card_for_play = create_autospec(Card) mock_card_for_play.color = 'red' mock_gamestate.player_hands = [[mock_card], [mock_card_for_play, mock_card]] mock_stack = create_autospec(CardStack) mock_stack.is_legal_play.return_value = False mock_gamestate.board.get_card_stack.return_value = mock_stack move = Move(move_type='play', player_index=1, card_index=0) mock_gamestate = move.apply(game_state=mock_gamestate) mock_gamestate.board.get_card_stack.assert_called_with('red') assert len(mock_gamestate.player_hands[1]) == 1 mock_gamestate.board.use_fuse_token.assert_called_once() mock_gamestate.board.discard_card.assert_called_with( mock_card_for_play)
def test_is_playable_information_number_subset_of_cards(self): mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 1 mock_card = create_autospec(Card) mock_card.number = 2 mock_card_not_two = create_autospec(Card) mock_card_not_two.number = 4 mock_gamestate.player_hands = [[ 'is_playable should', 'not look at players cards' ], [mock_card_not_two, mock_card, mock_card_not_two, mock_card]] info_dict = { 'player_id': 1, 'information_type': 'number', 'information': 2 } move = Move(move_type='give_information', player_index=0, information=info_dict) assert move.is_playable(mock_gamestate)
def test_hash(self): a = Move() a.positions = ['A', 'B', 'C'] b = Move() b.positions = ['A', 'B', 'C'] self.assert_(hash(a) == hash(b)) b.horizontal = False self.assert_(not hash(a) == hash(b)) b.positions = ['A', 'B', 'D'] self.assert_(not hash(a) == hash(b)) b.horizontal = True self.assert_(not hash(a) == hash(b)) b.positions = ['B', 'C', 'A'] self.assert_(hash(a) == hash(b))
def test_is_playable_information_color(self): mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_gamestate.board.clock_tokens = 1 mock_card = create_autospec(Card) mock_card.color = 'blue' mock_gamestate.player_hands = [[mock_card, mock_card], [ 'is_playable should', 'not look at players cards' ]] info_dict = { 'player_id': 0, 'information_type': 'color', 'information': 'blue' } move = Move(move_type='give_information', player_index=1, information=info_dict) assert move.is_playable(mock_gamestate)
def test_equality(self): a = Move() a.positions = ['A', (1, 2, 3)] b = Move() b.positions = ['A', (1, 2, 3)] self.assert_(a == b) b.positions = [(1, 2, 3), 'A'] self.assert_(a == b) b.horizontal = False self.assert_(not a == b) b.positions = ['A', (1, 2)] self.assert_(not a == b) b.horizontal = True self.assert_(not a == b)
def test_apply_play_successful_play_complete_stack(self): """Play (successful) should remove the card from hand, add it to the stack of the right color, call board.add_clock_token """ mock_gamestate = create_autospec(GameState) mock_gamestate.board = create_autospec(Board) mock_card = create_autospec(Card) mock_card_for_play = create_autospec(Card) mock_card_for_play.color = 'green' mock_gamestate.player_hands = [[mock_card], [mock_card_for_play, mock_card]] mock_stack = create_autospec(CardStack) mock_stack.is_legal_play.return_value = True mock_stack.is_complete.return_value = True mock_gamestate.board.get_card_stack.return_value = mock_stack move = Move(move_type='play', player_index=1, card_index=0) mock_gamestate = move.apply(game_state=mock_gamestate) mock_gamestate.board.get_card_stack.assert_called_with('green') mock_stack.play.assert_called_with(mock_card_for_play) assert len(mock_gamestate.player_hands[1]) == 1 mock_gamestate.board.add_clock_token.assert_called_once()
def get_valid_moves(self, board): # Prevents future calls appending to the same array self.valid_moves = [] transforms = [ T(1, 2, 1), T(2, 1, 1), T(2, -1, 1), T(1, -2, 1), T(-1, -2, 1), T(-2, -1, 1), T(-2, 1, 1), T(-1, 2, 1) ] for t in transforms: self._append_valid_move(board, Move(self, t)) return self.valid_moves
def test_add_letter(self): m = Move() m.add_letter('a', (0, 0)) self.assertEqual(m.positions[0].letter, 'a') self.assertEqual(m.positions[0].pos, (0, 0)) m.add_letter('b', (1, 1)) self.assertEqual(m.positions[1].letter, 'b') self.assertEqual(m.positions[1].pos, (1, 1))
def test_deepcopy(self): m = Move() m.add_letter('A', (0, 1)) m.add_letter('B', (0, 0)) m.drawn = ['A', 'B', 'C'] exp_pos = deepcopy(m.positions) exp_drawn = deepcopy(m.drawn) m2 = deepcopy(m) m.drawn[2] = 'D' m.positions[0] = ('D', (7, 7)) self.assertEqual(exp_pos, m2.positions) self.assertEqual(exp_drawn, m2.drawn)
def parse_cross_set(filename): with open(filename, 'r') as f: lines = map(lambda s: s.rstrip(), f.readlines()) lines = filter(lambda s: len(s) > 0 and not s.startswith('!:'), lines) i = 0 while i < len(lines): line = lines[i] board = [] if line != 'BEGIN BOARD': raise IOError('Improperly formatted scenario file, line %d: expected BEGIN BOARD.' % i) i += 1 line = lines[i] while line != 'END BOARD': board.append(list(line)) i += 1 line = lines[i] if len(board) == 0: board = deepcopy(default_board) i += 1 candidate_letters = lines[i] i += 1 r_match = re.match(r'^(\d+), (\d+)$', lines[i]) if not r_match: raise IOError('Improperly formatted scenario file, line %d: candidate position.' % i) candidate_pos = tuple(map(int, r_match.groups())) i += 1 if lines[i] == 'H': candidate_dir = True elif lines[i] == 'V': candidate_dir = False else: raise IOError('Improperly formatted scenario file, line %d: candidate direction.' % i) i += 1 # Increment before beginning of next iteration line = lines[i] if line != 'BEGIN CROSSES': raise IOError('Improperly formatted scenario file, line %d: expected BEGIN CROSSES.' % i) i += 1 line = lines[i] crosses = [] while line != 'END CROSSES': cross = {} r_match = re.match(r'^(\d+), (\d+)$', line) if not r_match: raise IOError('Improperly formatted scenario file, line %d: cross position.' % i) cross['x'], cross['y'] = tuple(map(int, r_match.groups())) i += 1 line = lines[i] if line[0] == '-': cross['horizontal'] = True elif line[0] == '|': cross['horizontal'] = False else: raise IOError('Improperly formatted scenario file, line %d: cross direction.' % i) i += 1 line = lines[i] if line == '/': cross['letters'] = set() else: cross['letters'] = set(line) crosses.append(cross) i += 1 line = lines[i] candidate = Move() offset = 0 for j, letter in enumerate(candidate_letters): if candidate_dir: x, y = candidate_pos[0], candidate_pos[1] + j + offset while y < len(board[x]) and board[x][y] not in empty_locations: offset += 1 y += 1 candidate.positions.append(BoardPosition(letter, (x, y))) else: x, y = candidate_pos[0] + j + offset, candidate_pos[1] while x < len(board) and board[x][y] not in empty_locations: offset += 1 x += 1 candidate.positions.append(BoardPosition(letter, (x, y))) candidate.horizontal = candidate_dir yield {'board': board, 'candidate': candidate, 'crosses': crosses} i += 1
def test_sort_letters(self): m = Move() m.add_letter('a', (0, 1)) m.add_letter('b', (0, 0)) m.sort_letters() self.assertEqual('b', m.positions[0].letter) self.assertEqual((0, 0), m.positions[0].pos) self.assertEqual('a', m.positions[1].letter) self.assertEqual((0, 1), m.positions[1].pos) m = Move() m.horizontal = False m.add_letter('a', (1, 0)) m.add_letter('b', (0, 0)) m.sort_letters() self.assertEqual('b', m.positions[0].letter) self.assertEqual((0, 0), m.positions[0].pos) self.assertEqual('a', m.positions[1].letter) self.assertEqual((1, 0), m.positions[1].pos)
def test_constructor(self): m = Move() self.assertEqual(m.score, 0) self.assert_(not m.positions) self.assert_(not m.drawn)
def parse_scenario(filename): with open(filename, 'r') as f: lines = map(lambda s: s.rstrip(), f.readlines()) lines = filter(lambda s: len(s) > 0 and not s.startswith('!:'), lines) i = 0 while i < len(lines): line = lines[i] board = [] if line != 'BEGIN BOARD': raise IOError( 'Improperly formatted scenario file, line %d: expected BEGIN BOARD.' % i) i += 1 line = lines[i] while line != 'END BOARD': board.append(list(line)) i += 1 line = lines[i] if len(board) == 0: board = deepcopy(default_board) i += 1 candidate_letters = lines[i] i += 1 r_match = re.match(r'^(\d+), (\d+)$', lines[i]) if not r_match: raise IOError( 'Improperly formatted scenario file, line %d: candidate position.' % i) candidate_pos = tuple(map(int, r_match.groups())) i += 1 if lines[i] == 'H': candidate_dir = True elif lines[i] == 'V': candidate_dir = False else: raise IOError( 'Improperly formatted scenario file, line %d: candidate direction.' % i) i += 1 j = int(lines[i]) if j == 1: exp_result = True elif j == 0: exp_result = False else: raise IOError( 'Improperly formatted scenario file, line %d: expected result.' % i) exp_score = -1 if exp_result: i += 1 exp_score = int(lines[i]) i += 1 # Increment before beginning of next iteration candidate = Move() offset = 0 for j, letter in enumerate(candidate_letters): if candidate_dir: x, y = candidate_pos[0], candidate_pos[1] + j + offset while y < len(board[x]) and board[x][y] not in empty_locations: offset += 1 y += 1 candidate.positions.append(BoardPosition(letter, (x, y))) else: x, y = candidate_pos[0] + j + offset, candidate_pos[1] while x < len(board) and board[x][y] not in empty_locations: offset += 1 x += 1 candidate.positions.append(BoardPosition(letter, (x, y))) candidate.horizontal = candidate_dir yield { 'board': board, 'candidate': candidate, 'result': exp_result, 'score': exp_score }
def _moves_in_direction(self, board, direction): moves = [] for i in range(8): moves.append(Move(self, T(direction[0], direction[1], i + 1))) return moves
def test_create_bad_move_type(self): with pytest.raises(ValueError) as exinfo: Move(move_type='cheat', player_index=0) assert str( exinfo.value ) == 'Moves must either play, discard, or share information.'
def test_create_bad_move_info(self): with pytest.raises(AssertionError): info_dict = {'has_no_useful_keys': 'or values!'} Move(move_type='give_information', player_index=0, information=info_dict)
def parse_scenario(filename): with open(filename, 'r') as f: lines = map(lambda s: s.rstrip(), f.readlines()) lines = filter(lambda s: len(s) > 0 and not s.startswith('!:'), lines) i = 0 while i < len(lines): line = lines[i] board = [] if line != 'BEGIN BOARD': raise IOError('Improperly formatted scenario file, line %d: expected BEGIN BOARD.' % i) i += 1 line = lines[i] while line != 'END BOARD': board.append(list(line)) i += 1 line = lines[i] if len(board) == 0: board = deepcopy(default_board) i += 1 candidate_letters = lines[i] i += 1 r_match = re.match(r'^(\d+), (\d+)$', lines[i]) if not r_match: raise IOError('Improperly formatted scenario file, line %d: candidate position.' % i) candidate_pos = tuple(map(int, r_match.groups())) i += 1 if lines[i] == 'H': candidate_dir = True elif lines[i] == 'V': candidate_dir = False else: raise IOError('Improperly formatted scenario file, line %d: candidate direction.' % i) i += 1 j = int(lines[i]) if j == 1: exp_result = True elif j == 0: exp_result = False else: raise IOError('Improperly formatted scenario file, line %d: expected result.' % i) exp_score = -1 if exp_result: i += 1 exp_score = int(lines[i]) i += 1 # Increment before beginning of next iteration candidate = Move() offset = 0 for j, letter in enumerate(candidate_letters): if candidate_dir: x, y = candidate_pos[0], candidate_pos[1] + j + offset while y < len(board[x]) and board[x][y] not in empty_locations: offset += 1 y += 1 candidate.positions.append(BoardPosition(letter, (x, y))) else: x, y = candidate_pos[0] + j + offset, candidate_pos[1] while x < len(board) and board[x][y] not in empty_locations: offset += 1 x += 1 candidate.positions.append(BoardPosition(letter, (x, y))) candidate.horizontal = candidate_dir yield {'board': board, 'candidate': candidate, 'result': exp_result, 'score': exp_score}