引言
贪吃蛇是一款经典的小游戏,简单有趣,适合初学者用来练习编程技能。本文将详细解析一个用 C 语言实现的贪吃蛇游戏代码,代码主要包含三个文件:test.cpp、snake.h
和 snake.cpp。
代码结构概述
- test.cpp:主要负责游戏的测试逻辑,包含
test
函数和main
函数。 - snake.h:头文件,包含必要的头文件引用、宏定义、类型声明和函数声明。
- snake.cpp:实现了
snake.h
中声明的函数,包含游戏的初始化、运行、结束等功能。
代码详细解析
snake.h 文件
#pragma once
#include <locale.h>
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#define POS_X 24
#define POS_Y 5
#pragma once
确保头文件只被包含一次,防止重复定义。- 引入了必要的头文件,用于输入输出、系统操作、随机数生成等。
- 定义了宏
POS_X
和POS_Y
,用于指定蛇的初始位置。// 蛇的方向 enum DIRECTION { UP, // 上 DOWN, // 下 LEFT, // 左 RIGHT // 右 }; // 蛇的状态 enum GAME_STATUS { OK, // 正常 KILL_BY_WALL, // 撞墙 KILL_BY_SELF, // 撞到自己 END_NORMAL // 正常退出 };
- 定义了两个枚举类型
DIRECTION
和GAME_STATUS
,分别表示蛇的移动方向和游戏状态。// 蛇身的节点声明 typedef struct SnakeNode { // 坐标 int x; int y; // 指针 struct SnakeNode* next; } SnakeNode, *pSnakeNode; // 贪吃蛇 typedef struct Snake { pSnakeNode _pSnake; // 指向蛇头的指针 pSnakeNode _pFood; // 指向食物的指针 enum DIRECTION dir; // 蛇的方向 enum GAME_STATUS status; // 游戏的状态 int food_weight; // 一个食物的分数 int score; // 总成绩 int sleep_time; // 休息时间,时间越短,蛇的速度越快 } Snake, *pSnake;
- 定义了蛇身节点结构体
SnakeNode
和贪吃蛇结构体Snake
,使用typedef
简化类型名。//函数的声明 //游戏的初始化 void GameStart(pSnake ps); //获得坐标 void SetPos(short x, short y); //欢迎界面 void WelcomeToGame(); //绘制地图 void CreateMap(); //初始化蛇 void InitSnake(pSnake ps); //创造食物 void CreateFood(pSnake ps); //运行游戏 void GameRun(pSnake ps); //蛇的移动 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.cpp 中实现。
-
test.cpp 文件
#include "snake.h" // 完成的是游戏的测试逻辑 void test() { int ch = 0; do { system("cls"); // 创建贪吃蛇 Snake snake = { 0 }; // 初始化游戏 // 1. 设置窗口大小,标题以及光标隐藏 // 2. 打印欢迎界面,功能介绍 // 3. 绘制地图 // 4. 创建蛇 // 5. 创建食物 GameStart(&snake); // 运行游戏 GameRun(&snake); // 结束游戏 - 善后工作 GameEnd(&snake); SetPos(20, 20); printf("再来一局吗(Y/N):"); ch = getchar(); while (getchar() != '\n'); } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); } int main() { srand((unsigned int)time(NULL)); test(); return 0; }
test
函数是游戏的主循环,负责初始化游戏、运行游戏、结束游戏,并询问玩家是否再来一局。main
函数初始化随机数种子,调用test
函数开始游戏。snake.cpp 文件
基本功能函数
// 获得坐标 void SetPos(short x, short y) { // 获得标准输出设备的句柄 HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); // 定位光标的位置 COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos);
SetPos
函数用于设置控制台光标的位置,方便在指定位置输出字符。// 欢迎界面的打印 void WelcomeToGame() { SetPos(40, 14); printf("欢迎来到贪吃蛇小游戏\n"); SetPos(38, 20); system("pause"); system("cls"); SetPos(30, 14); printf("用 ↑, ↓, ←, → 来控制贪吃蛇的方向,按F3加速,F4减速\n"); SetPos(30, 15); printf("加速能够得到更高的分数\n"); SetPos(38, 20); system("pause"); system("cls"); }
WelcomeToGame
函数打印欢迎界面和游戏说明,等待用户按下任意键继续。// 绘制地图 void CreateMap() { // 上 printf("🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣"); // 下 SetPos(0, 26); printf("🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣"); // 左 int i = 0; for (i = 1; i <= 25; i++) { SetPos(0, i); printf("🤣"); } // 右 for (i = 1; i <= 25; i++) { SetPos(56, i); printf("🤣"); } }
CreateMap
函数绘制游戏地图,使用字符🤣
表示墙壁。-
蛇的初始化和移动
void InitSnake(pSnake ps) { int i = 0; pSnakeNode cur = NULL; for (i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("malloc fail!"); 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; } } cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); printf("😋"); cur = cur->next; } // 设置贪吃蛇的属性 ps->dir = RIGHT; ps->score = 0; ps->food_weight = 10; ps->sleep_time = 200; // 单位是毫秒 ps->status = OK; }
InitSnake
函数初始化蛇,使用头插法创建一个长度为 5 的蛇链表,并设置蛇的初始方向、分数、食物权重和速度。// 创造食物 void CreateFood(pSnake ps) { int x = 0; int y = 0; // 生成的 x 是二的倍数 again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0); // 蛇身不能与食物冲突 pSnakeNode cur = ps->_pSnake; while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; } // 创建食物节点 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("malloc fail!"); exit(1); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); printf("🍔"); ps->_pFood = pFood; }
CreateFood
函数随机生成食物的位置,确保食物不会出现在蛇身上,并在指定位置打印食物。// 初始化游戏 void GameStart(pSnake ps) { // 1. 设置窗口大小,标题以及光标隐藏 system("chcp 65001"); system("mode con cols=100 lines=30"); system("title \xcc\xb0 \xb3\xd4 \xc9\xdf"); HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE); // 隐藏光标操作 _CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(houtput, &CursorInfo); // 获取控制台光标信息 CursorInfo.bVisible = false; // 隐藏控制台光标 SetConsoleCursorInfo(houtput, &CursorInfo); // 设置控制台光标状态 // 2. 打印欢迎界面,功能介绍 WelcomeToGame(); // 3. 绘制地图 CreateMap(); // 4. 创建蛇 InitSnake(ps); // 5. 创建食物 CreateFood(ps); }
GameStart
函数完成游戏的初始化工作,包括设置窗口大小、标题、隐藏光标,打印欢迎界面,绘制地图,创建蛇和食物。-
游戏运行和结束
// 运行游戏 void GameRun(pSnake ps) { // 打印帮助信息 PrintHelpInfo(); do { // 打印总分数和食物的总分值 SetPos(64, 8); printf("总分数:%d", ps->score); SetPos(64, 9); printf("当前食物分数:%2d", ps->food_weight); // 检测按键情况 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(); } 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); }
GameRun
函数是游戏的主循环,负责打印帮助信息、分数信息,检测用户按键,控制蛇的移动,并根据蛇的状态判断游戏是否结束。// 游戏善后的工作 void GameEnd(pSnake ps) { SetPos(24, 12); switch (ps->status) { case END_NORMAL: printf("您主动结束游戏\n"); break; case KILL_BY_SELF: printf("您撞到自己,游戏结束\n"); break; case KILL_BY_WALL: printf("您撞到墙上,游戏结束\n"); break; } // 打印死蛇 pSnakeNode prev = ps->_pSnake; while (prev) { SetPos(prev->x, prev->y); printf("😫"); prev = prev->next; } // 释放蛇身的链表 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode prev = cur; cur = cur->next; free(prev); } }
GameEnd
函数负责游戏结束后的善后工作,包括打印游戏结束信息,将蛇的表情改为😫
,并释放蛇身链表的内存。-
总结
通过对这个贪吃蛇游戏代码的详细解析,我们可以看到如何使用 C 语言和 Windows API 来实现一个简单的控制台游戏。代码中使用了链表来表示蛇的身体,通过控制光标的位置来实现游戏界面的更新。同时,代码还处理了用户输入、游戏状态判断、内存管理等问题,是一个很好的学习示例。