Linux系统用C语言实现贪吃蛇游戏

C语言实现贪吃蛇游戏

游戏简介

本游戏以Linux系统为平台,C语言编程为基础,通过GCC编译器实现游戏的正常运行。

游戏主要功能如下:

  1. 蛇的移动和食物的产生,分数增加,蛇身变长。
  2. 分数越高,移动速度越快,最高分数为50分。
  3. 游戏能够暂停,能够保存游戏、读取存档。有存档时,将直接读取存档继续游戏。
  4. 游戏过程中蛇头撞墙和撞身体时都会结束游戏。

游戏使用到的C语言相关知识

  1. 循环语句:if、for、while、swtich等语句;
  2. 自定义函数:本游戏使用了多个自定义函数,如游戏区域、蛇的移动、保存游戏数据、读取存档等;
  3. 宏定义和全局变量;
  4. 指针;
  5. 结构体:蛇的数据、食物的数据;
  6. 链表:用链表将蛇的身体数据串接;
  7. 保存文件、读取文件;

游戏原理

移动原理:(链表的运用)


整条蛇身属于一个链表,蛇尾为链表头,蛇头为链表尾,每一个蛇身都是链表的一个节点。移动时,从链表头开始,将后一个节点的内容赋给前一个节点,重复这个过程即可完成蛇身的移动。

身体变长原理(更新链表头)

吃食物前保存node1的坐标,蛇身继续向前运动。

吃到食物后,将保存的node1的坐标赋值给new节点

new节点成为新的链表头,蛇身继续向前运动。

保存和读取游戏进度

保存游戏时,将文件标识符(可无)、版本号(可无)、蛇身长度、运动方向、蛇身体数据即链表信息、食物信息、移动速度、分数等重要信息保存到特定文件里。
读取数据时,按照保存时的顺序,依次将信息读取到游戏中去。注:一定要按照保存数据时的顺序读取。

游戏实现

确定蛇身和食物需要的信息,放入结构体中。

typedef enum {UP = -1,DOWN = 1,LEFT = 2,RIGHT = 4} DIR;	//前进方向共四种
typedef struct s_snake_note	//此结构体保存蛇身坐标(x,y),前进方向dir;
{
	int x;
	int y;
	DIR dir;
}Snake_data;

typedef struct link_snake	//此结构体便于形成蛇身数据链表
{
	Snake_data data;
	struct link_snake *next;
}ls,*psnake;

typedef struct s_food		//此结构体保存食物坐标(x,y)
{
	int x;
	int y;
}Food,*pfood;

游戏区域的确定

#define MOVETO(x,y) printf("\033[%d;%dH",(y),(x))	//将光标移动到某一位置
void display()		//游戏区域
{
	int i;
	MOVETO(10,5);
	printf("╔");
	MOVETO(11,5);
	for (i = 0; i < AREA_WIDHT-2; i++)
		printf("═");
	MOVETO(10+AREA_WIDHT-1,5);
	printf("╗");
	for (i = 1; i <= AREA_HIGHT-2; i++)
	{
		MOVETO(10,5+i);
		printf("║");
		MOVETO(10+AREA_WIDHT-1,5+i);
		printf("║");
	}
	MOVETO(10,AREA_HIGHT+4);
	printf("╚");
	MOVETO(11,AREA_HIGHT+4);
	for (i = 0; i < AREA_WIDHT-2; i++)
		printf("═");
	MOVETO(10+AREA_WIDHT-1,AREA_HIGHT+4);
	printf("╝");
	MOVETO(1,25);
}
初始化蛇身和食物

游戏开始时蛇身长度为3,即一个蛇头两个蛇身,均使用结构体s_snake_note保存数据,使用结构体link_snake形成链表

void init_snake()	//初始化蛇身
{
	int i;
	psnake p;
	p = (psnake)malloc(sizeof(Snake_data));
	if (p == NULL)
	{
		printf("malloc fail (init_snake-if.1)\n");
		return;
	}
	p->data.x = 25;		//先确定链表头link_head坐标
	p->data.y = 13;
	p->data.dir = cur_dir;
	link_head = p;
	tail = p;
	for (i = 1; i <= snake_length - 1; i++)
	{
		p = (psnake)malloc(sizeof(Snake_data));
		if (p == NULL) 
		{
			printf("malloc fail (init_snake-if.2)\n");
			return;
		}
		p->data.x = 25 + i;
		p->data.y = 13;
		tail->next = p;
		tail = p;
	}
	tail->next = NULL;		//链表尾部,"tail" 即为蛇头
	snake_head = tail;
}

食物的产生采用系统随机数,在游戏区域内随机生成食物坐标。

void init_food()	//初始化食物
{
	srand(time(NULL));
	food = (pfood)malloc(sizeof(pfood));
	food->x = 11 + rand()%(AREA_WIDHT - 2);
	food->y = 6 + rand()%(AREA_HIGHT - 2);
}

使用特定的符号代替蛇头、蛇身、食物。可以从符号大全选择喜欢的符号。

void draw_snake_food()	//画蛇,食物
{
	psnake ps = link_head;
	MOVETO(snake_head->data.x,snake_head->data.y);		//用◆代替蛇头
	printf("◆");
	printf("\n");
	while (ps->next != NULL)			//用≈代表蛇身
	{
		MOVETO(ps->data.x,ps->data.y);
		printf("≈");
		printf("\n");
		ps = ps->next;
	}
	MOVETO(food->x,food->y);
	printf("✹");
	MOVETO(1,25);
	printf("\n");
}
snake_move函数:包括蛇身移动、方向控制、撞墙撞身体、吃食物、判断新产生的食物是否与身体重合

蛇身移动:通过将后一个节点的数据赋值给前一个节点来完成蛇身移动。

p = link_head;		//link_head为由蛇身组成链表的链表头
while (p != NULL && p->next != NULL)		//蛇身移动
{
	p->data = p->next->data;
	p = p->next;
}

方向控制:方向有UP=-1、DOWN=1、LEFT=2、RIGHT=4四种

if (cur_dir == RIGHT || cur_dir == LEFT)	//方向控制、p此时为链表尾,即蛇头
		p->data.x += cur_dir -3;			//左右变向改变x的值(+1、-1)
	else
		p->data.y += cur_dir;				//上下变向改变y的值(+1、-1)

撞墙和撞身体。撞墙:通过限定x和y的范围实现。撞身体:通过检测蛇头的坐标是否与所有的身体坐标重合。

if (p->data.x == 10 || p->data.x == 49 || p->data.y == 5 || p->data.y == 24)	//判断是否撞墙
{
	MOVETO(23,13);
	printf("\033[0;31;24m");		//改变打印信息颜色为红色,可省略
	printf("You hit the wall!\n");
	printf("\033[0m");				//还原打印信息颜色
	MOVETO(1,25);
	return 0;
}

撞身体:遍历整个链表(链尾除外)与链尾的坐标是否相等。

while (b != NULL && b->next != NULL)		//b = link_head, 判断是否撞身体
{
	if (p->data.x == b->data.x && p->data.y == b->data.y)
	{
		MOVETO(23,13);
		printf("\033[0;31;24m");	//改变打印信息颜色为红色,可省略
		printf("You hit yourself!\n");
		printf("\033[0m");			//还原打印信息颜色
		MOVETO(1,25);
		return 0;
	}
	b = b->next;
}

吃食物:判断蛇头(即链尾)与食物坐标是否重合。吃到食物后,判断新产生的食物是否与身体重合。

if (p->data.x == food->x && p->data.y == food->y)		//吃到食物
{
	score++;
	snake_length++;
	while(flag == 0)			//判断产生的食物是否与身体重合
	{
		food->x = 11 + rand()%(AREA_WIDHT - 2);
		food->y = 6 + rand()%(AREA_HIGHT - 2);
		while (f != NULL)		//f = link_head
		{
			if (food->x == f->data.x && food->y == f->data.y)
			{
				flag = 0;
				break;
			}
			f = f->next;
		}
		flag = 1;
	}
}

蛇身长度加1:保留蛇吃到食物前的蛇尾(即链表头)的坐标,当吃到食物后,将保留的坐标添加到吃到食物后的链表头部,从而实现身体变长。

old_link_head = (psnake)malloc(sizeof(psnake));	//为结构体变量申请动态存储空间
if (NULL == old_link_head)
{
	printf("malloc fail at snake_move(if-1)\n");
	return 1;
}
old_link_head->data.x = x;	//将吃食物前保留的坐标赋值给即将成为新的链表头的变量
old_link_head->data.y = y;
old_link_head->next = link_head;	//进行链表头部的更新
link_head = old_link_head;				//蛇身加 1 
RestoreConsole()和PrepareConsole()两个函数用于从键盘上即时读取按键信息,且不需要按回车,方便对运动方向的控制。在main函数主循环前调用PrepareConsole()函数,在退出时调用RestoreConsole()函数。
void PrepareConsole(void)
{
	tcgetattr(STDIN_FILENO, &oldTermios);
	struct termios newTermios = oldTermios;
	newTermios.c_lflag &= ~(ECHO | ICANON);
	oldFcntl = fcntl(STDIN_FILENO, F_GETFL);
	int newFcntl = oldFcntl | O_NONBLOCK;
	if (tcsetattr(STDIN_FILENO, TCSANOW, &newTermios) == -1 || fcntl(STDIN_FILENO, F_SETFL, newFcntl) == -1) 
	{
		RestoreConsole();
		exit(-1);
	}
}
void RestoreConsole(void)
{
	tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios);
	fcntl(STDIN_FILENO, F_SETFL, oldFcntl);
}
数据的保存和读取是这个游戏编程过程中的难点之一,由于读取数据时需要对数据申请空间进行存放,所以非常容易出现断错误。尽量做到按顺序保存读取数据,需要申请空间的不要吝啬,申请到的动态存储空间做到物尽其用,避免断错误的产生。

保存游戏数据

void save_game()
{
	FILE *fp;
	if ((fp = fopen("information.dat","w")) == NULL)
	{
		printf("open file fail,at read_information-if-1.\n");
		return;
	}
	fwrite("SNAKE",5,1,fp);
	fwrite(&ver,4,1,fp);
	fwrite(&snake_length,4,1,fp);
	fwrite (&cur_dir,4,1,fp);
	psnake p;
	p = link_head;
	while (p != NULL)
	{
		fwrite (&p->data,sizeof(Snake_data),1,fp);
		p = p->next;
	}
	fwrite (food,sizeof(pfood),1,fp);
	fwrite(&score,4,1,fp);
	fwrite(&speed,4,1,fp);
	fclose(fp);
	fp=NULL;
}

读取游戏数据

void read_game()
{
	int n,t = 1;
	signed int read_dir;
	char buf[6];
	FILE *fp;
	if ((fp = fopen("information.dat","r")) == NULL)
	{
		init_food();
		init_snake();
		printf("open file fail,at read_information-if-1.\n");
		return;
	}
	if(fp)	fclose(fp);
	fp = fopen("information.dat","r");
	fread(buf,5,1,fp);
	if (strncmp(buf,"SNAKE",5) != 0)
	{
		init_food();
		init_snake();
	}
	else
	{
		fread (&ver,4,1,fp);
		if (ver == 1)
		{
			int i;
			fread (&snake_length,4,1,fp);
			fread (&read_dir,4,1,fp);
			psnake p,ptail;
			for (i = 0; i < snake_length; i++)
			{
				p = (psnake)malloc(sizeof(psnake));
				if (p ==NULL)
				{
					printf("malloc fail,at read_information\n");
					return;
				}
				fread(&p->data,sizeof(Snake_data),1,fp);
				if (t == 1)
				{
					link_head = p;
					ptail = p;
					t = 0;
				}
				else
				{
					ptail->next = p;
					ptail = p;
				}
			}
			ptail->next = NULL;
			snake_head = ptail;
			cur_dir = read_dir;
			food = (pfood)malloc(sizeof(pfood));
			fread(food,sizeof(pfood),1,fp);
			fread (&score,4,1,fp);
			fread (&speed,4,1,fp);			
		}
		else if (n == 1)
		{
			init_food();
			init_snake();
		}
	}
	fclose(fp);
	fp = NULL;
}

暂停游戏。游戏过程中可暂停、可继续,暂停时也可保存并退出游戏。

int stop_game()		//游戏暂停(可保存退出)
{
	char ch;
	MOVETO(11,3);
	printf("Game stoping,Press space to continu");
	while (1)
	{
		if ((ch = getchar()) == 'q')
		{
			MOVETO(1,25);
			save_game();
			RestoreConsole();
			return 0;
		}
		else if ((ch = getchar()) == ' ')
			return 1;
	}
}

主函数实现

int main()
{
	int t;
	read_game();
	PrepareConsole();
	while(1)
	{
		t = Speed(speed);
		int c = EOF, ch = EOF;
		while ((c = getchar()) != EOF)
		{
			ch = c;
		}
		switch(ch)
		{
			case 'w':	cur_dir = UP;	 break;
			case 's':	cur_dir = DOWN;	 break;
			case 'a':	cur_dir = LEFT;	 break;
			case 'd':	cur_dir = RIGHT; break;
			case 'q':	save_game();	 RestoreConsole();	return 0;
			case ' ':	if (stop_game())
							break;
						else
							return 0;
		}
		display();
		system("clear");
		MOVETO(42,4);
		printf("speed: %d",8-t);
		MOVETO(10,4);
		printf("score: %d",score);
		if (snake_move() == 0)
			break;
		draw_snake_food();
		usleep(100000*t);
	}
	RestoreConsole();
	return 0;
}

最后附上游戏文件:http://gitee.com/zk151515/snake.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值