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 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_is_reachable_for_connected_neighbors(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert graph.is_reachable(BoardLocation(2, 4), BoardLocation(1, 4)) assert graph.is_reachable(BoardLocation(2, 4), BoardLocation(2, 3)) assert graph.is_reachable(BoardLocation(2, 4), BoardLocation(3, 4))
def param_tuple_to_param_dict(maze_string, leftover_out_paths, piece_starts, objective): """ Converts a tuple of board state defining parameters to a dictionary, which can be used by create_board_and_pieces. :param maze_string: a string defining a maze :param leftover_out_paths: the out paths of the leftover maze card :param piece_starts: a list of starting locations of pieces on the board. The number of pieces will be equal to the size of this list. Each location is a 2-tuple. :param objective: the location (2-tuple) of the objective, or 'leftover' to denote that the objective is the leftover maze card """ maze_card_factory = MazeCardFactory() maze = create_maze(maze_string, maze_card_factory) leftover_card = maze_card_factory.create_instance(leftover_out_paths, 0) param_dict = { "maze": maze, "leftover_card": leftover_card, "piece_locations": [BoardLocation(*piece_start) for piece_start in piece_starts] } if type(objective) == tuple: param_dict["objective_location"] = BoardLocation(*objective) elif objective == "leftover": param_dict["objective_maze_card"] = leftover_card return param_dict
def test_reachable_locations(): """ Tests reachable_locations """ maze = create_maze(MAZE_STRING) graph = Graph(maze) reachable = graph.reachable_locations(BoardLocation(0, 1)) expected = {BoardLocation(*coord) for coord in [(0, 1), (0, 2), (0, 3), (1, 3)]} assert set(reachable) == expected
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_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_is_reachable_for_swapped_locations(): """ Tests is_reachable. Re-runs all of the above tests with swapped locations. """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert not graph.is_reachable(BoardLocation(1, 0), BoardLocation(0, 0)) assert not graph.is_reachable(BoardLocation(0, 1), BoardLocation(0, 0)) assert not graph.is_reachable(BoardLocation(2, 5), BoardLocation(2, 4)) assert graph.is_reachable(BoardLocation(1, 4), BoardLocation(2, 4)) assert graph.is_reachable(BoardLocation(2, 3), BoardLocation(2, 4)) assert graph.is_reachable(BoardLocation(3, 4), BoardLocation(2, 4)) assert graph.is_reachable(BoardLocation(3, 2), BoardLocation(3, 1)) assert graph.is_reachable(BoardLocation(5, 0), BoardLocation(1, 4)) assert not graph.is_reachable(BoardLocation(4, 4), BoardLocation(1, 0)) assert graph.is_reachable(BoardLocation(6, 3), BoardLocation(5, 0)) assert graph.is_reachable(BoardLocation(2, 6), BoardLocation(0, 6))
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_create_fixed_maze_for_size_3(): """ Creates a fixed maze of size 3 using a maze string. Checks every single position for correct layout. """ maze = create_maze(maze_string_3) assert maze.maze_size == 3 assert maze[BoardLocation(0, 0)].out_paths == MazeCard.T_JUNCT assert maze[BoardLocation(0, 0)].rotation == 0 assert maze[BoardLocation(0, 1)].out_paths == MazeCard.CORNER assert maze[BoardLocation(0, 1)].rotation == 180 assert maze[BoardLocation(0, 2)].out_paths == MazeCard.T_JUNCT assert maze[BoardLocation(0, 2)].rotation == 270 assert maze[BoardLocation(1, 0)].out_paths == MazeCard.T_JUNCT assert maze[BoardLocation(1, 0)].rotation == 90 assert maze[BoardLocation(1, 1)].out_paths == MazeCard.CROSS assert maze[BoardLocation(1, 2)].out_paths == MazeCard.STRAIGHT assert maze[BoardLocation(1, 2)].rotation == 0 assert maze[BoardLocation(2, 0)].out_paths == MazeCard.CORNER assert maze[BoardLocation(2, 0)].rotation == 0 assert maze[BoardLocation(2, 1)].out_paths == MazeCard.CORNER assert maze[BoardLocation(2, 1)].rotation == 270 assert maze[BoardLocation(2, 2)].out_paths == MazeCard.STRAIGHT assert maze[BoardLocation(2, 2)].rotation == 90
def create_board(): card_factory = factory.MazeCardFactory() maze = factory.create_maze(MAZE_STRING, card_factory) leftover = card_factory.create_instance("NE", 0) return Board(maze=maze, leftover_card=leftover)
def test_is_reachable_for_connected_neighbors_wo_direct_path(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert graph.is_reachable(BoardLocation(3, 1), BoardLocation(3, 2))
def test_is_reachable_for_connected_distant_cards(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert graph.is_reachable(BoardLocation(1, 4), BoardLocation(5, 0))
def test_is_reachable_for_unconnected_cards_with_only_one_wall(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert not graph.is_reachable(BoardLocation(1, 0), BoardLocation(4, 4))
def test_is_reachable_for_paths_on_border(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert graph.is_reachable(BoardLocation(5, 0), BoardLocation(6, 3)) assert graph.is_reachable(BoardLocation(0, 6), BoardLocation(2, 6))
def create_board(maze_string=ALL_CONNECTED_3): maze = factories.create_maze(maze_string) return Board(maze=maze)
def test_is_reachable_for_same_location(): """ Tests is_reachable """ maze = create_maze(MAZE_STRING) graph = Graph(maze) assert graph.is_reachable(BoardLocation(0, 0), BoardLocation(0, 0))