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_can_provide_lookahead(): puzzle = Board([1, None, 3, 2]) assert puzzle.lookahead() == {1: [None, 1, 3, 2], 2: [1, 2, 3, None]}
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"