大家好,今天我带着大家从0构建起贪吃蛇项目的高楼大厦~
要实现这个游戏,我们需要实现哪些功能呢?
实现基本的功能: ? 贪吃蛇地图绘制 ? 蛇吃?物的功能(上、下、左、右?向键控制蛇的动作) ? 蛇撞墙死亡 ? 蛇撞??死亡 ? 计算得分 ? 蛇?加速、减速 ? 暂停游戏
调?这个服务中?的各种服务(每?种服务就是?个函数),可以帮应?程序达到开启 视窗、描绘图形、使?周边设备等?的。
平时我们运行起来的黑框程序其实就是控制台程序。
cmd命令来设置控制台窗?的?宽:例如设置控制台窗口的大小为30行,100列。
mode con cols=100 lines=30
也可以设置控制台窗口的名字:
title 贪吃蛇
这些能在控制台窗?执?的命令,也可以调?C语?函数system来执?。例如:
#include <stdio.h>
int main()
{
system("mode con cols=100 lines=30");
//设置cmd窗?名称
system("title 贪吃蛇");
return 0;
}
COORD是WindowsAPI中定义的?个结构体,表??个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)。
COORD类型的声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标赋值
COORD pos = { 10, 15 };
GetStdHandle是?个WindowsAPI函数。它?于从?个特定的标准设备(标准输?、标准输出或标准错误)中取得?个句柄(?来标识不同设备的数值),使?这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
例子:
HANDLE hOutput = NULL;
//获取标准输出的句柄(?来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
检索有关指定控制台屏幕缓冲区的光标??和可?性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标
例子:
HANDLE hOutput = NULL;
//获取标准输出的句柄(?来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
这个结构体,包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize,由光标填充的字符单元格的百分?。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的?平线条。 ?bVisible,游标的可?性。如果光标可?,则此成员为TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标
设置指定控制台屏幕缓冲区的光标的??和可?性
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
};
例子:?
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
我们将想要设置的坐标信息放在COORD类型的pos中,调?SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
};
例子:
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(?来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(?来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
获取按键情况
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");
}
}
}
窗?的坐标如下所?,横向的是X轴,从左向右依次增?,纵向是Y轴,从上到下依次增?。
?C语?的标准中不断加?了国际化的?持。?如:加?了宽字符的类型 wchar_t 和宽字符的输?和输出函数,加?了<locale.h>头?件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语?的地理区域)调整程序?为的函数。
LC_COLLATE:影响字符串?较函数 strcoll() 和 strxfrm() 。 ? LC_CTYPE:影响字符处理函数的?为。 ? LC_MONETARY:影响货币格式。 ? LC_NUMERIC:影响 printf() 的数字格式。 ? LC_TIME:影响时间格式 strftime() 和 wcsftime() 。 ? LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语?环境。
char* setlocale (int category, const char* locale);
C标准给第?个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。
比如切换为本地模式:
setlocale(LC_ALL, " ");//切换到本地环境
宽字符的字?量必须加上前缀“L”,否则C语?会把字?量当作窄字符类型处理。前缀“L”在单引 号前?,表?宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前?,表?宽字符串,对应 wprintf() 的占位符为 %ls
#include <stdio.h>
#include<locale.h>
int main() {
setlocale(LC_ALL, "");
wchar_t ch1 = L'●';
wchar_t ch2 = L'?';
wchar_t ch3 = L'特';
wchar_t ch4 = L'★';
printf("%c%c\n", 'a', 'b');
wprintf(L"%lc\n", ch1);
wprintf(L"%lc\n", ch2);
wprintf(L"%lc\n", ch3);
wprintf(L"%lc\n", ch4);
return 0;
}
我们假设实现?个棋盘27?,58列的棋盘(?和列可以根据??的情况修改),再围绕地图画出墙
初始化状态,假设蛇的?度是5,蛇?的每个节点是●,在固定的?个坐标处,?如(24,5)处开始出现 蛇,连续5个节点。 注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的?个节点有?半?出现在墙体中,另外?般在墙外的现象,坐标不好对?。 关于?物,就是在墙体内随机?成?个坐标(x坐标必须是2的倍数),坐标不能和蛇的?体重合,然后打印★。
在游戏运?的过程中,蛇每次吃?个?物,蛇的?体就会变??节,如果我们使?链表存储蛇的信 息,那么蛇的每?节其实就是链表的每个节点。每个节点只要记录好蛇?节点在地图上的坐标就?, 所以蛇节点结构如下:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
封装snake结构维护贪吃蛇
typedef struct Snake
{
pSnakeNode _pSnake;//维护整条蛇的指针
pSnakeNode _pFood;//维护?物的指针
enum DIRECTION _Dir;//蛇头的?向,默认是向右
enum GAME_STATUS _Status;//游戏状态
int _Socre;//游戏当前获得分数
int _foodWeight;//默认每个?物10分
int _SleepTime;//每??步休眠时间
}Snake, * pSnake;
蛇的方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
游戏状态
/游戏状态
enum GAME_STATUS
{
OK,//正常运?
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//咬到??
END_NOMAL//正常结束
};
游戏设计流程
设置程序?持本地模式,然后进?游戏的主逻辑
# define _CRT_SECURE_NO_WARNINGS
#include <locale.h>
void test()
{
int ch = 0;
srand((unsigned int)time(NULL));//生成随机数
do
{
Snake snake = { 0 };//创建结构体
GameStart(&snake);
GameRun(&snake);
GameEnd(&snake);
SetPos(20, 15);//设定光标位置
printf("再来?局吗?(Y/N):");
ch = getchar();//接收用户的输入值
getchar();//清理屏幕
} while (ch == 'Y');
SetPos(0, 27);//重新设定光标位置
}
int main()
{
//修改当前地区为本地模式,为了?持中?宽字符的打印
setlocale(LC_ALL, "");
//测试逻辑
test();
return 0;
}
这个模块完成游戏的初始化任务: ? 控制台窗???的设置 ? 控制台窗?名字的设置 ? ?标光标的隐藏 ? 打印欢迎界? ? 创建地图 ? 初始化第蛇 ? 创建第?个?物
# define _CRT_DEFINE_NO_WARNINGS
void GameStart(pSnake ps)
{
//设置控制台窗?的??,30?,100列
//mode 为DOS命令
system("mode con cols=100 lines=30");
//设置cmd窗?名称
system("title 贪吃蛇");
//获取标准输出的句柄(?来标识不同设备的数值)
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//打印欢迎界?
WelcomeToGame();
//打印地图
CreateMap();
//初始化蛇
InitSnake(ps);
//创造第?个?物
CreateFood(ps);
}
void WelcomeToGame()
{
SetPos(40, 15);
printf("欢迎来到贪吃蛇?游戏");
SetPos(40, 25);//让按任意键继续的出现的位置好看点
system("pause");
system("cls");
SetPos(25, 12);
printf("? ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
SetPos(25, 13);
printf("加速将能得到更?的分数。\n");
SetPos(40, 25);//让按任意键继续的出现的位置好看点
system("pause");
system("cls");
}
#define WALL L'□'
void InitSnake(pSnake ps)
{
pSnakeNode cur = NULL;
int i = 0;
//创建蛇?节点,并初始化坐标
//头插法
for (i = 0; i < 5; i++)
{
//创建蛇?的节点
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc()");
return;
}
//设置坐标
cur->next = NULL;
cur->x = POS_X + i * 2;
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->_SleepTime = 200;
ps->_Socre = 0;
ps->_Status = OK;
ps->_Dir = RIGHT;
ps->_foodWeight = 10;
}
先随机?成?物的坐标 ? x坐标必须是2的倍数 ? ?物的坐标不能和蛇?每个节点的坐标重复 ? 创建?物节点,打印?物
#define FOOD L'★'
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
//产?的x坐标应该是2的倍数,这样才可能和蛇头坐标对?。
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
//?物不能和蛇?冲突
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建?物
if (pFood == NULL)
{
perror("CreateFood::malloc()");
return;
else
{
pFood->x = x;
pFood->y = y;
SetPos(pFood->x, pFood->y);
wprintf(L"%c", FOOD);
ps->_pFood = pFood;
}
}
检测按键状态,我们封装了?个宏
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
void PrintHelpInfo()
{
//打印提?信息
SetPos(64, 15);
printf("不能穿墙,不能咬到??\n");
SetPos(64, 16);
printf("?↑.↓.←.→分别控制蛇的移动.");
SetPos(64, 17);
printf("F3 为加速,F4 为减速\n");
SetPos(64, 18);
printf("ESC :退出游戏.space:暂停游戏.");
}
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;
pNextNode->y = ps->_pSnake->y;
}
break;
}
//如果下?个位置就是?物
if (NextIsFood(pNextNode, ps))
{
EatFood(pNextNode, ps);
}
else//如果没有?物
{
NoFood(pNextNode, ps);
}
KillByWall(ps);
KillBySelf(ps);
}