迷宫探宝(c/c++完整代码)(贪吃蛇+迷宫)

游戏背景:

一天一条小蛇来到了一个迷宫探险,因为听说这个迷宫里面有些食物能让它变长,所以,它非常的兴奋想来吃走所有食物。
迷宫通道刚好只能容纳它的蛇身,但这一条小蛇有个特点,它有2个头,所以不需要掉头可以直接前后移动。
小蛇每吃一个食物,自身长度增一,且头不能碰到自己的身体(和贪吃蛇一样)。

具体玩法:
根据玩家自定义的地图大小,生成一个N*M的迷宫(初始化地图后长度为1的小蛇出现在迷宫入口处),生成的迷宫必须保证存在一条从入口到出口的路径,玩家可以通过键盘方向键控制蛇身在迷宫里探险寻找出口,注意前后移动就是前后平移(不能掉头,理解题意!)
根据玩家自定义的食物数量,随机在迷宫空白处生成食物,玩家需要控制蛇身吃到尽可能多的食物并且能顺利走出迷宫,但注意随着蛇身越来越长,可能会导致蛇身封住必要的道路,如何寻路以及利益最大化成了本游戏的亮点。

一、流程图:

在这里插入图片描述

二、现阶段问题的解决

一、画出迷宫,初始化小蛇
参考资料:用C语言实现走迷宫
https://blog.csdn.net/qq_41020768/article/details/78844043

算法:
迷宫的初始状态是墙壁都存在。选择一个开始区域。
随机得选择一个没有访问过的邻接区域,并打通与它之间的墙壁。
此邻接区域称为当前区域。
如果所有周围的区域都是访问过的,则退回上一个区域进行挖据墙壁,一直重复。
当开始的区域被退回的时候,算法结束。
(因为要打通区域,且各区域只有一个口子,所以必定会产生一条通往出口的,唯一一条路)

初始化蛇:

Direction赋值任意。

二、生成食物
参考资料:c语言贪吃蛇详解4.食物的投放与蛇的变长
https://www.cnblogs.com/hjw1/p/7987877.html

Foodnum是程序刚开始输入的食物的数量。
Height是迷宫的高度
Width是迷宫的宽度
Foodx,foody分别存储食物的横纵坐标,以确定食物生成的地点
Index[][]是每一个食物的索引,为以后小蛇每吃一个食物就加一个长度,并加分做铺垫。
Map[][]是存储迷宫地图的二维数组
“0”代表食物
即:

For(循环foodnum次)
{
	Do
{随机生成食物的横纵坐标。
}while(横纵坐标对应的点没有障碍物)
}

保存食物横纵坐标
打印食物

问题:最开始的时候食物会嵌入墙中。
随机出现的食物没有取好限制条件,有时候可以吃到有时候吃不到,后来发现还是位置的问题,因为gotoxy(x-1,y-1),所以其中map(foodx,foody)的foodx,foody都要加1.才运行正确。0可以看作是Road(#define Road 0).之所以加诸如不能出现在蛇身身上的条件,因为该条件的值,已经考虑到了这种情况。而且其概率使极低的。
三、画出小蛇
参考资料:c语言贪吃蛇详解-2.画出蛇
https://www.cnblogs.com/hjw1/p/7911995.html

sLength是蛇的长度,通过s[][]数组将蛇身产生。
通过key()和move()函数实现通过按键盘上的wsad移动小蛇,通过check()函数判断小蛇是否吃了食物。
但是运行出来以后,小蛇吃食物时会有一个符号在迷宫的左上角,所以在(0,0)处添加一个与墙壁一样的符号(符号不定),使看起来无闪烁。

四、小蛇移动以及触墙
参考资料:
1.c语言贪吃蛇详解3.让蛇动起来
https://www.cnblogs.com/hjw1/p/7912601.html
2.用C语言实现走迷宫
https://blog.csdn.net/qq_41020768/article/details/78844043#commentBox
3.c语言贪吃蛇详解4.食物的投放与蛇的变长
https://www.cnblogs.com/hjw1/p/7987877.html
4.怎么用C语言让一个字符动起来,可以控制上下左右
https://zhidao.baidu.com/question/747033611862946532.html

因为小蛇的移动会留下“足迹”(即之前打印了的“身体”),导致移动产生一串连续的字符,所以每移动一次就把尾巴擦掉,“假装”小蛇是整个移动了。
根据已知,可得(s[sLength - 1][0], s[sLength - 1][1])为小蛇尾巴横纵坐标(上一次移动的)

那么如何移动呢?
可以想象现实生活中蛇的伸缩运动:
伸缩运动,蛇身前部抬起,尽力前伸,接触到支持的物体时,蛇身后部即跟着缩向前去,然后再抬起身体前部向前伸,得到支持物,后部再缩向前去,这样交替伸缩,蛇就能不断地向前爬行.
通过数组表现为:

从尾巴开始,每一个点的位置等于它前面一个点的位置

怎么让小蛇不能穿墙呢?
在map中已经定义Wall是迷宫的墙,所以首先判定小蛇移动后是否“嵌”进墙中,如果嵌进去了,那么什么事都不做,没有嵌进去就随方向键移动。
如何判定?
参考资料2中有这么一段,实现小球不穿墙的代码:

case Up: //向上走
            if(map[x-1][y]!=Wall)
            {
                paint(x,y);
                x--;
            }

也就是说,如果map[x-1][y]不是墙,小球就能走。
通过分析,可以得知,gotoxy(x,y)和map[x-1][y]之间的关系。
因为坐标是通过gotoxy()控制的。
可知:gotoxy(x+i,y+i)==》map[x-1-i][y-i],其中,i∈R
从而:

五、小蛇变长
参考资料:c语言贪吃蛇详解4.食物的投放与蛇的变长
https://www.cnblogs.com/hjw1/p/7987877.html

之前将食物的坐标存入了数组index[][],在这里一个个比较,如果横纵坐标都符合,蛇头覆盖了食物的坐标。那么蛇就吃到了食物,长度也就加1。但是每次移动都在比较,对于已经吃到了的食物,不能再次利用,所以使已吃食物的坐标变为-1,避免重复。
误区:
先前生成食物理解错误,认为是一个个生成:

六、背景音乐
c语言如何编写一个简单的多线程程序

https://zhidao.baidu.com/question/1238373648389928899.html?qbl=relate_question_6&word=C����ʵ���̴߳���

怎么使用Playsound函数在c程序中添加背景音乐呢?求解!!!
https://www.baidu.com/link?url=dfyGU40L03D2FjfXwwDHXd8yhEa3BK1YYPPpirfPRFFY9JZMHlXMI7TfdDhHOxQBffYg5AL4WY0UigYUdX4Uza&wd=&eqid=f6f7648c0002a961000000025cffa194

大一 新手求助: 怎么在C语言中实现添加背景音乐 特效音乐? [问题点数:100分]
https://bbs.csdn.net/topics/390756394

c++利用winapi实现简单多线程
https://blog.csdn.net/u011575841/article/details/53116269

添加背景音乐:
需要导入Winmm.lib库:

pragma comment (lib, “Winmm.lib”)//导入Winmm.lib库

背景音乐被我放在了当前目录的Debug文件夹下(相对路径)。

只要一句:
PlaySound(TEXT(“Debug\bgsnake.wav”), NULL, SND_FILENAME);
就可以播放音乐。但是,我发现,当音乐播放时,游戏仿佛卡住了一样,不能动。在网上查阅资料得知,需要多开一个线程,使之互不影响。

其它
教你在windows上用C语言隐藏/显示控制台光标

https://www.baidu.com/link?url=Z2kQfvu0f0ojGjyi5JoPmUL77odNmu6fgclgtB1QETUKeQvohIKgpORN5xWkK50lyAmwvlqiIYzW4eYDQAkEYZOvMU20H-meq0kXk_CXLGG&wd=&eqid=ce42d3df00006eff000000025cf34701

https://blog.csdn.net/nocomment_84/article/details/53992730

//SetConsoleCursorPosition是一个window api;作用是设置控制台(cmd)光标位置
/GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备
(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。
可以嵌套使用。
/
//STD_OUTPUT_HANDLE:标准输出的句柄

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include<stdlib.h>
#include <time.h> 
# pragma comment (lib, "Winmm.lib")//导入Winmm.lib库
#pragma warning(disable:4996)
#define Wall 1
#define Road 0
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define Start 2
#define End 3

int Height;      //迷宫的高度,必须为奇数
int Width;   //迷宫的宽度,必须为奇数
int map[1001][1001];  //地图预定义大小
int foodnum;   //食物总数
int foodsum = 0;   //吞吃的食物数量
int s[1000][2];      //蛇身坐标数组
int sLength = 1;   //蛇的长度,蛇的最初长度为1
int direction;      //蛇的方向
int foodx, foody;   //食物的坐标
int index[100][100];  //食物索引
int step;

//多线程
DWORD WINAPI Function1Proc(LPVOID lpParameter);//音乐
DWORD WINAPI Function2Proc(LPVOID lpParameter);//各种函数
bool stop = false;

int main();

//程序开始时的初始化操作
void init()
{
	int i, j;

	s[0][0] = 1;
	s[0][1] = 0;        //给蛇头坐标赋值
	for (i = 1;i < sLength;i++)
	{
		s[i][0] = s[0][0] + i;
		s[i][1] = s[0][1];  //给刚开始的蛇身几个初始坐标
	}
	direction = RIGHT;//方向在右边
}
//坐标
void gotoxy(int i, int j)        //移动光标
{
	COORD position = { j,i };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
//初始设置

void set()
{
	gotoxy(4, 30);
	printf("欢迎来到迷宫贪吃蛇!");
	gotoxy(5, 30);
	printf("请设置地图的大小:(高和宽均为奇数,建议比值:1:1.5)\n");
	gotoxy(6, 30);
	printf("请输入地图的高度:");
	gotoxy(7, 30);
	scanf("%d", &Height);
	gotoxy(8, 30);
	printf("请输入地图的宽度:");
	gotoxy(9, 30);
	scanf("%d", &Width);
	gotoxy(10, 30);
	printf("请设置食物的数量:\n");
	gotoxy(11, 30);
	scanf("%d", &foodnum);


	//getch();

}

//产生食物
void createfood()
{
	srand((unsigned)time(NULL));//初始化随机种子
	for (int i = 0;i < foodnum;i++)
	{
		do
		{
			foodx = rand() % Height;
			foody = rand() % Width;
		} while (map[foodx + 1][foody + 1] != 0);//+1:参照gotoxy(x-1,y-1)
		//只有在空旷的地方才产生食物
		index[i][0] = foodx;//数组索引存入x坐标
		index[i][1] = foody;//数组索引存入y坐标
		gotoxy(foodx, foody);//在坐标处产生食物
		printf("O");
	}
}
//食物判定

void check()
{
	for (int i = 0;i < foodnum;i++)
	{
		if (index[i][0] == s[0][0] && index[i][1] == s[0][1])
		{
			sLength++;//长度+1
			index[i][0] = index[i][1] = -1;//避免重复
			gotoxy(8, Width + 10);
			printf("当前分数是:%d", 10 * (++foodsum));//显示分数
			//if (foodsum != foodnum)//判定食物吞吃情况
				//createfood();
		}
	}
}

//蛇的移动
void move()
{
	int i;
	gotoxy(s[sLength - 1][0], s[sLength - 1][1]);
	printf(" ");//在尾巴上面画空格以擦除尾巴
	gotoxy(Height + 2, 1);//显示小蛇所走的步数
	printf("小蛇当前步数为:%d", ++step);
	for (i = sLength - 1;i > 0;i--)    //从尾巴开始,每一个点的位置等于它前面一个点的位置
	{
		s[i][0] = s[i - 1][0];
		s[i][1] = s[i - 1][1];
	}
	switch (direction)
	{
	case UP:
		if (map[s[0][0] - 1 + 1][s[0][1] + 1] != Wall)
			//根据gotoxy(x-1,y-1)应+1
			s[0][0]--;
		break;
	case DOWN:
		if (map[s[0][0] + 1 + 1][s[0][1] + 1] != Wall)
			s[0][0]++;
		break;
	case LEFT:
		if (map[s[0][0] + 1][s[0][1] - 1 + 1] != Wall)
			s[0][1]--;
		break;
	case RIGHT:
		if (map[s[0][0] + 1][s[0][1] + 1 + 1] != Wall)
			s[0][1]++;
		break;
	}

}
//键值判定
void key()
{
	char in;
	while (in = getch())          //如果有键盘输入
	{
		switch (in)
		{
		case 27://Esc键的键值
			exit(0);
			break;
		case 'f':
		case 'F':
			system("cls");//清屏,重新对foodsum,sLength赋值,重新开始游戏
			foodsum = 0;
			sLength = 1;
			main();
			break;
		case 'w':
		case 'W':
			direction = UP;            //可以缩头
			break;
		case 's':
		case 'S':
			direction = DOWN;
			break;
		case 'a':
		case 'A':
			direction = LEFT;
			break;
		case 'd':
		case 'D':
			direction = RIGHT;
			break;
		}
	}
}
//画蛇
void drawSnake()                //画蛇
{
	int i, c;
	while (1)
	{
		for (i = 0;i < sLength;i++)
		{
			gotoxy(s[i][0], s[i][1]);        //移动光标到蛇的坐标
			printf("@");                    //在这个位置画蛇
		}
		gotoxy(0, 0);//解决(0,0)闪烁
		printf("&");

		key();//输入按钮
		move();//移动蛇
		//到达终点就完结
		if (map[s[0][0] + 1][s[0][1] + 1] == End)//如果到达了终点
		{
			gotoxy(Height, Width);
			printf("到达终点,按任意键结束!");
			getch();
			exit(0);
			return;
		}
		check();//判定是否吃了食物
	}
}
//隐藏光标
void hidden()//隐藏光标
{
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cci;
	GetConsoleCursorInfo(hOut, &cci);
	cci.bVisible = 0;//赋1为显示,赋0为隐藏
	SetConsoleCursorInfo(hOut, &cci);
	//SetConsoleCursorPosition是一个window api;作用是设置控制台(cmd)光标位置
	/*GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备
	(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。
	可以嵌套使用。*/
	//STD_OUTPUT_HANDLE:标准输出的句柄


}
//产生地图
void create(int x, int y) //随机生成迷宫
{
	int c[4][2] = { 0,1,1,0,0,-1,-1,0 }; //四个方向
	int i, j, t;
	//将方向打乱
	for (i = 0;i < 4;i++)
	{
		j = rand() % 4;
		t = c[i][0];c[i][0] = c[j][0];c[j][0] = t;
		//以t为媒介,swap(c[i][0],c[j][0])
		t = c[i][1];c[i][1] = c[j][1];c[j][1] = t;
		//以t为媒介,swap(c[i][1],c[j][1])
	}
	map[x][y] = Road;//设置路
	for (i = 0;i < 4;i++)
		if (map[x + 2 * c[i][0]][y + 2 * c[i][1]] == Wall)
		{//可看作map[x+2*c[0,1,2,3][0]][y+2*c[0,1,2,3][1]]==wall
			map[x + c[i][0]][y + c[i][1]] = Road;
			//map[x+c[0,1,2,3][0]][y+c[0,1,2,3][1]]==road
			create(x + 2 * c[i][0], y + 2 * c[i][1]);
			//递归方法,create(x+s*c[0,1,2,3][0],y+2*c[0,1,2,3][1])
		}
}
//画地图
void paint(int x, int y) //画迷宫
{
	//gotoxy(2 * y - 2, x - 1);
	gotoxy(x - 1, y - 1);//定点
	switch (map[x][y])//一个个点对应画出
	{
	case Start:
		printf("入");break; //画入口
	case End:
		printf("出");break; //画出口
	case Wall:
		printf("&");break; //画墙
	case Road:
		printf(" ");break; //画路
	}

}
//地图设置
void mymap()
{
	system("title Maze treasure");
	int i, j;
	srand((unsigned)time(NULL)); //初始化随机种子
	hidden(); //隐藏光标
	for (i = 0;i <= Height + 1;i++)
		for (j = 0;j <= Width + 1;j++)
			if (i == 0 || i == Height + 1 || j == 0 || j == Width + 1) //初始化迷宫
				map[i][j] = Road;
			else map[i][j] = Wall;

	create(2, 2); //从该点开始生成迷宫,该点行列都为偶数
	for (i = 0;i <= Height + 1;i++) //边界处理
	{
		map[i][0] = Wall;//左右边界
		map[i][Width + 1] = Wall;
	}

	for (j = 0;j <= Width + 1;j++) //边界处理
	{
		map[0][j] = Wall;//上下边界
		map[Height + 1][j] = Wall;
	}
	map[2][1] = Start; //给定入口
	map[Height - 1][Width] = End; //给定出口
	for (i = 1;i <= Height;i++)
		for (j = 1;j <= Width;j++) //画出迷宫
			paint(i, j);
	gotoxy(2, Width + 10);
	printf("注意事项:");
	gotoxy(3, Width + 10);
	printf("①.按F/f键重新开始游戏!(☆▽☆)");
	gotoxy(4, Width + 10);
	printf("②.小蛇可以缩头缩身体,可看作有两个头╰( ̄ω ̄o)");
	gotoxy(5, Width + 10);
	printf("③.使用键盘WSAD/wsad四个方向键可以控制小蛇的移动");
	gotoxy(6, Width + 10);
	printf("④.@是小蛇,o是食物,&是墙壁");
}


DWORD WINAPI Function1Proc(LPVOID lpParameter)
{
	//背景音乐
	PlaySound(TEXT("Debug\\bgsnake.wav"), NULL, SND_FILENAME);
	return 0;
}
DWORD WINAPI Function2Proc(LPVOID lpParameter)
{
	system("cls");
	mymap();//生成地图
	init();       //程序开始时的初始化操作
	createfood();   //生成食物
	drawSnake();       //画蛇
	getch();//接受一个字符
	return 0;
}
int main()
{
	set();

	HANDLE hThread1;
	HANDLE hThread2;
	hThread1 = CreateThread(NULL, 0, Function1Proc, NULL, 0, NULL);//开启多线程
	//第一个参数,使用默认的安全性;第二个参数,使用与调用函数的线程相同的栈大小
	//第三个参数,线程函数入口;第四个参数,传递给线程的参数
	//第五个参数,线程创建后立即运行,第六个参数,新线程的ID
	hThread2 = CreateThread(NULL, 0, Function2Proc, NULL, 0, NULL);
	if (getchar())stop = true;
	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	return 0;
}
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值