简体   繁体   中英

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)

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.

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 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM