环境:windows系统,VS2019编译器

项目分析

贪吃蛇游戏机制是通过控制蛇上下左右移动并吃到食物得分

障碍物随机生成,蛇头碰到墙壁、障碍物或者碰到蛇身就游戏结束

食物随机生成,蛇吃到食物之后蛇身变长,蛇速加快

代码运行后可自定义障碍物数量、蛇初始速度、蛇身初识长度

主函数界面有开始游戏、游戏设置、退出游戏三个选项,通过键盘上下键控制选择

读写文件记录并显示当前得分和历史最高分

项目效果

snake.h头文件

头文件包含、宏定义、外部变量声明、函数声明
#pragma once #pragma warning (disable:4996) //消除警告 #include <stdio.h> #include
<stdlib.h> #include <Windows.h> #include <string.h> #include <time.h> #include
<conio.h> #define ROW 22 #define COL 42 #define EMPTY 0 #define WALL 1 #define
FOOD 2 #define HEAD 3 #define BODY 4 #define UP 72 #define DOWN 80 #define LEFT
75 #define RIGHT 77 #define SPACE 32 #define ESC 27 #define ENTER 13 #define
RATE 3000 //速度 typedef struct Body { int x, y; }Body; typedef struct Snake {
int len; int x, y; Body* body; }Snake; extern int g_map[ROW][COL]; extern int
g_max, g_grade;         //历史最高分,当前得分 extern Snake snake;    extern int g_len;  
  //蛇初识长度 extern int g_wall, g_food, g_snake; //颜色 extern int g_obstacle;    
//障碍物数量 extern int g_rate;     //速度 void CursorJump(int x, int y); //光标跳转 void
Color(int x);         //颜色设置 void ReadGrade();             //从文件读取最高分 void
WriteGrade();             //更新最高分到文件 void InitMap();             //初始化地图 void
InitSnake();             //初始化蛇 void RandFood();               //随机生成食物 void
RandObstacle();             //随机生成障碍物 void DrawSnake(int flag);         //打印蛇
void MoveSnack(int x, int y); //移动蛇 void JudgeGame(int x, int y); //判断得分与结束
void Run(int x, int y); //执行按键 void Game();             //游戏流程 void MainGame();
            //主函数游戏流程 void Main();             //主函数直接调用,代替主函数执行代码,方便回调

snake.c源文件

文件读写

读档和存档游戏的最高分记录
#include "snake.h" void CursorJump(int x, int y) { COORD pos; //定义光标位置的结构体变量
pos.X = x; pos.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),
pos); //设置光标位置 } void Color(int x) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x); //设置颜色 // 6——土黄色
7——白色 10——绿色 12——红色 } void ReadGrade() { FILE* pf = fopen("snack.txt", "rb");
if (pf == NULL)     { pf = fopen("snack.txt", "wb"); fwrite(&g_max,
sizeof(int), 1, pf); } fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头 fread(&g_max,
sizeof(int), 1, pf); fclose(pf); pf = NULL; } void WriteGrade() { FILE* pf =
fopen("snack.txt", "wb"); if (pf == NULL)     { perror("writeGrade::fopen");
exit(1); } fwrite(&g_grade, sizeof(int), 1, pf); fclose(pf); pf = NULL; }

初始化

初始化地图、蛇
void InitMap() { Color(g_wall); int i, j; for (i = 0; i < ROW; ++i)     { for
(j = 0; j < COL; ++j)         { if (j == 0 || j == COL - 1)             {
g_map[i][j] = WALL; CursorJump(2 * j, i); printf("■"); } else if (i == 0 || i
== ROW - 1)             { CursorJump(2 * j, i); g_map[i][j] = WALL;
printf("■"); } else             { g_map[i][j] = EMPTY; CursorJump(2 * j, i);
printf(" "); } } } Color(7); CursorJump(0, ROW); printf("当前得分: %d", g_grade);
CursorJump(2 * COL - 18, ROW); printf("历史最高分: %d\0", g_max); } void InitSnake()
{ snake.len = g_len; //蛇身初识长度 snake.x = COL / 2; snake.y = ROW / 2; //蛇头u坐标
g_map[snake.y][snake.x] = HEAD; snake.body = (Body*)malloc(sizeof(Body) * ROW *
COL); if (snake.body == NULL)     { perror("InitSnack::malloc"); exit(1); }
memset(snake.body, 0, sizeof(Body) * ROW * COL); int i = 0; while (i < g_len)  
  { snake.body[i].x = snake.x - (i + 1); snake.body[i].y = snake.y;
g_map[snake.body[i].y][snake.body[i].x] = BODY; ++i; } //初始化蛇身 g_rate = RATE;
//初始化速度 }

随机生成

随机生成食物和障碍物,障碍物只是游戏开始时随机生成一次
void RandFood() { int row, col; do     { row = rand() % ROW; col = rand() %
COL; } while (g_map[row][col] != EMPTY); g_map[row][col] = FOOD; Color(g_food);
CursorJump(2 * col, row); printf("●"); } void RandObstacle() { Color(8); //灰色
int obstacle = g_obstacle; int row, col; while (obstacle)     { do         {
row = rand() % ROW; col = rand() % COL; } while (g_map[row][col] != EMPTY);
g_map[row][col] = WALL; CursorJump(2 * col, row); printf("■"); --obstacle; } }

打印蛇与移动蛇

打印蛇分为覆盖和打印,覆盖是指将蛇尾用空格覆盖

蛇每移动一次都是要经过一次覆盖一次打印,先将蛇尾覆盖为空格,再将蛇前移之后打印出来

覆盖蛇的时候注意,蛇吃到食物之后身体变长,此时蛇尾不覆盖
void DrawSnake(int flag) { if (flag == 1)     { //打印蛇 Color(g_snake);
CursorJump(2 * snake.x, snake.y); printf("■"); //打印蛇头 int i = 0; while (i <
snake.len)         { CursorJump(2 * snake.body[i].x, snake.body[i].y);
printf("□"); ++i; } //打印蛇身 } else     { //覆盖蛇 if (snake.body[snake.len - 1].x
!= 0)         { CursorJump(2 * snake.body[snake.len - 1].x,
snake.body[snake.len - 1].y); printf(" "); //将蛇尾覆盖为空格 } } } void MoveSnack(int
x, int y) { DrawSnake(0); //覆盖蛇 int tail = snake.len - 1;
g_map[snake.body[tail].y][snake.body[tail].x] = EMPTY; //将蛇尾标记为空
g_map[snake.y][snake.x] = BODY; //蛇移动后蛇头变为蛇身 while (tail)     {
snake.body[tail].x = snake.body[tail - 1].x; snake.body[tail].y =
snake.body[tail - 1].y; --tail; } snake.body[0].x = snake.x; snake.body[0].y =
snake.y; snake.x += x; snake.y += y; //蛇头位置更新 DrawSnake(1); //打印蛇 }

判断得分或游戏结束

若蛇头的下一步是食物,则得分,蛇身变长,蛇速变快

若蛇头的下一步是墙或者障碍物或者蛇身,则游戏结束,根据得分判断是否打破纪录

游戏结束之后询问玩家是否继续游戏
void JudgeGame(int x, int y) { if (g_map[snake.y + y][snake.x + x] == FOOD)  
  { //蛇头下一步位置是食物 //得分,蛇身变长,蛇速变快 ++snake.len; g_grade += 10; if (g_rate > 1000)
g_rate -= 50; Color(7); CursorJump(0, ROW); printf("当前得分: %d", g_grade);
//重新打印得分 RandFood(); //重新生成食物 } else if (g_map[snake.y + y][snake.x + x] ==
WALL || g_map[snake.y + y][snake.x + x] == BODY)     { Sleep(1000);
//暂停1秒,给玩家反应时间 system("cls"); Color(7); CursorJump(2 * COL / 3, ROW / 2 - 3);
if (g_grade > g_max)         { printf("恭喜你创下新纪录: %d", g_grade); WriteGrade(); }
else         { printf("请继续加油,本次得分为: %d", g_grade); } CursorJump(2 * COL / 3,
ROW / 2); printf("GAME OVER"); while (1)         { //询问玩家是否再来一局 char input;
CursorJump(2 * COL / 3, ROW / 2 + 3); printf(" "); CursorJump(2 * COL / 3, ROW
/ 2 + 3); printf("再次挑战?(y/n) "); scanf("%c", &input); switch (input)          
  { case 'y': case 'Y': system("cls"); MainGame(); break; case 'n': case 'N':
Main(); break; default: break; } } } }

按键执行

按键执行调用 conio.h 里面的kbhit() 函数,通过循环控制,若无按键则蛇自动走下一步

kbhit() 函数可能会引发编译器的报错,用#pragma warning (disable:4996) //消除警告

键盘敲击,则执行键盘敲击的操作,若无键盘敲击则蛇按照方向往前走
void Run(int x, int y) { int t = 0; while (1) { if (t == 0) t = g_rate; while
(--t)         { //键盘被敲击,退出循环 if (kbhit() != 0) break; } if (t == 0)         {
//键盘未敲击,判断得分与移动蛇 JudgeGame(x, y); MoveSnack(x, y); } else         { break;
//键盘被敲击,返回Game()函数 } } }

游戏操作

wsad、上下左右箭头:控制蛇移动的上下左右方向

space空格键:游戏暂停

r、R:重新开始

ESC:退出游戏

每一个键位的键值对在头文件进行宏定义
void Game() { int dir = RIGHT; //默认初识方向向右 int tmp = dir; //记录蛇移动的方向 while (1)
    { switch (dir)         { case 'w': case 'W': case UP: Run(0, -1);
//向上移动,x不变,y-1 tmp = UP; break; case 's': case 'S': case DOWN: Run(0, 1); tmp =
DOWN; break; case 'a': case 'A': case LEFT: Run(-1, 0); tmp = LEFT; break; case
'd': case 'D': case RIGHT: Run(1, 0); tmp = RIGHT; break; case SPACE:
system("pause>nul"); //暂停后按任意按键继续 break; case ESC: system("cls"); Color(7);
//颜色设置为白色 CursorJump(COL - 8, ROW / 2); printf(" 游戏结束 "); CursorJump(COL - 8,
ROW / 2 + 2); exit(0); case 'r': case 'R': system("cls"); MainGame(); } dir =
getch(); switch (dir)         { case 'w': case 'W': case UP: case 's': case
'S': case DOWN: if (tmp == UP || tmp == DOWN) dir = tmp; break; case 'a': case
'A': case LEFT: case 'd': case 'D': case RIGHT: if (tmp == LEFT || tmp ==
RIGHT) dir = tmp; break; case SPACE: case ESC: case 'r': case 'R': break;
//这是个按键无需调整 default: dir = tmp; } } }

游戏流程

*
读取历史最高分

*
初始化地图、蛇

*
随机生成食物、障碍物

*
打印蛇

*
游戏操作
void MainGame() { ReadGrade(); //读取历史最高分 InitMap(); InitSnake(); RandFood();
RandObstacle(); DrawSnake(1); Game(); }

自定义游戏属性

自定义障碍物数量、蛇速、蛇身长度

尽量保证合理性
void GameSetting() { system("cls"); CursorJump(2 * COL / 3, ROW / 2 - 5);
printf("设置障碍物数量 ( <=50 ): "); scanf("%d", &g_obstacle); while (g_obstacle > 50)
    { CursorJump(2 * COL / 3, ROW / 2 + 2); printf(" "); CursorJump(2 * COL /
3, ROW / 2 - 3); printf("障碍物过多,请重新设置: "); scanf("%d", &g_obstacle); }
CursorJump(2 * COL / 3, ROW / 2); printf("设置速度( >=1000 ): "); scanf("%d",
&g_rate); while (g_rate < 1000)     { CursorJump(2 * COL / 3, ROW / 2 + 2);
printf(" "); CursorJump(2 * COL / 3, ROW / 2 + 2); printf("速度过快,请重新设置: ");
scanf("%d", &g_rate); } CursorJump(2 * COL / 3, ROW / 2 + 5); printf("设置蛇身初始长度(
>= 1): "); scanf("%d", &g_len); while (g_len < 1)     { CursorJump(2 * COL / 3,
ROW / 2 + 2); printf(" "); CursorJump(2 * COL / 3, ROW / 2 + 7);
printf("蛇身太短,请重新设置: "); scanf("%d", &g_rate); } Main(); //回到主程序函数 }

初始界面设计
void PrintFrame(int col, int row) { system("cls"); CursorJump(col, ROW / 2 -
5); printf("开始游戏"); CursorJump(col, ROW / 2); printf("游戏设置"); CursorJump(col,
ROW / 2 + 5); printf("退出游戏"); CursorJump(col - 2, row - 1);
printf("*----------*"); CursorJump(col - 2, row); printf("| "); CursorJump(col
+ 8, row); printf(" |"); CursorJump(col - 2, row + 1); printf("*----------*");
CursorJump(2 * COL - 14, ROW - 1); printf(" ↑↓ 选择"); CursorJump(2 * COL - 14,
ROW); printf("Enter 确认"); } void Main() { srand((unsigned int)time(NULL));
system("title 贪吃蛇"); system("mode con cols=84 lines=23"); //设置终端窗口大小
CONSOLE_CURSOR_INFO curInfo; //光标信息结构体变量 curInfo.dwSize = 1; curInfo.bVisible =
FALSE; //光标光标隐藏不可见 SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&curInfo); //设置光标信息 Color(7); int col = 2 * COL / 3 + 5, row = ROW / 2 - 5;
PrintFrame(col, row); char input; while (1)     { while (1)         { if
(kbhit() != 0) break; } input = getch(); switch (input) { case UP: if (row ==
ROW / 2 - 5) row = ROW / 2 + 5; else row -= 5; PrintFrame(col, row); break;
case DOWN: if (row == ROW / 2 + 5) row = ROW / 2 - 5; else row += 5;
PrintFrame(col, row); break; case ENTER: if (row == ROW / 2 - 5)             {
MainGame(); } else if (row == ROW / 2)             { GameSetting(); } else    
        { system("cls"); CursorJump(2 * COL / 3 + 5, ROW / 2); printf("退出游戏");
CursorJump(0, ROW); exit(0); } break; default: break; } } }

main.c源文件
#include "snake.h" int g_map[ROW][COL]; int g_max = 0, g_grade = 0; Snake
snake; int g_len = 2; //蛇初识长度默认为2 int g_wall = 6, g_food = 12, g_snake = 10;
//默认颜色设置 int g_obstacle = 5; //默认障碍物数量 int g_rate = RATE;
//默认初始速度为3000,rate值越小速度越快,rate >= 2000 int main() { Main(); return 0; }

技术
今日推荐
PPT
阅读数 135
下载桌面版
GitHub
百度网盘(提取码:draw)
Gitee
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:766591547
关注微信