Ariya Reddy
Ariya Reddy

Reputation: 45

Minesweeper: Reveal Surrounding Blocks Function Freezes

I'm trying to make a function for a minesweeper game I'm making. This function's purpose is to reveal an element given the x and y (where it is located). This is probably not the most elegant way to accomplish it, but I'm making a list of ['-'] for each tile titled newField. A '-' represents a hidden block (where you don't know if its a bomb or how many bombs are surrounding it). Then, I change one element from the newField to equal its corresponding block from the listField (which is a list of lists. Each list within it represents a row). An X represents a bomb and the numbers represent how many bombs surround in.

In minesweeper, when a block with zero bombs surrounding it is revealed, it's surrounding blocks are also revealed. I've made a functioned titled revealSurroundings that accomplishes this. When I run revealSurroundings in the function revealElement as I do below, it freezes my computer. However, if I run revealSurroundings outside of the function revealElement it works fine.

If anyone has any advice on how to solve this issue and/or make it more efficient(because I know the method I'm using is very expensive), please let me know.

wl = 10

listField = 
    [['1', '1', '0', '0', '0', '0', '0', '0', '1', 'X'], 
    ['X', '2', '2', '2', '2', '2', '2', '1', '1', '1'], 
    ['1', '2', 'X', 'X', '2', 'X', 'X', '2', '2', '2'], 
    ['1', '2', '3', '2', '2', '2', '3', '4', 'X', 'X'], 
    ['1', 'X', '1', '0', '0', '1', '3', 'X', 'X', '3'], 
    ['1', '2', '2', '1', '0', '1', 'X', 'X', '4', '2'], 
    ['2', '3', 'X', '1', '0', '1', '2', '2', '2', 'X'], 
    ['X', 'X', '2', '1', '1', '1', '1', '0', '1', '1'], 
    ['4', '5', '4', '3', '3', 'X', '2', '1', '0', '0'], 
    ['X', 'X', 'X', 'X', 'X', '3', 'X', '1', '0', '0']]
hiddenField = ['-' for i in range(wl*wl)]

def removeN(ls, n, iterations):
  items = ls
  try:
    for i in range(iterations):
      items.remove(n)
  except:
    pass
  return items

def revealElement(wl, x, y, userField):
  yReal = y-1
  xReal = x-1
  newField = list(userField)
  removeN(newField, '\n', wl-1)
  newField[yReal*wl + xReal] = listField[yReal][xReal]
  if newField[yReal*wl + xReal] == '0':
    revealSurroundings(wl, x, y, userField)
  for i in range(wl-1, 0, -1): # go backwards
      newField.insert(wl*i, '\n')

  return "".join(newField) # make it a string

def revealSurroundings(wl, x, y, userField):
  yReal = y-1
  xReal = x-1
  newField = userField
  try:
    newField = revealElement(wl, x+1, y, newField)
  except: 
    pass
  #right
  try:
    if xReal != 0:
      newField = revealElement(wl, x-1, y, newField)
  except:
    pass
  #left
  try:
    if yReal != 0:
      newField = revealElement(wl, x, y-1, newField)
  except:
    pass
  #up
  try:
    newField = revealElement(wl, x, y+1, newField)
  except:
    pass
  #down
  try:
    if yReal != 0:
      newField = revealElement(wl, x+1, y-1, newField)
  except:
    pass
  #upper-right
  try:
    if yReal != 0 and xReal != 0:
      newField = revealElement(wl, x-1, y-1, newField)
  except:
    pass
  #upper left
  try:
    newField = revealElement(wl, x+1, y+1, newField)
  except:
    pass
  #bottom-right
  try:
    if  xReal != 0:
      newField= revealElement(wl, x-1, y+1, newField)
  except:
    pass
  #bottom-left
  return newField

print revealSurroundings(10, 7, 2, hiddenField)

Upvotes: 3

Views: 145

Answers (2)

ZeOnlyOne
ZeOnlyOne

Reputation: 179

The problem seems to be that when you run the revealSurroundings within revealElement, you're creating a never-ending loop.

When you run revealElement, if the element is 0 the function revealSurroundings is run. Within revealSurroundings you run revealElement. If new element revealed is also a zero, it once again runs revealSurroundings and detects the zero from the first iteration of revealElement.

This begins an infinite loop that never ends. I suggest adding another conditional in revealSurroundings, such as checking to see if you already revealed the characters next to it through a simple if statement. I would also recommend re-writing the code altogether because of what @gorlen stated.

Upvotes: 2

ggorlen
ggorlen

Reputation: 56965

This logic seems like overkill for a basic Minesweeper reveal algorithm. A few thoughts:

  • Minesweeper grids are stateful and consist of multiple variables and functions. A class is the typical construct for encapsulating all of these logically connected variables and functions in a single entity. You can use the __str__ method to print a board and pass the grid into the initializer. There's no need to stringify the grid for algorithmic purposes--leave it as a list internally.
  • Keeping squares in two separate lists for visible and non-visible isn't scalable. If you need to add a third property, then you have to create yet another 2d list. Consider a Tile class/recordtype/dict with properties for visible, status/contents. This lets you store all necessary grid data in one data structure.
  • Variable names are unclear throughout. wl must be the board size, but this is redundant information since lists already have a len property that's guaranteed consistent and is semantically meaningful.
  • Use snake_case instead of camelCase for all variables and functions. UpperCamelCase for class names. See PEP-8.
  • Indent 4 spaces; Python has no end keyword or braces, so anything less than 4 spaces makes it difficult to disambiguate blocks.
  • Don't use try/except blocks for logic that's easily converted to conditionals, especially if you're using Pokemon exceptions. You may inadvertently suppress errors other than those you intend to, and it's an abuse of a construct that's not designed for general-purpose control flow.
  • Use iterables and loops instead of long sequences of hand-typed conditionals, which are difficult to follow and prone to typo bugs.
  • Converting between 1-indexed "human" coordinates and 0-indexed coordinates as done with yReal = y-1; xReal = x-1 is likely causing confusion because it's performed on every frame recursively. If you have to do this, move the logic out of the algorithm and normalize/denormalize as close to the IO interface as possible.

Here's a re-write suggestion:

class Minesweeper:
    class Tile:
        def __init__(self, mark, visible=False):
            self.mark = mark
            self.visible = visible

        def __str__(self):
            return self.mark if self.visible else "-"

    neighbors = [(x, y) for x in range(-1, 2) 
                        for y in range(-1, 2) if x or y]

    def __init__(self, field):
        self.field = [[self.Tile(x) for x in row] for row in field]

    def in_bounds(self, x, y):
        return y >= 0 and y < len(self.field) and \
               x >= 0 and x < len(self.field[y])

    def reveal(self, x, y):
        if self.in_bounds(x, y) and not self.field[y][x].visible and \
          self.field[y][x].mark == "0":
            self.field[y][x].visible = True

            for dx, dy in self.neighbors:
                self.reveal(x + dx, y + dy)

    def __str__(self):
        return "".join("".join(map(str, row)) + "\n" for row in self.field)

if __name__ == "__main__":
    field = [
        "00000",
        "12210",
        "1XX10",    
    ]
    board = Minesweeper(field)
    print board
    board.reveal(3, 0)
    print board

Output:

-----
-----
-----

00000
----0
----0

Upvotes: 1

Related Questions