My Iterative Deepening Depth-First Search (IDDFS) of the 8 puzzle game returns a path length greater than my BFS. The total number of visited Nodes is 42 for the IDDFS while my BFS returns a total of 26. Is there something wrong with my IDDFS algorithm or is that just how it should behave???
import collections
import queue
import time
import itertools
class Node:
def __init__(self, puzzle, last=None):
self.puzzle = puzzle
self.last = last
#property
def seq(self): # to keep track of the sequence used to get to the goal
node, seq = self, []
while node:
seq.append(node)
node = node.last
yield from reversed(seq)
#property
def state(self):
return str(self.puzzle.board) # hashable so it can be compared in sets
#property
def isSolved(self):
return self.puzzle.isSolved
#property
def getMoves(self):
return self.puzzle.getMoves
class Puzzle:
def __init__(self, startBoard):
self.board = startBoard
#property
def getMoves(self):
possibleNewBoards = []
zeroPos = self.board.index(0) # find the zero tile to determine possible moves
if zeroPos == 0:
possibleNewBoards.append(self.move(0,1))
possibleNewBoards.append(self.move(0,3))
elif zeroPos == 1:
possibleNewBoards.append(self.move(1,0))
possibleNewBoards.append(self.move(1,2))
possibleNewBoards.append(self.move(1,4))
elif zeroPos == 2:
possibleNewBoards.append(self.move(2,1))
possibleNewBoards.append(self.move(2,5))
elif zeroPos == 3:
possibleNewBoards.append(self.move(3,0))
possibleNewBoards.append(self.move(3,4))
possibleNewBoards.append(self.move(3,6))
elif zeroPos == 4:
possibleNewBoards.append(self.move(4,1))
possibleNewBoards.append(self.move(4,3))
possibleNewBoards.append(self.move(4,5))
possibleNewBoards.append(self.move(4,7))
elif zeroPos == 5:
possibleNewBoards.append(self.move(5,2))
possibleNewBoards.append(self.move(5,4))
possibleNewBoards.append(self.move(5,8))
elif zeroPos == 6:
possibleNewBoards.append(self.move(6,3))
possibleNewBoards.append(self.move(6,7))
elif zeroPos == 7:
possibleNewBoards.append(self.move(7,4))
possibleNewBoards.append(self.move(7,6))
possibleNewBoards.append(self.move(7,8))
else:
possibleNewBoards.append(self.move(8,5))
possibleNewBoards.append(self.move(8,7))
return possibleNewBoards # returns Puzzle objects (maximum of 4 at a time)
def move(self, current, to):
changeBoard = self.board[:] # create a copy
changeBoard[to], changeBoard[current] = changeBoard[current], changeBoard[to] # switch the tiles at the passed positions
return Puzzle(changeBoard) # return a new Puzzle object
def printPuzzle(self): # prints board in 8 puzzle style
copyBoard = self.board[:]
for i in range(9):
if i == 2 or i == 5:
print((str)(copyBoard[i]))
else:
print((str)(copyBoard[i])+" ", end="")
print('\n')
#property
def isSolved(self):
return self.board == [0,1,2,3,4,5,6,7,8] # goal board
class Solver:
def __init__(self, Puzzle):
self.puzzle = Puzzle
def IDDFS(self):
def DLS(currentNode, depth):
if depth == 0:
return None
if currentNode.isSolved:
return currentNode
elif depth > 0:
for board in currentNode.getMoves:
nextNode = Node(board, currentNode)
if nextNode.state not in visited:
visited.add(nextNode.state)
goalNode = DLS(nextNode, depth - 1)
if goalNode != None: # I thought this should be redundant but it never finds a soln if I take it out
if goalNode.isSolved: # same as above ^
return goalNode
for depth in itertools.count():
visited = set()
startNode = Node(self.puzzle)
print(startNode.isSolved)
goalNode = DLS(startNode, depth)
if goalNode != None:
if goalNode.isSolved:
return goalNode.seq
startingBoard = [7,2,4,5,0,6,8,3,1]
myPuzzle = Puzzle(startingBoard)
mySolver = Solver(myPuzzle)
start = time.time()
goalSeq = mySolver.IDDFS()
end = time.time()
counter = -1 # starting state doesn't count as a move
for node in goalSeq:
counter = counter + 1
node.puzzle.printPuzzle()
print("Total number of moves: " + str(counter))
totalTime = end - start
print("Total searching time: %.2f seconds" % (totalTime))
Related
this is sample code of 8 puzzle game which take two matrix initial and goal state .
class Node:
def __init__(self,data,level,fval):
""" Initialize the node with the data, level of the node and the calculated fvalue """
self.data = data
self.level = level
self.fval = fval
def generate_child(self):
""" Generate child nodes from the given node by moving the blank space
either in the four directions {up,down,left,right} """
x,y = self.find(self.data,'_')
""" val_list contains position values for moving the blank space in either of
the 4 directions [up,down,left,right] respectively. """
val_list = [[x,y-1],[x,y+1],[x-1,y],[x+1,y]]
children = []
for i in val_list:
child = self.shuffle(self.data,x,y,i[0],i[1])
if child is not None:
child_node = Node(child,self.level+1,0)
children.append(child_node)
return children
def shuffle(self,puz,x1,y1,x2,y2):
""" Move the blank space in the given direction and if the position value are out
of limits the return None """
if x2 >= 0 and x2 < len(self.data) and y2 >= 0 and y2 < len(self.data):
temp_puz = []
temp_puz = self.copy(puz)
temp = temp_puz[x2][y2]
temp_puz[x2][y2] = temp_puz[x1][y1]
temp_puz[x1][y1] = temp
return temp_puz
else:
return None
def copy(self,root):
""" Copy function to create a similar matrix of the given node"""
temp = []
for i in root:
t = []
for j in i:
t.append(j)
temp.append(t)
return temp
def find(self,puz,x):
""" Specifically used to find the position of the blank space """
for i in range(0,len(self.data)):
for j in range(0,len(self.data)):
if puz[i][j] == x:
return i,j
class Puzzle:
def __init__(self,size):
""" Initialize the puzzle size by the specified size,open and closed lists to empty """
self.n = size
self.open = []
self.closed = []
def accept(self):
""" Accepts the puzzle from the user """
puz = []
for i in range(0,self.n):
temp = input().split(" ")
puz.append(temp)
return puz
def f(self,start,goal):
""" Heuristic Function to calculate hueristic value f(x) = h(x) + g(x) """
return self.h(start.data,goal)+start.level
def h(self,start,goal):
""" Calculates the different between the given puzzles """
temp = 0
for i in range(0,self.n):
for j in range(0,self.n):
if start[i][j] != goal[i][j] and start[i][j] != '_':
temp += 1
return temp
def process(self):
""" Accept Start and Goal Puzzle state"""
print("Enter the start state matrix \n")
start = self.accept()
print("Enter the goal state matrix \n")
goal = self.accept()
start = Node(start,0,0)
start.fval = self.f(start,goal)
""" Put the start node in the open list"""
self.open.append(start)
print("\n")
count=0
while True:
cur = self.open[0]
count=count+1
print("This Node number = \n", count)
print("")
print(" | ")
print(" | ")
print(" \\\'/ \n")
for i in cur.data:
for j in i:
print(j,end=" ")
print("")
""" If the difference between current and goal node is 0 we have reached the goal node"""
if(self.h(cur.data,goal) == 0):
break
for i in cur.generate_child():
i.fval = self.f(i,goal)
self.open.append(i)
self.closed.append(cur)
del self.open[0]
""" sort the opne list based on f value """
self.open.sort(key = lambda x:x.fval,reverse=False)
puz = Puzzle(3)
puz.process()
this code take initial state and goal state and start traversal and stop until
specific or reached goal state
i want to add limit of traversal to this code.so it itterate in
specific boundary either reached goal state or not
If I have a list class that can be initialized with a variable number of dimensions, how do I set an entry at the lowest level of the list with an element? (Also would like to know if my get method should work in theory)
I'm trying to simulate board games that use multiple dimensions (Can you even imagine 5-th dimensional chess? How about 17th?)
class Board():
DIMENSIONS = [8, 8]
#board and pieces have their respective rules.
def __init__(self, D=[8,8]):
if len(D) <= 0:
board = [None for i in range(D)]
else:
board = [None for i in range(D[0])]
for j in range(1,len(D)):
board = [board for i in range(D[j])]
def get(self, location):
try:
for coordinate in location:
result = board[coordinate]
return result
except:
print('Error: Cannot get coordinate')
return None
def set(self, location, piece):
try:
for coordinate in location:
result = self.board[coordinate]
result = piece
except:
print('Error: Cannot set coordinate')
def move(self, start, end):
x = self.get(start)
if x is not None:
for m, r in x.moves, x.rules:
if self.get(is_legitimate(self, start, m, r)) == end:
= x
pass
#Check alignment and position, if it's transformable react according to board rules.
#returns false if not, but returns location if legit.
def is_legitimate(self, start, move, rule):
location = start
forwardback = True
while move != 1:
primes = [2]
while move % primes[-1] == 0:
if forwardback:
location[len(primes) // 2]+=1
else:
location[len(primes) // 2]-=1
move = move % primes[-1]
if not self.bounds(location):
return False
primes.append(next_prime(primes))
forwardback = not forwardback
def bounds(self, location):
for coordinate, d in location, self.DIMENSIONS:
if coordinate < 0 or coordinate > d:
return False
return True
#using prime numbers?
def next_prime(primes):
if len(primes) == 0:
return 2
prev_result = 1
result = 2
while prev_result != result:
prev_result = result
for x in primes:
if result == x or result % x == 0:
result += 1
break
return result
Code is mostly rough draft, don't play just look.
I am trying to solve the 8 puzzle problem using BFS search, however, my code seems to get stuck in an infinite loop where it only moves the zero tile back and forth until the memory of the queue ends the program in an error.
import collections
import queue
class Node:
def __init__(self, puzzle, last=None):
self.puzzle = puzzle
self.last = last
#property
def seq(self): # to keep track of the sequence used to get to the goal
node, seq = self, []
while node:
seq.append(node)
node = node.last
yield from reversed(seq)
#property
def state(self):
return str(self) # hashable so it can be compared in sets
#property
def isSolved(self):
return self.puzzle.isSolved
#property
def getMoves(self):
return self.puzzle.getMoves
class Puzzle:
def __init__(self, startBoard):
self.board = startBoard
#property
def getMoves(self):
possibleNewBoards = []
zeroPos = self.board.index(0) # find the zero tile to determine possible moves
if zeroPos == 0:
possibleNewBoards.append(self.move(0,1))
possibleNewBoards.append(self.move(0,3))
elif zeroPos == 1:
possibleNewBoards.append(self.move(1,0))
possibleNewBoards.append(self.move(1,2))
possibleNewBoards.append(self.move(1,4))
elif zeroPos == 2:
possibleNewBoards.append(self.move(2,1))
possibleNewBoards.append(self.move(2,5))
elif zeroPos == 3:
possibleNewBoards.append(self.move(3,0))
possibleNewBoards.append(self.move(3,4))
possibleNewBoards.append(self.move(3,6))
elif zeroPos == 4:
possibleNewBoards.append(self.move(4,1))
possibleNewBoards.append(self.move(4,3))
possibleNewBoards.append(self.move(4,5))
possibleNewBoards.append(self.move(4,7))
elif zeroPos == 5:
possibleNewBoards.append(self.move(5,2))
possibleNewBoards.append(self.move(5,4))
possibleNewBoards.append(self.move(5,8))
elif zeroPos == 6:
possibleNewBoards.append(self.move(6,3))
possibleNewBoards.append(self.move(6,7))
elif zeroPos == 7:
possibleNewBoards.append(self.move(7,4))
possibleNewBoards.append(self.move(7,6))
possibleNewBoards.append(self.move(7,8))
else:
possibleNewBoards.append(self.move(8,5))
possibleNewBoards.append(self.move(8,7))
return possibleNewBoards # returns Puzzle objects (maximum of 4 at a time)
def move(self, current, to):
changeBoard = self.board[:] # create a copy
changeBoard[to], changeBoard[current] = changeBoard[current], changeBoard[to] # switch the tiles at the passed positions
return Puzzle(changeBoard) # return a new Puzzle object
def printPuzzle(self): # prints board in 8 puzzle style
copyBoard = self.board[:]
for i in range(9):
if i == 2 or i == 5:
print((str)(copyBoard[i]))
else:
print((str)(copyBoard[i])+" ", end="")
print('\n')
#property
def isSolved(self):
return self.board == [0,1,2,3,4,5,6,7,8] # goal board
class Solver:
def __init__(self, Puzzle):
self.puzzle = Puzzle
def solveBFS(self):
startNode = Node(self.puzzle)
myQueue = collections.deque([startNode])
visited = set()
visited.add(myQueue[0].state)
while myQueue:
currentNode = myQueue.pop()
# currentNode.puzzle.printPuzzle() # used for testing
if currentNode.puzzle.isSolved:
return currentNode.seq
for board in currentNode.getMoves:
nextNode = Node(board, currentNode)
if nextNode.state not in visited:
myQueue.appendleft(nextNode)
visited.add(nextNode.state)
startingBoard = [7,2,4,5,0,6,8,3,1]
myPuzzle = Puzzle(startingBoard)
mySolver = Solver(myPuzzle)
goalSeq = mySolver.solveBFS()
counter = -1 # starting state doesn't count as a move
for node in goalSeq:
counter = counter + 1
node.puzzle.printPuzzle()
print("Total number of moves: " + counter)
I thought adding each node to a set() would stop the code from getting caught in a loop. Is this not true?
#property
def state(self):
return str(self) # hashable so it can be compared in sets
This will return something that looks like <__main__.Node object at 0x02173A90>. The address is unique per Node object, so the state of two nodes with identical boards will still be considered distinct by the set.
Instead, try:
#property
def state(self):
return str(self.puzzle.board)
Now two Nodes with identical boards will be considered the same.
Also, change the last line of your script to
print("Total number of moves: " + str(counter))
Now you will get a result:
7 2 4
5 0 6
8 3 1
7 2 4
0 5 6
8 3 1
(snip)
1 0 2
3 4 5
6 7 8
0 1 2
3 4 5
6 7 8
Total number of moves: 26
I am trying to make a sudoku solver that solves boards very quickly. At the moment my solver works on easy boards but never terminates on harder boards. I believe it has something to do with my recursion because easy boards do not require recursion and hard boards do. Any help is appreciated.
import sys
def rowno(i):
return i // 9
def colno(i):
return i % 9
def boxno(i):
return (i // 9 // 3 )*3 + (i // 3) % 3
def isNeighbor(i, j):
if rowno(i) == rowno(j) or colno(i) == colno(j) or boxno(i) == boxno(j):
return True
else:
return False
def getFileName():
if sys.platform == "win32":
filename = input("Filename? ")
else:
filename = sys.argv[-1]
return filename
solutionlist = []
class Board(object):
def __init__(self, puzzle):
self.puzzle = puzzle
self.board = [Cell(int(value), idx) for idx, value in enumerate(puzzle)]
self.change = False
def printAll(self):
print [cell.candidates for cell in self.board]
#return str(" ")
def update(self):
self.change = False
l = [cell for cell in self.board if len(cell.candidates) == 1]
for i in l:
for j in xrange(81):
if isNeighbor(i.dex, j) and i.dex != j:
old = self.board[j].candidates
self.board[j].delCandidate(i.value)
if len(old) != len(self.board[j].candidates):
self.change = True
def toString(self):
str1 = ''.join(str(e.value) for e in self.board)
return str1
def solved(self):
for cell in self.board:
if len(cell.candidates) != 1:
return False
return True
def solve(self):
self.change = True
while self.change == True:
self.update()
if self.solved():
solutionlist.append(self.board)
return
l = [cell for cell in self.board if len(cell.candidates) > 1]
for i in l:
for j in i.candidates:
newBoard = Board(self.toString())
curLen = 12
curCell = -1
for u in l:
if len(u.candidates)<curLen:
curLen=len(u.candidates)
curCell = u.dex
for c in newBoard.board[curCell].candidates:
newBoard.board[curCell].candidates = [int(c)]
newBoard.board[curCell].value = int(c)
newBoard.solve()
return
def __repr__(self):
l = [cell.value for cell in self.board]
return str(l)
class Cell(object):
def __init__(self, value, dex):
self.value = value
self.dex = dex
if value == 0:
self.candidates = [1,2,3,4,5,6,7,8,9]
else:
self.candidates = [int(value)]
def __str__(self):
return str(self.value)
def delCandidate(self, value):
# deletes value from candidate list
#return self.candidate.remove(value);
self.candidates = [x for x in self.candidates if x != value]
if len(self.candidates) == 1:
self.value = self.candidates[0]
easy = "700583006006001405052006083300200958500078060648010300060802500003150072215600030"
twosol = "000805200800000401705040009000100702040000000006430000030900000010006080000000000"
hard = "040090008000000070060000120030020000005839060080600700050170600000043000003000200"
#easy solution: 794583216836721495152496783371264958529378164648915327967832541483159672215647839
b = Board(hard)
print b
b.solve()
print "end of the line"
for i in solutionlist:
print [cell.value for cell in i]
print "\n"
One major issue is the line for i in l: in the solve method. Since you're recursing, you only need to fill in one cell - the recursion will take care of the rest. So instead of for i in l:, just recurse on the one cell that is the best candidate (curCell):
l = [cell for cell in self.board if len(cell.candidates) > 1]
if len(l) > 0:
newBoard = Board(self.toString())
curLen = 12
curCell = -1
for u in l:
if len(u.candidates)<curLen:
curLen=len(u.candidates)
curCell = u.dex
for c in newBoard.board[curCell].candidates:
newBoard.board[curCell].candidates = [int(c)]
newBoard.board[curCell].value = int(c)
newBoard.solve()
After completely failing the minimax implementation for tic tac toe, I fail to see what's wrong. Right now, my AI just goes around in a circle...
import collections
class InvalidLocationError(Exception): pass
import copy
class Board(object):
def __init__(self, board=None):
if board is None:
self.clear()
else:
self._board = board[:]
def place(self, i, row, column):
if not ((0 <= row <= 2) and (0 <= column <= 2)):
raise InvalidLocationError("Invalid Location.")
if self._board[row][column]:
raise InvalidLocationError("There's already a piece there")
self._board[row][column] = i
return self.checkVictory()
def check(self, row, column):
return self._board[row][column]
def checkVictory(self, board=None):
if board is None:
board = self._board
draw = True
for i in xrange(3):
r = self.rowcount(i)
c = self.colcount(i)
if i < 3:
d = self.diagcount(i)
else:
d = {0: 0, 1: 0, 2: 0}
for j in xrange(1, 3):
if d[j] == 3 or r[j] == 3 or c[j] == 3:
return j
if r[0] > 0 or c[0] > 0:
draw = False
if draw:
return -1
return 0
def rowcount(self, row):
return collections.Counter(self._board[row])
def colcount(self, col):
return collections.Counter([self._board[i][col] for i in xrange(3)])
def diagcount(self, left=True):
if left:
a = [self._board[0][0], self._board[1][1], self._board[2][2]]
else:
a = [self._board[0][2], self._board[1][1], self._board[2][0]]
return collections.Counter(a)
def clear(self):
self._board = ([0, 0, 0], [0, 0, 0], [0, 0, 0])
def __str__(self):
return "\n".join(map(lambda x: " ".join(map(lambda y : str(y), x)), self._board))
#staticmethod
def flipPiece(p):
return int(not (p - 1)) + 1
class AI(object):
class Node(object):
def __init__(self, board, nextMove):
self.board = board
self.nextMove = nextMove
self.paths = []
self.score = None
template = self.board._board[:]
for r, row in enumerate(template):
for c, val in enumerate(row):
if val == 0:
template[r][c] = nextMove
self.paths.append(copy.deepcopy(template))
template[r][c] = 0
def __init__(self, mypiece, depth=8):
self.mypiece = mypiece
self.enemypiece = Board.flipPiece(mypiece)
self.depth = depth
def decide(self, board):
startNode = self.Node(board, self.mypiece)
best = self.minimax(startNode, self.depth)
for node in startNode.paths:
if node.value == best:
break
found = False
for row in xrange(3):
for col in xrange(3):
if board.check(row, col) != node.board.check(row, col):
found = True
break
if found:
break
print row, col
return row, col
def minimax(self, node, depth):
victory = node.board.checkVictory()
if victory:
if victory == self.mypiece:
h = 1
elif victory == -1:
h = 0
else:
h = -1
node.value = h
return h
if depth <= 0:
# h = self.heuristic(node.board, node.nextMove) # This is to the heuristic, which uses nextMove to evalate.
node.value = 0
return 0
h = -1
flip = Board.flipPiece(node.nextMove)
for i, board in enumerate(node.paths):
node.paths[i] = self.Node(Board(board), flip) # This is to the Node, which takes the nextMove of itself (which translates to the next next move from the current node)
score = self.minimax(node.paths[i], depth-1)
h = max(h, score) if node.nextMove == self.mypiece else min(h, score)
node.value = h
return h
Why is this happening?