你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

C语言初学者项目 200行代码用一个二维数组实现贪吃蛇游戏

2021/11/17 0:36:11

C语言初学者项目:一个二维数组实现贪吃蛇游戏

本文章面向c语言初学者,所以我尽可能用最基本的语法实现整个游戏,并且只用到一个二维数组实现,下面将详细讲解实现过程。
一个二维数组可以直接打印成一个平面,就形成了整个游戏的背景,我们可以通过改变数组的内容来控制蛇的移动,食物的生成等。
先建立整个游戏框架
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 10//行数
#define COL 25//列数
#define SPEED 100//这里其实是刷新率,所以越低蛇速度越快,后边会引用到
int a[ROW][COL];
int main(void)
{
    char play;
	do
	{
		game();
		printf("1:continue\n");
		printf("2:exit\n");
		play = getch();
	} while (play=='1');
	printf("exit\n");
	return 0;
}
将二维数组建立为全局变量,使数组所有的数都初始化为0。之后可以用数组中不同的数字代表不同的事物,比如我们将数组的边框初始化为-2,-2就代表了墙
	for (int i = 0; i < ROW; i++)//生成墙壁
		for (int j = 0; j < COL; j++)
			if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1)
				a[i][j] = -2;

蛇的数字处理很重要,这涉及到蛇的移动问题,蛇的移动实际上可以理解为将尾巴那一块转移到将要移动的前方去,如果蛇要向上移动,就是把尾巴那一块转移到头的上边,同理向右移动即把尾巴移动到头的右边,理解了这个移动方式,就可以对蛇在数组中的值进行创建了,令头的值为1,之后身体块就是2,3、、、直到尾巴那个值,我们初始化蛇有三节,所以尾巴数为3(当然你也可以初始化其他的值)。

	a[ROW-2][1] = 3;//生成蛇
	a[ROW-2][2] = 2;//生成蛇
	a[ROW-2][3] = 1;//生成蛇头
	int tailmax = 3;//蛇长,之后蛇的变长要靠这个值

食物生成比较简单,取随机数在数组的范围内,如果是不恰当的位置就重新设置,设置食物的值为-1

void toeat(int a[ROW][COL])//生成食物
{
	while (1)
	{
		int x = rand() % ROW;
		int y = rand() % COL;
		if (x != 0 && y != 0/*不能在边界上*/ && a[x][y] == 0/*只能在空的区域生成*/)
		{
			a[x][y] = -1;
			break;
		}
	}
}

s
现在数组的基本处理完成,数组内一共有4种值 ,一种是-2,表示墙,一种是-1,表示食物,一种是0,表示空白区域,剩下的正数全都代表蛇,蛇头是1,那么就可以编写打印函数

void print(int a[ROW][COL])//打印
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if ((i == 0 || i == ROW - 1) && j != 0 && j != COL - 1)//上下边界
				printf("-");
			else if ((j == 0 || j == COL - 1) && i != 0 && i != ROW - 1)//左右边界
				printf("|");
			else if ((i == 0 || i == ROW - 1) && (j == 0 || j == COL - 1))//四个角
				printf("+");
			else if (a[i][j] == 0)//空白区域
				printf(" ");
			else if (a[i][j] == 1)//蛇头,我用字母‘O’表示
				printf("O");
			else if (a[i][j] == -1)//食物
				printf("$");
			else
				printf("*");//剩下的都是正数,是蛇身
		}
		printf("\n");
	}
}

前三个if选择式可能有点吓人,因为我想建立一个(稍微)好看的边界,如果你嫌麻烦,可以直接让边界都是一个字符,如下

void print(int a[ROW][COL])//打印
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
		    if(a[i][j]==-2)
		        printf("*");
			else if (a[i][j] == 0)
				printf(" ");
			else if (a[i][j] == 1)
				printf("O");
			else if (a[i][j] == -1)
				printf("$");
			else
				printf("*");
		}
		printf("\n");
	}
}

这些都处理好后,就可以进行蛇的移动操作了,我们习惯用wsad表示上下左右(不是)。首先思考,如何表示一只蛇向上移动一次呢?

从前文我们知道只要找到头尾坐标,将尾巴放到头上方就可以,但是蛇是一串连续的数字,在数组中必须要经过数据处理才能进行,得到头尾坐标后,我们可以先让尾巴消失,即坐标设置为0,然后把包括蛇头在内的蛇本体数字全部加1,这样原来蛇头的数字变成了2,现在没有蛇头,我们就可以把原来蛇头上面的数(如果头是a[i][j],头上面就是a[i-1][j])设置为1,新的蛇头就生成了,而且蛇新的尾巴的值没有改变,这样我们简单实现了蛇向上移动一格这个操作,转换为代码如下

	int handx, handy;//蛇头坐标
	int tailx, taily;//蛇尾坐标
			for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == 1)
				{
					handx = i;
					handy = j;
				}//找到头坐标
		for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == tailmax)
				{
					tailx = i;
					taily = j;
				}//找到尾坐标,这里就用到了之前的tailmax
		if (a[handx - 1][handy] == 0)//判断前方是空白可行走
		{
				a[tailx][taily] = 0;//蛇尾设为0
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)//排除墙壁、食物、空白
							a[i][j]++;//蛇的身体和头都加1
				a[handx - 1][handy] = 1;//设置新蛇头
		}

运用这个逻辑,我们可以很容易实现吃食物这个过程,如果前进方向的值是-1即食物,就将整个蛇连头带尾所有值加1,然后把食物的值设为1,形成新的蛇头

			else if (a[handx - 1][handy] == -1)//判断食物
			{
				for (int i = 0;i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
							a[i][j]++;
				a[handx - 1][handy] = 1;
				tailmax++;//蛇变长了,所以尾巴的值要加1
				toeat(a);.//生成一个新的食物

蛇的前方除了是空白和食物外就是墙和蛇身,如果继续前进就会游戏结束

			else 
			gameover = 1;//之后用到

我们已经可以实现一次移动,稍微修改二维数组的参数就可以实现四个方向的移动,但是蛇的移动是一个连续的过程,我们应该用循环实现连续移动,但单纯的循环是非常快的,所以蛇会飞快移动,为了避免这种情况,我们要用到windows.h里边的Sleep函数即Sleep(m),m的值为停顿的毫秒,我们将之前定义的SPEED调用上去,Sleep(SPEED)就能控制蛇移动的速度。
另外,因为蛇一直在移动,程序一直在运行,我们要控制蛇必须要输入wsad的指令,不可能蛇动一次就输入一次,太麻烦,所以我们要学习一个新函数_kbhit(),它判断你的键盘有没有输入,没有就返回0,有就返回一个非0值。把他放在每次循环移动的后边,再用getct不回显直接读取输入的值,就可以操作蛇。
一次完整的移动如下

while(1)
{
		if(move=='w')//以向上移动为例
		{
			if (a[handx - 1][handy] == 0)//判断可行走
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
							a[i][j]++;
				a[handx - 1][handy] = 1;
			}
			else if (a[handx - 1][handy] == -1)//判断食物
			{
				for (int i = 0;i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
							a[i][j]++;
				a[handx - 1][handy] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;//判断游戏失败
			break;
		system("cls");//清除之前的屏幕
		print(a);//打印新的局面,这样刷新基本实现的蛇的动态过程
		Sleep(SPEED);//停一下
		if (_kbhit())//判断是否有输入,没有直接跳过
			{
				char cao = getch();//用另一个变量来接受,避免接受到wsad以外的垃圾值
				if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
					move = cao;
			}
		}
}		

因为要判断四个移动方向,我们用在while循环内部用switch语句来判断move的值决定移动的方向,
同时设置一个值int gameover=1来判断游戏是否结束。具体代码如下

	while (!gameover)
	{
		for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == 1)
				{
					handx = i;
					handy = j;
				}//找到头坐标
		for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == tailmax)
				{
					tailx = i;
					taily = j;
				}//找到尾坐标
		switch (move)
		{
		case'w':
			if (a[handx - 1][handy] == 0)//判断可行走
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
							a[i][j]++;
				a[handx - 1][handy] = 1;
			}
			else if (a[handx - 1][handy] == -1)//判断食物
			{
				for (int i = 0;i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
							a[i][j]++;
				a[handx - 1][handy] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case's':
			if (a[handx + 1][handy] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx + 1][handy] = 1;
			}
			else if (a[handx + 1][handy] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx + 1][handy] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case'a':
			if (a[handx][handy-1] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy - 1] = 1;
			}
			else if (a[handx][handy-1] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy - 1] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case'd':
			if (a[handx][handy + 1] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy + 1] = 1;
			}
			else if (a[handx][handy + 1] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy + 1] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		}
		system("cls");
		print(a);
		Sleep(SPEED);
		if (_kbhit())//判断是否有输入
		{
			char cao = getch();
			if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
				move = cao;
		}
	}
	printf("YOU DIE!\n");
	printf("score:%d", tailmax);//正好用tailmax的值判断蛇长表示得分
}

完整代码如下

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 10
#define COL 25
#define SPEED 100
int a[ROW][COL];
void toeat(int a[ROW][COL])//生成食物
{
	while (1)
	{
		int x = rand() % ROW;
		int y = rand() % COL;
		if (x != 0 && y != 0 && a[x][y] == 0)
		{
			a[x][y] = -1;
			break;
		}
	}
}
void print(int a[ROW][COL])//打印
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if ((i == 0 || i == ROW - 1) && j != 0 && j != COL - 1)
				printf("-");
			else if ((j == 0 || j == COL - 1) && i != 0 && i != ROW - 1)
				printf("|");
			else if ((i == 0 || i == ROW - 1) && (j == 0 || j == COL - 1))
				printf("+");
			else if (a[i][j] == 0)
				printf(" ");
			else if (a[i][j] == 1)
				printf("O");
			else if (a[i][j] == -1)
				printf("$");
			else
				printf("*");
		}
		printf("\n");
	}
}
void game()
{
	system("cls");
	for (int i = 0; i < ROW; i++)//重置数组,不然重开游戏会出现前一条蛇的尸体,甚至撞上去会死,而且食物数量会加1,莫名有意思,想体验可以注释这三行
		for (int j = 0; j < COL; j++)
			a[i][j] = 0;
	srand((unsigned int)time(NULL));
	for (int i = 0; i < ROW; i++)//生成墙壁
		for (int j = 0; j < COL; j++)
			if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1)
				a[i][j] = -2;
	a[ROW-2][1] = 3;//生成蛇
	a[ROW-2][2] = 2;//生成蛇
	a[ROW-2][3] = 1;//生成蛇头
	print(a);
	toeat(a);
	char move='w';//默认为先向上移,你也可以改为其他方向
	int handx, handy;
	int tailx, taily;
	int tailmax = 3;//蛇长
	int gameover=0;//判断游戏是否结束
	printf("\nPress any key to cintinue");
	char flag = getch();//按任意键开始游戏
	while (!gameover)
	{
		for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == 1)
				{
					handx = i;
					handy = j;
				}//找到头坐标
		for(int i=0;i<ROW;i++)
			for(int j=0;j<COL;j++)
				if (a[i][j] == tailmax)
				{
					tailx = i;
					taily = j;
				}//找到尾坐标
		switch (move)
		{
		case'w':
			if (a[handx - 1][handy] == 0)//判断可行走
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
							a[i][j]++;
				a[handx - 1][handy] = 1;
			}
			else if (a[handx - 1][handy] == -1)//判断食物
			{
				for (int i = 0;i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
							a[i][j]++;
				a[handx - 1][handy] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case's':
			if (a[handx + 1][handy] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx + 1][handy] = 1;
			}
			else if (a[handx + 1][handy] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx + 1][handy] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case'a':
			if (a[handx][handy-1] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy - 1] = 1;
			}
			else if (a[handx][handy-1] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy - 1] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		case'd':
			if (a[handx][handy + 1] == 0)
			{
				a[tailx][taily] = 0;
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy + 1] = 1;
			}
			else if (a[handx][handy + 1] == -1)
			{
				for (int i = 0; i < ROW; i++)
					for (int j = 0; j < COL; j++)
						if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
							a[i][j]++;
				a[handx][handy + 1] = 1;
				tailmax++;
				toeat(a);
			}
			else gameover = 1;
			break;
		}
		system("cls");
		print(a);
		Sleep(SPEED);
		if (_kbhit())//判断是否有输入
		{
			char cao = getch();
			if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
				move = cao;
		}
	}
	printf("YOU DIE!\n");
	printf("score:%d", tailmax);
}
int main(void)
{
    char play;
	do
	{
		game();
		printf("\n1:continue");
		printf("\n2:exit");
		play = getch();
	} while (play=='1');
	printf("\nexit");
	return 0;
}

最后存在的问题:因为是用这种刷新方式所以眼睛可能会不适,另外如果蛇往反方向走游戏会立即结束(应该没人会这样做吧)。