/*========================================================================
    精简的A*算法    作者:添翼虎
    网址:http://tyhweb.163.net  Email:tyhweb@163.net
    本程序参考了风云的最短路径代码(http://member.nease.com/~cloudwu),
    并加以改进和优化:
    1、把原来用于存放已处理节点的堆栈改为(store_queue)队列,这样在从
       sort_queue队列出列时可直接放入store_queue中。
    2、解除了地图大小的限制(如果有64K内存限制时,地图大小只能是180x180)
    3、删除了原程序中的一些冗余,见程序中的注释。
    4、程序继续使用dis_map数组保存各点历史历史最佳距离,也包含了某点是否已经
       经过的信息,虽然这样做可能会比使用链表多用一些内存,但是在搜索时可以
       节省不时间。
    5、程序更具有实用性,可直接或修改后运用于你的程序中,但请你使用该代码后
       应该返回一些信息给我,如算法的改进或使用于什么程序等。
       本程序可以用Borland C++或DJGPP编译,并附带有一个数据文件 map.dat,
    保存有地图的数据,(注:该地图文件格式与风云的原代码的地图格式不一样)
    算法描述:
    findpath()
    { 
        把S点加入树根(各点所在的树的高度表示从S点到该点所走过的步数);
        把S点加入排序队列(按该点到E点的距离排序+走过的步数从小到大排序);
        1、排序队列sort_queue中距离最小的第一个点出列,并保存入store_queue中
        2、从出列的点出发,分别向4个(或8个)方向中的一个各走出一步
        3、并估算第2步所走到位置到目标点的距离,并把该位置加入树,
        最后把该点按距离从小到大排序后并放入队列中。(由trytile函数实现)。
        4、如果该点从四个方向上都不能移动,则把该点从store_queue中删除
        5、回到第一点,直到找到E点则结束
        从目标点回溯树,直到树根则可以找到最佳路径,并保存在path[]中
    }
-------------------------------------------------------------------------*/
//#define NDEBUG
#include <stdio.h>
#include <conio.h>
#include <assert.h>
#include <stdlib.h>
#include <mem.h>

#define tile_num(x,y) ((y)*map_w+(x))  //将 x,y 坐标转换为地图上块的编号
#define tile_x(n) ((n)%map_w)          //由块编号得出 x,y 坐标
#define tile_y(n) ((n)/map_w)

#define MAPMAXSIZE 180  //地图面积最大为 180x180,如果没有64K内存限制可以更大
#define MAXINT 32767

//树结构, 比较特殊, 是从叶节点向根节点反向链接,方便从叶节点找到根节点
typedef struct tree_node *TREE;

struct tree_node {
    int h;            //节点所在的高度,表示从起始点到该节点所有的步数
    int tile;        //该节点的位置
    TREE father;    //该节点的上一步
};

//链接结构,用于保存处理过的和没有处理过的结点
typedef struct link_node *LINK;

struct link_node {
    TREE node;
    int f;
    LINK next;
};

LINK sort_queue;            // 保存没有处理的行走方法的节点
LINK store_queue;            // 保存已经处理过的节点 (搜索完后释放)

unsigned char * map;           //地图数据
unsigned int * dis_map;     //保存搜索路径时,中间目标地最优解

int map_w,map_h;                             //地图宽和高
int start_x,start_y,end_x,end_y;             //地点,终点坐标

// 初始化队列
void init_queue(void)
{
    sort_queue=(LINK)malloc(sizeof(*sort_queue));
    sort_queue->node=NULL;
    sort_queue->f=-1;
    sort_queue->next=(LINK)malloc(sizeof(*sort_queue));
    sort_queue->next->node=NULL;
    sort_queue->next->f=MAXINT;
    sort_queue->next->next=NULL;

    store_queue=(LINK)malloc(sizeof(*store_queue));
    store_queue->node=NULL;
    store_queue->f=-1;
    store_queue->next=NULL;
}

// 待处理节点入队列, 依靠对目的地估价距离插入排序
void enter_queue(TREE node,int f)
{
    LINK p=sort_queue,father,q;
    while(f>p->f) {
        father=p;
        p=p->next;
        assert(p);
    }
    q=(LINK)malloc(sizeof(*q));
    assert(sort_queue);
    q->f=f,q->node=node,q->next=p;
    father->next=q;
}

// 将离目的地估计最近的方案出队列
TREE get_from_queue(void)
{
    LINK bestchoice=sort_queue->next;
    LINK next=sort_queue->next->next;
    sort_queue->next=next;

    bestchoice->next=store_queue->next;
    store_queue->next=bestchoice;
    return bestchoice->node;
}

// 释放栈顶节点
void pop_stack(void)
{
    LINK s=store_queue->next;
    assert(s);
    store_queue->next=store_queue->next->next;
    free(s->node);
    free(s);
}

// 释放申请过的所有节点
void freetree(void)
{
    int i;
    LINK p;
    while(store_queue){
        p=store_queue;
        free(p->node);
        store_queue=store_queue->next;
        free(p);
    }
    while (sort_queue) {
        p=sort_queue;
        free(p->node);
        sort_queue=sort_queue->next;
        free(p);
    }
}

// 估价函数,估价 x,y 到目的地的距离,估计值必须保证比实际值小
int judge(int x,int y)
{
    int distance;
    distance=abs(end_x-x)+abs(end_y-y);
    return distance;
}

// 尝试下一步移动到 x,y 可行否
int trytile(int x,int y,TREE father)
{
    TREE p=father;
    int h;
    if (map[tile_num(x,y)]!=' ') return 1; // 如果 (x,y) 处是障碍,失败
    //这一步用来判断(x,y)点是否已经加入队列,多余可以删除,因为dis_map已经
    //保存该点是否已经保存
    //while (p) {
    //  if (x==tile_x(p->tile) && y==tile_y(p->tile)) return 1; //如果 (x,y) 曾经经过,失败
    //  p=p->father;
    //}
    h=father->h+1;
    if (h>=dis_map[tile_num(x,y)]) return 1; // 如果曾经有更好的方案移动到 (x,y) 失败
    dis_map[tile_num(x,y)]=h; // 记录这次到 (x,y) 的距离为历史最佳距离

    // 将这步方案记入待处理队列
    p=(TREE)malloc(sizeof(*p));
    p->father=father;
    p->h=father->h+1;
    p->tile=tile_num(x,y);
    enter_queue(p,p->h+judge(x,y));
    return 0;
}

// 路径寻找主函数
int * findpath(void)
{
    TREE root;
    int i,j;
    int * path;
    memset(dis_map,0xff,map_h*map_w*sizeof(*dis_map));    //填充dis_map为0XFF,表示各点未曾经过
    init_queue();
    root=(TREE)malloc(sizeof(*root));
    root->tile=tile_num(start_x,start_y);
    root->h=0;
    root->father=NULL;
    enter_queue(root,judge(start_x,start_y));
    for (;;) {
        int x,y,child;
        TREE p;
        root=get_from_queue();
        if (root==NULL) {
            return NULL;
        }
        x=tile_x(root->tile);
        y=tile_y(root->tile);
        gotoxy(x+1,y+1);
        putchar('\'');
        if (x==end_x && y==end_y) break; // 达到目的地成功返回

        child=trytile(x,y-1,root);  //尝试向上移动
        child&=trytile(x,y+1,root); //尝试向下移动
        child&=trytile(x-1,y,root); //尝试向左移动
        child&=trytile(x+1,y,root); //尝试向右移动
        //child&=trytile(x+1,y-1,root);//尝试向右上移动
        //child&=trytile(x+1,y+1,root); //尝试向右下移动
        //child&=trytile(x-1,y+1,root); //尝试向左下移动
        //child&=trytile(x-1,y-1,root); //尝试向左上移动

        if (child!=0)
            pop_stack();  // 如果四个方向均不能移动,释放这个死节点
    }

    // 回溯树,将求出的最佳路径保存在 path[] 中
    path=(int*)malloc((root->h+2)*sizeof(int));
    assert(path);
    for (i=0;root;i++) {
        path[i]=root->tile;
        root=root->father;
    }
    path[i]=-1;
    freetree();
    return path;
}

void printpath(int *path)
{
    int i;
    if(path==NULL)    return ;
    for (i=0;path[i]>=0;i++) {
        gotoxy(tile_x(path[i])+1,tile_y(path[i])+1);
        cprintf(".");
    }
}

int readmap(void)
{
    FILE *f;
    int i,j;
    f=fopen("map.dat","r");
    assert(f);
    fscanf(f,"%d,%d\n",&map_w,&map_h);
    map=malloc(map_w*map_h+1);
    assert(map);
    for(i=0;i<map_h;i++)
        fgets(map+tile_num(0,i),map_w+2,f);
    fclose(f);
    start_x=-1,end_x=-1;
    for (i=0;i<map_h;i++)
        for (j=0;j<map_w;j++) {
            if (map[tile_num(j,i)]=='s') map[tile_num(j,i)]=' ',start_x=j,start_y=i;
            if (map[tile_num(j,i)]=='e') map[tile_num(j,i)]=' ',end_x=j,end_y=i;
        }
    assert(start_x>=0 && end_x>=0);
    dis_map=malloc(map_w*map_h*sizeof(*dis_map));
    assert(dis_map);
    return 0;
}

void showmap(void)
{
    int i,j;
    clrscr();
    for (i=0;i<map_h;i++) {
        gotoxy(1,i+1);
        for (j=0;j<map_w;j++)
            if (map[tile_num(j,i)]!=' ') cprintf("O");
            else cprintf(" ");
    }
    gotoxy(start_x+1,start_y+1);
    cprintf("s");
    gotoxy(end_x+1,end_y+1);
    cprintf("e");
}

int main()
{
    int * path;
    readmap();
    showmap();
    getch();
    path=findpath();
    printpath(path);
    if(dis_map) free(dis_map);
    if(path) free(path);
    if(map) free(map);
    getch();
    return 0;
}

/*===============================================================================
地图文件:map.dat
79,24
ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
o                o                                                            o
o ooooooo        o                                                            o
o       o        o           oooooooooooooo                                   o
o    s  o                                 o                                   o
oooooooooooooooooo                        o                                   o
o                oooooooooooooooooooooooooo       oooooooo                    o
o    oooooo      o     oooo                       o      o                    o
o                o        o                       ooo  ooo                    o
o                oooo  oooo                                                   o
o                         o      oooooooooooooooooooooooooooooooooooooooooooooo
o  oooooooooooooooooooooooo                                                   o
o                                                                             o
o                                                                             o
ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo             o
o       o                                                           ooooooooooo
o       o   ooooooo                                             o             o
o       o         o                                             o             o
o       ooooooooooo        oooooooooo                           o             o
o                          o     e  ooo                         o             o
o                          ooooo      o                         o             o
o                              o      oooooooooooooooooo oooooooo             o
o                              o                                              o
ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
---------------------------------------------------------------------------------*/