800x800迷宫自动解路径
镇楼图
https://pan.baidu.com/s/1ql-fy9V_vmhQp2huFWhshw
迷宫游戏百度网盘在此
迷宫游戏
本人墨尔本大学大一学生一枚,前段利用暑假在家闲来无事写了一个迷宫游戏。起初只是想锻炼巩固一下BFS,以及链表的知识点,后来逐渐给自己的程序增加了一些自己想要的功能,同时也学习到了很多东西。
主要涉及到了三个部分:
1,迷宫的生成
2,BFS寻找路线,利用队列寻路
3,BMP图片的生成
4,在Console中动态显示BFS求得的路线,运用gotoxy();而不是system(“cls”),避免闪屏。
迷宫的生成
迷宫的生成主要利用了递归分割法,先初始化空白二维矩阵,N by N grid,
递归分割法通俗的说就是在这片空白的区域中砌墙,怎么个砌法呢,就是递归的用墙"切割"这片空白的空间,直到墙和墙之间达到最小的单位宽度,在此也就是一格。
如图所示便是递归分割法:
BFS寻找路线
BFS是一种寻路的很重要的算法,在该类迷宫中BFS可以找到最优的路径。
通俗的说,BFS在我看来就像是往迷宫的起点灌水(蔓延状的遍历整个图),一旦水到了出口,立刻停止灌水,(也就是增加队列),并且开始回溯路径。
当然,水是不能回溯路径的,而存在链表(队列)中的坐标是可以回溯的。我用的方法是创建了一个结构体,包含了当前x,y坐标以及pre_x,pre_y先前坐标,起点处的先前坐标设为-1,-1,也就是在回溯过程中,一旦先前坐标为-1,停止回溯,之后便得到了完整的BFS路径。
另一个问题,
如果有同样长度的路径到达起点,我们应该选择哪一条呢?
不用担心,在添加队列之前我们就应当设定好了一个方向顺序,比如先看当前坐标的"上",之后是"下",“左”,“右”。而刚刚我已经提到过,既然有顺序可言,并且往迷宫内"灌水"的行为是一旦到达目标终点并停止,那么我们只要回溯就能得到路径,根本就无需考虑相同长度的路径,因为即使它可能存在,但是你回溯路径的选择只有一条路。
最后的一些细枝末节
程序的主体大概就是如上所说,其他一些琐碎的东西便是将数据存入txt文档并再读取,用于解路线以及画图。
画图我一开始尝试用matlab,非常方便,但是发现打包之后的matlab的exe并不能在其他电脑上通用,而且体积庞大,于是花了一点时间学习了一下BMP图片的生成,并且动手改写了一下这里的代码,归为己有。
https://blog.csdn.net/sguniver_22/article/details/80379457
先给大家看一下我最后生成的bmp图片。这是最大的一只,800x800的,如果一个有一米长 那么这个迷宫便是一平方公里那么大了。
还有一些小一点的,对浏览者友好。没记错的话这应该就是120x120
图片的单位都是像素。
如何使用程序
为了方便运行,我没有在源代码中加入过多的system(“balabala”);语句,所以写了一个批处理文件 如下 也就是运行过程。
@echo off
color FA
title = AUTO MAZE SOLVER
mode con cols=120 lines=80
::设置颜色,大小和标题
::运行maze.exe会在当前文件夹中生成一个半成品maze.txt储存了block的坐标和迷宫的信息
maze.exe
::如果源文件夹已有迷宫图片自动删除(为了之后生成新的)
del MAZE.bmp
color F9
::BFS解迷宫程序输入信息,完善maze.txt为了下一步给创建BMP使用
maze_solver.exe<maze.txt
::创建BMP文件输入信息,输出MAZE.bmp
creatBMP.exe<maze.txt
pause
::程序完整运行完毕,显示迷宫彩色BMP图片
MAZE.bmp
del maze.txt
Alvin.vbs
在源代码BMP文件夹和MAZE文件夹中的批处理文件则是方便我修改代码时候编译用的,如果各位看客用的也是gcc编译那也可以使用。
链接
链接: https://www.youtube.com/watch?v=KiCBXu4P-2Y&t=878s
油管上BFS的视频
I TURN COFFEE INTO CODE!
下面附上源代码。
第一次写博客希望大家多多支持,代码繁琐之处欢迎大家在评论中指出! 抱拳感谢!
btw:程序的时间复杂度BIG-OH在迷宫尺寸大于四百之后不是很理想,三百尺寸是十秒左右,二百两三秒,一百以内毫秒。上面最大的800X800的图跑了十几分钟,希望大家能指出可以优化的地方!!!
哦对了,程序运行方法还没告诉你们呢,先输入尺寸,然后输入1是不改变默认终点,也就是左上到右下,如果输入0的话那就可以改变终点位置,再输入想要的终点,如 19,5 如果坐标invalid程序会自动提示重新输入。
大家可以讲console字体调到8号或者更小,因为程序设定如果迷宫大小大于130则不显示,所以如果字体过大,屏幕会乱的一塌糊涂,但是最后生成的图片是没问题的。
最后动态显示路线则是使用gotoxy函数实现。
/************************************************************************************/
生成BMP
//bmp.h
#ifndef _BMP_H
#define _BMP_H
//bmp 图片数据获取
//filePath : 路径
//picMaxSize : 返回图片数据字节数, 不接收置NULL
//width : 返回图片宽(像素), 不接收置NULL
//height : 返回图片高(像素), 不接收置NULL
//per : 返回图片每像素的字节数, 不接收置NULL
//返回 : 图片数据指针, 已分配内存, 用完记得释放
unsigned char *bmp_get(char *filePath, int *picMaxSize, int *width, int *height, int *per);
//生成 bmp 图片
//filePath : 路径
//data : 原始数据
//width : 宽(像素)
//height : 高(像素)
//per : 每像素字节数
//返回 : 成功创建的 .bmp 文件大小, 小于0则失败
int bmp_create(char *filePath, unsigned char *data, int width, int height, int per);
#endif
```c
//bmp.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
//
#define Bmp_FileHeader_Size 14 // sizeof(Bmp_FileHeader)的值不一定准确
typedef struct{
uint8_t bfType[2]; //文件类型: "BM"/bmp, "BA"/.. , ...
uint32_t bfSize; //整个文件的大小
uint16_t bfReserved1; //保留: 0
uint16_t bfReserved2; //保留: 0
uint32_t bfOffbits; //文件数据从第几个字节开始
}Bmp_FileHeader;
//
#define Bmp_Info_Size 40
typedef struct{
uint32_t biSize; //该段占用字节数
uint32_t biWidth; //图像宽度, 单位像素
int32_t biHeight; //图像高度, 单位像素(数据为正时为倒向)
uint16_t biPlanes; //平面数, 总是为1
uint16_t biBitCount; //单位像素占用比特数: 1, 4, 8, 16, 24, 42
uint32_t biCompression; //图像压缩方式: 0/BI_BGB 不压缩,
// 1/BI_RLE8 8比特游程压缩, 只用于8位位图
// 2/BI_RLE4 4比特游程压缩, 只用于4位位图
// 3/BI_BITFIELDS 比特域, 用于16/32位位图
// 4/BI_JPEG 位图含有jpeg图像, 用于打印机
// 5/BI_PWG 位图含有pwg图像, 用于打印机
uint32_t biSizeImage; //说明图像大小, 为BI_BGB时可能为0
int32_t biXPelsPerMeter;//水平分辨率, 像素/米, 有符号整数
int32_t biYPelsPerMeter;//垂直分辨率, 像素/米, 有符号整数
uint32_t biClrUsed; //位图实际使用彩色表中的颜色索引数(为0表示使用所有)
uint32_t biClrImportant;//图像显示有重要影响的颜色索引数
}Bmp_Info;
//功能: 读取bmp格式图片
//参数: filePath: 传入, 文件地址
// picMaxSize: 传出, 用以返回读取到的图片矩阵的总字节数
// width: 传出, 用以返回图片横向的像素个数
// height: 传出, 用以返回图片纵向的像素个数
// per: 传出, 用以返回图片每像素占用字节数
//返回: 图片矩阵数据的指针(注意指针是函数内分配的内存, 用完需释放)
unsigned char *bmp_get(char *filePath, int *picMaxSize, int *width, int *height, int *per)
{
FILE *fp;
Bmp_FileHeader bf;
Bmp_Info bi;
int perW, perWCount;
int ret;
int i, j, picCount, totalSize;
int overLineBytesNum;
unsigned int overLineBytesSum; // overLineBytesNum : 行结尾补0字节数 //每行字节数规定为4的倍数, 不足将补0, 所以读行的时候注意跳过
unsigned char buffHeader[512], *data, *pic;
//
if(filePath == NULL)
return NULL;
//
if((fp = fopen(filePath, "rb")) < 0)
{
printf("bmp_get : open file %s failed\r\n", filePath);
return NULL;
}
//Bmp_FileHeader
if(fread(buffHeader, 1, Bmp_FileHeader_Size, fp) <= 0)
{
printf("bmp_get : read Bmp_FileHeader failed\r\n");
fclose(fp);
return NULL;
}
bf.bfType[0] = buffHeader[0]; bf.bfType[1] = buffHeader[1];
bf.bfSize = buffHeader[2] + ((buffHeader[3]&0xFF)<<8) + ((buffHeader[4]&0xFF)<<16) + ((buffHeader[5]&0xFF)<<24);
bf.bfOffbits = buffHeader[10] + ((buffHeader[11]&0xFF)<<8) + ((buffHeader[12]&0xFF)<<16) + ((buffHeader[13]&0xFF)<<24);
//printf("bmp_get : bfType/%s, bfSize/%d, bfOffbits/%d\r\n", bf.bfType, bf.bfSize, bf.bfOffbits);
if(bf.bfType[0] != 'B' || bf.bfType[1] != 'M')
{
printf("bmp_get : bmp type err, bfType must be \"BM\"\r\n");
fclose(fp);
return NULL;
}
//Bmp_Info
if(bf.bfOffbits - Bmp_FileHeader_Size < Bmp_Info_Size || fread(buffHeader, 1, Bmp_Info_Size, fp) <= 0)
{
printf("bmp_get : read Bmp_Info failed\r\n");
fclose(fp);
return NULL;
}
bi.biSize = buffHeader[0] + ((buffHeader[1]&0xFF)<<8) + ((buffHeader[2]&0xFF)<<16) + ((buffHeader[3]&0xFF)<<24);
bi.biWidth = buffHeader[4] + ((buffHeader[5]&0xFF)<<8) + ((buffHeader[6]&0xFF)<<16) + ((buffHeader[7]&0xFF)<<24);
bi.biHeight = buffHeader[8] | ((buffHeader[9]&0xFF)<<8) | ((buffHeader[10]&0xFF)<<16) | ((buffHeader[11]&0xFF)<<24);
bi.biPlanes = buffHeader[12] + ((buffHeader[13]&0xFF)<<8);
bi.biBitCount = buffHeader[14] + ((buffHeader[15]&0xFF)<<8);
bi.biCompression = buffHeader[16] + ((buffHeader[17]&0xFF)<<8) + ((buffHeader[18]&0xFF)<<16) + ((buffHeader[19]&0xFF)<<24);
bi.biSizeImage = buffHeader[20] + ((buffHeader[21]&0xFF)<<8) + ((buffHeader[22]&0xFF)<<16) + ((buffHeader[23]&0xFF)<<24);
bi.biXPelsPerMeter = buffHeader[24] | ((buffHeader[25]&0xFF)<<8) | ((buffHeader[26]&0xFF)<<16) | ((buffHeader[27]&0xFF)<<24);
bi.biYPelsPerMeter = buffHeader[28] | ((buffHeader[29]&0xFF)<<8) | ((buffHeader[30]&0xFF)<<16) | ((buffHeader[31]&0xFF)<<24);
bi.biClrUsed = buffHeader[32] + ((buffHeader[33]&0xFF)<<8) + ((buffHeader[34]&0xFF)<<16) + ((buffHeader[35]&0xFF)<<24);
bi.biClrImportant = buffHeader[36] + ((buffHeader[37]&0xFF)<<8) + ((buffHeader[38]&0xFF)<<16) + ((buffHeader[39]&0xFF)<<24);
//perW 每像素字节数
if(bi.biBitCount >= 8)
perW = bi.biBitCount/8;
else
perW = 1;
//计算总字节数
//totalSize = bf.bfSize - bf.bfOffbits;
//计算总字节数
overLineBytesNum = 4- bi.biWidth*(bi.biBitCount/8)%4;
if(overLineBytesNum == 4)
overLineBytesNum = 0;
if(bi.biHeight < 0)
{
totalSize = bi.biWidth*(-bi.biHeight)*(bi.biBitCount/8);
overLineBytesSum = overLineBytesNum*(-bi.biHeight);
}else
{
totalSize = bi.biWidth*bi.biHeight*(bi.biBitCount/8);
overLineBytesSum = overLineBytesNum*bi.biHeight;
}
//printf("bmp_get : biSize/%d, biWidth/%d, biHeight/%d, biPlanes/%d, biBitCount/%d, biCompression/%d, biSizeImage/%d, biXPelsPerMeter/%d, biYPelsPerMeter/%d, biClrUsed/%d, biClrImportant/%d, overLineBytesNum/%d, overLineBytesSum/%d, totalSize/%d\r\n", bi.biSize, bi.biWidth, bi.biHeight, bi.biPlanes, bi.biBitCount, bi.biCompression, bi.biSizeImage, bi.biXPelsPerMeter, bi.biYPelsPerMeter, bi.biClrUsed, bi.biClrImportant, overLineBytesNum, overLineBytesSum, totalSize);
//指针移动到数据起始
if(fseek(fp, bf.bfOffbits, 0) < 0)
{
printf("bmp_get : lseek failed\r\n");
fclose(fp);
return NULL;
}
//分配内存一次读入整张图片
data = (unsigned char *)calloc(1, totalSize + overLineBytesSum + perW); //多1像素的字节, 防止操作不当溢出
if((ret = fread(data, 1, totalSize + overLineBytesSum, fp)) != (totalSize + overLineBytesSum))
{
if(ret <= 0)
{
printf("bmp_get : read data failed\r\n");
free(data);
fclose(fp);
return NULL;
}
}
//close
fclose(fp);
//
pic = (unsigned char *)calloc(1, totalSize);
memset(pic, 0, totalSize);
//根据图片方向拷贝数据
if(bi.biHeight > 0) //倒向 //上下翻转 + 左右翻转 + 像素字节顺序调整
{
for(i = 0, picCount = totalSize; i < totalSize + overLineBytesSum && picCount >= 0; )
{
picCount -= bi.biWidth*perW;
for(j = 0, perWCount = perW - 1; j < bi.biWidth*perW && i < totalSize + overLineBytesSum && picCount >= 0; j++)
{
pic[picCount + perWCount] = data[i++];
if(--perWCount < 0)
perWCount = perW - 1;
if(perWCount == perW - 1)
picCount += perW;
}
picCount -= bi.biWidth*perW;
i += overLineBytesNum;
}
}
else // 正向 //像素字节顺序调整
{
for(i = 0, j = 0, picCount = 0, perWCount = perW - 1; i < totalSize + overLineBytesSum && picCount < totalSize; )
{
pic[picCount + perWCount] = data[i++];
if(--perWCount < 0)
perWCount = perW - 1;
if(perWCount == perW - 1)
picCount += perW;
if(++j == bi.biWidth*perW)
{
j = 0;
i += overLineBytesNum;
}
}
}
//free
free(data);
//返回 宽, 高, 像素字节
if(picMaxSize)
*picMaxSize = totalSize;
if(width)
*width = bi.biWidth;
if(height)
{
if(bi.biHeight > 0)
*height = bi.biHeight;
else
*height = -bi.biHeight;
}
if(per)
*per = perW;
return pic;
}
//功能: 创建bmp格式图片
//参数: filePath: 传入, 文件地址
// data: 传入, 图片矩阵数据的指针
// width: 传入, 图片横向的像素个数
// height: 传入, 图片纵向的像素个数
// per: 传入, 图片每像素占用字节数
//返回: 创建的bmp图片文件的大小, -1表示创建失败
int bmp_create(char *filePath, unsigned char *data, int width, int height, int per)
{
FILE *fp;
int fileSize, fileSize2, count, headSize;
unsigned char *bmpData, *p;
int perWCount;
int i, j, picCount;
int overLineBytesNum;
unsigned int overLineBytesSum = 0;// overLineBytesNum : 行结尾补0字节数 //每行字节数规定为4的倍数, 不足将补0, 所以读行的时候注意跳过
//
if(width < 0)
{
printf("bmp_create : width < 0 , err !!\r\n");
return -1;
}
//
if((fp = fopen(filePath, "wb+")) < 0)
{
printf("bmp_create : create %s err\r\n", filePath);
return -1;
}
//
overLineBytesNum = width*per%4;
if(overLineBytesNum == 4)
overLineBytesNum = 0;
headSize = Bmp_FileHeader_Size + Bmp_Info_Size;
if(height < 0)
{
overLineBytesNum = overLineBytesNum*(-height);
fileSize2 = width*(-height)*per;
}
else
{
overLineBytesNum = overLineBytesNum*height;
fileSize2 = width*height*per;
}
fileSize = headSize + fileSize2;
bmpData = (unsigned char *)calloc(1, fileSize + overLineBytesNum);
//
count = 0;
//
bmpData[count++] = 'B'; //bfType
bmpData[count++] = 'M';
bmpData[count++] = (unsigned char)((fileSize>>0)&0xFF); //bfSize 低位在前
bmpData[count++] = (unsigned char)((fileSize>>8)&0xFF);
bmpData[count++] = (unsigned char)((fileSize>>16)&0xFF);
bmpData[count++] = (unsigned char)((fileSize>>24)&0xFF);
count++; //保留
count++;
count++;
count++;
bmpData[count++] = (unsigned char)((headSize>>0)&0xFF); //bfOffbits 低位在前
bmpData[count++] = (unsigned char)((headSize>>8)&0xFF);
bmpData[count++] = (unsigned char)((headSize>>16)&0xFF);
bmpData[count++] = (unsigned char)((headSize>>24)&0xFF);
bmpData[count++] = (unsigned char)((Bmp_Info_Size>>0)&0xFF); //biSize
bmpData[count++] = (unsigned char)((Bmp_Info_Size>>8)&0xFF);
bmpData[count++] = (unsigned char)((Bmp_Info_Size>>16)&0xFF);
bmpData[count++] = (unsigned char)((Bmp_Info_Size>>24)&0xFF);
bmpData[count++] = (unsigned char)((width>>0)&0xFF); //biWidth
bmpData[count++] = (unsigned char)((width>>8)&0xFF);
bmpData[count++] = (unsigned char)((width>>16)&0xFF);
bmpData[count++] = (unsigned char)((width>>24)&0xFF);
bmpData[count++] = (unsigned char)((height>>0)&0xFF); //biHeight
bmpData[count++] = (unsigned char)((height>>8)&0xFF);
bmpData[count++] = (unsigned char)((height>>16)&0xFF);
bmpData[count++] = (unsigned char)((height>>24)&0xFF);
bmpData[count++] = 0x01; //biPlanes
bmpData[count++] = 0x00;
bmpData[count++] = 24; //biBitCount
bmpData[count++] = 0;
bmpData[count++] = 0; //biCompression
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = (unsigned char)((fileSize2>>0)&0xFF); //biSizeImage
bmpData[count++] = (unsigned char)((fileSize2>>8)&0xFF);
bmpData[count++] = (unsigned char)((fileSize2>>16)&0xFF);
bmpData[count++] = (unsigned char)((fileSize2>>24)&0xFF);
bmpData[count++] = 0; //biXPelsPerMeter
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0; //biYPelsPerMeter
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0; //biClrUsed
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0; //biClrImportant
bmpData[count++] = 0;
bmpData[count++] = 0;
bmpData[count++] = 0;
//
p = &bmpData[count];
if(height >= 0) //倒向 //上下翻转 + 左右翻转 + 像素字节顺序调整
{
for(i = 0, picCount = fileSize2; i < fileSize2 + overLineBytesSum && picCount >= 0; )
{
picCount -= width*per;
for(j = 0, perWCount = per - 1; j < width*per && i < fileSize2 + overLineBytesSum && picCount >= 0; j++)
{
p[i++] = data[picCount + perWCount];
if(--perWCount < 0)
perWCount = per - 1;
if(perWCount == per - 1)
picCount += per;
}
picCount -= width*per;
i += overLineBytesNum;
}
}
else // 正向 //像素字节顺序调整
{
for(i = 0, j = 0, picCount = 0, perWCount = per - 1; i < fileSize2 + overLineBytesSum && picCount < fileSize2; )
{
p[i++] = data[picCount + perWCount];
if(--perWCount < 0)
perWCount = per - 1;
if(perWCount == per - 1)
picCount += per;
if(++j == width*per)
{
j = 0;
i += overLineBytesNum;
}
}
}
//
fileSize = fwrite(bmpData, 1, fileSize, fp);
free(bmpData);
fclose(fp);
//sync();
return fileSize;
}
//main.c
#include <stdio.h>
#include <stdlib.h> //malloc free
#include "bmp.h"
//一张 宽100*高50*像素3字节 的RGB图片
int BMP_WIDTH = 0;
int BMP_HEIGHT = 0;
#define BMP_PERW 3
//读取图片用
int width, height, perW, picSize;
int main(int argc, char *argv[])
{
int x,y,ch=0;
scanf("%d\n[%*d,%*d]\n[%*d,%*d]\n",&BMP_WIDTH);//去头
BMP_HEIGHT = BMP_WIDTH;
unsigned char buff[BMP_HEIGHT][BMP_WIDTH][BMP_PERW];
//---------- 创建一张图片 ----------
ch=getchar();
while(ch!='$'){
scanf("%d,%d]\n",&y,&x);
//block
buff[y][x][0] = 0xb0;
buff[y][x][1] = 0xb0;
buff[y][x][2] = 0xf0;
ch=getchar();
}
getchar();
//path
ch=getchar();
while(ch!='$'){
scanf("%d,%d]\n",&y,&x);
buff[y][x][0] = 0;
buff[y][x][1] = 0;
buff[y][x][2] = 0;
ch=getchar();
}
getchar();
ch=getchar();
//space
while(ch!=EOF){
scanf("%d,%d]\n",&y,&x);
buff[y][x][0] = 0xff;
buff[y][x][1] = 0xff;
buff[y][x][2