def test_computer_player_random_algorith_when_piece_is_pushed_out(post_move, post_shift, time_sleep): """ Tests case where piece is positioned on a shift location, so that it is pushed out. Runs computation 100 times. Push-out expectation rate is 1/12. Probability that no push-out takes place in 100 runs is negligible .start() is patched so that the compute method runs sequentially. This test recreates a bug, where the pushed-out piece is not updated correctly, leading to exceptions thrown when computer makes a move. """ card_factory = MazeCardFactory() board = Board(create_maze(MAZE_STRING, card_factory), leftover_card=card_factory.create_instance("NE", 0)) piece = board.create_piece() piece.maze_card = board.maze[BoardLocation(3, 6)] game = Mock() game.get_enabled_shift_locations.return_value = board.shift_locations game.board = board computer_player = ComputerPlayer(library_binding_factory=Mock(), move_url="move-url", shift_url="shift-url", game=game, identifier=9, board=board, piece=piece) for _ in range(100): shift_action, move_location = computer_player.random_actions() shift_location, _ = shift_action allowed_coordinates = [(3, 6)] if shift_location == BoardLocation(3, 6): allowed_coordinates = [(3, 5)] elif shift_location == BoardLocation(3, 0): allowed_coordinates = [(3, 0)] allowed_moves = {BoardLocation(*coordinates) for coordinates in allowed_coordinates} assert move_location in allowed_moves
def test_opposing_border_location_for_south_location(): """ Tests opposing_border_location """ board = Board() size = board.maze.maze_size assert board.opposing_border_location(BoardLocation(size - 1, 3)) == BoardLocation( 0, 3)
def test_opposing_border_location_for_west_location(): """ Tests opposing_border_location """ board = Board() size = board.maze.maze_size assert board.opposing_border_location(BoardLocation(1, 0)) == BoardLocation( 1, size - 1)
def dto_to_game(game_dto): """ maps a DTO to a game to deserialize a persisted instance. :param game_dto: a dictionary representing the structure of the game, created by game_to_dto :return: a Game instance whose state is equal to the DTO """ maze, leftover_card, maze_card_by_id = _dto_to_maze_cards_and_dictionary( game_dto[MAZE]) objective_maze_card = maze_card_by_id[game_dto[OBJECTIVE]] board = Board(maze, leftover_card, objective_maze_card=objective_maze_card) players = [ _dto_to_player(player_dto, board, maze_card_by_id) for player_dto in game_dto[PLAYERS] ] board._pieces = [player.piece for player in players] turns_prepare_delay = _dto_to_timedelta(game_dto[TURN_PREPARE_DELAY]) turns = _dto_to_turns(game_dto[NEXT_ACTION], players=players, prepare_delay=turns_prepare_delay) identifier = game_dto[ID] game = Game(identifier, board=board, players=players, turns=turns) for player in players: player.set_game(game) game.previous_shift_location = _dto_to_board_location( game_dto[PREVIOUS_SHIFT_LOCATION]) return game
def test_shift_updates_pieces_on_pushed_out_card(): """ Tests shift """ board = Board() piece = board.create_piece() pushed_card = board.leftover_card piece.maze_card = board.maze[BoardLocation(0, 3)] board.shift(BoardLocation(board.maze.maze_size - 1, 3), 90) assert piece.maze_card == pushed_card
def test_move_raises_error_on_unreachable_location(): """ Tests move validation """ maze_card_factory = MazeCardFactory() board = Board(maze=create_maze(MAZE_STRING, maze_card_factory), leftover_card=maze_card_factory.create_random_maze_card()) piece = board.create_piece() piece.maze_card = board.maze[BoardLocation(0, 1)] with pytest.raises(MoveUnreachableException): board.move(piece, BoardLocation(0, 0))
def test_move_new_objective_locations_after_reaching_location(): """ Tests new objective generation after reaching one """ maze_card_factory = MazeCardFactory() maze = create_maze(MAZE_STRING, maze_card_factory) objective_maze_card = maze[BoardLocation(1, 6)] board = Board(maze=maze, leftover_card=maze_card_factory.create_random_maze_card(), objective_maze_card=objective_maze_card) piece = board.create_piece() piece.maze_card = maze[BoardLocation(0, 6)] board.move(piece, BoardLocation(1, 6)) _assert_all_piece_and_objective_location_different(board)
def test_move_updates_players_maze_card_correctly(): """ Tests move Instead of calling init_board(), the board is built manually, and the player's position is set manually as well, so that randomness is eliminated for testing """ maze_card_factory = MazeCardFactory() board = Board(maze=create_maze(MAZE_STRING, maze_card_factory), leftover_card=maze_card_factory.create_random_maze_card()) piece = board.create_piece() piece.maze_card = board.maze[BoardLocation(0, 1)] board.move(piece, BoardLocation(0, 2)) assert board.maze[BoardLocation(0, 2)] == piece.maze_card
def test_create_piece_sets_all_pieces_on_corners(): """ Tests create_piece """ board = Board(create_random_maze()) def is_corner(location): return (location.row in [ 0, board.maze.maze_size - 1 ]) and (location.column in [0, board.maze.maze_size - 1]) for _ in range(8): piece = board.create_piece() piece_location = board.maze.maze_card_location(piece.maze_card) assert is_corner(piece_location)
def test_random_actions_computes_valid_actions(): """ Runs computation 100 times and expects that it returns valid actions in each run """ card_factory = MazeCardFactory() orig_board = Board(create_maze(MAZE_STRING, card_factory), leftover_card=card_factory.create_instance("NE", 0)) for _ in range(100): board = copy.deepcopy(orig_board) maze = board.maze piece = board.create_piece() piece.maze_card = maze[BoardLocation(0, 0)] game = Mock() game.get_enabled_shift_locations.return_value = board.shift_locations game.board = board computer_player = ComputerPlayer(library_binding_factory=Mock(), move_url="move-url", shift_url="shift-url", game=game, identifier=9, board=board, piece=piece) shift_action, move_location = computer_player.random_actions() shift_location, shift_rotation = shift_action assert shift_rotation in [0, 90, 180, 270] assert shift_location in board.shift_locations allowed_coordinates = [(0, 0)] if shift_location == BoardLocation(0, 1) and shift_rotation == 270: allowed_coordinates += [(0, 1)] elif shift_location == BoardLocation(0, 1) and shift_rotation == 180: allowed_coordinates += [(0, 1), (1, 1)] elif shift_location == BoardLocation(1, 0) and shift_rotation == 270: allowed_coordinates += [(1, 0)] elif shift_location == BoardLocation(1, 0) and shift_rotation == 0: allowed_coordinates += [(1, 0), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)] elif shift_location == BoardLocation(6, 1): allowed_coordinates += [(0, 1), (0, 2), (1, 1), (2, 1)] allowed_moves = { BoardLocation(*coordinates) for coordinates in allowed_coordinates } assert move_location in allowed_moves
def create_board_and_pieces(maze, leftover_card, piece_locations, objective_location=None, objective_maze_card=None): maze = copy.deepcopy(maze) if not objective_maze_card: objective_maze_card = maze[objective_location] board = Board(maze=maze, leftover_card=leftover_card, objective_maze_card=objective_maze_card) board.clear_pieces() for index, location in enumerate(piece_locations): piece = Piece(index, board.maze[location]) board.pieces.append(piece) return board
def test_get_enabled_shift_locations_with_previous_shift(): """ Tests get_enabled_shift_locations where the previous shift is (3, 0) """ board = Board() game = Game(identifier=0, board=board) game.previous_shift_location = BoardLocation(3, 0) expected_disabled = BoardLocation(3, board.maze.maze_size - 1) enabled_shift_locations = game.get_enabled_shift_locations() assert expected_disabled not in enabled_shift_locations
def test_create_piece_assigns_pieces_consecutive_unique_indices(): """ Tests create_piece. Adds four pieces, removes first two, adds one, removes third, adds two, and checks that all pieces have consecutive unique index """ board = Board(create_random_maze()) pieces = [ board.create_piece(), board.create_piece(), board.create_piece(), board.create_piece() ] board.remove_piece(pieces[0]) board.remove_piece(pieces[1]) pieces[0] = board.create_piece() board.remove_piece(pieces[3]) pieces[1] = board.create_piece() pieces[3] = board.create_piece() assert set([0, 1, 2, 3]) == set(map(lambda piece: piece.piece_index, pieces))
def test_add_player_start_game_calls_methods_on_turns(): """ Tests add_player, start_game and Player """ board = Board() turns = Mock() game = Game(identifier=0, board=board, turns=turns) for _ in range(4): player_id = game.unused_player_id() game.add_player(Player(player_id)) game.start_game() expected_turn_calls = [call.init(game.players)] + [call.start()] assert turns.mock_calls[-2:] == expected_turn_calls
def test_computer_player_calls_start_on_compute_method(post_move, post_shift, time_sleep): """ Tests that the computer player calls start() one its computation method. """ card_factory = MazeCardFactory() board = Board(create_maze(MAZE_STRING, card_factory), leftover_card=card_factory.create_instance("NE", 0)) piece = board.create_piece() game = Mock() type(game).identifier = PropertyMock(return_value=7) game.get_enabled_shift_locations.return_value = board.shift_locations mock_method = Mock() mock_method.start = Mock() mock_method.shift_action = BoardLocation(0, 1), 90 mock_method.move_action = board.maze.maze_card_location(piece.maze_card) mock_method_factory = Mock() mock_method_factory.return_value = mock_method player = ComputerPlayer(library_binding_factory=mock_method_factory, move_url="move-url", shift_url="shift-url", game=game, identifier=9, board=board, piece=piece) player.run() mock_method.start.assert_called_once() post_shift.assert_called_once() post_move.assert_called_once()
def test_clear_pieces_after_creations_empties_pieces(): """ Tests clear_pieces. """ board = Board(create_random_maze()) for _ in range(8): board.create_piece() assert len(board.pieces) == 8 board.clear_pieces() assert not board.pieces
def _create_test_game(with_computer=False): """ Creates a Game instance by hand """ card_factory = MazeCardFactory() board = Board(leftover_card=MazeCard(0, MazeCard.T_JUNCT, 0)) for row in range(board.maze.maze_size): for column in range(board.maze.maze_size): if row == 0 and column == 0: board.maze[BoardLocation( row, column)] = card_factory.create_instance( MazeCard.STRAIGHT, 0) elif row == 1 and column == 1: board.maze[BoardLocation( row, column)] = card_factory.create_instance( MazeCard.CORNER, 0) elif row == 2 and column == 2: board.maze[BoardLocation( row, column)] = card_factory.create_instance( MazeCard.T_JUNCT, 270) else: board.maze[BoardLocation( row, column)] = card_factory.create_instance( MazeCard.T_JUNCT, 0) player_ids = [3, 4] players = [ Player(identifier=player_id, game=None) for player_id in player_ids ] if with_computer: player_ids.append(42) players.append( create_computer_player(player_id=42, compute_method="dynamic-foo", shift_url="shift-url", move_url="move-url")) for player in players: player.set_board(board) players[0].piece.maze_card = board.maze[BoardLocation(3, 3)] players[1].piece.maze_card = board.maze[BoardLocation(5, 5)] players[0].piece.piece_index = 1 players[1].piece.piece_index = 0 players[0].score = 7 players[1].score = 8 board._objective_maze_card = board.maze[BoardLocation(1, 4)] turns = Turns(players, next_action=PlayerAction(players[1], PlayerAction.MOVE_ACTION)) game = Game(identifier=7, turns=turns, board=board, players=players) for player in players: player._game = game game.previous_shift_location = BoardLocation(0, 3) return game, player_ids
def test_random_actions_should_respect_no_pushback_rule(): """ Runs computation 50 times and checks that none of the computed shifts reverts the previous shift action """ card_factory = MazeCardFactory() orig_board = Board(create_maze(MAZE_STRING, card_factory), leftover_card=card_factory.create_instance("NE", 0)) for _ in range(50): board = copy.deepcopy(orig_board) maze = board.maze piece = board.create_piece() piece.maze_card = maze[BoardLocation(0, 0)] game = Game(0, board=orig_board) game.previous_shift_location = BoardLocation(0, 3) computer_player = ComputerPlayer(library_binding_factory=Mock(), move_url="move-url", shift_url="shift-url", game=game, identifier=9, board=board, piece=piece) shift_action, _ = computer_player.random_actions() shift_location, _ = shift_action assert shift_location != BoardLocation(6, 3)
def test_add_player_start_game_calls_methods_on_board(): """ Tests add_player, start_game and Player """ board = Board() turns = Mock() game = Game(identifier=0, board=board, turns=turns) with patch.object(board, 'create_piece', wraps=board.create_piece) as board_create_piece: for _ in range(4): player_id = game.unused_player_id() game.add_player(Player(player_id)) game.start_game() expected_board_calls = [ call.create_piece(), call.create_piece(), call.create_piece(), call.create_piece() ] assert board_create_piece.mock_calls == expected_board_calls
def test_shift_updates_old_leftover_rotation(): """ Tests shift """ board = Board() old_leftover = board.leftover_card board.shift(BoardLocation(0, 1), 270) assert old_leftover.rotation == 270
def create_board(maze_string=ALL_CONNECTED_3): maze = factories.create_maze(maze_string) return Board(maze=maze)
def test_after_series_of_creates_and_removes_no_corners_empty(): """ Tests create_piece. Adds three pieces, removes two, adds three, and checks that all pieces are on a different corner """ board = Board(create_random_maze()) piece1 = board.create_piece() piece2 = board.create_piece() board.create_piece() board.remove_piece(piece1) board.remove_piece(piece2) board.create_piece() board.create_piece() board.create_piece() assert len(board.pieces) == 4 piece_cards = {piece.maze_card for piece in board.pieces} assert len(piece_cards) == 4
def test_remove_piece_after_create_piece(): """ Tests remove_piece """ board = Board(create_random_maze()) piece = board.create_piece() board.remove_piece(piece) assert not board.pieces
def given_empty_game__when_adding_players__creates_pieces_on_board(): board = Board() game = Game(identifier=0, board=board, turns=Mock()) add_players(game, 4) assert len(board.pieces) == 4
def test_move_raises_error_on_invalid_location(): """ Tests move validation """ board = Board() piece = board.create_piece() with pytest.raises(InvalidLocationException): board.move(piece, BoardLocation(-1, -1))
def test_get_enabled_shift_locations_without_previous_shift(): """ Tests get_enabled_shift_locations where the previous shift is None """ board = Board() game = Game(identifier=0, board=board) enabled_shift_locations = game.get_enabled_shift_locations() assert set(enabled_shift_locations) == set(board.shift_locations)
def test_shift_raises_error_on_invalid_rotation(): """ Tests shift validation """ board = Board() with pytest.raises(InvalidRotationException): board.shift(BoardLocation(0, 1), 70)
def test_shift_raises_error_on_invalid_location(): """ Tests shift validation """ board = Board() with pytest.raises(InvalidShiftLocationException): board.shift(BoardLocation(0, 0), 90)
def test_shift_updates_new_leftover(): """ Tests shift """ board = Board() pushed_out = board.maze[BoardLocation(board.maze.maze_size - 1, 1)] board.shift(BoardLocation(0, 1), 270) assert pushed_out == board.leftover_card
def create_board(maze_size=7): """ Creates a board with a given maze size. The maze and the leftover obey the generalized original-game layout and maze card distribution rules """ maze, leftover = create_maze_and_leftover(size=maze_size) return Board(maze=maze, leftover_card=leftover)