贪吃蛇设计详解

在去年12月中,初次接触c语言,我靠着为数不多的知识,使用数组仿照写了一份贪吃蛇,现在时隔5个月,我已经有能力独立写出真正的贪吃蛇而不是简单的仿照(虽然写的是挺简单的)。

那么我们现在就正式开始今天的主题,贪吃蛇设计:

前置知识:

这一次的知识跨度有点大,介绍了不少新的内容才能完成贪吃蛇的设计,让我们先来看看前置知识:

控制台程序:

我们需要包含头文件<windows.h>

首先就来讲讲控制台的指令

cmd指令:

平常我们运⾏起来的⿊框程序其实就是控制台程序
我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的大小。
cmd指令写法为:
system(" mode con cols= 100 lines= 30 ")
cols为列,lines为行,即将窗口的大小设置为30行100列。

title指令:

使用title指令修改窗口名字
写法为:
system("title 贪吃蛇")
将窗口名字修改为贪吃蛇。

cls指令:

写法为:
system("cls")
用处为:清屏
pause指令
写法为:
system("pause");
用处为:按下任意键才能指向下一步操作

句柄:

句柄可以理解为一个可以操作其他设备的东西。

GetStdHandle函数:

GetStdHandle是⼀个Windows API函数。
它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标
准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
写法为:
HANDLE GetStdHandle (DWORD nStdHandle);
其中HANDLE为句柄的类型名, DWORD为需要获取的设备
例如:
HANDLE hOutput = NULL ;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
STD_OUTPUT_HANDLE即为输出设备,可以理解为窗口。

GetConsoleCursorInfo函数:

该函数的用处为检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。

该函数的形式为:

BOOL WINAPI GetConsoleCursorInfo (
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
第一个参数为句柄,第二个参数为指向 CONSOLE_CURSOR_INFO 类型变量的指针

CONSOLE_CURSOR_INFO为存放光标数据的结构体

形式为:
typedef struct _ CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完
全填充单元格到单元底部的⽔平线条。
bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。
由此,我们便可以写出:
HANDLE hOutput = NULL ;
// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )
hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo (hOutput, &CursorInfo); // 获取控制台光标信息
而将数据传递给光标,我们传递的也是句柄和结构体。
句柄用于找到设备,结构体用于传递信息。
我们需要用到下面一个函数:

SetConsoleCursorInfo函数

形式为:

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); // 设置控制台光标状态

SetConsoleCursorPosition函数

用于改变光标位置

COORD
COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0) 的原点位于缓冲区的顶部左侧单元格。
左侧参数为x坐标,右侧参数为y坐标。
从左上角开始,从左向右x坐标逐渐增大
从上到下y坐标逐渐增大
形式:
typedef struct _ COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
set函数形式:
BOOL WINAPI SetConsoleCursorPosition (
HANDLE hConsoleOutput,
COORD pos
);
传递的同样是句柄和结构体pos。
与上面比较我们可知,修改光标位置传递的为结构体,而修改光标信息则需要传递改后信息的地址。
示例:
COORD pos = { 10 , 5 };
HANDLE hOutput = NULL ;
// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )
hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
// 设置标准输出上光标的位置为 pos
SetConsoleCursorPosition (hOutput, pos);

setpos函数(自己创建)

为了方便修改光标位置,我们可以自己写一个名为setpos的函数。
该函数的参数为改后光标的x,y轴坐标。
步骤可分为:
1.获取句柄
2.创建COORD变量赋值为{x,y}
3.将句柄和创建好的COORD变量一起传递给 SetConsoleCursorPosition函数 进行修改即可。
示例:
void SetPos ( short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL ;
// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )
hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
// 设置标准输出上光标的位置为 pos
SetConsoleCursorPosition (hOutput, pos);
}

按键判断函数GetAsyncKeyState

在贪吃蛇中最重要的内容就是按键判断,通过按键来执行操作。

GetAsyncKeyState原型如下:
SHORT GetAsyncKeyState (
int vKey
);
这个函数我们尽管调用就好了,因为他的作用为:
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。
换句话说,我们只需要检测 GetAsyncKeyState函数 的的最高位或最低位即可。
我们可以进行宏定义检测函数返回值的最低值即可:
# define pdVK(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
将返回值与1进行与操作,其他位置都改为0,如果最低位为1则为真,为0则为假。
其实我们不需要使用双目操作符进行后面的判断,因为他的值只有1和0两种
写成#define pdVK(VK) ((GetAsyncKeyState(VK)&1))
同样可以完成目的。

宽字符:

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★
普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
同时使用这类宽字符还有一定要求

<locale.h>本地化

<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准中,依赖地区的部分有以下⼏项:
数字量的格式
货币量的格式
字符集
⽇期和时间的表⽰形式
我们使用的这些字符同样包括在内,所以需要对C标准库进行一点修改。
我们这里不做过多解释,直接说主要内容:

setlocale函数

char* setlocale (int category, const char* locale);

setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值: "C" (正常模式)和 "" (本地模式)。
切记,本地模式的""内部是空的,没有任何字符。
通过 setlocale (LC_ALL, "" );
我们就可以实现使用汉字和打印某些特殊字符的操作。

宽字符的打印:

宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引
号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应
wprintf() 的占位符为 %ls
例如:
setlocale (LC_ALL, "" );
wchar_t ch1 = L' ';
wprintf ( L"%lc\n" , ch1);

程序撰写:


OK,前置知识学习完之后我们就可以进行贪吃蛇的撰写了。

首先我们先理清楚思路

游戏进行过程如下:

1.跳出两张图片提示游戏开始和按键操作

2.第三张图分为框内框外,框的外侧印着分数。

3.框内侧为小蛇和食物

4.小蛇可以通过各种按键进行操控

5.小蛇吃掉食物后会长一节,食物会再次刷新。

大致内容分为以上五部分

我们先来看下函数的头文件

头文件:

col为列数,line为行数

我们将wall,snakebody定义为墙和蛇身,方便后面的书写。

sit为situation的缩写,意味小蛇此时的状态

dir为direction的缩写,意味小蛇此时的方向

我们在定义一个结构体类型snakenode意味蛇节点。

每个节点都有他的左边和下一个节点,蛇身采用的是链表的形式。

使用typedef将蛇节点结构体改名为snakenode,该结构提的指针为psnakenode。

然后我们再写一个蛇结构体,里面存放蛇的各种数据,包括蛇的状态,头结点,单个食物的分值,当前食物的位置,小蛇的方向,总分数和休眠时间(即蛇的速度),同样进行改名操作。

然后下面的函数简单进行声明,我们后面进行介绍。

主函数:

就只有两个内容,一个为切换到本地模式,一个为游戏开始。

函数编写:

首先对随机数进行修改,保证每次游戏都是不同的情况。

然后我们打印菜单栏(即两张图)

菜单:

前面提到过的将光标状态切换为不可见

将窗口大小设置为40行100列

然后找到位置打印文字“欢迎来到贪吃蛇小游戏”

将光标切换到下一行防止接到原文字之后。

文字显示完,按下任意键之后,清屏。

重复同样的操作打印游戏按键即可。

打印墙:

传入两个参数对应行和列

将上下左右四个方向分别打印上墙即可

注意:一个宽字符对应两个普通字符,所以打印最右侧时,需要将x坐标设置为2*(列数-1)

初始化蛇:

我们现将蛇变量创建出来,然后在编写函数用来初始化蛇

传递的为蛇指针

将各项值设置好即可。

需要注意的是我们要给食物指针专门开辟一块空间用来存放食物的,与蛇节点使同一个结构体

创建蛇:

使用链表的方式将蛇节点一个一个穿上即可

创建一个新的节点指针cur。

我们给蛇身创建5个长度即可。

每一个节点都单独开辟一块空间将指针传递给cur,cur的下一个节点指向头指针,将头指针指向cur即可实现增加一个节点,即从0开始的头插。至于设置位置这项操作在头插前还是后进行都没关系。只需要注意宽字符占2个普通字符宽度即可。

最后在每次循环插完节点之后在窗口上打印蛇身即可。

创建食物:

使用goto语句,随机到的节点如果在墙上或者在奇数位置的x上就重新随机,随机完之后再和蛇身的每一个节点进行比较,如果该点在蛇身上那么就重新上面的操作。

取到的位置合适就将蛇结构体中食物位置这一项修改为对应位置即可,在窗口上打印食物。

按键判断:

进入循环,循环退出条件为蛇不再正常。

在墙之外打印上我们的分数以及当前单个食物的分数

休眠时间也是存在蛇结构体中的。时间越长,蛇的速度越快。

在前面我们已经介绍过可以使用

#define pdVK(VK) ((GetAsyncKeyState(VK)&1))

来快速判断按键是否被按下,可以节省打字时间。

方向

如果按键方向不为蛇运动的反方向

则将蛇结构体的方向进行修改

加减速与暂停

加减速不能无上限的加速减速,我们给他设定一个范围,

在这个范围内每次加速可以增加2点食物分值,减速则减少两点,将休眠时间进行相应的增加与减少即可

如果我们按下空格(SPACE),那么就要进行暂停

stop函数很简单,死循环的进行判断即可,如果空格(SPACE)被再次按下即退出循环。

蛇的移动:

最后的最后就是我们的重中之重,蛇的运动了

经过前面的积累,蛇的移动已经显而易见了

创建下一个节点作为新的头结点

新节点为蛇头当前位置加上运动的方向。

修改完新节点后,我们还要进行判断(没错,还有)

如果新节点为食物,就进行吃食物操作

如果不是食物,就进行非食物操作

吃食物与不吃食物

传递新节点与蛇指针。

如果吃食物

那么就将新节点修改为新的头,在新节点的位置打印上蛇身,加个分,然后再创建一个新的食物即可。

如果不吃食物

新节点同样改为头结点,找到倒数第二个节点,通过倒数第二个节点先将最后一个节点的位置修改为'  '(宽字符,两个' '),释放掉最后一个节点的内存,将倒数第二个节点的下一个节点置为空,那么原倒数第二个节点就成为了现在的最后一个节点。

到这里为止,大部分内容都已完成,只剩下小蛇吃到自己与撞墙死的判断了。

在小蛇移动后进行判断

撞墙死

很简单,判断蛇头是否在墙的位置,是的话将小蛇状态改为撞墙死,退出循环,否则继续寻找下一个节点,直到全部节点都遍历过。

吃到自己

从蛇头的下一个节点开始比较,如果某个节点和蛇头位置撞了则将蛇的状态修改为吃到自己,退出循环。

游戏结束

小蛇撞到墙或者吃到自己则会导致游戏结束

将光标移动到中间某个位置,打印最终的分数

打印完之后将光标移动到下方避免程序结束出现的文字截断掉贪吃蛇的画面即可

到这里,游戏已经设计完成了,让我们看下最后的效果

效果展示:

ok,文章到这里就结束了,看在博主辛辛苦苦码了这么多字的情况下点个免费的赞吧,球球了(噗通一声倒地不起)

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值