一.技术要点:
贪吃蛇需要掌握:
c语言函数,枚举,结构体,动态内存管理,预处理指令,链表,Win32 API等
二.Win32 API
1.Win32 API简介
windows可以帮应用程序卡其视窗,描绘图案,使用周边设备,,Win32 API就是windows32位平台上的应用程序编程接口
2.控制台程序
(1).使用cmd命令设置控制台窗口的长宽
输入mode con cols=100 lines=30
(2.)可以通过命令设置控制台的窗口的名字
title xxx
(3.)C语言的system函数实现控制台的命令
#include<stdio.h>
#include<stdlib.h>
int main()
{
system("mode con cols=50 lines=20");
system("title 贪吃蛇");
system("pause");
return 0;
}
(4.)控制台屏幕上的坐标
COORD是windows API中定义的结构体,表示一个字符在控制台屏幕缓冲区上的坐标,坐标(0,0)位于缓冲区左上角
CORRD类型声明:
typedef struct _CORRD
{
SHORT X;
SHORT Y;
}CORRD, *PCORRD;
给结构体定坐标:
#include <windows.h>
CORRD pos = { 10,15 };
(5.)GetStdHandle获取设备
它也是一个windows API函数,用于从一个特定的标准设备(标准输入,标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备
函数的声明:
HANDLE GetStdHandle(DWORD nStdHandle);
eg.
HANDLE hOutput = NULL;
//获取标准输出的句柄
hOutput = GetStHandle(STD_OUTPUT_HANDLE);
(6.)GetConsoleCursorInfo获取光标信息
检索有关指定控制台屏幕缓冲区的光标大小和可见性信息(鼠标信息)
函数的声明:
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO结构的指针,该结构接受有感主机游标(光标)的信息
eg.
#include <windows.h>
int main()
{
CONSOLE_CURSOR_INFO cursor_info = { 0 };
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo(handle,&cursor_info);
return 0;
}
CONSOLE_CURSOR_INFO是结构体,其中有两个成员,:bVisible和dwSize分别控制光标的可可见性和大小
(7.)SetConsoleCursorPosition设置光标位置
void SetPos(int x,int y)
{
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标位置
CORRD pos = { x,y };
SetConsoleCursorPosition(handle,pos);
}
(8.)GetAsyncKeyState获取按键情况
想要判断一个键是否被按,可以检测GetAsyncKeyState返回值的最低为是否为1
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
eg.
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
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");
......
}
}
这样可以在按下数字键是在屏幕上输出数字
三.贪吃蛇游戏的设计与分析
一.地图
1.<locale.h>本地化
<locale.h>提供的函数用于控制C标准库中对于不同地区会产生不一样的行为的部分
在标准中,依赖地区的部分有以下几项:
1.数字量的格式
2.货币量的格式
3.字符集
4.日期和时间的表示形式
2.类项
1.LC_COLLATE:影响字符串比较函数strcoll()和strxfrm()
2.LC_CTYPE:影响字符处理函数的行为
3.LC_MONCTARY:影响货币格式
4.LC_NUMERIC:影响printf()的数字格式
5.LC_TIME:影响时间格式strftime()和wcsfting()
6.LC_ALL:针对所有类项修改,将以上的多有类别设置为给定的语言环境
3.setlocale函数
该函数用于修改当前的地区,可以针对一个类项,也可以针对所有类项
函数声明:
char* setlocale(int category,const char* locale);
第一个参数是修改的类项,第二个为地区
C的标准给了第二个参数2种可能取值:1."C"(正常模式) 2.""(本地模式)
在任意程序执行开始时,会隐藏执行调用setlocale(LC_ALL,"C");
第二个参数也可以传入NULL,通过传入NULL可以查询默认的本地信息
4.宽字符的打印
一个宽字符占领个字符的位置
int main()
{
setlocale(LC_ALL,"");
wchar_t ch = L'中国';
wprintf(L"%lc",ch);
return 0;
}
5.地图坐标
可以假设设计一个27行,58列的地图并围绕地图画出墙壁,如下
二.蛇身和食物
蛇身
可以初始化蛇身长度为5,在随机坐标出现蛇,连续五个节点代表蛇身
注意:为了防止社的一个节点的一半出现在墙体里,另一半在墙外,最好让蛇身的每一个节点的X坐标为2的倍数
食物
在墙体内随机生成一个坐标(x的坐标必须为2的倍数),同时坐标不能与蛇身重合
如图:
三.数据结构设计
一.蛇身节点
蛇身可以使用链表设计,需要存储的信息为当前节点的蛇身的坐标(x,y)和下一个节点
typedef struct snakenode
{
int x;
int y;//节点坐标
struct snake* next;
}snakenode, * psnakenode;
二.贪吃蛇状态
//游戏状态
enum GAME_STATUS
{
OK = 1,//正常进行
ESC,//退出
KILL_BY_WALL,//撞墙
KILL_BY_SELF//撞到自己
};
//运动方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
typedef struct snake
{
psnakenode psnake;//维护整条蛇的指针
psnakenode pfood; //指向食物的指针
int score; //当前累计的分数
int foodweight; //一个食物的分数
int sleeptime; //蛇休眠的时间.休眠时间越短,蛇移动的速度越快
//游戏当前的状态
enum GAME_STATUS status;
//蛇的运动方向
enum DIRECTION dir;
}snake,psnake;
三.开始游戏
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"
//设置光标位置
void SetPos(int x, int y)
{
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标位置
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
void welcome()
{
//欢迎信息
SetPos(35,10);
printf("欢迎来到贪吃蛇");
SetPos(37,20);
system("pause");//请按任意键继续
system("cls");//清理屏幕上的信息
//功能介绍
SetPos(15, 10);
printf("用↑,↓,←,→来控制蛇的移动,F3加速,F4减速");
SetPos(15, 11);
printf("加速可获得更高的分数");
SetPos(37, 20);
system("pause");
system("cls");
}
void creativemap()
{
int i = 0;
//上
SetPos(0, 0);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 0; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
system("pause");
}
void gamestart(psnake ps)
{
//设置控制台信息,窗口大小,窗口名
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取光标信息
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);
//打印欢迎信息
welcome();
//绘制地图
creativemap();
}
绘制地图:
void creativemap()
{
int i = 0;
//上
SetPos(0, 0);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 0; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
蛇的初始化
void InitSnake(psnake ps)
{
//创建5个蛇身节点
psnakenode pcur = NULL;
int i = 0;
for (i = 0; i < 5; i++)
{
pcur = (psnakenode)malloc(sizeof(snakenode));
if (pcur == NULL)
{
perror("InitSnake():malloc()");
return;
}
pcur->x = POS_X + 2 * i;
pcur->y = POS_Y;
pcur->next = NULL;
//进行头插
if (ps->psnake == NULL)
{
ps->psnake = pcur;
}
else
{
pcur->next = ps->psnake;
ps->psnake = pcur;
}
}
//打印蛇身
pcur = ps->psnake;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
//贪吃蛇的其他信息
ps->dir = RIGHT;
ps->foodweight = 1;
ps->pfood = NULL;
ps->score = 0;
ps->sleeptime = 200;//0.2秒
ps->status = OK;
getchar();
}
食物的初始化:
1.食物在地图中应该是随机出现的,因此食物的坐标需要随机
2.食物的坐标必须出现在墙内(2<=x<=54,1<=y<=15)
3.食物不能出现在蛇身上
void creatfood(psnake ps)
{
int x;
int y;
again:
do
{
x = rand() % 52 + 2;//x:(0 - 52) + 2
y = rand() % 24 + 1;//y:(0 - 24) + 1
//当x为奇数时会出现问题,因为蛇身始终处在偶数位
} while (x % 2 != 0);//当x为偶数时生成成功,若为奇数,重新生成
//坐标和蛇身坐标比较
//如果与蛇身重合,则重新生成
psnakenode pcur = ps->psnake;
while (pcur)
{
if (x == pcur->x && y == pcur->y)
{
goto again;
}
pcur = pcur->next;
}
//创建食物
psnakenode pfood = (psnakenode)malloc(sizeof(snakenode));
if (pfood == NULL)
{
perror("CreateFood()::malloc()");
return;
}
pfood->x = x;
pfood->y = y;
ps->pfood = pfood;
SetPos(x, y);
wprintf(L"%lc", FOOD);
getchar();
}
游戏的运行:
void pause()
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
int NextIsFood(psnake ps, psnakenode pnext)
{
if (ps->pfood->x == pnext->x && ps->pfood->y == pnext->y)
{
return 1;
}
else
return 0;
}
void EatFood(psnake ps, psnakenode pnext)
{
//ps->psnake是头节点
pnext->next = ps->psnake;
ps->psnake = pnext;
//打印蛇身
psnakenode pcur = ps->psnake;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
ps->score += ps->foodweight;
//删除旧的食物
free(ps->pfood);
//新建食物
creatfood(ps);
}
void NotEatFood(psnake ps, psnakenode pnext)
{
//先头插
pnext->next = ps->psnake;
ps->psnake = pnext;
//删除尾节点
//同时打印蛇身
psnakenode pcur = ps->psnake;
while (pcur->next->next!= NULL)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
//将尾节点变为空白字符
SetPos(pcur->next->x, pcur->next->y);
printf(" ");
free(pcur->next);
pcur->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 pcur = ps->psnake->next;
while (pcur)
{
if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y)
{
ps->status = KILL_BY_SELF;
return;
}
pcur = pcur->next;
}
}
void snakemove(psnake ps)
{
psnakenode pnext = (psnakenode)malloc(sizeof(snakenode));
if (pnext == NULL)
{
perror("snakemove()::malloc()");
return;
}
switch (ps->dir)
{
case UP:
pnext->x = ps->psnake->x;
pnext->y = ps->psnake->y - 1;
break;
case DOWN:
pnext->x = ps->psnake->x;
pnext->y = ps->psnake->y + 1;
break;
case LEFT:
pnext->x = ps->psnake->x - 2;
pnext->y = ps->psnake->y;
break;
case RIGHT:
pnext->x = ps->psnake->x + 2;
pnext->y = ps->psnake->y;
break;
}
//下一个坐标是否是食物
if (NextIsFood(ps, pnext))
{
//是食物
EatFood(ps, pnext);
}
else
{
//不是食物,正常向下走
NotEatFood(ps, pnext);
}
KillByWall(ps);
KillBySelf(ps);
}
void gamerun(psnake ps)
{
//打印帮助信息
PrintHelpInfo();
//对于游戏的循环结构,一般使用do while
do
{
//当前分数
SetPos(62, 10);
printf("总分:%6d\n", ps->score);
SetPos(62, 11);
printf("食物分值:%02d\n", ps->foodweight);
//检测按键
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_ESCAPE))
{
ps->status = ESC;
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = ESC;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停与恢复
pause();
}
else if (KEY_PRESS(VK_F3))
{
if(ps->sleeptime >= 50)
{
ps->sleeptime -= 50;
ps->score += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
if (ps->sleeptime <= 400)
{
ps->sleeptime += 50;
ps->foodweight -= 2;
}
}
//睡眠一会
Sleep(ps->sleeptime);
//走一步
snakemove(ps);
} while (ps->status == OK);
}
四.游戏结束
void GameEnd(psnake ps)
{
SetPos(16, 12);
switch (ps->status)
{
case ESC:
printf("退出游戏");
break;
case KILL_BY_WALL:
printf("撞墙了");
break;
case KILL_BY_SELF:
printf("吃到自己了");
break;
}
psnakenode pcur = ps->psnake;
psnakenode del = NULL;
while (pcur)
{
del = pcur;
pcur = pcur->next;
free(del);
}
free(ps->pfood);
ps = NULL;
}