一.技术要点:
贪吃蛇需要掌握:
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) + 2y = 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;}elsereturn 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 whiledo{//当前分数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;
}