基於Qt的A*算法可視化分析


代碼地址如下:
http://www.demodashi.com/demo/13677.html

需求
之前做過一個無人車需要自主尋找最佳路徑,所以研究了相關的尋路算法,最終選擇A算法,因為其簡單易懂,是入門級的尋路算法。
但是在驗證的算法的時候,沒有直觀的感受,總是覺得會有什么問題,所以我就寫了一個可視化的A
算法驗證,界面基於Qt開發。
項目說明
本項目主要分為2個部分,Qt繪制網格和A算法實現。下面可以看到,界面的實現和A算法的實現基本上是分離的。也就是說可以單獨使用,比如Qt網格繪制,可以用於掃雷游戲,A*算法的代碼可以直接拷貝,稍作修改就可以直接用於游戲,或者無人車。

效果


操作說明
鼠標右鍵:設置障礙物;鼠標左鍵:設置起點和終點。

項目文件截圖

項目實現
程序的實現流程:定義一個最小網格類->通過鼠標設置起點、終點和障礙物,並實時繪制->尋找路徑->繪制路徑。

定義最小網格類
首先,我們需要定義一個最小網格類,用於繪制網格地圖(GridMap)。
網格地圖是將二維場景中的地圖划分為一個個小網格,這個小網格是最小的空間單位,我們可以設置該網格為障礙物、起點或終點。
下面是最小網格類。

class Item
{
public:
    Item();
    Item(QPoint pos);

    QPoint m_pos;		//position
    bool m_bIsObstacle;	//whether is obstacle
    int m_nObjectType;         //the object type , Nothing,Robot or goal

};

可以看出最小網格對象很簡單,只需要一個位置信息,障礙物標志位,機器人或目標點標志。以上就是最小網格的全部屬性,當然如果想要實現更加復雜的功能,可以添加屬性。

初始化地圖
初始化地圖是指將2維地圖划分為網格的過程,其實就是new Item的過程。

void MainWindow::InitItems()
{
	for(int i=0; i<m_nColumes; i++)
	{
		QVector<Item*> rowItems;
		for(int j=0; j<m_nRows; j++)
		{
			QPoint pos = QPoint(i,j);
			Item* pItem = new Item(pos);
			rowItems.append(pItem);		
		}
		m_items.append(rowItems);
	}
}

其中m_nColumes和m_nRows是在初始化地圖之前設置的長寬。

設置障礙物
在尋路之前,我們要先設置好障礙物、起點和終點。設置障礙物用鼠標右鍵,單擊右鍵,設置小網格為障礙物,再次單擊取消障礙物;設置起點和終點用鼠標左鍵,單擊左鍵1次,設置小網格為起點,再次單擊為終點,再次單擊為空白,依次循環。
這里我們用鼠標事件實現,可以看到其實就是根據鼠標事件來設置相應位置Item對象的屬性。代碼很簡單。在設置起點和終點是需要特別標記,因為如果地圖中沒有標定起點和終點,就無法尋路了。

void MainWindow::mousePressEvent(QMouseEvent * e)
{
	//得到鼠標處的格子坐標
	QPoint pt;
	pt.setX( (e->pos().x() - START_X ) / RECT_WIDTH);
	pt.setY( (e->pos().y() - START_X ) / RECT_HEIGHT);
    //wheather is the point in the Game area
	if (!PointInGameArea(pt))
	{
		return;
	}
	//獲取所點擊矩形元素
	Item* pItem = m_items[pt.x()][pt.y()];
    //leftbutton set object tpye,rightbutton set obstacle
    if(e->button()==Qt::LeftButton) //left button ,set Robot or goal
	{
        //
        pItem->m_nObjectType++;
        pItem->m_nObjectType%=3;

        pItem->m_bIsObstacle=false;  //clear obstacle
	}
    else if(e->button()==Qt::RightButton)  //set obstacle or not obstacle
	{
        pItem->m_nObjectType=0;  //clear object type

        if (pItem->m_bIsObstacle)
		{
            pItem->m_bIsObstacle = false;
		}
        else
		{			
            pItem->m_bIsObstacle = true;
		}
	}
}

鼠標事件會自動觸發paintEvent()函數,所以在點擊鼠標后,地圖會重繪。

新建地圖
新建地圖需要將上次的地圖清除,也就是ReleaseItems(),同時清除路徑ReleasePath(),然后再初始化地圖。

void MainWindow::NewGame()
{
	resize(START_X*2 + m_nColumes*RECT_WIDTH  ,START_Y*2 + m_nRows*RECT_HEIGHT);

	ReleaseItems();
    ReleasePath();
    InitItems();
}

無論是初始化地圖還是新建地圖,都沒有提到地圖的繪制,那么地圖什么時候繪制呢?在NewGame()中有一個Resize()函數,也就是改變窗口的大小,該函數會觸發paintEvent()函數,所以當調用NewGame()函數后,就會繪制地圖了。

更新地圖
不說了,看代碼,很簡單,后面詳細介紹一下繪制地圖和繪制路徑。

void MainWindow::paintEvent(QPaintEvent *e)
{
    DrawChessboard(); //繪制地圖背景
    DrawItems();      //繪制地圖
    DrawPath();       //繪制路徑

    update();
}

繪制地圖
繪制地圖其實就是根據相應位置的Item對象的屬性,來繪制該網格。網格屬性在初始化和設置障礙物時,已經設置好了。代碼很簡單,就是判斷網格屬性是障礙物還是起點,然后直接繪制就好了。

void MainWindow::DrawItem(QPainter& painter,Item* pItem)
{
    if(pItem->m_bIsObstacle) //show obstacle
    {
        QRect rcSrc(0,0,m_ObstacleImage.width(),m_ObstacleImage.height());
        QRect rcTarget(START_X + pItem->m_pos.x()*RECT_WIDTH + 2,START_Y + pItem->m_pos.y()*RECT_HEIGHT + 2,RECT_WIDTH-4,RECT_HEIGHT-4);
        painter.drawPixmap(rcTarget,m_ObstacleImage,rcSrc);

        painter.setBrush(Qt::transparent);
        painter.drawRect( START_X + pItem->m_pos.x()*RECT_WIDTH,START_Y + pItem->m_pos.y()*RECT_HEIGHT,RECT_WIDTH,RECT_HEIGHT);
		return;
	}
    else if (pItem->m_nObjectType!=0)  //show Robot item or goal item
	{
        if(pItem->m_nObjectType == 1)  //show Robot
		{
            QRect rcSrc(0,0,m_RobotImage.width(),m_RobotImage.height());
            QRect rcTarget(START_X + pItem->m_pos.x()*RECT_WIDTH + 2,START_Y + pItem->m_pos.y()*RECT_HEIGHT + 2,RECT_WIDTH-4,RECT_HEIGHT-4);
            painter.drawPixmap(rcTarget,m_RobotImage,rcSrc);

            painter.setBrush(Qt::transparent);
            painter.drawRect( START_X + pItem->m_pos.x()*RECT_WIDTH,START_Y + pItem->m_pos.y()*RECT_HEIGHT,RECT_WIDTH,RECT_HEIGHT);
            return ;
		}
        else
		{
            QRect rcSrc(0,0,m_GoalImage.width(),m_GoalImage.height());
            QRect rcTarget(START_X + pItem->m_pos.x()*RECT_WIDTH + 2,START_Y + pItem->m_pos.y()*RECT_HEIGHT + 2,RECT_WIDTH-4,RECT_HEIGHT-4);
            painter.drawPixmap(rcTarget,m_GoalImage,rcSrc);

            painter.setBrush(Qt::transparent);
            painter.drawRect( START_X + pItem->m_pos.x()*RECT_WIDTH,START_Y + pItem->m_pos.y()*RECT_HEIGHT,RECT_WIDTH,RECT_HEIGHT);
            return;
		}
	}
	else
	{
        painter.setBrush(Qt::green);

	}
    painter.drawRect( START_X + pItem->m_pos.x()*RECT_WIDTH,START_Y + pItem->m_pos.y()*RECT_HEIGHT,RECT_WIDTH,RECT_HEIGHT);
}

尋找路徑
地圖繪制好了以后,就可以尋路了。單擊搜索路徑后,就可以尋路了。尋路直線需要清除原來的路徑,避免上次的路徑影響。如果發現地圖中沒有起點和終點,就直接退出,避免造成尋路異常。

void MainWindow::OnSearchPath()
{
    ReleasePath();
    createMazeMap();  //create Maze map and start end point
    if(!m_bIsHaveRobot || !m_bIsHaveGoal)
    {
        std::cout<<"have not roobot or goal"<<std::endl;
        return;
    }

    astar.InitAstar(mazeMap);

    path=astar.GetPath(start, end, false);

    markPathInMazeMap();
    printMazeMap();
    printPath();

    if(!path.empty())
        path.pop_back();
    if(!path.empty())
        path.pop_front();

    update();

}

代碼很簡單,關鍵就是path=astar.GetPath().下面重點分析一下尋路的過程。
尋路的代碼不是本人寫了,原作者已經不記得了,但是他的代碼有點問題,我調試后做了修改。下面是我參考的博客:
https://www.cnblogs.com/wlzy/p/7096114.html
博客上對A*的算法描述的非常清晰,簡單易懂,我就不重復了。但是我用他的代碼時,總是會出現路徑越界,調試之后發現是邊界檢測那塊有一點問題,也就是isCanreach()函數,讀者可以好好對比一下。同時我在源碼的基礎上添加了一些清除開路徑和閉路徑的操作,不知道為什么源碼沒有。如果沒有的話,會有問題的。

繪制路徑
尋路完成以后,就可以直接繪制路徑了。

void MainWindow::DrawPath()
{
    QPainter painter(this);

    painter.setBrush(Qt::yellow);


    if(!path.empty())
    for(auto &p:path)
    {
        painter.drawRect( START_X + p->x*RECT_WIDTH,START_Y + p->y*RECT_HEIGHT,RECT_WIDTH,RECT_HEIGHT);
    }

}

總結
完成了基於Qt的A算法可視化分析后,我發現以后很多算法都可以可視化分析了,非常直觀,有助於理解算法。
基於Qt的A
算法可視化分析

代碼地址如下:
http://www.demodashi.com/demo/13677.html

注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM