目录
效果
贪吃蛇(可以直接CV)
实现贪吃蛇游戏的C语言代码可以分为几个关键部分:初始化游戏界面、控制贪吃蛇移动、生成食物、处理用户输入、游戏逻辑判断等。下面是一个简易贪吃蛇的代码代码,展示了如何实现基本功能的贪吃蛇游戏
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include <conio.h>
#define wide 45
#define hige 20
#define open_score 100
#define space 32
typedef struct body
{
int x;
int y;
}BODY;
typedef struct snake
{
BODY list[wide*hige];
int size;
BODY food;
BODY move_xy;
BODY tail;
int score;
int higest_score;
char t;
unsigned char speed;
}SNAKE;
FILE *open_file(char *filename, char *mode);
void gui_init(int x, int y);
void snake_init(SNAKE *snake);
void food_init(SNAKE *snake);
void gui_show(SNAKE *snake);
void movecursor(int x, int y);
void start_game(SNAKE *snake);
void move_snake(SNAKE *snake);
void control_snake(SNAKE *snake);
void end_game(SNAKE *snake);
void snake_eat_body(SNAKE *snake);
void snake_eat_food(SNAKE *snake);
void hide_console_cursor();
void color(int c);
int main()
{
SNAKE *snake = (SNAKE *)malloc(sizeof(SNAKE));
if (snake == NULL) {
fprintf(stderr, "内存分配失败。\n");
return 1;
}
hide_console_cursor();
gui_init( 0, 0);
gui_init( wide + 10, 0);
snake_init(snake);
food_init(snake);
gui_show(snake);
start_game(snake);
free(snake);
return 0;
}
FILE *open_file(char *filename, char *mode)
{
FILE *fp = fopen(filename, mode);
if (!fp)
{
perror("打开失败");
return NULL;
}
return fp;
}
void gui_init(int x, int y)
{
system("title 贪吃蛇");
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
for(int i = 0; i < hige + 1; i ++)
{
movecursor( y + i, x);
for(int j = 0; j < wide + 1; j ++)
{
if(j == 0 || j == wide || i == 0 || i == hige)
printf("■");
else
printf(" ");
}
printf("\n");
}
}
void snake_init(SNAKE *snake)
{
snake->list[0].x = wide / 2;
snake->list[0].y = hige / 2;
snake->list[1].x = wide / 2 - 1;
snake->list[1].y = hige / 2;
snake->move_xy.x = 1;
snake->move_xy.y = 0;
snake->size = 2;
snake->score = 0;
snake->t = 1;
snake->speed = 200;
FILE *fp = open_file("./历史最高记录分数.txt","r");
fscanf(fp,"历史最高记录分数为%d",&(snake->higest_score));
fclose(fp);
}
void food_init(SNAKE *snake)
{
food_init:
srand(time(NULL));
snake->food.x = rand() % (wide - 2) + 1;
snake->food.y = rand() % (hige - 2) + 1;
if(snake->food.x - (snake->food.y * 2) && snake->t > 1)
{
snake->food.x = (rand() % (wide - 2) + 1) + wide + 10;
}
for(int i = 0; i < snake->size; i ++)
{
if(snake->food.x == snake->list[i].x && snake->food.y == snake->list[i].y)
goto food_init;
}
}
void gui_show(SNAKE *snake)
{
for(int i = 0; i < snake->size; i ++)
{
movecursor(snake->list[i].y,snake->list[i].x);
if(i == 0)
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY);
printf("@");
}
else
{
color(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
printf("●");
}
}
movecursor(snake->food.y,snake->food.x);
printf("$");
movecursor(snake->tail.y,snake->tail.x);
printf(" ");
movecursor(hige + 1,0);
printf("当前得分为%d\t历史最高记录分数为%d\n",snake->score,snake->higest_score);
if(snake->t == 1)
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY);
movecursor(hige/2 - 1,wide + 1);
printf("当前得分");
movecursor(hige/2,wide + 1);
printf("为%d时",open_score);
movecursor(hige/2 + 1,wide + 1);
printf("通道打开");
}
else if(snake->t > 1)
{
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);并加亮
movecursor(hige/2 - 1,wide + 1);
printf("■■■■■■■■■");
movecursor(hige/2,wide);
printf(" ",open_score);
movecursor(hige/2 + 1,wide + 1);
printf("■■■■■■■■■");
}
}
void movecursor(int x, int y)
{
COORD coord;
coord.X = y;
coord.Y = x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void start_game(SNAKE *snake)
{
while (snake->t == 1 && (snake->list[0].x < wide && snake->list[0].x > 0 && snake->list[0].y < hige && snake->list[0].y > 0))
{
control_snake(snake);
move_snake(snake);
gui_show(snake);
snake_eat_body(snake);
snake_eat_food(snake);
Sleep(snake->speed);
}
while (snake->t > 1 && (snake->list[0].x > 0 && snake->list[0].y < hige && snake->list[0].y > 0)
|| (snake->t > 1 && (snake->list[0].x < (2 * wide + 10) && snake->list[0].y < hige && snake->list[0].y > 0)))
{
if((snake->list[0].x == wide && snake->list[0].y != hige / 2) || (snake->list[0].x == wide + 10 && snake->list[0].y != hige / 2))
break;
control_snake(snake);
move_snake(snake);
gui_show(snake);
snake_eat_body(snake);
snake_eat_food(snake);
Sleep(snake->speed);
}
end_game(snake);
}
void move_snake(SNAKE *snake)
{
snake->tail.x = snake->list[snake->size - 1].x;
snake->tail.y = snake->list[snake->size - 1].y;
for(int i = snake->size - 1; i > 0; i--)
{
snake->list[i].x = snake->list[i - 1].x;
snake->list[i].y = snake->list[i - 1].y;
}
snake->list[0].x += snake->move_xy.x;
snake->list[0].y += snake->move_xy.y;
}
void control_snake(SNAKE *snake)
{
char key = 0;
while (_kbhit())
{
key = _getch();
}
switch (key)
{
case 'a':
if(snake->move_xy.x == 1)
break;
snake->move_xy.x = -1;
snake->move_xy.y = 0;
break;
case 'w':
if(snake->move_xy.y == 1)
break;
snake->move_xy.x = 0;
snake->move_xy.y = -1;
break;
case 's':
if(snake->move_xy.y == -1)
break;
snake->move_xy.x = 0;
snake->move_xy.y = 1;
break;
case 'd':
if(snake->move_xy.x == -1)
break;
snake->move_xy.x = 1;
snake->move_xy.y = 0;
break;
case space:
system("pause>nul");
break;
}
}
void end_game(SNAKE *snake)
{
if(snake->score > snake->higest_score)
{
snake->higest_score = snake->score;
system("cls");
color(FOREGROUND_BLUE | FOREGROUND_INTENSITY);
movecursor( hige / 2,0);
printf("恭喜你打破最高记录\n游戏结束得分为%d\t历史最高记录分数为%d\n是否再来一局?(y/n):",snake->score,snake->higest_score);
FILE *fp = open_file("./历史最高记录分数.txt","w");
fprintf(fp,"历史最高记录分数为%d\n",snake->higest_score);
fclose(fp);
}
else
{
system("cls");
color(FOREGROUND_BLUE | FOREGROUND_INTENSITY);
movecursor( hige / 2,0);
printf("请继续加油!\n游戏结束得分为%d\t历史最高记录分数为%d\n是否再来一局?(y/n):",snake->score,snake->higest_score);
}
int i = 0;
while (1) //询问玩家是否再来一局
{
char ch;
scanf("%c", &ch);
if (ch == 'y' || ch == 'Y')
{
i = 0;
system("cls");
main();
}
else if (ch == 'n' || ch == 'N')
{
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
movecursor( 0, 0);
system("cls");
Sleep(100);
exit(0);
}
else
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY);
movecursor( hige / 2 + 3 + i,0);
i ++;
printf("选择错误,请再次选择(y/n):");
}
}
}
void snake_eat_body(SNAKE *snake)
{
for(int i = 1; i < snake->size; i ++)
{
if(snake->list[0].x == snake->list[i].x && snake->list[0].y == snake->list[i].y)
{
end_game(snake);
}
}
}
void snake_eat_food(SNAKE *snake)
{
if(snake->list[0].x == snake->food.x && snake->list[0].y == snake->food.y)
{
food_init(snake);
snake->size ++;
snake->t = ((snake->score += (10 * snake->t)) / 100) + 1;
snake->speed = (snake->speed >= 50) ? (snake->speed - 5) : 50;
}
}
void hide_console_cursor()
{
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(consoleHandle, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(consoleHandle, &cursorInfo);
}
void color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
}
解析
变量
#define wide 45//墙宽
#define hige 20//墙高
#define open_score 100//通道打开的分数阈值
#define space 32 //暂停
typedef struct body
{
int x;//坐标
int y;
}BODY;
typedef struct snake
{
BODY list[wide*hige];//蛇
int size;//蛇长
BODY food;//食物的坐标
BODY move_xy;//移动方向
BODY tail;//蛇的尾部
int score;//当前分数
int higest_score;//最高分数
char t;//改变计分方式、判断条件等等
unsigned char speed;//刷屏速度
}SNAKE;
主函数
// 主函数,程序入口
int main()
{
SNAKE *snake = (SNAKE *)malloc(sizeof(SNAKE));//申请内存空间
if (snake == NULL) {
// 处理内存分配失败的情况
fprintf(stderr, "内存分配失败。\n");
return 1;
}
hide_console_cursor();//隐藏光标
gui_init( 0, 0);//gui界面显示(墙)
gui_init( wide + 10, 0);
snake_init(snake);//参数初始化
food_init(snake);//食物随机生成
gui_show(snake);//gui界面显示(蛇、食物等)
start_game(snake);//游戏开始
free(snake);//释放申请的内存
return 0; // 程序正常退出
}
文件操作(用于记录最高历史记录)
FILE *open_file(char *filename, char *mode)
{
FILE *fp = fopen(filename, mode); // 使用指定模式打开文件
if (!fp)
{
perror("打开失败"); // 如果打开失败,打印错误信息
return NULL;
}
return fp; // 返回文件指针
}
墙初始化
void gui_init(int x, int y)
{
// 设置控制台窗口标题为"贪吃蛇"
system("title 贪吃蛇");
// 设置文本颜色为黄色并加亮
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
// 绘制游戏界面
for(int i = 0; i < hige + 1; i ++) // 循环绘制每一行
{
movecursor(y + i, x); // 移动光标到指定位置(x, y + i)
for(int j = 0; j < wide + 1; j ++) // 循环绘制每一列
{
if(j == 0 || j == wide || i == 0 || i == hige)
printf("■"); // 如果是边界位置,打印实心方块
else
printf(" "); // 否则打印空格,用于填充内部空白区域
}
printf("\n"); // 每行绘制完成后换行
}
}
蛇初始化
void snake_init(SNAKE *snake)
{
// 设置蛇的初始位置
snake->list[0].x = wide / 2; // 蛇头的初始 x 坐标为界面宽度的一半
snake->list[0].y = hige / 2; // 蛇头的初始 y 坐标为界面高度的一半
snake->list[1].x = wide / 2 - 1; // 蛇尾的初始 x 坐标为蛇头左侧
snake->list[1].y = hige / 2; // 蛇尾的初始 y 坐标与蛇头一致
// 设置蛇的初始移动方向
snake->move_xy.x = 1; // 蛇初始向右移动
snake->move_xy.y = 0;
// 初始化蛇的长度、分数、时间、速度
snake->size = 2; // 蛇初始长度为2(包括头部和尾部)
snake->score = 0; // 分数初始为0
snake->t = 1; // 时间初始为1
snake->speed = 200; // 初始速度为200
// 从文件中读取历史最高分数
FILE *fp = open_file("./历史最高记录分数.txt", "r"); // 打开存放历史最高分数的文件
fscanf(fp, "历史最高记录分数为%d", &(snake->higest_score)); // 读取历史最高分数
fclose(fp); // 关闭文件
}
食物初始化
void food_init(SNAKE *snake)
{
food_init:
// 使用当前时间作为随机种子
srand(time(NULL));
// 随机生成食物的位置,确保不超出屏幕边界
snake->food.x = rand() % (wide - 2) + 1;
snake->food.y = rand() % (hige - 2) + 1;
// 检查通道是否打开,让食物可以在另外一个区域位置生成
//确保两个区域都有可能生成食物
if (snake->food.x - (snake->food.y * 2) && snake->t > 1)
{
snake->food.x = (rand() % (wide - 2) + 1) + wide + 10;
}
// 确保食物不与贪吃蛇的身体重叠,如果重叠则重新生成食物位置
for (int i = 0; i < snake->size; i++)
{
if (snake->food.x == snake->list[i].x && snake->food.y == snake->list[i].y)
goto food_init;
}
}
GUI界面显示蛇、食物等
void gui_show(SNAKE *snake)
{
// 遍历蛇的每个身体部分
for(int i = 0; i < snake->size; i ++)
{
// 将光标移动到蛇身体的当前位置
movecursor(snake->list[i].y, snake->list[i].x);
// 如果是蛇头部分(第一个节点)
if(i == 0)
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY); // 设置文本为红色并加亮
printf("@"); // 打印蛇头(使用@表示)
}
else
{
color(FOREGROUND_GREEN | FOREGROUND_INTENSITY); // 设置文本为绿色并加亮
printf("●"); // 打印蛇身体的其他部分(使用●表示)
}
}
// 将光标移动到食物的位置
movecursor(snake->food.y, snake->food.x);
printf("$"); // 打印食物(使用$表示)
// 将光标移动到蛇尾的位置,并打印空格来清除之前的蛇尾(移动效果)
movecursor(snake->tail.y, snake->tail.x);
printf(" ");
// 将光标移动到界面底部,并显示当前得分和历史最高分数
movecursor(hige + 1, 0);
printf("当前得分为%d\t历史最高记录分数为%d\n", snake->score, snake->higest_score);
// 如果分数没有到达100,显示相应的信息
if(snake->t == 1)
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY); // 设置文本为红色并加亮
movecursor(hige / 2 - 1, wide + 1); // 将光标移动到特定位置
printf("当前得分");
movecursor(hige / 2, wide + 1); // 继续在下一行打印
printf("为%d时", open_score); // 打印特定的得分值
movecursor(hige / 2 + 1, wide + 1); // 继续在下一行打印
printf("通道打开"); // 打印特定的状态信息
}
// 如果分数大于100(t大于1),显示通道
else if(snake->t > 1)
{
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); // 设置文本为黄色并加亮
movecursor(hige / 2 - 1, wide + 1); // 将光标移动到特定位置
printf("■■■■■■■■■");
movecursor(hige / 2, wide); // 继续在下一行打印
printf(" "); // 打印空白以覆盖之前的内容
movecursor(hige / 2 + 1, wide + 1); // 继续在下一行打印
printf("■■■■■■■■■"); // 打印特定的形状(方块)
}
}
移动光标
void movecursor(int x, int y)
{
COORD coord; // 声明一个 COORD 结构体变量 coord。COORD 是 Windows API 中的结构体,用于保存坐标对。
coord.X = y; // 将 coord 结构体的 X 坐标设置为 y 的值。在这里,'y' 表示列号。
coord.Y = x; // 将 coord 结构体的 Y 坐标设置为 x 的值。这里的 'x' 表示行号。
// SetConsoleCursorPosition 是 Windows API 中的函数,用于将光标移动到屏幕上的指定位置。
// GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出的句柄,代表控制台屏幕缓冲区。
// 它作为 SetConsoleCursorPosition 的第一个参数,用于指定光标应该移动到的位置。
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
// 这个函数调用实际上将光标移动到由坐标 (x, y) 指定的位置。
}
游戏逻辑
void start_game(SNAKE *snake)
{
// 第一个 while 循环:100分之前
while (snake->t == 1 && (snake->list[0].x < wide && snake->list[0].x > 0 && snake->list[0].y < hige && snake->list[0].y > 0))
{
control_snake(snake); // 控制蛇的移动方向
move_snake(snake); // 移动蛇的位置
gui_show(snake); // 更新图形界面显示
snake_eat_body(snake); // 检查蛇是否吃到了自己的身体
snake_eat_food(snake); // 检查蛇是否吃到了食物
Sleep(snake->speed); // 控制游戏速度
}
// 第二个 while 循环:100分之后
while (snake->t > 1 && (snake->list[0].x > 0 && snake->list[0].y < hige && snake->list[0].y > 0)
|| (snake->t > 1 && (snake->list[0].x < (2 * wide + 10) && snake->list[0].y < hige && snake->list[0].y > 0)))
{
// 如果蛇头碰到了墙,跳出循环,游戏结束
if((snake->list[0].x == wide && snake->list[0].y != hige / 2) || (snake->list[0].x == wide + 10 && snake->list[0].y != hige / 2))
break;
control_snake(snake); // 控制蛇的移动方向
move_snake(snake); // 移动蛇的位置
gui_show(snake); // 更新图形界面显示
snake_eat_body(snake); // 检查蛇是否吃到了自己的身体
snake_eat_food(snake); // 检查蛇是否吃到了食物
Sleep(snake->speed); // 控制游戏速度
}
end_game(snake); // 游戏结束,调用结束游戏的函数
}
蛇的移动
void move_snake(SNAKE *snake)
{
// 将蛇尾的位置保存下来,用于后续的移动操作
snake->tail.x = snake->list[snake->size - 1].x;
snake->tail.y = snake->list[snake->size - 1].y;
// 从蛇尾开始,每个身体块的位置更新为前一个身体块的位置
for (int i = snake->size - 1; i > 0; i--)
{
snake->list[i].x = snake->list[i - 1].x;
snake->list[i].y = snake->list[i - 1].y;
}
// 根据蛇头的移动方向更新蛇头的位置
snake->list[0].x += snake->move_xy.x;
snake->list[0].y += snake->move_xy.y;
}
键盘控制 (wasd与空格键暂停)
void control_snake(SNAKE *snake)
{
char key = 0; // 定义变量 key,用于存储按下的键值
while (_kbhit()) // 当有键盘输入时进入循环
{
key = _getch(); // 获取键盘输入的字符
}
switch (key) // 根据输入的键值进行相应的操作
{
case 'a':
if (snake->move_xy.x == 1)
break; // 如果蛇当前正在向右移动,则忽略向左移动的命令
snake->move_xy.x = -1; // 设置蛇向左移动
snake->move_xy.y = 0;
break;
case 'w':
if (snake->move_xy.y == 1)
break; // 如果蛇当前正在向下移动,则忽略向上移动的命令
snake->move_xy.x = 0; // 设置蛇向上移动
snake->move_xy.y = -1;
break;
case 's':
if (snake->move_xy.y == -1)
break; // 如果蛇当前正在向上移动,则忽略向下移动的命令
snake->move_xy.x = 0; // 设置蛇向下移动
snake->move_xy.y = 1;
break;
case 'd':
if (snake->move_xy.x == -1)
break; // 如果蛇当前正在向左移动,则忽略向右移动的命令
snake->move_xy.x = 1; // 设置蛇向右移动
snake->move_xy.y = 0;
break;
case space: // 空格键暂停游戏的情况
system("pause>nul"); // 调用系统命令暂停程序运行,按任意键继续
break;
}
}
结束游戏
void end_game(SNAKE *snake)
{
// 如果当前得分高于历史最高分,则更新历史最高分数
if (snake->score > snake->higest_score)
{
snake->higest_score = snake->score;
system("cls"); // 清空控制台屏幕
color(FOREGROUND_BLUE | FOREGROUND_INTENSITY); // 设置文本为蓝色并加亮
movecursor(hige / 2, 0); // 移动光标到指定位置
printf("恭喜你打破最高记录\n游戏结束得分为%d\t历史最高记录分数为%d\n是否再来一局?(y/n):", snake->score, snake->higest_score);
// 打开文件以写入历史最高分数
FILE *fp = open_file("./历史最高记录分数.txt", "w");
fprintf(fp, "历史最高记录分数为%d\n", snake->higest_score);
fclose(fp);
}
else
{
system("cls"); // 清空控制台屏幕
color(FOREGROUND_BLUE | FOREGROUND_INTENSITY); // 设置文本为蓝色并加亮
movecursor(hige / 2, 0); // 移动光标到指定位置
printf("请继续加油!\n游戏结束得分为%d\t历史最高记录分数为%d\n是否再来一局?(y/n):", snake->score, snake->higest_score);
}
int i = 0;
while (1) // 询问玩家是否再来一局
{
char ch;
scanf("%c", &ch); // 读取用户输入的字符
if (ch == 'y' || ch == 'Y')
{
i = 0;
system("cls"); // 清空控制台屏幕
main(); // 调用主函数以重新开始游戏
}
else if (ch == 'n' || ch == 'N')
{
color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); // 设置文本为白色并加亮
movecursor(0, 0); // 移动光标到指定位置
system("cls"); // 清空控制台屏幕
Sleep(100); // 等待100毫秒
exit(0); // 退出程序
}
else
{
color(FOREGROUND_RED | FOREGROUND_INTENSITY); // 设置文本为红色并加亮
movecursor(hige / 2 + 3 + i, 0); // 移动光标到指定位置
i++;
printf("选择错误,请再次选择(y/n):");
}
}
}
吃到自己
void snake_eat_body(SNAKE *snake)
{
for(int i = 1; i < snake->size; i ++) // 从蛇身体的第二节开始遍历到最后一节
{
if(snake->list[0].x == snake->list[i].x && snake->list[0].y == snake->list[i].y)
{
end_game(snake); // 如果蛇头与任意一节身体重合,则调用游戏结束函数
}
}
}
吃到食物
void snake_eat_food(SNAKE *snake)
{
if(snake->list[0].x == snake->food.x && snake->list[0].y == snake->food.y)
{
food_init(snake); // 初始化新的食物位置
snake->size ++; // 增加蛇的长度
snake->t = ((snake->score += (10 * snake->t)) / 100) + 1; // 更新分数与成长速率
snake->speed = (snake->speed >= 50) ? (snake->speed - 5) : 50; // 调整蛇的移动速度
}
}
隐藏光标
void hide_console_cursor()
{
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(consoleHandle, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(consoleHandle, &cursorInfo);
}
颜色
void color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); // 设置控制台输出文本的颜色属性为 c
}