目录
- 游戏背景
- 游戏效果展示
- 基本功能
- 技术要点
- WIN32 API介绍
- 设计与分析
- 实现
- 参考代码
1. 游戏背景
贪吃蛇是久负盛名的游戏,是一款经典游戏
2. 效果展示
3. 基本功能
使用c语言在windows环境的控制台模拟实现小游戏贪吃蛇
基本的功能:
- 地图绘制
- 吃食物
- 上下左右移动
- 撞墙、撞自己死亡
- 计算得分
- 加速、减速
- 暂停游戏
4. 要点
c语言函数、枚举、结构体、动态内存管理、预处理指令、链表。Win32API等
5. Win32API介绍
5.1 Win32API
windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为 Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口
5.2 控制台程序
平常的黑框程序就是控制台程序
可以使用cmd命令来控制控制台窗口的长宽: 设置控制台窗口的大小,30行,100列
mode con cols=100 lines=30
也可以设置控制台窗口的名字
title 贪吃蛇
这些能在控制台窗口执行的命令,也可以调用c语言函数system执行
#include <stdio.h>
int main()
{//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列system("mode con cols=100 lines=30");//设置cmd窗⼝名称system("title 贪吃蛇"); return 0;
}
5.3 控制台屏幕上的坐标COORD
COORD是windows api定义的一个结构体,表示一个字符在控制台屏幕上的坐标
typedef struct _COORD{
SHORT X;
SHORT Y;
} COORD,*PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };
5.4 GetStdHandle
用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备
HANDLE GetStdHandle (DWORD nStdHandle) ;
HANDLE hQutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)
hQutput = GetStdHandle(STD_OUTPUT_HANDLE);
5.5 GetConsoleCursorInfo
检测有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
)
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);
CONSOLE_CURSOR_INFO
这个结构体,包含控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO , *PCONSOLE_CURSOR_INFO;
- dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条
- bVisble,游标的可见性,可见设置为true
CursorInfo.bVisible = false; //隐藏控制台光标
5.7 SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将要设置的坐标信息放在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);//设置标准输出上光标的位置posSetConsoleCursorPosition(hOutput, pos);
}
5.8 GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey;
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态
GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置1说明,该按键被按过,否则是0
如果判断一个键是否被按过,可以检测这个函数的返回值的最低位是否为1
GetAsyncKeyState(VK) & 0x1 == 1
//宏定义
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? ! : 0)
6. 设计与分析
6.1 地图
如下图
想在控制台指定位置输出信息,必须知道该位置的坐标
如下图所示,横向的是x轴,从左右次依次增长,纵向是y轴,从上到下依次增长
在游戏地图上,打印墙体使用宽字符:□,蛇用宽字符:● 食物用宽字符:★
普通的字符是占一个字节的,这类宽字符是占用2个字节
c语言字符默认采用ASCII编码,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含了128个字符,在英语国家中,128个字符基本够用,但是在其他国家语言中,就无法表示。一些欧洲国家用最高位编入新的符号,比如,法语é的编码是130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,又出现了新的问题。不同的国家有不同的字母,哪怕都是用256个符号的编码方式,同一个码表示的字符确不一样,不管怎么样,0-127表示的符号是一样的,不一样的只是128-255这一段
至于亚洲国家的文字,需要的符号就更多了,汉字就多达10万左右,一个字节肯定不够,就必须使用多个字节表达一个符号。简体中文常见的编码方式是GB2312,使用后两个字节表示一个汉字,理论上最多可以表示256×256=65536个符号
后来为了使c语言国际化,不断加入了国家,用宽字节类型wchar_t和宽字节的输入和输出函数,加入和<locale.h>头文件,提供了设置特定的地区(通常是国家或者特定语言的地理区域)调整程序行为的函数
6.1.1 <locale.h>本地化
<locale.h>提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分
在标准中,依赖地区的部分有以下几项:
- 数字量的格式
- 货币量的格式
- 字符集
- 日期和时间的表现形式
6.1.2 类项
通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分不希望修改,支持堆不同的类项修改,下面的宏指定类项
- LC_COLLATE
- LC_CTYPE
- LC_NUMERIC
- LC_TIME
- LC_ALL -针对所有类项修改
6.1.3 setlocale函数
char* setlocale (int category, cosnt char* locale) ;
setlocale 函数用来修改当前地区,可以针对一个类项,也可以针对所有类项
setlocale 的第一个参数可以是前面说明的类型中的一个,那么每次只会影响一个类型,如果第一个参数是LC_ALL,就会影响所有的类项
c标准给第二个参数定义了2种可能取值: “C"和” ",任意程序执行开始,都会隐藏式执行调用
setlocale(LC_ALL, “C” ) ;
当地区设置为C,库函数按正常方式执行,小数点是一个点
想改变地区,只能显示调用函数,用“ ”作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符的输出
setlocale (LC_ALL, “”);
6.1.4 宽字符的打印
#include <stdio.h>
#include <locale.h>int main()
{setlocale(LC_ALL, "");wchar_t ch1 = L'●';wchar_t ch2 = L'★';printf("%c%c", 'a', 'b');printf("%c%c\n", 'a', 'b');wprintf(L"%c",ch1);wprintf(L"%c",ch2);
}
从结果看出,一个普通字符占一个位置,汉字字符占两个位置,所以打印宽字符地图,需要计算好坐标
从左上角的00点开始,和控制台边框空出一段间隔,x从2开始打印,y从1开始打印,宽字符一个字符占两列,地图19行44列,从上面的起点算,x从2-45,y从1-19,四周最边的一面为墙
6.2 蛇身和食物
初始化状态,假设蛇的长度是3,蛇身的每个节点●,在固定的坐标处,y在第一列,横坐标放在地图的中间。需要注意,每个节点的x坐标必须是2的倍数,否则就会和墙体对不齐
食物是在墙体内随机生成一个坐标,x坐标也必须是2的倍数对齐,同时不能和蛇身重合,打印★
6.3 数据结构设计
在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标和下一个结点就行
首先得有一个坐标的结构体
struct Point
{int x;int y;
};
蛇节点的结构体
//蛇身结点
typedef struct _SnakeNode
{struct Point spoint; //坐标struct _SnakeNode* next;
}SnakeNode, * pNode;
有了蛇身的结构,还需要管理整条蛇的属性信息,比如速度、蛇头等
//蛇属性
typedef struct _Snake
{pNode phead; //蛇头int score; //分数int weight; //权重int speed; //移动间隔enum Dorection dre; //方向enum STATUS sta; //状态
}* pSnake;
蛇的方向和状态用枚举出所有可能得结果
//方向
enum Dorection
{UP,DOWN,LEFT,RIGHT,
};
//状态
enum STATUS
{OK = 1,PAUSE,KILL_WALL,KILL_SELF,ESC,
};
6.4 游戏流程设计
7. 实现
7.1 主逻辑
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Sanke.h"int main()
{int ch = 0;do{//创建蛇struct _Snake snake;//创建食物struct Point food;//初始化GameInit(&snake, &food);//游戏循环GameRun(&snake, &food);//游戏结束GameEnd(&snake);MoveCursor(16, 11);printf("再来一局吗?(Y/N)");while (_kbhit()){getch();}ch = getchar();//读取换行getchar();} while (ch == 'Y' || ch == 'y');MoveCursor(0, 21);return 0;
}
7.2 初始化信息
void GameInit(pSnake snake, struct Point* food)
{//控制台设置system("mode con cols=82 lines=23");system("title 贪吃蛇");srand(time(0));HideCursor();菜单Menu();
7.2.1 打印菜单界面
开始之前,做一些功能提醒
void Menu()
{//欢迎MoveCursor(30, 10);puts("欢迎来到贪吃蛇游戏");MoveCursor(31, 18);system("pause");system("cls");//功能介绍MoveCursor(15, 8);puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");MoveCursor(18, 10);printf("加速能得到更高的分数");MoveCursor(31, 18);system("pause");system("cls");
}
7.2.2 创建地图
将墙打印出来,宽字节打印要用wprintf,字符串前面用L
关键是算好坐标
#define WALL L’□’
//地图大小
#define ROW 19
#define COL 44
void DrawMap()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);//四周for (int i = 0; i < COL; i += 2){MoveCursor(2 + i, 1);wprintf(L"%lc", WALL);MoveCursor(2 + i, 1 + ROW);wprintf(L"%lc", WALL);}for (int i = 0; i < ROW; i++){MoveCursor(2, 2 + i);wprintf(L"%lc", WALL);MoveCursor(COL, 2 + i);wprintf(L"%lc", WALL);}printf("\r\n");
}
7.2.3 初始化蛇身
蛇的初始长度宏定义,每结对应一个节点,每个节点都有自己的坐标,将所有节点用蛇头节点管理,创建完后打印。设置蛇的速度,方向,分数等
蛇打印的宽字符
#define SNAKE L’●’
//初始化蛇
snake->phead = NULL;
for (int i = 0; i < LENGTH; i++)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;newnode->spoint.x = SNAKE_X + i * 2;newnode->spoint.y = SNAKE_Y;//头插法if (snake->phead == NULL){snake->phead = newnode;}else{newnode->next = snake->phead;snake->phead = newnode;}}
snake->score = 0;
snake->weight = 10;
snake->speed = 500;
snake->dre = RIGHT;
snake->sta = OK;
7.2.4 创建第一个食物
随机生成食物的坐标
- x坐标必须是2的倍数
- 食物的坐标不能和蛇身每个节点的坐标重复
创建食物对象,打印食物
食物打印的宽字符
#define FOOD L’★’
首先食物的坐标不能生成在墙上,所以x坐标从4-42,就是先生成0-38的随机数,再加4,y坐标就是2-18,先生成0-16的随机数再加2。再判断是不是在蛇身上,下面是食物坐标的生成
void FoodPoint(struct Point* food , pSnake snake)
{pNode cur = snake->phead;int flag = 0;do{food->x = rand() % (COL - 5) + 4;food->y = rand() % (ROW - 2) + 2;flag = 1;while (cur != NULL){if (food->x == cur->spoint.x && food->y == cur->spoint.y){flag = 0;break;}cur = cur->next;}} while (food->x % 2 != 0 && flag == 1);}
7.3 游戏运行
右侧打印帮助信息
根据游戏状态检查是否继续,如果状态OK则继续,否则游戏结束
如果继续就要检测按键情况,确定蛇下一步的方向和坐标,是否加速减速暂停退出等,就可以移动蛇了
void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}
7.3.1 KEY_PRESS
检测按键,定义为宏
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
7.3.2 PrintHelpInfo
void PrintHelp()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);MoveCursor(49, 12);printf("1.不能穿墙,不能咬到自己");MoveCursor(49, 14);printf("2.用 w.s.a.d 来控制蛇的移动");MoveCursor(49, 16);printf("3.F3是加速,F4是减速");}
7.3.3 移动蛇
先创建下一个节点,根据移动的方向和蛇头的坐标,设置新蛇身的坐标
确定之后就要分下一个位置是不是食物,是食物就吃掉,刷新分数,重新生成食物不是就移动
将新节点连接蛇结构
移动后判断是否撞墙或撞自身,影响游戏状态结束游戏
void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}void MoveSnake(pSnake snake, struct Point* food)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;switch (snake->dre){case UP:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y - 1;break;case DOWN:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y + 1;break;case LEFT:newnode->spoint.x = snake->phead->spoint.x - 2;newnode->spoint.y = snake->phead->spoint.y;break;case RIGHT:newnode->spoint.x = snake->phead->spoint.x + 2;newnode->spoint.y = snake->phead->spoint.y;break;}//下一个坐标是不是食物if (newnode->spoint.x == food->x && newnode->spoint.y == food->y){EatFood(snake, food);}else{//释放尾结点pNode cur = snake->phead;while(cur->next->next != NULL){cur = cur->next;}//尾结点处打印空MoveCursor(cur->next->spoint.x, cur->next->spoint.y);printf(" ");free(cur->next);cur->next = NULL;}newnode->next = snake->phead;snake->phead = newnode;//检测撞KillWall(snake);KillSelf(snake);DrawSnake(snake);}
吃掉食物更新分数,重新生成食物
void EatFood(pSnake snake, struct Point* food)
{FoodPoint(food, snake);snake->score += snake->weight;DrawFood(food);
}
7.3.4 撞墙
void KillWall(pSnake snake)
{int x = snake->phead->spoint.x;int y = snake->phead->spoint.y;if (x == 2 || x == 44 || y == 1 || y == 19){snake->sta = KILL_WALL;return;}
}
7.3.5 撞自己
void KillSelf(pSnake snake)
{pNode cur = snake->phead->next;while (cur != NULL){if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y){snake->sta = KILL_SELF;return;}cur = cur->next;}
}
7.4 游戏结束
当游戏状态不再是OK或PAUSE的时候,游戏结束,告知结束的原因,并释放蛇身节点
void GameEnd(pSnake snake)
{MoveCursor(18, 9);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);switch (snake->sta){case ESC:printf("退出游戏");break;case KILL_WALL:printf("撞墙死亡");break;case KILL_SELF:printf("撞自己死亡");break;}//释放资源pNode cur = snake->phead;while (cur){pNode del = cur;cur = cur->next;free(del);}
}
8. 参考代码
源.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Sanke.h"int main()
{int ch = 0;do{//创建蛇struct _Snake snake;//创建食物struct Point food;//初始化GameInit(&snake, &food);//游戏循环GameRun(&snake, &food);//游戏结束GameEnd(&snake);MoveCursor(16, 11);printf("再来一局吗?(Y/N)");while (_kbhit()){getch();}ch = getchar();//读取换行getchar();} while (ch == 'Y' || ch == 'y');MoveCursor(0, 21);return 0;
}
Sanke.h
#pragma once//地图大小
#define ROW 19
#define COL 44//各种物体形状
#define WALL L'□'
#define SNAKE L'●'
#define FOOD L'★'//蛇的初始长度
#define LENGTH 3
//蛇初始位置
#define SNAKE_X COL / 2 + 2
#define SNAKE_Y 4//按键
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//方向
enum Dorection
{UP,DOWN,LEFT,RIGHT,
};
//状态
enum STATUS
{OK = 1,PAUSE,KILL_WALL,KILL_SELF,ESC,
};
struct Point
{int x;int y;
};
//蛇身结点
typedef struct _SnakeNode
{struct Point spoint; //坐标struct _SnakeNode* next;
}SnakeNode, * pNode;//蛇属性
typedef struct _Snake
{pNode phead; //蛇头int score; //分数int weight; //权重int speed; //移动间隔enum Dorection dre; //方向enum STATUS sta; //状态
}* pSnake;//隐藏光标
void HideCursor();
//移动光标
void MoveCursor(int x, int y);
//菜单界面
void Menu();
//食物坐标随机检测
void FoodPoint(struct Point* food, pSnake snake);
//游戏初始化
void GameInit(pSnake snake, struct Point* food);
//绘制地图
void DrawMap();
//绘制蛇
void DrawSnake(pSnake snake);
//绘制食物
void DrawFood();
//游戏暂停
void GamePause(pSnake snake);
//游戏循环
void GameRun(pSnake snake, struct Point* food);
//移动
void MoveSnake(pSnake snake, struct Point* food);
//吃食物
void EatFood(pSnake snake, struct Point* food);
//打印帮助信息
void PrintHelp();
//检测撞墙
void KillWall(pSnake snake);
//检测撞自己
void KillSelf(pSnake snake);
//游戏结束释放资源
void GameEnd(pSnake snake);
Sanke.c
#include "Sanke.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <locale.h>void HideCursor()
{CONSOLE_CURSOR_INFO info;HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &info);info.bVisible = false;SetConsoleCursorInfo(handle, &info);
}void MoveCursor(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD coord = { x , y };SetConsoleCursorPosition(handle, coord);
}void Menu()
{//欢迎MoveCursor(30, 10);puts("欢迎来到贪吃蛇游戏");MoveCursor(31, 18);system("pause");system("cls");//功能介绍MoveCursor(15, 8);puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");MoveCursor(18, 10);printf("加速能得到更高的分数");MoveCursor(31, 18);system("pause");system("cls");
}void FoodPoint(struct Point* food , pSnake snake)
{pNode cur = snake->phead;int flag = 0;do{food->x = rand() % (COL - 5) + 4;food->y = rand() % (ROW - 2) + 2;flag = 1;while (cur != NULL){if (food->x == cur->spoint.x && food->y == cur->spoint.y){flag = 0;break;}cur = cur->next;}} while (food->x % 2 != 0 && flag == 1);}void GameInit(pSnake snake, struct Point* food)
{//控制台设置system("mode con cols=82 lines=23");system("title 贪吃蛇");srand(time(0));HideCursor();菜单Menu();//初始化蛇snake->phead = NULL;for (int i = 0; i < LENGTH; i++){pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;newnode->spoint.x = SNAKE_X + i * 2;newnode->spoint.y = SNAKE_Y;//头插法if (snake->phead == NULL){snake->phead = newnode;}else{newnode->next = snake->phead;snake->phead = newnode;}}snake->score = 0;snake->weight = 10;snake->speed = 500;snake->dre = RIGHT;snake->sta = OK;//初始化食物FoodPoint(food, snake);//设置地区setlocale(LC_ALL, "");
}void DrawMap()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);//四周for (int i = 0; i < COL; i += 2){MoveCursor(2 + i, 1);wprintf(L"%lc", WALL);MoveCursor(2 + i, 1 + ROW);wprintf(L"%lc", WALL);}for (int i = 0; i < ROW; i++){MoveCursor(2, 2 + i);wprintf(L"%lc", WALL);MoveCursor(COL, 2 + i);wprintf(L"%lc", WALL);}printf("\r\n");
}void DrawSnake(pSnake snake)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);pNode cur = snake->phead;while (cur != NULL){MoveCursor(cur->spoint.x, cur->spoint.y);wprintf(L"%lc", SNAKE); cur = cur->next;}
}void DrawFood(struct Point* food)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);MoveCursor(food->x, food->y);wprintf(L"%lc", FOOD);
}void GamePause(pSnake snake)
{while (snake->sta == PAUSE){if (KEY_PRESS(VK_SPACE)){snake->sta = OK;}Sleep(100);}
}void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}void MoveSnake(pSnake snake, struct Point* food)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;switch (snake->dre){case UP:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y - 1;break;case DOWN:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y + 1;break;case LEFT:newnode->spoint.x = snake->phead->spoint.x - 2;newnode->spoint.y = snake->phead->spoint.y;break;case RIGHT:newnode->spoint.x = snake->phead->spoint.x + 2;newnode->spoint.y = snake->phead->spoint.y;break;}//下一个坐标是不是食物if (newnode->spoint.x == food->x && newnode->spoint.y == food->y){EatFood(snake, food);}else{//释放尾结点pNode cur = snake->phead;while(cur->next->next != NULL){cur = cur->next;}//尾结点处打印空MoveCursor(cur->next->spoint.x, cur->next->spoint.y);printf(" ");free(cur->next);cur->next = NULL;}newnode->next = snake->phead;snake->phead = newnode;//检测撞KillWall(snake);KillSelf(snake);DrawSnake(snake);}void EatFood(pSnake snake, struct Point* food)
{FoodPoint(food, snake);snake->score += snake->weight;DrawFood(food);
}void PrintHelp()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);MoveCursor(49, 12);printf("1.不能穿墙,不能咬到自己");MoveCursor(49, 14);printf("2.用 w.s.a.d 来控制蛇的移动");MoveCursor(49, 16);printf("3.F3是加速,F4是减速");}void KillWall(pSnake snake)
{int x = snake->phead->spoint.x;int y = snake->phead->spoint.y;if (x == 2 || x == 44 || y == 1 || y == 19){snake->sta = KILL_WALL;return;}
}void KillSelf(pSnake snake)
{pNode cur = snake->phead->next;while (cur != NULL){if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y){snake->sta = KILL_SELF;return;}cur = cur->next;}
}void GameEnd(pSnake snake)
{MoveCursor(18, 9);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);switch (snake->sta){case ESC:printf("退出游戏");break;case KILL_WALL:printf("撞墙死亡");break;case KILL_SELF:printf("撞自己死亡");break;}//释放资源pNode cur = snake->phead;while (cur){pNode del = cur;cur = cur->next;free(del);}
}
参考:汉字字符集编码查询;中⽂字符集编码:GB2312、BIG5、GBK、GB18030、Unicode