c++解决迷宫问题

一、实验内容

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 获取命令

通常的输入命令如cingetchar()有几个个问题。第一是回显,就是会在终端窗口显示输入的命令;第二,必须键入回车才会接收字符。既然是游戏,我们更好地肯定是实时操作,在 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

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值