One way to think about these sorts of search problems is a two pronged approach:
Can I find a path with 1 move? If not, try with 2 moves, 3 moves, etc. till you hit an upper bound and you decide to stop trying.
Instead of "searching," imagine a path is given to you; how would you check that it's a good path? The magic of SMT solving is that if you can write a program that verifies a given "alleged" solution is good, it can find you one that is indeed good.
The following is a solution to your problem following these lines of thought.
from z3 import *
Grid = [ ['T', 'T', 'T', 'T', 'T', 'T', 'T']
, ['T', ' ', ' ', ' ', ' ', ' ', 'T']
, ['T', ' ', 'A', 'O', ' ', 'O', 'T']
, ['T', 'O', ' ', ' ', ' ', ' ', 'T']
, ['T', ' ', ' ', 'O', 'O', 'C', 'T']
, ['T', ' ', ' ', ' ', ' ', ' ', 'T']
, ['T', 'T', 'T', 'T', 'T', 'T', 'T']
]
Cell, (Wall, Empty, Agent, Obstacle, Coin) = EnumSort('Cell', ('Wall', 'Empty', 'Agent', 'Obstacle', 'Coin'))
def mkCell(c):
if c == 'T':
return Wall
elif c == ' ':
return Empty
elif c == 'A':
return Agent
elif c == 'O':
return Obstacle
else:
return Coin
def grid(x, y):
result = Wall
for i in range (len(Grid)):
for j in range (len(Grid[0])):
result = If(And(x == IntVal(i), y == IntVal(j)), mkCell(Grid[i][j]), result)
return result
def validStart(x, y):
return grid(x, y) == Agent
def validEnd(x, y):
return grid(x, y) == Coin
def canMoveTo(x, y):
n = grid(x, y)
return Or(n == Empty, n == Coin, n == Agent)
def moveLeft(x, y):
return [x, If(canMoveTo(x, y-1), y-1, y)]
def moveRight(x, y):
return [x, If(canMoveTo(x, y+1), y+1, y)]
def moveUp(x, y):
return [If(canMoveTo(x-1, y), x-1, x), y]
def moveDown(x, y):
return [If(canMoveTo(x+1, y), x+1, x), y]
Dir, (Left, Right, Up, Down) = EnumSort('Dir', ('Left', 'Right', 'Up', 'Down'))
def move(d, x, y):
xL, yL = moveLeft (x, y)
xR, yR = moveRight(x, y)
xU, yU = moveUp (x, y)
xD, yD = moveDown (x, y)
xN = If(d == Left, xL, If (d == Right, xR, If (d == Up, xU, xD)))
yN = If(d == Left, yL, If (d == Right, yR, If (d == Up, yU, yD)))
return [xN, yN]
def solves(seq, x, y):
def walk(moves, curX, curY):
if moves:
nX, nY = move(moves[0], curX, curY)
return walk(moves[1:], nX, nY)
else:
return [curX, curY]
xL, yL = walk(seq, x, y)
return And(validStart(x, y), validEnd(xL, yL))
pathLength = 0
while(pathLength != 20):
print("Trying to find a path of length:", pathLength)
s = Solver()
seq = [Const('m' + str(i), Dir) for i in range(pathLength)]
x, y = Ints('x y')
s.add(solves(seq, x, y))
if s.check() == sat:
print("Found solution with length:", pathLength)
m = s.model()
print(" Start x:", m[x])
print(" Start y:", m[y])
for move in seq:
print(" Move", m[move])
break
else:
pathLength += 1
When run, this prints:
Trying to find a path of length: 0
Trying to find a path of length: 1
Trying to find a path of length: 2
Trying to find a path of length: 3
Trying to find a path of length: 4
Trying to find a path of length: 5
Found solution with length: 5
Start x: 2
Start y: 2
Move Down
Move Right
Move Right
Move Right
Move Down
So, it found a solution with 5 moves; you can chase it in your grid to see that it's indeed correct. (The numbering starts at 0,0 at the top-left corner; increasing as you go to right and down.) Additionally, you’re guaranteed that this is a shortest solution (not necessarily unique of course). That is, there are no solutions with less than 5 moves.
I should add that there are other ways to solve this problem without iterating, by using z3 sequences. However that’s even more advanced z3 programming, and likely to be less performant as well. For all practical purposes, the iterative approach presented here is a good way to tackle such search problems in z3.