环境:windows系统,VS2019编译器
贪吃蛇游戏机制是通过控制蛇上下左右移动并吃到食物得分
障碍物随机生成,蛇头碰到墙壁、障碍物或者碰到蛇身就游戏结束
食物随机生成,蛇吃到食物之后蛇身变长,蛇速加快
代码运行后可自定义障碍物数量、蛇初始速度、蛇身初识长度
主函数界面有开始游戏、游戏设置、退出游戏三个选项,通过键盘上下键控制选择
读写文件记录并显示当前得分和历史最高分
头文件包含、宏定义、外部变量声明、函数声明
#pragma once
#pragma warning (disable:4996) //消除警告
#include
#include
#include
#include
#include
#include
#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(); //主函数直接调用,代替主函数执行代码,方便回调
读档和存档游戏的最高分记录
#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;
}
}
}
#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;
}