其實以前就寫過一個迷宮的程序和DFS遍歷,不過弄丟了,前幾天閑就重寫了一下。歡迎交流和拍磚。有很多不足的地方也希望大家多指正。
迷宮生成的算法來自《計算機圖形學》,也就是這本書:
生成迷宮的算法描述如下:
由於表示牆使用了up_wall和left_wall兩個矩陣,所以格子的數量要比能顯示出來的多一行一列,否則屏幕最下邊和最右邊是沒有牆的。雖然可以后面畫上,不過我選擇這樣。
對於迷宮的遍歷使用DFS,另外由於使用了一個visited矩陣表示每個格子是否已經訪問過,所以即使迷宮里存在環也沒有任何影響,不會死循環。
另外當時不知道glutPostRedisplay()這玩意兒,所以很蠢的把遍歷的過程設成了DisplayFunc來看遍歷的過程。
生成迷宮及DFS遍歷的代碼如下:
1 #include <iostream> 2 #include <fstream> 3 #include <stack> 4 #include <GL/gl.h> 5 #include <GL/glu.h> 6 #include <GL/glut.h> 7 #include <cstdlib> 8 using namespace std; 9 10 /*Constant And Structures*/ 11 const int SCREEN_WIDTH = 1200; 12 const int SCREEN_HEIGHT = 600; 13 const int BLOCK_SIZE = 10; 14 15 const int BLOCK_VER_NUM = (SCREEN_WIDTH / BLOCK_SIZE) + 1; 16 const int BLOCK_HOR_NUM = (SCREEN_HEIGHT / BLOCK_SIZE) + 1; 17 18 bool up_wall[BLOCK_VER_NUM][BLOCK_HOR_NUM]; 19 bool left_wall[BLOCK_VER_NUM][BLOCK_HOR_NUM]; 20 bool visited[BLOCK_VER_NUM][BLOCK_HOR_NUM]; 21 22 const int wall_size = 1; 23 24 enum Dir 25 { 26 _up = 0, 27 _left = 1, 28 _right = 2, 29 _down = 3, 30 _none = 4 31 }; 32 const int DIR_SUM = 3; 33 34 struct Pos 35 { 36 int x, y; 37 Pos(int xx, int yy) 38 { 39 x = xx; 40 y = yy; 41 } 42 Pos() 43 { 44 x = 0; 45 y = 0; 46 } 47 Pos(const Pos& p) 48 { 49 x = p.x; 50 y = p.y; 51 } 52 Pos& operator =(const Pos& p) 53 { 54 x = p.x; 55 y = p.y; 56 } 57 58 bool operator ==(const Pos& p) 59 { 60 return (x == p.x) && (y == p.y); 61 } 62 }; 63 64 class Path : public stack<Pos> 65 { 66 public: 67 Path() : stack<Pos>() 68 {} 69 const deque<Pos>& _Get_container() 70 { 71 return c; 72 } 73 }; 74 75 //For Build Puzzle 76 stack<Pos> pos; 77 stack<Pos> temp; 78 79 Path path; 80 Pos entryPuzzle; 81 Pos entrancePuzzle; 82 bool arrive; 83 84 85 86 /*Function*/ 87 void myDisplay() 88 { 89 glClear(GL_COLOR_BUFFER_BIT); 90 glBegin(GL_POINTS); 91 92 for(int i = 0; i < SCREEN_WIDTH; ++i) 93 { 94 for(int j = 0; j < SCREEN_HEIGHT; ++j) 95 { 96 int bx = i / BLOCK_SIZE; 97 int by = j / BLOCK_SIZE; 98 99 // //cout << "Opering Cell: " << bx << "," << by << endl; 100 if(up_wall[bx][by]) 101 { 102 if(j % BLOCK_SIZE < wall_size) 103 { 104 ////cout << "Put Pix at " << i << "," << j << endl;; 105 glVertex2d(i, j); 106 } 107 } 108 if(left_wall[bx][by]) 109 { 110 if(i % BLOCK_SIZE < wall_size) 111 { 112 // //cout << "Put Pix at " << i << "," << j << endl; 113 glVertex2d(i, j); 114 } 115 } 116 } 117 } 118 119 glEnd(); 120 glFlush(); 121 //cout << "Render Over\n"; 122 } 123 124 void DisplayTraverse() 125 { 126 glClear(GL_COLOR_BUFFER_BIT); 127 glBegin(GL_POINTS); 128 129 glColor3f(0.0f, 0.0f, 0.0f);//Map Color Black 130 //glPointSize(1.0); 131 for(int i = 0; i < SCREEN_WIDTH; ++i) 132 { 133 for(int j = 0; j < SCREEN_HEIGHT; ++j) 134 { 135 int bx = i / BLOCK_SIZE; 136 int by = j / BLOCK_SIZE; 137 138 // //cout << "Opering Cell: " << bx << "," << by << endl; 139 if(up_wall[bx][by]) 140 { 141 if(j % BLOCK_SIZE < wall_size) 142 { 143 ////cout << "Put Pix at " << i << "," << j << endl;; 144 glVertex2d(i, j); 145 } 146 } 147 if(left_wall[bx][by]) 148 { 149 if(i % BLOCK_SIZE < wall_size) 150 { 151 // //cout << "Put Pix at " << i << "," << j << endl; 152 glVertex2d(i, j); 153 } 154 } 155 } 156 } 157 glEnd(); 158 159 glColor3f(0.0f, 0.0f, 1.0f);//Map Color Black 160 //glPointSize(2.0); 161 glBegin(GL_LINE_STRIP); 162 163 const int off = BLOCK_SIZE / 2; 164 165 auto dq = path._Get_container(); 166 for(auto it : dq) 167 { 168 glVertex2d(it.x * BLOCK_SIZE + off, it.y * BLOCK_SIZE + off); 169 } 170 171 glEnd(); 172 173 174 glFlush(); 175 } 176 177 bool canbeDestroy(int x, int y) 178 { 179 //cout << "Detect CanbeDestroy: " << x << "," << y << endl; 180 bool succ = true; 181 if(x < 0 || x >= BLOCK_VER_NUM - 1 || y < 0 || y >= BLOCK_HOR_NUM - 1) 182 { 183 return false; 184 } 185 186 succ = left_wall[x][y] && up_wall[x][y] && left_wall[x + 1][y] && up_wall[x][y + 1]; 187 //if(succ) //cout << "And Succ\n\n"; 188 //else //cout << "And False\n\n"; 189 return succ; 190 } 191 192 void Proc(Pos& position) 193 { 194 //cout << "Procing" << position.x << "," << position.y << endl; 195 if(position.x < 0 || position.x >= BLOCK_VER_NUM || position.y < 0 || position.y >= BLOCK_HOR_NUM) 196 return; 197 stack<Pos> temp; 198 199 //Find Neighbour Who can be access 200 for(int i = 0; i < 4; ++i) 201 { 202 switch(i) 203 { 204 case _up: 205 { 206 if(canbeDestroy(position.x, position.y - 1)) 207 { 208 temp.emplace(position.x, position.y - 1); 209 } 210 }break; 211 case _down: 212 { 213 if(canbeDestroy(position.x, position.y + 1)) 214 { 215 temp.emplace(position.x, position.y + 1); 216 } 217 }break; 218 case _left: 219 { 220 if(canbeDestroy(position.x - 1, position.y)) 221 { 222 temp.emplace(position.x - 1, position.y); 223 } 224 }break; 225 case _right: 226 { 227 if(canbeDestroy(position.x + 1, position.y)) 228 { 229 temp.emplace(position.x + 1, position.y); 230 } 231 }break; 232 default:break; 233 } 234 } 235 236 if(temp.size() == 0)//No valid target 237 return; 238 239 //Have valid target, and random choose one 240 int size = temp.size(); 241 int tar = rand() % size; 242 //cout << "Select " << tar + 1 << " ops of " << size << endl; 243 Pos next; 244 /*int i = 0; 245 while(!temp.empty()) 246 { 247 if(i++ == tar) 248 { 249 next = temp.top(); 250 temp.pop(); 251 } 252 253 //if(!visited[temp.top().x][temp.top().y]) 254 pos.push(temp.top()); 255 temp.pop(); 256 }*/ 257 for(int i = 0; i < size; ++i) 258 { 259 if(i == tar) 260 { 261 next = temp.top(); 262 temp.pop(); 263 } 264 else 265 { 266 //if(!visited[temp.top().x][temp.top().y]) 267 //pos.push(temp.top()); 268 temp.pop(); 269 } 270 } 271 pos.push(position); 272 273 if(next.y == position.y - 1) 274 up_wall[position.x][position.y] = false; 275 else if(next.y == position.y + 1) 276 up_wall[position.x][position.y + 1] = false; 277 else if(next.x == position.x - 1) 278 left_wall[position.x][position.y] = false; 279 else if(next.x == position.x + 1) 280 left_wall[position.x + 1][position.y] = false; 281 else 282 { 283 284 } 285 286 //visited[next.x][next.y] = true; 287 //cout << "Move To " << next.x << "," << next.y << endl; 288 //myDisplay(); 289 Proc(next); 290 } 291 292 void MakePuzzle() 293 { 294 while(!pos.empty()) 295 { 296 Pos curpos = pos.top(); 297 pos.pop(); 298 //visited[curpos.x][curpos.y] = true; 299 Proc(curpos); 300 } 301 302 303 cout << "MakePuzzle Over.\n"; 304 } 305 306 void myInit() 307 { 308 /* 309 Output Info 310 */ 311 //cout << "Screen Size: " << SCREEN_WIDTH << "*" << SCREEN_HEIGHT << endl; 312 //cout << "Block Size: " << BLOCK_SIZE << endl; 313 //cout << "Puzzle Size: " << BLOCK_VER_NUM << "*" << BLOCK_HOR_NUM << endl; 314 //cout << "Wall Size: " << wall_size << endl; 315 316 317 glClearColor((float)0x66 / 0x100, (float)0xcc / 0x100, 1.0, 0.0); 318 glColor3f(0.0f, 0.0f, 0.0f);//Map Color Black 319 //glPointSize(1.0); 320 glMatrixMode(GL_PROJECTION); 321 322 glLoadIdentity(); 323 gluOrtho2D(0.0, (GLdouble)SCREEN_WIDTH, 0.0, (GLdouble)SCREEN_HEIGHT); 324 glViewport(0.0, SCREEN_WIDTH, 0.0, SCREEN_HEIGHT); 325 326 for(int i = 0; i < BLOCK_VER_NUM; ++i) 327 { 328 for(int j = 0; j < BLOCK_HOR_NUM; ++j) 329 { 330 up_wall[i][j] = true; 331 left_wall[i][j] = true; 332 visited[i][j] = false; 333 } 334 } 335 336 cout << "Display after init\n"; 337 myDisplay(); 338 339 pos.emplace(BLOCK_VER_NUM / 2, BLOCK_HOR_NUM / 2); 340 cout << "myInit Over.\n"; 341 } 342 343 void TraverseDFS(const Pos& p) 344 { 345 cout << "Travering " << p.x << ", " << p.y << endl; 346 path.push(p); 347 visited[p.x][p.y] = true; 348 DisplayTraverse(); 349 if(entrancePuzzle == p) 350 { 351 arrive = true; 352 return; 353 } 354 355 if((p.x > 0) && !left_wall[p.x][p.y] && !visited[p.x - 1][p.y])//left 356 { 357 Pos next(p.x - 1, p.y); 358 TraverseDFS(next); 359 return; 360 } 361 if((p.x < BLOCK_VER_NUM - 1) && !left_wall[p.x + 1][p.y] && !visited[p.x + 1][p.y])//right 362 { 363 Pos next(p.x + 1, p.y); 364 TraverseDFS(next); 365 return; 366 } 367 if((p.y > 0) && !up_wall[p.x][p.y] && !visited[p.x][p.y - 1])//up 368 { 369 Pos next(p.x, p.y - 1); 370 TraverseDFS(next); 371 return; 372 } 373 if((p.y < BLOCK_HOR_NUM - 1) && !up_wall[p.x][p.y + 1] && !visited[p.x][p.y + 1])//down 374 { 375 Pos next(p.x, p.y + 1); 376 TraverseDFS(next); 377 return; 378 } 379 380 path.pop(); 381 } 382 383 void TraverseInit() 384 { 385 entryPuzzle.x = 0; 386 entryPuzzle.y = rand() % (BLOCK_HOR_NUM - 1); 387 entrancePuzzle.x = BLOCK_VER_NUM - 2; 388 entrancePuzzle.y = rand() % (BLOCK_HOR_NUM - 1); 389 390 cout << "Generated\nEntry: " << entryPuzzle.x << ", " << entryPuzzle.y << endl; 391 cout << "Entrance: " << entrancePuzzle.x << ", " << entrancePuzzle.y << endl; 392 393 path.push(entryPuzzle); 394 visited[entryPuzzle.x][entryPuzzle.y] = true; 395 arrive = false; 396 } 397 398 void Traverse() 399 { 400 while(!path.empty() && !arrive) 401 { 402 Pos& cur = path.top(); 403 path.pop(); 404 TraverseDFS(cur); 405 } 406 if(arrive) 407 cout << "Arrived.\n"; 408 else 409 cout << "Something Wrong, Not arrive."; 410 } 411 412 int main(int argc, char* argv[]) 413 { 414 glutInit(&argc, argv); 415 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); 416 glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT); 417 glutInitWindowPosition(0, 0); 418 glutCreateWindow("Puzzle"); 419 glutDisplayFunc(Traverse); 420 421 myInit(); 422 MakePuzzle(); 423 TraverseInit(); 424 glutMainLoop(); 425 426 return 0; 427 }
代碼中myInit函數用來初始化各個矩陣。之后是MakePuzzle構造迷宮。構造迷宮的過程使用了一個棧並且是用Proc函數遞歸調用來生成迷宮的。現在想想其實可以寫成完全的遞歸或者迭代的,不過當時寫着順手就這么寫了。canbeDestroy函數即是判斷一個格子是否有四堵完整的牆,同時考慮了邊界。
之后就是DFS遍歷了,TraverseInit函數在迷宮最左邊隨機選取一點作為起點,最右邊隨機選擇一點作為終點。然后起點壓棧,進入主循環也就是Traverse函數開始遍歷。Traverse還是使用了遞歸+迭代的方式,不斷遞歸直到進入死路,然后彈棧重新遞歸。
最后,最上面倆函數myDisplay和DisplayTraverse,前者是用來畫迷宮的,后者則是迷宮+DFS的路徑。另外我畫迷宮是一個像素一個像素去判斷是否在迷宮的牆上,是就畫不是就不畫,這樣其實效率很低,完全可以遍歷每行每列用GL_LINES去畫的。人懶沒葯醫啊。
運行情況(由於寬度1200截圖出來顯示效果不好所以我改成了800):
另外至於生成迷宮是叫MakePuzzle,完全是因為我英語不好並且寫這東西的時候完全是在無網絡的情況下所以隨便想了個單詞。