1. 程式人生 > >【C / EasyX】隨機迷宮生成和路徑搜尋(DFS)

【C / EasyX】隨機迷宮生成和路徑搜尋(DFS)

==========================================說明=====================================
1.用了寫遊戲的基本流程所以介面是持續重新整理的(可能會改成遊戲)
2.迷宮生成演算法是深度優先遍歷(DFS),為了讓路線不那麼容易走通,是從終點向起點生成的。

PS1:迷宮難度不高,路線完全隨機似乎不是太好,有待改進。

PS2:至於EasyX,是個C語言可用的不錯的圖形庫,下載的話百度一下就有了。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <graphics.h>
//////////////////////////////以下是 巨集定義引數 //////////////////////////////
#define WIN_WIDTH 1200                      // 窗體寬度
#define WIN_HEIGHT 680                      // 窗體高度
#define MAZE_WIDTH 15*34                    // 迷宮寬度
#define MAZE_HEIGHT 15*17                   // 迷宮高度
#define B_WIDTH 10                          // 格子寬度
#define X 51                                // 迷宮列數 
#define Y 26                                // 迷宮行數 
#define N X*2+1                             // 迷宮陣列列數 
#define M Y*2+1                             // 迷宮陣列行數 
#define L (N-1)*(M-1)                       // 迷宮路徑長度
#define LX 80                               // 起點座標X
#define LY 50                               // 起點座標Y
#define C_BLOCK RGB(153,102,85)             // 牆壁顏色
#define C_GRAY RGB(123,72,55)               // 陰影顏色
#define C_GROUND RGB(50,50,50)              // 地面顏色
#define C_ROOT RGB(170,170,210)             // 路徑顏色
#define C_LINE YELLOW           // 路徑線顏色
//////////////////////////////以下是  函式宣告  //////////////////////////////
float GetFPS();                             // 計算畫面FPS(每秒幀數)
void Delay(DWORD ms);                       // 絕對延時
bool IsInRect(int x,int y,RECT r);          // 是否在矩形內
void InitMaze();                            // 初始化迷宮陣列
void OutputMaze();                          // 輸出迷宮
void CopyScreenBuffer();                    // 複製螢幕到快取
void DrawScreenBuffer();                    // 繪製快取到螢幕
void OutPutPath();                          // 輸出路徑連線
void DrawFPS();                             // 輸出幀數
int Move(int d,int x,int y);                // 向指定方向移動,返回是否成功移動
void DFS_CreateMaze(int x,int y);           // 深度優先生成迷宮
void FindPath(int i,int j);                 // 深度優先搜尋迷宮路徑
void CreateMaze();                          // 建立迷宮
void DrawMenuItems();                       // 繪製選單按鈕
//////////////////////////////以下是全域性變數定義//////////////////////////////
DWORD* p_Screen;                                // 視訊記憶體指標
DWORD buff_Screen[WIN_WIDTH*WIN_HEIGHT];        // 顯示快取
int isShowFPS=1;                                // 是否顯示幀數
int isShowMazePath=0;                           // 顯示迷宮路徑
int Maze[M][N];                                 // 迷宮陣列   0 普通通路 1 牆 2 路徑點 3 起點 4 終點
int path_x[1000][L],path_y[1000][L];            // 路徑座標
int length[L];                                  // 路徑搜尋長度
int lengtha,count;                              // 搜尋計數
int mouseX;                                     // 滑鼠位置座標X
int mouseY;                                     // 滑鼠位置座標Y
bool isMouseDown;                               // 滑鼠按下
int start_x,start_y,end_x,end_y;                // 起點座標 終點座標
int temp_d;                                     // 上一步走的方向
int Move_flag;                                  // 移動標記
//////////////////////////////以下是輔助功能函式//////////////////////////////
// 計算畫面FPS(每秒幀數)
float GetFPS()
{
#define FPS_COUNT 8
    static int i = 0;
    static DWORD oldTime = GetTickCount();
    static float fps;
    if (i > FPS_COUNT)
    {
        i = 0;
        int newTime = GetTickCount();
        int elapsedTime = newTime - oldTime;
        fps = FPS_COUNT / (elapsedTime / 1000.0f);
        oldTime = newTime;
    }
    i++;
    return fps;
}
// 絕對延時
void Delay(DWORD ms)
{
    static DWORD oldTime = GetTickCount();
    while(GetTickCount() - oldTime < ms)
        Sleep(1);
    oldTime = GetTickCount();
}
// 是否在矩形內
bool IsInRect(int x,int y,RECT r)
{
    if((x>=r.left && x<=r.right) && (y>=r.top && y<=r.bottom)) return true;
    else return false;
}
// 初始化迷宮陣列 
void InitMaze(){
    for(int i=0;i<M;i++){
        for(int j=0;j<N;j++){
            Maze[i][j]=1;
        }
    }
    // 設定起點和終點
    Maze[1][0]=3;
    Maze[M-2][N-1]=4;
}
// 輸出迷宮 
void OutputMaze(){
    // 清屏
    setcolor(RGB(255,255,255));
    setfillcolor(RGB(255,255,255));
    fillrectangle(0,0,WIN_WIDTH,WIN_HEIGHT);
    for(int i=0;i<M;i++){
        for(int j=0;j<N;j++){
            int xx=LX+j*B_WIDTH;
            int yy=LY+i*B_WIDTH;
            switch(Maze[i][j]){
                case 0:
                    setcolor(C_GROUND);
                    setfillcolor(C_GROUND);
                    fillrectangle(xx,yy,xx+B_WIDTH,yy+B_WIDTH);
                    break;
                case 1:
                    setcolor(C_BLOCK);
                    setfillcolor(C_BLOCK);
                    fillrectangle(xx,yy,xx+B_WIDTH,yy+B_WIDTH);
                    if(Maze[i][j]==1 && (i==M-1 || Maze[i+1][j]!=1))
                    {
                        setcolor(C_GRAY);
                        setfillcolor(C_GRAY);
                        fillrectangle(xx,yy+B_WIDTH/2,xx+B_WIDTH,yy+B_WIDTH);
                    }
                    break;
                case 2:
                    setcolor(C_GROUND);
                    setfillcolor(C_GROUND);
                    fillrectangle(xx,yy,xx+B_WIDTH,yy+B_WIDTH);
                    /*
                    setcolor(C_ROOT);
                    setfillcolor(C_ROOT);
                    fillellipse(xx+B_WIDTH/2-1,yy+B_WIDTH/2-1,xx+B_WIDTH/2+1,yy+B_WIDTH/2+1);
                    */
                    break;
                case 3:
                    setcolor(C_GROUND);
                    setfillcolor(C_GROUND);
                    fillrectangle(xx,yy,xx+B_WIDTH,yy+B_WIDTH);
                    break;
                case 4:
                    setcolor(C_GROUND);
                    setfillcolor(C_GROUND);
                    fillrectangle(xx,yy,xx+B_WIDTH,yy+B_WIDTH);
                    break;
            }
        }
    }
    
    
}
// 複製螢幕到快取
void CopyScreenBuffer()
{
    for(int i=0;i<WIN_WIDTH*WIN_HEIGHT;i++)
    {
        buff_Screen[i] = p_Screen[i];
    }
}
// 繪製快取到螢幕
void DrawScreenBuffer()
{
    for(int i=0;i<WIN_WIDTH*WIN_HEIGHT;i++)
    {
         p_Screen[i]=buff_Screen[i];
    }
}
// 輸出路徑連線
void OutPutPath()
{
    if(isShowMazePath)
    {
        for(int i=0;i<length[0]-1;i++)
        {
            setcolor(C_LINE);
            int x1=path_x[0][i]*B_WIDTH + B_WIDTH/2 + LX;
            int x2=path_x[0][i+1]*B_WIDTH + B_WIDTH/2 + LX;
            int y1=path_y[0][i]*B_WIDTH + B_WIDTH/2 + LY;
            int y2=path_y[0][i+1]*B_WIDTH + B_WIDTH/2 + LY;
            line(x1,y1,x2,y2);
        }
    }
}
// 輸出幀數
void DrawFPS()
{
    //輸出幀數
    if(isShowFPS)
    {
        setcolor(RED);
        settextstyle(14, 0, _T("宋體"));
        TCHAR  s[5];
#if _MSC_VER > 1200
        _stprintf_s(s, _T("%.1f"), GetFPS());
#else
        _stprintf(s, _T("%.1f"), GetFPS());
#endif
        outtextxy(4, 2, _T("FPS:"));
        outtextxy(34, 2, s);
    }
}
// 向指定方向移動,返回是否成功移動
int Move(int d,int x,int y)
{
    switch(d)
    {
    // 向上
    case 0:{
        if(Maze[y-3][x]>0 && Maze[y-2][x-1]>0 && Maze[y-2][x+1]>0 && y-2>0)
        {
            Maze[y-2][x]=0;
            Maze[y-1][x]=0;
            Move_flag=1;
            temp_d=0;
            DFS_CreateMaze(x,y-2);
        }
        break;
           }
    // 向下
    case 1:{
        if(Maze[y+3][x]>0 && Maze[y+2][x-1]>0 && Maze[y+2][x+1]>0 && y+2<M)
        {
            Maze[y+2][x]=0;
            Maze[y+1][x]=0;
            Move_flag=1;
            temp_d=1;
            DFS_CreateMaze(x,y+2);
        }
        break;
           }
    // 向左
    case 2:{
        if(Maze[y][x-3]>0 && Maze[y-1][x-2]>0 && Maze[y+1][x-2]>0 && x-2>0)
        {
            Maze[y][x-1]=0;
            Maze[y][x-2]=0;
            Move_flag=1;
            temp_d=2;
            DFS_CreateMaze(x-2,y);
        }       
        break;
           }
    // 向右
    case 3:{
        if(Maze[y][x+3]>0 && Maze[y-1][x+2]>0 && Maze[y+1][x+2]>0 && x+2<N)
        {
            Maze[y][x+1]=0;
            Maze[y][x+2]=0;
            Move_flag=1;
            temp_d=3;
            DFS_CreateMaze(x+2,y);  
        }
        break;
           }
    }
    return Move_flag;
}// 深度優先生成迷宮
// 深度優先生成迷宮
void DFS_CreateMaze(int x,int y)
{
    //置通路
    Maze[y][x]=0;
    //隨機方向
    int d=rand()%4;
    int direct=temp_d;
    int t[4]={0,0,0,0};// 已走方向標記
    Move_flag=0;// 重置移動標記
    // 保證隨機性的同時走滿地圖
    while(t[0]!=1 || t[1]!=1 || t[2]!=1 || t[3]!=1)
    {
        int i=rand()%4;
        if(t[i]!=1) 
        {
            t[i]=1;
            Move(i,x,y);
        }
    }
}
// 深度優先搜尋迷宮路徑
void FindPath(int x,int y)
{
    // 置路徑
    Maze[y][x]=2;
    path_x[count][lengtha]=x;
    path_y[count][lengtha]=y;
    lengtha++;
    // 搜尋到目的地
    if(x==end_x && y==end_y)
    {
        //success=1;
        length[count] = lengtha;
        count++;
        if(count>0)
        {
            // 複製上一次的路徑到下一次待搜尋路徑中
            for(int i=0;i<length[count-1];i++)
            {
                path_x[count][i]=path_x[count-1][i];
                path_y[count][i]=path_y[count-1][i];
            }
        }
    }
    else
    {
        // 搜尋並回溯
        if(Maze[y][x+1]==0) {FindPath(x+1,y); Maze[y][x+1]=0;lengtha--;}
        if(Maze[y+1][x]==0) {FindPath(x,y+1); Maze[y+1][x]=0;lengtha--;}
        if(Maze[y][x-1]==0) {FindPath(x-1,y); Maze[y][x-1]=0;lengtha--;}
        if(Maze[y-1][x]==0) {FindPath(x,y-1); Maze[y-1][x]=0;lengtha--;}
    }
    //if(success!=1)
    //return success;
}
// 建立迷宮
void CreateMaze()
{
    // 初始化迷宮陣列
    InitMaze();
    isShowMazePath = 0;
    // 從終點開始逆向生成迷宮
    start_x=1;
    start_y=1;
    end_x=N-2;
    end_y=M-2;
    DFS_CreateMaze(end_x,end_y);
    // 搜尋路徑
    lengtha=0;
    count=0;
    FindPath(start_x,start_y);
    // 結果路徑複製到地圖
    for(int i=0;i<length[0];i++)
    {
        Maze[path_y[0][i]][path_x[0][i]]=2;
    }
}
// 繪製選單按鈕
void DrawMenuItems()
{
    RECT r;
    r.left=860;
    r.top=610;
    r.right=r.left+90;
    r.bottom=r.top+38;
    setcolor(RGB(50,50,50));
    if(IsInRect(mouseX,mouseY,r)) 
    {
        setfillcolor(RGB(220,220,220));
        // 生成迷宮按鍵響應
        if(isMouseDown)
        {
            // 建立迷宮
            CreateMaze();
            // 繪製一次到螢幕並複製到快取
            OutputMaze();
            CopyScreenBuffer();
            isMouseDown = 0;
        }
    }
    else setfillcolor(RGB(240,240,240));
    fillrectangle(r.left,r.top,r.right,r.bottom);
    settextstyle(25, 9, _T("Verdana"));
    setcolor(RGB(60,60,60));
    outtextxy(r.left+5, r.top+5,_T(" 生成迷宮"));
    r.left=1020;
    r.top=610;
    r.right=r.left+90;
    r.bottom=r.top+38;
    setcolor(RGB(50,50,50));
    if(IsInRect(mouseX,mouseY,r)) 
    {
        setfillcolor(RGB(220,220,220));
        // 顯示/隱藏路徑
        if(isMouseDown)
        {
            isShowMazePath = !isShowMazePath;
            isMouseDown = 0;
        }
    }
    else setfillcolor(RGB(240,240,240));
    fillrectangle(r.left,r.top,r.right,r.bottom);
    settextstyle(25, 9, _T("Verdana"));
    setcolor(RGB(60,60,60));
    if(isShowMazePath) outtextxy(r.left+5, r.top+5,_T(" 隱藏路徑"));
    else outtextxy(r.left+5, r.top+5,_T(" 顯示路徑"));
}
// 主函式
void main(){
    // 置隨機數種子 
    srand(unsigned(time(NULL)));
    // 初始化裝置,載入圖片
    initgraph(WIN_WIDTH, WIN_HEIGHT);
    // 設定視窗標題
    SetWindowText(GetHWnd(),_T("Maze v0.9 By:ls9512"));
    cleardevice();
    // 設定黑色背景
    setbkmode(TRANSPARENT);
    settextcolor(BLACK);
    // 開啟雙緩衝,防止閃屏
    BeginBatchDraw();
    // 滑鼠訊息變數
    MOUSEMSG mmsg;
    // 獲取視訊記憶體指標
    p_Screen = GetImageBuffer();
    // 建立迷宮
    CreateMaze();
    // 繪製一次到螢幕並複製到快取
    OutputMaze();
    CopyScreenBuffer();
    // 主迴圈
    while(true)
    {
        //處理滑鼠訊息
        while(MouseHit())
        {
            mmsg = GetMouseMsg();
            switch(mmsg.uMsg)
            {
                case WM_MOUSEMOVE:  mouseX = mmsg.x; mouseY = mmsg.y; break;
                case WM_LBUTTONDOWN: isMouseDown=true ;break;
                case WM_LBUTTONUP: isMouseDown=false;break;
            }
        }
        // 繪製,將快取資料複製到螢幕提速
        DrawScreenBuffer();
        OutPutPath();
        DrawMenuItems();
        DrawFPS();
        // 顯示快取的繪製內容
        FlushBatchDraw();
        // 延遲,幀數控制
        Delay(13);
    }
    // 關閉
    EndBatchDraw();
    closegraph();
}