一天一条小蛇来到了一个迷宫探险,因为听说这个迷宫里面有些食物能让它变长,所以,它非常的兴奋想来吃走所有食物。
迷宫通道刚好只能容纳它的蛇身,但这一条小蛇有个特点,它有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库:
背景音乐被我放在了当前目录的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
#include
#include
#include
#include
# 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;
}