首先说明:
1.
这个贪吃蛇代码只有在Windows中执行才会起效果
我用的是Windows系统中的VS2019编译器
2.
我们先给出贪吃蛇的完整代码,是为了让大家提起接下来往后仔细看完这篇博客的热情
3.
这个贪吃蛇代码的前置知识:
1.C语言:函数,结构体,枚举,指针,动态内存管理(free,malloc…),宏
2.数据结构:链表
1.温馨提示
想要执行这个代码,在VS2019中需要调整一下控制台的属性
我们先在VS2019中随意跑一段简单的hello world调出控制台来进行属性的调整
默认情况下:我们的控制台是这个样子的
我们需要修改一下这个控制台的属性
然后就会出现这个
只有这样,我们才可以更好的实现这个窗口
否则,同样的代码在这个控制台窗口下就会出现这种样子
而我们修改了之后的样子是这样的
所以我们才要去修改这个控制台窗口的属性
2.最终实现版本的样子
1.游戏开始-欢迎界面
2.游戏运行界面
3.游戏结束界面
4.选择是否继续玩
1.选择继续
输入Y/y并按下回车即可继续玩
然后回到游戏最开始
2.选择退出游戏
3.完整代码
大家可以先在自己的VS中执行一下玩一玩
1.Snake.h
#pragma once #include <stdio.h> #include <Windows.h> #include <locale.h> #include <stdbool.h> #include <stdlib.h> #include <time.h> #define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0) #define WALL L'□' #define BODY L'●' #define FOOD L'★' #define INIT_X 24 #define INIT_Y 6 typedef struct SnakeNode { struct SnakeNode* next; int x; int y; }SNode; enum Direction { UP, DOWN, LEFT, RIGHT }; enum GameState { OK, EXIT_NORMAL, KILL_BY_WALL, KILL_BY_SELF }; typedef struct Snake { SNode* _pSnake;//蛇头节点 SNode* _pFood;//食物 enum Direction _dir;//蛇移动的方向 enum GameState _state;//当前游戏状态 int _score;//当前得分 int _foodWeight;//每个食物的分数 int _sleepTime;//蛇的休息时间,影响加速和减速和暂停 }Snake; void SetPos(short x, short y); void GameStart(Snake* ps); void WelcomeToGame(); void CreateMap(); void InitSnake(Snake* ps); void CreateFood(Snake* ps); void GameRun(Snake* ps); void PrintHelpInfo(); void SnakeMove(Snake* ps); //判断是否撞墙 void IfKillByWall(Snake* ps,int x, int y); //判断是否咬到自己 void IfKillBySelf(Snake* ps, int x, int y); void EatFood(SNode* pNextNode, Snake* ps); void NoFood(SNode* pNextNode, Snake* ps); //暂停函数 void pause(); void GameEnd(Snake* ps);
2.Snake.c
#include "Snake.h" //设置控制台光标位置的函数 void SetPos(short x, short y) { COORD pos = { x,y }; HANDLE handle = NULL; //获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备 handle = GetStdHandle(STD_OUTPUT_HANDLE); //设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中 //调用SetConsoleCursorPosition函数将光标位置设置到指定的位置 SetConsoleCursorPosition(handle, pos); } //system("mode con cols=120 lines=35"); void WelcomeToGame() { SetPos(45, 12); printf("欢迎来到贪吃蛇小游戏"); SetPos(45, 18); system("pause"); system("cls"); SetPos(45, 12); printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速"); SetPos(45, 13); printf("加速能够得到更高的分数"); SetPos(45, 18); system("pause"); system("cls"); } void CreateMap() { //上 SetPos(0, 0); for (int i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (int i = 0; i <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (int i = 0; i <= 25; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (int i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } } void InitSnake(Snake* ps) { //初始化蛇身 for (int i = 0; i < 5; i++) { SNode* newnode = (SNode*)malloc(sizeof(SNode)); if (newnode == NULL) { perror("InitSnake():: malloc fail"); exit(-1); } newnode->next = NULL; newnode->x = INIT_X + 2 * i; newnode->y = INIT_Y; if (ps->_pSnake == NULL) { ps->_pSnake = newnode; } else { newnode->next = ps->_pSnake; ps->_pSnake = newnode; } } //打印蛇身 SNode* cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //初始化其他属性 ps->_dir = RIGHT; ps->_state = OK; ps->_foodWeight = 10; ps->_score = 0; ps->_sleepTime = 200; } void CreateFood(Snake* ps) { //创建食物 while (1) { //保证初始化到墙内 //x:2~54 int x = rand() % 53 + 2;//0~52+2 -> 2~54 //y:1~25 int y = rand() % 25 + 1;//0~24+1 -> 1~25 //保证初始化的x必须为偶数 if (x % 2 != 0) { continue; } //保证初始化时不跟蛇身重合 SNode* cur = ps->_pSnake; bool flag = false; while (cur) { //跟蛇身重合 if (cur->x == x && cur->y == y) { flag = true; break; } cur = cur->next; } //没有跟蛇身重合 if (!flag) { SNode* newnode = (SNode*)malloc(sizeof(SNode)); if (newnode == NULL) { perror("CreateFood():: malloc fail"); exit(-1); } newnode->next = NULL; newnode->x = x; newnode->y = y; ps->_pFood = newnode; break; } } //打印食物 SetPos(ps->_pFood->x, ps->_pFood->y); wprintf(L"%lc", FOOD); } void GameStart(Snake* ps) { WelcomeToGame(); CreateMap(); InitSnake(ps); CreateFood(ps); } void PrintHelpInfo() { SetPos(65, 17); printf("不能穿墙,不能咬到自己"); SetPos(65, 18); printf("用↑.↓.←.→ 分别控制蛇的移动"); SetPos(65, 19); printf("F1为加速,F2为减速"); SetPos(65, 20); printf("Esc: 退出游戏 space:暂停游戏"); SetPos(65, 22); printf("编写者:wzs"); } void EatFood(SNode* pNextNode, Snake* ps) { pNextNode->next = ps->_pSnake; ps->_pSnake = pNextNode; //打印蛇身 SNode* cur = ps->_pSnake; while (cur) { SetPos(cur->x,cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //释放食物节点 free(ps->_pFood); ps->_pFood = NULL; //加分 ps->_score += ps->_foodWeight; //创建新食物 CreateFood(ps); } void NoFood(SNode* pNextNode, Snake* ps) { pNextNode->next = ps->_pSnake; ps->_pSnake = pNextNode; //释放最后一个节点 SNode* cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); free(cur->next); cur->next = NULL; } //判断是否撞墙 void IfKillByWall(Snake* ps,int x,int y) { if (x == 0 || x == 56 || y == 0 || y == 26) { ps->_state = KILL_BY_WALL; } } //判断是否咬到自己 void IfKillBySelf(Snake* ps, int x, int y) { SNode* cur = ps->_pSnake->next; while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_state = KILL_BY_SELF; break; } cur = cur->next; } } void SnakeMove(Snake* ps) { //1.根据蛇头的坐标和方向,计算下一个节点的坐标 int x = ps->_pSnake->x; int y = ps->_pSnake->y; switch (ps->_dir) { case UP: y--; break; case DOWN: y++; break; case LEFT: x -= 2; break; case RIGHT: x += 2; break; } //创建下一个节点 SNode* pNextNode = (SNode*)malloc(sizeof(SNode)); if (pNextNode == NULL) { perror("SnakeMove():: malloc fail"); exit(-1); } pNextNode->x = x; pNextNode->y = y; pNextNode->next = NULL; //判断下一个是不是食物 if (x == ps->_pFood->x && y == ps->_pFood->y) { //下一个位置有食物 EatFood(pNextNode, ps); } //下一个位置没有食物 else { NoFood(pNextNode, ps); } //判断是否撞墙 IfKillByWall(ps, x, y); //判断是否咬到自己 IfKillBySelf(ps, x, y); } //暂停函数 void pause() { while (1) { Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } } void GameRun(Snake* ps) { PrintHelpInfo(); do { SetPos(65, 10); printf("得分: %d , 每个食物得分: %d ", ps->_score, ps->_foodWeight); //调整方向 if (KEY_PRESS(VK_UP) && ps->_dir != DOWN) { ps->_dir = UP; } else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP) { ps->_dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT) { ps->_dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT) { ps->_dir = RIGHT; } //暂停 else if (KEY_PRESS(VK_SPACE)) { pause(); } //Esc退出 else if (KEY_PRESS(VK_ESCAPE)) { ps->_state = EXIT_NORMAL; break; } //加速 else if (KEY_PRESS(VK_F1)) { //防止一直加速导致sleepTime<0出现bug if (ps->_sleepTime >= 50) { ps->_sleepTime -= 30; ps->_foodWeight += 2;//加速时食物的分值会增加 } } //减速 else if (KEY_PRESS(VK_F2)) { //防止一直减速导致程序运行太慢出现卡顿影响用户体验 if (ps->_sleepTime < 350) { ps->_sleepTime += 30; ps->_foodWeight -= 2; //防止太慢时食物得分减为负数 if (ps->_sleepTime == 350) { ps->_foodWeight = 1; } } } //蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快 Sleep(ps->_sleepTime); SnakeMove(ps); } while (ps->_state == OK); } void GameEnd(Snake* ps) { SetPos(65, 24); switch (ps->_state) { case EXIT_NORMAL: printf("玩家选择退出,游戏结束"); break; case KILL_BY_WALL: printf("很遗憾,撞墙了,游戏结束"); break; case KILL_BY_SELF: printf("很遗憾,咬到自己了,游戏结束"); break; default: break; } //释放蛇身的节点 SNode* cur = ps->_pSnake; while (cur) { SNode* del = cur; cur = cur->next; free(del); } }
3.test.c文件
#include "Snake.h" //初始化光标信息等 void Init() { HANDLE handle = NULL; //获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备 handle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo;//CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息 GetConsoleCursorInfo(handle, &CursorInfo);//检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息 CursorInfo.bVisible = false;//隐藏控制台光标 SetConsoleCursorInfo(handle, &CursorInfo);//设置指定控制台屏幕缓冲区的光标的大小和可见性 } int main() { setlocale(LC_ALL, ""); system("mode con cols=120 lines=35"); system("title 贪吃蛇"); Init(); char input = 0; do { Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL srand((unsigned int)time(NULL)); GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(65, 26); printf("要在玩一局吗?(Y/N)"); input = getchar(); getchar();//清理'\n' system("cls");//清屏 SetPos(45,12); if (input == 'n' || input == 'N') { printf("欢迎再次在玩"); } else if (input == 'Y' || input == 'y') { printf("游戏即将开始,祝您玩的开心"); SetPos(45, 14); system("pause"); system("cls");//清屏 } } while (input == 'y' || input == 'Y'); SetPos(32, 0); return 0; }