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

注:由于这是本人第一次写博客,难免排版或用词上有所欠缺,请大家多多包涵。

注:如需转载文章,请注明出处,谢谢。

一、项目介绍:

1.概述

项目名:方块迷宫

作者:沫小亮。

编程框架与语言:Flutter&Dart

开发环境:Android Studio 3.6.2

学习参考:慕课网-看得见的算法

项目完整源码地址:(待更新)

游戏截图:

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> mazeMap; //迷宫地形(1代表路,0代表墙)

List> visited; //是否已经访问过

List> path; //是否是正确解的路径

List> 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>();

visited = new List>();

path = new List>();

//初始化迷宫起点与终点坐标

_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 temp = direction[random];

direction[random] = direction[i];

direction[i] = temp;

}

//初始化迷宫地图

for (int i = 0; i < rowSum; i++) {

List mazeMapList = new List();

List visitedList = new List();

List 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{

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 _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 {

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: [

//游戏地图区域

_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: [

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: [

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随机迷宫生成和解迷宫小游戏功能的源码的文章就介绍到这了,更多相关Flutter迷宫小游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

flutter生成源代码_Flutter随机迷宫生成和解迷宫小游戏功能的源码相关推荐

  1. 百度SEO站群在线随机看抖音快手小姐姐网站源码

    这个是Pc电脑在线看抖音和快手视频源码,源码是php的,上传即可使用.里面有接口地址,也可以自己写~ 下载地址: http://www.bytepan.com/nwDwpaoDSPl 源码截图:

  2. 最新版一键生成小程序系统 前段源码 小程序开发者必备

    [实例简介] 需要认证的微信公众号 申请微信支付接口 1.一键生成小程序自动化平台,自动购买和发货 2.支持二次开发 3.支持代理商 4.29个小程序完整 5.带配置教程 小程序生成平台源码 一键生成 ...

  3. 检讨书生成微信小程序工具源码-支持流量主

    检讨书生成微信小程序工具源码-支持流量主 源码介绍 源码演示 下载地址 源码介绍 对于经常写检讨的小伙伴来说,福音来了 因为这是一款检讨书生成小程序 所以再也不用为了写检讨而烦恼了哦 支持自定义字数下 ...

  4. 【小程序源码】检讨书生成微信小程序工具源码-安装搭建简单

    对于经常写检讨的小伙伴来说,福音来了 因为这是一款检讨书生成小程序 所以再也不用为了写检讨而烦恼了哦 支持自定义字数下线,主题自定义 支持多种类型检讨比如:学生党的,男朋友,领导演讲稿,共青团申请书等 ...

  5. 检讨书生成微信小程序工具源码-拥有流量主安装搭建简单

    对于经常写检讨的小伙伴来说,福音来了 因为这是一款检讨书生成小程序 所以再也不用为了写检讨而烦恼了哦 支持自定义字数下线,主题自定义 支持多种类型检讨比如:学生党的,男朋友,领导演讲稿,共青团申请书等 ...

  6. 检讨书生成微信小程序工具源码

    文章目录 前言 一.检讨书生成微信小程序工具源码 二.程序演示与下载 1.程序演示 2.程序下载 前言 检讨书作为一种日常应用,是犯了错误的人向当事人或组织写出的检讨错误的书信.在内容写作上,检讨书包 ...

  7. C语言实现随机快速排序random quick sort算法(附完整源码)

    随机快速排序random quick sort算法 随机快速排序random quick sort算法的完整源码(定义,实现,main函数测试) 随机快速排序random quick sort算法的完 ...

  8. 我搭建了一个随机「毒鸡汤」语录网站附源码下载

    小伙伴们注意:公众号的推送机制不再按照时间前后推送了,微信公众号信息流乱序.君哥建议大家把科技毒瘤君公众号置顶(设为星标⭐),以便第一时间看到推送,非常感谢~,方法如下图: 1 演示效果 ★ 遇到喜欢 ...

  9. flutter生成源代码_Flutter创建工程的主要代码详解

    使用Android Studio创建的默认Flutter工程主要代码在lib文件夹下的main.dart文件中,本文主要对该文件进行一个讲解. main.dart文件可以认为是Flutter工程的入口 ...

最新文章

  1. linux进程间通信:命名管道FIFO
  2. Matlab入门笔记
  3. 读入excel中的数据到数据库中
  4. Linux ekho
  5. ASP.NET文件上传和下载
  6. SQL 注入式攻击的本质
  7. 程序员必备 Git 分支开发规范指南
  8. 隔行变色java代码_jquery入门—选择器实现隔行变色实例代码
  9. [SHOI2009] 会场预约
  10. java Unsafe
  11. mysql主从搭建_手把手教你搭建MySQL主从架构
  12. 51单片机c语言正弦波,用51单片机产生正弦波,那个数据表怎么得到的?谢谢!...
  13. 在Global Mapper中导入点的文本格式
  14. 散列表的开放定址法以及再散列法(C语言)
  15. BetterZip 4.2.4 激活版下载安装– Mac上最快速的压缩工具
  16. python常用marker
  17. 锻炼完美腹肌的7条原则
  18. 织梦标签全攻略[转]
  19. JavaScript 中的数字在计算机内存中占多少个Byte?
  20. 知识问答领域方法概述

热门文章

  1. conv2d 公式_理解keras中conv2d层的输出形状
  2. 配置环境变量path误删之后该怎么办?
  3. 车联网Tbox电源模式管理
  4. SAP PLM模块常用表及表关系
  5. 精品基于NET实现的家电维修保养信息系统
  6. cucumber的hooks
  7. java se与jdk的关系_JDK与Java SE/EE/ME的区别
  8. 3d 视频切换到全屏播放
  9. 软件测试基础知识-测试用例设计方法
  10. (2)Android常见界面布局