迷宫生成及深度优先搜索遍历迷宫(OpenGL)
其实以前就写过一个迷宫的程序和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,完全是因为我英语不好并且写这东西的时候完全是在无网络的情况下所以随便想了个单词。