Search code examples
pythonrecursionminesweeper

Minesweeper code opening the zero boxes without max recursion depth


I am making Minesweeper in Python. I have all of the code done and working, but one feature I would like to add is that when you open a tile with zero mines nearby, it opens all of the squares around it. The problem stems from the fact that if any of the boxes I open are also zero, they also need to open all of the boxes around them. I believe I have a function to do this, but the max recursion depth is always reached. I tried raising the limit to probably higher than I should have, but I still got the error. I was wondering if their was a different way to do this without so much recursion. Thanks for any help, here is my current code:

def open_zero(x):
    # For opening the boxes with no nearby mines
    # First, opens the box the user clicked on
    button_list[x].config(bg="#90949b")
    # Then opens all nearby boxes through the box_open function
    # I need to run these values through the function so that if they are
    # zero, they will open all nearby tiles as well.
    # This causes too much recursion.
    box_open(x+1)
    box_open(x-1)
    box_open(x+width)
    box_open(x+width+1)
    box_open(x+width-1)
    box_open(x-width)
    box_open(x-width-1)
    box_open(x-width+1)

  def box_open(x):
      if box_list[x] == "M":
          # Checks if the block is a mine
          button_list[x].config(image=photo_mine, relief = SUNKEN)
          # Stops if it was a mine
          button_frame.destroy()
          all_code()
      elif box_list[x] == 0:
          # If there are no nearby mines, open all nearby tiles.
          open_zero(x)
      else:
          # If not a mine, change button text to the amount of nearby mines.
          button_list[x].config(text=box_list[x], bg="#90949b")

Hopefully you can understand my code from this snippet. It may not be possible the way I have coded it, but if anyone has any ideas, I would love to hear them. Thank you for any help!


Solution

  • You can use a queue. In Python, this may come in the form of a list. Use .append() to enqueue an item and .pop() to dequeue an item. (Note that you don't need to use a queue. You can use a stack or a plain list, but a queue will simulate cells spreading from the centre of the click, which may help if you have some neat animation in mind.)

    ##  TODO check over this implementation
    def is_open(x):
        return button_list[x].bg == "#90949b"
    
    
    def open_zero(x):
        button_list[x].config(bg="#90949b")
    
        queue = [x]                     ##  init
    
        while queue:
            curr = queue.pop(0)         ##  dequeue cells here
    
            if is_open(curr):           ##  STOPPING CONDITION    [1]
                continue
    
            box_open(curr)
    
            if box_list[curr] != 0:     ##  checks if curr is a zero-cell, you've already implemented this previously 👍
                continue
    
    
            ##  enqueue nearby cells here
    
            # if curr >= width:         ##  STOPPING CONDITION(s) [2]
            queue.append(curr+1)        ##  TODO check curr isn't on the right edge
            queue.append(curr-1)        ##  TODO check curr isn't on the left edge
            queue.append(curr+width)    ##  TODO check curr isn't on the bottom edge
            queue.append(curr+width-1)  ##  ... 
            queue.append(curr+width+1)  ##  ... the rest has been left as an
            queue.append(curr-width)    ##  ... exercise for the reader
            queue.append(curr-width-1)
            queue.append(curr-width+1)
    
            ##  NOTE: without STOPPING CONDITION(s), recursion or the while-loop
            ##  will never terminate
            ##  except for the max-recursion depth 🙄
    

    Note that, one reason why Python is upset with the recursion in your code, is that you're not providing any stopping or terminating conditions. I can't stress how important these are. I've partially-implemented one of the conditions (is_open(x), which checks if cell x is open or not).

    Since this is Minesweeper, I'm assuming you're making this on a matrix/grid (but storing buttons in a list). Still, you need to check the boundaries of the grid, or else your recursion will jump through the grid walls or do other weird things.

    (Thought experiment: Imagine me clicking on the bottom-left cell of the grid. This will trigger box_open(x). This will in turn trigger box_open(x-1) or queue.append(curr-1)... but these will open up the rightmost cell on the second row from the bottom. This is unexpected. You need to check that the cell isn't on the left edge... before doing box_open(x-1).)

    I am highly confident that your original code can work, but one of the important key elements of recursion (or any looping) is to provide stopping/terminating conditions. That should be everyone's main takeaway here (aside from the implementation of the queue).