贪吃蛇项目是在b站的一位老师那里边学边敲的(现在视频被老师删掉了),在这里记录一下代码和学习时的一些思考,希望自己以后能用链表将贪吃蛇实现。
思考过程:
贪吃蛇 C语言实现
1.打印首页 --FirstPage()
功能:提示用户按下空格开始游戏。;
知识点:C语言中接受直接接受键盘的一个字符而不用按回车就开始执行下一步命令的函数:_getch(),在conio.ht头文件中。
2.清空屏幕 --TestSpace()
功能:用户按下空格以后清空首页。
知识点:利用system("cls")实现。
3.打印游戏界面 --ShowBackground()
功能:打印一个行为20,宽为20的一个游戏区域。
知识点:把游戏区域的小方块和空格存在一个g_strGameBack二维数组里面,每次需要打印游戏界面的时候,就打印一次g_strGameBack数组。
4.刷新游戏界面 --Sleep()
功能:蛇的每次状态改变,为了在视觉上产生变化,都需要我们去打印一次游戏界面,即打印一次变化后的g_strGameBack数组。
知识点:使用Sleep()函数和system("cls")实现屏幕的刷新,Sleep()函数可以使程序在此等待一段时间后执行,单位是毫秒,该函数包含在windows.h头文件中。
5.将生成的蛇画到背景中 DrawSnake()
功能:根据蛇的坐标,将蛇显示在背景中。
知识点:用g_arrsnake[SNAKE_LONG][3]数组来保存蛇的坐标,然后使打印背景的数组g_strGameBack,这个数组上面的对应坐标打印出小方块。打印出的小方块就是蛇。
6.设置蛇的开始位置 SetSnakeRandPos()
功能:游戏开始时,生成蛇的随机坐标,蛇的默认长度是3个小方块。
知识点:设置随机数种子,然后分别生成横坐标和纵坐标的随机值。使用srand((unsigned)time(NULL))来设置随机数种子,然后rand生成随机数。相关函数在time.h头文件中。
生成的横坐标就是小方块的横坐标,生成的纵坐标得乘上2,因为小方块占用2个字节。然后把坐标值保存在g_arrsnake数组中,然后调用
DrawSnake()函数,把蛇画到背景中。
7.删除蛇 --DestroySnake()
功能:从背景中删掉上一个状态的蛇。
知识点:连续循环3次,把方块拷贝到空白地图上面
8.蛇动起来 --SnakeMove()
功能:让蛇朝着某一个方向移动, 其实是删除蛇原来蛇原来的位置以后,将蛇的上个格子的值,赋给当前的值。然后再将蛇头转移到下一个方向上,并判断,如果蛇头与方格重合到,
那么就表示蛇已经死亡,这个时候应该结束程序。
知识点:遍历g_arrsnake数组的所有行下标,如果下标不为0,就表示这是上一次蛇所在位置,那么这一次我们可以将前一个结点的值赋给当前结点,这个时候,除蛇头以外的所有
点坐标都已经更新,然后再处理蛇头,而蛇头的方向是有可能改变的,这个时候可以事先设立一个全局数组g_nSnakeDir,来表示蛇头每次的方向。通过g_nSnakeDir改变
蛇头的方向以后,利用循环判断蛇会不会咬到自己。
9.蛇改变方向 --ChangeDir()
功能:用户从键盘输入蛇的前进方向,根据用户输入的值改变前进方向。
知识点:该步骤得用GetAsyncKeystate()异步检测函数,不能用_getch()函数,该函数会等待用户输入,如果不输入则蛇不移动(同步检测),比如:GetAsyncKeyState('W');
表示如果输入W,就返回一个非0值。
10.判断蛇是否死亡 --SnakeDie()
功能:判断蛇是否会撞墙,在SnakeMove()函数中,蛇头改变方向以后,我们可以根据蛇头的坐标和蛇身体的坐标来判断蛇会不会咬到自己,现在我们要来判断蛇会不会撞墙。
知识点:我们可以判断蛇头的坐标这个位置是不是小方格来判断蛇有没有撞墙。所以这个函数专门用于新的背景被打印出来以后。
11.随机产生食物 --ProduceFood()
功能:在游戏区域随机尝试一个食物,首先要判断有没有必要产生新的食物,因为如果上一次蛇没有吃到食物的话,是没有必要产生新的食物的。
知识点:先利用随机数种子产生一对随机数坐标,然后要判断,如果这个随机坐标产生在蛇身所在位置,是不可以的。这里可以用while循环套住,
只有产生正确而且可用的坐标才可以结束while循环。然后将最后的正确坐标打印在游戏区域,显示为食物。并将下次有必要设置食物的
bool类型的值置为false。
12.蛇变长 --SnakeGrow()
功能:蛇吃完食物以后尾结点会变长。
知识点:判断蛇头坐标与食物坐标是否相等,如果相等,那么尾结点增1,注意要分四个方向来讨论,因为尾结点处于四个不同方向上,对于蛇数组的
处理方式是不同的。
13.打印分数 --Score()
功能:蛇每次吃食物后,在游戏区域的旁边实时的打印分数。
知识点:首先确定分数所在位置的坐标,然后利用SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), rd)函数设置光标的位置,最后打印。
GetStdHandle()返回标准的输入、输出或错误的设备的句柄,也就是获得输入、输出/错误的屏幕缓冲区的句柄。
#然后要确定main函数中各个函数的执行顺序:
FirstPage() -> TestSpace() -> system("cls") -> SetSnakeRandPos() ->
while() -> {system("cls") -> ChangeDir() -> SnakeGrow() -> ProduceFood() -> SnakeMove() -> if (SnakeDie()) -> DrewSnake() -> ShowBackground() -> Score() -> Sleep()} ->
system("pause") -> return 0
代码:
head.h
#define SNAKE_LONG 60
#define bool int
#define false 0
#define true 1
char g_strGameBack[20][48] = {
"■■■■■■■■■■■■■■■■■■■■■■■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■ ■\n",
"■■■■■■■■■■■■■■■■■■■■■■■\n"
};
//方向
enum {to_east = 2, to_west = -2, to_north = -1, to_south = 1};
//蛇数组
int g_arrsnake[SNAKE_LONG][3] = {0}; //[i][0]表示行的坐标,[i][1]表示列的坐标,[i][2]表示移动的方向
//蛇的方向
int g_nSnakeDir = to_west;
//
bool g_ProFood = true;
//产生随机坐标
int g_Row, g_Col;
//蛇长度
int g_SnakeLen = 2;
//分数
int g_Score = 0;
main.c
#include
#include
#include
#include //_getch所在头文件
#include
#include "head.h"
void FirstPage() {
printf("\n\n\n\n\t\t\t <<按下空格开始游戏>>\n");
}
void TestSpace() {
char ch;
//当输入为空格,就进入游戏
while (ch = _getch()) { //_getch的作用是读入一个字符,不用按回车
if (ch == ' ') break;
}
}
void ShowBackground() {
for (int i = 0; i < 20; i++) {
printf(g_strGameBack[i]); //打印g_strGameBack数组20次
}
}
//画蛇,根据设置好的数组将蛇画到背景中
void DrawSnake() {
strncpy(&g_strGameBack[g_arrsnake[0][0]][g_arrsnake[0][1]], "□", 2);
for (int i = 1; g_arrsnake[i][0] != 0; i++) { //连续循环3次,把方块拷贝到空白地图上面
strncpy(&g_strGameBack[g_arrsnake[i][0]][g_arrsnake[i][1]], "■", 2);
}
}
//删除蛇残留
void DestroySnake() {
for (int i = 0; g_arrsnake[i][0] != 0; i++) { //连续循环3次,把方块拷贝到空白地图上面
strncpy(&g_strGameBack[g_arrsnake[i][0]][g_arrsnake[i][1]], " ", 2);
}
}
//设置蛇的开始位置
void SetSnakeRandPos() {
int nX = -1, nY = -1;
srand((unsigned int)time(NULL)); //设置随机数种子
nX = rand() % 19 + 1; //因为初始长度是3,最后一列22是墙,所以第一个蛇方块上限是19
nY = rand() % 18 + 1; //因为最后一行19是墙,所以上限是18
// printf("%d %d\n", nX, nY);
g_arrsnake[0][0] = nY; //行
g_arrsnake[0][1] = nX * 2;
g_arrsnake[0][2] = to_west;
g_arrsnake[1][0] = nY; //行
g_arrsnake[1][1] = nX * 2 + 2;
g_arrsnake[1][2] = to_west;
g_arrsnake[2][0] = nY; //行
g_arrsnake[2][1] = nX * 2 + 4;
g_arrsnake[2][2] = to_west;
DrawSnake();
}
//蛇动起来
void SnakeMove() {
//先从背景删掉原来的蛇
DestroySnake();
int i = SNAKE_LONG - 1; //数组下标0到19
for (i; i >= 1; i--) {
//过滤掉非法蛇身
if (g_arrsnake[i][1] == 0) continue;
//把前一个结点的值,赋给当前结点
g_arrsnake[i][0] = g_arrsnake[i - 1][0];
g_arrsnake[i][1] = g_arrsnake[i - 1][1];
g_arrsnake[i][2] = g_arrsnake[i - 1][2];
}
//处理蛇头
g_arrsnake[0][2] = g_nSnakeDir; //初始化方向为向西
//如果是东西方向
if (g_arrsnake[0][2] == to_west || g_arrsnake[0][2] == to_east) {
g_arrsnake[0][1] += g_arrsnake[0][2];
} else { //如果是东西方向
g_arrsnake[0][0] += g_arrsnake[0][2];
}
for (int j = SNAKE_LONG - 1; j >= 1; j--) {
if (g_arrsnake[j][1] == 0) continue;
//g_arrsnake[0][0]和g_arrsnake[0][1]为蛇头坐标
//判断蛇头改变方向以后会不会咬到自己
if (g_arrsnake[0][0] == g_arrsnake[j][0] && g_arrsnake[0][1] == g_arrsnake[j][1]) {
printf("Snake Die!\n");
printf("Ending Scores: %d\n", g_Score);
system("pause");
}
}
}
//蛇随方向动起来
void ChangeDir() {
//不可以用_getch(),该函数会等待用户输入,如果不输入则蛇不移动(同步检测)
//GetAsyncKeystate()异步检测函数
//GetAsyncKeyState('W'); //如果输入W,就返回一个非0值
if (GetAsyncKeyState('W')) {
if (g_arrsnake[0][2] != to_south) { //不能向相反的方向走
g_nSnakeDir = to_north;
}
} else if (GetAsyncKeyState('S')) {
if (g_arrsnake[0][2] != to_north) {
g_nSnakeDir = to_south;
}
} else if (GetAsyncKeyState('A')) {
if (g_arrsnake[0][2] != to_east) {
g_nSnakeDir = to_west;
}
} else if (GetAsyncKeyState('D')) {
if (g_arrsnake[0][2] != to_west) {
g_nSnakeDir = to_east;
}
}
}
//判断蛇是否死亡
bool SnakeDie() {
//如果蛇头的下一个位置是方块,就返回0
//if (g_arrsnake[0][2] == to_west || g_arrsnake[0][2] == to_east) {
// if (!strncmp(&g_strGameBack[g_arrsnake[0][0]][g_arrsnake[0][1] + g_arrsnake[0][2]], "■", 2))
//if (!strncmp(&g_strGameBack[g_arrsnake[0][0]][g_arrsnake[0][1]], "■", 2))
//return false;
//} else if (g_arrsnake[0][2] == to_north || g_arrsnake[0][2] == to_south)
// if (!strncmp(&g_strGameBack[g_arrsnake[0][0] + g_arrsnake[0][2]][g_arrsnake[0][1]], "■", 2))
if (!strncmp(&g_strGameBack[g_arrsnake[0][0]][g_arrsnake[0][1]], "■", 2)) {
return false;
}
return true;
}
//随机产生新的食物
void ProduceFood() {
//判断是否有必要产生新的食物
if (g_ProFood == false)
return;
bool flag = true;
srand((unsigned int)time(NULL));
while (1) {
g_Row = rand() % 18 + 1;
g_Col = rand() % 20 + 1;
//遍历蛇身,如果随机坐标产生在蛇身,就重新生成随即坐标
for (int i = 0; g_arrsnake[i][0] != 0; i++)
if (g_arrsnake[i][0] == g_Row && g_arrsnake[i][1] == g_Col)
flag = false;
if (flag == true)
break;
}
//坐标画食物
strncpy(&g_strGameBack[g_Row][g_Col * 2], "●", 2);
g_ProFood = false;
}
//蛇变长
void SnakeGrow() {
//如果食物坐标和蛇头坐标相等
if (g_arrsnake[0][0] == g_Row && g_arrsnake[0][1] == g_Col * 2) {
if (g_arrsnake[0][2] == to_east) { //判断尾结点在哪个方向上
g_arrsnake[g_SnakeLen + 1][0] = g_arrsnake[g_SnakeLen][0];
g_arrsnake[g_SnakeLen + 1][1] = g_arrsnake[g_SnakeLen][1] - 2;
g_arrsnake[g_SnakeLen + 1][2] = g_arrsnake[g_SnakeLen][2];
} else if (g_arrsnake[0][2] == to_west) {
g_arrsnake[g_SnakeLen + 1][0] = g_arrsnake[g_SnakeLen][0];
g_arrsnake[g_SnakeLen + 1][1] = g_arrsnake[g_SnakeLen][1] + 2;
g_arrsnake[g_SnakeLen + 1][2] = g_arrsnake[g_SnakeLen][2];
} else if (g_arrsnake[0][2] == to_north) {
g_arrsnake[g_SnakeLen + 1][0] = g_arrsnake[g_SnakeLen][0] + 1;
g_arrsnake[g_SnakeLen + 1][1] = g_arrsnake[g_SnakeLen][1];
g_arrsnake[g_SnakeLen + 1][2] = g_arrsnake[g_SnakeLen][2];
} else if (g_arrsnake[0][2] == to_south){
g_arrsnake[g_SnakeLen + 1][0] = g_arrsnake[g_SnakeLen][0] - 1;
g_arrsnake[g_SnakeLen + 1][1] = g_arrsnake[g_SnakeLen][1];
g_arrsnake[g_SnakeLen + 1][2] = g_arrsnake[g_SnakeLen][2];
}
g_SnakeLen++;
g_ProFood = true;
g_Score++;
}
}
void Score() {
//COORD是Windows API中定义的一种结构,表示一个字符在控制台屏幕上的坐标。
COORD rd;
rd.X = 56;
rd.Y = 8;
//设置光标位置
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), rd);
//打印
printf("Score: ");
rd.X = 64;
rd.Y = 8;
//设置光标位置
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), rd);
//打印
printf("%d", g_Score);
}
int main()
{
//首页
FirstPage();
//按下空格
TestSpace();
// 清空屏幕
system("cls");
//设置蛇的位置
SetSnakeRandPos();
//打印游戏界面
while (1) {
system("cls");
//改变方向
ChangeDir();
//蛇变长
SnakeGrow();
//产生食物
ProduceFood();
//蛇移动
SnakeMove();
//判断蛇是否死亡
if (SnakeDie() == false) {
printf("Snake Die!\n");
printf("Ending Scores: %d\n", g_Score);
break;
}
//在背景上显示蛇
DrawSnake();
//打印背景
ShowBackground();
//打印分数
Score();
Sleep(150); //每150毫秒刷新刷新打印一次,在windows.h头文件中
}
system("pause");
return 0;
}
游戏截图:
*
*
*
*
*
*
其中关于SetConsoleCursorPosition()函数的知识点,我参考了:C语言如何在控制台窗口设置光标到指定位置