# 計算機軟體技術實習日誌專案三(二) 迷宮專案實現
計算機軟體技術實習日誌專案三(二) 迷宮專案實現
前言
大家好,我將為大家介紹我實現迷宮的具體細節,做軟體是我之前沒有接觸過的領域,做得不好,大家不要見怪。本文章介紹prim生成迷宮和A星自動尋路。本文著重介紹專案實現,原理知識請參考《計算機軟體技術實習日誌專案三(一) 迷宮專案準備》。這篇文章我本來寫過了,但是失誤刪掉了,所以我又重寫了一遍。太南了
一、引數定義
我們定義地圖塊結構體
#include <utility>
using namespace std;
typedef pair<int, int> prii;
struct node {
CRect rec; //用於畫圖
int x, y, id, flag,dir; //座標,用於生成地圖通路的id,用於判斷該方塊是什麼型別,方向(最後好像並沒有用到)
int f, g, h,ida; //(A星相關的引數)f=g+h,ida用於表示它在A星生成順序
bool in_open, in_close; //用於判斷它是否在open佇列和close佇列
prii fa,son; //prii其實是pair<int,int> prii
node() { //初始化函式
flag = 1;
x = y = id = dir = 0;
f = g = h = in_open = in_close = ida = 0;
}
bool operator<(const node& rhs) const {
/*
因為用到了優先佇列,所以自定義結構體要過載運算子,優先佇列預設是大根堆,
所以過載小於號,返回true時表示左邊的優先順序小於右邊的。
*/
if (f == rhs.f) {
return ida < rhs.ida; //舊點優先順序低
}
else {
return f > rhs.f; //f大的點優先順序低
}
}
void update(node tmp); //由父節點更新的更新的函式
void up();
void down();
void left();
void right();
};
void node::update(node tmp) {
g = tmp.g+1, h = abs(39-x)+abs(39-y); //哈密頓距離
f = g + h;
in_open = 1; //表示在open佇列裡
ida = ++acnt; //ida號
fa = {tmp.x,tmp.y}; //指向父節點
open_queue.push(*this); //進隊
}
void node::up() {
dir = 1;
x -= 1;
}
void node::down() {
dir = 2;
x += 1;
}
void node::left() {
dir = 3;
y -= 1;
}
void node::right() {
dir = 4;
y += 1;
}
struct Edge { //鏈式向前星表示圖
int v, next,w;
};
void addedge(int u, int v, int w);
void add(int u, int v);
void addedge(int u, int v, int w) {
edge[++cnt].v = v, edge[cnt].w = w, edge[cnt].next = head[u], head[u] = cnt;
}
void add(int u, int v) {
edgem[++cntm].v = v, edgem[cntm].next = headm[u], headm[u] = cntm;
}
struct tnode { //prim結點,此節點表示點b到點c的距離為a
int a, b, c;
bool operator<(const tnode& rhs) const { //過載運算子
return a > rhs.a;
}
};
二、迷宮生成
我門生成迷宮的大致思路是,從(1,1)地圖塊開始間隔產生通路結點,然後將相鄰的結點之間建邊邊權隨機,這樣我們用prim生成最小生成樹的時候就是隨機的了,然後我們用完prim是邏輯上建立了最小生成樹,實際上並沒有,我們還要深搜遍歷一遍最小生成樹將地圖塊的flag改為2。
地圖生成函式
void CmazeDlg::productmaze() {
mcnt = 0;
for (int i = 0; i < 41; ++i) { //為了懶省事直接暴力初始化地圖塊的每個引數了
for (int j = 0; j < 41; ++j) {
maze[i][j].rec.left = 0 + j * 20;
maze[i][j].rec.right = 20 + j * 20;
maze[i][j].rec.top = 0 + i * 20;
maze[i][j].rec.bottom = 20 + i * 20;
maze[i][j].flag = 1;
maze[i][j].id = -1;
maze[i][j].x = i, maze[i][j].y = j;
maze[i][j].f = 0;
maze[i][j].g = 0;
maze[i][j].h = 0;
maze[i][j].ida = 0;
maze[i][j].in_close = 0;
maze[i][j].in_open = 0;
maze[i][j].fa.first = 0;
maze[i][j].fa.second = 0;
}
}
for (int i = 1; i < 41; i += 2) { //間隔生成通路節點
for (int j = 1; j < 41; j += 2) {
maze[i][j].id = ++mcnt;
maze[i][j].flag = 2;
ma[mcnt] = maze[i][j];
ma[mcnt].x = maze[i][j].x;
ma[mcnt].y = maze[i][j].y;
}
}
srand((unsigned)time(NULL)); //設定隨機數種子
cnt = cntm = 0; //初始化通路節點的連通圖的相關引數
memset(head, 0, sizeof head);
memset(vis, 0, sizeof vis);
memset(headm, 0, sizeof headm);
int tdis = 0;
for (int i = 1; i < 41; i += 2) { //相鄰的通路結點建立隨機權值的邊
for (int j = 1; j < 39; j += 2) {
tdis = rand() % 100 + 1;
addedge(maze[i][j].id, maze[i][j + 2].id, tdis);
addedge(maze[i][j + 2].id, maze[i][j].id, tdis);
}
}
for (int i = 1; i < 41; i += 2) {
for (int j = 1; j < 39; j += 2) {
tdis = rand() % 100 + 1;
addedge(maze[j][i].id, maze[j + 2][i].id, tdis);
addedge(maze[j + 2][i].id, maze[j][i].id, tdis);
}
}
prim(); //開始在連通圖上生成最小生成樹
dfs(1); //最小生成樹邏輯邏輯上是建好了,但是現實的話還是孤立的
//深搜遍歷最小生成樹,把兩個樹節點(即通路結點)間的障礙結點變為通路節點。
//paintnow();
}
此時我們建立了連通圖,如下圖。我們用細黃線表示建邊了,沒畫完。但它實際上是孤立的通路節點(黃色)。
我們建立完連通圖後,就要在連通圖上建立最小生成樹了
prim函式
void CmazeDlg::prim() { //這裡使用了優先佇列優化
for (int i = 1; i <= mcnt; ++i) { //左右點首先設為B類
dis[i] = inf;
}
dis[1] = 0; //一開始將點1設為距離A類點集距離最小的B類點。
int tcnt = 0, pre=0, now=0;
tnode tmp;
q.push({ 0,0,1});
while (++tcnt <= mcnt) {
while (!q.empty()) {
tmp = q.top(); //找到距離A類點集最小的點
q.pop();
pre = tmp.b, now = tmp.c;
if (!vis[now]) break; //這個點必須是B類點,也就是之前沒訪問過
}
vis[now] = 1; //現在他是A類點了
add(pre, now); //與他前導結點相連,一個點的前導結點就是,將該節點到A類點集的距離更新為最小的點。
for (int i = head[now], v; i; i = edge[i].next) { //更新與新A類結點相連的點。
v = edge[i].v;
if (!vis[v] && dis[v] > edge[i].w) { //如果距離小於之前的距離切實B類點,就更新入隊。
dis[v] = edge[i].w;
q.push({ dis[v],now,v });
}
}
}
}
我們呼叫完最小生成樹後,只是建立的邏輯上的最小生成樹。他的顯示效果還是如下圖所示
但是邏輯上他是這樣的,如下圖。黃線代表連線的最小生成樹。沒畫完整,簡單演示一下。
現在我們就深搜遍歷生成樹,然後將連邊上的障礙結點改為通路節點
void CmazeDlg::dfs(int id) {
for (int i = headm[id],v; i; i = edgem[i].next) {
v = edgem[i].v;
int mi1 = min(ma[id].x, ma[v].x), mi2 = max(ma[id].x, ma[v].x), ma1 = min(ma[id].y, ma[v].y), ma2 = max(ma[id].y, ma[v].y);
for (int i = mi1; i <= mi2; ++i) {
for (int j = ma1; j <= ma2; ++j) {
maze[i][j].flag = 2;
}
}
dfs(v);
}
}
這樣我們就生成了一個迷宮
三、A*自動尋路
我們在建立好的迷宮即一個最小生成樹上用A*搜尋出起點到終點的通路。
priority_queue<node> open_queue;
void CmazeDlg::astar() {
node tmp;
int tx, ty, ttx, tty;
open_queue.push(maze[1][1]);
maze[1][1].f = maze[1][1].g = maze[1][1].h = 0; //起點初始化,本迷宮固定了起點和終點,但是其實可以自己隨機設定的,因為並沒有什麼實現的難度,所以大家可以自己實現一下。
maze[1][1].in_open = 1;
while (!maze[39][39].in_open) { //當終點不在open佇列時,說明終點還不可達,所以我們繼續搜尋。
tmp = open_queue.top(); //讀取當前open佇列的f值最小的點
tx = tmp.x, ty = tmp.y;
maze[tx][ty].in_close = 1; //將它取出方法close佇列裡
open_queue.pop();
for (int i = 1; i <= 4; ++i) {
ttx = tx + dr[i][0], tty = ty + dr[i][1]; //遍歷周圍四個結點
if (maze[ttx][tty].flag != 1 && !maze[ttx][tty].in_close) { //如果他不是障礙,且他不在close佇列裡
if (!maze[ttx][tty].in_open || maze[ttx][tty].g > tmp.g + 1) { //四周的點如果不在open佇列裡,或者新的g值更小
maze[ttx][tty].update(maze[tx][ty]); //我們就更新它
}
}
}
}
while (!open_queue.empty()) { //清空open佇列為下次做準備
open_queue.pop();
}
node nodep = maze[39][39]; //我們從終點開始找父節點,
while (!(nodep.x == 1 && nodep.y == 1)) { //直到找到了起點
tx = nodep.fa.first,ty=nodep.fa.second;
maze[tx][ty].son.first = nodep.x; //在找的時候我們更新該節點的父節點的子節點(這個子節點也就是該點)
maze[tx][ty].son.second = nodep.y;
maze[tx][ty].flag = 0; //flag設為0方便繪圖
nodep = maze[tx][ty]; //向上遍歷
}
paintnow();
}
我們通過設定定時器可以讓小人自動走
void CmazeDlg::OnBnClickedauto()
{
// TODO: 在此新增控制元件通知處理程式程式碼
astar();
SetTimer(1, 150, NULL);
}
void CmazeDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值
if ((mplayer.x != 39 || mplayer.x != 39)) {
int sx = maze[mplayer.x][mplayer.y].son.first; //下一跳結點座標
int sy = maze[mplayer.x][mplayer.y].son.second; //
mplayer.x = sx, mplayer.y = sy;
paintnow();
}
CDialogEx::OnTimer(nIDEvent);
}
四、演示
手動操作
自動操作
五、總結
這一次通過學習迷宮,更新了我對prim演算法的理解,對他的應用有了新的認知,我還學會了以前聽說過的A*,對於定時器的運用也更加熟練。
相關文章
- 軟體工程實踐專案學習與執行日誌軟體工程
- Python專案實戰:20行程式設計迷宮大陣Python行程程式設計
- 寒假補充專案-回溯法走迷宮
- 軟體專案管理的實質(三)(轉)專案管理
- 計算機軟體實習專案四 —— 校園一卡通管理系統 (程式碼實現) 12-27計算機
- 專案日誌
- 軟體專案管理實踐(#0)專案管理
- 【Javascript + Vue】實現隨機生成迷宮圖片JavaScriptVue隨機
- 軟體專案管理 8.4.軟體專案質量計劃專案管理
- 突破軟體專案實施困境(轉)
- java專案日誌配置檔案Java
- 實現專案計劃(上)薦
- ELK實時分析之php的laravel專案日誌PHPLaravel
- Java新手學習Java專案打日誌Java
- 軟體專案管理實踐:專案成功的關鍵是什麼?專案管理
- 總體設計(軟體專案)
- Kafka專案實戰-使用者日誌上報實時統計之編碼實踐Kafka
- Kafka專案實戰-使用者日誌上報實時統計之分析與設計Kafka
- php專案框架搭建(三)【實體篇】PagePHP框架
- [原創]軟體實施專案記(一)
- 軟體專案管理的實質(一)(轉)專案管理
- 探祕技術專案管理(二)(轉)專案管理
- 解密迷宮問題:三種高效演算法Java實現,讓你輕鬆穿越未知迷宮解密演算法Java
- 揭祕 Reddit 愚人節專案的技術實現過程
- 探祕技術專案管理(三)(轉)專案管理
- 前端技術演進(六):前端專案與技術實踐前端
- 開源專案管理軟體有哪些?分享7個實用開源專案管理軟體專案管理
- 【Lolttery】專案開發日誌 (三)維護好一個專案好難
- 新浪微博實習體會——應用專案vs心理學研究專案
- Kafka專案實戰-使用者日誌上報實時統計之應用概述Kafka
- koajs 專案實戰(二)JS
- React專案實踐系列二React
- 實戰react技術棧+express前後端部落格專案(1)– 整體專案結構搭建ReactExpress後端
- 實戰react技術棧+express前後端部落格專案(1)-- 整體專案結構搭建ReactExpress後端
- 為何軟體專案的目標難於實現薦
- 實驗三結對專案
- 實驗三的專案分析
- 專案如何實現限流?