朋友们!好久不见。经过一段时间的沉淀,我这篇文章来和大家分享贪吃蛇大作战这个游戏是怎么实现的。
(一).贪吃蛇背景了解及效果展示
首先相信贪吃蛇游戏绝对称的上是我们00后的童年,不仅是贪吃蛇还有俄罗斯⽅块,扫雷等都是以前十分流行的游戏,下面我们就通过代码的形式进行贪吃蛇的实现,在进行代码实现之前我们先来看看实现的效果看看有些什么,相信大家一定会对它十分感兴趣的。
贪吃蛇游戏
上面这个就是关于贪吃蛇游戏的实现效果图,相信大家都十分的想了解到底它是怎样实现的,那么下面我们就来学习关于它的实现。
(二).预了解知识(相关Win32API介绍)
首先在实现贪吃蛇之前我们要先了解一些关于Win32API的知识。
2.1.控制台程序
在这里我们需要改变控制台,才能完成贪吃蛇游戏,如图:
这里我们首先要将默认终端应用程序改成如图所示,
mode命令:指的是改变控制台界面大小
如图所示:
title命令:改变控制台的名字
总的来说这两个命令实际运用如图所示:
改变的就是如图中的两个东西实际代码如下:
#include<stdio.h>
int main()
{
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
return 0;
}
2.2.控制台屏幕上的坐标COORD
COORD也就是控制台屏幕上的坐标
也就是如图所示的坐标。
COORD类型的声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };
2.3.GetStdHandle
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标 准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
这里简而言之就是和我们鼠标相同的东西。
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
这里运用GetStdHandle来获取句柄。
2.4.GetConsoleCursorInfo 函数
作用:检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
GetConsoleCursorInfo 函数的实际运用就是如图所示,用来获取控制台光标的信息。
2.5.CONSOLE_CURSOR_INFO
这个结构体,包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize:由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。
bVisible:游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标
2.6.SetConsoleCursorInfo
作用:设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
这里就是用 SetConsoleCursorInfo函数来设置控制台光标状态, 只有运用了这个函数光标才能够被隐藏。
2.7.SetConsoleCursorPosition
作用:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。简而言之就是将光标设置在自己想要它在的地方通过坐标的形式。
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
SetPos:封装⼀个设置光标位置的函数
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
2.8GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
实例:检测数字键
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
#include <stdio.h>
#include <windows.h>
int main()
{
while (1)
{
if (KEY_PRESS(0x30))
{
printf("0\n");
}
else if (KEY_PRESS(0x31))
{
printf("1\n");
}
else if (KEY_PRESS(0x32))
{
printf("2\n");
}
else if (KEY_PRESS(0x33))
{
printf("3\n");
}
else if (KEY_PRESS(0x34))
{
printf("4\n");
}
else if (KEY_PRESS(0x35))
{
printf("5\n");
}
else if (KEY_PRESS(0x36))
{
printf("6\n");
}
else if (KEY_PRESS(0x37))
{
printf("7\n");
}
else if (KEY_PRESS(0x38))
{
printf("8\n");
}
else if (KEY_PRESS(0x39))
{
printf("9\n");
}
}
return 0;
}
这里就是用来检测键盘上面的键是否被按过。
2.9设置本地化环境
<locale.h>本地化
提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。 在标准中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式
2.10.setlocale
C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。
在我们实现的时候通常我们会先把它设置为本地模式,以便进行环境的匹配。
setlocale(LC_ALL, "C"); 注意这里使用setlocale函数要有头文件<locale.h>。
2.11.宽字符的打印
那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。
这三个内容其实是连在一起用的,要先进行本地环境的设置再来进行宽字符的打印。
总结:上面的全部都是我们在完成贪吃蛇之前需要了解的知识,可能我们就这样直接来看可能不容易理解下面我们进行贪吃蛇的实现将这些知识带进去我们大家可能更容易理解。
(三).项目实践
项目进行之前我们要先进行准备工作,首先定义一个结构体,来包装贪吃蛇
typedef struct Snake
{
pSnakeNode _psnake;//指向蛇头的指针
pSnakeNode _pfood;//指向食物节点的指针
enum DIRECTION _dir;//蛇的方向
enum GAME_STATUS _status;//游戏的状态
int _food_weight;//一个食物的分数
int _score;//总分数
int _sleep_time;//休息时间,休息时间越短,速度越快
}Snake,* pSnake;
这里面就是贪吃蛇的全部东西我们这样将它的七个内容定义成一个结构体的好处是有利于后期的管理和实现,同样里面蛇的方向有多个,游戏状态也有多种,这两个我们就可以利用枚举的方式来包装起来,蛇的节点类型其实就是一个链表同样这里我们也需要定义一个结构体来表示蛇的节点,这三个实现的代码我总结到下面:
//枚举蛇的方向
enum DIRECTION
{
UP,
DOWN,
LEFT,
RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {
OK,//正常
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞到自己
END_NORMAL //正常退出
};
//蛇身节点类型
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,* pSnakeNode;
好了当我们把这些准备工作做好以后我们就可以来进行贪吃蛇的实现了,首先我们来看游戏开始部分。
3.1.游戏开始(GameStart)
游戏开始阶段一共有七个内容我们来逐一学习。
3.1.1.设置游戏窗⼝的⼤⼩
设置游戏窗口的大小,就是我们上面说的mode命令
system("mode con cols=100 lines=30");就是有system函数来设置控制台窗口的大小。
3.1.2.设置窗⼝的名字
设置窗口的名字就是通过title命令来设置窗口的名字
system("title 贪吃蛇");就是运用system函数来设置窗口的名字
当这两个程序完成之后的窗口样子就是如下:
3.1.3.隐藏屏幕光标
隐藏光标实际上就是把如图所示的东西给隐藏掉
那么这个程序我们该如何去写呢?接下来紧跟我们脚步我们一起来学习
通过上面我们了解的一些知识我们先看看下面这段代码:
//设置光标的坐标
void SetPos(short x, short y)
{
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
COORD pos = { x, y };
SetConsoleCursorPosition(hOutput, pos);
}
//获取输出设备的句柄
HANDLE hOutput = NULL;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息
这里面就解释了我们如何来隐藏光标,先获取句柄,定义一个houtput来接收,再通过定义一个结构体,在获取控制台光标信息,最后再来隐藏控制台光标。
3.1.4.打印欢迎界⾯-WelcomeToGame
欢迎界面就是如图所示,同样我们的代码程序如下:
void WelcomeToGame()
{
SetPos(38,14);
wprintf(L"欢迎来到贪吃蛇小游戏");
SetPos(40, 18);
system("pause");
system("cls");//清理界面
SetPos(20, 14);
wprintf(L"用↑ . ↓ . ← . →来控制蛇的移动,按F3加速,按F4减速\n");
SetPos(20, 15);
wprintf(L"加速能够获得更高的分数\n");
SetPos(40, 20);
system("pause");
system("cls");
}
3.1.5.创建地图-CreateMap
地图创建我们需要我们来进行创建以下的界面
创建一个x轴为58y轴为26的一个游戏地图,这里我们就可以运用循环语句来打印□从而创建游戏窗口。
实际代码如图所示:
void CreatMap()
{
//上
int i = 0;
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 1; i < 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
上面的SetPos是指的是定位光标的位置。
3.1.6.初始化蛇⾝-InitSnake
//创建蛇身
void InitSnack(pSnake ps)
{
int i = 0;
pSnakeNode cur = NULL;
for (i; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnack()::malloc");
return;
}
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"%lc", BODY);
cur = cur->next;
}
//设置贪吃蛇属性
ps->_dir = RIGHT;//默认向右
ps->_score = 0;
ps->_food_weight = 10;
ps->_sleep_time = 200;//单位是毫秒
ps->_status = OK;
}
3.1.7.创建⻝物
//创建食物
void CreatFood(pSnake ps)
{
int x = 0;
int y = 0;
//生成x必须是2的倍数
//x:2--54
//y:1--25
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//x和y不能与蛇身体的位置冲突
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("pFood::malloc");
return;
}
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
ps->_pfood = pFood;
}
3.2.游戏进行(GameRun)
3.2.1.右侧打印帮助信息-PrintHelpInfo
//打印运行游戏旁边的提示信息
void printfhelpInfo()
{
SetPos(64, 12);
wprintf(L"%ls", L"不能撞墙,不能咬到自己");
SetPos(64, 13);
wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");
SetPos(64, 14);
wprintf(L"%ls", L"按F3加速,按F4减速");
SetPos(64, 15);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
3.2.2.打印当前已获得分数和每个⻝物的分数
//打印总分数和食物的分值
SetPos(64, 9);
printf("总分数:%d\n", ps->_score);
SetPos(64, 10);
printf("当前食物的分数:%2d\n", ps->_food_weight);
3.2.3.获取按键情况-KEY_PRESS
//按键的设置
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;
}
}
3.2.4.根据按键情况移动蛇-SnakeMove 2~4循环,直到游戏是结束状态
void SnakeMove(pSnake ps)
{
//创建一个节点表示蛇即将要到的下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("pNextNode()::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;
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);
}
3.2.4.1. 根据蛇头的坐标和⽅向,计算下⼀个节点的坐标
void SnakeMove(pSnake ps)
{
//创建一个节点表示蛇即将要到的下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("pNextNode()::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;
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);
}
3.2.4.2. 判断下⼀个节点是否是⻝物-NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps)
{
return (ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}
3.2.4.3.是⻝物就吃掉-EatFood
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)
{
SetPos(cur->x,cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_score += ps->_food_weight;
//重新创建食物
CreatFood(ps);
}
3.2.4.4.不是⻝物,吃掉⻝物,尾巴删除⼀节-NoFood
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"%lc", BODY);
cur = cur->next;
}
//把最后一个节点打印成空格
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
3.2.4.5.判断是否撞墙-KillByWall
//检测是否撞墙
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;
}
}
3.2.4.6.判断是否撞上⾃⼰-KillBySelf
//检测是否撞到自己
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;
}
cur = cur->next;
}
}
3.3游戏结束(GameEnd)
3.3.1.告知游戏结束的原因
3.3.2.释放蛇⾝节点
//结束游戏--善后工作
void GameEnd(pSnake ps)
{
switch (ps->_status)
{
case END_NORMAL:
printf("主动结束游戏\n");
break;
case KILL_BY_SELF:
printf("撞到了自己,游戏结束\n");
break;
case KILL_BY_WALL:
printf("撞到了墙游戏结束\n");
break;
}
//释放链表
pSnakeNode cur = ps->_psnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
(四).代码汇总
4.1.snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//枚举蛇的方向
enum DIRECTION
{
UP,
DOWN,
LEFT,
RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {
OK,//正常
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞到自己
END_NORMAL //正常退出
};
//蛇身节点类型
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;
//定位光标位置
void SetPos(short x, short y);
//初始化游戏
void GameStart(pSnake ps);
//打印欢迎界面和功能介绍
void WelcomeToGame(pSnake ps);
//2.绘制地图
void CreatMap();
//初始化蛇
void InitSnack(pSnake ps);
//创建食物
void CreatFood(pSnake ps);
//运行游戏
void GameRun(pSnake ps);
//蛇的移动
void SnakeMove(pSnake ps);
//实现暂停逻辑
void pause();
//检测下一个坐标是否是食物
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);
4.2.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);
//设置标准输出上光标的位置为pos
COORD pos = { x, y };
SetConsoleCursorPosition(hOutput, pos);
}
//1.打印欢迎界面和功能介绍
void WelcomeToGame()
{
SetPos(38,14);
wprintf(L"欢迎来到贪吃蛇小游戏");
SetPos(40, 18);
system("pause");
system("cls");//清理界面
SetPos(20, 14);
wprintf(L"用↑ . ↓ . ← . →来控制蛇的移动,按F3加速,按F4减速\n");
SetPos(20, 15);
wprintf(L"加速能够获得更高的分数\n");
SetPos(40, 20);
system("pause");
system("cls");
}
//2.绘制地图
void CreatMap()
{
//上
int i = 0;
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 1; i < 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//创建蛇身
void InitSnack(pSnake ps)
{
int i = 0;
pSnakeNode cur = NULL;
for (i; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnack()::malloc");
return;
}
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"%lc", BODY);
cur = cur->next;
}
//设置贪吃蛇属性
ps->_dir = RIGHT;//默认向右
ps->_score = 0;
ps->_food_weight = 10;
ps->_sleep_time = 200;//单位是毫秒
ps->_status = OK;
}
//创建食物
void CreatFood(pSnake ps)
{
int x = 0;
int y = 0;
//生成x必须是2的倍数
//x:2--54
//y:1--25
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//x和y不能与蛇身体的位置冲突
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("pFood::malloc");
return;
}
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
ps->_pfood = pFood;
}
void GameStart(pSnake ps)
{
//首先设置窗口的大小,进行光标隐藏
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//获取输出设备的句柄
HANDLE hOutput = NULL;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息
//1.打印欢迎界面和功能介绍
WelcomeToGame();
//2.绘制地图
CreatMap();
//3.创建蛇
InitSnack(ps);
//4.创建食物
CreatFood(ps);
}
//打印运行游戏旁边的提示信息
void printfhelpInfo()
{
SetPos(64, 12);
wprintf(L"%ls", L"不能撞墙,不能咬到自己");
SetPos(64, 13);
wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");
SetPos(64, 14);
wprintf(L"%ls", L"按F3加速,按F4减速");
SetPos(64, 15);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
void pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
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)
{
SetPos(cur->x,cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_score += ps->_food_weight;
//重新创建食物
CreatFood(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"%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)
{
pSnakeNode cur = ps->_psnake->next;
while (cur)
{
if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y)
{
ps->_status = KILL_BY_SELF;
}
cur = cur->next;
}
}
void SnakeMove(pSnake ps)
{
//创建一个节点表示蛇即将要到的下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("pNextNode()::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;
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);
}
//运行游戏
void GameRun(pSnake ps)
{
printfhelpInfo();
do {
//打印总分数和食物的分值
SetPos(64, 9);
printf("总分数:%d\n", ps->_score);
SetPos(64, 10);
printf("当前食物的分数:%2d\n", 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);
}
//结束游戏--善后工作
void GameEnd(pSnake ps)
{
switch (ps->_status)
{
case END_NORMAL:
printf("主动结束游戏\n");
break;
case KILL_BY_SELF:
printf("撞到了自己,游戏结束\n");
break;
case KILL_BY_WALL:
printf("撞到了墙游戏结束\n");
break;
}
//释放链表
pSnakeNode cur = ps->_psnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
4.3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"
//完成游戏的测试逻辑
void test()
{
int ch = 0;
do {
system("cls");//在每次开始前先清理屏幕
//创建贪吃蛇
Snake snake = { 0 };
//初始化游戏
//1.打印欢迎界面
//2.功能介绍
//3.绘制地图
//4.创建蛇
//5.创建食物
//6.设置游戏相关信息
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏--善后工作
GameEnd(&snake);
SetPos(20,15);
printf("只来一局?(Y/N):");
ch=getchar();
getchar();
while (getchar() != '\n');
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
//设置适配环境(本地环境)
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));//生成随机数值
test();
return 0;
}
好了,兄弟们以上就是贪吃蛇项目的全部内容,通过去写这个项目我自己反正是感悟挺大的,因为从学习到自己实现这个过程虽然很艰苦,但当自己写出来能实现的时候觉得一切都值得,以后的编程之路一定会使我越来越牛掰,一起加油,兄弟们!