大家好,其实我的迷宫早已经编好了。但是我一直很懒,没有时间把自己的迷宫程序给大家看看。这回我终于有时间了。我这回一定要给大家一个好的迷宫。
首先大家看看,这个迷宫怎么样?由于我的DirectX图形化编程技术还不过关,因此我只用自己熟悉的win32控制台程序来写迷宫,其中一个堆栈完全是我写的。整个工作都是我做的,我希望能够把它完成,并且最终成为一个DirectX图形化程序。
首先介绍我的头文件JStackDefine.h,这个头文件定义了我的数据结构:堆栈。堆栈也称栈,是一种比较重要的数据结构,在走迷宫中尤以得到应用。由于考虑到了重用性,所以使用了模板。但是使用模板时会有一个问题,请大家继续往下看。
- #ifndef_J_DEFINE_H_
- #define_J_DEFINE_H_
- //UsingC++style
- template<typenameCustomType>//可将任意类型应用于此
- structCustomStruct
- {
- CustomTypeelem;
- CustomStruct*link;
- };
- template<typenameCustomType>
- classJStack
- {
- public:
- JStack():base(NULL),top(NULL),JStackSize(NULL){}//默认构造函数
- ~JStack(){}//默认析构函数
- boolIsEmpty(void);//判断是否为空栈
- CustomTypeGetTop(void);//取出栈顶的元素
- CustomTypePop(void);//将元素从栈中弹出
- boolPush(CustomTypee);//将元素压入栈顶
- private:
- CustomStruct<CustomType>*base;//栈底
- CustomStruct<CustomType>*top;//栈顶
- intJStackSize;//栈的元素个数
- };//栈结构的定义
- #endif
接下来我要介绍的是JStackDefine.cpp文件。这个文件包含的是我的类的实现。这样做是为了满足定义和实现的分离,这是一种很好方式。以后希望大家能够使用。这个文件仅仅定义了三个函数模板:判断是否为空栈、将元素压入栈、将元素弹出栈和获取栈顶元素。
- #include"JStackDefine.h"
- template<typenameCustomType>
- boolJStack<CustomType>::IsEmpty(void)//判断是否为空栈
- {
- if(top==base)
- returntrue;
- returnfalse;
- }
- template<typenameCustomType>
- boolJStack<CustomType>::Push(CustomTypee)//将元素压入栈顶
- {
- CustomStruct<CustomType>*temp;
- temp=top;
- top=newCustomStruct<CustomType>;
- if(!top)
- returnfalse;
- top->elem=e;
- top->link=temp;
- JStackSize++;
- returntrue;
- }//此方法相当于头插法
- template<typenameCustomType>
- CustomTypeJStack<CustomType>::Pop(void)//将元素从栈中弹出
- {
- CustomStruct<CustomType>temp;
- temp.elem=top->elem;
- temp.link=top->link;
- deletetop;
- top=temp.link;
- JStackSize--;
- returntemp.elem;
- }
- template<typenameCustomType>
- CustomTypeJStack<CustomType>::GetTop(void)//取出栈顶的元素
- {
- returntop->elem;
- }
使用这个cpp的时候注意,因为仅仅是定义的是函数模板,如果要进行实例化的话,你仍然要在使用的时候包含这个文件,因为系统不会一边寻找h文件对应cpp文件一边帮你实例化的,这一点我在论坛上也没有看到满意的解释。但是他们就是说必须这样的,这里我给的解释应该是合理的。待会儿我告诉大家怎样处理这样的情况。
讲到这里,我觉得好像忘记了什么...对啦,原来我没有给大家介绍一个大体的框架,这怎么办...好吧,我把图截下来,给大家看看我这个程序总共有多少文件。
大家看到了吧,我的这个项目有6个文件。如果你们喜欢的话,完全可以写在一个文件中。但是这样做是不符合软件工程的要求的。所以我就把它写在了六个文件中,这样的话,调用关系很清晰,也很容易找代码。
那么接下来我要向大家介绍的是MazeDefine.h文件。这个文件很简单,只有一条包含语句。
- #ifndef_J_MAZEDEFINE_H_
- #define_J_MAZEDEFINE_H_
- #include"MazeAutoCreate.h"
- #endif
那么它所包含的头文件是什么样子呢?让我们转到MazeAutoCreate.h文件中去。
- #ifndef_J_MAZEAUTOCREATE_H_
- #define_J_MAZEAUTOCREATE_H_
- //MNmustbeoddnumbersforitcanbeprocesscorrectly
- #defineM23
- #defineN39
- structMaze
- {
- inti,j;
- intstate;
- };
- voidMazeAutoCreate(void);
- voidMazeInitialize(void);
- voidMazeShow(void);
- intGetRandom(intseed);
- voidRandomPath(void);
- #endif
这个文件中有对迷宫的定义,其中横坐标M纵坐标N的值必须为奇数。这样做是为了显示的美观。然后定义了一个结构体Maze,这里有横坐标、纵坐标和状态。其中状态有1——走得通,0——走不通,2为入口和出口。然后在MazeAutoCreate()函数中我会对堆栈这个存储结构和数据进行频繁地调用我的堆栈成员函数。MazeInitialize()函数主要是初始化Maze对象的。如果你想再来一次的话,可以调用这个函数。MazeShow()函数主要是显示当前的迷宫。另外GetRandom()函数主要是模拟随机数的生成。这里优化的代码没有使用rand()和srand()函数,没有了这两个函数,我发现我的迷宫效率有着飞速的提高。RandomPath()函数主要处理的是自动走迷宫的问题。事实上,这个函数是最长的。
自动创建的迷宫是怎么实现的呢?让我们来看看MazeAutoCreate.cpp。
- #include<iostream>
- #include<ctime>
- #include"JStackDefine.h"
- #include"JStackDefine.cpp"//Essentialbecausetemplatefunctionbodyneedscalling.
- #include"MazeAutoCreate.h"
- usingnamespacestd;
- //全局变量
- Mazeg_m[M][N];
- JStack<Maze>g_mStack;
- voidMazeAutoCreate(void)
- {
- //首先选择一条路
- MazeInitialize();
- RandomPath();
- MazeShow();
- //MazeClear();//
- }
- voidMazeShow(void)
- {
- inti,j;
- //Definetheentryandexit
- g_m[0][0].state=g_m[M-1][N-1].state=2;
- for(i=0;i<M;i++)
- {
- for(j=0;j<N;j++)
- switch(g_m[i][j].state)
- {//0走不通,1走得通
- case0:cout<<"■";break;
- case1:cout<<"";break;
- case2:cout<<"↘";break;
- }
- cout<<'/n';
- }
- }
- voidMazeInitialize(void)
- {
- inti,j;
- for(i=0;i<M;i++)
- for(j=0;j<N;j++)
- {
- g_m[i][j].i=i;
- g_m[i][j].j=j;
- }
- }
- intGetRandom(intseed)
- {
- returntime(NULL)/seed%4;
- }
- voidRandomPath(void)
- {
- inti=(M/3)*2,j=(N/3)*2;
- intr=3;
- Mazetemp={i,j,0};//temporaryMazestructure
- boollock[4]={false,false,false,false};//Fourdirectionlockvariable
- g_m[i][j].state=1;
- g_mStack.Push(temp);
- while(1)
- {
- temp.i=i,temp.j=j;
- switch(GetRandom(r++))
- {
- case0:
- if(lock[0]==false//是否被锁住
- &&i>1/*是否越界*/
- &&g_m[i-2][j].state!=1/*隔一块是否为空*/
- &&g_mStack.GetTop().i!=i-2)/*是否走回头路*/
- {g_mStack.Push(temp);g_m[i-1][j].state=1;g_m[i-2][j].state=1;i-=2;lock[0]=false,lock[1]=false,lock[2]=false,lock[3]=false;/*Movebacktofalse*/}
- elselock[0]=true;
- break;
- case1:
- if(lock[1]==false//是否被锁住
- &&i<M-2/*是否越界*/
- &&g_m[i+2][j].state!=1/*隔一块是否为空*/
- &&g_mStack.GetTop().i!=i+2)/*是否走回头路*/
- {g_mStack.Push(temp);g_m[i+1][j].state=1;g_m[i+2][j].state=1;i+=2;lock[0]=false,lock[1]=false,lock[2]=false,lock[3]=false;/*Movebacktofalse*/}
- elselock[1]=true;
- break;
- case2:
- if(lock[2]==false//是否被锁住
- &&j>1/*是否越界*/
- &&g_m[i][j-2].state!=1/*隔一块是否为空*/
- &&g_mStack.GetTop().j!=j-2)/*是否走回头路*/
- {g_mStack.Push(temp);g_m[i][j-1].state=1;g_m[i][j-2].state=1;j-=2;lock[0]=false,lock[1]=false,lock[2]=false,lock[3]=false;/*Movebacktofalse*/}
- elselock[2]=true;
- break;
- case3:
- if(lock[3]==false//是否被锁住
- &&j<N-2/*是否越界*/
- &&g_m[i][j+2].state!=1/*隔一块是否为空*/
- &&g_mStack.GetTop().j!=j+2)/*是否走回头路*/
- {g_mStack.Push(temp);g_m[i][j+1].state=1;g_m[i][j+2].state=1;j+=2;lock[0]=false,lock[1]=false,lock[2]=false,lock[3]=false;/*Movebacktofalse*/}
- elselock[3]=true;
- break;
- }
- if(lock[0]==true&&lock[1]==true&&lock[2]==true&&lock[3]==true)
- {
- if(g_mStack.IsEmpty()==true)
- return;
- else
- {
- i=g_mStack.GetTop().i;
- j=g_mStack.GetTop().j;
- g_mStack.Pop();
- lock[0]=false,lock[1]=false,lock[2]=false,lock[3]=false;//Movebacktofalse
- }
- }
- }
- }
当然,作为自动生成迷宫,时间函数当然是要使用的。注意第五行,我特意加上了英文注释,因为这条语句正如我以前所说,是很有用的,在对类模板进行实例化后必须要包含函数模板,这样编译器才能顺利地通过。在这里我重点介绍RandomPath()这个函数。这个函数这一句“int i = (M/3)*2, j = (N/3)*2;”为的是让初始的元素在迷宫的中间处循环而不是在(0,0)处。这一句也是非常重要的“bool lock[4] = { false, false, false, false };//Four direction lock variable”,因为如果循环的时候向贪食蛇一样的撞向自己以前开辟的道路的话,那该怎么办呢?所以我们要避免这个情况。这里定义了四个方向的锁。如果一个方向走不通了,就必须将这个方向锁住,然后再选择走另一个方向。如果四个方向都走不通的话,就表明已经死锁了,这样如果不采取措施的话,就会陷入死循环,迷宫就再也不会出来了。所以要设一句条件句,如果发生了死锁的话,弹出栈顶元素,并且解除锁,再进行随机数的取值。这条语句就是
if ( lock[0] == true && lock[1] ==true && lock[2] == true && lock[3] == true )
{
if ( g_mStack.IsEmpty() == true )
return;
else
{
i = g_mStack.GetTop().i;
j = g_mStack.GetTop().j;
g_mStack.Pop();
lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;//Move back to false
}
}
可能大家还是不明白我的思想,我其实是这样想的。首先我们走的是一条全是墙的空迷宫,然后又计算机自己随便选择上下左右地走两格,并把自己的足迹设为可走,如果要往前可走的路不能走的话,那么就重新地去随机数。不要看“if ( lock[1] == false//是否被锁住
&& i < M-2 /*是否越界*/
&& g_m[i+2][j].state != 1 /*隔一块是否为空*/
&& g_mStack.GetTop().i != i+2 )/*是否走回头路*/”这些判断句子很简单,其实这些都是我的优化算法。它们的排列顺序的不同是影响到执行的效率的(因为与&&运算规则是如果前面一个是假,那么后面的判断将不会执行了。)。随机取道路函数结束后,最后就要显示迷宫的内容了,这个很简单,是什么状态就显示什么的符号。
最后我就要介绍的是我的主函数MainFrame.cpp了。
- #include<cstdlib>
- #include"MazeDefine.h"
- //主函数
- intmain(intargc,char**argv)
- {
- MazeAutoCreate();
- system("pause");
- return0;
- }
这个函数很简单吧,我就不再介绍了。
最后希望大家能够支持我的工作,如果大家觉得好的话,不忘给我表个态。当然我还是希望自己的迷宫能够成功地移植到win32应用程序中。这样我的第一个像样的DirectX也总算有个雏形了。
更新于10月22日:在ubuntu操作系统下(我使用的ubuntu操作系统的版本是9.10),使用终端(terminal),再使用gcc对这些程序进行编译,也可以显示迷宫。这说明这个迷宫的雏形是可以跨平台的。