Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
def test_board_can_move_piece():
    puzzle = Board([2, 3, 1, None])
    puzzle.move(3)
    assert puzzle.tiles == [2, None, 1, 3]
Ejemplo n.º 3
0
def test_board_cannot_move_if_tile_surrounded():
    puzzle = Board([1, 2, 3, 4, 5, 6, 7, None, 8])
    puzzle.move(2)
    assert puzzle.tiles == [1, 2, 3, 4, 5, 6, 7, None, 8]
Ejemplo n.º 4
0
def test_board_cannot_make_diagonal_move():
    puzzle = Board([2, 3, 1, None])
    puzzle.move(2)
    assert puzzle.tiles == [2, 3, 1, None]
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 = []
        self.shortest_solution = None

    def solve(self):
        # moves = self.find_shortest_path()
        # self.solution_path = moves
        self.find_all_paths_from_point(self.start_state, [], [], [])
        if (self.shortest_solution != None):
            for move in self.shortest_solution:
                self.board.move(move)
        else:
            print("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 = self.lookahead(board_state)
        print(
            "\nGoing into path search loop.\nBoard state: {} \nAvailable Moves: {}"
            .format(board_state, available_moves))
        move_scores = self.prioritize(available_moves)
        print("Move Scores: {}".format(move_scores))
        move_priorities = sorted(move_scores, key=move_scores.get)
        for move in move_priorities:
            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)
                if (self.shortest_solution == None):
                    self.shortest_solution = path
                elif (len(path) < len(self.shortest_solution)):
                    self.shortest_solution = path
            elif (len(completed_moves) > 0) and (move == completed_moves[-1]):
                # ignore attempts to undo
                pass
            elif (self.shortest_solution != None) and (len(all_paths) > 9000):
                all_paths.append(["Z"])
                break  # you have a solution, just give up
            elif ((len(path) >= 30)
                  or ((self.shortest_solution != None) and
                      (len(path) + 1) >= len(self.shortest_solution))):
                # give up
                path.append(move)
                path.append("L")
                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))
        print("Returning from find_all_paths_from_point\nExplored {} paths".
              format(len(all_paths)))
        if (self.shortest_solution != None):
            print("...shortest path has {} moves\n  > {}".format(
                len(self.shortest_solution), self.shortest_solution))
        else:
            print("...no solution yet.\n")
        return all_paths

    def prioritize(self, possibilities):
        scores = {}
        for move in possibilities:
            # print("\nWorking on move {}".format(move))
            board_state = possibilities[move]
            move_score = self.sum_scores(board_state)
            # lookahead_score = 0
            # lookahead = self.lookahead(board_state)
            # for board in lookahead:
            #     lookahead_score += self.sum_scores(lookahead[board])
            # scores[move] = move_score + lookahead_score
            scores[move] = move_score
        # gc.collect()
        # tracemalloc.start(10)
        #
        # snapshot = tracemalloc.take_snapshot()
        # top_stats = snapshot.statistics('lineno')
        # print("[ Top 10 ]")
        # for stat in top_stats[:10]:
        #     print(stat)
        # stats = snapshots[-1].filter_traces().compare_to(self.snapshots[-2], 'filename')
        # for stat in stats[:10]:
        #     print("{} new KiB {} total KiB {} new {} total memory blocks: ".format(stat.size_diff / 1024, stat.size / 1024,
        #                                                          stat.count_diff, stat.count))
        return scores

    def sum_scores(self, tiles):
        score = 0
        print("Scoring board state: {}".format(tiles))
        inversions = self.count_inversions(tiles) * 10
        deltapos = self.count_delta_from_position(tiles) * 10
        deltarowcol = self.count_delta_from_rowcol(tiles) * 10
        countneighbor = self.count_delta_from_neighbor(tiles)
        print(
            "inversion {} | deltapos {} | deltarowcol {} | neighbor {}".format(
                inversions, deltapos, deltarowcol, countneighbor))
        return inversions + deltarowcol + deltapos + countneighbor

    def count_inversions(self, tiles):
        numbers = [item for item in tiles if isinstance(item, int)]
        num_inversions = 0
        for num, tile in enumerate(numbers, start=0):
            for i in range(num + 1, len(numbers)):
                if (numbers[i] < tile):
                    num_inversions += 1
        # print("...inversions is {}".format(num_inversions))
        return num_inversions

    def count_delta_from_position(self, tiles):
        delta = 0
        for num, tile in enumerate(tiles, start=1):
            if (tile != None):
                delta += abs(tile - num)
        # print("...position delta is {}".format(delta))
        return delta

    def count_delta_from_rowcol(self, tiles):
        delta = 0
        for num, tile in enumerate(tiles, start=1):
            if (tile == None):
                tile = (self.board.dimension)**2
            tilerow = (num // self.board.dimension) + 1
            tilecol = ((num + 1) % self.board.dimension) + 1
            tilerow_solution = (tile // self.board.dimension) + 1
            tilecol_solution = ((tile + 1) % self.board.dimension) + 1
            delta = (abs(tilerow_solution - tilerow)) + (abs(tilecol_solution -
                                                             tilecol))
        # print("...rowcol delta is {}".format(delta))
        return delta

    def count_delta_from_neighbor(self, tiles):
        count = 0
        for num, tile in enumerate(tiles, start=0):
            neighbor_positions = self.neighbor_indices(tiles, num)
            for neighbor in neighbor_positions:
                if (tile != None and tiles[neighbor] != None):
                    count += abs(tile - tiles[neighbor])
        count += 8 - tiles.index(None)
        return count

    def lookahead(self, board):
        available_moves = self.get_available_moves_from(board)
        none_position = board.index(None)
        whatif = {}
        for tile in available_moves:
            whatif_board = board[:]
            tile_position = board.index(tile)
            whatif_board[tile_position] = None
            whatif_board[none_position] = tile
            whatif[tile] = whatif_board
        return whatif

    def get_available_moves_from(self, board):
        none_index = board.index(None)
        neighbor_indices = self.neighbor_indices(board, none_index)
        moves = []

        for position in neighbor_indices:
            moves.append(board[position])

        return sorted(moves)

    def neighbor_indices(self, board, position):
        neighbor_indices = []

        upper = position - self.board.dimension
        if (upper >= 0):
            neighbor_indices.append(upper)

        right = position % self.board.dimension
        if (right != self.board.dimension - 1):
            neighbor_indices.append(position + 1)

        lower = position + self.board.dimension
        if (lower < len(board)):
            neighbor_indices.append(lower)

        left = position % self.board.dimension
        if (left != 0):
            neighbor_indices.append(position - 1)

        return neighbor_indices
class Solver:
    def __init__(self, tile_config):
        self.board = Board(tile_config)
        self.moves = []
        self.visited_states = []
        self.status = "Solving..."
        self.locked_tiles = []

    def solve(self):
        if self.board.is_solvable():
            self.visited_states.append(self.get_board_state())
            while (not self.board.solved()) and (self.status != "stuck"):
                self.find_best_move()
        else:
            self.status = "Unsolvable"

    def get_available_moves(self):
        return sorted([tile for tile in self.board.tile_neighbors(None) if isinstance(tile, int)])

    def get_board_state(self):
        board_state = self.board.tiles[:]
        return board_state

    def check_locks(self):
        # this is a really really dumb implementation - TEMPORARY
        # ONLY WORKS for 3x3s, not 4x4s

        self.locked_tiles = []

        # if 1 is in the 1 place, lock it
        if (self.board.tiles[0] == 1):
            self.locked_tiles.append(1)
            # and if 2 AND 3 are in place, lock them
            if (self.board.tiles[1] == 2 and self.board.tiles[2] == 3):
                self.locked_tiles.append(2)
                self.locked_tiles.append(3)

        if ((self.board.dimension == 3) and
                (1 in self.locked_tiles) and
                (self.board.tiles[3] == 4) and
                (self.board.tiles[6] == 7)
        ):
            self.locked_tiles.append(4)
            self.locked_tiles.append(7)

    def find_best_move(self):
        possible_moves = self.board.lookahead()
        move_scores = {}
        for move in possible_moves:
            move_scores[move] = self.board.solution_diff_score_from(possible_moves[move])
            # only consider moves that don't get us back to a known state
            if (move in self.locked_tiles):
                move_scores[move] += 9999
            if len(self.visited_states) > 0 and (possible_moves[move] == self.visited_states[-1]):
                move_scores[move] += 99999
            if (possible_moves[move] in self.visited_states):
                move_scores[move] += 99

        best_move = min(move_scores, key=move_scores.get)
        # print("BEST MOVE: {}".format(best_move))
        self.board.move(best_move)
        self.moves.append(best_move)
        self.visited_states.append(self.get_board_state())
        # if (best_move in self.locked_tiles):
        #     self.locked_tiles.remove(best_move)
        self.check_locks()
        if (len(self.moves) > 500):
            self.status = "stuck"