C 语言实现贪吃蛇

本博客的主要目的是学习<windows.h><conio.h>头文件 和 各种有趣的函数,顺便观膜真正简洁与高级的源代码

参考po主:RainbowRoad1,非常感谢po主提供了源码

贪吃蛇源代码(VS2019运行)

#include<windows.h>
#include<stdlib.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
#define Width 25///可以宏定义地图大小(奇数,否则墙会不整齐)
int main()
{
	srand((unsigned)time(NULL));//初始化随机数种子
	do///重新开始,直接套一个循环
	{
		int hX = Width / 5, hY = Width / 5, len = 4, i = 0, map[Width * Width] = { 0 };//头座标,蛇长,循环变量,地图(-1:食物;0:空白;>0:蛇身)
		for (i = 0; i < Width; i += 2)///地图元素:-2:墙
			map[i] = map[Width * Width - 1 - i] = map[i * Width] = map[i * Width + Width - 1] = -2;//使四周的墙都隔一格分布
		char c = 'd', cl = 3, deaw[Width * Width * 2 + 1] = { 0 };//初始方向,输入缓存,绘制缓存
		sprintf_s(deaw, 32, "mode con: cols=%d lines=%d", Width * 2, Width);
		system(deaw);//修改控制台窗口大小
		for (int num = 3; num; num--)///生成多个食物,num控制数量
		{
			do i = rand() % (Width * Width);
			while (map[i]);
			map[i] = -1;
		}
		for (system("title 得分:0"); 1; Sleep(100))///初始化计分板,延迟
		{
			if (_kbhit() && (cl = _getch()))//判断是否输入
				while (cl == ' ')
				{
					switch (cl)
					{
					case 'a':case 'A':if (c != 'd')c = 'a'; break;//判断与原方向是否冲突
					case 'd':case 'D':if (c != 'a')c = 'd'; break;
					case 's':case 'S':if (c != 'w')c = 's'; break;
					case 'w':case 'W':if (c != 's')c = 'w'; break;
					case ' ':cl = _getch(); break;///空格暂停
					case 27:exit(0); break;///Esc退出
					}
				}
			switch (c)//允许穿墙
			{
			case 'a':hX -= (hX > 0 ? 1 : 1 - Width); break;//更新头座标
			case 'd':hX += (hX < Width - 1 ? 1 : 1 - Width); break;
			case 's':hY += (hY < Width - 1 ? 1 : 1 - Width); break;
			case 'w':hY -= (hY > 0 ? 1 : 1 - Width); break;
			}
			if (map[hY * Width + hX] > 1 || map[hY * Width + hX] == -2) break;//判断是否吃到自己 或 是否撞墙
			if (map[hY * Width + hX] == -1)//判断是否吃到食物
			{
				len++;
				do i = rand() % (Width * Width);//刷新食物
				while (map[i]);//防止食物位置覆盖蛇
				map[i] = -1;
				sprintf_s(deaw, 32, "title 得分:%d", len - 4);///计分板
				system(deaw);
			}
			else
				for (i = 0; i < Width * Width; i++)//全部蛇身值-1
					if (map[i] > 0)map[i] -= 1;
			map[hY * Width + hX] = len;//蛇头赋值
			for (i = 0; i < Width * Width * 2; i++)//更新绘制缓存
			{
				if (map[i / 2] == 0)deaw[i] = ' ';
				else if (map[i / 2] > 0)deaw[i] = (i & 1) ? ')' : '(';
				else if (map[i / 2] == -2)deaw[i] = (i & 1) ? ']' : '[';///墙壁
				else deaw[i] = '0';//食物
			}
			system("cls");//清屏
			printf(deaw);//打印(能去闪烁)
		}
		sprintf_s(deaw, 48, "title GameOver!得分:%d 按空格键重新开始", len - 4);
		system(deaw);
	} while (_getch() == ' ');///空格键继续
}

讲解函数

  • srand()函数(头文件 <stdlib.h> )
    众所周知,rand() 函数不需要参数,它会返回 0 到 RAND_MAX(一般是32767)之间的任意的整数,但它也是伪随机数,只是周期特别长,所以在一定范围内可以看成随机的,若想要生成随机数,加上srand() 函数即可,它相当于初始化随机数发生器,举例如下:
srand(1)
int temp = rand()  //1314

srand(2)
int temp = rand()  //8848

二者生成的数值是不同的,利用这一特性,我们加以利用time()函数 (头文件<time.h>)
time()函数是获取当前系统时间的函数,以秒作单位,我们把它嵌入到srand()函数中

srand(time(0))  //写 time(0)、time(NULL)都是一样的
int temp = rand()  //2333

现在生成的就是真正的随机数

补充:若想生成 0 - 1 内的随机数可以写(double)rand() / RAND_MAX

  • sprintf_s()函数(头文件 <stdio.h> )
    其函数功能是将数据格式化输出到字符串,括号内包括四个参数,第一个参数是存储的位置,第二个参数是要存储的大小,第三个是数据格式化,第四参数是存入的位置,返回值是已存入的空间大小
举个例子:

char buffer[200], s[] = "computer", c = 'l';
int i = 35, j;
float fp = 1.7320534f;
j = sprintf_s( buffer, 200, " String: %s\n", s );
j += sprintf_s( buffer + j, 200 - j, " Character: %c\n", c );
j += sprintf_s( buffer + j, 200 - j, " Integer: %d\n", i );
j += sprintf_s( buffer + j, 200 - j, " Real: %f\n", fp );
printf_s( "Output:\n%s\ncharacter count = %d\n", buffer, j );

打印结果如下:
Output:
String: computer
Character: l
Integer: 35
Real: 1.732053

character count = 61
  • system()函数(头文件 <windows.h> )
    执行 shell 命令,也就是向操作系统发送一条指令,举例如下:
system("mode con: cols=60 lines=30");

代表调整系统控制台显示的宽度和高度,宽度为60个字符,高度为30个字符

而源代码出现的:
sprintf_s(deaw, 32, "mode con: cols=%d lines=%d", Width * 2, Width);
system(deaw);

//意思都一样,只是分开写了

至于 sprintf_s 后面的两个参数比是 2 :1 是为了系统控制台为正方形,第二参数是 32 可能是因为操作系统的大小为 32 吧(这个我不确定)

system("title 得分:0")

显示系统控制台的标题

system("cls");

同样的道理,清屏

  • Sleep()函数(头文件 <windows.h> )
举例:
Sleep(1000)  //表示休息一秒

在本代码中可以理解为贪吃蛇移动速度(屏幕刷新频率)
  • kbhit()函数 和 getch()函数(头文件 <conio.h> )
    kbhit() 函数用于检测用户是否有按下某键(键盘任意一键),当用户按下一次按键时返回 1,否则返回 0;重点在于,程序运行到 kbhit() 语句时程序不会暂停,而会继续运行,故当多次使用 kbhit() 时,程序会不停接收返回值
    getch() 函数用于返回从键盘上读取到的字符,而且当检测到用户输入一个字符后就会继续程序,不需要按回车键,并且字符不会在屏幕上显示
    之所以代码中写的是 _getch()_kbhit(),是因为 VS2019 不支持传统函数(部分),会认为其不安全,加个下划线就好了

分析代码

一、空格暂停是如何实现的?

以下部分源码说明:

if (_kbhit() && (cl = _getch()))
	while (cl == ' ')
	{
		switch (cl)
		{
		case 'a':case 'A':if (c != 'd')c = 'a'; break;
		case 'd':case 'D':if (c != 'a')c = 'd'; break;
		case 's':case 'S':if (c != 'w')c = 's'; break;
		case 'w':case 'W':if (c != 's')c = 'w'; break;
		case ' ':cl = _getch(); break;
		case 27:exit(0); break;
		}
	}

一般情况下,程序遇到 _getch() 函数是要暂停的,但这个 if 从句中还包含 _kbhit() 函数,故在没有输入键位的情况下依然会执行本语句,直到出现空格键,程序暂停,进入 case 中,之后若输入空格或别的键位才会继续,若输入 a w s d 中的其中一个键位则会直接向其方向运动

二、如何实现使贪吃蛇蛇尾消失?

本代码并没用运用列表,蛇尾是随着循环自动消失的,以下部分源码说明:

for (i = 0; i < Width * Width; i++)
	if (map[i] > 0)map[i] -= 1;

先将所有蛇身减去 1

map[hY * Width + hX] = len;
if (map[i / 2] == 0)deaw[i] = ' ';

将蛇头赋值为身体长度,再将蛇身为 0 删去,意思是蛇身的每个部分会随着循环逐渐减小,直到最后为 0,就消失

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章