精华内容
下载资源
问答
  • 随机迷宫生成

    2014-12-09 15:54:31
    随机迷宫生成,包含了栈等一系列C知识,非常适合初学者学习。
  • java实现随机迷宫生成、走迷宫小游戏的完整代码,初始化迷宫生成,按下空格为系统提示,上下左右控制移动。本实例需要从 .txt 文件中读取迷宫并绘制。 本程序适用于java程序员巩固类与对象、文件读取、事件响应、...
  • 主要介绍了Flutter随机迷宫生成和解迷宫小游戏,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 随机迷宫生成算法浅析 随机迷宫生成算法浅析
  • 迷宫发电机 JavaScript中的随机迷宫生成器 在这里进行测试: :
  • MazeGenerator:使用递归算法的随机迷宫生成
  • 今天小编就为大家分享一篇关于Java基于深度优先遍历的随机迷宫生成算法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • Flutter-随机迷宫生成和解迷宫小游戏

    千次阅读 2020-04-15 22:06:18
    此博客旨在帮助大家更好的了解图的遍历算法,通过Flutter移动端平台将图的遍历算法运用在迷宫生成和解迷宫上,让算法变成可视化且可以交互,最终做成一个可进行随机迷宫生成和解迷宫的APP小游戏。本人是应届毕业生,...

    此博客旨在帮助大家更好的了解图的遍历算法,通过Flutter移动端平台将图的遍历算法运用在迷宫生成和解迷宫上,让算法变成可视化且可以进行交互,最终做成一个可进行随机迷宫生成和解迷宫的APP小游戏。本人是应届毕业生,希望能与大家一起讨论和学习~

    注:由于这是本人第一次写博客,难免排版或用词上有所欠缺,请大家多多包涵。
    注:如需转载文章,请注明出处,谢谢。

    一、项目介绍:

    1.概述
    项目名:方块迷宫
    作者:沫小亮。
    编程框架与语言:Flutter&Dart
    开发环境:Android Studio 3.6.2
    学习参考:慕课网-看得见的算法
    项目完整源码地址:https://gitee.com/moxiaoliang123/maze_game.git
    游戏截图:
    在这里插入图片描述在这里插入图片描述

    2.迷宫生成原理
    1.采用图的遍历进行迷宫生成,其本质就是生成一棵树,树中每个节点只能访问一次,且每个节点之间没有环路(迷宫的正确路径只有一条)。
    2.初始化:设置起点和终点位置,并给所有行坐标为奇数且列坐标为奇数的位置设置为路。其余位置设置为墙。(坐标从0…开始算)

    (如下图,蓝色位置为墙,橙色位置为路,橙色线条为可能即将打通的路,此图来源于慕课网-看得见的算法)
    在这里插入图片描述
    3.在遍历过程中,不断遍历每个位置,同时遍历过的位置设为已访问位置,结合迷宫生成算法(见迷宫特点第6点)让相邻某个墙变成路,使之路径联通。直至所有位置都遍历完成则迷宫生成结束(每个节点只能遍历一次)。

    (如下图,蓝色位置为墙,橙色位置为路,橙色线条为可能即将打通的路,此图来源于慕课网-看得见的算法)
    在这里插入图片描述

    3.迷宫特点(可根据需求自行扩展)
    1.迷宫只有一个起点、一个终点,且起点和终点的位置固定。
    2.迷宫的正确路径只有一条。
    3.迷宫的正确路径是连续的。
    4.迷宫地图是正方形,且方块行数和列数都为奇数。
    5.迷宫中每个方块占用一个单元格。
    6.迷宫生成算法:图的深度优先遍历和广度优先遍历相结合 + 随机队列(入队和出队随机在队头或队尾)+ 随机方向遍历顺序(提高迷宫的随机性)。
    7.迷宫自动求解算法:图的深度优先遍历(递归方法)。

    4.玩法介绍(可根据需求自行扩展)
    1.游戏共设置有10个关卡,到达终点可以进入下一关,随着关卡数的增加,迷宫地图大小(方块数)增加,但限定时间也会增加。
    2.点击方向键可对玩家角色的位置进行控制。
    2.每个关卡都有限定时间,超过限定时间仍未到达终点则闯关失败,可从本关继续挑战。
    3.每个关卡都可以使用一次提示功能,可展示2秒的正确路径,便于小白玩家入门。
    4. 颜色对应:
    蓝灰色方块->墙(不可经过)
    蓝色方块->玩家角色(可控制移动)
    白色方块->路(可经过)
    深橘色->终点(通关)
    橙色->正确路径(提示功能)

    二、项目源码(主要部分):

    pubspec.yaml //flutter配置清单

    dependencies:
      flutter:
        sdk: flutter
      //toast库
      fluttertoast: ^3.1.3
      //Cupertino主题图标集
      cupertino_icons: ^0.1.2
    

    在这里插入图片描述

    • maze_game_model.dart //迷宫游戏数据层
    class MazeGameModel {
      int _rowSum; //迷宫行数
      int _columnSum; //迷宫列数
      int _startX, _startY; //迷宫入口坐标([startX,startY])
      int _endX, _endY; //迷宫出口坐标([endX,endY])
      static final int MAP_ROAD = 1; //1代表路
      static final int MAP_WALL = 0; //0代表墙
      List<List<int>> mazeMap; //迷宫地形(1代表路,0代表墙)
      List<List<bool>> visited; //是否已经访问过
      List<List<bool>> path; //是否是正确解的路径
      List<List<int>> direction = [
        [-1, 0],
        [0, 1],
        [1, 0],
        [0, -1]
      ]; //迷宫遍历的方向顺序(迷宫趋势)
      int spendStepSum = 0; //求解的总步数
      int successStepLength = 0; //正确路径长度
      int playerX, playerY; //当前玩家坐标
    
      MazeGameModel(int rowSum, int columnSum) {
        if (rowSum % 2 == 0 || columnSum % 2 == 0) {
          throw "model_this->迷宫行数和列数不能为偶数";
        }
        this._rowSum = rowSum;
        this._columnSum = columnSum;
        mazeMap = new List<List<int>>();
        visited = new List<List<bool>>();
        path = new List<List<bool>>();
    
        //初始化迷宫起点与终点坐标
        _startX = 1;
        _startY = 0;
        _endX = rowSum - 2;
        _endY = columnSum - 1;
    
        //初始化玩家坐标
        playerX = _startX;
        playerY = _startY;
    
        //初始化迷宫遍历的方向(上、左、右、下)顺序(迷宫趋势)
        //随机遍历顺序,提高迷宫生成的随机性(共12种可能性)
        for (int i = 0; i < direction.length; i++) {
          int random = Random().nextInt(direction.length);
          List<int> temp = direction[random];
          direction[random] = direction[i];
          direction[i] = temp;
        }
    
        //初始化迷宫地图
        for (int i = 0; i < rowSum; i++) {
          List<int> mazeMapList = new List();
          List<bool> visitedList = new List();
          List<bool> pathList = new List();
    
          for (int j = 0; j < columnSum; j++) {
            //行和列都为基数则设置为路,否则设置为墙
            if (i % 2 == 1 && j % 2 == 1) {
              mazeMapList.add(1); //设置为路
            } else {
              mazeMapList.add(0); //设置为墙
            }
            visitedList.add(false);
            pathList.add(false);
          }
          mazeMap.add(mazeMapList);
          visited.add(visitedList);
          path.add(pathList);
        }
        //初始化迷宫起点与终点位置
        mazeMap[_startX][_startY] = 1;
        mazeMap[_endX][_endY] = 1;
      }
    
      //返回迷宫行数
      int getRowSum() {
        return _rowSum;
      }
    
      //返回迷宫列数
      int getColumnSum() {
        return _columnSum;
      }
    
      //返回迷宫入口X坐标
      int getStartX() {
        return _startX;
      }
    
      //返回迷宫入口Y坐标
      int getStartY() {
        return _startY;
      }
    
      //返回迷宫出口X坐标
      int getEndX() {
        return _endX;
      }
    
      //返回迷宫出口Y坐标
      int getEndY() {
        return _endY;
      }
    
      //判断[i][j]是否在迷宫地图内
      bool isInArea(int i, int j) {
        return i >= 0 && i < _rowSum && j >= 0 && j < _columnSum;
      }
    }
    
    • position.dart //位置类(实体类)
      注:x对应二维数组中的行下标,y对应二维数组中的列下标(往后也是)
    class Position extends LinkedListEntry<Position>{
      int _x, _y;             //X对应二维数组中的行下标,y对应二维数组中的列下标
      Position _prePosition;  //存储上一个位置
      
      Position(int x, int y,  { Position prePosition = null } ) {
        this._x = x;
        this._y = y;
        this._prePosition = prePosition;
      }
    
      //返回X坐标()
      int getX() {
        return _x;
      }
    
      //返回Y坐标()
      int getY() {
        return _y;
      }
    
      //返回上一个位置
      Position getPrePosition() {
        return _prePosition;
      }
    }
    
    • random_queue.dart //随机队列
      入队:头部或尾部(各50%的概率)
      出队:头部或尾部(各50%的概率)
      底层数据结构:LinkedList
    class RandomQueue {
      LinkedList<Position> _queue;
    
      RandomQueue(){
        _queue = new LinkedList();
      }
    
      //往随机队列里添加一个元素
      void addRandom(Position position) {
        if (Random().nextInt(100) < 50) {
         //从头部添加
          _queue.addFirst(position);
        }
        //从尾部添加 
        else {
          _queue.add(position);
        }
      }
      
      //返回随机队列中的一个元素
      Position removeRandom() {
        if (_queue.length == 0) {
          throw "数组元素为空";
        }
        if (Random().nextInt(100) < 50) {
          //从头部移除
          Position position = _queue.first;
          _queue.remove(position);
          return position;
        } else {
          //从尾部移除
          Position position = _queue.last;
          _queue.remove(position);
          return position;
        }
      }
    
      //返回随机队列元素数量
      int getSize() {
        return _queue.length;
      }
    
      //判断随机队列是否为空
      bool isEmpty() {
        return _queue.length == 0;
      }
    }
    
    • main.dart //迷宫游戏视图层和控制层

    1. APP全局设置

    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        if (Platform.isAndroid) {
          // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。
          SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
          SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
        }
        return MaterialApp(
          title: '方块迷宫',     //应用名
          theme: ThemeData(
            primarySwatch: Colors.blue, //主题色
          ),
          debugShowCheckedModeBanner: false,  //不显示debug标志
          home: MyHomePage(),   //主页面
        );
      }
    }
    

    2.界面初始化

     class MyHomePage extends StatefulWidget {
      MyHomePage({Key key}) : super(key: key);
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int gameWidth, gameHeight;     //游戏地图宽度和高度
      double itemWidth, itemHeight;  //每个小方块的宽度和高度
      int level = 1;                 //当前关卡数(共10关)
      int rowSum = 15;               //游戏地图行数
      int columnSum = 15;            //游戏地图列数
      int surplusTime;               //游戏剩余时间
      bool isTip = false;            //是否使用提示功能
      Timer timer;                   //计时器
      MazeGameModel _model;          //迷宫游戏数据层
    
      //初始化状态
      @override
      void initState() {
        super.initState();
        _model = new MazeGameModel(rowSum, columnSum);
    
        //新建一个事件循环队列,确保不堵塞主线程
        new Future(() {
          //生成一个迷宫
          _doGenerator(_model.getStartX(), _model.getStartY() + 1);
        });
    
        //设置倒计时
        _setSurplusTime(level);
      }
    
    

    3.界面整体结构

     @override
      Widget build(BuildContext context) {
        //获取手机屏幕宽度,并让屏幕高度等于屏幕宽度(确保形成正方形迷宫区域)
        //结果向下取整,避免出现实际地图宽度大于手机屏幕宽度的情况
        gameHeight = gameWidth = MediaQuery.of(context).size.width.floor();
        //每一个小方块的宽度和长度(屏幕宽度/列数)
        itemHeight = itemWidth = (gameWidth / columnSum);
        return Scaffold(
          appBar: PreferredSize(
              //设置标题栏高度
              preferredSize: Size.fromHeight(40),
              //标题栏区域
              child: _appBarWidget()),
          body: ListView(
            children: <Widget>[
              //游戏地图区域
              _gameMapWidget(),
              //游戏提示与操作栏区域
              _gameTipWidget(),
              //游戏方向控制区域
              _gameControlWidget(),
            ],
          ),
        );
      }
    

    4.游戏地图区域

    注:由于游戏提示与操作栏区域、游戏方向键控制区域不是本文章要讲的重点,故不详细介绍,有兴趣的朋友可以到完整项目源码地址中查看。

     //游戏地图区域
      Widget _gameMapWidget(){
        return Container(
            width: gameHeight.toDouble(),
            height: gameHeight.toDouble(),
            color: Colors.white,
            child: Center(
              //可堆叠布局(配合Positioned绝对布局使用)
              child: Stack(
                //按行遍历
                children: List.generate(_model.mazeMap.length, (i) {
                  return Stack(
                    //按列遍历
                      children: List.generate(_model.mazeMap[i].length, (j) {
                        //绝对布局
                        return Positioned(
                            //每个方块的位置
                            left: j * itemWidth.toDouble(),
                            top: i * itemHeight.toDouble(),
                            //每个方块的大小和颜色
                            child: Container(
                                width: itemWidth.toDouble(),
                                height: itemHeight.toDouble(),
                                //位于顶层的颜色应放在前面进行判断,避免被其他颜色覆盖
                                //墙->蓝灰色
                                //路->白色
                                //玩家角色->蓝色
                                //迷宫终点-> 深橘色
                                //迷宫正确路径->橙色
                                color: _model.mazeMap[i][j] == 0
                                    ? Colors.blueGrey
                                    : (_model.playerX == i && _model.playerY == j)
                                    ? Colors.blue
                                    : (_model.getEndX() == i && _model.getEndY() == j)
                                    ? Colors.deepOrange
                                    : _model.path[i][j] ? Colors.orange : Colors.white));
                      }));
                }),
              ),
            ));
      }
    

    5.生成迷宫

    //开始生成迷宫地图
      void _doGenerator(int x, int y) {
        RandomQueue queue = new RandomQueue();
        //设置起点
        Position start = new Position(x, y);
        //入队
        queue.addRandom(start);
        _model.visited[start.getX()][start.getY()] = true;
        while (queue.getSize() != 0) {
          //出队
          Position curPosition = queue.removeRandom();
          //对上、下、左、右四个方向进行遍历,并获得一个新位置
          for (int i = 0; i < 4; i++) {
            int newX = curPosition.getX() + _model.direction[i][0] * 2;
            int newY = curPosition.getY() + _model.direction[i][1] * 2;
            //如果新位置在地图范围内且该位置没有被访问过
            if (_model.isInArea(newX, newY) && !_model.visited[newX][newY]) {
              //入队
              queue.addRandom(new Position(newX, newY, prePosition: curPosition));
              //设置该位置为已访问
              _model.visited[newX][newY] = true;
              //设置该位置为路
              _setModelWithRoad(curPosition.getX() + _model.direction[i][0], curPosition.getY() + _model.direction[i][1]);
            }
          }
        }
      }
    

    6.自动解迷宫(提示功能)

    //自动解迷宫(提示功能)
      //从起点位置开始(使用递归的方式)求解迷宫,如果求解成功则返回true,否则返回false
      bool _doSolver(int x, int y) {
        if (!_model.isInArea(x, y)) {
          throw "坐标越界";
        }
        //设置已访问
        _model.visited[x][y] = true;
        //设置该位置为正确路径
        _setModelWithPath(x, y, true);
    
        //如果该位置为终点位置,则返回true
        if (x == _model.getEndX() && y == _model.getEndY()) {
          return true;
        }
        //对四个方向进行遍历,并获得一个新位置
        for (int i = 0; i < 4; i++) {
          int newX = x + _model.direction[i][0];
          int newY = y + _model.direction[i][1];
          //如果该位置在地图范围内,且该位置为路,且该位置没有被访问过,则继续从该点开始递归求解
          if (_model.isInArea(newX, newY) &&
              _model.mazeMap[newX][newY] == MazeGameModel.MAP_ROAD &&
              !_model.visited[newX][newY]) {
            if (_doSolver(newX, newY)) {
              return true;
            }
          }
        }
        
        //如果该位置不是正确的路径,则将该位置设置为非正确路径所途径的位置
        _setModelWithPath(x, y, false);
        return false;
      }
    

    7.控制玩家角色移动

    • 移动到新位置
    //控制玩家角色移动
      void _doPlayerMove(String direction) {
        switch (direction) {
          case "上":
          //如果待移动的目标位置在迷宫地图内,且该位置是路,则进行移动
            if (_model.isInArea(_model.playerX - 1, _model.playerY) && _model.mazeMap[_model.playerX - 1][_model.playerY] == 1) {
              setState(() {
                _model.playerX--;
              });
            }
            break;
    //省略其他三个方向的代码
    
    • 玩家到达终点位置
    //如果玩家角色到达终点位置
    if (_model.playerX == _model.getEndX() && _model.playerY == _model.getEndY()) {
          isTip = false;     //刷新可提示次数
          timer.cancel();    //取消倒计时
          //如果当前关是第10关
          if (level == 10) {
            showDialog(
                barrierDismissible: false,
                context: context,
                builder: (BuildContext context) {
                  return AlertDialog(
                    content: Text("骚年,你已成功挑战10关,我看你骨骼惊奇,适合玩迷宫(狗头"),
                    actions: <Widget>[
                      new FlatButton(
                        child: new Text('继续挑战第10关(新地图)', style: TextStyle(fontSize: 16)),
                        onPressed: () {
                          setState(() {
                            _model.playerX = _model.getStartX();
                            _model.playerY = _model.getStartY();
                          });
                          //重新初始化数据
                          _model = new MazeGameModel(rowSum, columnSum);
                          //生成迷宫和设置倒计时
                          _doGenerator(_model.getStartX(), _model.getStartY() + 1);
                          _setSurplusTime(level);
                          Navigator.of(context).pop();
                        },
                      )
                    ],
                  );
                });
          }
          //如果当前关不是第10关
          else {
            showDialog(
                barrierDismissible: false,
                context: context,
                builder: (BuildContext context) {
                  return AlertDialog(
                    content: Text("恭喜闯关成功"),
                    actions: <Widget>[
                      new FlatButton(
                        child: new Text('挑战下一关', style: TextStyle(fontSize: 16)),
                        onPressed: () {
                          setState(() {
                            //关卡数+1,玩家角色回到起点
                            level++;
                            _model.playerX = _model.getStartX();
                            _model.playerY = _model.getStartY();
                          });
                          //重新初始化数据
                          _model = new MazeGameModel(rowSum = rowSum + 4, columnSum = columnSum + 4);
                          //生成迷宫和设置倒计时
                          _doGenerator(_model.getStartX(), _model.getStartY() + 1);
                          _setSurplusTime(level);
                          Navigator.of(context).pop();
                        },
                      )
                    ],
                  );
                });
          }
        }
    

    注:其他与控制逻辑相关的方法不在此文中详细介绍,有兴趣的朋友可以到完整项目源码地址中浏览。

    展开全文
  • Random-Maze-Generator:我创建的随机迷宫生成
  • 接下来一段时间,想要研究下随机迷宫生成算法,打算在有空可时候偶尔更新一下这方面的学习过程。随机迷宫的生成算法有很多种,比如递归回溯,递归分割,随机Prime等等。今天是第一次尝试随机迷宫生成,就先试一下用...

    迷宫生成算法(1)深度优先搜索

    接下来一段时间,想要研究下随机迷宫生成算法,打算在有空可时候偶尔更新一下这方面的学习过程。随机迷宫的生成算法有很多种,比如递归回溯,递归分割,随机Prime等等。今天是第一次尝试随机迷宫生成,就先试一下用递归的方法通过深度优先搜索来生成随机迷宫。

    首先我们来明确一下基本观念,迷宫可以通过一个二维数组来表示,二维数组中的元素就表示存在于迷宫中的位置,他们可能是可以行走的路,也有可能是不能进入的障碍物或者围栏。我们只要通过两种不同的字符就可以标记障碍物和通道,比如我们使用false来表示一个位置是障碍物,而使用true来表示位置可以通行。在下面的例子中我们就沿用这个规定,整个迷宫可以使用一个二维的布尔型数组来表示。

    接下来我们确定一下对迷宫的看法,我们认为一个迷宫只能有唯一解,也就是说从起点到终点不会有两条不一样的路线。而遍历迷宫的过程可以被看成是一个拆墙的过程,如果拆了一个墙会导致两个已经被标记为通道的方块连接,那么拆这面墙就是不合法的,这个条件是递归回溯过程中最重要的判定条件。

    最后来看一下递归回溯算法的过程:

    1. 将初始位置设置为当前位置(入栈),然后将它标记为通路
    2. 从上,下,左,右四个方向寻找当前方块的相邻方块,判断找到的相邻方块周围是否有其他通路,如果有则继续寻找其他相邻方块,如果没有则选择该方块为当前方块(入栈)
    3. 标记当前方块为通路,然后重复进行上一个过程
    4. 如果当前方块的相邻方块都不满足条件,则恢复上一个方块为当前方块(出栈),并继续执行过程2

    以上就是深度优先遍历生成随机迷宫的基本步骤,接下来我们看代码实现:

    定义一个迷宫类,它的私有数据成员保存着记录迷宫状态的二维数组,查找方向,迷宫尺寸,入口点等信息:

    class Maze
    {
    public:
        //构造函数,通过传入的参数创建并创建并初始化二维数组
    	Maze(int row, int column);
    	ostream& print();
    	//设置迷宫入口的内联函数,注意这个入口并不是遍历起点,而是在边界上挖出的一个洞
    	void setEntry(int x,int y)
    	{
    
    		if (isValidEntry(x,y)) {
    			mazePtr[x - 1][y - 1] = true;
    			if (x == 1) {
    				startX = 2;
    				startY = y;
    			}
    			if (x == row) {
    				startX = row-1;
    				startY = y;
    			}
    			if (y == 1) {
    				startX = x;
    				startY = 2;
    			}
    			if (y == column) {
    				startX = 2;
    				startY = column-1;
    			}
    			//cout << mazePtr[x - 1][y - 1];
    		}
    	}
        //创建迷宫
    	bool createMaze();
    private:
    	bool isInRange(int x, int y);
    	bool isValidEntry(int x, int y);
        //递归回溯的算法的核心实现
    	bool dig(int x, int y);
    	int startX;
    	int startY;
    	int row;
    	int column;
    	vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
    	unique_ptr<bool*[]> mazePtr;
    };
    

    然后我们在类外完成对构造函数的定义,他接受行列两个参数来动态创建一个布尔型二维数组,并与此同时初始化为false,允许通过初始化列表来为动态分配的内存初始化是C++11引入的新特性,这里为了方便管理堆内存,采用了智能指针类型unique_ptr来管理动态分配的内存,这种智能指针类型也是C++11新增的:

    Maze::Maze(int row, int column):row(row),column(column)
    {
    	mazePtr.reset(new bool*[row]);
    	for (int i = 0; i < row; i++) {
    		mazePtr[i] = new bool[column]{false};
    	}
    }
    

    定义成员函数createMaze,这个函数用来执行迷宫生成算法,通过调用另一个私有成员dig来完成:

    bool Maze::createMaze()
    {
    	int x = startX;
    	int y = startY;
    	if (!dig(x, y)) {
    		return false;
    	}
    	return true;
    }
    

    然后就是实现dig函数,正如他的名字所显示的那样,该函数所做的就是从一个起点开始挖掘障碍物,只要条件允许(存在不邻接多个路径的新方块)他总是尽可能的去多挖掘新的方块:

    bool Maze::dig(int x, int y)
    {
    	if (!isInRange(x, y)) {
    		return false;
    	}
    	mazePtr[x - 1][y - 1] = true;
    	//随机改变方向
    	//random_shuffle(direction.begin(),direction.end());
    	for (int i = 0; i < 4; i++) {
    		int tmpX = x + 2 * direction[i].first;
    		int tmpY = y + 2 * direction[i].second;
    		if (!isInRange(tmpX, tmpY)) {
    			continue;
    		}
    		if (mazePtr[tmpX-1][tmpY-1] == false) {
    			mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
    			//print() << endl;
    			if (!dig(tmpX, tmpY)) {
    				mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
    				continue;
    			}
    		}
    	}
    	return true;
    }
    

    剩下的就是一些提供辅助功能的成员函数了,比如判断指定方块是否超出迷宫边界,计算入口是否合法,通过入口寻找遍历起点,打印迷宫状态等等,他们的实现如下:

    bool Maze::isInRange(int x, int y)
    {
    	if (x <= 0 or x >= row) {
    		return false;
    	}
    	if (y <= 0 or y >= column) {
    		return false;
    	}
    	return true;
    }
    
    bool Maze::isValidEntry(int x, int y)
    {
    	if ((x == 1 and y == 1) ||
    		(x == 1 and y == column) ||
    		(x == row and y == 1) ||
    		(x == row and y == column)) {
    		return false;
    	}
    	if (x == 1 or x == row or y == 1 or y == column) {
    		return true;
    	}
    	return false;
    }
    
    ostream& Maze::print()
    {
    	for (int i = 0; i < row; i++) {
    		for (int j = 0; j < column; j++) {
    			cout << mazePtr[i][j] << " ";
    		}
    		cout << endl;
    	}
    	return cout;
    }
    

    调用这段代码可以得到下图这种迷宫:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbdu3SJu-1582995964414)(D:\cLearning\c++primer习题\文章\img\maze1\1.png)]

    显然这样生成的迷宫不具备足够的随机性,接着在上面的递归回溯中添加一些小的改变,让每次遍历方向随机选择,这样就可以得到下面这样的代码:

    #include<iostream>
    #include<vector>
    #include<string>
    #include<cctype>
    #include<iterator>
    #include<stdexcept>
    #include<memory>
    #include<math.h>
    #include<algorithm>
    
    using namespace std;
    
    class Maze
    {
    public:
    	Maze(int row, int column);
    	ostream& print();
    
    	void setEntry(int x,int y)
    	{
    
    		if (isValidEntry(x,y)) {
    			mazePtr[x - 1][y - 1] = true;
    			if (x == 1) {
    				startX = 2;
    				startY = y;
    			}
    			if (x == row) {
    				startX = row-1;
    				startY = y;
    			}
    			if (y == 1) {
    				startX = x;
    				startY = 2;
    			}
    			if (y == column) {
    				startX = 2;
    				startY = column-1;
    			}
    			//cout << mazePtr[x - 1][y - 1];
    		}
    	}
    	bool createMaze();
    private:
    	bool isInRange(int x, int y);
    	bool isValidEntry(int x, int y);
    	bool dig(int x, int y);
    	int startX;
    	int startY;
    	int row;
    	int column;
    	vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
    	unique_ptr<bool*[]> mazePtr;
    };
    
    bool Maze::isInRange(int x, int y)
    {
    	if (x <= 0 or x >= row) {
    		return false;
    	}
    	if (y <= 0 or y >= column) {
    		return false;
    	}
    	return true;
    }
    
    bool Maze::dig(int x, int y)
    {
    	if (!isInRange(x, y)) {
    		return false;
    	}
    	mazePtr[x - 1][y - 1] = true;
        
    	//每次遍历四个方向之前,对方向进行一次随机洗牌
    	random_shuffle(direction.begin(),direction.end());
        
    	for (int i = 0; i < 4; i++) {
    		int tmpX = x + 2 * direction[i].first;
    		int tmpY = y + 2 * direction[i].second;
    		if (!isInRange(tmpX, tmpY)) {
    			continue;
    		}
    		if (mazePtr[tmpX-1][tmpY-1] == false) {
    			mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
    			//print() << endl;
    			if (!dig(tmpX, tmpY)) {
    				mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
    				continue;
    			}
    		}
    	}
    	return true;
    }
    bool Maze::createMaze()
    {
    	int x = startX;
    	int y = startY;
    	if (!dig(x, y)) {
    		return false;
    	}
    	return true;
    }
    
    bool Maze::isValidEntry(int x, int y)
    {
    	if ((x == 1 and y == 1) ||
    		(x == 1 and y == column) ||
    		(x == row and y == 1) ||
    		(x == row and y == column)) {
    		return false;
    	}
    	if (x == 1 or x == row or y == 1 or y == column) {
    		return true;
    	}
    	return false;
    }
    
    ostream& Maze::print()
    {
    	for (int i = 0; i < row; i++) {
    		for (int j = 0; j < column; j++) {
    			cout << mazePtr[i][j] << " ";
    		}
    		cout << endl;
    	}
    	return cout;
    }
    
    Maze::Maze(int row, int column):row(row),column(column)
    {
    	mazePtr.reset(new bool*[row]);
    	for (int i = 0; i < row; i++) {
    		mazePtr[i] = new bool[column]{false};
    	}
    }
    
    int main(int argc, char*argv[])
    {
    	Maze maze(15,15);
    	maze.setEntry(1, 2);
    	//cout << "设置入口后:" << endl;
    	//maze.print();
    	maze.createMaze();
    	maze.print();
    	system("pause");
    	return 0;
    }
    

    这次结果如下,随机性增强了很多:

    在这里插入图片描述

    这次就先做着写,接下来在慢慢补充改进。

    展开全文
  • 在上一篇文章中我们实现了基于深度优先搜索(DFS)的随机迷宫生成。方法类似,我们也可以利用DFS来进行随机迷宫寻路,也就是我们这一次文章的实现目标。 我们将尝试通过深度优先搜索来求解上一篇文章中生成的随机...

    通过深度优先搜索求解迷宫

    上一篇文章中我们实现了基于深度优先搜索(DFS)的随机迷宫生成。方法类似,我们也可以利用DFS来进行随机迷宫寻路,也就是我们这一次文章的实现目标。

    我们将尝试通过深度优先搜索来求解上一篇文章中生成的随机迷宫,稍有不同的是这次我们通过栈和迭代代替上次使用的函数递归调用,这两种方法理论上来分析是等价的。

    该方法很容易理解:

    1. 首先我们将迷宫的起始点入栈
    2. 将栈顶元素标记为当前坐标,然后按照已经定义好的方向数组依序从上、下、左、右四个方向(方向可以是任意的,但没必要每次都进行随机选取)来遍历当前坐标的相邻方块
    3. 如果这个取得的方块是合法的(也即是说这个方块不是边界,不是障碍物,并且没有标记为路线或者死胡同),则结束搜索。
    4. 如果这个取得的方块不合法,则进行下一个方向的搜索
    5. 当四个方向都搜索结束以后,判断在这个过程中是否找到了一个可行的相邻方块,如果找到了则将找到的方块入栈,然后回到步骤2
    6. 如果没有找到合适的相邻方块,说明当前坐标的搜索失败,此时应该将栈顶元素出栈并回到步骤2(回到上一个栈顶继续搜索),如果栈为空说明搜索回到起点,一切结束。

    下面看代码,我们给上次的迷宫类添加一个新方法mazeSolve用于迷宫求解,添加一个新的stack<pair<int,int>>变量用于保存求解路线:

    bool Maze::solveMaze()
    {
    	path.push(start);//起始点入栈
    	int currentX, currentY;
    	bool flag = false;
    	while (true) {
    		currentX = path.top().first;//记录栈顶元素为当前坐标
    		currentY = path.top().second;
    		for (int i = 0; i < 4; i++) {//从四个方向搜索
    			flag = false;
    			currentX += direction[i].first;
    			currentY += direction[i].second;
    			if (currentX == exit.first and currentY == exit.second) {//是否看到出口
    				return true;
    			}
    			if (isInRange(currentX, currentY) and (getData(currentX, currentY) > 0)) {//如果搜索到合法相邻元素
    				flag = true;
    				path.push({ currentX, currentY });//当前坐标入栈
    				setData(currentX, currentY, -2);
    				break;
    			}
    			currentX -= direction[i].first;
    			currentY -= direction[i].second;
    		}
    		if (!flag) {//若未找到合法相邻坐标
    			setData(currentX, currentY, -1);//标记为走错
    			path.pop();
    			if (path.empty()) {
    				return false;
    			}
    		}
    		
    	}
    
    }
    

    除此以外,为了能够打印正确路线,还需要调整一下之前的print方法。新的print方法接受一个布尔类型参数,如果参数为false表示打印求解前的迷宫,如果参数为真表示打印求解后的迷宫,全部代码如下:

    #include<iostream>
    #include<vector>
    #include<memory>
    #include<algorithm>
    #include<cstdlib>
    #include<ctime>
    #include<stack>
    #include<queue>
    
    using namespace std;
    
    pair<int, int> input()
    {
    	cout << "please input the size of the maze:" << endl;
    	int row, column;
    	cin >> row >> column;
    	row = row % 2 == 0 ? row + 1 : row;
    	column = column % 2 == 0 ? column + 1 : column;
    	return { row, column };
    }
    
    class Maze
    {
    private:
    	unique_ptr<int *[]> mazePtr;
    	pair<int, int> size;
    	pair<int, int> entry;
    	pair<int, int> exit;
    	pair<int, int> start;
    	vector<pair<int, int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
    	stack<pair<int, int>> path;
    public:
    	Maze(int x, int y);
    	ostream& print(bool mode = false);
    	bool createMaze();
    	bool solveMaze();
    private:
    	bool setEntry(int x, int y);
    	void setStartPoint();
    	bool dig(int x, int y);
    	bool isInRange(int x, int y);
    	void setData(int i, int j, int d) { mazePtr[i-1][j-1] = d; };
    	int getData(int i, int j) { return mazePtr[i-1][j-1]; };
    };
    
    bool Maze::solveMaze()
    {
    	path.push(start);
    	int currentX, currentY;
    	bool flag = false;
    	while (true) {
    		currentX = path.top().first;
    		currentY = path.top().second;
    		for (int i = 0; i < 4; i++) {
    			flag = false;
    			currentX += direction[i].first;
    			currentY += direction[i].second;
    			if (currentX == exit.first and currentY == exit.second) {
    				return true;
    			}
    			//非边界,不是墙,没走过,没标记死路
    			if (isInRange(currentX, currentY) and (getData(currentX, currentY) > 0)) {
    				//只要存在合法节点
    				flag = true;
    				//找到通路则进入下一个节点并退出当前循环
    				path.push({ currentX, currentY });
    				//-2标记为走过
    				setData(currentX, currentY, -2);
    				break;
    			}
    			//走错恢复
    			currentX -= direction[i].first;
    			currentY -= direction[i].second;
    		}
    		if (!flag) {
    			//寻毕全部相邻节点均不对
    			//-1标记为不可行
    			setData(currentX, currentY, -1);
    			path.pop();
    			if (path.empty()) {
    				return false;
    			}
    		}
    		
    	}
    
    }
    
    bool Maze::isInRange(int x, int y)
    {
    	if (x <= 1 or x >= size.first) {
    		return false;
    	}
    	if (y <= 1 or y >= size.second) {
    		return false;
    	}
    	return true;
    }
    
    bool Maze::dig(int x, int y)
    {
    	setData(x, y, '1');
    	srand(static_cast<unsigned int>(time(0)));
    	random_shuffle(direction.begin(), direction.end());
    	for (int i = 0; i < 4; i++) {
    		int tmpX = x + 2 * direction[i].first;
    		int tmpY = y + 2 * direction[i].second;
    		if (isInRange(tmpX, tmpY) and !getData(tmpX, tmpY)) {
    			setData(x+direction[i].first, y+direction[i].second, '1');
    			//print() << endl;
    			if (!dig(tmpX, tmpY)) {
    				continue;
    			}
    		}
    	}
    	//四个方向都没有未标记方块
    	if (x == start.first and y == start.second) {
    		return true;//栈空返回true,完成迷宫生成
    	}
    	else {
    		//print() << endl;
    		return false;//栈非空返回false,返回上一层
    	}
    }
    
    Maze::Maze(int row, int column) :size({ row,column })
    {
    	mazePtr.reset(new int*[row] {0});
    	for (int i = 0; i < row; i++) {
    		mazePtr[i] = new int[column] {0};
    	}
    	setEntry(2,1);
    }
    
    void Maze::setStartPoint()
    {
    	if (entry.first == 1) {
    		start.first = 2;
    		start.second = entry.second;
    	}
    	if (entry.first == size.first) {
    		start.first = entry.first - 1;
    		start.second = entry.second;
    	}
    	if (entry.second == 1) {
    		start.first = entry.first;
    		start.second = 2;
    	}
    	if (entry.second == size.second) {
    		start.first = entry.first;
    		start.second = entry.second - 1;
    	}
    }
    
    bool Maze::createMaze()
    {
    	setStartPoint();
    	dig(start.first, start.second);
    	exit = { size.first - 1, size.second };
    	setData(size.first-1, size.second, 1);
    	return true;
    }
    
    bool Maze::setEntry(int x, int y)
    {
    	//禁止拐角作为入口
    	if ((x == 1 and y == 1) ||
    		(x == 1 and y == size.second) ||
    		(x == size.first and y == 1) ||
    		(x == size.first and y == size.second)) {
    		return false;
    	}
    
    	if (x == 1 or x == size.first or y == 1 or y == size.second) {
    		entry = { x,y };
    		mazePtr[x - 1][y - 1] = 1;
    		return true;
    	}
    
    	return false;
    }
    
    ostream& Maze::print(bool mode)
    {
    	if (!mode) {
    		system("color e9");
    		for (int i = 0; i < size.first; i++) {
    			for (int j = 0; j < size.second; j++) {
    				cout << ((mazePtr[i][j] > 0) ? "☆" : "■");
    			}
    			cout << endl;
    		}
    	}
    	else {
    		system("color e9");
    		for (int i = 1; i <= size.first; i++) {
    			for (int j = 1; j <= size.second; j++) {
    				switch (getData(i, j)) {
    				case 0:
    					cout << "■";
    					break;
    				case -2:
    					cout << "□";
    					break;
    				case -1:
    					cout << "▲";
    					break;
    				default:
    					cout << "☆";
    					break;
    				}
    			}
    			cout << endl;
    		}
    	}
    	return cout;
    }
    
    int main(int argc, char*argv[])
    {
    	pair<int, int> size = input();
    	Maze maze(size.first, size.second);
    	maze.createMaze();
    	maze.print() << endl;
    	maze.solveMaze();
    	maze.print(true);
    	system("pause");
    	return 0;
    }
    

    经测试,执行结果如下图所示:

    求解前:

    在这里插入图片描述

    求解后:

    在这里插入图片描述

    展开全文
  • 生成和解决随机迷宫! 您看到的特定生成算法称为 。 它从网格中的随机单元格开始,将其标记为已发现,并将该单元格的所有相邻墙添加到队列中。 然后它从队列中随机选择一堵墙,如果尚未发现墙另一侧的单元格,则...
  • 在给定墙壁的长度,宽度和密度的情况下,该程序将一直运行,直到生成具有至少一种可能解决方案的迷宫为止。 生成有两种类型,快的(先生成路径,然后生成墙)和慢的(生成墙,然后生成路径) 包含A *路径查找算法...
  • 随机迷宫生成算法.7z

    2020-02-29 18:18:18
    使用时可修改prim.h 头文件中的row 和column宏定义,来修改生成图片大小的尺寸。使用时请预先安装EGE库。
  • 随机迷宫生成算法——深度优先算法

    万次阅读 多人点赞 2018-09-27 21:20:27
    迷宫是我们小时候经常玩的游戏,如何用代码来快速生成上面这种迷宫呢? 迷宫算法有三大算法:深度优先算法、prim算法和递归分割算法。这里用的是深度优先算法,在此说一下算法思路,希望对各位有所帮助。 首先我...

    迷宫是我们小时候经常玩的游戏,如何用代码来快速生成上面这种迷宫呢?

    迷宫算法有三大算法:深度优先算法、prim算法和递归分割算法。这里用的是深度优先算法,在此说一下算法思路,希望对各位有所帮助。

     

    首先我的假设是,迷宫只有一条正确的道路。

    这个时候请把自己想象成一只地鼠,要在这个区域不停的挖,直到任何一块区域再挖就会挖穿了为止。

    我们挖的道路就像树结构,树上有很多的分支,分支也有子分支,每个子分支都不能相交,相交了就说明墙被挖穿了,那么此时的迷宫就可能存在多条正确道路,这不是我想看到的。

    那么基于唯一道路的原则,我们向某个方向挖一块新的区域时,要先判断新区域是否有挖穿的可能,如果可能挖穿要立即停止并换个方向再挖。如图:

    如图所示,挖之前要确保上下左右四个位置中只有一个位置是路。了解了这一规则以后,就可以上代码了:

    #include<stdio.h>
    #include<Windows.h>
    #include<time.h>
    #include<math.h>
    
    //地图长度L,包括迷宫主体40,外侧的包围的墙体2,最外侧包围路径2(之后会解释)
    #define L 44
    
    //墙和路径的标识
    #define WALL  0
    #define ROUTE 1
    
    //控制迷宫的复杂度,数值越大复杂度越低,最小值为0
    static int Rank = 0;
    
    //生成迷宫
    void CreateMaze(int **maze, int x, int y);
    
    int main(void) {
    	srand((unsigned)time(NULL));
    
    	int **Maze = (int**)malloc(L * sizeof(int *));
    	for (int i = 0; i < L; i++) {
    		Maze[i] = (int*)calloc(L, sizeof(int));
    	}
    
    	//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
    	for (int i = 0; i < L; i++){
    		Maze[i][0] = ROUTE;
    		Maze[0][i] = ROUTE;
    		Maze[i][L - 1] = ROUTE;
    		Maze[L - 1][i] = ROUTE;
    	}
    
    	//创造迷宫,(2,2)为起点
    	CreateMaze(Maze, 2, 2);
    
    	//画迷宫的入口和出口
    	Maze[2][1] = ROUTE;
    
    	//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
    	for (int i = L - 3; i >= 0; i--) {
    		if (Maze[i][L - 3] == ROUTE) {
    			Maze[i][L - 2] = ROUTE;
    			break;
    		}
    	}
    
    	//画迷宫
    	for (int i = 0; i < L; i++) {
    		for (int j = 0; j < L; j++) {
    			if (Maze[i][j] == ROUTE) {
    				printf("  ");
    			}
    			else {
    				printf("国");
    			}
    		}
    		printf("\n");
    	}
    
    	for (int i = 0; i < L; i++) free(Maze[i]);
    	free(Maze);
    
    	system("pause");
    	return 0;
    }
    
    void CreateMaze(int **maze, int x, int y) {
    	maze[x][y] = ROUTE;
    
    	//确保四个方向随机
    	int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };
    	for (int i = 0; i < 4; i++) {
    		int r = rand() % 4;
    		int temp = direction[0][0];
    		direction[0][0] = direction[r][0];
    		direction[r][0] = temp;
    
    		temp = direction[0][1];
    		direction[0][1] = direction[r][1];
    		direction[r][1] = temp;
    	}
    
    	//向四个方向开挖
    	for (int i = 0; i < 4; i++) {
    		int dx = x;
    		int dy = y;
    
    		//控制挖的距离,由Rank来调整大小
    		int range = 1 + (Rank == 0 ? 0 : rand() % Rank);
    		while (range>0) {
    			dx += direction[i][0];
    			dy += direction[i][1];
    
    			//排除掉回头路
    			if (maze[dx][dy] == ROUTE) {
    				break;
    			}
    
    			//判断是否挖穿路径
    			int count = 0;
    			for (int j = dx - 1; j < dx + 2; j++) {
    				for (int k = dy - 1; k < dy + 2; k++) {
    					//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
    					if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
    						count++;
    					}
    				}
    			}
    
    			if (count > 1) {
    				break;
    			}
    
    			//确保不会挖穿时,前进
    			--range;
    			maze[dx][dy] = ROUTE;
    		}
    
    		//没有挖穿危险,以此为节点递归
    		if (range <= 0) {
    			CreateMaze(maze, dx, dy);
    		}
    	}
    }

    算法的资源消耗较大,因此我设置了Rank来降低复杂度,但因此也会使得迷宫变得非常简单,在平衡中做取舍吧。

    随机迷宫生成算法——prime算法

    随机迷宫生成算法——递归分割算法

    防止各位没看明白,重新写了篇,个人觉得讲的更详细点

    展开全文
  • 迷宫是许多游戏都会出现的经典场景,那么,如何用代码生成一个随机迷宫呢?
  • 随机迷宫生成用来我自己设计的一种算法,可以生成有一条通路的迷宫。并可自动寻路。 有两种模式 自动和手动。 自己游戏选手动,查看如何寻路的选自动 自动寻路开始快捷键SPACE 没有C++,与API编程基础的人不要看。 ...
  • 假期写了个基于随机迷宫的游戏,学习了相关随机迷宫生成算法。 其他算法都好理解,也很容易写出来。但就是随机Prim,怎么都写不对,生成的迷宫乱七八糟的,急死人了 没办法只好看别人的,但是又死活看不明白注释...
  • 随机迷宫生成算法整理分析

    千次阅读 2018-05-29 14:35:36
    搜集整理了一些游戏迷宫生成的算法与实现前言前段时间学校游戏开发课大作业,做了一个Roguelike的恐怖游戏。搜集整理了一些迷宫生成的算法。当初也受了indienova上一些文章的启发。现在在此把学到的一些东西理一理...
  • 【Prim迷宫算法】随机迷宫生成

    万次阅读 2018-05-21 17:24:18
    由于要用到迷宫,所以在网上学习了一下Prim迷宫算法,先用C++手撸了一个简单的模板。 Prim算法描述 随机选择一个白色格子[i,j] (实际位置为i*2+1,j*2+1)作为当前正在访问的格子,同时把该格子放入一个已经访问...
  • 随机迷宫生成算法——prime算法

    千次阅读 多人点赞 2018-09-28 23:13:36
    本以为Prime迷宫生成算法和图论的Prime算法有什么关联,貌似并没有。 Prime迷宫生成算法的原理: (1)初始地图所有位置均设为墙 (2)任意插入一个墙体进墙队列 (3)判断此时墙体是否可以设置为路(判断依据...
  • 随机迷宫生成算法——递归分割算法

    千次阅读 多人点赞 2018-09-29 18:41:30
    迷宫生成三大算法,Prime算法、深度优先算法、递归分割算法,其中递归分割算法最简单,效率也最高,不过生成的迷宫也最简单,看图: 原理很简单,首先假设迷宫全是路,在里面画四面墙,把迷宫分割成四个新区域,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 664
精华内容 265
关键字:

随机迷宫生成