计算机软件实习3—基于WIN32的迷宫设计
课前预习
课程要求
- 迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
- 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
- 要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于 A*算法实现,输出走迷宫的最优路径并显示。 设计交互友好的游戏图形界面。
题目难点
- 要随机生成迷宫,并且要给出解决方案。
- 支持玩家走迷宫和系统走迷宫两种方案。
- 设计友好的交互界面。
迷宫实现所需的算法原理
在这里,我们选择了Prime算法来作为实现随机生成迷宫的算法,下面我们先了解一下prime算法。
Prime算法的核心步骤:
在带权连通图中V是包含所有顶点的集合, U已经在最小生成树中的节点,从图中任意某一顶点v开始,此时集合U={v}。
重复执行下述操作:
在所有u∈U,w∈V-U的边(u,w)∈E中找到一条权值最小的边,将(u,w)这条边加入到已找到边的集合,并且将点w加入到集合U中,当U=V时,就找到了这颗最小生成树。
下面我们通过图示法来演示一下工作流程:
首先,确定起始顶点。在这里以顶点A作为起始点。根据查找法则,与点A相邻的点有点B和点H,比较AB与AH,AB的权值小,因此我们选择点B,如下图。并将点B加入到U中。
继续下一步,此时集合U中有{A,B}两个点,再分别以这两点为起始点,根据查找法则(选取权值较小的边),找到边BC(当有多条边权值相等时,可选任意一条),如下图。并将点C加入到U中。
继续,此时集合U中有{A,B,C}三个点,根据查找法则,我们找到了符合要求的边CI,如下图。并将点I加入到U中。
继续,此时集合U中有{A,B,C,I}四个点,根据查找法则,找到符合要求的边CF(当C有多条边时,选择权值最小的那个,因此这里选择CF),如下图。并将点F加入到集合U中。
继续,依照查找法则我们找到边FG,如下图。并将点G加入到U中。
继续,依照查找法则我们找到边GH,如下图。并将点H加入到U中。
继续,依照查找法则我们找到边CD,如下图。并将点D加入到U中。
继续,依照查找法则我们找到边DE,如下图。并将点E加入到U中。
此时,满足U = V,即找到了这颗最小生成树。
基于WIN32的迷宫制作
功能实现
本次实验只完成了随机生成迷宫和人工走迷宫,因此一共分为两大块:
第一块是随机生成迷宫:
struct block{
int row,column,direction;
block(int _row,int _column,int _direction){
row = _row;
column = _column;
direction = _direction;
}
};
struct point {
int x;
int y;
}start,end;
这是定义一个向量,以及初始化挖墙的矿工。
vector<block> myblock;
int x_num=1,y_num=1;//矿工位置
int G[100][100];
这是一个初始化函数,游戏结束后重新开始,就需初始化。
void init() {
//将地图全部置为墙
memset(G,WALL,sizeof(G));
//定义起始点
G[0][0] = NOTHING;
start.x = start.y = 0;
m_play.len=1;
m_play.m_pos[0].x=1;
m_play.m_pos[0].y=1;
generatedestination();
}
这一部分是矿工找墙。
void FindBlock() {
//找出与当前位置相邻的墙
if(x_num+1<=m && G[x_num+1][y_num] == WALL) {//down
myblock.push_back(block(x_num+1,y_num,down));
}
if(y_num+1<=n && G[x_num][y_num+1] == WALL) {//right
myblock.push_back(block(x_num,y_num+1,rt));
}
if(x_num-1>=1 && G[x_num-1][y_num] == WALL) {//up
myblock.push_back(block(x_num-1,y_num,up));
}
if(y_num-1>=1 && G[x_num][y_num-1] == WALL) {//left
myblock.push_back(block(x_num,y_num-1,lt));
}
}
这一部分是随机生成迷宫。
void CreateMaze(){
//生成迷宫
srand((unsigned)time(NULL));//随机数种子
FindBlock();
//第一步压入两堵墙(起点右边和起点下面)进入循环
while(myblock.size()) {
int BlockSize = myblock.size();
//随机选择一堵墙(生成0 ~ BlockSize-1之间的随机数,同时也是vector里墙的下标)
int randnum = rand() % BlockSize;
block SelectBlock = myblock[randnum];
x_num = SelectBlock.row;//矿工来到我们"选择的墙"这里
y_num = SelectBlock.column;
//根据当前选择的墙的方向进行后续操作
//此时,起始点 选择的墙 目标块 三块区域在同一直线上
//我们让矿工从"选择的墙"继续前进到"目标块"
//矿工有穿墙能力 :)
switch(SelectBlock.direction) {
case down: {
x_num++;
break;
}
case rt: {
y_num++;
break;
}
case lt: {
y_num--;
break;
}
case up: {
x_num--;
break;
}
}
//目标块如果是墙
if(G[x_num][y_num]==WALL) {
//打通墙和目标块
G[SelectBlock.row][SelectBlock.column] = G[x_num][y_num] = NOTHING;
//再次找出与矿工当前位置相邻的墙
FindBlock();
}
else{//如果不是呢?说明我们的矿工挖到了一个空旷的通路上面 休息一下就好了
//relax
}
//删除这堵墙(把用不了的墙删了,对于那些已经施工过了不必再施工了,同时也是确保我们能跳出循环)
myblock.erase(myblock.begin()+randnum);
}
}
这一部分是用来在界面上生成迷宫以及展现用户自己走迷宫的路线。
void MyPaint(HDC hdc){//生成迷宫
//绘制背景
HBRUSH hbr=CreateSolidBrush(RGB(0,0,0));//创建画刷颜色为黑色,用来绘制背景
SelectObject(hdc,hbr);//通过for循环,初始化网格的背景色为黑色
for(int y=0;y<SIZE;y++)
{
for(int x=0;x<SIZE;x++)
{
Rectangle(hdc,x*WIDTH,y*HEIGHT,(x+1)*WIDTH,(y+1)*HEIGHT);
}
}
CreateMaze();
//绘制路
HBRUSH hbrwhite=CreateSolidBrush(RGB(255,255,255));//创建画刷颜色为白色,用来绘制路
SelectObject(hdc,hbrwhite);//通过for循环,将选定坐标的对应的矩形涂满颜色
for(int i=1;i<SIZE;i++){
for(int j=1;j<SIZE;j++){
if(G[i][j] == NOTHING)Rectangle(hdc,i*WIDTH,j*HEIGHT,(i+1)*WIDTH,(j+1)*HEIGHT);
}
}
//绘制走的路
HBRUSH hbrred=CreateSolidBrush(RGB(255,0,0));//创建画刷颜色为红色,用来绘制走的路
SelectObject(hdc,hbrred);//通过for循环,将选定坐标的对应的矩形涂满颜色
Rectangle(hdc,1*WIDTH,1*HEIGHT,(1+1)*WIDTH,(1+1)*HEIGHT);
for(int z=0;z<m_play.len;z++)
{
Rectangle(hdc,m_play.m_pos[z].x*WIDTH,m_play.m_pos[z].y*HEIGHT,(m_play.m_pos[z].x+1)*WIDTH,(m_play.m_pos[z].y+1)*HEIGHT);
}
//绘制终点
HBRUSH hbrblue=CreateSolidBrush(RGB(0,0,255));//创建画刷颜色为蓝色,用来绘制终点
SelectObject(hdc,hbrblue);
Rectangle(hdc,m_destination.x*WIDTH,m_destination.y*HEIGHT,(m_destination.x+1)*WIDTH,(m_destination.y+1)*HEIGHT);
}
//定义一个程序开始的函数
void OnStart(HWND hWnd){
init();
}
第二部分是用键盘走迷宫:
这个结构体用来表示用户自己走迷宫。
struct play{
POINT m_pos[MAXSIZE];//定义结点的X,Y坐标表示用户要走的路,其中m_pos[0]表示第一个位置
int m_direction;//定义变量表示运行的方向
int len;//长度
}m_play;
下面是键盘控制部分。
switch(wParam)
{
case VK_UP:
m_play.m_pos[0].y=m_play.m_pos[0].y-1;//向上运动
m_play.len++;
break;
case VK_RIGHT:
m_play.m_pos[0].x=m_play.m_pos[0].x+1;//向左运动
m_play.len++;
break;
case VK_DOWN:
m_play.m_pos[0].y=m_play.m_pos[0].y+1;//向下运动
m_play.len++;
break;
case VK_LEFT:
m_play.m_pos[0].x=m_play.m_pos[0].x-1;//向右运动
m_play.len++;
break;
}
for(z=m_play.len-1;z>=1;z--){//更新迷宫的其他部分
m_play.m_pos[z]=m_play.m_pos[z-1];
}
//到达目的地,游戏结束
if(m_play.m_pos[0].x==m_destination.x&&m_play.m_pos[0].y==m_destination.y){//当到达终点
if(IDYES==MessageBox(hWnd,"Game Over,你想重新开始吗?","提示",MB_YESNO)){//游戏结束后出现交互界面,判断是重新开始游戏还是结束游戏
OnStart(hWnd);
}
else{
PostQuitMessage(0);
}
}
hdc=GetDC(hWnd);//提取迷宫移动的方向的句柄
MyPaint(hdc);//用画刷展示
break;
成果展示
最终的视频演示如下:
prime算法内容讲解转载博客