def test_board_can_generate_all_valid_2x2_permutations(): puzzles = Board.get_boards(2) assert [1, 2, 3, None] not in puzzles # does not include solution assert [1, 3, 2, None] not in puzzles # does not include invalid config assert [1, 2, None, 3] in puzzles # does include a known valid config for puzzle in puzzles: assert Board.valid(puzzle) # and all the others are valid
def __init__(self, tile_config): self.start_state = tile_config[:] self.board = Board(tile_config) self.solutions = {} self.recursed = 0 self.all_paths = [] self.solution_path = []
def __init__(self, dimension): self.dimension = dimension solution = [] for num in range(1, dimension**2): solution.append(num) solution.append(None) self.starting_state = solution self.board = Board(solution)
class Solver: def __init__(self, tile_config): self.start_state = tile_config[:] self.board = Board(tile_config) self.solutions = {} self.recursed = 0 self.all_paths = [] self.solution_path = [] def solve(self): moves = self.find_shortest_path() self.solution_path = moves if (moves != None): for move in moves: self.board.move(move) else: print("CRUD. Could not find a solution for {}.".format(self.start_state)) return self.board.solved() def find_all_paths_from_point(self, board_state, completed_moves, previous_states, paths_so_far): all_paths = paths_so_far[:] available_moves = Board(board_state).lookahead() for move in available_moves: new_board_state = available_moves[move] path = completed_moves[:] if (new_board_state == self.board.solution): # we're done path.append(move) all_paths.append(path) elif (len(completed_moves) > 0) and (move == completed_moves[-1]): # ignore attempts to undo pass elif (len(path) > 2): path.append(move) path.append("R") all_paths.append(path) elif (new_board_state in previous_states): path.append(move) path.append("X") all_paths.append(path) else: path.append(move) previous_states.append(board_state) all_paths.extend(self.find_all_paths_from_point(new_board_state, path, previous_states, all_paths)) return all_paths def find_shortest_path(self): self.all_paths = self.find_all_paths_from_point(self.start_state, [], [], []) shortest_path = None for path in self.all_paths: if (not isinstance(path[-1], int)): # skip the ones marked as not viable pass elif (shortest_path == None) or (len(shortest_path) > len(path)): shortest_path = path return shortest_path
def test_board_can_generate_all_2x2_permutations(): assert Board.get_permutations([1, 2, 3, None]) == [[1, 2, 3, None], [1, 2, None, 3], [1, 3, 2, None], [1, 3, None, 2], [1, None, 2, 3], [1, None, 3, 2], [2, 1, 3, None], [2, 1, None, 3], [2, 3, 1, None], [2, 3, None, 1], [2, None, 1, 3], [2, None, 3, 1], [3, 1, 2, None], [3, 1, None, 2], [3, 2, 1, None], [3, 2, None, 1], [3, None, 1, 2], [3, None, 2, 1], [None, 1, 2, 3], [None, 1, 3, 2], [None, 2, 1, 3], [None, 2, 3, 1], [None, 3, 1, 2], [None, 3, 2, 1]]
def find_all_paths_from_point(self, board_state, completed_moves, previous_states, paths_so_far): all_paths = paths_so_far[:] available_moves = Board(board_state).lookahead() for move in available_moves: new_board_state = available_moves[move] path = completed_moves[:] if (new_board_state == self.board.solution): # we're done path.append(move) all_paths.append(path) elif (len(completed_moves) > 0) and (move == completed_moves[-1]): # ignore attempts to undo pass elif (len(path) > 2): path.append(move) path.append("R") all_paths.append(path) elif (new_board_state in previous_states): path.append(move) path.append("X") all_paths.append(path) else: path.append(move) previous_states.append(board_state) all_paths.extend(self.find_all_paths_from_point(new_board_state, path, previous_states, all_paths)) return all_paths
def test_all_2x2s(): print("\n") all_valid_boards = Board.get_boards(2) generated_solutions = [[[1, 2, 3, None], []]] for move_count in range(1, 10): generated_solutions.extend(Generator(2).inmoves(move_count)) # for thing in solutions: # print(solutions[thing]) for board in all_valid_boards: solved = None for solution in generated_solutions: if (solution[0] == board): if (solved == None or len(solved) > len(solution[1])): solved = solution[1] solved.reverse() print("Puzzle {} solution {}".format(board, solved))
class Generator: def __init__(self, dimension): self.dimension = dimension solution = [] for num in range(1, dimension**2): solution.append(num) solution.append(None) self.starting_state = solution self.board = Board(solution) def inmoves(self, num_moves): paths = [] check_states = {0: [[self.starting_state, []]]} for depth in range(num_moves): at_depth = (depth == num_moves - 1) check_states[depth + 1] = [] for state in check_states[depth]: if at_depth: paths.extend( self.get_next_paths_from_state(state[0], state[1])) else: check_states[depth + 1].extend( self.get_next_paths_from_state(state[0], state[1])) return paths def get_next_paths_from_state(self, tiles, path_to_state): self.board.tiles = tiles paths = [] lookahead = self.board.lookahead() for move in lookahead: if (len(path_to_state) == 0): paths.append([lookahead[move], [move]]) elif (path_to_state[-1] != move): paths.append([lookahead[move], path_to_state + [move]]) return paths
def test_board_init_throws_error_if_board_too_small(): with pytest.raises(RuntimeError) as excinfo: puzzle = Board([0, None]) assert 'Tile configuration invalid' in str(excinfo.value)
def test_board_knows_when_tiles_numbers_are_not_sequential(): assert not Board().valid([1, 5, 2, None])
def test_board_knows_when_there_is_more_than_one_space(): assert not Board().valid([1, 2, None, None])
def test_board_knows_when_there_is_no_space(): assert not Board().valid([1, 2, 3, 4])
def test_board_knows_8_tiles_is_not_valid(): assert not Board().valid([1, 2, 3, 4, 5, 6, 7, None])
def test_board_init(): puzzle = Board() assert isinstance(puzzle.tiles, list)
def test_default_board(): puzzle = Board() assert puzzle.tiles == [1, 2, 3, 4, 5, 6, 7, 8, None]
def test_board_can_tell_when_not_solved(): puzzle = Board([2, 3, 1, None]) assert not puzzle.solved()
def test_board_has_a_solution(): puzzle = Board([1, 3, 2, None]) assert puzzle.solution == [1, 2, 3, None]
def test_board_init_throws_error_if_tiles_not_sequential(): with pytest.raises(RuntimeError) as excinfo: puzzle = Board([3, 5, 6, None]) assert 'Tile configuration invalid' in str(excinfo.value)
def test_board_can_generate_all_2d_permutations(): assert sorted(Board.get_permutations([1, 2])) == [[1, 2], [2, 1]]
def test_board_knows_dimensions(): puzzle = Board([3, 2, 1, 4, 5, 6, 7, 8, None]) assert puzzle.dimension == 3
def test_board_can_report_diff_score_to_possible_new_state(): puzzle = Board([1, None, 3, 2]) assert puzzle.solution_diff_score_from([None, 1, 3, 2]) == 3
def test_board_can_list_available_moves(): puzzle = Board([1, None, 3, 2]) assert puzzle.available_moves() == [1, 2]
def test_board_init_throws_error_if_board_has_no_space(): with pytest.raises(RuntimeError) as excinfo: puzzle = Board([1, 2, 3, 4]) assert 'Tile configuration invalid' in str(excinfo.value)
def test_board_knows_0_tiles_is_not_valid(): assert not Board().valid([])
def test_board_can_tell_when_solved(): puzzle = Board([1, 2, 3, None]) assert puzzle.solved()
def test_board_knows_1_tile_is_not_valid(): assert not Board().valid([1])
def test_board_knows_where_the_blank_is(): puzzle = Board([2, 3, 1, None]) assert puzzle.blank_position == 3
def test_board_knows_4_tiles_is_valid(): assert Board().valid([1, 2, 3, None])
def test_board_can_move_piece(): puzzle = Board([2, 3, 1, None]) puzzle.move(3) assert puzzle.tiles == [2, None, 1, 3]
def test_board_knows_9_tiles_is_valid_regardless_of_order(): assert Board().valid([3, 2, 1, 8, 5, 6, 7, 4, None])