文章目录
效果演示:
贪吃蛇演示
一、核心目标与技术储备
1. 核心目标
本次项目的核心目标是使用C语言在Windows控制台中模拟实现贪吃蛇游戏,具体需要完成以下功能:
- 绘制贪吃蛇游戏地图
- 通过方向键(上、下、左、右)控制蛇的移动
- 实现蛇吃食物增长身体的功能
- 判定蛇撞墙死亡和蛇撞自身死亡的逻辑
- 计算并显示游戏得分
- 支持蛇身加速、减速功能
- 实现游戏暂停功能
2. 核心技术要点
- C语言基础:函数、枚举、结构体、动态内存管理、预处理指令
- 数据结构:链表(用于管理蛇身节点)
- 系统接口:Win32 API(用于控制台操作)
二、Win32 API核心知识
Windows系统提供了丰富的应用程序编程接口(API),我们需要利用这些接口实现控制台的各种操作。
1. 控制台程序基础
我们日常看到的"黑框程序"就是控制台程序,可通过命令或API函数控制其窗口属性:
- 设置窗口大小:
mode con cols=100 lines=30
(100列,30行) - 设置窗口标题:
title 贪吃蛇
- 在C语言中通过
system
函数执行上述命令:
#include <stdio.h>
int main() {
// 设置控制台窗口大小
system("mode con cols=100 lines=30");
// 设置窗口名称
system("title 贪吃蛇");
return 0;
}
2. 控制台坐标系统
控制台使用COORD
结构体表示坐标,原点(0,0)位于窗口左上角:
// COORD结构体定义
typedef struct _COORD {
SHORT X; // 横向坐标(列)
SHORT Y; // 纵向坐标(行)
} COORD, *PCOORD;
// 坐标赋值示例
COORD pos = { 10, 15 }; // X=10,Y=15的位置
3. 控制台句柄操作
- GetStdHandle:获取标准设备(输入、输出、错误)的句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
- 光标控制:通过
GetConsoleCursorInfo
和SetConsoleCursorInfo
控制光标可见性
// 隐藏光标示例
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo); // 获取光标信息
CursorInfo.bVisible = false; // 隐藏光标
SetConsoleCursorInfo(hOutput, &CursorInfo); // 设置光标状态
- 设置光标位置:使用
SetConsoleCursorPosition
函数
// 封装设置光标位置的函数
void SetPos(short x, short y) {
COORD pos = { x, y };
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOutput, pos);
}
4. 键盘输入检测
GetAsyncKeyState
的返回值是short
类型,在上⼀次调用GetAsyncKeyState
函数后返回的16位的short
数据中:
- 最高位是1,说明按键的状态是按下,
- 最高是0,说明按键的状态是抬起;
- 最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState
返回值的最低值是否为1.
虚拟键码:
常数 | Value | 说明 |
---|---|---|
VK_LEFT | 0x25 | 向左键 |
VK_UP | 0x26 | 向上键 |
VK_RIGHT | 0x27 | 向右键 |
VK_DOWN | 0x28 | 向下键 |
通过宏定义简化检测逻辑
// 按键检测宏定义
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
// 检测数字键示例
if (KEY_PRESS(0x30)) printf("0被按下\n"); // 0x30是数字0的虚拟键码
if (KEY_PRESS(VK_UP)) printf("上方向键被按下\n");
与00000001
按位与得到的结果就是最后一位的数值
三、 数据结构设计
1. 蛇身节点结构
使用链表存储蛇身,每个节点记录坐标信息:
// 蛇身节点结构体
typedef struct SnakeNode {
int x; // X坐标
int y; // Y坐标
struct SnakeNode* next; // 指向 next 节点
} SN, *pSN;
2. 游戏核心结构
封装游戏所需的所有状态信息:
// 方向枚举
enum DIRECTION {
UP = 1,
DOWN,
LEFT,
RIGHT
};
// 游戏状态枚举
enum GAME_STATUS {
OK, // 正常运行
KILL_BY_WALL, // 撞墙
KILL_BY_SELF, // 咬到自己
END_NOMAL // 正常结束
};
// 游戏主结构体
typedef struct Snake {
pSN _pSnake; // 蛇身链表头指针
pSN _pFood; // 食物节点指针
enum DIRECTION _Dir; // 当前方向
enum GAME_STATUS _Status;// 游戏状态
int _Socre; // 当前得分
int _foodWeight; // 每个食物的分数
int _SleepTime; // 移动间隔时间(控制速度)
} Snake, *pSnake;
四、游戏初始化(initSnake)
1.设置窗口
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
2. 隐藏光标
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(houtput, &CursorInfo);
3. 欢迎界面(welcomeToGame)
void welcomeToGame()
{
setPos(40, 14);
wprintf(L"欢迎来到贪吃蛇大作战");
setPos(41, 20);
system("pause");
//清理屏幕
system("cls");
//功能介绍
setPos(35, 10);
wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
setPos(41, 13);
wprintf(L"按F3加速,F4减速\n");
setPos(38, 16);
wprintf(L"加速能够得到更高的分数\n");
setPos(42, 25);
system("pause");
system("cls");
}
4. 游戏地图(createMap)
4.1 地图坐标与宽字符
游戏地图使用宽字符绘制,需注意:
- 宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。
- 前缀“L”在单引号前面,表示宽字符,对应
wprintf()
的占位符为%lc
- 前缀“L”在双引号前面,表示宽字符串,对应
wprintf()
的占位符为%ls
。 - 普通字符占1个字节,宽字符占2个字节
需通过setlocale
函数设置本地模式以支持宽字符输出:
#include <locale.h>
int main() {
setlocale(LC_ALL, ""); // 切换到本地模式,支持中文和宽字符
return 0;
}
- 游戏使用的宽字符定义:
#define WALL L'□' // 墙体
#define BODY L'●' // 蛇身
#define FOOD L'★' // 食物
4.2 地图结构
设计一个27行(Y=0到Y=26)、58列(X=0到X=56)的地图:
- 上边界:(0,0)到(56,0)
- 下边界:(0,26)到(56,26)
- 左边界:(0,1)到(0,25)
- 右边界:(56,1)到(56,25)
void createMap()
{
//上
for (int i = 0;i < 29;i++)
{
wprintf(L"%lc", L'□');
}
//下
setPos(0, 26);
for (int i = 0;i < 29;i++)
{
wprintf(L"%lc", L'□');
}
//左 右
for (int i = 1;i <= 25;i++)
{
setPos(0, i);
wprintf(L"%lc", L'□');
setPos(56, i);
wprintf(L"%lc", L'□');
}
}
5. 初始化蛇身
- 蛇身初始状态:长度为5节,从坐标(24,5)开始,每节X坐标递增2(适应宽字符)
void initSnake(pSnake ps)
{
//创建
pSN cur = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSN)malloc(sizeof(SN));
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);
wprintf(L"%c", BODY);
cur = cur->next;
}
//设置属性
ps->_dir = RIGHT;//默认向右
ps->_score = 0;
ps->_pFood = 10;
ps->_sleep_time = 200;
ps->_status = OK;
}
6.创建食物
食物生成规则:
- 随机生成在墙体内
- X坐标必须是2的倍数(与蛇身对齐)
- 不能与蛇身坐标重合
void creatFood(pSnake ps)
{
int x, y;
again:
//x:2-54 y:1-25
do {
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//不能与蛇身重复,便利蛇身
pSN cur = ps->_psnake;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
//创建食物的节点
pSN pFood = (pSN)malloc(sizeof(SN));
if (pFood == NULL)
{
perror("malloc fail");
exit(1);
}
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
//打印
setPos(x, y);
wprintf(L"%c", FOOD);
ps->_pFood = pFood;
}
五、游戏运行逻辑
1. 打印帮助信息(printHelpInfo)
void printHelpInfo()
{
setPos(66, 17);
wprintf(L"不能穿墙,不能咬到自己");
setPos(64, 19);
wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
setPos(70, 21);
wprintf(L"按F3加速,按F4减速\n");
setPos(61, 23);
wprintf(L"按ESC退出游戏,按空格键暂停或继续游戏\n");
setPos(71, 26);
wprintf(L"@Daisy 制作\n");
}
2. 蛇移动逻辑(SnakeMove)
蛇的移动是游戏的核心机制,需要根据当前方向计算下一个位置,并处理吃食物、撞墙、撞自身等情况。
void SnakeMove(pSnake ps) {
// 创建下一个节点(蛇头将要移动到的位置)
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL) {
perror("SnakeMove()::malloc()");
return;
}
// 根据当前方向计算下一个节点坐标
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; // 左移2单位(宽字符)
pNextNode->y = ps->_pSnake->y;
break;
case RIGHT:
pNextNode->x = ps->_pSnake->x + 2; // 右移2单位(宽字符)
pNextNode->y = ps->_pSnake->y;
break;
}
// 判断下一个位置是否是食物
if (NextIsFood(pNextNode, ps)) {
EatFood(pNextNode, ps); // 吃食物逻辑
} else {
NoFood(pNextNode, ps); // 不吃食物逻辑
}
// 检测是否撞墙或撞自身
KillByWall(ps);
KillBySelf(ps);
}
2.1 判断是否吃到食物(NextIsFood)
通过比较下一个节点与食物的坐标,判断蛇是否吃到食物:
int NextIsFood(pSnakeNode psn, pSnake ps) {
return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}
2.2 吃食物处理(EatFood)
吃到食物后,蛇身增长,分数增加,并生成新食物:
void EatFood(pSnakeNode psn, pSnake ps) {
// 头插法将新节点加入蛇身
psn->next = ps->_pSnake;
ps->_pSnake = psn;
// 重新绘制蛇身
pSnakeNode cur = ps->_pSnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
// 增加分数
ps->_Socre += ps->_foodWeight;
// 释放当前食物节点并创建新食物
free(ps->_pFood);
CreateFood(ps);
}
2.3 未吃食物处理(NoFood)
未吃到食物时,蛇移动后尾部需要删除,保持长度不变:
void NoFood(pSnakeNode psn, pSnake ps) {
// 头插法添加新节点(蛇头)
psn->next = ps->_pSnake;
ps->_pSnake = psn;
// 绘制蛇身(除最后一节)
pSnakeNode cur = ps->_pSnake;
while (cur->next->next) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
// 清除尾部节点并释放内存
SetPos(cur->next->x, cur->next->y);
printf(" "); // 用空格覆盖原尾部
free(cur->next);
cur->next = NULL; // 避免野指针
}
3. 碰撞检测
3.1 撞墙检测(KillByWall)
判断蛇头是否与墙体坐标重合:
int 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; // 设置状态为撞墙
return 1;
}
return 0;
}
3.2 撞自身检测(KillBySelf)
判断蛇头是否与自身身体其他节点坐标重合:
int KillBySelf(pSnake ps) {
pSnakeNode cur = ps->_pSnake->next; // 从第二节开始检查
while (cur) {
if ((ps->_pSnake->x == cur->x) && (ps->_pSnake->y == cur->y)) {
ps->_Status = KILL_BY_SELF; // 设置状态为撞自身
return 1;
}
cur = cur->next;
}
return 0;
}
六、游戏结束处理(GameEnd)
当游戏状态变为非OK时,显示结束原因并释放内存资源:
void GameEnd(pSnake ps) {
pSnakeNode cur = ps->_pSnake;
SetPos(24, 12); // 定位显示结束信息
// 根据游戏状态显示不同信息
switch (ps->_Status) {
case END_NOMAL:
printf("您主动退出游戏\n");
break;
case KILL_BY_SELF:
printf("您撞上自己了,游戏结束!\n");
break;
case KILL_BY_WALL:
printf("您撞墙了,游戏结束!\n");
break;
}
// 释放蛇身所有节点内存
while (cur) {
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
七、完整代码结构
1. test.c(程序入口)
包含main函数和测试逻辑,负责启动游戏:
#define _CRT_SECURE_NO_WARNINGS 1;
#include"snake.h"
//游戏测试逻辑
void test()
{
int ch;
do
{
//创建
Snake snake = { 0 };
//初始化
gameStart(&snake);
//运行
gameRun(&snake);
//结束
gameEnd(&snake);
setPos(20, 15);
printf("再来一局吗?(Y/N)");
setPos(27, 17);
ch = getchar();
getchar();//清理\n
} while (ch == 'y' || ch == 'Y');
setPos(0, 27);
}
int main()
{
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));
test();
return 0;
}
2. Snake.h(头文件)
声明结构体、枚举和所有函数:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
//类型声明
//方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//游戏状态
enum GAME_STATUS
{
OK,//正常
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞到自己
END_NORMAI//正常退出
};
//蛇身
struct SnakeNode
{
//坐标
int x;
int y;
struct SnakeNode* next;
};
typedef struct SnakeNode SN;
typedef struct SnakeNode* pSN;
//蛇
typedef struct snake
{
pSN _psnake;//指向蛇头的指针
pSN _pFood;//指向食物的指针
enum DIRECTION _dir;//蛇头方向
enum GAME_STATUS _status;//游戏状态
int _food_weight;//一个食物的分数
int _score;//分数
int _sleep_time;//休息时间--->速度
}Snake,* pSnake;
//函数声明
//初始化
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(pSN pn, pSnake ps);
//吃掉食物
void eatFood(pSN pn, pSnake ps);
//不是食物
void notFood(pSN pn, pSnake ps);
//检测是否撞墙
void killByWall(pSnake ps);
//检测蛇是否撞到自己
void killBySelf(pSnake ps);
//结束游戏
void gameEnd(pSnake ps);
3. Snake.c(函数实现)
#define _CRT_SECURE_NO_WARNINGS 1;
#include"snake.h"
//获取光标
void setPos(short x, short y)
{
//获取句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
//欢迎界面
void welcomeToGame()
{
setPos(40, 14);
wprintf(L"欢迎来到贪吃蛇大作战");
setPos(41, 20);
system("pause");
//清理屏幕
system("cls");
//功能介绍
setPos(35, 10);
wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
setPos(41, 13);
wprintf(L"按F3加速,按F4减速\n");
setPos(38, 16);
wprintf(L"加速能够得到更高的分数\n");
setPos(42, 25);
system("pause");
system("cls");
}
//创建地图
void createMap()
{
//上
for (int i = 0;i < 29;i++)
{
wprintf(L"%lc", WALL);
}
//下
setPos(0, 26);
for (int i = 0;i < 29;i++)
{
wprintf(L"%lc", WALL);
}
//左 右
for (int i = 1;i <= 25;i++)
{
setPos(0, i);
wprintf(L"%lc", WALL);
setPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化蛇身
void initSnake(pSnake ps)
{
//创建
pSN cur = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSN)malloc(sizeof(SN));
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);
wprintf(L"%c", BODY);
cur = cur->next;
}
//设置属性
ps->_dir = RIGHT;//默认向右
ps->_score = 0;
ps->_pFood = 10;
ps->_sleep_time = 200;
ps->_status = OK;
ps->_food_weight = 10;
}
//创建食物
void createFood(pSnake ps)
{
int x, y;
again:
//x:2-54 y:1-25
do {
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//不能与蛇身重复,便利蛇身
pSN cur = ps->_psnake;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
//创建食物的节点
pSN pFood = (pSN)malloc(sizeof(SN));
if (pFood == NULL)
{
perror("malloc fail");
exit(1);
}
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
//打印
setPos(x, y);
wprintf(L"%c", FOOD);
ps->_pFood = pFood;
}
//初始化
void gameStart(pSnake ps)
{
//1.窗口大小
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//2.隐藏光标
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(houtput, &CursorInfo);
//3.欢迎界面
welcomeToGame();
//4.绘制地图
createMap();
//5.创建蛇
initSnake(ps);
//6.创建食物
createFood(ps);
}
//帮助信息
void printHelpInfo()
{
setPos(66, 15);
wprintf(L"不能穿墙,不能咬到自己");
setPos(64, 17);
wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
setPos(70, 19);
wprintf(L"按F3加速,按F4减速\n");
setPos(61, 21);
wprintf(L"按ESC退出游戏,按空格键暂停或继续游戏\n");
setPos(71, 26);
wprintf(L"@Daisy 制作\n");
}
//暂停
void pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
break;
}
}
//判断下一个坐标是否为食物
int NextIsFood(pSN pn, pSnake ps)
{
return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}
//吃掉食物
void eatFood(pSN pn,pSnake ps)
{
//头插增加蛇身长
ps->_pFood->next = ps->_psnake;
ps->_psnake = ps->_pFood;
//释放下一位置节点
free(pn);
pn = NULL;
pSN cur = ps->_psnake;
//打印
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_score += ps->_food_weight;
//创建新食物
createFood(ps);
}
//不是食物
void notFood(pSN pn, pSnake ps)
{
//头插蛇头
pn->next = ps->_psnake;
ps->_psnake = pn;
//蛇身
pSN 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 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)
{
pSN 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 snakeMove(pSnake ps)
{
//创建一个节点,表示蛇的下一个节点
pSN pNextNode = (pSN)malloc(sizeof(SN));
if (pNextNode == NULL)
{
perror("snakeMove()::malloc");
}
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
{
notFood(pNextNode, ps);
}
//检测蛇是否撞墙
killByWall(ps);
//检测蛇是否撞到自己
killBySelf(ps);
}
//游戏运行
void gameRun(pSnake ps)
{
//打印帮助信息
printHelpInfo();
do
{
//打印总分数和食物分值
setPos(64, 6);
printf("得分:%d", ps->_score);
setPos(64, 8);
printf("食物分值:%d", 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_NORMAI;
//加速
else if (KEY_PRESS(VK_F3))
{
if (ps->_sleep_time >= 80)
{
ps->_sleep_time -= 30;
ps->_score += 2;
}
}
//减速
else if (KEY_PRESS(VK_F4))
{
if (ps->_sleep_time <= 320)
{
ps->_sleep_time += 30;
ps->_score -= 2;
}
}
snakeMove(ps);//蛇走一步的过程
Sleep(ps->_sleep_time);
} while (ps->_status == OK);
}
//结束游戏
void gameEnd(pSnake ps)
{
setPos(21, 12);
switch (ps->_status)
{
case END_NORMAI:
{
printf("退出游戏");
break;
}
case KILL_BY_WALL:
{
printf("撞墙,游戏结束\n");
break;
}
case KILL_BY_SELF:
{
printf("撞到自己,游戏结束\n");
break;
}
}
//释放链表
pSN cur = ps->_psnake;
while (cur)
{
pSN del = cur;
cur = cur->next;
free(del);
}
}
八、控制台设置与常见问题
1. Win11系统控制台适配
部分Win11用户可能遇到控制台显示异常,可按以下步骤调整:
- 打开命令提示符,点击右上角设置图标
- 进入"默认终端应用程序"设置
- 将"终端启动时"的默认配置文件设置为"命令提示符"
- 调整启动大小为合适的行列数
- 保存设置后重新打开控制台
2. 宽字符显示问题
若游戏中墙体、蛇身或食物显示为乱码,需检查:
- 是否正确调用
setlocale(LC_ALL, "")
切换到本地模式 - 宽字符打印是否使用
wprintf
函数并添加L
前缀 - 控制台字体是否支持中文和特殊字符
3. 编译环境
推荐使用支持Win32 API的编译环境,如:
- Microsoft Visual Studio
- Dev-C++(需配置链接器支持Windows库)
- MinGW(需包含Windows头文件)
通过以上步骤,我们完成了贪吃蛇游戏的设计与实现。这个项目涵盖了C语言结构体、枚举、链表等核心知识点,以及Win32 API控制台操作技巧,是一个非常适合巩固编程基础的实践案例。