A* Pathfinder Visuals Failing

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

Quite a while ago, I tried to recreate the code shown in the article ‘Tutorial: The A* Algorithm’. I had fixed my original issues, but now the visuals are not working. By this, I mean that nothing visually appears. All I get is a black screen, which, of course, isn’t what should appear. It’s not like there are any error messages in terminal; it’s just that nothing appears on the simulator’s screen.

If anyone could help me with this unusual glitch, that would be great.

Here’s the code for main.lua:

display.setStatusBar( display.HiddenStatusBar )  
  
local pathfinder = require("pathfinder")  
  
local kLevelRows = 50 -- Rows in map  
local kLevelCols = 50 -- Columns in map  
  
local kRoadProbability = 6  
  
local level = {}  
  
local curGameFunction = nil  
  
local cellWidth = display.contentWidth/kLevelCols  
local cellHeight = display.contentHeight/kLevelRows  
  
local startCell = {col = 1, row = 1}  
local endCell = {col = 1, row = 1}  
  
local cells = {}  
  
local walker  
  
-- builds our grid --  
function buildGrid()  
 -- build map array --  
 for x = 0, kLevelCols do  
 level[x] = {}  
 for y = 0, kLevelRows do  
 local probability = math.random(0, 10)  
 if probability \<= kRoadProbability then  
 level[x][y] = 1  
 else  
 level[x][y] = 0  
 end  
 end  
 end  
  
 -- build screen now --  
 for x = 0, kLevelCols do  
 for y = 0, kLevelRows do  
 local cell = display.newRect(x\*cellWidth, y\*cellHeight, cellWidth, cellHeight)  
 cell.strokeWidth = 1  
 cell:setStrokeColor(0, 0, 0)  
 if level[x][y] == 0 then  
 cell:setFillColor(255, 0, 0)  
 end  
  
 if cells[x] == nil then  
 cells[x] = {}  
 end  
  
 cells[x][y] = cell  
 end  
 end  
  
 print2d(level)  
end  
  
-- Touch handler. Delegates call to current selected game function --  
function onBoardTouched(event)  
 if event.phase == "began" then  
 curGameFunction(event)  
 end  
end  
  
function onStartCellSelected(event)  
end  
  
function onEndCellSelected(event)  
end  
  
function onDetermineAStar(event)  
end  
  
function onEnd(event)  
end  
  
function onEnd(event)  
 -- start over --  
 curGameFunction = function(event) onStartCellSelected(event) end  
end  
  
curGameFunction = function(event) onStartCellSelected(event) end  
  
-- gets the display.newRect object based on x,y screen value --  
function getCell(x, y)  
 local indices = getIndices(x, y)  
 return cells[indices[1]][indices[2]]  
end  
  
-- colors a cell on the grid --  
function colorCell(cell, red, green, blue)  
 cell:setFillColor(red, green, blue)  
end  
  
local instructions = nil  
  
-- displays instructions (albeit hard to read) to the user --  
function displayInstructions(string)  
 if instructions ~= nil then  
 instructions:removeSelf()  
 end  
  
 instructions = display.newText(string, 0, 0, native.systemFontBold, 20)  
 instructions:setTextColor(0, 0, 0)  
end  
  
-- called to select the starting point on the grid --  
function onStartCellSelected(event)  
 local indices = getIndices(event.x, event.y)  
  
 if level[indices[1]][indices[2]] == 0 then  
 displayInstructions("Cannot select red. Try again.")  
 else  
 startCell.col = indices[1]  
 startCell.row = indices[2]  
 displayInstructions("Select the ending cell.")  
 colorCell(getCell(event.x, event.y), 0, 255, 0)  
 curGameFunction = function(event) onEndCellSelected(event) end  
 end  
end  
  
-- called to select the ending point in the grid --  
function onEndCellSelected(event)  
 local indices = getIndices(event.x, event.y)  
  
 if level[indices[1]][indices[2]] == 0 then  
 displayInstructions("Cannot select red. Try again")  
 else  
 endCell.col = indices[1]  
 endCell.row = indices[2]  
 colorCell(getCell(event.x, event.y), 0, 0, 255)  
 displayInstructions("Touch anywhere to see A\* go")  
 curGameFunction = function(event) onDetermineAStar(event) end  
 end  
end  
  
-- called when the demonstration ends (resets the grid) --  
function onEnd(event)  
 for x = 0, kLevelCols do  
 for y = 0, kLevelRows do  
 cells[x][y]:removeSelf()  
 end  
 end  
  
 if walker then  
 walker:removeSelf()  
 walker = nil  
 end  
  
 cells = {}  
  
 buildGrid()  
 displayInstructions("Select the starting cell")  
 curGameFunction = function(event) onStartCellSelected(event) end  
end  
  
local path = pathfinder.pathFind(level, kLevelCols, kLevelRows, startCell.col, startCell.row, endCell.col, endCell.row)  
  
function newWalker(path)  
 local walker = display.newCircle((startCell.col + 0.5) \* cellWidth, (startCell.row + 0.5) \* cellHeight, cellWidth)  
 walker.strokeWidth = 1  
 walker:setStrokeColor(0, 0, 0)  
 walker:setFillColor(0, 255, 255)  
 walker.pathIndex = 0  
 walker.pathLen = table\_len(path)  
 walker.speed = 50  
  
 function walker:go()  
 if self.pathIndex \< self.pathLen then  
 local dir = path[self.pathIndex]  
 self.transition = transition.to(self, { time = self.speed \* dir.count,  
 x = self.x + dir.dx \* dir.count \* cellWidth,  
 y = self.y + dir.dy \* dir.count \* cellHeight,  
 onComplete = function() self:go() end})  
 end  
  
 return walker  
end  
  
-- called to get A\* algorithm going --  
function onDetermineAStar(event)  
 displayInstructions("")  
  
 -- run A\* --  
 local path = pathfinder.pathFind(level, kLevelCols, kLevelRows, startCell.col, startCell.row, endCell.col, endCell.row)  
 print("Path", path)  
  
 if path ~= false then  
 -- color the path --  
 local currentCell = {x=startCell.col, y=startCell.row}  
  
 for k = 0, #path do  
 local cellDirectionX = path[k].dx  
 local cellDirectionY = path[k].dy  
 local count = path[k].count  
  
 for l = 1, count do  
 currentCell.x = currentCell.x + cellDirectionX  
 currentCell.y = currentCell.y + cellDirectionY  
 if currentCell.x ~= endCell.col or currentCell.y ~= endCell.row then  
 colorCell(cells[currentCell.x][currentCell.y], 255, 255, 0)  
 end  
 end  
 end  
  
 -- create a moving object  
 walker = newWalker(path)  
 walker:go()  
  
 curGameFunction = function(event) onEnd(event) end  
 else  
 displayInstructions("Suitable path not found")  
 curGameFunction = function(event) onEnd(event) end  
 end  
end  
end  

Here’s the code for pathfinder.lua:

[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

local startX = 1
local startY = 1


– 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)
assert(the_map, mapW, mapH, startX, startY, targetX)
– 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)
print( startX )
print( startY )
print( mapW )
print( mapH )
print( n0 )
print( n0.priority )
assert(open_nodes_map)
assert(startX)
assert(startY)
assert(open_nodes_map[startY][startX])
assert(n0)
assert(n0.priority)
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
[/code] [import]uid: 82408 topic_id: 22877 reply_id: 322877[/import]

I didn’t digest all of this, but here are a few things I noticed.

For many of your for loop you start with 0. For example,

for x = 0, kLevelCols do

And then you use x as an index into an array. In Lua, these should always start with 1 instead of 0.
I don’t see a main() function in your main.lua file. So how are the other functions getting called. I would guess that the program is just starting up and initializing the stuff that isn’t inside a function and then doing nothing. [import]uid: 67839 topic_id: 22877 reply_id: 91394[/import]