用c++实现贪吃蛇小游戏,初学者记录一下首次实现的经历,有超详细的思路与语法讲解,新手向

首先,感谢一下这篇博客,帮助了我很多,教会了我很多语法,而且由于博主写不出一个合理的随机数和难度函数,是直接运用的该博客的。

先上代码,由于博主是第一次接触此类Windows程序设计的程序,学到啥都想往上用,除去单独的贪吃蛇,还加了一些别的东西。思路在最后,中间是博主的碎碎念。

/************************贪吃蛇***********************/
/**********************2020-12-12*********************/
#include<bits/stdc++.h>
#include<windows.h>
#include<conio.h>
#include<time.h>
using namespace std;
int length;
int modx,mody;
queue <int> follow; 
//申请一个通用的用于输出的句柄
HANDLE Printf=GetStdHandle(STD_OUTPUT_HANDLE);

//定义一个坐标类型的变量 
COORD snakehead;
COORD snakelast;
COORD food;
COORD use;

//判断食物是否刷新在了蛇身上,同时可以依靠此来判断蛇是否死亡 
bool pd(COORD x)
{
	char ls;
	DWORD read;
	ReadConsoleOutputCharacterA(Printf,&ls,1,x,&read);
	if(ls=='*'||ls=='@')
	{
		return false;
	}
	else return true;
}

//没有操作时蛇的前进
int IfOutX(int x)
{
	if(x==0)
	{
		return modx;
	}
	else if(x==modx+1)
	{
		return 1;
	}
	else return x;
}
int IfOutY(int y)
{
	if(y==0)
	{
		return mody;
	}
	else if(y==mody+1)
	{
		return 1;
	}
	else return y;
}
COORD go(COORD x,int y)
{
	switch(y)
	{
		case 1:	x.Y--;
				x.Y=IfOutY(x.Y);break;
		case 3:	x.Y++;
				x.Y=IfOutY(x.Y);break;
		case 2:	x.X--;
				x.X=IfOutX(x.X);break;
		case 4:	x.X++;
				x.X=IfOutX(x.X);break;
	}
	return x; 
}

//判断玩家的操作
int direction;
int play(char x)
{
	switch(x)
	{
		case 72://if(direction!=3)
		{
			return 1;
		}
		else break;
		case 75://if(direction!=4)
		{
			return 2;
		}
		else break;
		case 80://if(direction!=1)
		{
			return 3;
		}
		else break;
		case 77://if(direction!=2)
		{
			return 4;
		}
		else break;
	}
	return 0;
}

//判断有无吃到食物 
bool eat()
{
	if(snakehead.X==food.X&&snakehead.Y==food.Y)
	{
		return true;
	}
	else return false;
}

//隐藏光标
void hide()
{
    CONSOLE_CURSOR_INFO nowcursor;
    nowcursor.bVisible=0;
    nowcursor.dwSize=1;
    SetConsoleCursorInfo(Printf,&nowcursor);
}

/*** 生成随机数 ***/
double random(double start, double end)
{
    return start+(end-start)*rand()/(RAND_MAX + 1.0);
}

//打印食物
void printf_food()
{
	while(false==false)
	{
		food.X=random(0,modx)+1;
		food.Y=random(0,mody)+1;
		if(pd(food))
		{
			break;
		} 
	}
	SetConsoleCursorPosition(Printf,food);
	cout<<"$";
}

//打印蛇
char hit;
int yx;
bool printf_snake()
{
	if(kbhit())
	{
		hit=getch();
		if(hit==-32)
		{
			hit=getch(); 
			int ls=play(hit);
			if(ls)
			{
				direction=ls;
			}
		}
	}
	SetConsoleCursorPosition(Printf,snakehead);
	cout<<"*";
	snakehead=go(snakehead,direction);
	follow.push(direction);
	if(eat())
	{
		printf_food();
		length++;
		use.X=12;
		use.Y=mody+2;
		SetConsoleCursorPosition(Printf,use);
		cout<<length;
	}
	else
	{
		SetConsoleCursorPosition(Printf,snakelast);
		cout<<" ";
		yx=follow.front();
		follow.pop();
		snakelast=go(snakelast,yx);
	}
	if(pd(snakehead)==false)
	{
		return true; 
	}
	SetConsoleCursorPosition(Printf,snakehead);
	cout<<"@";
	return false; 
}

//打印墙 
void printf_wall(int n,int m)
{
    cout<<" ";
    for(int i=1;i<=m;i++)
    {
    	cout<<"-";
	}
    cout<<endl;
    for(int i=1;i<=n;i++)
    {
        cout<<"|";
        for(int j=1;j<=m;j++)
		{
			cout<<" ";
		} 
        cout<<"|"<<endl;
    }
    cout<<" ";
    for(int i=1;i<=m;i++)
    {
    	cout<<"-";
	}
}

int main()
{
	int n;
	irresistible:n=MessageBox(NULL,"想明白生命的意义吗?想真正的……活着吗?","邀请函",MB_YESNO);
	if(n==IDYES)
	{
		MessageBox(NULL,"游戏开始!","那么",MB_OK);
		again:SetConsoleTextAttribute(Printf,FOREGROUND_INTENSITY|FOREGROUND_BLUE);
		cout<<"--------------------贪吃蛇---------------------"<<endl;
		cout<<"请先输入两个数,表示地图大小.要求长宽均不小于10."<<endl;
		cout<<"其中长度值最高为40,宽度值最高为99,请不要超出."<<endl;
		cout<<"请注意窗口大小,以免发生错位.建议将窗口调为最大."<<endl;
		cout<<"再选择难度.请在1-20中输入1个数,1最简单,20则最难"<<endl;
		cout<<"然后进入游戏画面,以方向键控制方向.祝你游戏愉快!"<<endl;
		cout<<"-----------------------------------------------"<<endl;
    	while(false==false)
    	{
    		cin>>mody>>modx;
    		if(mody<10||modx<10||mody>40||modx>100)
    		{
    			MessageBox(NULL,"请重新输入","蠢材",MB_OK); 
    			continue;
			}
			else break;
		}
		int hard;
	    while(false==false)
    	{
    		cin>>hard;
    		if(hard<=0||hard>20)
    		{
    			MessageBox(NULL,"请重新输入","蠢材",MB_OK); 
    			continue;
			}
			else break;
		}
		//游戏开始
		system("cls");
		while(follow.size()!=0)
		{
			follow.pop();
		}
		hide();
		printf_wall(mody,modx);
		printf_food();
		use.X=modx/2;
		use.Y=mody/2;
		SetConsoleCursorPosition(Printf,use);
		cout<<"*@";
		snakelast=use;
		use.X++;
		snakehead=use;
		length=2;
		follow.push(4);
		use.X=0;
		use.Y=mody+2;
		SetConsoleCursorPosition(Printf,use);
		cout<<"输入任意方向键开始游戏"; 
    	while(false==false)
		{
			if(kbhit())
			{
				hit=getch();
				if(hit==-32)
				{
					hit=getch();
					int ls=play(hit);
					if(ls)
					{
						direction=ls;
						break;
					}
				}
				
			}
		}
		use.X=0;
		use.Y=mody+2;
		SetConsoleCursorPosition(Printf,use);
		cout<<"                            ";
		use.X=0;
		use.Y=mody+2;
		SetConsoleCursorPosition(Printf,use);
    	cout<<"Now length: ";
    	use.X=12;
		use.Y=mody+2;
    	SetConsoleCursorPosition(Printf,use);
    	cout<<length;
    	
	    double hard_len;
	    clock_t a,b;
	    while(false==false)
	    {
	    	/*** 难度随长度增加而提高 ***/
	        hard_len=(double)length/(double)(modx*mody);
	        /*** 调节时间,单位是ms ***/
	        a=clock();
	        while(true==true)
	        {
	            b=clock();
	            if(b-a>=(int)(400-30*hard)*(1-sqrt(hard_len)))
	            {
	            	break;
				}
	        }
	        
	        //如果死亡 
	        if(printf_snake())
	        {
	        	use.X=12;
				use.Y=mody+2;
    			SetConsoleCursorPosition(Printf,use);
				cout<<length;
	        	system("pause");
	        	system("cls");
	        	if(length==modx*mody||modx==100)
				{
					MessageBox(NULL,"足够贪婪,一种美德!","恭喜您获胜",MB_OK);
	        		n=MessageBox(NULL,"请问您是否再次挑战?","邀请函",MB_OKCANCEL);
	        		if(n==IDOK)
					{
						goto again;
					} 
	        		else break;
				}
	        	else if(length==5)
	        	{
	        		MessageBox(NULL,"知足是一种美德!","恭喜您获胜",MB_OK);
	        		n=MessageBox(NULL,"请问您是否再次挑战?","邀请函",MB_OKCANCEL);
	        		if(n==IDOK)
					{
						goto again;
					} 
	        		else break;
				}
				else 
				{
					MessageBox(NULL,"贪婪是一种罪!","你已经输了",MB_OK);
	        		goto again;
				}
			}
		}
	}
	else goto irresistible; 
	return 0;
}

代码如上,接下了谈谈博主写这个的心路历程,分享一些易错点。

博主写这个是因为学了很多算法,但是没学任何关于基础的工程实现 (我也不知道一般是怎么描述这个的,反正就是写能实际应用的程序的意思) ,就很想试着去实现一下,于是就瞄准了这个贪吃蛇。

由于博主基本上算是零基础,只会一点C++的皮毛,不懂任何关于工程实现的语法,博主还特意跑图书馆借了一堆书 (不过基本没用上,还是度娘好用!)

首先,Windows程序设计运用的基本上都是API函数,记住这个名词,博主因为不知道这个于是像个无头苍蝇找了很久,问了很多人。也记住如果想用C++来实现,请注意,API函数好像Java也有,博主第一次跑图书馆借书借了本Java的,回去之后一翻当场懵掉。

其次,关于Windows程序设计,我所学到的最重要的名词便是句柄。在此我根据百度知道上一位大佬的回答总结一下,他的回答让我感觉豁然开朗。句柄就好像你去银行办事,你得先去取号,号有不同,所以句柄也有不同,不同的号,或者说不同的句柄会有区别对待,就好比贵宾号能干普通号能干的事情,但是普通号不一定能享受贵宾服务。 不过,俺觉得,像贪吃蛇这种小游戏,直接用HANDLE万能句柄应该没啥大问题吧?

至于其他的语法,都是博主一个个的测试出效果,根据得到的结果反推函数变量的意义,所以应该也算是学懂了吧。

然后,博主在度娘搜索资料的时候还查出了一些错误资料,分享出来大家注意一下,比方说上下左右方向键的ascll码

ascll码方向
72
80
75
77

博主搜到的一篇博客上写的是错的,导致博主因此查了很久bug,也怪博主没仔细看,拿来就用,明明下方评论都在说这个是错的,引以为戒。

然后写几个会用到的函数,解释一下他的意思

//申请一个通用的用于输出的句柄
HANDLE Printf=GetStdHandle(STD_OUTPUT_HANDLE);
COORD snakehead;
SetConsoleCursorPosition(Printf,snakehead);
cout<<"@*";

比方说这个,申请了一个名称叫Printf的用于输出的句柄,申请了一个名称叫snakehead的坐标,然后,在snakehead的所表示的坐标输出“@”,“@”在snakehead所表示的坐标,“”在snakehead.X+1,snakehead.Y所表示的坐标。坐标的话,以左上角为(0,0),向右为X轴,向左为Y轴,一个像素(也就是一个英语字符)为一个单位。

char hit;
if(kbhit())
	{
		hit=getch();
		if(hit==-32)
		{
			hit=getch(); 
			int ls=play(hit);
			if(ls)
			{
				direction=ls;
			}
		}
	}

kbhit()是已有函数,用于判断玩家是否敲击过键盘。

getch()和getchar()一样都是读入一个字符,但是不同的是getch()读入字符后并不显示在屏幕上。

if(hit==-32)我也没明白,在文末提出了问题,但是加上这个操作立杆见影的顺畅了起来。

play()函数是我定义的一个判断输入的字符是否是有效字符(方向键),该把方向改为哪个方向。

现在我们已经知道怎么在相应的位置输出我们想要的字符图像了,那我们还需要删除一些图像,不然这蛇不吃都胖,我不会删除,但是经过我的一系列试验测试,我发现,字符是可以被覆盖的,所以我用输出“ ”来替代删除。一边打印蛇头,一边用空格覆盖蛇尾,于是画面就更新了。

然后怎么维持画面的刷新的速度呢,我是用死循环,通过时间函数在里面跑一定的时间再跳出来,就可以消耗时间,使界面刷新变慢,小蛇自然也就运动的慢了起来。

主要需要用到的语法就是这些了,其余的全是一些界面的优化,有兴趣的可以自己去搜索一些。

现在讲讲我的思路。

在我学会以上语法之后,我的思路就渐渐完善了,删减了一些我最初思路中我无法实现的东西,改为用以上语法来等效替代,比如删除字符我不会,用了替代字符。

我一直认为,写程序就像垒积木,组装零件,每一个程序都是通过一些函数通过精巧的思路组装,逐渐成型成一个完美的程序。这也是编程最吸引我的我一点,我觉得编程更注重思路而不是其他,就像数学一样。

啊跑偏了,现在真的讲思路了。

贪吃蛇,蛇不断的在区域里移动,蛇头触碰到了食物就变长,没吃到食物就不变,需要程序有效运行很久,所以我们需要在最外层写上一个死循环,从图像上来看,似乎每次操作,只要把蛇头变更为蛇身,蛇头向它的行进方向进行判定,有无自撞或者有无吃到食物,如果自撞游戏结束,如果吃到食物,我们就不进行其他操作,因为其实此时原蛇身应该是没有任何变化的。但如果没有吃到食物,其实很容易发现,每次变化的也只有蛇尾而已,每次把蛇尾的那个字符给消失,并不需要模拟蛇的身子都向前走,不过用数组指针模拟蛇前进也是一种可行办法,不过效率应该会低上一点,毕竟每次移动都是整个蛇身在动,而我的想法只需要动两个——蛇头和蛇尾。

但这个也有一个问题,用数组或指针模拟的话,蛇身该怎么动一目了然,走到上一截身体的位置就行了。 而我的想法蛇尾就不能跟着上一截身体走了,毕竟他们根本没动。那如果遇上拐弯什么的,我不就找不到蛇尾了吗?

我的尾巴它不见了呜呜呜!

博主想了想,发现蛇尾需要有延迟的跟着蛇头动,假如蛇长为4,那么,我蛇尾运动的方向应该是蛇头三格前运动的方向,我就照着这个方向覆盖蛇尾不就好起来了吗!

所以,博主掏出了他的法宝,队列!

队列的先进先出,真的是完完全全符合博主对于它的期望。将蛇头的运动方向存进队列,如果没吃到食物,那么从队列前头取出一个方向,正是蛇头很久之前的运动方向。

另外,博主还有一些疑惑,希望有路过的大佬能帮助解决一下,博主在此先谢过。

1.博主最开始除了方向键,还弄了一套wasd作为操作系统的,可是在判断按键的case那里写上了却并不能触发,还有空格也是,本来博主还想加一个暂停的功能的。

2.博主想知道ascll码-32到底是啥东西,实在是百度不到,尝试输出也是一片空白,在只取一次的按键的时候界面卡顿的不行,但是加上了这个之后,取两次按键,操作立竿见影的顺畅了起来。

顺带一提,调试真好用!

本博客鸣谢那些所有在网络上分享自己所学的大佬,感谢你们分享的知识,让我能实现这个贪吃蛇的代码。

本博客转载还请注明出处,谢谢。

  • 36
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值