一、实验内容
1.1 知识点
- 链表
- C++ 基本操作
- 终端字符处理
- 深度优先遍历算法
1.2 实验环境
- xfce 终端
- g++
- Ubuntu16.04
2.1 地图
利用二维数组定义一个高 20 长 30 的地图,同时用特殊代码标记地图的每一个位置。比如通路设置为 0,死路设置为 1 等,在显示地图的时候只需要根据标记打印出地图每个位置对应的符号。比如 0 为通路,用 “ ”,两个空格表示;1 为死路,用“■”,方块表示。
地图标记列表:
0:" " 双空格
1: ■ 方块(通常输入法中输入fangkuai可查找,下同)
2: × 叉(cha)
3: ↓ 下(xia)
4: → 右(you)
5: ← 左(zuo)
6: ↑ 上(shang)
7: ※ 星(xing)
二、实验原理
2.2 路径
路径的生成需要用到深度优先遍历的思想。
-
深度优先遍历
- 从图中某个顶点 V0 出发,并访问此顶点;
- 从 V0 出发,访问 V0 的某个未曾访问的邻接点 W1,W2,…,Wk(多个中选一个即可);
- 然后,重复步骤 2,如果已经该节点没有未被访问的邻接点了,则回到上一个节点,从它的另外一个邻接点开始;
- 重复步骤 2 和步骤 3,直到全部顶点都被访问为止。
-
深度优先生成树
- 在深度优先遍历中,如果将每次“前进”(纵深)路过的(将被访问的)结点和边都记录下来,就得到一个子图,该子图为以出发点为根的树,称为深度优先生成树。这种情况与广度优先遍历类似。
2.3 游戏规则
游戏分为自动操作和手段操作:自动操作是利用深度优先算法自动寻找路径,直到到达出口;手动操作分为上下左右移动,分别对应w
,x
,a
,d
,需要判断下一步操作地址进行比较,如果下一步操作可行,则移动,并且清除当前信息,到达出口则游戏结束。
3.2 全局变量
在全局变量中定义地图,包括地图的长和高(30 * 20),同时还需定义一些标记。
falg
用于创建地图,true 代表地图创建成功;slow
用于更换游戏速度,true 表示慢速游戏;autogame
用于更换游戏模式,true 代表自动游戏。
3.3 路线栈
路线栈设计来记录移动路径。路径栈是一个类,它可以记录地图的坐标和移动轨迹。
class stack_of_maze{
private:
//code
public:
//code
};
成员变量(私有)
在成员变量中定义一个数据结构来包括地图坐标和移动信息。
//记录迷宫坐标
struct node
{
int x;
int y;
char direction; //上一步路径(如何来的)
node* next;
};
node* head;
成员函数(公有)
构造和析构
stack_of_maze(){
//初始化
head = NULL;
}
~stack_of_maze(){
node* p = head;
//逐个删除
while(head!=NULL){
head = head->next;
delete p;
p = head;
}
}
压栈
设置一个新节点用于插入,首先将信息记录到新节点中,再把新节点插入到栈顶。这个函数的功能是用于添加新的移动信息。
void push(int xx,int yy,char ddirection){
//定义一个新节点
node* new_node = new node;
//赋值
if(new_node!=NULL){
new_node->x = xx;
new_node->y = yy;
new_node->direction = ddirection;
new_node->next = NULL;
//判断栈是否为空,如果为空则直接把新节点赋值给栈,否则添加到栈顶
if(head==NULL)
head = new_node;
else{
new_node->next = head;
head = new_node;
}
}
else
cout<<"内存分配失败"<<endl;
}
出栈
推出栈顶元素,用于判断路径是否正确,如果下一个节点为空则说明路径错误,前方不能通行。
node* pop(int& xx,int& yy){
if(head!=NULL){
node* p = head;
head = head->next;
xx = p->x;
yy = p->y;
delete p;
}
return head;
}
打印
打印路径信息。
void print(){
if(head!=NULL){
node* p = head;
while(p!=NULL){
cout<<" "<<p->x<<" "<<p->y<<" "<<p->direction<<endl;
p = p->next;
}
}
else
cout<<"栈为空,打印失败"<<endl;
}
3.4 创建地图
首先把地图全置为 1,即死路,然后除了出入口和最外一层墙壁保持不变,其他位置采用随机函数来初始化为通路 0。纵坐标为 x,横坐标为 y。
void createMaze(){
int maxway = MAX_X * MAX_Y; //最大通路
int x,y;
//先填充迷宫
for(x=0;x<MAX_X;x++)
for(y=0;y<MAX_Y;y++)
maze[x][y] = 1;
//随机函数种子,以时间为参数
srand((unsigned)time(NULL));
//随机构建迷宫通路
for(int i=0;i<maxway;i++)
{
x = rand() % (MAX_X-2) + 1;
y = rand() % (MAX_Y-2) + 1;
maze[x][y] = 0;
}
maze[1][1] = 0; //入口
maze[MAX_X-2][MAX_Y-2] = 0; //出口
maze[0][1] = 3;
maze[MAX_X-1][MAX_Y-2] = 0;
}
根据标记列表对应符号来打印地图。
注意:根据不同的字符编码,有的操作系统"■"为一个字符,有的为两个,我们这里使用两个字符表示(和双空格对应)。因为我们的实验环境识别"■"为一个字符,所以在需添加空格"■ ",其他字符相同。
void printMaze(){
int x,y;
//清屏,如果是windows环境使用system("cls")
system("clear");
//打印地图
for(x=0;x<MAX_X;x++)
{
for(y=0;y<MAX_Y;y++)
{
if(maze[x][y]==0){cout<<" ";continue;} //通路
if(maze[x][y]==1){cout<<"■ ";continue;} //墙
if(maze[x][y]==2){cout<<"× ";continue;} //死胡同
if(maze[x][y]==3){cout<<"↓ ";continue;} //向下走
if(maze[x][y]==4){cout<<"→ ";continue;}
if(maze[x][y]==5){cout<<"← ";continue;}
if(maze[x][y]==6){cout<<"↑ ";continue;}
if(maze[x][y]==7){cout<<"※ ";continue;} //当前站立位置
}
cout<<endl;
}
//是否慢速游戏
if(slow){
sleep(1); //延时函数
}
}
3.4 检查地图
因为地图的创建是随机的,所以为了能正确的进行游戏(不是死胡同),所以这个函数就是为了检查已创建的地图是否正确,这里传递定义一条备用路径来检验,如果通路则flag
标记为 true,游戏使用这个地图
void check(stack_of_maze &s){
//备份地图
int temp[MAX_X][MAX_Y];
for(int x=0;x<MAX_X;x++)
for(int y=0;y<MAX_Y;y++)
temp[x][y] = maze[x][y];
int x=1,y=1; //出发点
while(1){
temp[x][y] = 2;
//向下
if(temp[x+1][y]==0){
s.push(x,y,'D');
//在当前位置做一个向下的标志
temp[x][y] = 3;
x = x + 1;
temp[x][y] = 7; //当前位置
//判断是否到达出口,如果到达出口则flag标记为true,下同
if((x==MAX_X-1)&&(y==MAX_Y-2)){
flag = true;
return;
}
else
continue;
}
//向右
if(temp[x][y+1]==0){
s.push(x,y,'R');
//在当前位置做一个向右的标志
temp[x][y] = 4;
y = y + 1;
temp[x][y] = 7;
if((x==MAX_X-1)&&(y==MAX_Y-2)){
flag = true;
return;
}
else
continue;
}
//向上
if(temp[x-1][y]==0){
s.push(x,y,'U');
//在当前位置做一个向上的标志
temp[x][y] = 6;
x = x - 1;
temp[x][y] = 7;
if((x==MAX_X-1)&&(y==MAX_Y-2)){
flag = true;
return;
}
else
continue;
}
//向左
if(temp[x][y-1]==0){
s.push(x,y,'L');
//在当前位置做一个向右的标志
temp[x][y] = 5;
y = y - 1;
temp[x][y] = 7;
if((x==MAX_X-1)&&(y==MAX_Y-2)){
flag = true;
return;
}
else
continue;
}
//上下左右不通,则回退到起点
if(s.pop(x,y)==NULL && temp[x-1][y]!=0 && temp[x][y-1]!=0 && temp[x][y+1]!=0 && temp[x+1][y]!=0){
temp[0][1] = 7;
if(temp[1][1]!=1)
temp[1][1] = 2;
return;
}
}
}
3.6 获取命令
通常的输入命令如cin
或getchar()
有几个个问题。第一是回显,就是会在终端窗口显示输入的命令;第二,必须键入回车才会接收字符。既然是游戏,我们更好地肯定是实时操作,在 Windows 中我们只需要使用conio.h
中的getch()
函数就行了,但是在 Linux 没有这个库,所以在这里我们需要自定义一个相同功能的函数。
char getch(){
char ch;
//保存原有终端属性和新设置的终端属性
static struct termios oldt, newt;
//获得终端原有属性并保存在结构体oldt
tcgetattr( STDIN_FILENO, &oldt);
//设置新的终端属性
newt = oldt;
newt.c_lflag &= ~(ICANON);
tcsetattr( STDIN_FILENO, TCSANOW, &newt);
//取消回显
system("stty -echo");
ch = getchar();
system("stty echo");
//让终端恢复为原有的属性
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
3.7 移动
移动的规则在上面已经有过介绍,在这里主要进行命令识别。得到正确的命令后,首先需要判断下一个地址是否时通路,如果时则需要清除当前信息在移动到下一步。
void move(){
int x=1,y=1; //出发点
//一直游戏,直到走出
while(1){
//判断输入的命令
switch(getch()){
case 's':
if(maze[x+1][y]==0){
maze[x][y] = 0;
x = x + 1;
maze[x][y] = 7; //当前位置
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
cout<<"\n\n 成功走出"<<endl;
return;
}
}
break;
case 'd':
if(maze[x][y+1]==0){
if(maze[x][y+1]==0){
maze[x][y] = 0;
y = y + 1;
maze[x][y] = 7;
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
cout<<"\n\n 成功走出"<<endl;
return;
}
}
}
break;
case 'w':
if(maze[x-1][y]==0){
maze[x][y] = 0;
x = x - 1;
maze[x][y] = 7;
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
cout<<"\n\n 成功走出"<<endl;
return;
}
}
break;
case 'a':
if(maze[x][y-1]==0){
maze[x][y] = 0;
y = y - 1;
maze[x][y] = 7;
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
cout<<"\n\n 成功走出"<<endl;
return;
}
}
break;
}
}
}
3.8 自动游戏
游戏会根据深度优先算法自动搜索路径,其中游戏会根据slow
标志选择游戏的速度,slow
为 true 会慢速游戏显示每一步过程,算法和上面检测功能十分相似。
void autoMove(stack_of_maze &s){
int x=1,y=1; //出发点
while(1){
maze[x][y] = 2;
//向下
if(maze[x+1][y]==0){
s.push(x,y,'D');
maze[x][y] = 3; //在当前位置做一个向下的标志
x = x + 1;
maze[x][y] = 7; //当前位置
if(slow)
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
s.push(x,y,'*');
cout<<"\n\n 成功走出"<<endl;
return;
}
else
continue;
}
//向右
if(maze[x][y+1]==0){
s.push(x,y,'R');
maze[x][y] = 4; //在当前位置做一个向右的标志
y = y + 1;
maze[x][y] = 7;
if(slow)
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
s.push(x,y,'*');
cout<<"\n\n 成功走出"<<endl;
return;
}
else
continue;
}
//向上
if(maze[x-1][y]==0){
s.push(x,y,'U');
maze[x][y] = 6; //在当前位置做一个向上的标志
x = x - 1;
maze[x][y] = 7;
if(slow)
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
s.push(x,y,'*');
cout<<"\n\n 成功走出"<<endl;
return;
}
else
continue;
}
//向左
if(maze[x][y-1]==0){
s.push(x,y,'L');
maze[x][y] = 5; //在当前位置做一个向右的标志
y = y - 1;
maze[x][y] = 7;
if(slow)
printMaze();
if((x==MAX_X-1)&&(y==MAX_Y-2)){
s.push(x,y,'*');
cout<<"\n\n 成功走出"<<endl;
return;
}
else
continue;
}
//上下左右不通,则回退
if(s.pop(x,y)==NULL && maze[x-1][y]!=0 && maze[x][y-1]!=0 && maze[x][y+1]!=0 && maze[x+1][y]!=0){
cout<<"\n\n 没有找到合适的路径"<<endl;
maze[0][1] = 7;
if(maze[1][1]!=1)
maze[1][1] = 2;
return;
}
}
}
3.9 开始游戏
首先会调用函数创建地图,然后根据标志位来进行游戏。因为这里会回调 menu() 函数,所以在前面需先声明一下。
void menu();
void gamestart(){
//初始化地图
flag = false;
while(!flag){
stack_of_maze stack;
//创建地图
createMaze();
//检查地图是否创建成功
check(stack);
//模仿进度条
system("clear");
cout<<"\t* loading. *"<<endl;
system("clear");
cout<<"\t* loading.. *"<<endl;
system("clear");
cout<<"\t* loading... *"<<endl;
}
//输出当前迷宫的初始状态
printMaze();
cout<<"\n\n 输入enter键继续"<<endl;
getchar();
//自行游戏
if(!autogame){
move();
cout<<"\n\n 输入enter键继续"<<endl;
getchar();
menu();
}
//自动游戏
else{
stack_of_maze stack1;
autoMove(stack1); //行走中……
}
printMaze(); //输出迷宫的最终状态
cout<<"\n\n 输入enter键继续"<<endl;
getchar();
menu();
}
3.10 菜单
定义一个简单的菜单来选择游戏模式
void menu(){
system("clear");
int num;
cout<<"\t****************************************"<<endl;
cout<<"\t* *"<<endl;
cout<<"\t* 1.查看路径 *"<<endl;
cout<<"\t* *"<<endl;
cout<<"\t* 2.自动进行 *"<<endl;
cout<<"\t* *"<<endl;
cout<<"\t* 3.自行游戏 *"<<endl;
cout<<"\t* *"<<endl;
cout<<"\t* 4.退出游戏 *"<<endl;
cout<<"\t* *"<<endl;
cout<<"\t****************************************"<<endl;
slow = false;
//选择模式
switch(getch()){
case '1':
autogame = true;
gamestart();
break;
case '2':
autogame = true;
slow = true;
gamestart();
break;
case '3':
autogame = false;
gamestart();
break;
case '4':
exit(1);break;
default:
cout<<"\n\n 错误操作,输入enter返回!"<<endl;
getchar();
menu();
}
getchar();
}
3.11 主函数
主函数非常简单,只需要调用 menu 函数即可。
int main(int argc,char** argv){
menu();
return 0;
}
四、运行
4.1 编译
g++ maze.cpp -o maze
4.2 运行
./maze