目录
二、了解一下Win32API以及当前游戏实现需要用的函数:头lude
(2)、欢迎界面&&功能介绍(关于光标定位函数在上面讲解Win32API时已经写过)
一、技术要求:
C语言的函数、结构体、单链表、动态内存管理、枚举、Win32API、预处理指令等
二、需要的功能:
游戏界面(地图绘制),蛇的身体,蛇的移动方向,蛇的状态,暂停,蛇的加速与减速,食物,一食物的分数,总分,
二、了解一下Win32API以及当前游戏实现需要用的函数:头lude<windows.h>
1、概念:
Win32 API是一套由微软提供的应用程序编程接口(Application Programming Interface)。它是Windows操作系统的核心组成部分,为开发者提供了访问操作系统底层功能和资源的接口,包括图形界面、文件操作、进程和线程管理(可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等)等。
2、控制台程序:(经常运行的黑色框框就是控制台)
VS2022的控制运行时是这样的,这个叫终端,要先改掉
设置里面默认终端应用程序改成Windows 控制台主机
就是如下qing
mode命令 设置控制台的长度和宽度
system("mode con cols=100 lines=30");
用到了system函数,用来控制控制台信息要包含头文件<stdlib.h>
title命令 设置控制台名字
system("title 贪吃蛇");
3、控制台屏幕上的坐标COORD
COORD是WindowsAPI中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0)的原点位于缓冲区的顶部左侧单元格。
如下是COORD的结构体类型声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
4、句柄 GetStdHandle 函数
句柄是啥意思???控制台窗口,你要去操作他,那你要先获得他的使用权,才能操作,相当于“手柄”,你插了自己的手柄是不是可以抓住他的,然后去实行游戏的各种状态。想要炒菜,是不是得抓住锅的把手 (相当与标识(标记))。
HANDLE是一个指针(typedef改名而来)
在函数里面有三种选择,根据需求进行选择,这里我们选择的是标准输出设备
返回的是一个值
5、光标的隐藏
GetConsoleCursorInfo 函数
获得光标信息
第一个变量是句柄,第二个是一个结构体
这个结构体 包括光标的占比和可见性
dwSize,由光标填充的字符单元格的百分⽐。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。
bVisible,光标的可⻅性
先创建一个结构体变量,然后取其地址传入光标信号获取函数
对于如下结构体变量可以初始化为0更好
修改光标需要用到头文 <stdbool.h>
结构体成员 bVisible 默认情况下是 true (开启)
SetConsoleCursorInfo 函数
你获得到信息以后,将结构体成员的值进行了修改,那么是不是还需要一个函数来完成设置
第一个是句柄,第二个是结构体
如下:你修改好光标后,传个SetConsoleCursorInfo函数,设置完成
流程如下:
6、光标的坐标修改
SetConsoleCursorPosition 函数 定位光标的坐标
第一个是HANDLE 类型也就是 句柄 第二个参数是COORD类型,就是关于坐标相关的结构体
在蛇的移动过程中,我们蛇是不断改变位置的,那么光标也是要不断变化的,可以分装一个函数来进行光标位置的定位
//定位光标
void SetPos(short x, short y)
{
//句柄(标识标准输出,屏幕缓冲区)
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//给光标进行定位
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
7、获取按键(虚拟按键)的情况
getAsyncKeyState 函数 (winuser.h)
键盘上每一个键的虚拟键都有一个虚拟值,传参的是虚拟件的代码 虚拟键代码
返回值是16位的short值,最高位是1,则是按下的,若是为0,则是抬起的情况
我们可以想到按位&1,得到的值无非是 0或者1
可以设置一个变量宏,
三、关于实现地图绘制
看下面的光标,x与y的关系是y = 2x,长方形的样子(因为默认是以单字符的形式建立的坐标系)
实际上我们打印的地图是宽字符,打印出来是占2个字节的位置(平时我们使用的是窄字符)
1、 讲解一下 C语言的国际化(地区化)
过去C语⾔并不适合⾮英语国家(地区)使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件<locale.h>,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数
setlocale 函数 第二个参数单纯使用 “” 就是本地化,“C” 就是正常模式,传递NULL 就是当前地区
setlocale(LC_ALL, " ");//切换到本地环境
setlocale(LC_ALL, "C");//正常情况
修改类别(那些调整)
LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
LC_CTYPE:影响字符处理函数的⾏为。
LC_MONETARY:影响货币格式。
LC_NUMERIC:影响 printf() 的数字格式。
LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
LC_ALL 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
2、本地化怎么用
宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls
宽字符类型 wchar_t
哪里都要加L
四、开始实现贪吃蛇:
四步走: 创建贪吃蛇 -》初始化游戏 -》运行游戏-》 游戏的结束(善后游戏)
1、贪吃蛇:
(1)、蛇身和食物:
初始情况下,蛇身是由多个节点连接起来的,那么蛇身 ● 每个节点x的坐标必须是2的倍数
不然会出现一半卡抢墙里头去了,同样的食物得节点的x坐标也必须是2的倍数
以如下坐标创建游戏
创建指链表
蛇身和食物都是节点,那么我可以写一个链表
每个节点保存的内容为坐标;*pTaSe 相当于 typedef struct TaSe* pTaSe
typedef struct TaSe {
//坐标
int x;
int y;
//指向下一个节点
struct TaSe* next;
}TaSe,* pTaSe;
(2)、蛇的运动方向(上、下、左、右)
用枚举类型来一一列举
enum DIRECTON
{
UP = 1,//上
DOWN,//下
LEFT,//左
RIGHT//右
};
(3)、蛇的状态
正常结束,撞墙,撞自己,正常游戏中
enum GAMESTATUS
{
OK,//正常进行游戏
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞自己
END_NORMAL//正常退出游戏
};
(4)、蛇的速度(加速,减速)
可以考虑修改代码运行的时间 Sleep函数
创建一个int 型变量
int _sleep_time;
(5)、食物当前的分数以及总分
//一个食物分数
int _food_wegiht;
//总分数
int _score;
整合起来
当写完这些代码后,你是否还记得结构体,结构体是将不同类型的却相关的数据集中到一起
这些都是蛇的一部,那么我们可以由此创建一个结构体:
//贪吃蛇(结构体来保存关于蛇的所有信息)
typedef struct Snake
{
//指向蛇头的指针
pTaSe _pSnak;
//指向食物的指针
pTaSe _pFond;
//蛇运动的方向
enum DIRECTON _dir;
//速度(根据时间来计算,休息越短,速度快,越长。速度慢)
int _sleep_time;
//游戏状态
enum GAMESTATUS _status;
//一个食物分数
int _food_wegiht;
//总分数
int _score;
}Snake,* pSnake;
2、初始化界面:
(1)、隐藏光标和设置大小
写一个函数先将光标隐藏掉,顺便设置控制台的名称以及长宽
void GetsdHande()
{
//打印欢迎界面和隐藏光标.x=100,y=30
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//句柄(标识标准输出,屏幕缓冲区)
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取控制台信息
//1.创建一个如下结构体变量
CONSOLE_CURSOR_INFO cursor_info;
//通过如下函数获取光标信息在结构体中
GetConsoleCursorInfo(houtput, &cursor_info);
//将光标隐藏掉
cursor_info.bVisible = false;
//传修改后的结构体地址给控制台 设置光标信息
SetConsoleCursorInfo(houtput, &cursor_info);
}
(2)、欢迎界面&&功能介绍(关于光标定位函数在上面讲解Win32API时已经写过)
写一个函数,用来写入这个界面
定位光标的位置,然后打印,打印以后用 pause 这个是暂停的意思,确保出现请按任意键继续这个字样 坐标随便改,尽量好看点
//1.欢迎界面&&介绍功能
void WelcomeToGame()
{
SetPos(40, 14);
wprintf(L"欢迎进入贪吃蛇小游戏\n");
SetPos(42, 20);
system("pause");//相当于按任意建继续
system("cls");
SetPos(25, 14);
wprintf(L"用↑ ↓ → ←来控制蛇的移动,按F3进行加速,F4进行减速\n");
SetPos(25, 15);
wprintf(L"加速可以得到更高的分数\n");
SetPos(42, 20);
system("pause");
//清除
system("cls");
}
cls 是清除的意思,你按了任意键之后,应该清楚上一个界面的
(3)、绘制地图
我们要的地图长这样:》》》》》
这个地图为例:从左到右是不是要走58个小格子(0-57),宽字符就是29个
去掉横向的 纵向则是25个格子
所以横向可以写个循环来打印 i = 0 i<29
纵向则是 i = 1,i <26
为了方便以后更改大小,可以用一个宏定义:
//墙的大小
#define WLLCOOL 29
#define WLLINE 26
那么代码如下:
//2.绘制地图
void CreateMap()
{
//上
int i;
for (i = 0; i < WLLCOOL; i++)
{
wprintf(L"%lc", WLL);
}
//下
SetPos(0, WLLINE);
for (i = 0; i < WLLCOOL; i++)
{
wprintf(L"%lc", WLL);
}
//上到下有0-26的范围即27个格子,中间就只要25个
//左(从1开始)
for (i = 1; i <= WLLINE-1; i++)
{
SetPos(0, i);
wprintf(L"%lc", WLL);
}
//右
for (i = 1; i <= WLLINE-1; i++)
{
//29*2 == 58(0-57)//2倍关系,对应的56
SetPos(56, i);
wprintf(L"%lc", WLL);
}
}
就是我们通过光标定位来打印位置
(4)、初始化蛇身:
>>要创建一个5节点的蛇身:
for循环来实现,每个节点的创建和连接
申请空间:这里的x和y我们也是用宏定义来写
我选择的是头插,先判断空还是非空链表
为了防止对链表进行操作,用cur接受链表,然后遍历并打印出蛇
初始下,蛇向右走,时间为200ms,正常游戏,一个食物分数为 10 总分为 0 代码如下
//初始化蛇身
void InitSnake(pSnake ps)
{
int i = 0;
pTaSe cur = NULL;
for (i = 0; i < 5; i++)
{
//申请节点
cur = (pTaSe)malloc(sizeof(TaSe));
if (cur == NULL)
{
perror("malloc");
return;
}
cur->next = NULL;
cur->x = POS_X+2*i;
cur->y = POS_y;
//用头插
//空
if (ps->_pSnak== NULL)
{
ps->_pSnak = cur;
}
else//非空
{
cur->next = ps->_pSnak;
ps->_pSnak = cur;
}
}
cur = ps->_pSnak;
while (cur)
{
//打印出来
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_dir = RIGHT;
ps->_food_wegiht = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_status = OK;
}
(5)、食物节点的建立
》》随机创建食物位置
坐标为随机值,想到了 rand srand time 函数,srand将伪随机值的种子变成随机值,以时间来随机
随机值
srand((unsigned int)time(NULL));
确定x和y的范围 (食物不能再墙体外和墙体) 上面墙体那里提到过,2*WLLCOOL = 58
因为rand()%10 随机值为0-9 但是x的取值范围为2-54 且需要为2的倍数
x后面注释写错了::::
所以可以用do{}while(x%2!=0); 来保证x的位置
又因为不能蛇身上可以遍历一下蛇身来保证坐标不会冲突
若发生冲突的话,我们可以使用 goto agin
//防止坐标冲突
while (cur)
{
if (cur->x==x&&cur->y==y)
{
//跳回回去重新来
goto again;
}
cur = cur->next;
}
然后就是食物节点的申请,原理和上面蛇身节点申请一摸一样,所以直接写整个代码了
//食物
void CreateFond(pSnake ps)
{
int x, y;
again:
do {
x = rand() %(2*WLLCOOL-5) + 2;//2-55
y = rand() % (WLLINE - 2) + 2;//1-25
} while (x % 2 != 0);
pTaSe cur = ps->_pSnak;
//防止坐标冲突
while (cur)
{
if (cur->x==x&&cur->y==y)
{
//跳回回去重新来
goto again;
}
cur = cur->next;
}
//创建食物的节点
pTaSe newnode = (pTaSe)malloc(sizeof(TaSe));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->x = x;
newnode->y = y;
newnode->next = NULL;
//放到结构体
ps->_pFond = newnode;
SetPos(x, y);
wprintf(L"%lc", FOND);
}
(6)、整合下来:初始化
void GameStrat(pSnake ps)
{
//0.隐藏光标和设置大小
GetsdHande();
//1.欢迎界面&&介绍功能
WelcomeToGame();
//2.绘制地图
CreateMap();
//初始化蛇身
InitSnake(ps);
//创建食物
CreateFond(ps);
}
3、游戏运行
(1)、帮助信息
告诉你规则:
那么不就是定位光标,然后打印
//帮助信息
void PrintHelpInfo()
{
SetPos(COL, ROW);
wprintf(L"%ls", L"蛇不能穿墙,不能咬到自己");
SetPos(COL, ROW+1);
wprintf(L"用↑ ↓ → ←来控制蛇的移动");
SetPos(COL, ROW + 2);
wprintf(L"%ls", L"按F3进行加速,F4进行减速");
SetPos(COL, ROW + 3);
wprintf(L"加速可以得到更高的分数");
SetPos(COL, ROW + 4);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
还是宏定义,为了以后便于修改,不做赘述!!!
(2)、开始写蛇的运动
要想完成蛇一直在走,那么就得不断进行循环(do{}while()),节点位置不断发生改变才行,那么蛇怎么走呢通过虚拟按键实现,我们可以通过对虚拟按键的判断,来实现你运动方向
运动方向
注意的是你按下了向左,不能再按向右
同理,向上也是
那么,暂停呢?如何实现·
写一个函数,当你按了暂停时进入其中,while循环,Sleep()时间你可以写更长,让他不动,但是你在按一次暂停,此时就返回1,击破死循环;
加速:
加速无非就是将运行停顿时间减速
同理,减速就是将时间加长
当然不能太慢,也不能太快,太快时间小于0是不行的,会变成bug滴
退出:
正常退出,在蛇运动的循环里,只要将循环条件改成 运动状态为正常 即可
蛇的一步一步走:
先申请一个节点,这个节点存放你走下一步的坐标,要switch语句来实现,因为你会走哪一步要根据你创建的蛇身结构体中表示运动方向的情况决定的,所以确定以后在哪坐标的更改
将下一个节点创建好以后,我们用头插,将其弄到蛇里面
但是啊,下个节点是啥呢???是食物或者不是食物结果是不一样的,是食物就得变长,不是食物就得删掉尾节点
判断下一节点是不是食物:
传蛇身(结构体)和下一个节点
若是与食物得坐标相同则返回0
写函数来判断,不同结果进行不同操作
若是食物: 吃掉便是
无非就是头差,注意要光标定位,因为你要打印蛇身哦,吃完了,得加分以及在出现一个食物
不是食物:截断屁股
头插,插完以后,尾删,再打印,不要忘记销毁尾节点
蛇走的代码如下:
void SnakeMove(pSnake ps)
{
//先申请一个空间存放,要前进的那个节点
pTaSe node = (pTaSe)malloc(sizeof(TaSe));
if (node == NULL)
{
perror("malloc");
return;
}
node->next = NULL;
switch (ps->_dir)
{
case UP:
node->x = ps->_pSnak->x;
node->y = ps->_pSnak->y - 1;
break;
case DOWN:
node->x = ps->_pSnak->x;
node->y = ps->_pSnak->y + 1;
break;
case LEFT:
node->x = ps->_pSnak->x - 2;
node->y = ps->_pSnak->y;
break;
case RIGHT:
node->x = ps->_pSnak->x + 2;
node->y = ps->_pSnak->y;
break;
}
//判断下个节点是食物么???
int ne = NextIsFood(ps, node);
if (ne)
{
EatFood(ps, node);
node = NULL;
}
else
{
NodeFood(ps, node);
}
//撞墙
KillByWall(ps);
//撞到自己
KillBySelf(ps);
}
状态也要判断一次无非就是和自己的节点坐标以及墙的节点坐标判断一下就行了!!!
总结:全部代码
void GameRun(pSnake ps)
{
//帮助信息
PrintHelpInfo();
do
{
//打印总分和食物分数
SetPos(COL, ROW - 5);
wprintf(L"当前食物分数:%2ld",ps->_food_wegiht);
SetPos(COL, ROW - 4);
wprintf(L"总分:%ld",ps->_score);
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_F3))
{
//加速
if (ps->_sleep_time > 80)
{
ps->_sleep_time -= 30;
ps->_food_wegiht += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速:若是分数都少于2了,不能再减速了
if (ps->_food_wegiht>2)
{
//时间越长,速度越慢
ps->_sleep_time += 30;
ps->_food_wegiht -= 2;
}
}
else if(KEY_PRESS(VK_ESCAPE))
{
//ESC退出(正常退出状态)
ps->_status = END_NORMAL;
}
//蛇走一步的状态
SnakeMove(ps);
//走一步的时间
Sleep(ps->_sleep_time);
} while (ps->_status==OK);
4、结束游戏(善后工作)
善后么,无非就是销毁节点之类的,动态内存要还回去了呗
(1)、分状态提示你结束游戏的原因:
又是switch 根据你的游戏状态进行判断
(2)、销毁游戏
不就链表的销毁么??在链表那里怎么学的就怎么用呗,一个循环
五、总代码:
TaSe.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<stdbool.h>
#include<Windows.h>
#include<time.h>
//写关于贪吃蛇的链表
//节点(蛇身)
typedef struct TaSe {
//坐标
int x;
int y;
//指向下一个节点
struct TaSe* next;
}TaSe,* pTaSe;
//方向
enum DIRECTON
{
UP = 1,//上
DOWN,//下
LEFT,//左
RIGHT//右
};
//状态
enum GAMESTATUS
{
OK,//正常进行游戏
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞自己
END_NORMAL//正常退出游戏
};
//贪吃蛇(结构体来保存关于蛇的所有信息)
typedef struct Snake
{
//指向蛇头的指针
pTaSe _pSnak;
//指向食物的指针
pTaSe _pFond;
//蛇运动的方向
enum DIRECTON _dir;
//速度(根据时间来计算,休息越短,速度快,越长。速度慢)
int _sleep_time;
//游戏状态
enum GAMESTATUS _status;
//一个食物分数
int _food_wegiht;
//总分数
int _score;
}Snake,* pSnake;
//坐标
void SetPos(short x, short y);
//初始化游戏(传地址)
void GameStrat(pSnake);
//0.隐藏光标和设置大小
void GetsdHande();
//1.欢迎界面&&介绍功能
void WelcomeToGame();
//2.绘制地图
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFond(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//是否是食物
int NextIsFood(pSnake ps, pTaSe node);
//蛇走一步
void SnakeMove(pSnake ps);
//结束游戏
void GameEnd(pSnake ps);
TaSe.c文件
#define _CRT_SECURE_NO_WARNINGS 3
#include"Tase.h"
#define COOL 100
#define LINE 30
//墙的大小
#define WLLCOOL 29
#define WLLINE 26
//图标
#define WLL L'□'
#define BODY L'●'
#define FOND L'★'
//初始坐标
#define POS_X 24
#define POS_y 5
//帮助信息的行 列
#define ROW 15
#define COL 64
//虚拟按键判断是否按下
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//定位光标
void SetPos(short x, short y)
{
//句柄(标识标准输出,屏幕缓冲区)
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//给光标进行定位
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
//0.隐藏光标和设置大小
void GetsdHande()
{
//打印欢迎界面和隐藏光标.x=100,y=30
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//句柄(标识标准输出,屏幕缓冲区)
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取控制台信息
//1.创建一个如下结构体变量
CONSOLE_CURSOR_INFO cursor_info;
//通过如下函数获取光标信息在结构体中
GetConsoleCursorInfo(houtput, &cursor_info);
//将光标隐藏掉
cursor_info.bVisible = false;
//传修改后的结构体地址给控制台 设置光标信息
SetConsoleCursorInfo(houtput, &cursor_info);
}
//1.欢迎界面&&介绍功能
void WelcomeToGame()
{
SetPos(40, 14);
wprintf(L"欢迎进入贪吃蛇小游戏\n");
SetPos(42, 20);
system("pause");//相当于按任意建继续
system("cls");
SetPos(25, 14);
wprintf(L"用↑ ↓ → ←来控制蛇的移动,按F3进行加速,F4进行减速\n");
SetPos(25, 15);
wprintf(L"加速可以得到更高的分数\n");
SetPos(42, 20);
system("pause");
//清除
system("cls");
}
//2.绘制地图
void CreateMap()
{
//上
int i;
for (i = 0; i < WLLCOOL; i++)
{
wprintf(L"%lc", WLL);
}
//下
SetPos(0, WLLINE);
for (i = 0; i < WLLCOOL; i++)
{
wprintf(L"%lc", WLL);
}
//上到下有0-26的范围即27个格子,中间就只要25个
//左(从1开始)
for (i = 1; i <= WLLINE-1; i++)
{
SetPos(0, i);
wprintf(L"%lc", WLL);
}
//右
for (i = 1; i <= WLLINE-1; i++)
{
//29*2 == 58(0-57)//2倍关系,对应的56
SetPos(56, i);
wprintf(L"%lc", WLL);
}
}
//初始化蛇身
void InitSnake(pSnake ps)
{
int i = 0;
pTaSe cur = NULL;
for (i = 0; i < 5; i++)
{
//申请节点
cur = (pTaSe)malloc(sizeof(TaSe));
if (cur == NULL)
{
perror("malloc");
return;
}
cur->next = NULL;
cur->x = POS_X+2*i;
cur->y = POS_y;
//用头插
//空
if (ps->_pSnak== NULL)
{
ps->_pSnak = cur;
}
else//非空
{
cur->next = ps->_pSnak;
ps->_pSnak = cur;
}
}
cur = ps->_pSnak;
while (cur)
{
//打印出来
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_dir = RIGHT;
ps->_food_wegiht = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_status = OK;
}
//食物
void CreateFond(pSnake ps)
{
int x, y;
again:
do {
x = rand() %(2*WLLCOOL-5) + 2;//2-55
y = rand() % (WLLINE - 2) + 2;//1-25
} while (x % 2 != 0);
pTaSe cur = ps->_pSnak;
//防止坐标冲突
while (cur)
{
if (cur->x==x&&cur->y==y)
{
//跳回回去重新来
goto again;
}
cur = cur->next;
}
//创建食物的节点
pTaSe newnode = (pTaSe)malloc(sizeof(TaSe));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->x = x;
newnode->y = y;
newnode->next = NULL;
//放到结构体
ps->_pFond = newnode;
SetPos(x, y);
wprintf(L"%lc", FOND);
}
//初始化游戏
void GameStrat(pSnake ps)
{
//0.隐藏光标和设置大小
GetsdHande();
//1.欢迎界面&&介绍功能
WelcomeToGame();
//2.绘制地图
CreateMap();
//初始化蛇身
InitSnake(ps);
//创建食物
CreateFond(ps);
}
//帮助信息
void PrintHelpInfo()
{
SetPos(COL, ROW);
wprintf(L"%ls", L"蛇不能穿墙,不能咬到自己");
SetPos(COL, ROW+1);
wprintf(L"用↑ ↓ → ←来控制蛇的移动");
SetPos(COL, ROW + 2);
wprintf(L"%ls", L"按F3进行加速,F4进行减速");
SetPos(COL, ROW + 3);
wprintf(L"加速可以得到更高的分数");
SetPos(COL, ROW + 4);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
//暂停
void Pause()
{
while (1)
{
Sleep(2000);
//再按就击破循环
if(KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//是否是食物
int NextIsFood(pSnake ps, pTaSe node)
{
return (ps->_pFond->x == node->x && ps->_pFond->y == node->y);
}
//吃掉食物
void EatFood(pSnake ps,pTaSe node)
{
//头插
ps->_pFond->next = ps->_pSnak;
ps->_pSnak = ps->_pFond;
//释放节点
free(node);
node = NULL;
//打印
pTaSe pcu = ps->_pSnak;
while (pcu)
{
//光标位置改动
SetPos(pcu->x, pcu->y);
wprintf(L"%lc", BODY);
pcu = pcu->next;
}
//分数加了
ps->_score += ps->_food_wegiht;
//重新建食物
CreateFond(ps);
}
//不是食物
void NodeFood(pSnake ps, pTaSe node)
{
//头插
node->next = ps->_pSnak;
ps->_pSnak = node;
//删掉尾节点
pTaSe pcur = ps->_pSnak;
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);
//倒数第二个节点赋值为kong
pcur->next = NULL;
}
//撞墙
void KillByWall(pSnake ps)
{
if (ps->_pSnak->x == 0 || ps->_pSnak->x == 56 || ps->_pSnak->y == 0 || ps->_pSnak->y == 26)
{
ps->_status = KILL_BY_WALL;
}
}
//撞到自己
void KillBySelf(pSnake ps)
{
pTaSe pcu = ps->_pSnak->next;
while(pcu)
{
if (pcu->x == ps->_pSnak->x && pcu->y == ps->_pSnak->y)
{
ps->_status = KILL_BY_SELF;
}
pcu = pcu->next;
}
}
//蛇走一步
void SnakeMove(pSnake ps)
{
//先申请一个空间存放,要前进的那个节点
pTaSe node = (pTaSe)malloc(sizeof(TaSe));
if (node == NULL)
{
perror("malloc");
return;
}
node->next = NULL;
switch (ps->_dir)
{
case UP:
node->x = ps->_pSnak->x;
node->y = ps->_pSnak->y - 1;
break;
case DOWN:
node->x = ps->_pSnak->x;
node->y = ps->_pSnak->y + 1;
break;
case LEFT:
node->x = ps->_pSnak->x - 2;
node->y = ps->_pSnak->y;
break;
case RIGHT:
node->x = ps->_pSnak->x + 2;
node->y = ps->_pSnak->y;
break;
}
//判断下个节点是食物么???
int ne = NextIsFood(ps, node);
if (ne)
{
EatFood(ps, node);
node = NULL;
}
else
{
NodeFood(ps, node);
}
//撞墙
KillByWall(ps);
//撞到自己
KillBySelf(ps);
}
//游戏运行
void GameRun(pSnake ps)
{
//帮助信息
PrintHelpInfo();
do
{
//打印总分和食物分数
SetPos(COL, ROW - 5);
wprintf(L"当前食物分数:%2ld",ps->_food_wegiht);
SetPos(COL, ROW - 4);
wprintf(L"总分:%ld",ps->_score);
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_F3))
{
//加速
if (ps->_sleep_time > 80)
{
ps->_sleep_time -= 30;
ps->_food_wegiht += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速:若是分数都少于2了,不能再减速了
if (ps->_food_wegiht>2)
{
//时间越长,速度越慢
ps->_sleep_time += 30;
ps->_food_wegiht -= 2;
}
}
else if(KEY_PRESS(VK_ESCAPE))
{
//ESC退出(正常退出状态)
ps->_status = END_NORMAL;
}
//蛇走一步的状态
SnakeMove(ps);
//走一步的时间
Sleep(ps->_sleep_time);
} while (ps->_status==OK);
}
//游戏结束
void GameEnd(pSnake ps)
{
SetPos(24, 12);
switch (ps->_status)
{
case END_NORMAL:
wprintf(L"退出游戏");
break;
case KILL_BY_WALL:
wprintf(L"和墙亲吻了");
break;
case KILL_BY_SELF:
wprintf(L"给自己撞死了");
break;
}
pTaSe pcu = ps->_pSnak;
while (pcu)
{
pTaSe del = pcu;
pcu = pcu->next;
free(del);
del = NULL;
}
}
test.c文件(实现运行)
#define _CRT_SECURE_NO_WARNINGS 3
#include"Tase.h"
void test()
{
int ch = 0;
do {
//创建贪吃蛇
Snake snake = { 0 };
//初始化游戏
GameStrat(&snake);
//游戏运行
GameRun(&snake);
//游戏结束
GameEnd(&snake);
//光标改下
SetPos(25, 14);
wprintf(L"再来一次么?(Y/N)");
ch = getchar();
while (getchar()!='\n');
} while (ch=='Y'||ch=='y');
SetPos(0, 26);
}
int main()
{
//本地化
setlocale(LC_ALL, "");
//设置种子值为随机值
srand((unsigned int)time(NULL));
test();
}
六、总结:
本游戏只是简单的实现,其实学会了这个简易的,在将自己想法写进去更好,比如双人游戏(初始化时2个蛇身),地图更大点,食物不止局限一个,一次性生成食物更多个,甚至能写bug玩,主要就是培养兴趣,发挥你的想象