我的一片天

我的一片天
posts - 27, comments - 12, trackbacks - 0, articles - 16
   :: 首页 :: 新随笔 :: 联系 ::  :: 管理

A星(A-Star)寻路算法

Posted on 2007-12-31 21:19 xyz 阅读(2886) 评论(0)  编辑 收藏 引用 所属分类: DELPHI

unit aStarSearchPath;

interface
 uses Classes,SysUtils;
 
type
  pAStarPathNode=^tAStarPathNode;
  tAStarPathNode=record
      x,y:word;
      F:word;
      G:word;
      H:word;
      father:pAStarPathNode; //地图方格的父方格
      prev  :pAStarPathNode; //链表上的上个节点
      next  :pAStarPathNode; //链表上的下个节点
  end;
  tAStarMapBuf=array of byte;

  pAStarArea=^tAStarArea; //区域
  tAStarArea=record
      headNode:pAStarPathNode;
      endNode:pAStarPathNode;
  end;

var
  mapWidth,mapHeight:integer;
  mapToX,mapToY:integer;
  AStarOpenArea:pAStarArea;
  AStarCloseArea:pAStarArea;
  pAStarMapBuf:pByte;
  nearGBuf:array [0..2,0..2] of byte;
 
  function ArrayOf(buf:pByte;x,y,xSize:word):byte;
  function AStarFindPath(var returnPath:tstringlist;fromX,fromY,toX,toY,mapW,mapH:word;mapBuf:pByte):boolean;
  function AStarGetHValue(fX,fY:word):word;
  function AStarGetGValue(fX,fY,tX,tY:word):word;
  procedure AStarInitNearG(x,y:word);
  procedure AStarInsertByF(pHead:pAStarArea;thisNode:pAStarPathNode);
  function AStarSearchMinF(fatherNode:pAStarPathNode):pAStarPathNode;

implementation

  //数组与地图XY的对应关系: ary[y,x] 这样就能对应到地图的(x,y)
  //因此传入的地图数组数据,第1个是Y,第2个是X,要注意与数据的位置对应

  function AStarFindPath(var returnPath:tstringlist;fromX,fromY,toX,toY,mapW,mapH:word;mapBuf:pByte):boolean;
  var
    p:pAStarPathNode;
    i:integer;
    s:string;
  begin
     result:=false;
     pAStarMapBuf:=mapBuf;
     mapToX:=toX;
     mapToY:=toY;
     mapWidth:=mapW;
     mapHeight:=mapH;

     new(AStarCloseArea);    //建立开放区
     AStarCloseArea.headNode:=nil;
     AStarCloseArea.endNode:=nil;

     new(AStarOpenArea);     //建立封闭区
     AStarOpenArea.headNode:=nil;
     AStarOpenArea.endNode:=nil;

     new(p);                //起点加入开放区
     p.x:=fromX;
     p.y:=fromY;
     p.G:=0;
     p.H:=AStarGetHValue(fromx,fromY);
     p.F:=p.G+p.H;
     p.next:=nil;
     p.father:=nil;
     AStarOpenArea.headNode:=p;
     AStarOpenArea.endNode:=p;

     while true do
       begin
        if (p.x=tox) and (p.y=toy) then break; //找到终点就退出
        p:=AStarSearchMinF(p);
        if p=nil then exit;
       end;
     result:=true;
     while true do //从终点开始延者父节点返回到起点就是路径
       begin
         if p=nil then break;
         s:=inttostr(p.x)+','+inttostr(p.y);
         returnPath.Insert(0,s);
         p:=p.father;
       end;
  end;

  //计算到目标点的H值
  function AStarGetHValue(fX,fY:word):word;
  var
    dx,dy:integer;
  begin
    dx:=abs(fX-mapToX);
    dy:=abs(fY-mapToY);
    result:=dx*10+dy*10;
  end;

  //计算周围8个方向的G值
  function AStarGetGValue(fX,fY,tX,tY:word):word;
  var
    dx,dy:integer;
  begin
    dx:=abs(fX-tX);
    dy:=abs(fY)-(tY);
    if (dx=0) and (dy=0) then result:=0
     else
      if dx*dy=0 then result:=10 else result:=14;
  end;

  //通过指针访问数组
  function ArrayOf(buf:pByte;x,y,xSize:word):byte;
  var
    j:integer;
  begin
     j:=y*xSize+x;
     inc(buf,j);
     result:=buf^;
  end; 

  //设置x,y周围8个各自的G值
  procedure AStarInitNearG(x,y:word);
  var
   i,j,v,x1,y1:integer;
   p:pbyte;
  begin
    p:=pAStarMapBuf; //全局地图指针
    fillChar(nearGBuf,9,1);
    for i:=-1 to 1 do  //Y
      begin
        y1:=y+i;
        for j:=-1 to 1 do //X
         begin
           if nearGBuf[i+1,j+1]=0 then continue; //由于路障被预先处理了
           x1:=x+j;
           //中间的点,超出地图范围的点不可用
           if ((i=0) and (j=0)) or (x1<0) or (y1<0) or (x1>mapWidth) or (y1>mapHeight) then v:=-1
             else
               begin
                  v:=ArrayOf(p,x1,y1,mapWidth); //看地图数据里这格是否是路障
                  v:=AStarGetGValue(x,y,x1,y1) * v; //计算临近的格G值
               end;

           if (v=0) and ((x1=x) or (y1=y))  then //格子平行方向不可行或竖向不可行,上下或左右的也不可行
             begin
                if y1=y then  //左右边右路障,因方格有角,路障下面和上面的格也不可过
                  begin
                     nearGBuf[i,j+1]:=0;
                     nearGBuf[i+1,j+1]:=v; //自己=0
                     nearGBuf[i+2,j+1]:=0;
                  end;
                if x1=x then
                  begin
                     nearGBuf[i+1,j]:=0;
                     nearGBuf[i+1,j+1]:=v;
                     nearGBuf[i+1,j+2]:=0;
                  end;
                continue;
             end;
           if v<0 then v:=0; //非超出范围的位置
           nearGBuf[i+1,j+1]:=v;
         end;
      end;
  end;

  //根据thisNode进行F值降序调整链接节点位置或者插入
  procedure AStarInsertByF(pHead:pAStarArea;thisNode:pAStarPathNode);
  var
    p,p2:pAStarPathNode;
    F:word;
  begin
    p:=pHead.headNode;
    while true do
      begin
        if thisNode.F<p.F then //找到F值比自己大的
          begin
             p2:=p.prev; //上个节点
             if p2=nil then //这个节点是头节点
               begin
                 thisNode.prev:=nil;//变成头属性
                 pHead.headNode:=thisNode; //插入第一个节点作为头
               end
             else
               begin
                p2.next:=thisNode; //跟上一个点对接
                thisNode.prev:=p2;
               end;

             p.prev:=thisNode; //插入后对接
             thisNode.next:=p;
             exit;
          end;
        if p.next=nil then //末尾
          begin
             if p=thisNode then exit; //就是自己啦
             p.next:=thisNode;
             thisNode.prev:=p;
             thisNode.next:=nil;
             pHead.endNode:=thisNode;
             exit;
          end;         
        p:=p.next;
      end;
  end;

  //从开发区中选出F值最小的点
  //原理:以基点为中心通过周围8方向辐射的方式不断的添加新格子(以基点作为父节点)到开放区
  //      (完成这个工作后这个辐射基点要加到封闭区)
  // 在辐射的过程中,同时检测早先被加入开放区的格子的G值是不是最小的(从辐射基点到该格子的G值)
  // 如果这个基点到该格子的G值更小,则变换这个格子的父节点为基点并重新计算F值
  //,然后在开放区中选出最小F值的格子,再继续以选定的点往外辐射,直至辐射到目的点
  function AStarSearchMinF(fatherNode:pAStarPathNode):pAStarPathNode;
  var
    x,y,x1,y1:integer;
    p,p2:pAStarPathNode;
    pX,pY:integer;
    g,h,f:word;
  begin
    pX:=fatherNode.x;
    pY:=fatherNode.y;
    AStarInitNearG(pX,pY);//周围的G值如何,路障,原点排除

    for y:=1 downto -1 do  //Y的3行
      begin
        for x:=1 downto -1 do //X的3列
          begin
             x1:=x+pX;    //尝试周围的坐标
             y1:=y+pY;
             g:=nearGBuf[y+1,x+1]; //有可能被预先清0了(路障在中间时)
             if g=0 then continue ;//跳过没有G值的
             p:=AStarOpenArea.headNode;
             while true do  //搜索此点是否在开发区内
               begin
                 if p=nil then break;
                 if (p.x=x1) and (p.y=y1) then break; //在开放区
                 p:=p.next;
               end;

             if p<>nil then //在开放区
               begin
                  g:=p.G+g;  //起点经由选中点到此点的G值
                  if g<p.g then  //经过此点更近,早先设置的父节点需要更改
                    begin
                       p.father:=fatherNode;  //更新它的父节点
                       p.G:=g;
                       p.F:=p.G+p.H;//重新计算F值
                    end;
                  continue;//后面不用再搜索封闭区了
               end;
             p:=AStarCloseArea.headNode;
             while true do  //搜索封闭区
               begin
                  if p=nil then break;  //没有在封闭区
                  if (p.x=x1) and (p.y=y1) then break; //在封闭区
                  p:=p.next;
               end;
             if p=nil then  //这是个新点,设定父节点
               begin
                  new(p);
                  p.prev:=AStarOpenArea.endNode ; //接到最后的开放节点上
                  AStarOpenArea.endNode:=p;
                  p.next:=nil;
                  p.x:=x1;
                  p.y:=y1;
                  p.G:=fatherNode.G+g;
                  p.H:=AStarGetHValue(x1,y1);
                  p.F:=p.G+p.H;
                  p.father:=fatherNode; //父节点,保存是谁把它加入开放区的
                  AStarInsertByF(AStarOpenArea,p);//插入开发区中
               end;
          end; // next x
      end; //next y;

    //把此节点从开放区抽出来,做好开放区节点的重新连接
    if fatherNode.prev=nil then  //当前点是开放区的第一个
      begin
         p:=fatherNode.next;//下一个接的准备变成头
         p.prev:=nil;      //头属性
         AStarOpenArea.headNode:=p;//更新头
      end
    else
      begin
        p:=fatherNode.next;    //下个节点
        p.prev:=fatherNode.prev; //连到这个点的上个节点上
        fatherNode.prev.next:=p;
      end;

    //准备加到封闭区的末尾
    p:=AStarCloseArea.endNode;  //封闭区最末的点
    if p=nil then AStarCloseArea.headNode:=fatherNode; //如果没有,那这个也是头也是未
    AStarCloseArea.endNode:=fatherNode; //是未位
    fatherNode.prev:=nil;
    fatherNode.next:=nil;
    if p<>nil then //有其他的末位
      begin
       p.next:=fatherNode; //连到尾巴上
       fatherNode.prev:=p;
      end;
     
    AStarOpenArea.headNode.prev:=nil; //最小的F已经在首个,设置成首属性
    result:=AStarOpenArea.headNode;//返回F值最小的
  end;

end.

调用方法: AStarFindPath
地图数据为字节型的数组,大小为mapW x mapH,调用时传入该数组的地址(pByte)

 

只有注册用户登录后才能发表评论。