一、题目要求
给定一个n*m的迷宫,0表示迷宫中的通路,1表示迷宫中的墙壁,给定起点和终点,求解从起点到终点的所有通路,或者没有找到通路的结论。
二、需求分析
1、使用文件读入迷宫地图和迷宫的起点与终点。
2、可视化显示迷宫。
3、使用链表作为数据结构的队列类型求解迷宫问题。
4、判断迷宫是否有通路,有则以三元组( i,j,d)的形式输出。其中 i, j ,指示迷宫中的一个坐标,d表示走到下一坐标的方向,没有则输出迷宫没有通路。
5、如果有通路则以方阵形式输出迷宫和通路。
6、显示迷宫最短路径。
三、设计过程
3.1概要设计
首先使用文件读入方式读入迷宫数据,把迷宫数据传给绘图函数进行可视化显示迷宫地图,使用队列求出通路并记录下来,最后把数据传给绘图函数进行可视化展示。
3.2详细设计
3.2.1读入迷宫
使用fopen函数打开迷宫文件,然后判断是否打开成功,成功则使用fscanf函数读取文件的迷宫数据和起点与终点。
3.2.2显示迷宫
定义枚举类型,0代表空格(Space),1代表墙壁(Wall),2代表走过的路径(Gamer),3代表起点(start),4代表终点(endd),对地图进行遍历,使用easyx的fillrectangle函数对墙壁进行可视化绘图。
3.2.3求解所有可能通路
为了保存路径,定义一个结构体记录信息,用col,colume记录当前所在的位置,用pre记录当前位置由哪一个位置得来,用dir记录由哪一个方向得来,用dirr记录当前位置往哪一个方向走,以便得到路径三元组(i,j,d)。
Step1:定义一个队列,将起始点放入队列中。
Step2:取出队头元素,并将其从队列中删除,判断其是否为终点,如果是,则保存路径,根据pre逆向获取路径,并保存至stk数组中,以便使用。
Step3:对队头进行东、南、西、北四个方向进行扩展,再对待扩展的结点进行判断,如果待扩展的结点不为墙,并且在当前路径中没有出现过,即为满足条件的点,可加入队列,并记录结点属性,如此即可通过队列求得所有满足条件的路径。
3.2.4输出迷宫及其通路
把迷宫地图中需要走的路径标记为枚举类型中的Gamer,然后再使用fillrectangle函数进行迷宫及其通路的绘图即可。
3.2.5显示最短路径
求得的最短路径即为stk数组中的第一条路径,使用draw()函数可视化输出即可。
四、实现效果
迷宫路径动态展示
五、编程代码
#include<stdio.h>
#include<graphics.h>
#include<assert.h>
#include<string.h>
#include<stdbool.h>
#include <conio.h>
#include<iostream>
#include<string>
#define GRID_SIZE 50
#define MaxSize 100000
#define M 100
#define N 100
int cnt = 0; //记录路径的数量
using namespace std;
int map[M][N];
int mapp[M][N];
int n, m;
int sx, sy, ex, ey;
enum SpriteType {
Space,
Wall,
Gamer,
start,
endd
};
IMAGE img_gamer;
struct Button
{
int x;
int y;
int w;
int h;
COLORREF curColor;
COLORREF inColor;
COLORREF outColor;
char* text;
};
struct Button* createButton(int x, int y, int w, int h, const char* str, COLORREF inColor, COLORREF outColor) {
struct Button* pB = (struct Button*)malloc(sizeof(struct Button));
assert(pB);
pB->x = x;
pB->y = y;
pB->w = w;
pB->h = h;
pB->inColor = inColor;
pB->outColor = outColor;
pB->curColor = pB->outColor;
int textLength = strlen(str) + 1;
pB->text = (char*)malloc(sizeof(char) * textLength);
assert(pB->text);
strcpy_s(pB->text, textLength, str);
return pB;
}
//画按钮
void drawbutton(struct Button* pB) {
setfillcolor(pB->curColor);
fillrectangle(pB->x, pB->y, pB->x + pB->w, pB->y + pB->h);
settextcolor(BLACK);
setbkmode(TRANSPARENT);
settextstyle(15, 0, "楷体");
int textw = textwidth(pB->text);
int texth = textheight(pB->text);
int xx = pB->x + (pB->w - textw) / 2;
int yy = pB->y + (pB->h - texth) / 2;
outtextxy(xx, yy, pB->text);
}
//判断是否为按钮
bool isInButton(struct Button* pB, ExMessage m) {
if (m.x > pB->x && m.x<pB->x + pB->w && m.y>pB->y && m.y < pB->y + pB->h) {
pB->curColor = pB->inColor;
return true;
}
pB->curColor = pB->outColor;
return false;
}
//判断是否点击按钮
bool isClickButton(struct Button* pB, ExMessage m) {
if (isInButton(pB, m) && m.message == WM_LBUTTONDOWN) {
return true;
}
return false;
}
void readmap(const char* filename, int map[M][N]) {
FILE* fp = fopen(filename, "r");
if (!fp) {
perror("file open failed~");
return;
}
//读取数据
fscanf(fp, "%d ", &n); //n表示迷宫的行数
fscanf(fp, "%d ", &m); //m表示迷宫的列数
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
fscanf(fp, "%d ", &map[i][j]);
}
}
fscanf(fp, "%d ", &sx); //sx表示起点x坐标
fscanf(fp, "%d ", &sy); //sy表示起点y坐标
fscanf(fp, "%d ", &ex); //ex表示终点x坐标
fscanf(fp, "%d ", &ey); //ey表示终点y坐标
fclose(fp);
}
void init()
{
loadimage(&img_gamer, "C:/Users/XHX/Pictures/Camera Roll/th.jpg", GRID_SIZE, GRID_SIZE);//加载图片
}
void draw(int mapp[M][N]){
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int x = i * GRID_SIZE, y = j * GRID_SIZE+110;
switch (mapp[i][j]) {
case Space:
break;
case Wall:
setfillcolor(RGB(93, 107, 153));
fillrectangle(y, x, y + GRID_SIZE, x + GRID_SIZE);
break;
case Gamer:
putimage(y, x, &img_gamer); //绘制图片
break;
case start:
setfillcolor(RGB(0, 0, 170));
fillrectangle(y, x, y + GRID_SIZE, x + GRID_SIZE);
break;
case endd:
setfillcolor(RED);
fillrectangle(y, x, y + GRID_SIZE, x + GRID_SIZE);
break;
default:
break;
}
}
}
}
typedef struct
{
int col, colume; //方块的位置
int pre; //本路径中上一方块在队列中的下标
int dir, dirr; //dir记录从哪一个方向转移来,dirr记录往哪一个方向转移
} Box; //方块类型
typedef struct
{
Box data[MaxSize];
int front, rear; //队头指针和队尾指针
} QuType; //顺序队类型
struct boxx {
Box data[MaxSize];
int sz;
}stk[100];
void InitQueue(QuType*& q) //初始化队列
{
q = (QuType*)malloc(sizeof(QuType));
q->front = q->rear = -1;
}
void DestroyQueue(QuType*& q) //销毁队列
{
free(q);
}
bool QueueEmpty(QuType* q) //判断队列是否为空
{
return(q->front == q->rear);
}
bool enQueue(QuType*& q, Box e) //进队列
{
if (q->rear == MaxSize - 1) //队满上溢出
return false; //返回假
q->rear++; //队尾增1
q->data[q->rear] = e; //rear位置插入元素e
return true; //返回真
}
bool deQueue(QuType*& q, Box& e) //出队列
{
if (q->front == q->rear) //队空下溢出
return false;
q->front++;
e = q->data[q->front];
return true;
}
void request(QuType* qu, int front) //从队列qu中输出路径
{
cnt++;
int k = front, j, ns = 0;
do
{
j = k;
k = qu->data[k].pre;
qu->data[k].dirr = qu->data[j].dir;
stk[cnt].data[++ns] = qu->data[j];
} while (k != 0);
stk[cnt].data[++ns] = qu->data[0];
stk[cnt].sz = ns;
}
void print()
{
settextstyle(15, 0, "楷体");
string s;
char demo[1000000];
if (cnt == 0) {
settextstyle(50, 0, "楷体");
s = "迷宫没有通路";
strcpy_s(demo, s.c_str());
outtextxy(110, 0, demo);
}
else {
int num = 0;
for (int i = 1; i <= cnt; i++) {
string s = "第" + to_string(i) + "条路径为:";
strcpy_s(demo, s.c_str());
outtextxy(110, 20 * num, demo);
num++;
s = "";
for (int j = stk[i].sz; j; j--) {
if (j != 1)
s = s + '(' + to_string(stk[i].data[j].col) + ',' + to_string(stk[i].data[j].colume) + ',' + to_string(stk[i].data[j].dirr) + ')' + ' ';
else s = s + '(' + to_string(stk[i].data[j].col) + ',' + to_string(stk[i].data[j].colume) + ')';
if ((stk[i].sz - j+1) % 5 == 0) {
strcpy_s(demo, s.c_str());
outtextxy(110, 20 * num , demo);
s = "";
num++;
}
}
strcpy_s(demo, s.c_str());
outtextxy(110, 20 * num, demo);
num++;
s = "";
}
}
}
void mappath1(int startx, int starty, int endx, int endy)
{
Box stepHead, stepNext;
int direct, dir1;
bool CheckStepNext;
QuType* qu; //定义顺序队指针qu
InitQueue(qu); //初始化队列qu
stepHead.col = startx; stepHead.colume = starty; stepHead.pre = -1;
enQueue(qu, stepHead); //将起点加入队列中
while (!QueueEmpty(qu)) //队不空且循环
{
deQueue(qu, stepHead); //出队方块e,由于不是环形队列,该出队元素仍在队列中
stepHead = stepHead;
if (stepHead.col == endx && stepHead.colume == endy) //找到了出口,输出路径
{
request(qu, qu->front);
continue; //调用print函数输出路径
}
for (direct = 0; direct < 4; direct++) //循环扫描每个方位,把每个可走的方块插入队列中
{
CheckStepNext = true; //判断当前待扩展结点可不可行,开始默认可行
switch (direct)
{
case 0:stepNext.col = stepHead.col - 1; stepNext.colume = stepHead.colume; break;
case 1:stepNext.col = stepHead.col; stepNext.colume = stepHead.colume + 1; break;
case 2:stepNext.col = stepHead.col + 1; stepNext.colume = stepHead.colume; break;
case 3:stepNext.col = stepHead.col; stepNext.colume = stepHead.colume - 1; break;
}
if (map[stepNext.col][stepNext.colume] == 0)
{
for (int i = qu->front; i >= 0; i = qu->data[i].pre) {
if (qu->data[i].col == stepNext.col && qu->data[i].colume == stepNext.colume) {
CheckStepNext = false; //与当前路径冲突,即当前待扩展结点不可行
break;
}
}
if (CheckStepNext) {
stepNext.dir = direct;
stepNext.pre = qu->front;
enQueue(qu, stepNext);
}
}
}
}
}
int main() {
readmap("C:/Users/XHX/Desktop/迷宫.txt", map);
mappath1(sx, sy, ex, ey);
initgraph(16 * GRID_SIZE, 12*GRID_SIZE);
setbkcolor(WHITE);
cleardevice();
init();
struct Button* button1 = createButton(0,0, 100, 50, "输出迷宫", RGB(236, 244, 255),RGB(204,213,240));
struct Button* button2 = createButton(0, 60, 100, 50, "显示迷宫通路", RGB(236, 244, 255), RGB(204, 213, 240));
struct Button* button3 = createButton(0, 120, 100, 50, "显示路径", RGB(236, 244, 255), RGB(204, 213, 240));
struct Button* button4 = createButton(0, 180, 100, 50, "动态显示通路", RGB(236, 244, 255), RGB(204, 213, 240));
struct Button* button5= createButton(0, 240, 100, 50, "动态最短通路", RGB(236, 244, 255), RGB(204, 213, 240));
struct Button* button6 = createButton(0, 300, 100, 50, "静态最短通路", RGB(236, 244, 255), RGB(204, 213, 240));
ExMessage mes;
BeginBatchDraw();
while (1) {
drawbutton(button1);
drawbutton(button2);
drawbutton(button3);
drawbutton(button4);
drawbutton(button5);
drawbutton(button6);
peekmessage(&mes, EM_MOUSE);
if (isClickButton(button1, mes))
{
cleardevice();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
mapp[i][j] = map[i][j];
}
}
mapp[sx][sy] = 3;
mapp[ex][ey] = 4;
draw(mapp);
}
if (isClickButton(button2, mes)) {
cleardevice();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) { //将原始map复制给mapp,避免对原始地图的破坏
mapp[i][j] = map[i][j];
}
}
for (int i = 1; i <= cnt; i++) {
for (int j = stk[i].sz; j; j--) { //修改地图上的点为路径上的点的枚举类型
mapp[stk[i].data[j].col][stk[i].data[j].colume] = 2;
}
}
mapp[sx][sy] = 3; //修改起点
mapp[ex][ey] = 4; //修改终点
draw(mapp);
}
if (isClickButton(button3, mes)) {
cleardevice();
print();
}
if (isClickButton(button4, mes)) {
int r = sx, c = sy;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
mapp[i][j] = map[i][j]; //将原始map复制给mapp,避免对原始地图的破坏
}
}
mapp[sx][sy] = 3;
mapp[ex][ey] = 4;
for (int i = 1; i <= cnt; i++) {
for (int j = stk[i].sz-1; j; j--) {
BeginBatchDraw(); //开启双缓冲绘图
cleardevice();
mapp[stk[i].data[j].col][stk[i].data[j].colume] = 2;//每次只修改一个点
if (j != stk[i].sz && j + 1 != stk[i].sz) mapp[stk[i].data[j + 1].col][stk[i].data[j + 1].colume] = 0;
draw(mapp);
mapp[ex][ey] = 4;
Sleep(200);
FlushBatchDraw(); //结束双缓冲绘图
}
}
}
if (isClickButton(button5, mes)) {
cleardevice();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) { //将原始map复制给mapp,避免对原始地图的破坏
mapp[i][j] = map[i][j];
}
}
mapp[sx][sy] = 3;
mapp[ex][ey] = 4;
for (int i = 1; i <= cnt; i++) {
if (i != 1 && stk[i].sz != stk[i - 1].sz) break;
for (int j = stk[i].sz-1; j; j--) {
BeginBatchDraw(); //开启双缓冲绘图
cleardevice();
mapp[stk[i].data[j].col][stk[i].data[j].colume] = 2;//每次只修改一个点
/*复原修改的点*/ if (j != stk[i].sz && j + 1 != stk[i].sz) mapp[stk[i].data[j + 1].col][stk[i].data[j + 1].colume] = 0;
draw(mapp);
mapp[ex][ey] = 4;
Sleep(200);
FlushBatchDraw(); //结束双缓冲绘图
}
}
}
if (isClickButton(button6, mes)) {
cleardevice();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) { //将原始map复制给mapp,避免对原始地图的破坏
mapp[i][j] = map[i][j];
}
}
for (int j = stk[1].sz; j; j--) { //修改地图上的点为路径上的点的枚举类型
mapp[stk[1].data[j].col][stk[1].data[j].colume] = 2;
}
mapp[sx][sy] = 3; //修改起点
mapp[ex][ey] = 4; //修改终点
draw(mapp);
}
FlushBatchDraw();
}
EndBatchDraw();
closegraph();
return 0;
}