环境: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; }