前言
这是我自己做的第五个小项目---贪吃蛇游戏(代码篇)。后期我会继续制作其他小项目并开源至博客上。
上一小项目是贪吃蛇游戏(必备知识篇),没看过的同学可以去看看:
有关贪吃蛇必备知识的小项目
实现代码
1. 下面代码直接复制即可运行。
2. 每个代码块都有简洁的总结和解释。
<snake.h>文件
#define _CRT_SECURE_NO_WARNINGS #include <locale.h> #include <stdio.h> #include <windows.h> #include <stdbool.h> #include <stdlib.h> #include <time.h> #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0) //设置键值 #define POS_X 24 //蛇初始位置 #define POS_Y 5 //蛇初始位置 //节点类型 typedef struct SnakeNode { //节点的坐标 int x; int y; //指向下一个节点的指针 struct SnakeNode* next; }SnakeNode; typedef struct SnakeNode* pSnakeNode; //贪吃蛇的信息 typedef struct Snake { pSnakeNode _pSnake;//贪吃蛇的身体节点 pSnakeNode _pFood;//食物节点 enum Direction _dir;//贪吃蛇的方向 enum Game_Statues _status;//贪吃蛇的状态 int _food_weight;//一个食物的分数 int _score;//总分数 int _sleep_time;//休息时间,即贪吃蛇的速度 }Snake; typedef struct Snake* pSnake; //方向 enum Direction { UP, DOWN, LEFT, RIGHT }; //状态 enum Game_Status { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; //游戏开始 void GameStart(pSnake ps); //欢迎函数 void WelcomeToGame(); //定位坐标 void SetPos(int x, int y); //打印地图 void CreateMap(); //初始化贪吃蛇 void InitSnake(pSnake ps); //创造食物 void CreateFood(pSnake ps); //游戏运行 void GameRun(pSnake ps); //打印帮助信息 void PrintHelpInfo(); //暂停设置 void Pause(); //实现贪吃蛇的移动 void SnakeMove(pSnake ps); //判断是否吃到食物 int NextIsFood(pSnakeNode pn, pSnake ps); //实现贪吃蛇吃食物并增长蛇身 void EatFood(pSnakeNode pn, pSnake ps); //吃到食物后使食物消失 void NoFood(pSnakeNode pn, pSnake ps); //被墙杀死 void KillByWall(pSnake ps); //被自己杀死 void KillBySelf(pSnake ps); //游戏结束 void GameEnd(pSnake ps);
<snake.c>文件
#include "snake.h" //游戏开始 void GameStart(pSnake ps) { //设置窗口 system("mode con cols=100 lines=30");//调整CMD行与列 system("title 贪吃蛇");//修改CMD的标题 //获取标准输出的句柄,存放在houtput中。 HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE); if (houtput == INVALID_HANDLE_VALUE) // 处理错误,例如输出错误信息 { fprintf(stderr, "Failed to get standard output handle.\n"); return; } //创建一个CONSOLE_CURSOR_INFO的结构体 CONSOLE_CURSOR_INFO CursorInfo; if (!GetConsoleCursorInfo(houtput, &CursorInfo)) // 处理错误,例如输出错误信息 { fprintf(stderr, "Failed to get console cursor info.\n"); return; } //隐藏控制台光标 CursorInfo.bVisible = false; if (!SetConsoleCursorInfo(houtput, &CursorInfo)) // 处理错误,例如输出错误信息 { fprintf(stderr, "Failed to set console cursor info.\n"); return; } //欢迎函数 WelcomeToGame(); //打印地图 CreateMap(); //初始化贪吃蛇 InitSnake(ps); //设置食物的位置 CreateFood(ps); } //欢迎函数 void WelcomeToGame() { SetPos(32, 13); printf("Welcome to the Classic Snake Game!"); SetPos(39, 22); system("pause");//打印完一个界面后直接暂停,直到点击继续 system("cls");//在清空界面,打印新的一个界面 SetPos(30, 13); wprintf(L"Navigate the Snake using ↑ ↓ ← →."); SetPos(33, 15); wprintf(L"Accelerate to earn more points."); SetPos(38, 23); system("pause"); system("cls"); } //定位坐标 void SetPos(int x, int y) { //获取标准输出的句柄,存放在houtput中 HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE); //设定我们想要定位的坐标 COORD pos = { x,y }; //将光标定位到pos2 SetConsoleCursorPosition(houtput, pos); } //打印地图 void CreateMap() { int i = 0; //打印上边界 for (i = 0; i < 29; i++) { wprintf(L"□"); } //打印下边界 SetPos(0, 26); for (i=0; i < 29; i++) { wprintf(L"□"); } //打印左边界 for (i = 1; i <= 25; i++) { SetPos(0, i); wprintf(L"□"); } //打印右边界 for (i = 1; i <= 25; i++) { SetPos(56, i); wprintf(L"□"); } } //初始化贪吃蛇 void InitSnake(pSnake ps) { int i = 0; for (i = 0; i < 5; i++)//开始贪吃蛇一共设置五个长度 { pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake error"); exit(1); } cur->next = NULL; cur->x = POS_X + 2 * i; cur->y = POS_Y; //头插法 if (ps->_pSnake == NULL) { ps->_pSnake = cur; } else { cur->next = ps->_pSnake; ps->_pSnake = cur; } } pSnakeNode cur = ps->_pSnake; while (cur != NULL) { SetPos(cur->x, cur->y); wprintf(L"●"); cur = cur->next; } //设置贪吃蛇的属性 ps->_dir = RIGHT; ps->_score = 0; ps->_food_weight = 10; ps->_sleep_time = 200; ps->_status = OK; } //设置食物的位置 void CreateFood(pSnake ps) { int x; int y; again: do { x = (rand()) % 53 + 2; y = (rand()) % 25 + 1; } while (x % 2 != 0); //不能与蛇身冲突 pSnakeNode cur = ps->_pSnake; while (cur != NULL) { if ((x == cur->x) && (y == cur->y)) { goto again; } cur = cur->next; } //创建食物节点 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreateFood error"); exit(1); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); wprintf(L"★"); ps->_pFood = pFood; } //游戏运行 void GameRun(pSnake ps) { //打印欢迎界面 PrintHelpInfo(); //游戏开始运行 do { //显示分数 SetPos(64, 7); printf("Current score: %d", ps->_score); SetPos(64, 8); printf("Current food score: %2d", ps->_food_weight); //判断玩家操作 if (KEY_PRESS(VK_UP) && (ps->_dir != UP)) { ps->_dir = UP; } else if (KEY_PRESS(VK_DOWN) && (ps->_dir != DOWN)) { ps->_dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && (ps->_dir != LEFT)) { ps->_dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && (ps->_dir != RIGHT)) { ps->_dir = RIGHT; } else if (KEY_PRESS(VK_SPACE)) { Pause();//暂停设置 } else if (KEY_PRESS(VK_ESCAPE)) { ps->_status = END_NORMAL; } else if (KEY_PRESS(VK_F3)) { if (ps->_sleep_time > 80) { ps->_sleep_time -= 30; ps->_food_weight += 2; } } else if (KEY_PRESS(VK_F4)) { if (ps->_food_weight > 2) { ps->_sleep_time += 30; ps->_food_weight -= 2; } } //实现贪吃蛇的移动 SnakeMove(ps); //通过短暂暂停来展现动态效果 Sleep(ps->_sleep_time); } while (ps->_status == OK); } //打印欢迎界面 void PrintHelpInfo() { SetPos(64, 10); wprintf(L"No wall passing. No self-biting."); SetPos(64, 12); wprintf(L"F3 to speed up. F4 to slow down."); SetPos(64, 14); wprintf(L"ESC to exit. Space to pause."); SetPos(74, 21); wprintf(L"Made by HSY,"); SetPos(66, 22); wprintf(L"a uniquely independent pig."); } //暂停设置 void Pause() { while (1) { Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } } //实现贪吃蛇的移动 void SnakeMove(pSnake ps) { pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNextNode == NULL) { perror("SnakeMove error"); exit(1); } switch (ps->_dir) { case UP: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y - 1; break; case DOWN: pNextNode->x = ps->_pSnake->x; pNextNode->y = ps->_pSnake->y + 1; break; case LEFT: pNextNode->x = ps->_pSnake->x - 2; pNextNode->y = ps->_pSnake->y; break; case RIGHT: pNextNode->x = ps->_pSnake->x + 2; pNextNode->y = ps->_pSnake->y; break; } if (NextIsFood(pNextNode,ps)) { EatFood(pNextNode, ps); } else { NoFood(pNextNode, ps); } KillByWall(ps);//被墙杀死 KillBySelf(ps);//被自己杀死 } //判断是否吃到食物 int NextIsFood(pSnakeNode pn, pSnake ps) { return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y); } //实现贪吃蛇吃食物并增长蛇身 void EatFood(pSnakeNode pn, pSnake ps) { ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood; free(pn); pn = NULL; pSnakeNode cur = ps->_pSnake; while (cur!=NULL) { SetPos(cur->x, cur->y); wprintf(L"●"); cur = cur->next; } ps->_score += ps->_food_weight; CreateFood(ps); } //吃到食物后使食物消失 void NoFood(pSnakeNode pn, pSnake ps) { pn->next = ps->_pSnake; ps->_pSnake = pn; pSnakeNode cur = ps->_pSnake; while (cur->next->next != NULL) { SetPos(cur->x, cur->y); wprintf(L"●"); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); free(cur->next); cur->next = NULL; } //被墙杀死 void KillByWall(pSnake ps) { if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26) { ps->_status = KILL_BY_WALL; } } //被自己杀死 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->_pSnake->next; while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_status = KILL_BY_SELF; break; } cur = cur->next; } } //游戏结束 void GameEnd(pSnake ps) { //判断哪种结束方式 switch (ps->_status) { case END_NORMAL: SetPos(17, 12); printf("You have ended the game."); break; case KILL_BY_WALL: SetPos(10, 12); printf("You ended the game by hitting a wall."); break; case KILL_BY_SELF: SetPos(10, 12); printf("You ended the game by self-collision."); break; } //清除贪吃蛇 pSnakeNode cur = ps->_pSnake; pSnakeNode prev = NULL; while (cur) { prev = cur; cur = cur->next; free(prev); } }
<test.c>文件
#include "snake.h" //游戏的主体进程 void test() { char ch; do { system("cls"); Snake snake = { 0 }; GameStart(&snake);//游戏开始 GameRun(&snake);//游戏运行 GameEnd(&snake);//游戏结束 SetPos(20, 15);//结束之后,询问是否再来一次 printf("Play again? (Y/N)"); ch = getchar();//用户输入一个字符并按回车后,实际上有两个字符进入了输入缓冲区:用户输入的字符和随后的换行符。第一个 getchar() 会读取用户输入的字符,而第二个 getchar() 则用来读取(并丢弃)换行符。 getchar(); } while (ch == 'Y'|| ch == 'y'); SetPos(0, 28);//如果游戏结束,(为了美观)退出代码定位 } //主函数 int main() { //设置本地环境 setlocale(LC_ALL, ""); //生成随机值 srand((unsigned int)time(NULL)); //测试游戏 test(); return 0; }
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!