C++趣味编程——贪吃蛇游戏

一、变量作用域和框架

        在程序中变量起作用的范围,称为变量的作用域。根据作用域的不同C语言中的变量可以分为局部变量全局变量

局部变量

        在函数内部定义的变量称为局部变量,其作用域从变量定义开始,到函数结束时“}”处结束。

#include <conio.h>
#include <stdio.h>

void fun()
{
	int a; // 局部变量,仅能在fun函数内部使用
	a = 10;
}

int main()
{
	int x; // 局部变量,仅能在main函数内部使用
	x = 1;
	_getch();
	return 0;
}

全局变量

        全局变量是在所有函数定义之外的变量,其作用域从变量定义处开始,到整个程序最后结束。

#include <conio.h>
#include <stdio.h>

int m = 1; // 全局变量,整个程序都可以访问

void fun()
{
	m = 3;
}

int main()
{
	printf("%d\n",m);
	m = 2;
	printf("%d\n",m);
	fun();
	printf("%d\n",m);
	_getch();
	return 0;
}

        ps.如果全局变量与局部变量同名,则在局部变量作用域内取局部变量访问

        利用函数和变量作用域的知识,可以设计出一个简单的游戏框架

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>

// 全局变量定义

void startup()  //  初始化函数
{
}

void show()  // 绘制函数
{
}	

void updateWithoutInput() // 与输入无关的更新
{
}

void updateWithInput()  // 和输入有关的更新
{
}

int main() //  主函数
{
	startup();  // 初始化函数,仅执行一次	
	while (1)   // 一直循环
	{
		show();  // 进行绘制
		updateWithoutInput(); // 和输入无关的更新 
		updateWithInput();    // 和输入有关的更新
	}
	return 0;

        首先在所有函数外定义全局变量,然后通过四个函数实现具体功能:

        程序从主函数开始,先运行一次startup(),进行游戏初始化;再进入show()函数进行绘制;updateWithoutInput()执行重复和输入无关的更新,updateWithInput()执行和输入有关的更新

二、基于二维数组的游戏地图

        

        为了实现游戏地图的网状效果,这一节介绍二维数组的概念:

#include <conio.h>
#include <stdio.h>

int main()
{
	int a[3][5];
	int i,j;
	for (i=0;i<3;i++)
		for (j=0;j<5;j++)
			a[i][j] = i*10 + j;

	for (i=0;i<3;i++)
	{
		for (j=0;j<5;j++)
			printf("%3d",a[i][j]);
		printf("\n");
	}
	_getch();
	return 0;
}

        上述代码中,int  a[3][5];定义了一个二维数组a,有三行五列15个元素

  二维数组定义     

 对于二维数组定义时初始化,也可以采用如下方式,比如定义a[2][4] 时:

1、int   a[2][4]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}

        {1,2,3,4,5}赋值给第0行, {6,7,8,9,10}赋值给第1行 ,{11,12,13,14,15}赋值给第2行

2、int   a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}

        把所有数据写在一个花括号内,依次对其赋值

3、int   a[3][4]={1,2,3,4}

        如果只对部分元素赋值,其余元素自动为0

        接下来,利用二维数组来存储地图信息:

首先定义格子数目

#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 

 然后用数组来存储格子的信息

int Blocks[HEIGHT][WIDTH] = { 0 }; //  二维数组,用于记录所有的游戏数据

再用双重for循环给二维数组赋值 

for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素值初始化
		for (j = 0;j < WIDTH;j++)
			Blocks[i][j] = rand() % 30; // 赋为随机数

开始作图,新开画面

initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); //  新开画面
	cleardevice();

根据随机的二维数组数值设定,宽与高重复循环填充颜色,在对应位置绘制小方格


	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素遍历
	{
		for (j = 0;j < WIDTH;j++)
		{
			setlinecolor(RGB(200, 200, 200));
			setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); // 根据元素值设定填充颜色	
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
		}
	}

完整代码如下:

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>

#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 

int main() // 主函数
{
	initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); //  新开画面
	cleardevice();
	int Blocks[HEIGHT][WIDTH] = { 0 }; //  二维数组,用于记录所有的游戏数据
	int i, j;

	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素值初始化
		for (j = 0;j < WIDTH;j++)
			Blocks[i][j] = rand() % 30; // 赋为随机数

	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素遍历
	{
		for (j = 0;j < WIDTH;j++)
		{
			setlinecolor(RGB(200, 200, 200));
			setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); // 根据元素值设定填充颜色	
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
		}
	}

	_getch();
	closegraph();
	return 0;
}

代码输出结果如图:

        

        

        接下来,让“蛇”的身体部分有颜色,其余变为灰色。在数组中,令蛇身以外的部分数值为0,蛇头蛇身部分用1,2,3···来记录 ,设置颜色和绘制方块如下

if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
				setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
			else
				setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
				(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);

        fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);

  fillrectangle()是绘制矩形的函数,其中四个参数对应长方形四个角的坐标,在之前小球躲避方块有提及。

完整代码如下:

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 
int main() // 主函数
{
	initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); //  新开画面
	setlinecolor(RGB(200, 200, 200));
	cleardevice();
	int Blocks[HEIGHT][WIDTH] = { 0 }; //  二维数组,用于记录所有的游戏数据
	int i, j;
	Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
	for (i = 1;i <= 4;i++) //  向左依次4个蛇身,数值依次为2,3,4,5
		Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素遍历
	{
		for (j = 0;j < WIDTH;j++)
		{
			if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
				setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
			else
				setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
				(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
		}
	}
	_getch();
	closegraph();
	return 0;
}

完整代码输出结果如下: 

将其放入框架的初始绘制函数中,整理之前最开始给出的简单框架:

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 

// 全局变量定义
int Blocks[HEIGHT][WIDTH] = {0}; //  二维数组,用于记录所有的游戏数据

void startup()  //  初始化函数
{
	int i;
	Blocks[HEIGHT/2][WIDTH/2] = 1; // 画面中间画蛇头,数字为1
	for (i=1;i<=4;i++) //  向左依次4个蛇身,数值依次为2,3,4,5
		Blocks[HEIGHT/2][WIDTH/2-i] = i+1;
	initgraph(WIDTH*BLOCK_SIZE,HEIGHT*BLOCK_SIZE); //  新开画面
	setlinecolor(RGB(200,200,200)); // 设置线条颜色
	BeginBatchDraw(); // 开始批量绘制
}

void show()  // 绘制函数
{
	cleardevice(); // 清屏
	int i,j;
	for (i=0;i<HEIGHT;i++) //  对二维数组所有元素遍历
	{ 
		for (j=0;j<WIDTH;j++)
		{
			if (Blocks[i][j]>0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
				setfillcolor(HSVtoRGB(Blocks[i][j]*10,0.9,1));
			else 
				setfillcolor(RGB(150,150,150)); // 元素为0表示为空,颜色为灰色
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j*BLOCK_SIZE,i*BLOCK_SIZE,
				(j+1)*BLOCK_SIZE,(i+1)*BLOCK_SIZE); 
		}
	}
	FlushBatchDraw(); // 批量绘制
}	

void updateWithoutInput() // 与输入无关的更新函数
{
}

void updateWithInput()  // 和输入有关的更新函数
{
}

int main() //  主函数
{
	startup();  // 初始化函数,仅执行一次	
	while (1)   // 一直循环
	{
		show();  // 进行绘制
		updateWithoutInput(); // 和输入无关的更新 
		updateWithInput();    // 和输入有关的更新
	}
	return 0;
}

三、小蛇移动

3.1小蛇向右移动

        将原本数组中5,4,3,2,1的位置向右移动,不改变长度,其中一种思路是,先把所有元素+1,再把不该存在的6去掉,令原本的蛇头1增加到2的右边。

定义小蛇的移动代码如下: 

void moveSnake()
{
	int i,j;
	for (i = 0;i < HEIGHT;i++)//遍历行列找大于零的小蛇身躯
		for (j = 0;j< WIDTH;j++)
			if (Blocks[i][j] > 0)
				Blocks[i][j]++;//让其+1
	
	int oldTail_i, oldTail_j, oldHead_i, oldHead_j;//定义变量存储旧的蛇尾坐标,旧的蛇头坐标
	int max = 0;
	for (i = 0;i < HEIGHT;i++)
	{
		for (j = 0;j < WIDTH;j++)//遍历地图
		{
			if (max < Blocks[i][j])//如果有元素值大于max(初始为0)
			{
				max = Blocks[i][j];//不断更新找到最大的值
				oldTail_i = i;
				oldTail_j = j;//这个最大值就是旧蛇尾
			}
			if (Blocks[i][j] == 2)//找到+1后2的位置,即旧蛇头的位置
			{
				oldHead_i = i;
				oldHead_j = j;//记录旧的蛇头位置

			}
		}

	}
	int newHead_i = oldHead_i;
	int newHead_j = oldHead_j;
	newHead_j = oldHead_j+1;
	Blocks[newHead_i][newHead_j] = 1;
	Blocks[oldTail_i][oldTail_j] = 0;

}

 更新屏幕现实动画实现代码如下:


void updateWithoutInput() // 与输入无关的更新函数
{
	moveSnake();
	Sleep(100);

}

四、小蛇向四个方向移动

     变量oldHead_i,oldHead_j储存移动前的蛇头位置(对应元素在二维数组中的行号、列号),newHead_i、newHead_j存储移动后蛇头的位置。

        如果小蛇向上移动,只需把新蛇头的坐标设为旧蛇头的上方即可。即:

                                                newHead_i= oldHead_i+1;

4.1 if-else选择语句

        else不能单独出现,必须和if搭配使用,若else后有多条语句,也需要用{}将多条语句包含起来

样例如下:

#include<conio.h>
#include<stdio.h>
int main()
{
	int x;
	scanf("%d",&x);
	
	if(x>=90)
		printf("优秀");
	else if(x>=80)
		printf("良好");
	else if(x>=70)
		printf("中等");
	else if(x>=60)
		printf("及格");
	else if(x<60)
		printf("不及格");
	
	_getch();
	return 0; 
	
	
	
 } 

利用else语句,可以减少不必要的重复if判断

char moveDirection;
if(moveDirection=='a')//向左移动
  newHead_j=oldHead_j-1;
else if(moveDirection=='d')//向右移动
    newHead_j=oldHead_j+1;
else if(moveDirection=='w')//向上移动
    newHead_i=oldHead_i-1;
else if(moveDirection=='s')//向下移动
    newHead_i=oldHead_i+1;

利用 update WithInput函数获得按键输入

获得输入后更新moveDirection变量,执行moveSnake()函数让小蛇向对应方向移动;

void updateWithInput()  // 和输入有关的更新函数
{
	if(kbhit())
	{
		char input = getch();
		if (input == 'a' || input == 's' || input == 'd' || input == 'w')
		{
			moveDirection = input;
			moveSnake();


		}
	}
}

在此时需要定义小蛇初始的运动方向,即在未接收输入是小蛇的运动方向,在startup()函数中加入

moveDirection='d';

并且需要控制小蛇的运动方向不能为当前运动方向的反向

所以最终检测输入的代码如下:

char input = _getch();
		// 检查输入方向是否与当前方向相反
		if ((input == 'a' && moveDirection == 'd') ||
			(input == 'd' && moveDirection == 'a') ||
			(input == 'w' && moveDirection == 's') ||
			(input == 's' && moveDirection == 'w'))
		{
			// 输入为反方向,忽略操作
			return;
		}

		// 输入合法,更新方向
		if (input == 'a' || input == 's' || input == 'd' || input == 'w')
		{
			moveDirection = input;
			moveSnake(); // 调用小蛇移动函数
		}

五、控制时间的改进

        updateWithoutInput()函数中调用Sleep()函数来降低小蛇的移动速度。然而Sleep()运行时整个程序暂停,包括用户输入模块。

5.1静态变量与动态变量

#include<conio.h>
#include<stdio.h>

void fun()
{
	int m=0;
	m++;
	printf("%d\n",m);
	
 } 
 
 int main()
 {
 	fun();
 	fun();
 	fun();
 	_getch();
 	return 0;

 	
 }

        函数中定义的变量默认为动态变量,程序从变量的作用域开始,为其动态分配内存空间;到变量作用域结束时,收回变量的内存空间。

        主函数第一次调用fun()时,动态分配内存空间,m初始化为0,然后加1,输出1,fun()结束;第二次调用fun(),重新分配内存空间,m初始化为0,加1,输出1,收回;第三次同样。

        

将动态分配该为静态分布

static int m=0;

主函数第一次调用fun()时,分配内存空间,m初始化为0,然后加1,输出1,fun()结束,不收回空间;第二次调用fun(),m直接加1,输出2,收回;第三次调用,输出3。

利用静态变量,将updateWithoutInput()修改如下:

void updateWithoutInput() // 与输入无关的更新函数
{
	static int waitIndex = 1;//静态局部变量,初始化时为1
	waitIndex++;//每一帧加1
	if (waitIndex = 10)//等于10时执行,10帧小蛇移动一次
	{
		moveSnake();//调用小蛇移动
		waitIndex = 1;


	}

}

ps.动态变量如果不赋处置,其处置为随机函数;静态变量如果不赋初值,其处置为0.

六、添加食物

          添加全局变量记录食物的位置:

int food_i,food_j;

        在startup()函数中初始化食物的位置:

void startup()

{

food_i=rand()%(HEIGHT-5)+2;
food_j=rand()%(WIDTH-5)+2;

}

        在show()函数中在食物位置绘制一个绿色方块

setfillcolor(RGB(0, 255, 0));//绘制食物为绿色
	
fillrectangle(food_j * BLOCK_SIZE, food_i*BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE);

当新蛇头碰到食物时,保留原蛇尾,不必将最大值变为0,即可让蛇的长度加1

bool ateFood = (newHead_i == food_i && newHead_j == food_j);

	// 更新新蛇头的位置
	Blocks[newHead_i][newHead_j] = 1;

	// 如果吃到食物,不移除尾部;否则移除尾部
	if (!ateFood)
	{
		Blocks[oldTail_i][oldTail_j] = 0;
	}
	else
	{
		// 食物被吃掉,生成新食物
		food_i = rand() % (HEIGHT - 5) + 2;
		food_j = rand() % (WIDTH - 5) + 2;
	}

七、失败判断与显示

        定义全局变量isFailure表示游戏是否失败,初始化为0:

int isFailure=0;

        小蛇的失败条件判断如下:

//如果蛇头超出边界或者碰到蛇身,游戏失败
	if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j]>0)
	{
		isFailure = 1;
		return;

	}

        在show函数中添加游戏失败后的显示信息:

if (isFailure)//如果游戏失败
	{
		setbkmode(TRANSPARENT);//文字字体透明
		settextcolor(RGB(255, 0, 0));//设定文字颜色
		settextstyle(80, 0, _T("宋体"));//设定文字大小样式
		outtextxy(240, 220, _T("游戏失败"));//输出文字内容

	}

并在 updateWithoutInput() 给出条件让游戏失败时直接跳出后续的绘制

if (isFailure)
		return;//如果游戏失败,函数返回

对于updateWithInput() 也严格控制当不失败时才允许输入

if(_kbhit()&&isFailure==0)

完整游戏效果如图:

完整游戏代码如下:

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 

// 全局变量定义
int Blocks[HEIGHT][WIDTH] = { 0 }; //  二维数组,用于记录所有的游戏数据
int food_i, food_j;
char moveDirection;
int isFailure = 0;



void moveSnake()
{
	int i, j;
	// 遍历地图,让蛇的每一节身体的值增加 1
	for (i = 0; i < HEIGHT; i++)
		for (j = 0; j < WIDTH; j++)
			if (Blocks[i][j] > 0)
				Blocks[i][j]++;

	int oldTail_i = -1, oldTail_j = -1, oldHead_i = -1, oldHead_j = -1; // 定义变量存储旧蛇尾、蛇头坐标
	int max = 0;
	for (i = 0; i < HEIGHT; i++)
	{
		for (j = 0; j < WIDTH; j++) // 遍历地图
		{
			if (max < Blocks[i][j]) // 找到蛇尾,值最大的坐标
			{
				max = Blocks[i][j];
				oldTail_i = i;
				oldTail_j = j;
			}
			if (Blocks[i][j] == 2) // 找到蛇头,值为 2的位置
			{
				oldHead_i = i;
				oldHead_j = j;
			}
		}
	}

	// 计算新蛇头的位置
	int newHead_i = oldHead_i;
	int newHead_j = oldHead_j;

	if (moveDirection == 'a') // 向左移动
		newHead_j = oldHead_j - 1;
	else if (moveDirection == 'd') // 向右移动
		newHead_j = oldHead_j + 1;
	else if (moveDirection == 'w') // 向上移动
		newHead_i = oldHead_i - 1;
	else if (moveDirection == 's') // 向下移动
		newHead_i = oldHead_i + 1;


//如果蛇头超出边界或者碰到蛇身,游戏失败
	if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j]>0)
	{
		isFailure = 1;
		return;

	}


	// 判断是否吃到食物
	bool ateFood = (newHead_i == food_i && newHead_j == food_j);

	// 更新新蛇头的位置
	Blocks[newHead_i][newHead_j] = 1;

	// 如果吃到食物,不移除尾部;否则移除尾部
	if (!ateFood)
	{
		Blocks[oldTail_i][oldTail_j] = 0;
	}
	else
	{
		// 食物被吃掉,生成新食物
		food_i = rand() % (HEIGHT - 5) + 2;
		food_j = rand() % (WIDTH - 5) + 2;
	}
}






void startup()  //  初始化函数
{
	int i;
	food_i = rand()%(HEIGHT - 5) + 2;
	food_j= rand() % (WIDTH - 5) + 2;//初始化食物位置

	Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
	for (i = 1;i <= 4;i++) //  向左依次4个蛇身,数值依次为2,3,4,5
		Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
	moveDirection='d';
	initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); //  新开画面
	setlinecolor(RGB(200, 200, 200)); // 设置线条颜色
	BeginBatchDraw(); // 开始批量绘制
}

void show()  // 绘制函数
{
	cleardevice(); // 清屏
	int i, j;
	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素遍历
	{
		for (j = 0;j < WIDTH;j++)
		{
			if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
				setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
			else
				setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
				(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
		}
	}

	setfillcolor(RGB(0, 255, 0));//绘制食物为绿色
	fillrectangle(food_j * BLOCK_SIZE, food_i* BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE);

	if (isFailure)//如果游戏失败
	{
		setbkmode(TRANSPARENT);//文字字体透明
		settextcolor(RGB(255, 0, 0));//设定文字颜色
		settextstyle(80, 0, _T("宋体"));//设定文字大小样式
		outtextxy(240, 220, _T("游戏失败"));//输出文字内容

	}
	FlushBatchDraw(); // 批量绘制
}

void updateWithoutInput() // 与输入无关的更新函数
{

	if (isFailure)
		return;//如果游戏失败,函数返回
	static int waitIndex = 1;//静态局部变量,初始化时为1
	waitIndex++;//每一帧加1
	if (waitIndex == 10)//等于10时执行,10帧小蛇移动一次
	{
		moveSnake();//调用小蛇移动
		waitIndex = 1;


	}

}

void updateWithInput()  // 和输入有关的更新函数
{
	if(_kbhit()&&isFailure==0)//有按键输入,并且不失败
	{
		char input = _getch();
		if (input == 'a' || input == 's' || input == 'd' || input == 'w')
		{
			moveDirection = input;
			moveSnake();


		}
	}
}

int main() //  主函数
{
	startup();  // 初始化函数,仅执行一次	
	while (1)   // 一直循环
	{
		
		updateWithoutInput(); // 和输入无关的更新 
		updateWithInput();    // 和输入有关的更新
		show();  // 进行绘制
	}
	return 0;
}

八、优化与改进

        对于以上简单的贪吃蛇,依旧存在一些可以改进的地方,比如,按键实现游戏的重新开始,得分的显示,拾取食物后小蛇加速的实现

8.1给出重置游戏函数

void resetGame() {
    memset(Blocks, 0, sizeof(Blocks)); // 重置地图
    food_i = rand() % (HEIGHT - 5) + 2;
    food_j = rand() % (WIDTH - 5) + 2;
    Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 重置蛇头
    for (int i = 1; i <= 4; i++) // 重置蛇身
        Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
    moveDirection = 'd';
    score = 0;           // 重置得分
    speed = initialSpeed; // 重置速度
    isFailure = 0;        // 重置失败标志
}

在游戏失败时,按键输入使游戏重新开始

void updateWithInput()  // 和输入有关的更新函数
{


	if(_kbhit())//有按键输入

	{
		char input = _getch();
		if (isFailure && input == 'r') {
			resetGame();
		}
		if ( isFailure == 0 &&input == 'a' || input == 's' || input == 'd' || input == 'w')
		{
			moveDirection = input;
			moveSnake();


		}
	}
}

8.1得分显示

        对于得分,我们设置一个变量存储

int score = 0; // 当前得分

        在moveSnake()中设置吃到食物得分增加

score += 10;

得分的显示:

	TCHAR scoreText[50];//显示分数
	swprintf_s(scoreText, _T("Score: %d"), score);
	settextcolor(RGB(255, 255, 255));
	settextstyle(20, 0, _T("宋体"));
	outtextxy(10, 10, scoreText);

8.3 拾取食物后小蛇的加速

        设置变量控制速度,也即更新频率的帧数间隔

int speed = 10; // 初始速度(帧数间隔) int initialSpeed = 10;

        给出与输入无关的帧数更新代码

void updateWithoutInput() // 与输入无关的更新函数
{

	if (isFailure)
		return;//如果游戏失败,函数返回
	static int waitIndex = 1;//静态局部变量,初始化时为1
	waitIndex++;//每一帧加1
	if (waitIndex == speed)//等于速度时执行,每速度帧小蛇移动一次
	{
		moveSnake();//调用小蛇移动
		waitIndex = 1;


	}

}

最后游戏完整效果如下:

 

 最后优化后的完整代码如下:

#include <graphics.h>  
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小 
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 //  宽度上一共40个小格子 

// 全局变量定义
int Blocks[HEIGHT][WIDTH] = { 0 }; //  二维数组,用于记录所有的游戏数据
int food_i, food_j;
char moveDirection;
int isFailure = 0;
int score = 0;            // 当前得分
int speed = 10;           // 初始速度(帧数间隔)
int initialSpeed = 10;    // 初始速度,用于重置


void moveSnake()
{
	int i, j;
	// 遍历地图,让蛇的每一节身体的值增加 1
	for (i = 0; i < HEIGHT; i++)
		for (j = 0; j < WIDTH; j++)
			if (Blocks[i][j] > 0)
				Blocks[i][j]++;

	int oldTail_i = -1, oldTail_j = -1, oldHead_i = -1, oldHead_j = -1; // 定义变量存储旧蛇尾、蛇头坐标
	int max = 0;
	for (i = 0; i < HEIGHT; i++)
	{
		for (j = 0; j < WIDTH; j++) // 遍历地图
		{
			if (max < Blocks[i][j]) // 找到蛇尾,值最大的坐标
			{
				max = Blocks[i][j];
				oldTail_i = i;
				oldTail_j = j;
			}
			if (Blocks[i][j] == 2) // 找到蛇头,值为 2的位置
			{
				oldHead_i = i;
				oldHead_j = j;
			}
		}
	}

	// 计算新蛇头的位置
	int newHead_i = oldHead_i;
	int newHead_j = oldHead_j;

	if (moveDirection == 'a') // 向左移动
		newHead_j = oldHead_j - 1;
	else if (moveDirection == 'd') // 向右移动
		newHead_j = oldHead_j + 1;
	else if (moveDirection == 'w') // 向上移动
		newHead_i = oldHead_i - 1;
	else if (moveDirection == 's') // 向下移动
		newHead_i = oldHead_i + 1;


//如果蛇头超出边界或者碰到蛇身,游戏失败
	if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j]>0)
	{
		isFailure = 1;
		return;

	}


	// 判断是否吃到食物
	bool ateFood = (newHead_i == food_i && newHead_j == food_j);

	// 更新新蛇头的位置
	Blocks[newHead_i][newHead_j] = 1;

	// 如果吃到食物,不移除尾部;否则移除尾部
	if (!ateFood)
	{
		Blocks[oldTail_i][oldTail_j] = 0;
	}
	else
	{
		// 食物被吃掉,生成新食物
		food_i = rand() % (HEIGHT - 5) + 2;
		food_j = rand() % (WIDTH - 5) + 2;
		score += 10;
		speed = speed > 2 ? speed - 1 : speed; // 提升速度,避免过快限制最小值
	}
}


void resetGame() {
	memset(Blocks, 0, sizeof(Blocks)); // 重置地图
	food_i = rand() % (HEIGHT - 5) + 2;
	food_j = rand() % (WIDTH - 5) + 2;
	Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 重置蛇头
	for (int i = 1; i <= 4; i++) // 重置蛇身
		Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
	moveDirection = 'd';
	score = 0;           // 重置得分
	speed = initialSpeed; // 重置速度
	isFailure = 0;        // 重置失败标志
}



void startup()  //  初始化函数
{
	int i;
	food_i = rand()%(HEIGHT - 5) + 2;
	food_j= rand() % (WIDTH - 5) + 2;//初始化食物位置

	Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
	for (i = 1;i <= 4;i++) //  向左依次4个蛇身,数值依次为2,3,4,5
		Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
	moveDirection='d';
	initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); //  新开画面
	setlinecolor(RGB(200, 200, 200)); // 设置线条颜色
	BeginBatchDraw(); // 开始批量绘制
}

void show()  // 绘制函数
{
	cleardevice(); // 清屏
	int i, j;
	for (i = 0;i < HEIGHT;i++) //  对二维数组所有元素遍历
	{
		for (j = 0;j < WIDTH;j++)
		{
			if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
				setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
			else
				setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
			// 在对应位置处,以对应颜色绘制小方格
			fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
				(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
		}
	}

	setfillcolor(RGB(0, 255, 0));//绘制食物为绿色
	fillrectangle(food_j * BLOCK_SIZE, food_i* BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE);

	TCHAR scoreText[50];//显示分数
	swprintf_s(scoreText, _T("Score: %d"), score);
	settextcolor(RGB(255, 255, 255));
	settextstyle(20, 0, _T("宋体"));
	outtextxy(10, 10, scoreText);

	if (isFailure)//如果游戏失败
	{
		setbkmode(TRANSPARENT);//文字字体透明
		settextcolor(RGB(255, 0, 0));//设定文字颜色
		settextstyle(80, 0, _T("宋体"));//设定文字大小样式
		outtextxy(240, 220, _T("游戏失败"));//输出文字内容
		settextstyle(30, 0, _T("宋体"));
		outtextxy(240, 320, _T("按 R 键重新开始"));
	}
	FlushBatchDraw(); // 批量绘制
}

void updateWithoutInput() // 与输入无关的更新函数
{

	if (isFailure)
		return;//如果游戏失败,函数返回
	static int waitIndex = 1;//静态局部变量,初始化时为1
	waitIndex++;//每一帧加1
	if (waitIndex == speed)//等于速度时执行,每速度帧小蛇移动一次
	{
		moveSnake();//调用小蛇移动
		waitIndex = 1;


	}

}

void updateWithInput()  // 和输入有关的更新函数
{


	if(_kbhit())//有按键输入
	{
		char input = _getch();
		if (isFailure && input == 'r')
		{
			resetGame();
		}
	;
		// 检查输入方向是否与当前方向相反
		if ((input == 'a' && moveDirection == 'd') ||
			(input == 'd' && moveDirection == 'a') ||
			(input == 'w' && moveDirection == 's') ||
			(input == 's' && moveDirection == 'w'))
		{
			// 输入为反方向,忽略操作
			return;
		}

		// 输入合法,更新方向
		if (input == 'a' || input == 's' || input == 'd' || input == 'w')
		{
			moveDirection = input;
			moveSnake(); // 调用小蛇移动函数
		}
	}
}
	


int main() //  主函数
{
	startup();  // 初始化函数,仅执行一次	
	while (1)   // 一直循环
	{
		
		updateWithoutInput(); // 和输入无关的更新 
		updateWithInput();    // 和输入有关的更新
		show();  // 进行绘制
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值