Can't get A* Pathfinder to work

Please note, I am eleven years old and a novice.

I’m working on recreating the code shown in the page ‘Tutorial: The A* Algorithm’. I have the pathfinder.lua file and Corona’s definitely trying to use it, but I’m having issues with the file. In terminal I keep getting this:

Runtime error: …pathfinder.lua:208: attempt to index field ‘?’ (a nil value)

Oddly, I looked at line 208 but nothing showed up. Not even an ‘invisible’ piece of text. Any ideas? Here’s all the code in the file:

[code]
– A* pathfinding module
– Author: Lerg
– Release date: 2011-08-25
– Version: 1.1
– License: MIT
– Based on the python implementation.

– USAGE:
– Import this module and use pathFind() function. Map should be zero indexed (first element index is zero).
local _M = {}

local mAbs = math.abs
local mSqrt = math.sqrt


– Set a value to bounds

local function clamp(value, low, high)
if value < low then value = low
elseif high and value > high then value = high end
return value
end


– Implementation of min binary heap for use as a priority queue
– each element should have ‘priority’ field or one that you defined
– when created a heap.

local function newHeap (priority)
if not priority then
priority = ‘priority’
end
heapObject = {}
heapObject.heap = {}
heapObject.len = 0 – Size of the heap
function heapObject:push (newElement) – Adds new element to the heap
local index = self.len
self.heap[index] = newElement – Add to bottom of the heap
self.len = self.len + 1 – Increase heap elements counter
self:heapifyUp(index) – Maintane min heap
end

function heapObject:heapifyUp (index)
local parentIndex = clamp(math.floor((index - 1) / 2), 0)
if self.heap[index][priority] < self.heap[parentIndex][priority] then
self.heap[index], self.heap[parentIndex] = self.heap[parentIndex], self.heap[index] – Swap
self:heapifyUp(parentIndex) – Continue sorting up the heap
end
end

function heapObject:pop (index) – Returns the element with the smallest priority or specific one
if not index then index = 0 end
local minElement = self.heap[index]
self.heap[index] = self.heap[self.len - 1] – Swap
– Remove element from heap
self.heap[self.len - 1] = nil
self.len = self.len - 1
self:heapifyDown(index) – Maintane min heap
return minElement
end

function heapObject:heapifyDown (index)
local leftChildIndex = 2 * index + 1
local rightChildIndex = 2 * index + 2
if (self.heap[leftChildIndex] and self.heap[leftChildIndex][priority] and self.heap[leftChildIndex][priority] < self.heap[index][priority])
or
(self.heap[rightChildIndex] and self.heap[rightChildIndex][priority] and self.heap[rightChildIndex][priority] < self.heap[index][priority]) then
if (not self.heap[rightChildIndex] or not self.heap[rightChildIndex][priority]) or self.heap[leftChildIndex][priority] < self.heap[rightChildIndex][priority] then
self.heap[index], self.heap[leftChildIndex] = self.heap[leftChildIndex], self.heap[index] – Swap
self:heapifyDown(leftChildIndex) – Continue sorting down the heap
else
self.heap[index], self.heap[rightChildIndex] = self.heap[rightChildIndex], self.heap[index] – Swap
self:heapifyDown(rightChildIndex) – Continue sorting down the heap
end
end
end

function heapObject:root () – Returns the root element without removing it
return self.heap[0]
end

return heapObject
end


– Calculate number of elements in a table
– Correctly manages zero indexed tables

function table_len (t)
local len = #t + 1
if len == 1 and t[0] == nil then
len = 0
end
return len
end


– Reverse a table

local function table_reverse (t)
local r = {}
local tl = table_len(t)
for k,v in pairs(t) do
r[tl - k - 1] = v
end
return r
end


– Print two dimensional arrays

function print2d(t)
for r = 0, table_len(t) - 1 do
local str = ‘’
for c = 0, table_len(t[r]) - 1 do
local val = t[c][r] or 0 – Codepunk: Changed to print in [x][y] direction
val = math.round(val)
if val == 0 then
val = ’ ’
end
str = str … val … ’ ’
end
print(str)
end
end

– This represents a track node (each step)
local function newNode (posX, posY, distance, priority)
node = {}
node.posX = posX
node.posY = posY
node.distance = distance
node.priority = priority
– Estimation function for the remaining distance to the goal.
function node:estimate(destX, destY)
local dx = destX - self.posX
local dy = destY - self.posY
– Manhattan distance
return mSqrt(dx * dx + dy * dy) --mAbs(dx) + mAbs(dy)
–Euclidian Distance
–return mSqrt(dx * dx + dy * dy)
end
function node:updatePriority(destX, destY)
self.priority = self.distance + self:estimate(destX, destY) * 10 – A*
end
function node:nextMove()
– give higher priority to going straight instead of diagonally
–if dirs == 8 and d % 2 ~= 0 then
– self.distance = self.distance + 14
–else
self.distance = self.distance + 10
end
mt = { __lt = function (a, b)
return { value = a.priority < b.priority }
end }
setmetatable(node, mt)

return node
end

– A-star algorithm.
– The path returned will be a table of directions and number of steps
@param the_map 2D table of the map representation, 0 means now way, 1 means a road. Tables are 0 indexed.
@param mapW number Width of the map
@param mapH number Height of the map
@param startX number Start point
@param startY number
@param targetX number End point
@param targetY number
@return table|mixed Path is returned or false if no path is found
function _M.pathFind(the_map, mapW, mapH, startX, startY, targetX, targetY)
– Number of directions: 4 or 8
local dirs = 4
local dx = {}
dx[0], dx[1], dx[2], dx[3] = 1, 0, -1, 0
local dy = {}
dy[0], dy[1], dy[2], dy[3] = 0, 1, 0, -1
– For 8 directions:
– dx = 1, 1, 0, -1, -1, -1, 0, 1
– dy = 0, 1, 1, 1, 0, -1, -1, -1
local closed_nodes_map = {} – map of closed (tried-out) nodes
local open_nodes_map = {} – map of open (not-yet-tried) nodes
local dir_map = {} – map of dirs
local row = {}
for i = 0, mapW - 1 do
row[i] = 0
end

for i = 0, mapH - 1 do – create 2d arrays
closed_nodes_map[i] = {}
open_nodes_map[i] = {}
dir_map[i] = {}
for j = 0, mapW - 1 do
closed_nodes_map[i][j] = 0
open_nodes_map[i][j] = 0
dir_map[i][j] = 0
end
end

local pq = {} – priority queues of open (not-yet-tried) nodes
pq[0] = newHeap()
pq[1] = newHeap()
local pqi = 0 – priority queue index
– create the start node and push into list of open nodes
local n0 = newNode(startX, startY, 0, 0)
n0:updatePriority(targetX, targetY)
pq[pqi]:push(n0)
open_nodes_map[startY][startX] = n0.priority – mark it on the open nodes map
– A* search
while pq[pqi].len > 0 do
– get the current node w/ the highest priority
– from the list of open nodes
local n1 = pq[pqi]:pop() – top node
local n0 = newNode(n1.posX, n1.posY, n1.distance, n1.priority)
local x = n0.posX
local y = n0.posY
– remove the node from the open list
open_nodes_map[y][x] = 0
closed_nodes_map[y][x] = 1 – mark it on the closed nodes map

– quit searching when the goal is reached
– form direction table
if x == targetX and y == targetY then
– generate the path from finish to start
– by following the dirs
local path = {}
local pathIndex = 0
local function pathInsert (a_dir, dir_count)
– TODO: find a bug when zero count directions are inserted
if dir_count then
local rev_dir – reverse direction
if a_dir == 0 then rev_dir = 2 end
if a_dir == 1 then rev_dir = 3 end
if a_dir == 2 then rev_dir = 0 end
if a_dir == 3 then rev_dir = 1 end
local item = {dx = dx[rev_dir], dy = dy[rev_dir], count = dir_count}
path[pathIndex] = item
pathIndex = pathIndex + 1
end
end

local prev_cur
local dir_count = 0
local cur_dir
while not (x == startX and y == startY) do
cur_dir = dir_map[y][x]
if not prev_dir then prev_dir = cur_dir end
if prev_dir ~= cur_dir then
pathInsert(prev_dir, dir_count)
dir_count = 0
end
dir_count = dir_count + 1
prev_dir = cur_dir
x = x + dx[cur_dir]
y = y + dy[cur_dir]
end

pathInsert(cur_dir, dir_count)
return table_reverse(path)
end
– generate moves (child nodes) in all possible dirs
for i = 0, dirs - 1 do
local xdx = x + dx[i]
local ydy = y + dy[i]
if not (xdx < 0 or xdx >= mapW or ydy < 0 or ydy >= mapH or the_map[xdx][ydy] ~= 1 or closed_nodes_map[ydy][xdx] == 1) then
– generate a child node
local m0 = newNode(xdx, ydy, n0.distance, n0.priority)
m0:nextMove(dirs, i)
m0:updatePriority(targetX, targetY)
– if it is not in the open list then add into that
if open_nodes_map[ydy][xdx] == 0 then
open_nodes_map[ydy][xdx] = m0.priority
pq[pqi]:push(m0)
– mark its parent node direction
dir_map[ydy][xdx] = (i + dirs / 2) % dirs
elseif open_nodes_map[ydy][xdx] > m0.priority then
– update the priority
open_nodes_map[ydy][xdx] = m0.priority
– update the parent direction
dir_map[ydy][xdx] = (i + dirs / 2) % dirs
– replace the node
– by emptying one pq to the other one
– except the node to be replaced will be ignored
– and the new node will be pushed in instead
while pq[pqi][0] and not (pq[pqi][0].posX == xdx and pq[pqi][0].posY == ydy) do
pq[1 - pqi]:push(pq[pqi]:pop())
end
pq[pqi]:pop() – remove the target node
– empty the larger size priority queue to the smaller one
if pq[pqi].len > pq[1 - pqi].len then
pqi = 1 - pqi
end
while pq[pqi].len > 0 do
pq[1-pqi]:push(pq[pqi]:pop())
end
pqi = 1 - pqi
pq[pqi]:push(m0) – add the better node instead
end
end
end
end
return false – if no route found
end

return _M [import]uid: 82408 topic_id: 19782 reply_id: 319782[/import]

Hmmm, have you got all the files you need? You should have main.lua as well.

This file is just the include file with all functions. I am not sure where you picked this up, but you cant find all info within the section “share code” here in the community. I tried it a couple of days ago, and it works fine.

Joakim [import]uid: 81188 topic_id: 19782 reply_id: 76660[/import]

Yes, I have main.lua, that’s how I’m linking to it. Oddly, the terminal shows that it has an error when it reaches line 208 of pathfinder.lua. I don’t see any unusual text there, though. [import]uid: 82408 topic_id: 19782 reply_id: 76808[/import]

Well, 208 above is a comment, so it’s got to be some other line causing the error… [import]uid: 19626 topic_id: 19782 reply_id: 76823[/import]

Sorry, I should have included all that copyright text up top. Line 208 is actually

open_nodes_map[startY][startX] = n0.priority – mark it on the open nodes map

Sorry about this. [import]uid: 82408 topic_id: 19782 reply_id: 77086[/import]

Can you post the code to where you call:

_M.pathFind()

??

It would be useful to see what your passing in.

There are 5 things that could be nil in that string:

open_nodes_map, but its declared just above it and I don’t see anything nil’ing it out.

n0 could be, but it gets returned from a function on line 205 and I don’t see how it could be nil.

I also don’t see, based on just the code above where n0.priority could be nil.

That leaves startX or startY as the most likely subjects since they are things you pass in to _M.pathFind() and if what you’re passing is nil, you’re going to see this problem.

The other possibility is something overwriting one of those variables. somewhere, but I’d bet on a problem with startX or startY
[import]uid: 19626 topic_id: 19782 reply_id: 77090[/import]

Have you downloaded the complete sample?

https://github.com/codepunkschmidt/Codepunk-s-Code/zipball/master

http://developer.anscamobile.com/code/pathfinding-demo

Joakim [import]uid: 81188 topic_id: 19782 reply_id: 77093[/import]

@robmiracle,

Sure, here’s the code for _M.pathFind():

– A-star algorithm.
– The path returned will be a table of directions and number of steps
@param the_map 2D table of the map representation, 0 means now way, 1 means a road. Tables are 0 indexed.
@param mapW number Width of the map
@param mapH number Height of the map
@param startX number Start point
@param startY number
@param targetX number End point
@param targetY number
@return table|mixed Path is returned or false if no path is found
function _M.pathFind(the_map, mapW, mapH, startX, startY, targetX, targetY)
– Number of directions: 4 or 8
local dirs = 4
local dx = {}
dx[0], dx[1], dx[2], dx[3] = 1, 0, -1, 0
local dy = {}
dy[0], dy[1], dy[2], dy[3] = 0, 1, 0, -1
– For 8 directions:
– dx = 1, 1, 0, -1, -1, -1, 0, 1
– dy = 0, 1, 1, 1, 0, -1, -1, -1
local closed_nodes_map = {} – map of closed (tried-out) nodes
local open_nodes_map = {} – map of open (not-yet-tried) nodes
local dir_map = {} – map of dirs
local row = {}
for i = 0, mapW - 1 do
row[i] = 0
end

for i = 0, mapH - 1 do – create 2d arrays
closed_nodes_map[i] = {}
open_nodes_map[i] = {}
dir_map[i] = {}
for j = 0, mapW - 1 do
closed_nodes_map[i][j] = 0
open_nodes_map[i][j] = 0
dir_map[i][j] = 0
end
end

As for the startX, startY theory, I’m pretty sure that’s the case. The first instances of startX and startY are both in the _M.pathFind() function, but it seems that _M.pathFind() isn’t actually defining them, just calling for them. Weirdly, Terminal doesn’t give an error for line 171 (the line with _M.pathFind() ). [import]uid: 82408 topic_id: 19782 reply_id: 77112[/import]

I guess I wasn’t clear… Where is the code where you call _M.pathFind() showing what you are passing to it?

[import]uid: 19626 topic_id: 19782 reply_id: 77123[/import]

I’m not exactly understanding. What do you mean ‘passing to it’? [import]uid: 82408 topic_id: 19782 reply_id: 77126[/import]

I added some code at the top of the file, defining startX and startY both as 1, but it didn’t do anything. It can’t be startX or startY, so it’s got to be something invisible in the code. This is backed up by the fact that the field it tries to index is called ‘?’. If it really was startX or startY, it would call the field ‘startX’ or ‘startY’. However, when I checked the ‘Show Invisibles’ thing, I didn’t see anything unusual. There’s some weird formatting going on here, I think. [import]uid: 82408 topic_id: 19782 reply_id: 77131[/import]

somewhere in your code, probably in your main.lua you have to be calling the A* code.

What is that code? [import]uid: 19626 topic_id: 19782 reply_id: 77132[/import]

Somewhere you have to have a line of code that looks like:
_M.pathFind(the_map, mapW, mapH, startX, startY, targetX, targetY)

Your variables for the_map, mapW, mapH, etc. maybe different in your code, but you have to include each of those variables as parameters to the _M.pathFind function.

[import]uid: 19626 topic_id: 19782 reply_id: 77137[/import]

local path = pathfinder.pathFind(level, kLevelCols, kLevelRows, startCell.col, startCell.row, endCell.col, endCell.row)

Perfect. Either startCell.col, startCell or startCell.col is probably nil.
[import]uid: 19626 topic_id: 19782 reply_id: 77138[/import]

Oh, now I understand. At the first line of main.lua, I define ‘pathfinder’ roughly the same way I would define ‘physics’:

local pathfinder = require(“pathfinder”)

Then, at line 157 of main.lua, I call for pathfinder:

local path = pathfinder.pathFind(level, kLevelCols, kLevelRows, startCell.col, startCell.row, endCell.col, endCell.row)

Hope that helps. :wink: [import]uid: 82408 topic_id: 19782 reply_id: 77135[/import]

I don’t think so. I have several pieces of code that I think define startCell, startCell.col and startCell.row. Here’s the code for defining them in main.lua:

local startCell = {col = -1, row = -1}

startCell and the attached variables ‘col’ and ‘row’ are all defined here, so they can’t be nil. Or am I missing something? [import]uid: 82408 topic_id: 19782 reply_id: 77140[/import]

Try putting this assert statement just after this line in pathfinder.lua

[code]
function _M.pathFind(the_map, mapW, mapH, startX, startY, targetX, targetY)
assert(the_map, mapW, mapH, startX, startY, targetX)

Then just before the line that is erroring:

assert(open\_nodes\_map)  
assert(startY)  
assert(startX)  
assert(open\_nodes\_map[startY][startX])  
assert(n0)  
assert(n0.priority)  
open\_nodes\_map[startY][startX] = n0.priority  

Actually looking at the code, if mapW and mapH are 0 it could cause open_nodes_map[1][1] to not exist and you would get a nil from that. [import]uid: 19626 topic_id: 19782 reply_id: 77172[/import]

Thanks, rob, but it just created a new error. It’s on that part of the code you just gave where you say:

assert(open\_nodes\_map[startY][startX])  

Once again, it’s trying to index field ‘?’, which is a nil value. I tried moving this line past all the other assertations, but it didn’t work. My guess is that open_nodes_map really is nil. Also, I defined mapW and mapH both as 1 at the top of my code. I suppose we’ll see if that does something. [import]uid: 82408 topic_id: 19782 reply_id: 77370[/import]

That’s exactly what “assert” does. It gave us the variable that is erroring.

open_nodes_map is probably not nil, but open_nodes_map[1] may very well be.

The next step is to print the values of startX, startY, mapH and mapW are.

If mapH and/or mapY are 0 then this block of code will have problems:

 for i = 0, mapH - 1 do -- create 2d arrays  
 closed\_nodes\_map[i] = {}  
 open\_nodes\_map[i] = {}  
 dir\_map[i] = {}  
 for j = 0, mapW - 1 do  
 closed\_nodes\_map[i][j] = 0  
 open\_nodes\_map[i][j] = 0  
 dir\_map[i][j] = 0  
 end  
 end  

if mapH is 0, then the first line becomes:
for i=0, -1 do
since 0 is greater than -1, it fails to process that block and
open_nodes_map[i] = nil
since it never got initialized.

The for j loop has the same issue.

But if you think about it, mapW and mapH are the size of the map you’re using and trying to use a 0x0 map doesnt make much sense.
[import]uid: 19626 topic_id: 19782 reply_id: 77377[/import]

And open_nodes_map[-1] doesn’t exist nor does open_nodes_map[-1][-1]
[import]uid: 19626 topic_id: 19782 reply_id: 77391[/import]