此博客旨在帮助大家更好的了解图的遍历算法,通过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 {@overrideWidget 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;          //迷宫游戏数据层//初始化状态@overridevoid initState() {super.initState();_model = new MazeGameModel(rowSum, columnSum);//新建一个事件循环队列,确保不堵塞主线程new Future(() {//生成一个迷宫_doGenerator(_model.getStartX(), _model.getStartY() + 1);});//设置倒计时_setSurplusTime(level);}

3.界面整体结构

 @overrideWidget 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,否则返回falsebool _doSolver(int x, int y) {if (!_model.isInArea(x, y)) {throw "坐标越界";}//设置已访问_model.visited[x][y] = true;//设置该位置为正确路径_setModelWithPath(x, y, true);//如果该位置为终点位置,则返回trueif (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();},)],);});}}

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

Flutter-随机迷宫生成和解迷宫小游戏相关推荐

  1. flutter生成源代码_Flutter随机迷宫生成和解迷宫小游戏功能的源码

    此博客旨在帮助大家更好的了解图的遍历算法,通过Flutter移动端平台将图的遍历算法运用在迷宫生成和解迷宫上,让算法变成可视化且可以进行交互,最终做成一个可进行随机迷宫生成和解迷宫的APP小游戏.本人 ...

  2. js迷宫生成与迷宫求解算法

    迷宫问题是一个很经典的问题,本文记叙迷宫的生成和求解(深度优先),完整dome见文章末尾(包括动画演示) 所涉及迷宫为: 方形规则迷宫 只有一个出口和一个入口 路径连续 只有一个解 先看效果: a.迷 ...

  3. Python——迷宫生成和迷宫破解算法

    迷宫绘制函数 def draw(num_rows, num_cols, m):image = np.zeros((num_rows * 10, num_cols * 10), dtype=np.uin ...

  4. java动画迷宫寻路_[人工智能] 迷宫生成、寻路及可视化动画

    前言 数据结构准备 迷宫生成算法 迷宫寻路算法 前言 本次带来迷宫相关的算法,迷宫的算法涉及到不少经典的图论算法,在游戏中NPC这些算法被大量的运用,深入了解和学习这些算法是为开发游戏打下坚实的基础. ...

  5. 迷宫生成算法和迷宫寻路算法

    迷宫生成算法和迷宫寻路算法 大学二年级的时候,作为对栈这个数据结构的复习,我制作了一个迷宫生成算法的小程序,当时反响十分好,过了几天我又用自己已经学的DirectX技术制作了DirectX版的程序.这 ...

  6. 不用 H5,闲鱼 Flutter 如何玩转小游戏?

    简介: 最近APP游戏化成为了一个新的风口,把在游戏中一些好玩的.能吸引用户的娱乐方式或场景应用在应用当中,以达到增加用户粘性,提升DAU的效果,成本较低.同时在一些需要对用户有引导性的场景,游戏化还 ...

  7. flutter能开发游戏吗_不用 H5,闲鱼 Flutter 如何玩转小游戏?-阿里云开发者社区...

    什么是Candy引擎? Candy 是闲鱼技术团队设计开发的一款引擎: APP嵌入式的.轻量级的.易于开发.性能稳定的互动引擎: 绘制系统高度融合Flutter体系,游戏场景和Flutter UI支持 ...

  8. 7句话让Codex给我做了个小游戏,还是极简版塞尔达,一玩简直停不下来

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 梦晨 萧箫 发自 凹非寺 量子位 | 公众号 QbitAI 什么,7 ...

  9. 使用记录6_发布微信小游戏

    转载自 https://blog.csdn.net/haibo19981/article/details/80435594 1.cocos creator发布微信小游戏 官方说明文档地址如下 http ...

最新文章

  1. Apache+php+tomcat+mysqlon linxu
  2. 分分合合分分,谷歌医疗走向大败退
  3. 【深度学习】简单理解Batch Normalization批标准化
  4. 为什么要使用数据连接池
  5. systemstate dump 介绍
  6. 明天支付宝就开始提现收费了!这几招可以让你受用
  7. ASO优化教程:产品预热与应用提交aso主要优化,ASO优化
  8. 向量叉积和点积混合运算_向量点积与叉积的意义
  9. 经济学硕士计算机博士,去美国那些大学攻读经济学博士比较好?看完你就清楚了...
  10. 全球程序员收入出炉!北京收入排入全球第十
  11. 电气专业c语言要学得非常好吗,电气自动化专业需要学C语言吗?
  12. 游戏中的人工智能AI设计
  13. 【题解】P1979 [NOIP2013 提高组] 华容道(SPFA,BFS,常数优化)
  14. Dremel-大数据上的交互式分析
  15. matlab兰州交通大学,兰州交通大学教务处.pdf
  16. tomcat 历史版本下载
  17. asp毕业设计—— 基于asp+access的网上花店设计与实现(毕业论文+程序源码)——网上花店
  18. 物理学在计算机领域的应用,物理学在计算机中的应用.pdf
  19. java web电脑网站微信扫码支付(Servlet+JSP)
  20. spring与flex blazed整合(一般方式或注解)

热门文章

  1. 怎么测试linux丢包率,linux上测试丢包率的工具iperf介绍
  2. 网络演进中的LTE短信解决方案研究
  3. Java-----网络编程
  4. mysql的查询qps_mysql查询qps
  5. form is not define的原因-JavaScript
  6. Windows怎么开机自动登录?怎么取消自动登录
  7. php正则 网址,PHP正则表达式如何匹配任意类型网址
  8. 微发微积之jQuery放上去切换
  9. 计算器算贝塞尔公式_浅谈计算器的普及与中学数学教科书的关系
  10. 全省排名10000计算机专业,高考理科600分,全省一万名左右,可以选择这4所211高校...