예제 #1
0
 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'
     }
예제 #2
0
 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'}
예제 #3
0
 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
예제 #4
0
 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
예제 #5
0
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
예제 #6
0
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
예제 #7
0
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)
예제 #8
0
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)