本博客的主要目的是学习<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,就消失