0

For my AI class, we were assigned to write a solver for the 8 squares puzzle. My program works for cases with few moves to achieve a solution (up to 9), but much more than that causes infinite recursion. I'm storing the previous states to prevent repetition of the same 2 states, so I'm not sure what's wrong. Can one of you help me? Thanks.

class Queue:
    def __init__(self):
        self.content=[]
    def put(self, x):
        self.content.append(x)
    def empty(self):
        return len(self.content)==0
    def get(self):
        return self.content.pop(0)

class Board:
    def __init__(self, b, m=[]):
        self.board=b
        self.moves=m
        
    def clone(self):
        asdf=Board(list(self.board),list(self.moves))
        return asdf
    
    def swap(self, a, b):
        temp=self.board[b]
        self.board[b]=self.board[a]
        self.board[a]=temp
        
    def findblank(self):
        for i in range(9):
            if(self.board[i]==0):
                return i
        return 9
    
    def up(self):
        asdf=self.clone()
        a=asdf.findblank()
        b=a-3
        if a>=3:
            asdf.swap(a, b)
            asdf.moves.append("u")
            return asdf
        else:
            return self.clone()

    def down(self):
        asdf=self.clone()
        a=asdf.findblank()
        b=a+3
        if a<6:
            asdf.swap(a, b)
            asdf.moves.append("d")
            return asdf
        else:
            return self.clone()

    def left(self):
        asdf=self.clone()
        a=asdf.findblank()
        b=a-1
        if b%3 != 2:
            asdf.swap(a, b)
            asdf.moves.append("l")
            return asdf
        return self.clone()

    def right(self):
        asdf=self.clone()
        a=asdf.findblank()
        b=a+1
        if b%3 != 0:
            asdf.swap(a, b)
            asdf.moves.append("r")
            return asdf
        return self.clone()

    def check(self):
        solutions=[[0,1,2,3,4,5,6,7,8],[1,2,3,8,0,4,7,6,5],[1,2,3,4,5,6,7,8,0]]
        if self.board in solutions:
            return True
        return False

def bfs(q,seen=[]):
    if(q.empty()):
        print("No solution")
        print("Iterations:")
        print(len(seen))
        return Board([0,0,0,0,0,0,0,0,0])
    
    pos=q.get()
    if pos.check():
        print(len(seen))
        return pos
    
    seen.append(pos.board)
    
    if len(pos.moves)==0:
        lastmove="no."
    else:
        lastmove=pos.moves[-1]
          
    if (pos.up().board!=pos.board and lastmove!='d' and (pos.up().board not in seen)):
        q.put(pos.up())
              
    if (pos.down().board!=pos.board and lastmove!='u' and (pos.down().board not in seen)):
        q.put(pos.down())
              
    if (pos.left().board!=pos.board and lastmove!='r' and (pos.left().board not in seen)):
        q.put(pos.left())
              
    if (pos.right().board!=pos.board and lastmove!='l' and (pos.right().board not in seen)):
        q.put(pos.right())
        
    return bfs(q,seen)

def solve(board):
    q=Queue()
    q.put(board)
    return bfs(q)

board=Board([0,7,8,3,6,2,4,5,1])

solved=solve(board)
print(board.board[0],board.board[1],board.board[2])
print(board.board[3],board.board[4],board.board[5])
print(board.board[6],board.board[7],board.board[8])
print("to")
print(solved.board[0],solved.board[1],solved.board[2])
print(solved.board[3],solved.board[4],solved.board[5])
print(solved.board[6],solved.board[7],solved.board[8])

print("Number of Steps:")
print(len(solved.moves))

print("Steps:")
print(solved.moves)

SOLVED: There's nothing wrong with the code; Python's max call stack size is just too small. I made it iterative, and it works fine. The central problem was that the program has an exponential time complexity, and making it iterative (with a 'while True:') solves that problem.

Community
  • 1
  • 1
Carrara
  • 1
  • 3
  • 1
    `def bfs(q,seen=[]):` is a definite code smell, see: [“Least Astonishment” in Python: The Mutable Default Argument](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) – Łukasz Rogalski Sep 18 '16 at 22:22
  • @ŁukaszRogalski Thanks; that's definitely useful to know, but I don't think that's the cause of the problem. I'm using '[new state] in seen', so duplicates should have no effect. – Carrara Sep 18 '16 at 22:26
  • Your code makes quite a lot of clones and you call the functions multiple times when perhaps you could call it once and save the value e.g. `if (pos.right().board!=pos.board and lastmove!='l' and (pos.right().board not in seen)): q.put(pos.right())`. There are 3 calls to `right` there each of which make a clone. It doesnt help with the recursion issue but might make it a fair bit quicker if you only called `right` once. – Paul Rooney Sep 19 '16 at 00:53

0 Answers0