class MineSolver: """ Solves Minesweeper by running through a few algorithms: Flag/reveal Confined Mine Shared Mine """ def __init__(self, load_game=None): self.rows = 16 self.columns = 30 self.num_mines = 99 if load_game is None: self.mine = Minesweeper(self.rows, self.columns, self.num_mines, gui=False) else: self.mine = Minesweeper.load_state(load_game) self.servant = MineServant(self.mine) self.exposed_indices = self.mine.exposed_field # Move these to self.exposed_field self.newly_exposed = [] # Add block indices to here as they're revealed self.unchecked_blocks = [] # Blocks scheduled to be removed after some processes for loops self.blocks_to_remove = set([]) self.flags_planted = 0 # Lose/win status. Both start as false naturally self.lose = False self.win = False self.semi_solver = None def reveal_squares(self, coordinate): """ Calls the Minesweeper button_reveal function so progress can be tracked with the GUI. :param coordinate: :return: """ for coord in coordinate: self.lose, self.newly_exposed = self.mine.button_reveal(None, coord) self.unchecked_blocks.extend(self.newly_exposed) def logical_solver(self): """ Process that loops through unchecked blocks and runs the flag/reveal process. :return: """ # Will break out if no moves found solver_repeat = True while solver_repeat: # Try the easy stuff solver_repeat = self.flag_reveal_loop() # Now try the hard stuff for coordinate in self.unchecked_blocks: unrevealed = self.servant.get_unrevealed_blocks(coordinate) if unrevealed: area_reveal, area_flag = self.confined_mine_process(coordinate, unrevealed) if self.lose: print "Somehow we lost?! It's your fault." return # Break out of hard stuff and go to flag/reveal if area_reveal or area_flag: solver_repeat = True break # Check for a shared mine shared_reveal, shared_flag = self.shared_mine_process(coordinate) if self.lose: print "Somehow we lost?! It's your fault." return if shared_reveal or shared_flag: solver_repeat = True break # Schedule block for deletion after for loop. else: self.blocks_to_remove.add(coordinate) if self.blocks_to_remove: self.remove_checked_blocks() print "Out of moves. Full field:" # Quasi pretty-prints the field for the console. self.servant.pretty_print_field() if self.win: print "You won, because you're the best maybe." def probability_guesser(self): """ :return: """ # Get all unrevealed blocks that touch unchecked blocks blocks_to_guess = set([]) numbers_dictionary = {} for coordinate in self.unchecked_blocks: numbers_dictionary[coordinate] = self.servant.get_real_block_value(coordinate) unrevealed_blocks = self.servant.get_unrevealed_blocks(coordinate) blocks_to_guess.update(unrevealed_blocks) # self.servant.custom_pretty_print_field(self.exposed_field, blocks_to_guess) self.semi_solver = MineSemiSolver(blocks_to_guess, numbers_dictionary, self.rows, self.columns) self.semi_solver.choose() # self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=blocks_to_guess) def remove_checked_blocks(self): """ Removes self.blocks_to_remove from self.exposed_field. :return: """ for block in self.blocks_to_remove: self.unchecked_blocks.remove(block) self.blocks_to_remove.clear() def flag_reveal_loop(self): """ A loop to call flag_reveal_process in an efficient manner. :return: """ solver_repeat = False flag_reveal_repeat = True # Do the easy stuff first while flag_reveal_repeat: # Will break out if no flag or reveal flag_reveal_repeat = False for coordinate in self.unchecked_blocks: # Check if there are any unrevealed blocks unrevealed = self.servant.get_unrevealed_blocks(coordinate) if unrevealed: reveal, flag = self.flag_reveal_process(coordinate, unrevealed) # If it hits even once, repeat flag/reveal if reveal or flag: flag_reveal_repeat = True if self.lose: print "Somehow we lost?! It's your fault." return # If not, schedule block for deletion after for loop. else: self.blocks_to_remove.add(coordinate) # Remove blocks before moving on if self.blocks_to_remove: self.remove_checked_blocks() if self.win: solver_repeat = False break return solver_repeat def flag_reveal_process(self, coordinate, unrevealed): """ Main process in which unrevealed blocks are found to reveal or flag. :param coordinate: :return: """ reveal = False flag = False num_unrevealed = len(unrevealed) real_block_value = self.servant.get_real_block_value(coordinate) # All mines accounted for. Reveal rest if real_block_value == 0: self.reveal_squares(unrevealed) self.blocks_to_remove.add(coordinate) reveal = True # All unrevealed are mines. Flag them elif real_block_value == num_unrevealed: for coord in unrevealed: self.win = self.mine.button_flag(None, coord) self.flags_planted += 1 self.blocks_to_remove.add(coordinate) flag = True return reveal, flag def confined_mine_process(self, coordinate, unrevealed): """ Check if a mine is enclosed in an area, and if surrounding blocks can be flagged or revealed. :param coordinate: :return: """ reveal = False flag = False mines_in_area = self.servant.get_real_block_value(coordinate) # starts with first block, then whittles down similar_neighbors = set(self.servant.get_exposed_neighbor_coords(unrevealed[0])) for block in unrevealed[1:]: # gets the neighbors of the next block block_neighbors = self.servant.get_exposed_neighbor_coords(block) similar_neighbors.intersection_update(block_neighbors) # Now we have a list of shared neighbors. We need to check them all. for neighbor in similar_neighbors: real_block_value = self.servant.get_real_block_value(neighbor) # starts as full blocks_out_of_area = set(self.servant.get_unrevealed_blocks(neighbor)) # Gets rid of blocks in unrevealed blocks_out_of_area.difference_update(unrevealed) num_outside = len(blocks_out_of_area) if num_outside > 0: # Flag blocks outside of area. if real_block_value == num_outside + mines_in_area: for block in blocks_out_of_area: self.win = self.mine.button_flag(None, block) self.flags_planted += 1 flag = True # Reveal blocks outside area if real_block_value == mines_in_area: self.reveal_squares(blocks_out_of_area) reveal = True return reveal, flag def shared_mine_process(self, coordinate): """ Loop through all neighbors and check if they share mines. :return: """ flag = False reveal = False neighbor_blocks = self.servant.get_neighbor_blocks(coordinate) real_block_value = self.servant.get_real_block_value(coordinate) # Loops through all neighbors for neighbor in neighbor_blocks: # Don't do checked neighbors if neighbor not in self.unchecked_blocks: continue # Find shared unrevealed blocks blocks_unrevealed = set(self.servant.get_unrevealed_blocks(coordinate)) neighbor_unrevealed = set(self.servant.get_unrevealed_blocks(neighbor)) # Get all unrevealed blocks shared by the neighbors shared_unrevealed = blocks_unrevealed.intersection(neighbor_unrevealed) num_shared = len(shared_unrevealed) neighbor_real_block_value = self.servant.get_real_block_value(neighbor) possible_area_mines = min(real_block_value, neighbor_real_block_value, num_shared) neighbors_outside_area = len(neighbor_unrevealed) - num_shared if neighbors_outside_area > 0 and \ neighbor_real_block_value == neighbors_outside_area + possible_area_mines: # Flag neighbor blocks not shared with coordinate for block in neighbor_unrevealed.difference(blocks_unrevealed): self.win = self.mine.button_flag(None, block) self.flags_planted += 1 flag = True # Check if the original block can reveal things. if real_block_value == possible_area_mines: # Reveal blocks not shared with neighbor self.reveal_squares(blocks_unrevealed.difference(neighbor_unrevealed)) reveal = True return reveal, flag
class MineSemiSolver: def __init__(self, unrevealed, neighbors, rows, columns): self.unrevealed = unrevealed self.neighbors = neighbors self.rows = rows self.columns = columns self.flags = [] self.servant = MineServant(self) self.exposed_field = self.neighbors.keys() self.possible_plays = [] self.color_coding = { 'flag': 'green', 'bomb': 'red', 'unrevealed': 'yellow', 'zero': 'grey', 'number': 'white' } def choose(self): self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) for coordinate in self.exposed_field: print '*****COORDINATE*****', coordinate real_block_value = self.servant.get_real_block_value(coordinate) unrevealed = self.get_unrevealed_blocks(coordinate) if real_block_value == 0 and len(unrevealed) > 0: for index in unrevealed: self.unrevealed.remove(index) continue if real_block_value < 0 or len(unrevealed) == 0: continue # All possible locations of the mine(s). We need to only choose one. all_sets = combinations(list(unrevealed), real_block_value) # Validates all moves valid_moves = [] for move_set in all_sets: valid_move = self.negative_neighbor_move_validation(move_set) blocks_to_remove = unrevealed.difference(move_set) if valid_move: valid_move = self.isolated_neighbor_move_validation( coordinate, move_set, list(blocks_to_remove)) # Adds valid moves to the list if valid_move: valid_moves.append(move_set) try: chosen_placement = choice(valid_moves) print 'chosen move: ', chosen_placement except IndexError: print 'no moves!' else: # Remove blocks first for index in unrevealed: self.unrevealed.remove(index) # Then flag and remove blocks around neighbors for chosen in chosen_placement: self.flags.append(chosen) self.remove_neighbor_unrevealed(chosen) self.servant.custom_pretty_print_field( self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) def negative_neighbor_move_validation(self, move_set): """ Validates move by looking if neighbor values go negative :param move_set: :return: """ valid_move = True shared_neighbors = set([]) # Get all neighbors that border the move set for coordinate in move_set: shared_neighbors.update( self.servant.get_neighbor_blocks(coordinate)) # Loop through and check if any are too low. for neighbor in shared_neighbors: # Get number of flags that touch that neighbor neighbor_surrounding = self.get_surrounding_block_coords(neighbor) num_flags = len(neighbor_surrounding.intersection(move_set)) # Block value if that move were performed block_value = self.servant.get_real_block_value( neighbor) - num_flags # Any neighbor can invalidate the move if block_value < 0: valid_move = False break return valid_move def isolated_neighbor_move_validation(self, coordinate, move_set, blocks_to_remove): """ Validates moves by checking that another coordinate does not become isolated with no mines. :param coordinate: :param move_set: :param blocks_to_remove: :return: """ # Have to look at it from the perspective of the blocks to remove # print 'blocks to remove: ', blocks_to_remove # print 'move set: ', move_set # Innocent until proven guilty valid_move = True # Check doesn't need to be performed for zero blocks if len(blocks_to_remove) > 0: # Gets the neighbors attached to the blocks to remove neighbors = self.servant.get_neighbor_blocks(blocks_to_remove[0]) for block in blocks_to_remove[1:]: neighbors.update(self.servant.get_neighbor_blocks(block)) # Remove original coordinate neighbors.remove(coordinate) # Look at each neighbor and their unrevealed for neighbor_2 in neighbors: neighbor_block_value = self.servant.get_real_block_value( neighbor_2) # Skip the neighbor if neighbor has been dealt if neighbor_block_value == 0: continue neighbor_unrevealed = self.get_unrevealed_blocks(neighbor_2) # Remove unrevealed from the move for block in blocks_to_remove: if block in neighbor_unrevealed: neighbor_unrevealed.remove(block) # If there are fewer blocks than the value, then the move is bad if len(neighbor_unrevealed) < neighbor_block_value: valid_move = False # Layer 1: potential move set # Layer 2: Neighbors touching potential move set neighbors_layer_2 = set([]) # Gets all the neighbor blocks which we will need to slightly whittle down for flag_coord in move_set: neighbors_layer_2.update( self.servant.get_neighbor_blocks(flag_coord)) for neighbor_2 in neighbors_layer_2: neighbor_2_surrounding = self.get_surrounding_block_coords( neighbor_2) num_neighbor_2_flags = len( neighbor_2_surrounding.intersection(move_set)) # The block value if the move were performed layer_2_block_value = self.servant.get_real_block_value( neighbor_2) - num_neighbor_2_flags if layer_2_block_value == 0: # These will be revealed. # Layer 3: Unrevealed of layer 2 unrevealed_layer_3 = self.servant.get_unrevealed_blocks( neighbor_2) # Look at blocks neighboring these unrevealed # Layer 4: Neighbors of layer 3 neighbors_layer_4 = set([]) for unrevealed in unrevealed_layer_3: neighbors_layer_4.update( self.servant.get_neighbor_blocks(unrevealed)) # Remove the original block neighbors_layer_4.remove(neighbor_2) # Remove these unrevealed from the neighbors unrevealed for neighbor_4 in neighbors_layer_4: neighbor_4_surrounding = self.get_surrounding_block_coords( neighbor_4) num_neighbor_4_flags = len( neighbor_4_surrounding.intersection(move_set)) # The block value if the move were performed layer_4_block_val = self.servant.get_real_block_value( neighbor_4) - num_neighbor_4_flags # Don't need to worry about completed blocks if layer_4_block_val == 0: continue # Layer 5: Unrevealed of layer 4 unrevealed_layer_5 = self.get_unrevealed_blocks( neighbor_4) for unrevealed in unrevealed_layer_3: if unrevealed in unrevealed_layer_5: unrevealed_layer_5.remove(unrevealed) if len(unrevealed_layer_5) < layer_4_block_val: print 'invalid move' valid_move = False return valid_move def remove_neighbor_unrevealed(self, flag_coordinate): """ Removes all blocks around all neighbors with zero value. :param flag_coordinate: :return: """ neighbors = self.servant.get_neighbor_blocks(flag_coordinate) # Loop through all neighbors for neighbor in neighbors: block_value = self.servant.get_real_block_value(neighbor) # Remove all blocks if zero if block_value == 0: blocks_to_reveal = self.get_unrevealed_blocks(neighbor) for block in blocks_to_reveal: self.unrevealed.remove(block) def get_unrevealed_blocks(self, coordinate): """ Returns the coordinates for all unrevealed blocks around a coordinate. :param coordinate: :return: """ # Gets all surrounding coordinates unrevealed_coords = self.get_surrounding_block_coords(coordinate) # Reduces to what's in self.unrevealed unrevealed_coords.intersection_update(self.unrevealed) return unrevealed_coords # TODO: Can be inherited from Minesweeper def get_num_flag_neighbors(self, coordinate): """ Returns number of flags around the coordinate :param coordinate: :return: """ # Gets all surrounding coordinates flag_coords = self.get_surrounding_block_coords(coordinate) # Reduces to what's in self.flags flag_coords.intersection_update(self.flags) # Gets the length of that flag_neighbors = len(flag_coords) return flag_neighbors # TODO: Can be inherited from Minesweeper def get_surrounding_block_coords(self, coordinate): """ Gets the coordinates for all blocks surrounding the desires coords. :param coordinate: :return: """ # Will choose 0 if coordinate is 0 start_row = max(0, coordinate[0] - 1) # Will choose rows - 1 if coordinate is rows - 1 end_row = min(coordinate[0] + 1, self.rows - 1) start_column = max(0, coordinate[1] - 1) end_column = min(coordinate[1] + 1, self.columns - 1) # Create list of lists for the range surrounding_blocks = [(row, column) for row in range(start_row, end_row + 1) for column in range(start_column, end_column + 1) ] return set(surrounding_blocks)
class MineSolver: """ Solves Minesweeper by running through a few algorithms: Flag/reveal Confined Mine Shared Mine """ def __init__(self, load_game=None): self.rows = 16 self.columns = 30 self.num_mines = 99 if load_game is None: self.mine = Minesweeper(self.rows, self.columns, self.num_mines, gui=False) else: self.mine = Minesweeper.load_state(load_game) self.servant = MineServant(self.mine) self.exposed_indices = self.mine.exposed_field # Move these to self.exposed_field self.newly_exposed = [] # Add block indices to here as they're revealed self.unchecked_blocks = [] # Blocks scheduled to be removed after some processes for loops self.blocks_to_remove = set([]) self.flags_planted = 0 # Lose/win status. Both start as false naturally self.lose = False self.win = False self.semi_solver = None def reveal_squares(self, coordinate): """ Calls the Minesweeper button_reveal function so progress can be tracked with the GUI. :param coordinate: :return: """ for coord in coordinate: self.lose, self.newly_exposed = self.mine.button_reveal( None, coord) self.unchecked_blocks.extend(self.newly_exposed) def logical_solver(self): """ Process that loops through unchecked blocks and runs the flag/reveal process. :return: """ # Will break out if no moves found solver_repeat = True while solver_repeat: # Try the easy stuff solver_repeat = self.flag_reveal_loop() # Now try the hard stuff for coordinate in self.unchecked_blocks: unrevealed = self.servant.get_unrevealed_blocks(coordinate) if unrevealed: area_reveal, area_flag = self.confined_mine_process( coordinate, unrevealed) if self.lose: print "Somehow we lost?! It's your fault." return # Break out of hard stuff and go to flag/reveal if area_reveal or area_flag: solver_repeat = True break # Check for a shared mine shared_reveal, shared_flag = self.shared_mine_process( coordinate) if self.lose: print "Somehow we lost?! It's your fault." return if shared_reveal or shared_flag: solver_repeat = True break # Schedule block for deletion after for loop. else: self.blocks_to_remove.add(coordinate) if self.blocks_to_remove: self.remove_checked_blocks() print "Out of moves. Full field:" # Quasi pretty-prints the field for the console. self.servant.pretty_print_field() if self.win: print "You won, because you're the best maybe." def probability_guesser(self): """ :return: """ # Get all unrevealed blocks that touch unchecked blocks blocks_to_guess = set([]) numbers_dictionary = {} for coordinate in self.unchecked_blocks: numbers_dictionary[coordinate] = self.servant.get_real_block_value( coordinate) unrevealed_blocks = self.servant.get_unrevealed_blocks(coordinate) blocks_to_guess.update(unrevealed_blocks) # self.servant.custom_pretty_print_field(self.exposed_field, blocks_to_guess) self.semi_solver = MineSemiSolver(blocks_to_guess, numbers_dictionary, self.rows, self.columns) self.semi_solver.choose() # self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=blocks_to_guess) def remove_checked_blocks(self): """ Removes self.blocks_to_remove from self.exposed_field. :return: """ for block in self.blocks_to_remove: self.unchecked_blocks.remove(block) self.blocks_to_remove.clear() def flag_reveal_loop(self): """ A loop to call flag_reveal_process in an efficient manner. :return: """ solver_repeat = False flag_reveal_repeat = True # Do the easy stuff first while flag_reveal_repeat: # Will break out if no flag or reveal flag_reveal_repeat = False for coordinate in self.unchecked_blocks: # Check if there are any unrevealed blocks unrevealed = self.servant.get_unrevealed_blocks(coordinate) if unrevealed: reveal, flag = self.flag_reveal_process( coordinate, unrevealed) # If it hits even once, repeat flag/reveal if reveal or flag: flag_reveal_repeat = True if self.lose: print "Somehow we lost?! It's your fault." return # If not, schedule block for deletion after for loop. else: self.blocks_to_remove.add(coordinate) # Remove blocks before moving on if self.blocks_to_remove: self.remove_checked_blocks() if self.win: solver_repeat = False break return solver_repeat def flag_reveal_process(self, coordinate, unrevealed): """ Main process in which unrevealed blocks are found to reveal or flag. :param coordinate: :return: """ reveal = False flag = False num_unrevealed = len(unrevealed) real_block_value = self.servant.get_real_block_value(coordinate) # All mines accounted for. Reveal rest if real_block_value == 0: self.reveal_squares(unrevealed) self.blocks_to_remove.add(coordinate) reveal = True # All unrevealed are mines. Flag them elif real_block_value == num_unrevealed: for coord in unrevealed: self.win = self.mine.button_flag(None, coord) self.flags_planted += 1 self.blocks_to_remove.add(coordinate) flag = True return reveal, flag def confined_mine_process(self, coordinate, unrevealed): """ Check if a mine is enclosed in an area, and if surrounding blocks can be flagged or revealed. :param coordinate: :return: """ reveal = False flag = False mines_in_area = self.servant.get_real_block_value(coordinate) # starts with first block, then whittles down similar_neighbors = set( self.servant.get_exposed_neighbor_coords(unrevealed[0])) for block in unrevealed[1:]: # gets the neighbors of the next block block_neighbors = self.servant.get_exposed_neighbor_coords(block) similar_neighbors.intersection_update(block_neighbors) # Now we have a list of shared neighbors. We need to check them all. for neighbor in similar_neighbors: real_block_value = self.servant.get_real_block_value(neighbor) # starts as full blocks_out_of_area = set( self.servant.get_unrevealed_blocks(neighbor)) # Gets rid of blocks in unrevealed blocks_out_of_area.difference_update(unrevealed) num_outside = len(blocks_out_of_area) if num_outside > 0: # Flag blocks outside of area. if real_block_value == num_outside + mines_in_area: for block in blocks_out_of_area: self.win = self.mine.button_flag(None, block) self.flags_planted += 1 flag = True # Reveal blocks outside area if real_block_value == mines_in_area: self.reveal_squares(blocks_out_of_area) reveal = True return reveal, flag def shared_mine_process(self, coordinate): """ Loop through all neighbors and check if they share mines. :return: """ flag = False reveal = False neighbor_blocks = self.servant.get_neighbor_blocks(coordinate) real_block_value = self.servant.get_real_block_value(coordinate) # Loops through all neighbors for neighbor in neighbor_blocks: # Don't do checked neighbors if neighbor not in self.unchecked_blocks: continue # Find shared unrevealed blocks blocks_unrevealed = set( self.servant.get_unrevealed_blocks(coordinate)) neighbor_unrevealed = set( self.servant.get_unrevealed_blocks(neighbor)) # Get all unrevealed blocks shared by the neighbors shared_unrevealed = blocks_unrevealed.intersection( neighbor_unrevealed) num_shared = len(shared_unrevealed) neighbor_real_block_value = self.servant.get_real_block_value( neighbor) possible_area_mines = min(real_block_value, neighbor_real_block_value, num_shared) neighbors_outside_area = len(neighbor_unrevealed) - num_shared if neighbors_outside_area > 0 and \ neighbor_real_block_value == neighbors_outside_area + possible_area_mines: # Flag neighbor blocks not shared with coordinate for block in neighbor_unrevealed.difference(blocks_unrevealed): self.win = self.mine.button_flag(None, block) self.flags_planted += 1 flag = True # Check if the original block can reveal things. if real_block_value == possible_area_mines: # Reveal blocks not shared with neighbor self.reveal_squares( blocks_unrevealed.difference(neighbor_unrevealed)) reveal = True return reveal, flag
class MineSemiSolver: def __init__(self, unrevealed, neighbors, rows, columns): self.unrevealed = unrevealed self.neighbors = neighbors self.rows = rows self.columns = columns self.flags = [] self.servant = MineServant(self) self.exposed_field = self.neighbors.keys() self.possible_plays = [] self.color_coding = {'flag': 'green', 'bomb': 'red', 'unrevealed': 'yellow', 'zero': 'grey', 'number': 'white'} def choose(self): self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) for coordinate in self.exposed_field: print '*****COORDINATE*****', coordinate real_block_value = self.servant.get_real_block_value(coordinate) unrevealed = self.get_unrevealed_blocks(coordinate) if real_block_value == 0 and len(unrevealed) > 0: for index in unrevealed: self.unrevealed.remove(index) continue if real_block_value < 0 or len(unrevealed) == 0: continue # All possible locations of the mine(s). We need to only choose one. all_sets = combinations(list(unrevealed), real_block_value) # Validates all moves valid_moves = [] for move_set in all_sets: valid_move = self.negative_neighbor_move_validation(move_set) blocks_to_remove = unrevealed.difference(move_set) if valid_move: valid_move = self.isolated_neighbor_move_validation(coordinate, move_set, list(blocks_to_remove)) # Adds valid moves to the list if valid_move: valid_moves.append(move_set) try: chosen_placement = choice(valid_moves) print 'chosen move: ', chosen_placement except IndexError: print 'no moves!' else: # Remove blocks first for index in unrevealed: self.unrevealed.remove(index) # Then flag and remove blocks around neighbors for chosen in chosen_placement: self.flags.append(chosen) self.remove_neighbor_unrevealed(chosen) self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) self.servant.custom_pretty_print_field(self.exposed_field, unrevealed=self.unrevealed, flags=self.flags) def negative_neighbor_move_validation(self, move_set): """ Validates move by looking if neighbor values go negative :param move_set: :return: """ valid_move = True shared_neighbors = set([]) # Get all neighbors that border the move set for coordinate in move_set: shared_neighbors.update(self.servant.get_neighbor_blocks(coordinate)) # Loop through and check if any are too low. for neighbor in shared_neighbors: # Get number of flags that touch that neighbor neighbor_surrounding = self.get_surrounding_block_coords(neighbor) num_flags = len(neighbor_surrounding.intersection(move_set)) # Block value if that move were performed block_value = self.servant.get_real_block_value(neighbor) - num_flags # Any neighbor can invalidate the move if block_value < 0: valid_move = False break return valid_move def isolated_neighbor_move_validation(self, coordinate, move_set, blocks_to_remove): """ Validates moves by checking that another coordinate does not become isolated with no mines. :param coordinate: :param move_set: :param blocks_to_remove: :return: """ # Have to look at it from the perspective of the blocks to remove # print 'blocks to remove: ', blocks_to_remove # print 'move set: ', move_set # Innocent until proven guilty valid_move = True # Check doesn't need to be performed for zero blocks if len(blocks_to_remove) > 0: # Gets the neighbors attached to the blocks to remove neighbors = self.servant.get_neighbor_blocks(blocks_to_remove[0]) for block in blocks_to_remove[1:]: neighbors.update(self.servant.get_neighbor_blocks(block)) # Remove original coordinate neighbors.remove(coordinate) # Look at each neighbor and their unrevealed for neighbor_2 in neighbors: neighbor_block_value = self.servant.get_real_block_value(neighbor_2) # Skip the neighbor if neighbor has been dealt if neighbor_block_value == 0: continue neighbor_unrevealed = self.get_unrevealed_blocks(neighbor_2) # Remove unrevealed from the move for block in blocks_to_remove: if block in neighbor_unrevealed: neighbor_unrevealed.remove(block) # If there are fewer blocks than the value, then the move is bad if len(neighbor_unrevealed) < neighbor_block_value: valid_move = False # Layer 1: potential move set # Layer 2: Neighbors touching potential move set neighbors_layer_2 = set([]) # Gets all the neighbor blocks which we will need to slightly whittle down for flag_coord in move_set: neighbors_layer_2.update(self.servant.get_neighbor_blocks(flag_coord)) for neighbor_2 in neighbors_layer_2: neighbor_2_surrounding = self.get_surrounding_block_coords(neighbor_2) num_neighbor_2_flags = len(neighbor_2_surrounding.intersection(move_set)) # The block value if the move were performed layer_2_block_value = self.servant.get_real_block_value(neighbor_2) - num_neighbor_2_flags if layer_2_block_value == 0: # These will be revealed. # Layer 3: Unrevealed of layer 2 unrevealed_layer_3 = self.servant.get_unrevealed_blocks(neighbor_2) # Look at blocks neighboring these unrevealed # Layer 4: Neighbors of layer 3 neighbors_layer_4 = set([]) for unrevealed in unrevealed_layer_3: neighbors_layer_4.update(self.servant.get_neighbor_blocks(unrevealed)) # Remove the original block neighbors_layer_4.remove(neighbor_2) # Remove these unrevealed from the neighbors unrevealed for neighbor_4 in neighbors_layer_4: neighbor_4_surrounding = self.get_surrounding_block_coords(neighbor_4) num_neighbor_4_flags = len(neighbor_4_surrounding.intersection(move_set)) # The block value if the move were performed layer_4_block_val = self.servant.get_real_block_value(neighbor_4) - num_neighbor_4_flags # Don't need to worry about completed blocks if layer_4_block_val == 0: continue # Layer 5: Unrevealed of layer 4 unrevealed_layer_5 = self.get_unrevealed_blocks(neighbor_4) for unrevealed in unrevealed_layer_3: if unrevealed in unrevealed_layer_5: unrevealed_layer_5.remove(unrevealed) if len(unrevealed_layer_5) < layer_4_block_val: print 'invalid move' valid_move = False return valid_move def remove_neighbor_unrevealed(self, flag_coordinate): """ Removes all blocks around all neighbors with zero value. :param flag_coordinate: :return: """ neighbors = self.servant.get_neighbor_blocks(flag_coordinate) # Loop through all neighbors for neighbor in neighbors: block_value = self.servant.get_real_block_value(neighbor) # Remove all blocks if zero if block_value == 0: blocks_to_reveal = self.get_unrevealed_blocks(neighbor) for block in blocks_to_reveal: self.unrevealed.remove(block) def get_unrevealed_blocks(self, coordinate): """ Returns the coordinates for all unrevealed blocks around a coordinate. :param coordinate: :return: """ # Gets all surrounding coordinates unrevealed_coords = self.get_surrounding_block_coords(coordinate) # Reduces to what's in self.unrevealed unrevealed_coords.intersection_update(self.unrevealed) return unrevealed_coords # TODO: Can be inherited from Minesweeper def get_num_flag_neighbors(self, coordinate): """ Returns number of flags around the coordinate :param coordinate: :return: """ # Gets all surrounding coordinates flag_coords = self.get_surrounding_block_coords(coordinate) # Reduces to what's in self.flags flag_coords.intersection_update(self.flags) # Gets the length of that flag_neighbors = len(flag_coords) return flag_neighbors # TODO: Can be inherited from Minesweeper def get_surrounding_block_coords(self, coordinate): """ Gets the coordinates for all blocks surrounding the desires coords. :param coordinate: :return: """ # Will choose 0 if coordinate is 0 start_row = max(0, coordinate[0] - 1) # Will choose rows - 1 if coordinate is rows - 1 end_row = min(coordinate[0] + 1, self.rows - 1) start_column = max(0, coordinate[1] - 1) end_column = min(coordinate[1] + 1, self.columns - 1) # Create list of lists for the range surrounding_blocks = [(row, column) for row in range(start_row, end_row + 1) for column in range(start_column, end_column + 1)] return set(surrounding_blocks)