0

I'm working on a project to build a perfect maze. I have a class Maze and a class Cell which represents each square in a maze. In my Cell class, I have four Boolean variable(north, south, east, west) to represent whether there is a wall at north or south.. of cell. There is also a boolean variable named visit to check if the cell has been visited. Here is my code for the init() for Cell class.

def __init__(self):
    self.north = True
    self.south = True
    self.east = True
    self.west = True
    self.visit = False

And for the Maze class, I have self.maze(a bunch of Cell) and the self.size = N(build a N*N maze). Here is the init() for class Maze:

def __init__(self, N):

    self.size = N
    self.maze = [[i for i in range(N + 2)] for i in range(N + 2)]

    for r in range(self.size + 2):
        for c in range(self.size + 2):
            self.maze[r][c] = Cell()

While I'm updating the index of maze, I wrote two function to check whether the newX and newY is in the range of 1 <= x <= self.size and 1 <= y <= self.size, also whether the cell has been visited. Here is the code:

def in_range(self, x, y):

    if 1 <= x <= self.size and 1 <= y <= self.size:
        return True
    else:
        return False

def is_valid(self, x, y):

    if not self.maze[x][y].getVisit() and self.in_range(x,y):
        return True
    else:
        return False

After all this, I wrote the main structure:

def walk(self, s, x, y):

    neighbor = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]


    if s.size() == self.size**2: return

    else:

        while True:
            new = choice(neighbor)#choice() is import from random
            #print(self.is_valid(new[0], new[1]))
            if self.is_valid(new[0], new[1]):break
            else:
                if len(neighbor) != 0:
                    neighbor.remove(new)
                    new = choice(neighbor)
                else:
                    temp = s.pop(s)
                    self.walk(s, temp[0], temp[1])

                break
    print(new)

However, running my code stills give me index that is not between 1 and self.size. I couldn't figure out why, I think my checking algorithm works fine. Here is what I got:

>>> ================================ RESTART 

================================
>>> 
>>> a = Maze(5)
>>> a.search()
1 2
(1, 3)
(2, 3)
(2, 4)
(1, 4)
(2, 4)
(2, 5)
(3, 5)
(4, 5)
(4, 4)
(4, 3)
(3, 3)
(3, 2)
(2, 2)
(2, 1)
(1, 1)
(0, 1)
(-1, 1)
(0, 1)
(1, 1)
(0, 1)
(-1, 1)
(-1, 2)
(-1, 1)
(0, 1)

Can someone help me out? plz, really appreciate!

smci
  • 32,567
  • 20
  • 113
  • 146
TIANQI QI
  • 9
  • 1
  • 2
    Is it [build-a-perfect-maze](http://stackoverflow.com/questions/29450400/build-a-perfect-maze-recursively-in-python) week again? – Bas Swinckels Apr 05 '15 at 07:42
  • Why do you use `range(N + 2)` everywhere? That will create an (N+1)x(N+1) maze. Just create a [numpy.array](http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html) of Cell. – smci Apr 05 '15 at 08:28
  • Glad to help. Could you please post your class code for the Maze and the Cell? – Jose Buraschi Apr 05 '15 at 09:11
  • And what is `if s.size() == self.size**2: return` checking for? How is that a termination condition? Unless you're trying to randomly discover paths of length N**2 (but they're not necessarily loops or complete traversals). – smci Apr 05 '15 at 09:13
  • Coz the Prof said to build a N+2 maze to avoid some tedious problem. To be frankly, I don't know the reason either. – TIANQI QI Apr 05 '15 at 15:52
  • class MyStack: def __init__(self): self.s = [] def push(self, item): self.s.insert(0, item) def pop(self): return self.s.pop(0) def isEmpty(self): return True if len(self.s) == 0 else False def size(self): return len(self.s).
    I have this class MyStack, which is `s` represents for. Stack keeps track what cell(x, y) I've been visited and `s.size()` return the length of the stack. If it equals to n**2, then I know I visited all the cell in maze, so I can terminate the recursion.
    – TIANQI QI Apr 05 '15 at 17:34
  • @smci I try to formatting my comment. But looks like the code part didn't work out. So sorry for the mess. – TIANQI QI Apr 05 '15 at 17:46

2 Answers2

0

In Python range starts with 0 if you don't specify another start position. Thus, when you initialize your maze, you have cells in (0, *) and (*, 0):

for r in range(self.size + 2):
    for c in range(self.size + 2):
        self.maze[r][c] = Cell()

When you compute your neighbors you don't check that you don't get out:

neighbor = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]

And it's only after having assigned new to such a coordinates that you check it is valid. But then, if it is not valid and there is no neighbors, you're calling walk again. You should try to check for in_range independently of visit.

Also your loop can only run once because either new is valid and you break out of the loop or it is not valid and you break out of the loop.

Finally, you forgot to give us the search function and what s is (and s.pop(s) looks funny as usually the argument of pop is an index, not the sequence again).

Francis Colas
  • 3,459
  • 2
  • 26
  • 31
0

You could have found this by looking closely at walk(), or splattering it with print statements to trace things. Things are going wrong at the extremities of your grid (when either x or y == 0 or N-1). Unless you're allowing wraparound, I assume your grid initialization puts walls on the north of the grid(y==0), south, east and west. By the way, your grid has two unnecessary rows and columns: if you want N cells, numbered O..(N-1), then use range(N), not range(N+2), Or else use range(1,N+1). (Do you pad any unneeded cells with valid==False?)

Anyway, walk() first picks a random neighbor cell and breaks out of the while-loop if it is a valid cell. But if it isn't, it cheerfully picks any remaining neighbor cell (regardless whether valid) and soldiers on. That's your bug. Always test validity. If it has run out of valid neighbor cells, it backtracks by doing s.pop(s). s.pop(s) also looks like a bug. Why not s.pop()? Btw, call the variable path instead of s.

Also, this is only checking the coords for validity; you never actually check if you've visited that cell before, so there's no end condition to your recursive backtracking; you could very easily go in loops.

You could help yourself a lot if you refactored walk() into using a separate generator get_random_valid_neighbor() which returns randomly-chosen neighbors already tested as valid (and None when you run out of neighbors).

def walk(self, s, x, y):

...
      while True:
            new = choice(neighbor)
            if self.is_valid(new[0], new[1]):break # tested for validity
            else:
                if len(neighbor) != 0:
                    neighbor.remove(new)
                    new = choice(neighbor) # BUG: never tested for validity
                ...
smci
  • 32,567
  • 20
  • 113
  • 146