C语言制作游戏——贪吃蛇

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:[email protected]

成品图

C语言制作游戏——贪吃蛇_第1张图片

游戏模块组成

  • 开拓疆土——绘制游戏窗口
  • 上帝造蛇和食物——初始化蛇和食物
  • 自动裁判员——菜单信息
  • 食物从天而降——食物的随机生成
  • 让蛇扭起来——移动蛇
  • 大开吃戒——吃食物
  • 瞬间移动——穿墙
  • 生命凋零——蛇死亡
  • 一切的开始——游戏开始
  • 一切的结束——结束游戏
  • **上帝控制台——主函数 **

开拓疆土——绘制游戏窗口

大家都玩过游戏,游戏一定有一个限制框来限制我们游戏主人公的行动,说白了就是地图,这就是我们的游戏窗口。见如下代码

void MakeFrame(HANDLE hOut)
{
	//打印边框
	SetPosition(hOut, FRAMEX, FRAMEY);										//设置光标为左上角坐标处
	printf("┏");

	SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY);					//设置光标为右上角坐标处
	printf("┓");
	
	SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT);						//设置光标为左下角坐标处
	printf("┗");

	SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT);	//设置光标为右下角坐标处
	printf("┛");

	//水平-顶端
	for (int i = 2; i < 2 * FRAMEWIDTH - 2; i+=2)
	{
		SetPosition(hOut, FRAMEX + i, FRAMEY);
		printf("━");
	}

	//水平-底端
	for (int i = 2; i < 2 * FRAMEWIDTH - 2; i += 2)
	{
		SetPosition(hOut, FRAMEX + i, FRAMEY + FRAMEHEIGHT);
		printf("━");
	}

	//竖直-左端
	for (int i = 1; i < FRAMEHEIGHT; i++)
	{
		SetPosition(hOut, FRAMEX, FRAMEY + i);
		printf("|");
	}

	//竖直-右端
	for (int i = 1; i < FRAMEHEIGHT; i++)
	{
		SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + i);
		printf("|");
	}

	//打印游戏名称
	SetPosition(hOut, FRAMEX + FRAMEWIDTH - 5, FRAMEY - 2);
	printf("贪吃蛇游戏");

	//打印游戏操作
	SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 2);
	printf("游戏操作:	上: ↑	左: ←	右: →	下: ↓");
	SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 4);
	printf("加速: 长按方向键	退出: ESC	暂停:Space");
	
}

以上代码需要说明的有
1. SetPosition(HANDLE hOut, int x, int y)函数:

void SetPosition(HANDLE hOut, int x, int y)
{
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOut, pos);
}

从上式代码可以看出,SetPosition(HANDLE hOut, int x, int y)函数是用于把光标放在我们需要的(x, y)上,从而能在(x, y)处打印我们需要的东西。

**2. 常量:FRAMEX和FRAMEY和FRAMEHEIGHT和FRAMEWIDTH的含义 **
正如常量的名字一样,我们在主函数的前面定义了这四个常量,其含义和数值如下:

#define FRAMEX	4				//窗口左上角横坐标
#define FRAMEY	4				//窗口左上角纵坐标
#define FRAMEWIDTH	25			//游戏窗口宽度
#define FRAMEHEIGHT	25			//游戏窗口高度

上帝造蛇和食物——初始化蛇和食物

根据蛇在本游戏中具有的性质,我们将这些性质打包到名为蛇的结构体中,同理也定义了一个食物的结构体。代码如下:
//蛇
typedef struct SNAKE
{
	int x[100];					//蛇的横坐标, x[0]蛇尾横坐标
	int y[100];					//蛇的纵坐标, y[0]蛇尾纵坐标
	int nCount;					//蛇吃食物总数
	int nLength;				//蛇的长度
	int nSpeed;					//蛇的移动速度
}Snake;

//食物
typedef struct FOOD
{
	int x;	//食物的横坐标
	int y;	//食物的纵坐标
}Food;

//初始化蛇
void InitSnake(Snake* pSnake)
{
	pSnake->x[0] = FRAMEX + 2;					//初始化蛇尾的横坐标
	pSnake->y[0] = FRAMEX + FRAMEHEIGHT / 2;	//初始化蛇尾的纵坐标
	pSnake->nSpeed = 80;						//初始化蛇的移动速度
	pSnake->nCount = 0;							//食物个数
	pSnake->nLength = 3;						//蛇身长度

	for (int i = 1; i < pSnake->nLength; i++)
	{
		pSnake->x[i] = pSnake->x[i - 1] + 2;
		pSnake->y[i] = pSnake->y[i - 1];
	}
}		

//打印蛇
void PrintfSnake(HANDLE hOut, Snake* pSnake)
{
	for (int i = 0; i < pSnake->nLength; i++)
	{
		SetPosition(hOut, pSnake->x[i], pSnake->y[i]);

		//打印蛇尾
		if (i == 0)
		{
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
			printf("○");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		}
			
		//打印蛇头
		else if (i == pSnake->nLength - 1)
		{
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
			printf("¤");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		}
			
		//打印蛇身
		else
		{
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED);
			printf("⊙");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		}
			
	}
}

自动裁判员——菜单信息

作为一个心系玩家的开发者。我们需要将玩家在玩游戏时所产生的一些信息包括等级、得分等打印到屏幕上。这样可以让玩家在游戏得分中得到鼓舞,不断挑战自己。

void PrintfMenu(HANDLE hOut, Snake* pSnake, Food* pFood)	
{
	//游戏等级
	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 1);
	printf("游戏等级:%d", pSnake->nCount / 5 + 1);			//每吃五个食物升一级

	//游戏得分
	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 3);
	printf("游戏得分:%d分", pSnake->nCount);				   //每吃一个食物得一分

	//食物个数
	
	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 5);
	printf("所吃食物总个数:%d个", pSnake->nCount);			 //玩家所吃的食物总数

	//游戏速度
	if (pSnake->nSpeed == 80)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:1");	
	}
	else if (pSnake->nSpeed == 70)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:2");
	}
	else if (pSnake->nSpeed == 60)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:3");
	}
	else if (pSnake->nSpeed == 50)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:4");
	}
	else if (pSnake->nSpeed == 40)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:5");
	}
	else if (pSnake->nSpeed == 30)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:6");
	}
	else
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
		printf("贪吃蛇的速度级别为:逆天级别");
	}


	//食物坐标
	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
	printf("食物的坐标:(%d, %d)", pFood->x, pFood->y);
	
	//温馨提示:
	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 12);
	SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
	printf("游戏规则:吃到食物,碰到自身则蛇死亡。");

	SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 14);
	printf("PS:这不是普通的墙哦,可以穿过去的(*^▽^*)");
	SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

}

食物从天而降——食物的随机生成

关于食物的生成,有一下几点需要注意的:

  1. 食物出现的位置不能超过我们界定的游戏窗口
  2. 食物不能产生在我们的蛇身上(不然我们就看不到食物了)

根据以上两点,可写出如下代码:

void MakeFood(HANDLE hOut, Snake* pSnake, Food* pFood)
{
	srand((unsigned)time(NULL));
	/*
	1.在游戏框架内
	2.不在蛇身上
	*/
	while(1) 
	{
		pFood->x = rand() % (FRAMEWIDTH - 1);		//0 - FRAMEWIDTH-1;
		pFood->y = rand() % FRAMEHEIGHT;			//0 - FRAMEHEIGHT-1;

		if (pFood->x == 0 || pFood->y == 0)			//食物不能再边界处
			continue;

		pFood->x = 2 * pFood->x + FRAMEX;
		pFood->y += FRAMEY;

		//判断食物是不是在蛇的身上
		int temp; 
		for (temp = 0; temp < pSnake->nLength; temp++)
		{
			if (pFood->x == pSnake->x[temp] && pFood->y == pSnake->y[temp])
				break;
		}

		if (temp == pSnake->nLength)
		{
			SetPosition(hOut, pFood->x, pFood->y);
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);
			printf("◎");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

			break;
		}
	}
}	

让蛇扭起来——移动蛇

看到这里,大家想想要如何让蛇移动起来呢?我们从前面的代码知道了整条蛇由众多的小蛇块组成,其中x[0]和y[0]是蛇尾的坐标。那么我们只要将让蛇尾去代替蛇尾前一块蛇的位置,以此不断推前,就能完成蛇的移动。在移动蛇的位置我们不考虑蛇头的位置,蛇头的运动由我们的玩家来控制,在StartGame()函数里面执行,现在可先不管。

C语言制作游戏——贪吃蛇_第2张图片

如图,假设这条蛇的长度为6,x[5]为蛇头,x[0]为蛇尾。那么下一个时刻,除去蛇头,其余部位的位置应该如上图。x[0]所在的位置应该没有蛇的部位,反而,x[0]的位置挪到了x[1]的位置,以此类推,完成蛇的移动。至于蛇头的位置去哪了呢。不急~且看后文。看懂这个移动的原理后我们就可以写出下面的代码:

void MoveSnake(HANDLE hOut, Snake* pSnake)
{
	SetPosition(hOut, pSnake->x[0], pSnake->y[0]);
	printf("  ");

	//后一个坐标代替前一个坐标
	for (int i = 1; i < pSnake->nLength; i++)
	{
		pSnake->x[i - 1] = pSnake->x[i];
		pSnake->y[i - 1] = pSnake->y[i];
	}

}

大开吃戒——吃食物

这部分没什么好讲的,大家看看代码就能理解了~

void EatFood(HANDLE hOut, Snake* pSnake, Food* pFood)
{
	if (pSnake->x[pSnake->nLength - 1] == pFood->x && pSnake->y[pSnake->nLength - 1] == pFood->y)
	{
		//如果蛇头位置与食物位置相同,吃食物
		pSnake->nLength++;
		for (int i = pSnake->nLength - 1; i > 0; i--)
		{
			pSnake->x[i] = pSnake->x[i - 1];
			pSnake->y[i] = pSnake->y[i - 1];
		}
		pSnake->x[0] = tail[0];		//得到蛇尾移动前的横坐标
		pSnake->y[0] = tail[1];		//得到蛇尾移动前的纵坐标  

		//重新产生食物
		MakeFood(hOut, pSnake, pFood);
		pSnake->nCount++;			//食物的数量加一

		//当蛇吃Up_level个食物时,速度加快Up_speed毫秒并且升一级
		if (pSnake->nCount % 6 == 0)
			pSnake->nSpeed -= 10;
	}
}

以上代码需要说明的有一点。
tail数组的含义
在主函数的前面,我们定义了一个全局变量tail[2]数组,来记录蛇尾移动前的位置

int tail[2];     //用于记住蛇尾坐标,其中tail[0]、tail[1]分别表示横、竖坐标

##瞬间移动——穿墙
大家看成品图应该看到了,那一句蓝色的提示语,是的,在本游戏中,这条蛇是可以穿墙的,这穿墙不是指穿过墙跑到界外。而是,比如蛇撞到右边的墙壁中,则会从左边的墙壁出现。

void ThroughWall(HANDLE hOut, Snake* pSnake, char ch)
{
	//如果蛇在上框且向上移动,穿墙
	if (ch == 72 && pSnake->y[pSnake->nLength - 1] == FRAMEY)
	{
		pSnake->y[pSnake->nLength - 1] = FRAMEY + FRAMEHEIGHT - 1;
	}

	//如果蛇在下框且向下移动,穿墙
	if (ch == 80 && pSnake->y[pSnake->nLength - 1] == FRAMEY + FRAMEHEIGHT)
	{
		pSnake->y[pSnake->nLength - 1] = FRAMEY + 1;
	}

	//如果蛇在右框且向右移动,穿墙
	if (ch == 75 && pSnake->x[pSnake->nLength - 1] == FRAMEX)
	{
		pSnake->x[pSnake->nLength - 1] = FRAMEX + 2 * FRAMEWIDTH - 4;
	}

	//如果蛇在左框且向左移动,穿墙
	if (ch == 77 && pSnake->x[pSnake->nLength - 1] == FRAMEX + 2 * FRAMEWIDTH - 2)
	{
		pSnake->x[pSnake->nLength - 1] = FRAMEX + 2;
	}
}

##生命凋零——蛇死亡
在本游戏中,蛇死亡的条件很简单,碰到自身就死了。

bool IfSnakeDie(Snake* pSnake)
{
	//当蛇头碰到自身时,蛇死,返回值为TRUE
	for (int i = 0; i < pSnake->nLength - 1; i++)
	{
		if (pSnake->x[pSnake->nLength - 1] == pSnake->x[i] && pSnake->y[pSnake->nLength - 1] == pSnake->y[i])
			return TRUE;
	}
	return FALSE;
}

##一切的开始——游戏开始

bool StartGame()
{
	unsigned char chOld = 77;
	unsigned char chNow = 77;
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	
	//定义蛇
	Snake snake;

	//定义食物
	Food food;
		
	//制作游戏窗口
	MakeFrame(hOut);
	
	//初始化蛇
	InitSnake(&snake);
	
	//产生食物
	MakeFood(hOut, &snake, &food);

	while (1)
	{
		//打印菜单信息
		PrintfMenu(hOut, &snake, &food);

		tail[0] = snake.x[0];       //记住蛇尾的横坐标
		tail[1] = snake.y[0];       //记住蛇尾的纵坐标

		bool speedUp = FALSE;
		//判断键盘是否按下
		if (kbhit())
		{
			chNow = getch();
			//Sleep(10);			
			if (kbhit())
				speedUp = TRUE;
		}

		switch (chNow)
		{
			case 72:	//向上
			{
							
							MoveSnake(hOut, &snake);
							if (chOld == 80)		//如果蛇调头,则无视调头继续走
							{
								snake.y[snake.nLength - 1] += 1;
								chNow = chOld;
								break;
							}
							chOld = chNow;
							snake.y[snake.nLength - 1] -= 1;
							break;
			}
			case 80:	//向下
			{
							MoveSnake(hOut, &snake);
							if (chOld == 72)
							{
								snake.y[snake.nLength - 1] -= 1;
								chNow = chOld;
								break;
							}
							chOld = chNow;
							snake.y[snake.nLength - 1] += 1;
							break;
			}
			case 75:	//向左
			{
							MoveSnake(hOut, &snake);
							if (chOld == 77)
							{
								snake.x[snake.nLength - 1] += 2;
								chNow = chOld;
								break;
							}
							chOld = chNow;
							snake.x[snake.nLength - 1] -= 2;
							break;
			}
			case 77:	//向右
			{
							MoveSnake(hOut, &snake);
							if (chOld == 75)
							{
								snake.x[snake.nLength - 1] -= 2;
								chNow = chOld;
								break;
							}
							chOld = chNow;
							snake.x[snake.nLength - 1] += 2;
							break;
			}
			case 27:		//ESC键				
							break;
			case 32:		//暂停
			{
							
							break;
			}
				
			default:		//去掉其他按键的影响
							chNow = chOld;
							break;
		}
		//判断是否要出暂停提示
		if (chNow != 32)	//清空暂停的提示
		{
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
			SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);
			printf("                          ");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		}
		else//显示暂停指示
		{
			SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);
			SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);
			printf("已暂停,按方向键可继续游戏");
			SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		}
		//穿墙
		ThroughWall(hOut, &snake, chNow);
		
		//判断有无吃食物
		EatFood(hOut, &snake, &food);
		
		//打印蛇
		PrintfSnake(hOut, &snake);
		
		/*
		游戏结束条件:
		1. 蛇碰到自身
		2. 按ESC键
		*/
		if (IfSnakeDie(&snake) == TRUE || chNow == 27)
		{
			SetPosition(hOut, FRAMEX + FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT / 2 - 1);
			bool State = OverGame(hOut, &snake);
			return State;
		}
		PrintfMenu(hOut, &snake, &food);
		if (speedUp == FALSE)
		{
			//SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
			//printf("          ");
			Sleep(snake.nSpeed);
		}			
		else
		{
			//SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
			//printf("正在加速...");
			Sleep(10);
		}
	}		
}

##一切的结束——结束游戏

bool OverGame(HANDLE hOut, Snake* pSnake)
{
	system("cls");
	SetPosition(hOut, FRAMEX + FRAMEWIDTH + 10, FRAMEY + FRAMEHEIGHT / 4);
	printf("Game Over");
	//打印得分
	if (pSnake->nCount < 20)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
		printf("好可惜哦!你的游戏得分为:%d分, 要不再来一盘突破自己?", pSnake->nCount);
	}
	else if (pSnake->nCount < 30)
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
		printf("不错哦!你的游戏得分为:%d分, 我猜你还没拿出实力吧", pSnake->nCount);
	}
	else
	{
		SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
		printf("哇好腻害(⊙o⊙)!你的游戏得分为:%d分, 你的手速已经超越地球人了", pSnake->nCount);
	}
	SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT/4 + 4);
	printf("重新开始游戏:Enter 	退出游戏:ESC");
	
	int userKey = 0;
	while (1)
	{
		if ((userKey = getch()) == 13)
			return FALSE;
		else if ((userKey = getch()) == 27)
			return TRUE;
		else
			continue;
	}
}							

##上帝控制台——主函数

int main()
{
	while(1) 
	{
		//开始游戏
		system("cls");
	    bool State= StartGame();
	    if (State)
	    	break;
	}
	return 0;
}

你可能感兴趣的:(c语言小游戏,c语言,游戏,贪吃蛇)