a* pathfinding

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
dumbledore
Posts: 680
Joined: May 28, 2005 1:11
Contact:

a* pathfinding

Post by dumbledore »

there've been a few threads about this on qbasicnews, i thought i'd add mine to the jumble ;P it doesn't save the path, it's probably best for monster movements 'n stuff like that, it's a lot faster than calculating the entire path and picking that apart ;) i could add route optimizing pretty easy if i add path saving, then again ai's shouldn't be too smart imo

Code: Select all

#include once "crt.bi"
#define WALL 4
#define FINISH 5
#define START 6

option explicit

type coord
    x as ubyte
    y as ubyte
end type
type node
    x as ubyte
    y as ubyte
    g as integer
    f as integer
    p as node ptr
end type

declare function timesvisited( byval x as integer, byval y as integer ) as byte
declare function outofbounds( byval x as integer, byval y as integer ) as byte
declare function onclosedlist( byref what as coord ) as byte
declare function removefromopenlist( byval idx as integer ) as byte
declare function onopenlist( byref what as coord ) as byte

redim preserve shared openlist( 0 ) as node ptr, closedlist( 1 ) as coord, allnodes( 0 ) as node ptr
dim as integer rx, ry, cx, cy, ex, ey, i

cls
dim shared as integer mx, my
read mx
read my
dim as ubyte room( mx * my )
for ry = 0 to my - 1
    for rx = 0 to mx - 1
        read room( rx + ry * mx )
        select case room( rx + ry * mx )
        case WALL
            ? "|";
        case START
            ? "@";
            cx = rx
            cy = ry
            closedlist( 0 ).x = rx
            closedlist( 0 ).y = ry
        case FINISH
            ? "*";
            ex = rx
            ey = ry
        case else
            ? " ";
        end select
    next
    ?
next

sleep

dim as node ptr tmpnode
tmpnode = callocate( len( node ) )
tmpnode->x = cx
tmpnode->y = cy
allnodes( ubound( allnodes ) ) = tmpnode
redim preserve shared allnodes( ubound( allnodes ) + 1 )
while cx <> ex or cy <> ey
    
    dim as integer max = 100, aye, x, y
    
    aye = 0
    
    for x = -1 to 1
        for y = -1 to 1 step 1 - ( x = 0 )
            
            dim as coord working
            working.x = cx + x
            working.y = cy + y
            
            if onclosedlist( working ) = 0 and onopenlist( working ) = 0 and _
               room( cx + x + ( cy + y ) * mx ) <> WALL and _
               room( cx + ( cy + y ) * mx ) <> WALL and _
               room( cx + x + cy * mx ) <> WALL and _
               outofbounds( cx + x, cy + y ) = 0 then
                
                openlist( ubound( openlist ) ) = callocate( len( node ) )
                dim as node ptr temp
                temp = openlist( ubound( openlist ) )  '' use a temp var so we don't have to
                                                       '' keep using ubound()
                temp->x = working.x
                temp->y = working.y
                
                temp->f = 10 * sqr( abs( x ) + abs( y ) ) + _                   '' the g score
                          10 * sqr( ( cx + x - ex ) ^ 2 + ( cy + y - ey ) ^ 2 ) '' the h score
                if temp->f > max then max = temp->f
                temp->p = tmpnode
                
                allnodes( ubound( allnodes ) ) = temp
                redim preserve shared allnodes( ubound( allnodes ) + 1 )
                
                redim preserve shared openlist( ubound( openlist ) + 1 )
                
            end if
            
        next
    next
    
    dim as integer lowest, lowestidx = 0
    lowest = max + 1
    for i = 0 to ubound( openlist ) - 1
        if openlist( i )->f < lowest and openlist( i )->f <> -1 then
            lowest = openlist( i )->f
            lowestidx = i
        end if
    next
    
    locate cy + 1, cx + 1: ? " "
    
    if lowest <> max + 1 then
        cx = openlist( lowestidx )->x
        cy = openlist( lowestidx )->y
    end if
    
    locate cy + 1, cx + 1: ? "@"
    
    closedlist( ubound( closedlist ) ).x = cx
    closedlist( ubound( closedlist ) ).y = cy
    redim preserve shared closedlist( ubound( closedlist ) + 1 )
    
    tmpnode = openlist( lowestidx )
    removefromopenlist( lowestidx )
    
    sleep
    
wend

'' as a function this would return tmpnode

while tmpnode <> 0
    locate tmpnode->y + 1, tmpnode->x + 1
    ? "*"
    tmpnode = tmpnode->p
wend

'' it will be necessary to clean up memory afterward, otherwise we'll end up with
'' a leak the size of the pacific ocean ;P

for i = 0 to ubound( allnodes )
    
    deallocate allnodes( i )
    
next

sleep

end

data 12,10
data 4,4,4,4,4,4,4,4,4,4,4,4
data 4,0,0,0,0,0,0,0,0,0,0,4
data 4,0,0,0,0,0,0,0,0,0,0,4
data 4,0,0,0,4,4,4,0,0,0,0,4
data 4,0,0,0,0,0,0,4,0,0,0,4
data 4,0,0,0,0,6,0,4,0,5,0,4
data 4,0,0,0,0,0,0,4,0,0,0,4
data 4,0,0,0,4,4,4,0,0,0,0,4
data 4,0,0,0,0,0,0,0,0,0,0,4
data 4,4,4,4,4,4,4,4,4,4,4,4

function outofbounds( byval x as integer, byval y as integer ) as byte
    
    return x < 0 or x > mx - 1 or y < 0 or y > my - 1
    
end function

function onclosedlist( byref what as coord ) as byte
    
    dim as integer i
    for i = 0 to ubound( closedlist ) - 1
        if memcmp( @closedlist( i ), @what, len( coord ) ) = 0 then return -1
    next
    return 0
    
end function

function removefromopenlist( byval idx as integer ) as byte
    
    if idx > ubound( openlist ) then return 0
    dim as integer i
    for i = idx to ubound( openlist ) - 1
        openlist( i ) = openlist( i + 1 )
    next
    redim preserve openlist( ubound( openlist ) - 1 )
    
    return -1
    
end function

function onopenlist( byref what as coord ) as byte
    
    dim as integer i
    for i = 0 to ubound( openlist ) - 1
        if memcmp( openlist( i ), @what, len( coord ) ) = 0 then return -1
    next
    return 0
    
end function
[edit] changed to use open / closed lists
Last edited by dumbledore on Jul 02, 2005 2:37, edited 1 time in total.
Ryan
Posts: 695
Joined: Jun 10, 2005 2:13
Location: Louisville, KY
Contact:

Post by Ryan »

Thanks! I'm lost when it comes to pathfinding and need to figure it out for the Rogue competition. No one else seemed quite sure of their code, though. = P
dumbledore
Posts: 680
Joined: May 28, 2005 1:11
Contact:

Post by dumbledore »

i added opened / closed lists to it, should work a bit faster now.
Frobozz
Posts: 33
Joined: Jun 12, 2005 20:32

Post by Frobozz »

Personally I don't see the point of strong pathfinding in a roguelike - unless you like the idea of mobs being able to find you even with a solid wall blocking their view. ^_^
dumbledore
Posts: 680
Joined: May 28, 2005 1:11
Contact:

Post by dumbledore »

no, it'll have to be modified a bit to allow for line of sight type of things, what'll end up happening is if a monster can see you it'll use the pathfinding algo to figure out where to go, but it will only pass the stuff it can see to the algo. this way it will only be as smart as it should be.
rdc
Posts: 1741
Joined: May 27, 2005 17:22
Location: Texas, USA
Contact:

Post by rdc »

Nice code.

If the monster can hear you, it doesn't need to see you. Sound could travel for quite a distance in a dank, dark dungeon. :)
Ryan
Posts: 695
Joined: Jun 10, 2005 2:13
Location: Louisville, KY
Contact:

Post by Ryan »

And if all the monster has is one big nose, it can hunt you down. ^_^ I like the idea of having monsters use all three methods of finding the player. It's nice to use pathfinding if there are other things in the room besides the player, too. (Other monsters..) Though I guess full fledged pathfinding might not be needed for that.
inded005
Posts: 126
Joined: Jan 04, 2006 5:43
Location: Kingaroy Australia

Post by inded005 »

that code is very hard to understand...
i wrote an a* with opens/closed lists, and a bit of terrain analysis, trouble implementing it though. will anyone be interested in seeing it?
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Post by Dr_D »

Sure. Here's something I made also. It isn't 3D, but it shows that FB is more than fast enough. ;)
http://www.freebasic.net/forum/viewtopi ... 0234#40234
Ryan
Posts: 695
Joined: Jun 10, 2005 2:13
Location: Louisville, KY
Contact:

Post by Ryan »

That is awesome. I can't believe I never saw that back in the day. : P
inded005
Posts: 126
Joined: Jan 04, 2006 5:43
Location: Kingaroy Australia

Post by inded005 »

here's my attempt at implementing A*.

Code: Select all


function GameUnitType.WayPathGen(Xt As integer,Yt As integer) As integer 
 ' =========================================================
 ' Function: Uses A* pathfinding algorithm to generate 
 '            a path from this(x,y) to Xt,Yt
 ' Params: target/destination coords (as 32x32 tile coords)
 ' Returns: fills an array(the unit's WayPath()) and returns length(int) of the path found 
 ' Notes: Uses simple distance-based heuristic.(beeline dist to tgt x 10)
 '        this causes it to use diagonals alot
 '==========================================================
 
  Debug "Starting Gen............................"
 ' 1. Add the starting node
 
 Dim As integer Xs,Ys,nextOpen,nextClosed, IsTgt
 Dim As integer iX,iY,i,lowF,f,lowFref, foundPath, tempF 
 
 Debug "resetting arrays..."
 Dim wpt(MAP_XS,MAP_YS) As WptSqrType 
 Dim opens(MAX_ASTARWPTS*2) As Coords
 ' ==============================================
 ' Notes about the arrays:
 '  wpt(x,y) type definition: (in separate header) 
   '  Type WptSqrType
   '   State As byte ' 0 = none: 1 = open: 2=closed
   '   ParentX As integer 'was ushort
   '   ParentY As integer 'was ushort 
   '   F As ubyte
   '   G As ubyte
   '   H As ubyte
   '   listRef As ushort
   ' End Type
 ' The State byte is essentially the closed list.
 ' If the State is open, it uses listRef to find the node in opens() 
 '  this is to avoid doing looping array lookups.
 ' Same reason i used the 2d arrays: so i can just reference the coords
 ' instead of having to order them or look them up.
 ' Hopefully, it turns out faster. Seems to run pretty fast, and 
 ' I really dont feel like doing a time-cost analysis to compare it
 ' with a version using list objects, ordering, and replacement.
 ' Plenty of memory available too, so why not?
 ' Anyway, as you can see, the F, G, and H costs for each square considered
 ' are recorded. The G cost is the movement cost up to this square.
 ' Diagonal moves cost more than orthogonal moves. (10 for ortho, 14
 ' for diag)
 ' These scores are calculated when the sqr is added to the 
 ' open list. Squares are added to the opens() when they are candidates for 
 ' movement; the only criteria being they must be within bounds, and must
 ' not be solid.     
 ' The ParentX/Y are really a 2D index reference to the square that precedes this
 ' node in the eventual "finished product" waypath.
 '
 ' The opens() array is just a simple integer coords type (x,y).
 ' These coordinate serve, again, as an index reference into the 
 ' wpt() array. For a square/node to be "open" it must be on this
 ' list, else the algorithm will not consider/discover it as a better
 ' option... an integral part of shortest-path wayfinding!
 '
 ' There is another 2D array, MapSqr(x,y), that stores map tile
 ' info such as fog, solid state, and importantly to this alg, Cost.
 ' This cost is added to each square when it's F cost is being
 ' calculated.
 '
 ' Oh, and Debug is just a trace func that outputs to console.
    
 Debug " done local Dims"
    Xs = X
    Ys = Y
    ToSqrs(Xs,Ys) ' convert pixel coords to tile size coords (32x32) (bitshifts right 5)
 Debug " STARTSQR: try to add wpt at "+Str(Xs)+":"+Str(Ys)   
    wpt(Xs,Ys).State = 1 ' puts current unit sqr on opens    
 Debug " STARTSQR: try to add to opens at "+Str(Xs)+":"+Str(Ys)+" nO:"+Str(nextOpen)
    nextOpen = 1
    wpt(Xs,Ys).listRef = nextOpen   
    opens(nextOpen).X = Xs
    opens(nextOpen).Y = Ys
    nextOpen += 1
    lowF = 255
    IF DEBUGGING = TRUE then ScreenSet Primary,Primary
 Debug "-done add start node"
  
  ' 550 is a magic number, I know, but it's just
  ' how many nodes (map squares) the alg will try before
  ' giving up; therefore this governs the max length of
  ' the end result path....  
  For i = 1 To 550
    ' get lowest F score sqr in the open list
    ' for i = 1 this will always be the unit's start pos 
    ' or, (Xs, Ys)
    lowF = 255
    For f = 0 To MAX_ASTARWPTS
      If opens(f).X > 0 And opens(f).Y > 0 Then
         If wpt(opens(f).X,opens(f).Y).F < lowF and _
            wpt(opens(f).X,opens(f).Y).listRef > 0 and _
            wpt(opens(f).X,opens(f).Y).State = 1 Then
               lowF = wpt(opens(f).X,opens(f).Y).F
               lowFref = f           
         End If
      End if
    Next f
    Debug "-done find next low F"
    
    Xs = opens(lowFref).X
    Ys = opens(lowFref).Y
    Debug "curSqr:"+Str(Xs) +" "+ Str(Ys)+" openFRef:"+Str(lowFref)+" F:"+Str(lowF)
    'If Xs = 0 And Ys = 0 Then 
    ' Debug "waypathGen died"
    ' Return 0
    'End if  
    If Xs = Xt And Ys = Yt Then
       ' target square found!
       Debug "hallelujah"
       foundPath = 1
       Exit for
    End If
    
    ' put it on closed 'list' (state) (and take it off opens list)
    ' the path will DEFINITELY use this node, since it has the
    ' lowest F cost.
    wpt(Xs,Ys).listRef = 0
    wpt(Xs,Ys).State = 2
    opens(lowFref).X = 0
    opens(lowFref).Y = 0
    
    For iX = -1 To 1 
       For iY = -1 To 1
          isTgt = 0 
          If iX <> 0 or iY <> 0 Then 
            If Xs + iX > 0 Then           ' -\
            If Ys + iY > 0 Then           '   |__ bounds checking
            If Xs + iX <= MAP_XS Then     '   |
            If Ys + iY <= MAP_YS Then     ' -/
                'DBG:     debug "access:"+Str(Xs+iX)+" : "+Str(Ys+iY)
                'DBG:     If nextOpen = MAX_ASTARWPTS - 1 Then
                'DBG:        Debug "opens nearly full!"
                'DBG:     End if
                     If (Xs+iX=Xt and Ys+iY=Yt) Then isTgt = 1 
                     If MapSqr(Xs+iX,Ys+iY).Cost < 255 or isTgt = 1 Then 
                        ' either not solid, or this is the target
                        Debug "- not solid"
                       If wpt(Xs+iX,Ys+iY).State <> 2 or isTgt = 1 Then 
                          ' not on closed list
                          Debug "- not closed"
                          If wpt(Xs+iX,Ys+iY).State = 1 Then 
                            ' aready open
                            Debug "- already open" 
                           ' check to see if this G score is better
                           Dim As integer tempG
                           If iX = 0 Or iY = 0 Then
                              tempG = wpt(Xs,Ys).G + 10   
                           Else
                              tempG = wpt(Xs,Ys).G + 14
                           End If 
                           'tempG is the Gscore if using currentsqr: (Xs,Ys)
                           If tempG < wpt(Xs+iX,Ys+iY).G Then
                            ' recalc the scores, and change the parent
                            ' since this is a better path
                            wpt(Xs+iX,Ys+iY).ParentX = Xs
                            wpt(Xs+iX,Ys+iY).ParentY = Ys
                             ' get H
                            wpt(Xs+iX,Ys+iY).H = Int(DIST(Xt,Yt,Xs+iX,Ys+iY)) * 10 
                            ' get F
                            tempF = wpt(Xs+iX,Ys+iY).G + wpt(Xs+iX,Ys+iY).H + MapSqr(Xs+iX,Ys+iY).Cost
                            If tempF > 255 Then tempF = 255
                            If isTgt = 1 Then tempF = 1  
                            wpt(Xs+iX,Ys+iY).F = tempF  
                           End If
                          Else   ' not already open
                           Debug "- new open"  
                           wpt(Xs+iX,Ys+iY).ParentX = Xs
                           wpt(Xs+iX,Ys+iY).ParentY = Ys
                           wpt(Xs+iX,Ys+iY).State = 1
                           wpt(Xs+iX,Ys+iY).listRef = nextOpen
                           opens(nextOpen).X = Xs+iX
                           opens(nextOpen).Y = Ys+iY
                           nextOpen += 1
                           Debug "created new open"
                           Line((Xs+iX) Shl 5,(Ys+iY) Shl 5)-Step(5,15),RGB(20,255,0),b
                           ' get G 
                           Debug "X:"+Str(Xs+iX)+ " Y:"+Str(Ys+iY)                           
                           If iX = 0 Or iY = 0 Then
                              wpt(Xs+iX,Ys+iY).G = wpt(Xs,Ys).G + 10   
                           Else
                              wpt(Xs+iX,Ys+iY).G = wpt(Xs,Ys).G + 14
                           End If 
                           Debug "calc'ed new G"
                           ' get H
                           wpt(Xs+iX,Ys+iY).H = int(DIST(Xt,Yt,Xs+iX,Ys+iY)) * 10 
                           ' get F                           
                           ' use tempF to avoid overflows (is this necessary?)
                           tempF = wpt(Xs+iX,Ys+iY).G + wpt(Xs+iX,Ys+iY).H + MapSqr(Xs+iX,Ys+iY).Cost
                           If tempF > 255 Then tempF = 255
                           If isTgt = 1 Then tempF = 1  
                           wpt(Xs+iX,Ys+iY).F = tempF                                                      
                          End If
                          Debug "- done check opens"
                       End If    
                     End If 
                   End If
                End If
             End If
            End If
          End if  
       Next iY
    Next iX 
    If DEBUGGING = TRUE then Line(Xs Shl 5 , Ys Shl 5)-Step(5,5),RGB(255,255,0),b
 Next i
 Debug "sqrs used:"+Str(i)
 
 If foundPath = 1 Then
    ' work back from Xt,Yt
    Dim As integer newXs,newYs
    Xs = Xt
    Ys = Yt
    For i = 1 To 50
       With wpt(Xs,Ys)
        asWpt(i).X = Xs Shl 5
        asWpt(i).Y = Ys Shl 5
        asWpt(i).Parent = i - 1
        If DEBUGGING = TRUE Then 
         Line(Xs Shl 5, Ys Shl 5)-Step(15,5),RGB(255,255,255),b
         If i > 1 Then
            Line (Xs Shl 5, Ys Shl 5)-(asWpt(i-1).X, asWpt(i-1).Y),RGB(255,0,255)
         End if   
        End if 
        If Xs Shr 5 = (X Shr 5) And Ys Shr 5 = Y Shr 5 Then
           Debug "traced back to start"
           CASP = i
           Exit for
        End if
        If .ParentX = 0 And .ParentY = 0 Then
           Debug "traced back to parents (0,0)"
           CASP = i
           Exit for   
        Else
           newXs = .ParentX
           newYs = .ParentY
           Xs = newXs
           Ys = newYs
           Debug "new asWpt:"+Str(i)+ ": "+Str(Xs)+ "-"+Str(Ys)
        End if   
       End with
    Next i
 Else
  Debug "didnt find a path"   
 End If
 IF DEBUGGING = TRUE then 
  Sleep
  ScreenSet Primary,Secondary
 End if   
 Return casp

End Function  





Sub GameUnitType.MoveToTarget   
 ' =========================================================
 ' Function: *WAS* a Pathfinding proc for unit. move 2ward X/Ytgt
 ' Params: 
 ' Returns: 
 ' Notes:  OLD NOTES The current avoidStep is the main thing.
 '         if it's 1: apply a wing-and-finger spreading
 '         pathfinder around the unit to find an opening
 '         in the obstacles.
 '         2: follow the waypath set out by avoidStep 1
 '         else: beeline towards X/Ytgt
 '==========================================================
 Dim As Integer xo, yo, wpg 
 If CVAR_nomovelimit = True Then AvoidStep = 0
 If CASP = 0 Then 
  'If DIST(X,Y,Xtgt,Ytgt) > 8 then  
  Clear asWpt(1), 0, Len(asWpt(1)) * UBound(asWpt)
   wpg = WayPathGen(Xtgt Shr 5,Ytgt Shr 5) 
   Debug "wayPathGen:"+Str(wpg)
   IF wpg = 0 then CASP = -1
  'End if
 Else
   ' Debug " MoveToTgt: casp:"+Str(CASP)
 End if 
 AvoidStep = 3 ' hack: this is to use WayPathGen waypoints instead.
  Select Case AvoidStep
   Case 1                
    ' old wingandfinger waypathing went here
   Case 3 ' FOLLOW A* WAYPOINTS
    If CASP > 0 Then
    If X Shr 5 = asWpt(CASP).X Shr 5 And Y shr 5  = asWpt(CASP).Y Shr 5 Then
      Debug "reached asWpt#"+Str(CASP) 
      CASP -= 1      
      If CASP <= 0 Then 
         CASP = -1
         Debug "reached casp=0"
         'reset unit's wpts to blank
         ResetWPTs
         Exit sub
      End if 
    End If         
    With asWpt(CASP)  
     If DEBUGGING = True Then Line(.X,.Y)-Step(4,2),Rgb(90,255,0),bf 
     If X Shr 5 < .X Shr 5 Then xo = 1            
     If X Shr 5 > .X Shr 5 Then xo = -1
     If Y Shr 5 < .Y Shr 5 Then yo = 1
     If Y Shr 5 > .Y Shr 5 Then yo = -1  
     'Debug " follow casp:"+Str(xo)+" :"+Str(yo)  
    End With 
    Else
      ' Debug "CASP <= 0: "+Str(CASP)
    End if
   Case 2
    ' follow the wing waypaths 
    If Int(X/4) = Int(WingWpt(CWWP).X/4) And Int(Y/4) = Int(WingWpt(CWWP).Y/4) Then
     CWWP = WingWpt(CWWP).Link(1)
     If DEBUGGING = True Then PlaySound 7
     If CWWP = 0 Then 
      ' beeline baby       
      If DEBUGGING = True Then  PlaySound 6
      AvoidStep = 3
      Exit Sub
     End If 
    End If 
    With WingWpt(CWWP)  
     If DEBUGGING = True Then Line(.X,.Y)-Step(2,2),Rgb(90,255,0),bf 
     If X Shr 2 < .X Shr 2 Then xo = 1            
     If X Shr 2 > .X Shr 2 Then xo = -1
     If Y Shr 2 < .Y Shr 2 Then yo = 1
     If Y Shr 2 > .Y Shr 2 Then yo = -1     
    End With               
    With WingWpt(WingWpt(CWWP).Link(1))  
     If DEBUGGING = True Then Line(.X,.Y)-Step(2,2),Rgb(0,255,90),bf 
    End With 
   Case Is > 40
    AvoidStep = 1 
   Case Else               
    ' these are only shr 2 to allow for more accurate targeting
    ' (i.e. so xo or yo can be 0, stops the unit from bouncing between 1 and -1)
    If Xtgt Shr 3 < X Shr 3 Then xo = -1 
    If Xtgt Shr 3 > X Shr 3 Then xo = 1 
    If Ytgt Shr 3 < Y Shr 3 Then yo = -1 
    If Ytgt Shr 3 > Y Shr 3 Then yo = 1 
   End Select 
    If DIST(X,Y,Xtgt,Ytgt) > 16 Then
     If xo <> 0 And yo <> 0 Then
      If Int(Rnd*3) = 0 Then 
       yo = 0  
      Else
       xo = 0
      End If 
     End If
     Move xo,yo                
    Else
     AvoidStep = 0 
    End If 
    OldXO = xo
    OldYO = yo
End Sub

This is just a fragment(!!!), it won't compile/run.
If you don't know much about A*, i doubt you will be able to follow it.
(I will be surprised if you 'follow' it even if you know all about A*!!)
It works fine MOST times, but doesnt do very long paths for some reason.
(ie only about 15 squares long)
I might upload a stripped-down demonstration version of the game i'm using it in.
inded005
Posts: 126
Joined: Jan 04, 2006 5:43
Location: Kingaroy Australia

Post by inded005 »

ok... I fixed everything (including the small distances), and uploaded a demo of the waypathing in my game to <a href = http://members.dodo.com.au/pklanham/kngt_a04.zip > this location </a> . check it out, some feedback (esp. regarding the movement) would be cool
cheers
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Post by Dr_D »

looks like that could end up being pretty damn fun dude... keep it up! :)
Torahteen
Posts: 91
Joined: Jul 15, 2005 15:58
Contact:

Post by Torahteen »

Hehe, very cool guys. I remember writing my A* implementation. It's back on QBN. If anyone's interested, I can get the link. Also, someone mention LOS. I did make an LOS tutorial on Pete's QB Express :P.
Post Reply