贪吃蛇(一)--用C++编写一个简单的贪吃蛇

这里简单介绍怎么用C++编写一个简单的黑白框的贪吃蛇游戏,复杂的加了可视化界面程序点击这里贪吃蛇(二)–easyX图形库进行可视化界面制作。

首先分析在黑白框中的贪吃蛇需要哪些功能:
(1)需要能在界面指定位置(x,y)直接输出对应内容
(2)需要动态数组储存蛇的身体节点
(3)需要能接收键盘指令对贪吃蛇运动方向进行调整
(4)需要随机生成食物
(5)判断蛇是否撞到墙或者自己的身体

基本满足这些功能就可以实现一个简单的贪吃蛇,但是为了游戏的稳定性我们需要尽可能考虑详细,避免程序出bug。现在依次对这5个功能进行实现。

1.在界面指定位置(x,y)输出对应内容
这里直接引用C语言本身存在的一个操作台函数,代码如下:

#include
#include
using namespace std;
//在指定位置显示内容 
void gotoxy(int x,int y,char c)
{
	CONSOLE_SCREEN_BUFFER_INFO csbiInfo; //variablendklaration
	HANDLE hConsoleOut;
	hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleScreenBufferInfo(hConsoleOut,&csbiInfo);
	csbiInfo.dwCursorPosition.X = x; //cursorposition X koordinate festlegen
	csbiInfo.dwCursorPosition.Y = y; //cursorposition Y koordinate festlegen
	SetConsoleCursorPosition(hConsoleOut,csbiInfo.dwCursorPosition); //den cursor an die festgelegte koordinate setzen
	printf("%c",c);//输出你指定的字符 
}  
int main()
{
	gotoxy(5,5,'*');//在坐标(5,5)的位置显示'*' 
	gotoxy(10,10,'#'); //在坐标(10,10)的位置显示'#'
	return 0;
} 

其中的gotoxy函数就是我们可以在指定位置输出指定字符的函数,其对应的库函数为"windows.h",上述代码运行结果为
贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第1张图片
于是我们趁热打铁,把贪吃蛇的围墙顺便画出来,围墙为一个矩形,而矩形的左上角和右下角端点坐标就可以确定这个矩形,于是我们只需要定义左上角和右下角坐标即可。

int X1=1,Y1=1;//活动范围的左上角坐标
int X2=60,Y2=30;//活动范围的右下角坐标
void init()//初始化函数 
{
	for(int i=X1;i<=X2;i++)
	{
		gotoxy(i,Y1,'#');
		gotoxy(i,Y2,'#');
	}
	for(int j=Y1;j<=Y2;j++)
	{
		gotoxy(X1,j,'#');
		gotoxy(X2,j,'#');
	}	
} 

在主函数中运行初始化函数init()结果为
贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第2张图片于是画出了一个简单的边框了。

2.动态数组储存蛇的身体节点
这里直接用stl库中的vector储存了吧,然后定义一个结构体表示蛇的身体节点,这样书写比较容易,嘿嘿,代码如下。

struct Snake
{
	int x,y;
};
vector<Snake>snake;

然后库函数部分记得添加vector

#include
#include
#include
using namespace std;

这就是定义简单的蛇节点的动态数组了,然后我们初始化让开始的蛇头在活动范围的中间位置,然后在主函数中一直循环显示出来。这里我让蛇先按朝着往下的方向移动,蛇的节点很多,那么如何让蛇移动呢?其实只需要改变蛇头位置就行,然后往后的节点更新到上一个节点的坐标位置,比如先把蛇头位置更新给第二个节点,蛇头位置再更新成我们调整的位置。

但是蛇移动后原来位置输出的内容是需要清除的,这里我需要补充说一下了,可能在很多其他的贪吃蛇的写法中,为了书写方便直接用了system(“cls”)清空了整个界面内容,再把新的界面信息输出,这样没问题,但是黑白框不是专门用于可视化界面程序的,这样会让贪吃蛇程序看起来很卡顿,后期的可视化界面的贪吃蛇确实需要直接清空整个界面,但是现在的这个黑白框的程序没必要,我为了减小程序时间复杂度,考虑到贪吃蛇实际每次运动都是两个节点在变化,蛇头和蛇尾,蛇头是新输出的内容不管,蛇尾移动后原来的蛇尾就需要清空,于是我们在每次更新蛇的节点信息前,先把原来的蛇尾的位置清除,用gotoxy(x,y,’ ');空格就会显示没有,看起来就是清除的作用。
然后到这里完整的代码就是这样了:

#include
#include
#include
using namespace std;
//在指定位置显示内容 
void gotoxy(int x,int y,char c)
{
	CONSOLE_SCREEN_BUFFER_INFO csbiInfo; //variablendklaration
	HANDLE hConsoleOut;
	hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleScreenBufferInfo(hConsoleOut,&csbiInfo);
	csbiInfo.dwCursorPosition.X = x; //cursorposition X koordinate festlegen
	csbiInfo.dwCursorPosition.Y = y; //cursorposition Y koordinate festlegen
	SetConsoleCursorPosition(hConsoleOut,csbiInfo.dwCursorPosition); //den cursor an die festgelegte koordinate setzen
	printf("%c",c);//输出你指定的字符 
}  
int X1=1,Y1=1;//活动范围的左上角坐标
int X2=60,Y2=30;//活动范围的右下角坐标
struct Snake
{
	int x,y;
};
vector<Snake>snake;
void init()//初始化函数 
{ 
	for(int i=X1;i<=X2;i++)
	{
		gotoxy(i,Y1,'#');
		gotoxy(i,Y2,'#');
	}
	for(int j=Y1;j<=Y2;j++)
	{
		gotoxy(X1,j,'#');
		gotoxy(X2,j,'#');
	}
	snake.clear();//初始化清空	
	Snake t;
	t.x=(X1+X2)/2;
	t.y=(Y1+Y2)/2;
	snake.push_back(t);
} 
//更新的位置,上下左右 
int XX[4]={0,0,-1,1};
int YY[4]={-1,1,0,0}; 
void Print(int direction)//显示蛇的内容
{
	int n=snake.size()-1;
	gotoxy(snake[n].x,snake[n].y,' ');//先清除蛇尾留下的痕迹 
	for(int i=n;i>=1;i--)
	snake[i]=snake[i-1];//进行节点更新
	snake[0].x+=XX[direction];
	snake[0].y+=YY[direction]; 
	for(int i=1;i<=n;i++)
	gotoxy(snake[i].x,snake[i].y,'*');
	gotoxy(snake[0].x,snake[0].y,'@');//蛇头用@表示 
} 
int main()
{
	init();
	while(true)
	{
		Print(1);
		Sleep(500);//延迟500ms,控制程序显示时间	
	} 
	return 0;
} 

动态图显示如下

贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第3张图片
到这里看起来就有点意思了,我们继续干。

3.接收键盘指令对贪吃蛇运动方向进行调整
现在要根据我们输入的指令进行贪吃蛇运动的调整,我们的贪吃蛇运动是通过死循环+睡眠延时所以看起来在运动一样,那么怎么在循环过程中加入操作指令呢,这里又需要用到一个函数了,叫kbhit函数,这个函数是键盘监听函数,主要功能就是如果当前你按了键盘(不论什么键),这个函数就会返回true,表示有键盘指令,不然就是false,表示当前无操作。于是我们可以利用这个输入我们的操作指令。其次还需要一个函数,getch(),这个函数是输入单个字符,不用再按回车,因为我们按方向键后要求蛇马上按我们的方向去运动。

kbhit和getch函数都是在库函数conio.h中,注意添加库函数

#include

然后在主函数中进行的更改为

int main()
{
	init();
	int direction=1;
	while(true)
	{
		Print(direction);
		if(kbhit())//判断有键盘指令输入 
		{
			char c=getch();
			if(c=='W')//往上 
			direction=0;
			else if(c=='S')//往下 
			direction=1;
			else if(c=='A')//往左 
			direction=2;
			else if(c=='D')//往右 
			direction=3;
		}
		Sleep(300);//延迟300ms,控制程序显示时间	
	} 
	return 0;
} 

我现在手动输入方向键“W”,“S”,“A”,"D"进行一定方向操作控制,运行结果如下。
贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第4张图片
这里备注下,因为截取的动态图没有完全显示,实际显示的内容@后面会有一个白色的光标,因为@是最后一个输出的内容,会跟着一个白色光标,为了美观,我们将白色光标移去其他位置,比如每次在界面外输出gotoxy(x,y,’ '),x和y是在这个墙的范围外的任意一点。
在主函数中添加如下

	while(true)
	{
		Print(direction);
		if(kbhit())//判断有键盘指令输入 
		{
			char c=getch();
			if(c=='W')//往上 
			direction=0;
			else if(c=='S')//往下 
			direction=1;
			else if(c=='A')//往左 
			direction=2;
			else if(c=='D')//往右 
			direction=3;
		}
		gotoxy(X2+1,Y2+1,' ');//为了不在游戏界面中显示白色光标影响界面效果 
		Sleep(300);//延迟500ms,控制程序显示时间	
	} 

4.需要随机生成食物

随机生成食物,也是整个程序最复杂的部分,你以为是简单的随机数生成吗?不是,确实需要随机生成,但是更麻烦的在于,如果随机生成的位置恰好是蛇的身体部分,那么是不对的,重新再生成,但是如果蛇越来越长,那么就会有很大的概率生成的食物和蛇的身体位置冲突,就会导致一直循环判断生成食物不要和蛇的身体冲突,甚至会死循环,因为是随机生成的,你难免会每次都生成的食物位置恰好和蛇的身体都冲突了。

所以怎么随机生成食物,恰好又可以高效的找到不会和蛇的身体冲突的位置呢?

于是我想到了一个有趣的方法,分治处理,我们的地图是二维的,但是可以转换成一维表示,这个没问题,然后我们针对给定的一维坐标范围[L,R]随机生成一个数字X,把X转换成二维坐标,如果这个X对应的二维坐标和蛇的身体冲突了,那么就二分处理,分成[L,X-1]和[X+1,R]两个子区间,再分别在这两个子区间随机找,一直这样下去,就可以很快找到不会和蛇的身体冲突的坐标,递归处理,哈哈哈。
随机递归生成食物代码如下

int food_x,food_y; 
bool search(int L,int R)
{
	if(L>R)return false;
	int MID=random(L,R);
	int d=X2-X1-1;//不能包括围墙,需要把围墙的宽度减去 
	int y=(MID+d)/d+X1;
	int t=MID%d;
	int x=Y1;
	if(t==0)
	x+=d;
	else x+=t; 
	bool flag=false;
	for(int i=0;i<snake.size();i++)
	{
		if(snake[i].x==x&&snake[i].y==y)
		{
			flag=true;
			break;
		}
	}
	if(flag)
	{
		bool res=search(L,MID-1);
		if(res)return true;
		res=search(MID+1,R);
		if(res)return true;
		return false;
	}
	else 
	{
		food_x=x;
		food_y=y;
		return true;
	}
}

目前为止完整代码如下,目前已经可以正常使用,吃食物等,但是没有添加撞墙判断

#include
#include
#include
#include
#include
using namespace std;
#define random(a,b) (rand()%(b-a+1)+a)
//在指定位置显示内容 
void gotoxy(int x,int y,char c)
{
	CONSOLE_SCREEN_BUFFER_INFO csbiInfo; //variablendklaration
	HANDLE hConsoleOut;
	hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleScreenBufferInfo(hConsoleOut,&csbiInfo);
	csbiInfo.dwCursorPosition.X = x; //cursorposition X koordinate festlegen
	csbiInfo.dwCursorPosition.Y = y; //cursorposition Y koordinate festlegen
	SetConsoleCursorPosition(hConsoleOut,csbiInfo.dwCursorPosition); //den cursor an die festgelegte koordinate setzen
	printf("%c",c);//输出你指定的字符 
}  
int X1=1,Y1=1;//活动范围的左上角坐标
int X2=60,Y2=30;//活动范围的右下角坐标
struct Snake
{
	int x,y;
};
vector<Snake>snake;
void init()//初始化函数 
{ 
	srand((int)time(0));  // 产生随机种子  把0换成NULL也行
	for(int i=X1;i<=X2;i++)
	{
		gotoxy(i,Y1,'#');
		gotoxy(i,Y2,'#');
	}
	for(int j=Y1;j<=Y2;j++)
	{
		gotoxy(X1,j,'#');
		gotoxy(X2,j,'#');
	}
	snake.clear();//初始化清空	
	Snake t;
	t.x=(X1+X2)/2;
	t.y=(Y1+Y2)/2;
	snake.push_back(t);
} 
//更新的位置,上下左右 
int XX[4]={0,0,-1,1};
int YY[4]={-1,1,0,0};
int food_x,food_y; 
bool search(int L,int R)
{
	if(L>R)return false;
	int MID=random(L,R);
	int d=X2-X1-1;//不能包括围墙,需要把围墙的宽度减去 
	int y=(MID+d)/d+X1;
	int t=MID%d;
	int x=Y1;
	if(t==0)
	x+=d;
	else x+=t; 
	bool flag=false;
	for(int i=0;i<snake.size();i++)
	{
		if(snake[i].x==x&&snake[i].y==y)
		{
			flag=true;
			break;
		}
	}
	if(flag)
	{
		bool res=search(L,MID-1);
		if(res)return true;
		res=search(MID+1,R);
		if(res)return true;
		return false;
	}
	else 
	{
		food_x=x;
		food_y=y;
		return true;
	}
}
void Print(int direction)//显示蛇的内容
{
	int n=snake.size()-1;
	gotoxy(snake[n].x,snake[n].y,' ');//先清除蛇尾留下的痕迹 
	for(int i=n;i>=1;i--)
	snake[i]=snake[i-1];//进行节点更新
	snake[0].x+=XX[direction];
	snake[0].y+=YY[direction]; 
	for(int i=1;i<=n;i++)
	gotoxy(snake[i].x,snake[i].y,'*');
	gotoxy(snake[0].x,snake[0].y,'@');//蛇头用@表示 
	gotoxy(food_x,food_y,'O');
} 
int main()
{
	init();
	int direction=1;
	bool is_food=false;//是否有食物判断
	while(true)
	{
		if(is_food==false)
		{
			search((X1+2)*(Y1+2),(X2-2)*(Y2-2));
			is_food=true;
		}
		Print(direction);
		
		if(snake[0].x==food_x&&snake[0].y==food_y)
		{
			Snake t;
			t.x=food_x,t.y=food_y;
			is_food=false;
			snake.insert(snake.begin(),t);
		}
		if(kbhit())//判断有键盘指令输入 
		{
			char c=getch();
			if(c=='W')//往上 
			direction=0;
			else if(c=='S')//往下 
			direction=1;
			else if(c=='A')//往左 
			direction=2;
			else if(c=='D')//往右 
			direction=3;
		}
		gotoxy(X2+1,Y2+1,' ');//为了不在游戏界面中显示白色光标影响界面效果 
		Sleep(150);//延迟150ms,控制程序显示时间	
	} 
	return 0;
} 

现在的运行结果如下图所示
贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第5张图片

现在进行最简单的,就是判断碰撞。

5.判断蛇是否撞到墙或者自己的身体

这里很简单了,就是用蛇头去判断有没有和墙或者自己的身体相撞就可以了,不然就输出游戏结束,也可以自己设计累计得分。

**

最终的完整代码

**

#include
#include
#include
#include
#include
using namespace std;
#define random(a,b) (rand()%(b-a+1)+a)
//在指定位置显示内容 
void gotoxy(int x,int y,char c)
{
	CONSOLE_SCREEN_BUFFER_INFO csbiInfo; //variablendklaration
	HANDLE hConsoleOut;
	hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleScreenBufferInfo(hConsoleOut,&csbiInfo);
	csbiInfo.dwCursorPosition.X = x; //cursorposition X koordinate festlegen
	csbiInfo.dwCursorPosition.Y = y; //cursorposition Y koordinate festlegen
	SetConsoleCursorPosition(hConsoleOut,csbiInfo.dwCursorPosition); //den cursor an die festgelegte koordinate setzen
	printf("%c",c);//输出你指定的字符 
}  
int X1=1,Y1=1;//活动范围的左上角坐标
int X2=60,Y2=30;//活动范围的右下角坐标
struct Snake
{
	int x,y;
};
vector<Snake>snake;
void init()//初始化函数 
{ 
	srand((int)time(0));  // 产生随机种子  把0换成NULL也行
	for(int i=X1;i<=X2;i++)
	{
		gotoxy(i,Y1,'#');
		gotoxy(i,Y2,'#');
	}
	for(int j=Y1;j<=Y2;j++)
	{
		gotoxy(X1,j,'#');
		gotoxy(X2,j,'#');
	}
	snake.clear();//初始化清空	
	Snake t;
	t.x=(X1+X2)/2;
	t.y=(Y1+Y2)/2;
	snake.push_back(t);
} 
//更新的位置,上下左右 
int XX[4]={0,0,-1,1};
int YY[4]={-1,1,0,0};
int food_x,food_y; 
bool search(int L,int R)
{
	if(L>R)return false;
	int MID=random(L,R);
	int d=X2-X1-1;//不能包括围墙,需要把围墙的宽度减去 
	int y=(MID+d)/d+X1;
	int t=MID%d;
	int x=Y1;
	if(t==0)
	x+=d;
	else x+=t; 
	bool flag=false;
	for(int i=0;i<snake.size();i++)
	{
		if(snake[i].x==x&&snake[i].y==y)
		{
			flag=true;
			break;
		}
	}
	if(flag)
	{
		bool res=search(L,MID-1);
		if(res)return true;
		res=search(MID+1,R);
		if(res)return true;
		return false;
	}
	else 
	{
		food_x=x;
		food_y=y;
		return true;
	}
}
void Print(int direction)//显示蛇的内容
{
	int n=snake.size()-1;
	gotoxy(snake[n].x,snake[n].y,' ');//先清除蛇尾留下的痕迹 
	for(int i=n;i>=1;i--)
	snake[i]=snake[i-1];//进行节点更新
	snake[0].x+=XX[direction];
	snake[0].y+=YY[direction]; 
	for(int i=1;i<=n;i++)
	gotoxy(snake[i].x,snake[i].y,'*');
	gotoxy(snake[0].x,snake[0].y,'@');//蛇头用@表示 
	gotoxy(food_x,food_y,'O');
} 
bool game_over()
{
	for(int i=1;i<snake.size();i++)
	{
		if(snake[0].x==snake[i].x&&snake[0].y==snake[i].y)return false;
	}
	if(snake[0].x>=X2||snake[0].x<=X1)return false;
	if(snake[0].y>=Y2||snake[0].y<=Y1)return false;
	return true;
}
int main()
{
	init();
	int direction=1;
	bool is_food=false;
	while(true)
	{

		if(is_food==false)
		{
			search((X1+2)*(Y1+2),(X2-2)*(Y2-2));
			is_food=true;
		}
		if(snake[0].x==food_x&&snake[0].y==food_y)
		{
			Snake t;
			t.x=food_x,t.y=food_y;
			is_food=false;
			snake.insert(snake.begin(),t);
		}
		Print(direction);		
		if(game_over()==false)
		{
			gotoxy(X1,Y2+1,' ');
			cout<<"游戏结束!!!\n";
			return 0;
		}
		if(kbhit())//判断有键盘指令输入 
		{
			char c=getch();
			if(c=='W')//往上 
			direction=0;
			else if(c=='S')//往下 
			direction=1;
			else if(c=='A')//往左 
			direction=2;
			else if(c=='D')//往右 
			direction=3;
		}
		gotoxy((X1+X2)/2,Y2+2,' ');//为了不在游戏界面中显示白色光标影响界面效果
		cout<<"当前得分:"<<snake.size()*10<<endl;
		Sleep(150);//延迟150ms,控制程序显示时间	
	} 
	return 0;
} 

运行结果
贪吃蛇(一)--用C++编写一个简单的贪吃蛇_第6张图片

后期将写一个有可视化界面的贪吃蛇程序,希望我的分享对你的学习有所帮助,如果有问题请及时指出,谢谢~

你可能感兴趣的:(游戏,游戏,c++,编程语言)