注意:本代码是在VC++6.0环境下编译的,在其他环境如codeblocks下运行可能会产生意想不到的问题,请尽量使用VC!
最近由于小编闲着慌,捣鼓了一个贪吃蛇游戏,系统由纯C语言开发,VC++6.0编译通过,具体的运行效果如下:
【图1】
【图2】
【图3】
游戏说明:游戏界面如图2所示,左侧方框是地图,菱形组成的线是蛇,红色圆点是食物,吃到加分。
好了,让我们进入正题。
贪吃蛇游戏是一款经典的益智游戏,有PC和手机等多平台版本。既简单又耐玩。该游戏通过控制蛇头方向吃食物,从而使得蛇变得越来越长。它的基本规则是:一条蛇出现在封闭空间中,空间中随机出现一个食物,通过键盘上下左右方向键控制蛇前进方向。蛇头撞到食物,食物消失,蛇身体增长一节,累计得分,刷新食物。如果蛇在前进过程中撞到墙或自己身体,则游戏失败。
我们可以简单地画出游戏的大概流程图:
贪吃蛇游戏分为四个界面,包括欢迎界面,游戏界面,游戏说明和游戏结束界面。实现的主要功能有:
1.字符图案装饰。
2.绘制游戏地图。
3.绘制移动的贪吃蛇。
4.随机绘制食物并且位置不与当前蛇身体重合。
5.键盘按键控制蛇的前进方向。
6.不按键时,蛇自动前进。
7.文件读写,存入和读取最高分。
我们列出为实现各个模块功能所需要的函数:
暂且省略定义,下面依次介绍各个函数的功能,首先说明欢迎界面的函数,因为游戏说明界面其实只是打印字符较为简单,我们也一并列出。为了在窗口内合适位置打印字符,我们需要控制光标的位置,声明gotoxy()函数以实现设置光标位置。
//设置光标位置
void gotoxy(int x,int y){
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
}
为了让显示的文字色彩多样,我们声明color()函数用于改变文字颜色。
//文字颜色函数
int color(int c){
//SetConsoleTextAttribute是API设置控制台窗口字体颜色和背景色的函数
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //更改文字颜色
return 0;
}
接着声明printsnake()函数用于打印欢迎界面的字符装饰,color(2)设置文字颜色为绿色。
//字符拼成英文图案
void printsnake(){
color(2);
printf("
");
printf(" __________ ___
");
printf(" / / |____ ____
");
printf(" / ________ / ___ _/ __ | | /
");
printf(" | | |__| _/_ |_| / [|] |/
");
printf(" | | | | | / _|_ __/
");
printf(" _______ / |___/ ____
");
printf(" ____ ____ ____ __ | | ___ ______
");
printf(" _______ | |/ / _/ / | | / / /
");
printf(" | ___ / ____ / | |/ / / ____
");
printf(" __ | | | / | | | / | / | /____ |
");
printf(" _______| | | | | | | |__| | | | ________/
");
printf(" / | | | | | | ____
");
printf(" __________/ |__| |__| ___/__ |__| __ ______/
");
}
然后我们声明welcometogame()函数绘制菜单选项并通过switch实现选择功能。
//开始界面
void welcometogame(){
int n;
int i,j=1;
gotoxy(43,18);
color(11);
printf("贪 吃 蛇 游 戏");
color(14); //黄色边框
for(i=20;i<=26;i++){ //输出上下边框┅
for(j=27;j<=74;j++){ //输出左右边框┇
gotoxy(j,i);
if(i==20||i==26)
printf("-");
else if(j==27||j==74)
printf("|");
}
}
color(10);
gotoxy(35, 22);
printf("1.开始游戏");
gotoxy(55, 22);
printf("2.游戏说明");
gotoxy(35, 24);
printf("3.退出游戏");
gotoxy(29,27);
color(3);
printf("请选择[1 2 3]:[ ]"); //为退格,使得光标处于[]中间
color(14);
scanf("%d",&n); //输入选项
switch (n){
case 1: //选择开始游戏
system("cls");
createMap(); //创建地图
initsnake(); //初始化蛇身
createfood(); //初始化食物
keyboardControl(); //控制键盘按钮
break;
case 2: //选择游戏说明
explation();
break;
case 3: //选择退出游戏
exit(0); //退出游戏
break;
default: //输入非1~3之间的选项
color(12);
gotoxy(40,28);
printf("请输入1~3之间的数!");
getch(); //输入任意键
system("cls"); //清屏
printsnake();
welcometogame();
}
}
游戏说明界面explation()函数实现打印说明文字的功能。
//游戏说明
void explation(){
int i,j=1;
system("cls");
color(15);
gotoxy(44,3);
printf("游戏说明");
color(2);
for(i=6;i<=22;i++){ //输出上下边框===
for(j=20;j<=76;j++){ //输出左右边框||
gotoxy(j,i);
if(i==6||i==22)printf("=");
else if(j==20||j==75)printf("||");
}
}
color(3);
gotoxy(30,8);
printf("tip1: 不能撞墙,不能咬到自己");
color(10);
gotoxy(30,11);
printf("tip2: 用↑.↓.←.→分别控制蛇的移动");
color(14);
gotoxy(30,14);
printf("tip3: F1 为加速,F2 为减速");
color(11);
gotoxy(30,17);
printf("tip4: 按空格键暂停游戏,再按空格键继续");
color(4);
gotoxy(30,20);
printf("tip5: Esc :退出游戏");
getch(); //按任意键返回主界面
system("cls");
printsnake();
welcometogame();
}
在开始游戏之前,首先我们需要初始化游戏界面,调用createMap()函数创建地图,createMap()函数实现很简单,用for循环打印出边框和中心网格即可。
//创建地图
void createMap(){
int i,j;
for(i=0;i<58;i+=2){ //打印上下边框
gotoxy(i,0);
color(6); //深绿色的边框
printf("□");
gotoxy(i,26);
printf("□");
}
for(i=1;i<26;i++){ //打印左右边框
gotoxy(0,i);
printf("□");
gotoxy(56,i);
printf("□");
}
for(i = 2;i<56;i+=2){ //打印中间网格
for(j = 1;j<26;j++){
gotoxy(i,j);
color(3);
printf("■
");
}
}
}
接着,我们需要初始化“主角”——贪吃蛇蛇身,在这里,蛇身的每一个节点,我们使用的是结构体保存,结构体也很简单明了,里面仅定义了蛇身节点的x,y坐标以及next指针,在生成贪吃蛇时我们使用的是头插法,即先生成的节点作为尾节点,后加入的节点作为头节点。
//初始化蛇身,画蛇身
void initsnake(){
snake *tail;
int i;
tail=(snake*)malloc(sizeof(snake)); //从蛇尾开始,头插法,以x,y设定开始的位置
tail->x=24; //蛇的初始位置(24,5)
tail->y=5;
tail->next=NULL;
for(i=1;i<=4;i++){ //设置蛇身,长度为5
head=(snake*)malloc(sizeof(snake)); //初始化蛇头
head->next=tail; //蛇头的下一位为蛇尾
head->x=24+2*i; //设置蛇头位置
head->y=5;
tail=head; //蛇头变成蛇尾,然后重复循环
}
while(tail!=NULL){ //从头到尾,输出蛇身
gotoxy(tail->x,tail->y);
color(14);
printf("◆"); //输出蛇身,蛇身使用◆组成
tail=tail->next; //蛇头输出完毕,输出蛇头的下一位,一直输出到蛇尾
}
}
输出完蛇身后,我们还需要输出食物,使用随机数生成食物位置并判断是否与蛇身重合,如果重合则从新生成。
//随机出现食物
void createfood(){
snake *food_1;
srand((unsigned)time(NULL)); //初始化随机数
food_1=(snake*)malloc(sizeof(snake)); //初始化food_1
while((food_1->x%2)!=0) //保证其为偶数,使得食物能与蛇头对齐,出现在网格线上
food_1->x=rand()%52+2; //食物随机出现,食物的x坐标在2~53
food_1->y=rand()%24+1; //食物的y坐标在1~24
q=head;
while(q->next==NULL){
if(q->x==food_1->x && q->y==food_1->y){ //判断蛇身是否与食物重合
free(food_1); //如果蛇身和食物重合,那么释放食物指针
createfood(); //重新创建食物
}
q=q->next;
}
gotoxy(food_1->x,food_1->y);
food=food_1;
color(12);
printf("●"); //输出食物
}
游戏界面输出完毕,接下来实现右侧的提示函数scoreandtips(),读取save.txt文件中的数据打印输出。
//读取文件,打印输出
void scoreandtips(){
File_out(); //调用File_out(),读取文件save.txt中的内容
gotoxy(64,4); //确定打印输出的位置
color(11); //设置颜色
printf("☆最高记录☆:%d",HighScore); //打印最高分
gotoxy(64,8);
color(14);
printf("当前得分:%d ",score);
color(15);
gotoxy(73,11);
printf("小 提 示");
gotoxy(60,13);
color(6);
printf("╬ ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ ╬");
gotoxy(60,25);
printf("╬ ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ ╬");
color(3);
gotoxy(64,14);
printf("每个食物得分:%d分",add);
gotoxy(64,16);
printf("不能撞墙,不能咬到自己");
gotoxy(64,18);
printf("用↑ ↓ ← →分别控制蛇的移动");
gotoxy(64,20);
printf("F1键加速,F2键减速");
gotoxy(64,22);
printf("空格键暂停游戏");
gotoxy(64,24);
printf("Esc键退出游戏");
}
游戏的静态初始化已经完成,下面要做的就是动态逻辑的编写,在游戏开始后,我们需要实现的有几个功能:在不按下按键(上下左右)时,蛇朝着默认方向前进,在按下方向键时,蛇改变前进方向,如果蛇撞到墙或者咬到自己,则游戏结束。那么问题来了,如何用代码实现键盘控制方向呢,这里用到的是short GetAsyncKeyState(int nVirtKey);方法,该方法传入的参数nVirtKey为虚拟键盘值常量,这样一来,通过传入的nVirtKey参数我们就可以控制蛇的方向了,同理,加速减速以及暂停等功能也可以实现。
//控制键盘按键
void keyboardControl(){
status=R; //初始蛇向右移动
while(1){
scoreandtips();
if(GetAsyncKeyState(VK_UP)&&status!=D) //GetAsyncKeyState函数用来判断函数调用时指定虚拟键的状态
status=U; //如果蛇不是向下前进的时候,按上键,执行向上前进操作
else if(GetAsyncKeyState(VK_DOWN)&&status!=U) //如果蛇不是向上前进的时候,按下键,执行向下前进操作
status=D;
else if(GetAsyncKeyState(VK_LEFT)&&status!=R) //如果蛇不是向右前进的时候,按左键,执行向左前进
status=L;
else if(GetAsyncKeyState(VK_RIGHT)&&status!=L) //如果蛇不是向左前进的时候,按右键,执行向右前进
status=R;
if(GetAsyncKeyState(VK_SPACE)){ //按暂停键,执行pause暂停函数
while(1){
Sleep(300); //sleep()函数,头文件#include 令进程暂停,直到达到里面设定的参数的时间
if(GetAsyncKeyState(VK_SPACE)) //按空格键暂停
break;
}
}
else if(GetAsyncKeyState(VK_ESCAPE)){
endgamestatus=3; //按esc键,直接到结束界面
break;
}
else if(GetAsyncKeyState(VK_F1)) //按F1键,加速
speedup();
else if(GetAsyncKeyState(VK_F2)) //按F2键,减速
speeddown();
Sleep(sleeptime);
snakemove();
}
}
蛇在前进时,是通过新建头节点,擦去尾节点实现的,并且在吃到食物时,会自动加速。
//控制方向
void snakemove() //蛇前进,上U,下D,左L,右R
{
snake * nexthead;
cantcrosswall();
nexthead=(snake*)malloc(sizeof(snake)); //为下一步开辟空间
if(status==U)
{
nexthead->x=head->x; //向上前进时,x坐标不动,y坐标-1
nexthead->y=head->y-1;
nexthead->next=head;
head=nexthead;
q=head; //指针q指向蛇头
if(nexthead->x==food->x && nexthead->y==food->y) //如果下一个有食物 下一个位置的坐标和食物的坐标相同
{
while(q!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆"); //原来食物的位置,从●换成◆
q=q->next; //指针q指向的蛇身的下一位也执行循环里的操作
}
score=score+add; //吃了一个食物,在总分上加上食物的分
speedup();
createfood(); //创建食物
}
else
{
while(q->next->next!=NULL) //如果没遇到食物
{
gotoxy(q->x,q->y);
color(14);
printf("◆"); //蛇正常往前走,输出当前位置的蛇身
q=q->next; //继续输出整个蛇身
}
gotoxy(q->next->x,q->next->y); //经过上面的循环,q指向蛇尾,蛇尾的下一位,就是蛇走过去的位置
color(3);
printf("■");
free(q->next); //进行输出■之后,释放指向下一位的指针
q->next=NULL; //指针下一位指向空
}
}
if(status==D)
{
nexthead->x=head->x; //向下前进时,x坐标不动,y坐标+1
nexthead->y=head->y+1;
nexthead->next=head;
head=nexthead;
q=head;
if(nexthead->x==food->x && nexthead->y==food->y) //有食物
{
while(q!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
score=score+add;
speedup();
createfood();
}
else //没有食物
{
while(q->next->next!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
gotoxy(q->next->x,q->next->y);
color(3);
printf("■");
free(q->next);
q->next=NULL;
}
}
if(status==L)
{
nexthead->x=head->x-2; //向左前进时,x坐标向左移动-2,y坐标不动
nexthead->y=head->y;
nexthead->next=head;
head=nexthead;
q=head;
if(nexthead->x==food->x && nexthead->y==food->y)//有食物
{
while(q!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
score=score+add;
speedup();
createfood();
}
else //没有食物
{
while(q->next->next!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
gotoxy(q->next->x,q->next->y);
color(3);
printf("■");
free(q->next);
q->next=NULL;
}
}
if(status==R)
{
nexthead->x=head->x+2; //向右前进时,x坐标向右移动+2,y坐标不动
nexthead->y=head->y;
nexthead->next=head;
head=nexthead;
q=head;
if(nexthead->x==food->x && nexthead->y==food->y)//有食物
{
while(q!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
score=score+add;
speedup();
createfood();
}
else //没有食物
{
while(q->next->next!=NULL)
{
gotoxy(q->x,q->y);
color(14);
printf("◆");
q=q->next;
}
gotoxy(q->next->x,q->next->y);
color(3);
printf("■");
free(q->next);
q->next=NULL;
}
}
if(biteself()==1) //判断是否会咬到自己
{
endgamestatus=2;
endgame();
}
}
加速代码即缩短时间间隔,同时得分增加。
//加速,蛇吃到食物会自动提速,并且按F1会加速
void speedup(){
if(sleeptime>=50){
sleeptime=sleeptime-10;
add=add+2;
}
}
减速也是如此。
//减速,按F2会减速
void speeddown(){
if(sleeptime<350){ //如果时间间隔小于350
sleeptime=sleeptime+30; //时间间隔加上30
add=add-2; //每吃一次食物的得分减2
}
}
如何判断蛇是否咬到自己,是通过遍历蛇身节点判断是否重复实现的。
//判断是否咬到了自己
int biteself(){
snake *self; //定义self为蛇身上的一个节点
self=head->next; //self是蛇头之外的蛇身上的节点
while(self!=NULL){
if(self->x==head->x&&self->y==head->y) //如果self和蛇身上的节点重合
return 1; //返回1
self=self->next;
}
return 0;
}
撞墙检查更为简单,判断头节点与上下左右“墙”的坐标值是否重合即可。
//检查是否撞墙
void cantcrosswall(){
if(head->x==0||head->x==56||head->y==0||head->y==26){ //如果蛇头碰到了墙壁
endgamestatus=1; //返回第一种情况
endgame(); //出现游戏结束界面
}
}
为了使游戏更为生动,有必要编写一个清晰的失败界面,这里我们先用代码勾勒框架。
//打印失败界面
void Lostdraw(){
system("cls");
int i;
gotoxy(45,1);
color(6);
printf(" |-----| "); //匹诺曹的帽子
gotoxy(45,2);
color(6);
printf(" | | ");
gotoxy(43,3);
color(6);
printf("-------------");
gotoxy(44,4);
color(14);
printf("(");
gotoxy(47,4);
color(15);
printf(" > <"); //眼睛
gotoxy(54,4);
color(14);
printf(")");
gotoxy(17,5);
color(11);
printf("+------------------------"); //上边框
gotoxy(35,5);
color(14);
printf("oOOo");
gotoxy(39,5);
color(11);
printf("----------"); //上边框
gotoxy(48,5);
color(14);
printf("| |"); //鼻子
gotoxy(48,6);
color(14);
printf("|_|");
gotoxy(51,5);
color(11);
printf("----------"); //上边框
gotoxy(61,5);
color(14);
printf("oOOo");
gotoxy(65,5);
color(11);
printf("-----------------+"); //上边框
for(i = 6;i<=19;i++) //竖边框
{
gotoxy(17,i);
printf("|");
gotoxy(82,i);
printf("|");
}
gotoxy(17,20);
printf("+------------------------------------------"); //下边框
gotoxy(60,20);
color(11);
printf("----------------------+"); //下边框
}
然后往里面写入内容,我们已经从之前的代码中判断出游戏失败的情况并且将其赋值给了endgamestatus()变量,现在要做的就取出该变量并判断输出内容。
//结束游戏
void endgame(){
system("cls");
if(endgamestatus==1)
{
Lostdraw();
gotoxy(35,9);
color(7);
printf("对不起,您撞到墙了。游戏结束!");
}
else if(endgamestatus==2)
{
Lostdraw();
gotoxy(35,9);
color(7);
printf("对不起,您咬到自己了。游戏结束!");
}
else if(endgamestatus==3)
{
Lostdraw();
gotoxy(40,9);
color(7);
printf("您已经结束了游戏。");
}
gotoxy(43,12);
color(14);
printf("您的得分是 %d",score);
if(score >= HighScore)
{
color(10);
gotoxy(33,16);
printf("创纪录啦!最高分被你刷新啦,真棒!!!");
File_in(); //把最高分写进文件
}
else
{
color(10);
gotoxy(33,16);
printf("继续努力吧~ 你离最高分还差:%d",HighScore-score);
}
choose();
}
在输出失败信息后,我们应该给用户选择继续游戏还是退出。
//边框下面的分支选项
void choose(){
int n;
gotoxy(30,23);
color(12);
printf("重玩一局 [1]");
gotoxy(55,23);
printf("溜了溜了 [2]");
gotoxy(45,25);
color(11);
printf("选择:");
scanf("%d", &n);
switch (n)
{
case 1:
system("cls"); //清屏
score=0; //分数归零
sleeptime=200; //设定初始速度
add = 10; //使add设定为初值,吃一个食物得分10,然后累加
printsnake(); //返回欢迎界面
welcometogame();
break;
case 2:
exit(0); //退出游戏
break;
default:
gotoxy(35,27);
color(12);
printf("※※您的输入有误,请重新输入※※");
system("pause >nul");
endgame();
choose();
break;
}
}
接下来编写用于从文件读取和写入最高分的函数。
//在文件中读取最高分
void File_out(){
FILE *fp;
fp = fopen("save.txt", "a+"); //打开文件save.txt
fscanf(fp, "%d", &HighScore); //把文件中的最高分读出来
fclose(fp); //关闭文件
}
//储存最高分进文件
void File_in(){
FILE *fp;
fp = fopen("save.txt", "w+"); //以读写的方式建立一个名为save.txt的文件
fprintf(fp, "%d", score); //把分数写进文件中
fclose(fp); //关闭文件
}
至此,贪吃蛇的全部代码已经编写完成。