How to find shortest path in X*Y grid - python

I have a grid N*M in Python.
where
"X" represents the border,
"1" represents current position,
"2" represents finish, and
"3" represents forbidden locations.
The last thing is that (imagine you're a car) you can go only straight or right.
Example 3x3 except borders:
[X,X,X,X,X]
[X,2,1,0,X]
[X,0,3,0,X]
[X,0,0,0,X]
[X,X,X,X,X]
Another example:
[X,X,X,X,X]
[X,2,0,0,X]
[X,3,3,1,X]
[X,X,X,X,X]
Or another:
[X,X,X,X,X,X,X]
[X,0,2,0,0,0,X]
[X,0,3,0,3,0,X]
[X,0,0,0,0,0,X]
[X,0,3,0,3,0,X]
[X,0,3,1,3,0,X]
[X,X,X,X,X,X,X]
Do you have any suggestion for concrete script that will find the fastest way?
And if there's not, print("No solution")?
Thank you very much!
To help you understand these situations:
pictures of examples 1 and 2

You can use this code, which based on Grap theory:
grid = [
['X','X','X','X','X','X','X'],
['X',2,0,0,0,0,'X'],
['X',0,3,0,3,0,'X'],
['X',0,0,0,0,0,'X'],
['X',0,3,0,3,0,'X'],
['X',0,3,1,3,0,'X'],
['X','X','X','X','X','X','X']
]
INF=1000000
def is_valid(grid:list):
ends = 0
starts = 0
for line in grid:
for el in line:
if el==2:
ends+=1
if el==1:
starts+=1
return False if ends!=1 or starts!=1 else True
def redef_grid(grid:list):
''' Deleting from grid all 'X' '''
g=grid.copy()
if 'X' in g[0]: del g[0]
if 'X' in g[-1]: del g[-1]
for line in g:
while 'X' in line:
line.remove('X')
return g
def uni_index(grid:list, pos:tuple):
return len(grid[0])*pos[0]+pos[1]
def real_index(grid:list, index:int):
row =index//len(grid[0])
col=index-row*len(grid[0])
return (row, col)
def get_neibs(grid:list, index:int):
def get_vertical(grid:list, index:int):
return [index+k for k in [len(grid[0]), -len(grid[0])] if index+k>=0 and index+k<uni_index(grid, (len(grid)-1, len(grid[0])-1))]
def get_horizontal(grid:list, index:int):
return [index+k for k in [1, -1] if index+k>=(index//len(grid[0]))*(len(grid[0])) and index+k<=uni_index(grid, (index//len(grid[0]), len(grid[0])-1))]
return get_vertical(grid, index)+get_horizontal(grid, index)
def get_matrix(grid:list):
last_el = uni_index(grid, (len(grid)-1, len(grid[0])-1))
elements=len(grid[0])*len(grid)
matrix=[[INF for _ in range(elements)] for _ in range(elements)]
for index in range(last_el):
neibs=get_neibs(grid, index)
neibs=[neib for neib in neibs if grid[real_index(grid, neib)[0]][real_index(grid, neib)[1]]!=3]
for neib in neibs:
matrix[index][neib]=1
return matrix
def get_start(grid:list):
for i in range(len(grid)):
try:
return (i, grid[i].index(1))
except ValueError:
pass
def get_end(grid:list):
for i in range(len(grid)):
try:
return (i, grid[i].index(2))
except ValueError:
pass
def Dijkstra(N, S, matrix):
valid = [True]*N
weight = [1000000]*N
weight[S] = 0
for i in range(N):
min_weight = 1000001
ID_min_weight = -1
for i in range(N):
if valid[i] and weight[i] < min_weight:
min_weight = weight[i]
ID_min_weight = i
for i in range(N):
if weight[ID_min_weight] + matrix[ID_min_weight][i] < weight[i]:
weight[i] = weight[ID_min_weight] + matrix[ID_min_weight][i]
valid[ID_min_weight] = False
return weight
grid=redef_grid(grid)
if is_valid(grid):
matrix=get_matrix(grid)
paths = Dijkstra(len(grid)*len(grid[0]), uni_index(grid, get_start(grid)), matrix)
shortest_path = paths[uni_index(grid, get_end(grid))]
if shortest_path==INF: shortest_path='No solution'
print(shortest_path)
else: print('Invalid grid')

This was a fun one. A brute force approach calculates all possible paths through the system, and finally finds minimum path length of those ending in 2. Note that init just returns a valid matrix with which to work. If no solution is found for the matrix, None is returned by solve function.
BLOCK=3
FINAL=2
TERMINATE=None
INITIAL=1
PATH=0
def init(n,m):
# returns array with essential components, but not necessarily with solution
import numpy
import random
while True:
try:
a=numpy.zeros((n,m),dtype=int)
for i in range(n):
for j in range(m):
t=INITIAL
# initialize to anything but INITIAL
while t==INITIAL: t=numpy.random.randint(PATH,BLOCK)
a[i,j]=t
c=random.choice(list(zip(*(a==PATH).nonzero())))
a[c]=INITIAL
finals=list(zip(*(a==FINAL).nonzero()))
c=random.choice(finals)
for i in finals:
if i!=c: a[i]=BLOCK
break
except:
continue
return a
def possible_moves(a,pos):
n,m=a.shape
test_moves=[(0,1),(1,0),(-1,0),(0,-1)]
#print('from pos={}'.format(pos))
x,y=pos[0:2]
valid_moves=[]
for t in test_moves:
new=(t[0]+x,t[1]+y)
if t[0]+x>-1 and \
t[0]+x<n and \
t[1]+y>-1 and \
t[1]+y<m:
if a[new] not in [BLOCK]:
valid_moves.append((t[0]+x,t[1]+y))
return valid_moves
def allpaths(a,paths):
for path in paths:
if path[-1] in [TERMINATE,FINAL]: continue
pos=path[-1]
moves=possible_moves(a,pos)
if not moves:
path.append(TERMINATE) # if no moves left, path is terminated
continue
base=path.copy()
for im,move in enumerate(moves):
b=a.copy()
# set previous position to BLOCK so can't move into previous positions
b[pos]=BLOCK
if im==0:
if a[move]==FINAL: path.append(FINAL) # if position to move to is FINAL, indicate
else: path.append(move)
else:
if a[move]==FINAL: paths.append(base+[FINAL])
else: paths.append(base+[move])
paths=allpaths(b,paths)
return paths
def solve(a):
ipos=list(zip(*(a==INITIAL).nonzero()))
paths=[ipos]
paths=allpaths(a,paths)
M=a.shape[0]*a.shape[1]
minlen=M
for path in paths:
if path[-1] in [FINAL]:
minlen=min(minlen,len(path)-1)
if minlen==M: return None
else: return minlen
n,m=5,6
a=init(n,m)
print(n,m)
print(a)
minlen=solve(a)
print(minlen)
To capture "right," you could simply track previous position through to the possible_moves function, and ensure you are returning only "straight" and "right" directions ("backward" is already guarded against as it's encoded).

Related

CSP Recursive Calls Fail with with Range(a,b) but not Explicit Range

Sudoku is a well known CSP and, in this case, LC problem. I don't need the solution, which follows.
My question is why does self.DOMAIN = "123456789" (line #4) work, whereas self.DOMAIN = map(str, range(1, 10)) (line #5) does not? Are they not equivalent?
class Solution:
def __init__(self):
self.SIZE = 9
# self.DOMAIN = map(str, range(1, self.SIZE + 1)) # THIS DES NOT WORK NOT WORK
self.DOMAIN = "123456789" # THIS WORKS
self.EMPTY = "."
from collections import defaultdict
self.rows = defaultdict(set) # {row:set}
self.cols = defaultdict(set) # {col:set}
self.boxs = defaultdict(set) # {box:set}
self.row_idx = lambda cell: cell // self.SIZE # determines row from cell num
self.col_idx = lambda cell: cell % self.SIZE # determins col from cell num
self.box_idx = lambda r, c: (r // 3) * 3 + c // 3 # determines box from row, col
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
assert(len(board) == self.SIZE and len(board[0]) == self.SIZE), \
"Sudoku board must be 9x9 in dimensions"
# Initialize board state
self.board = board
for r in range(self.SIZE):
for c in range(self.SIZE):
val = self.board[r][c]
if val != self.EMPTY:
self.rows[r].add(val)
self.cols[c].add(val)
self.boxs[self.box_idx(r, c)].add(val)
# Begin backtracking search from the first cell
self.backtrack(0)
def backtrack(self, cell):
if cell == 81:
# all values have been set and we are outside the board range
return True
r, c = self.row_idx(cell), self.col_idx(cell)
if self.board[r][c] != self.EMPTY:
# nothing to do, continue search
return self.backtrack(cell + 1)
# explore values in the domain for this cell
for candidate_val in self.DOMAIN:
if self.is_valid(r, c, candidate_val):
# place candidate
self.board[r][c] = candidate_val
self.rows[r].add(candidate_val)
self.cols[c].add(candidate_val)
self.boxs[self.box_idx(r, c)].add(candidate_val)
# continue search
if self.backtrack(cell + 1):
return True
# remove candidate and backtrack
self.board[r][c] = self.EMPTY
self.rows[r].remove(candidate_val)
self.cols[c].remove(candidate_val)
self.boxs[self.box_idx(r, c)].remove(candidate_val)
# no solution found for all values in the domain for this cell
return False
def is_valid(self, r, c, val):
""" Returns whether a value can be placed in board[r][c] in the current game state """
if val in self.rows[r]:
return False
if val in self.cols[c]:
return False
if val in self.boxs[self.box_idx(r, c)]:
return False
return True
You are suffering confusion between "generator" and "container".
Consult type( ... ) to tell the difference between them.
Python generators are "lazy", for excellent technical reasons.
Sometimes we can get away with just evaluating
the first K items of an infinite generator.
In your method you want everything greedily pulled out,
and then stored in a container.
The conventional way to phrase this is to wrap map with list:
self.DOMAIN = list(map(str, range(1, 10)))
or perhaps in your case you would prefer that .join pull them out:
self.DOMAIN = ''.join(map(str, range(1, 10)))

Implementing Breadth first search

Your friend bought you a present for the New Year, it's a puzzle! The puzzle consists of a number of
wooden rectangular pieces of varying lengths and widths and a board. The goal is to position the
wooden pieces on the board in a way such that all of the pieces will fit.
I have this program and I need help fixing my breadth first search algorithm.
Right now it is very slow and using a lot of memory. I think it is because I deep copy multiple times. The solve function is the main function and will do the heavy work.
I added a text file that has the first line as the dimensions of the puzzle and the rest of the lines are pieceID, pieceWidth and pieceLength respectively.
This is the Input File. Thank you so much.
10,10
1,10,1
2,1,10
3,1,5
4,3,5
5,20,2
6,1,5
7,1,5
8,2,5
import argparse, copy
import queue
import copy
import numpy as np
class PuzzleBoard():
def __init__(self, board_length, board_width ):
self.l = board_length
self.w = board_width
self.state = [[0 for _ in range(board_width)] for _ in range(board_length)]
self.used_piece = []
# Input: point - tuple cotaining (row_index, col_index) of point in self.state
# Returns true if point is out of bounds; otherwise, returns false
def __out_of_bounds(self, point):
# TODO: Implement this function
if(point < 0 or point > (len(self.state)) or (point > (self.state[0]))):
return True
return False
# Finds the next available open space in the PuzzleBoard (looking from the top-left in row-major order)
def __next(self):
for i in range(len(self.state)) :
for j in range(len(self.state[0])):
if (self.state[i][j] == 0):
return (i, j)
return False
# Input: piece - PuzzlePiece object
# Check if piece fits in the next available space (determined by __next method above)
def fits(self, piece):
position = self.__next()
if not position:
return False
#TODO: Check if any part of the piece is out of bounds
#if piece will be out bounds when place rotate to see if that helps
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
piece.rotate()
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return False
#TODO: Check if piece can be placed without intersecting another placed piece
return True
# Input: piece - PuzzlePiece object
# Insert piece into the next available position on the board and update state
def place(self, piece):
# TODO: Bug in this function. Pieces not being placed correctly.
position = self.__next()
if self.fits(piece):
for i in range(position[0], position[0] + piece.w ):
for j in range(position[1], position[1] + piece.l):
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return
if(self.state[i][j]== 0):
#self.used_piece.append(piece)
self.state[i][j] = piece.id
else:
continue
return position
def check(self, piece):
position = self.__next()
if(position[0] + piece.w > self.w or position[1] + piece.l > self.l):
return False
return True
# Returns whether the board has been filledwith pieces
def completed(self):
return True if not self.__next() else False
def copy(self):
copied = PuzzleBoard(self.l, self.w)
copied.state = copy.deepcopy(self.state)
return copied
class PuzzlePiece():
def __init__(self, pid, length, width):
self.id = pid
self.l = length
self.w = width
itfits = False
def rotate(self):
#TODO: Bug in this function. Pieces are not rotating correctly
temp = self.l
self.l = self.w
self.w = temp
def orientation(self):
return "H" if self.w >= self.l else "V"
def __str__(self):
return f"ID: {self.id}, LENGTH: {self.l}, WIDTH: {self.w}, ROTATED: {self.rotated}"
def parse_input(filepath) :
#TODO: Bug in this function. Error raised when called
parsed = {'board' : {}, 'pieces' : {}}
with open(filepath, 'r') as f:
file_contents = f.read().strip().split("\n")
board_length, board_width = file_contents[0].strip().split(",")
parsed['board']['length'] = int(board_length)
parsed['board']['width'] = int(board_width)
for i in range(1, len(file_contents)):
#FIX: the issue was fix
pid, l, w = file_contents[i].strip().split(",")
pid, l, w = int(pid), int(l), int(w)
parsed['pieces'][pid] = {}
parsed['pieces'][pid]['length'] = l
parsed['pieces'][pid]['width'] = w
return parsed
def helper(board, piece):
unused = []
#for piece in pieces:
if board.fits(piece):
position = board.place(piece)
board.used_piece.append((piece, position))
return board
def solve(board, remaining, used_pieces=[]):
# TODO: Implement a solution for a variable amount of pieces and puzzle board size.
# HINT: Recursion might help.7
poss = queue.Queue()
poss.put(board)
currboard = PuzzleBoard(len(board.state), len(board.state[0]))
while not currboard.completed():
currboard = poss.get()
#print(currboard.state)
for piece in remaining:
fakeboard = copy.deepcopy(currboard)
if(not (piece.id in np.array(fakeboard.state))):
#if( fakeboard.check(piece)):
poss.put(helper(fakeboard, piece))
print("Suff done")
return currboard
'''if(len(remaining) != 0):
board, used_pieces, unused_pieces = helper(board, remaining, used_pieces)
if board.completed():
return board, used_pieces
for i in board.state:
print(i)
print("\n \n")
return solve(board, unused_pieces, used_pieces)
return board'''
def main():
#TODO: Bug in this function. Positions are not correct after solution is found.
parser = argparse.ArgumentParser()
parser.add_argument('input')
args = parser.parse_args()
parsed = parse_input(args.input)
board = PuzzleBoard(parsed['board']['length'], parsed['board']['width'])
pieces = []
for k, v in parsed['pieces'].items():
pieces.append(PuzzlePiece(k, v['length'], v['width']))
solved = solve(board, pieces)
if not solved:
print("No solution found for given input.")
else:
print("Solution found.")
board = solved
for u, position in solved.used_piece:
print(f"Piece ID: {u.id}, Position:{position}, Orientation: {u.orientation()}")
if __name__ == "__main__":
main()

A* Search for Maze - Infinite Loop When Path does not Exist - Python

I am trying to implement an A-star search algorithm to find a path in a square maze from (0,0) to (dimension - 1, dimension - 1). My algorithm returns the correct path when it exists; however, if there is no path, then it runs in an infinite loop. How do I fix this? For now, I've put a condition to run if the length of the open list exceeds (dimension ^ 4), but this is obviously not a permanent fix. I am using Python 3.7.3.
import numpy as np
class node():
def __init__(self, parent=None, location=None):
self.parent = parent
self.location = location
self.g = float(0)
self.h = float(0)
self.f = float(0)
#returns euclidean distance between two nodes
#takes the locations/tuples of two nodes as arguments
#works properly
def euclidean_distance(node_1, node_2):
return float((((node_2[1] - node_1[1])**2) + ((node_2[0] - node_1[0])**2))**0.5)
#to make extracting the value at a given location in the maze easier
#takes the maze and two integers as arguments
def get_value(maze, a, b):
return maze[a][b]
def out_of_bounds(a, b, dim):
return (a < 0 or a >= dim) or (b < 0 or b >= dim)
#Euclidean A* Search, takes the maze and dimension as arguments
def a_star_euclidean(maze, dim):
#initializing start node and end node
start = node(None, (0,0))
end = node(None, (dim-1, dim-1))
#initializing open list and closed list
open_list = []
closed_list = []
open_list.append(start)
while len(open_list) > 0:
#assigning currentNode
currentNode = open_list[0]
currentNode_index = 0
#current location
for index, item in enumerate(open_list):
if item.f < currentNode.f:
currentNode = item
currentNode_index = index
#(currentNode.location)
row = currentNode.location[0]
column = currentNode.location[1]
#updating open list and closed list
open_list.pop(currentNode_index)
closed_list.append(currentNode)
#in case goal node is already reached
if currentNode.location == end.location:
path = []
current = currentNode
while current is not None:
path.append(current.location)
current = current.parent
#return path[::-1] #returning the path from start to end
path.reverse()
return path
else:
closed_list.append(currentNode)
#generating childs
child_locations = [(row+1, column), (row-1, column), (row, column+1), (row, column-1)]
#print(child_locations)
child_nodes = [node(currentNode, location) for location in child_locations]
#print(child_nodes)
for child in child_nodes:
#declaring row and column variables for child nodes
child_row = int(child.location[0])
child_column = int(child.location[1])
if not out_of_bounds(child_row, child_column, dim):
# Child is on the closed list
if child in open_list:
continue
#computing g(n), h(n), f(n)
child.g = float(currentNode.g + 1)
child.h = float(euclidean_distance(child.location, end.location))
child.f = float(child.g + child.h)
#child is in open list
if child in closed_list:
continue
if get_value(maze, child_row, child_column) == 0:
open_list.append(child)
else:
continue
else:
continue
#if (len(open_list) > dim**4): #b^d worst case
#return None
def main():
maze = []
dim = int(input("Enter the dimension of the game: "))
print(dim)
for row in range(dim):
maze.append([])
for column in range(dim):
maze[row].append(int(np.random.binomial(1, 0.3, 1)))
maze[0][0] = 0
maze[dim-1][dim-1] = 0
print(maze)
print("----------")
print(a_star_euclidean(maze,dim))
#print(euclidean_distance((1,1), (2,2)))
main()
I believe the issue is that child in closed_list is never true, because you haven't overriden the __eq__ operator of the node class. Because of this, python doesn't know how to compare two instances of the node class, so falls back to comparing if they are references to the same object in memory, otherwise it returns false. So two nodes are never equal when searching through closed_list.
Try defining the __eq__ operator for the node class like so. You can change the comparison to include other properties as you need.
class node():
def __init__(self, parent=None, location=None):
self.parent = parent
self.location = location
self.g = float(0)
self.h = float(0)
self.f = float(0)
def __eq__(self, other):
return self.location == other.location

python create a binary search tree using existing function

I'm practicing creating a balanced binary search tree in python.
I already have these below, any idea on how to create a balance_bst funtion that passed a list of unique values that are
sorted in increasing order. It returns a reference to the root of a well-balanced binary search tree:
class LN:
def __init__(self,value,next=None):
self.value = value
self.next = next
def list_to_ll(l):
if l == []:
return None
front = rear = LN(l[0])
for v in l[1:]:
rear.next = LN(v)
rear = rear.next
return front
def str_ll(ll):
answer = ''
while ll != None:
answer += str(ll.value)+'->'
ll = ll.next
return answer + 'None'
# Tree Node class and helper functions (to set up problem)
class TN:
def __init__(self,value,left=None,right=None):
self.value = value
self.left = left
self.right = right
def height(atree):
if atree == None:
return -1
else:
return 1+ max(height(atree.left),height(atree.right))
def size(t):
if t == None:
return 0
else:
return 1 + size(t.left) + size(t.right)
def is_balanced(t):
if t == None:
return True
else:
return abs(size(t.left)-size(t.right)) <= 1 and is_balanced(t.left) and is_balanced(t.right)
def str_tree(atree,indent_char ='.',indent_delta=2):
def str_tree_1(indent,atree):
if atree == None:
return ''
else:
answer = ''
answer += str_tree_1(indent+indent_delta,atree.right)
answer += indent*indent_char+str(atree.value)+'\n'
answer += str_tree_1(indent+indent_delta,atree.left)
return answer
return str_tree_1(0,atree)
How do write the balance_bst?
def balance_bst(l):
Here is what I did:
def build_balanced_bst(l):
if l == None:
return None
else:
middle = len(l) // 2
return TN(l[middle],
build_balanced_bst(l[:middle]),
build_balanced_bst(l[middle + 1:]))
It gives me:
IndexError: list index out of range
How do I fix it?
I'm not going to write it for you since that's not what SO is about, but here's the general idea. Since the list is already sorted, the root should be the element in the middle of the list. Its left child will be the root of the balanced tree consisting of the elements to the left of the root in the list, and the right sub-tree will be the rest.

Always getting the same path with A* implementation

I'm trying to implementing A* from the pseudo code from wikipedia however I'm getting some weird results.
The implementation finds what at first looks like a good path, but with a further look it always produces the same path!
Can anyone spot anything wrong? The code is written in python 3.1 and uses pygame.
import pygame
import sys, traceback
import random
import math
TILE_WIDTH = 30
TILE_HEIGHT = 30
NUM_TILES_X = 30
NUM_TILES_Y = 30
NUM_TILES = NUM_TILES_X * NUM_TILES_Y
GRID_WIDTH = TILE_WIDTH * NUM_TILES_X
GRID_HEIGHT = TILE_HEIGHT * NUM_TILES_Y
# h(x,y)
def heuristic_dist(source,dest):
return int(( (source.x - dest.x)**2 + (source.y - dest.y)**2 ) **0.5)
def a_star(nodes,start,goal):
# Set up data structures
closedset = []
openset = [start]
came_from={}
g_score = {}
g_score[start.index] = 0
h_score = {}
h_score[start.index] = heuristic_dist(start,goal)
f_score = {}
f_score[start.index] = h_score[start.index]
while len(openset) > 0:
# Find node with least f_score in openset
x = min(openset,key=lambda el:f_score[el.index])
# We have reached our goal!
if x.index == goal.index:
path = reconstruct_path(came_from,goal.index)
# Mark the path with green color
for node in path:
nodes[node].color=(0,255,0)
print( "Yihaaa!" )
return True
# Filter out x from openset and add it to closedset
openset = list(filter(lambda y:y.index!=x.index,openset))
closedset.append(x)
# Go through all neighbours
for y in x.get_neighbours():
# If this neighbour has been closed, skip it
if y in closedset: continue
# Not sure that this is correct.
tentative_g_score = g_score[x.index] + heuristic_dist(x,y)
if y not in openset:
openset.append(y)
tentative_is_better = True
elif tentative_g_score < g_score[y.index]:
tentative_is_better = True
else:
tentative_is_better = False
if tentative_is_better:
if y.index in came_from:
if f_score[x.index] < f_score[came_from[y].index]:
came_from[y.index] = x
else:
came_from[y.index] = x
g_score[y.index] = tentative_g_score
h_score[y.index] = heuristic_dist(y, goal)
f_score[y.index] = g_score[y.index] + h_score[y.index]
print("Couldn't find a path!")
return False
# Traverse the path backwards
def reconstruct_path(came_from,current_node,depth=0):
if current_node in came_from:
p = reconstruct_path(came_from,came_from[current_node].index)
return p + [current_node]
else:
return [current_node]
def draw_string(surface,string,x,y):
s = font.render(string,True,(0,0,0))
surface.blit(s,(x,y))
# Tile or Node that has a cuple of attributes: color, cost and x,y
class Tile:
def __init__(self,x,y,cost,index):
self.x=x
self.y=y
self.cost=cost
self.index=index
self.color = (255,255,255)
def draw(self,surface):
surface.fill(self.color,pygame.Rect(self.x*TILE_WIDTH,self.y*TILE_HEIGHT,TILE_WIDTH,TILE_HEIGHT))
pygame.draw.rect(surface,(255, 180, 180),pygame.Rect(self.x*TILE_WIDTH,self.y*TILE_HEIGHT,TILE_WIDTH,TILE_HEIGHT),2)
draw_string(surface,str(self.cost),self.x*TILE_WIDTH+TILE_WIDTH//3,self.y*TILE_HEIGHT+TILE_HEIGHT//3)
def get_neighbours(self):
nbs = []
# Where are our neighbours?
offsets = [(0,-1),(-1,0),(1,0),(0,1)]
for offset in offsets:
x = self.x + offset[0]
y = self.y + offset[1]
try: # coord_to_tile throws exception if no such neighbour exists (out of bounds for example)
nbs.append(coord_to_tile(x,y))
except Exception as e:
pass
return nbs
def __eq__(self,other):
return self.x == other.x and self.y==other.y
# Small helper function to convert x,y coords to a tile instance
nodes_lookup={}
def coord_to_tile(x,y):
return nodes_lookup[(x,y)]
def main():
global nodes_lookup
screen = pygame.display.set_mode((GRID_WIDTH, GRID_HEIGHT))
tiles = []
for x in range(NUM_TILES_X):
for y in range(NUM_TILES_Y):
# Create a random distribution where max grows
cost = random.randint(1,min(x*y,98)+1)
# Let the bottom line cost 1 as well
if y == NUM_TILES_Y-1: cost = 1
t = Tile(x,y,cost,len(tiles))
nodes_lookup[(x,y)] = t
tiles.append(t)
# Do a*
a_star(tiles,tiles[0],tiles[len(tiles)-1])
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break
for tile in tiles:
tile.draw(screen)
pygame.display.flip()
pygame.init()
font = pygame.font.SysFont("Times New Roman",18)
try:
main()
except Exception as e:
tb = sys.exc_info()[2]
traceback.print_exception(e.__class__, e, tb)
pygame.quit()
I really have no clue, since I think I have pretty much implemented the pseudo code statement by statement.
Here's a screenshot as well:
http://andhen.mine.nu/uploads/astar.dib
Thanks!
You access came_from on time with y, and one time with y.index in
if tentative_is_better:
if y.index in came_from:
if f_score[x.index] < f_score[came_from[y].index]: // index by y
came_from[y.index] = x // index by y.index
else:
You probably meant
if f_score[x.index] < f_score[came_from[y.index].index]:
in the first line.
Besides that, the code looks ok.
Anyway, what do you mean by always produces the same path? The algorithm is supposed to return the optimal path which should always be the same... (or did you mean, it always produces the same path independently of start and goal?)`
EDIT:
You don't use your random cost anywhere in the algorithm. The 'costs' the algorithm is using are always the distance between two adjacent nodes: They are defined in heuristic_distance and used in the line
tentative_g_score = g_score[x.index] + heuristic_dist(x,y)
If you want to define random costs, you must first realize that this algorithm assigns costs to edges, not to vertices. You'll have to define some function real_costs(x,y) which calculates the costs for going from node x to node y and use this cost function instead of heuristic_dist in the above line.

Categories