c语言实现贪吃蛇教程

效果图如图 

首先发现组成元素是“实心方块”我们可以百度 也可以在我这里直接复制 ▇ 进编译环境 (这个方块是两个字节这个很重要)

完成这个小程序基本上我们分以下几步

1.完成所有静态的元素(四周的方块界线)

2.绘制蛇

3.使蛇吃东西

 

下面分布进行实现

首先完成第一步

/*****************************************************************头文件**********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
/*****************************************************************函数声明*********************************************************/
void muban();
void Pos(int x, int y);
/*****************************************************************自定义函数*******************************************************/
void Pos(int x, int y)//设置光标位置,从哪里开始输出
{
    COORD pos;//表示一个字符在控制台屏幕上的坐标,左上角(0,0)
    HANDLE hOutput;
    pos.X = x;
    pos.Y = y;
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//返回标准的输入、输出或错误的设备的句柄,也就是获得输入、输出/错误的屏幕缓冲区的句柄
    SetConsoleCursorPosition(hOutput, pos);
}

void muban()
{	
	int i;
	for(i=0;i<=60;i+=2)//方块水平方向占两个字节
	{
       Pos(i,0);
	   printf("▇");//上行
	   Pos(i,26);
	   printf("▇");//下行
	}
	for(i=0;i<=25;i+=1)//方块竖直方向占1个字节
	{
       Pos(0,i);//左列
	   printf("▇");
	   Pos(60,i);//右列
	   printf("▇");
	}
}
/*******************************************************************主函数************************************************************/
int main()
{
    muban();
	return 0;
}

下面我们来描绘蛇,这里要用到结构体指针和链表

主要思想是 一个▇是蛇的一段 然后用链表将它们链接起来

具体的链表和结构体指针可以看下面的文章:(只用掌握最基本的用法即可)

结构体https://blog.csdn.net/zhanghow/article/details/53463825

链表https://blog.csdn.net/govshell/article/details/63274614

指针https://blog.csdn.net/cyh183269855/article/details/52278941(这篇文章讲的很棒,配合结构体食用)

接下来继续撸代码

void initSnake()
{
  snake *tail;//尾指针
  snake *head;//头指针
  tail = (snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用tail指向这个结构体
  tail ->x=30;//因为实心方块宽度为2个单位长度,所以必须为偶数
  tail ->y=10;
  tail ->next=NULL;
  for(i = 0;i<4;i++)
  {
    head=(snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用head指向这个结构体
	head->next=tail;//将
	tail=head;//将尾指针传向下一个头指针
  }
}

比较难理解的是这段尾插法,如果不会的可以去看这篇https://blog.csdn.net/viafcccy/article/details/84502334

这里下面的代码

typedef struct Snake//相当于蛇一个节点
{
	int x;//横坐标
	int y;//纵坐标
    struct Snake *next;
}snake;

等价于

struct Snake
{
  int x;//横坐标
  int y;//纵坐标
  struct Snake *next;
};
struct Snake snake;

关于typedef看这篇https://blog.csdn.net/viafcccy/article/details/84204456

下面完成蛇身的打印

其中第一遍的代码是这样

void initSnake()
{
  int i;
  snake *tail;//尾指针
  snake *head;//头指针
  tail = (snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用tail指向这个结构体
  tail ->x=30;//因为实心方块宽度为2个单位长度,所以必须为偶数
  tail ->y=10;
  tail ->next=NULL;
  for(i = 0;i<4;i++)
  {
    head=(snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用head指向这个结构体
	head->next=tail;//链接成链
	head->x=30+2*i;//下一节点的位置
	head->y=10;
	tail=head;//将尾指针传向下一个头指针
  }

 

结果是

我们错在原来蛇是从右向左打印了8个单位 但是应为固定输出press any keyt continues将三个实心方块挡住了,于是将代码修改

/*****************************************************************头文件**********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
/*****************************************************************函数声明*********************************************************/
void muban();//打印四周的边界
void Pos(int x, int y);//设置光标输出位置
void initSnake();//初始化蛇身,将结构体中的坐标读取打印实心方块
/*****************************************************************结构体***********************************************************/
typedef struct Snake//将蛇身的位置存入结构体,相当于蛇身上的一个实心方块
{
  int x;//横坐标
  int y;//纵坐标
  struct Snake *next;
}snake;
/*****************************************************************自定义函数*******************************************************/
void Pos(int x, int y)//设置光标位置,从哪里开始输出
{
    COORD pos;//表示一个字符在控制台屏幕上的坐标,左上角(0,0)
    HANDLE hOutput;
    pos.X = x;
    pos.Y = y;
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//返回标准的输入、输出或错误的设备的句柄,也就是获得输入、输出/错误的屏幕缓冲区的句柄
    SetConsoleCursorPosition(hOutput, pos);
}

void muban()
{	
	int i;
	for(i=0;i<=60;i+=2)//方块水平方向占两个单位
	{
       Pos(i,0);
	   printf("▇");//上行
	   Pos(i,26);
	   printf("▇");//下行
	}
	for(i=0;i<=25;i+=1)//方块垂直方向占1个单位
	{
       Pos(0,i);//左列
	   printf("▇");
	   Pos(60,i);//右列
	   printf("▇");
	}
}

void initSnake()
{
  int i;
  snake *tail;//尾指针
  snake *head;//头指针
  tail = (snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用tail指向这个结构体
  tail ->x=30;//因为实心方块宽度为2个单位长度,所以必须为偶数
  tail ->y=10;
  tail ->next=NULL;
  for(i = 1;i<=4;i++)
  {
    head=(snake*)malloc(sizeof(snake));//以snake结构体的形式开辟一块新的内存,内存中的数据是新的,用head指向这个结构体
	head->next=tail;//链接成链
	head->x=30-2*i;//下一节点的位置
	head->y=10;
	tail=head;//将尾指针传向下一个头指针
  }
  //遍历打印出来
  while(tail->next!=NULL)
  {
  Pos(tail->x,tail->y);
  printf("▇");
  tail = tail->next;
  }
}

/*******************************************************************主函数************************************************************/
int main()
{
    muban();
	initSnake();
	return 0;
}

这样我用四个永远紧紧相连的方块构成了蛇的身体,下面我们来生成食物

思路是随机生成两个随机数使x,y分别满足在边界内,但是x有一个要求就是要是偶数这个和实心方块两个字节有关,我们要避免下图的情况产生

void creatFood()//创建食物
{
	snake *food;//创造一个食物
	food=(snake*)malloc(sizeof(snake));
	srand((unsigned int)time(NULL));//随着时间变化,产生不一样种子,就会得到没规律的食物
	while(food->x%2!=0)
	{
		food->x=rand()%56+2;
	}
	food->y=rand()%23+1;

	//上面虽然解决了食物不会出现在城墙里,没有考虑食物出现在蛇本身里面
	p=head;//用p来遍历
    while(p!=NULL)//解决食物出现在蛇本身
	{
	        if(food->x==p->x&&food->y==p->y)
			{
				free(food);
				creatFood();
			}
			p=p->next;
	}
	Pos(food->x,food->y);
	food1=food;//food1用来标记的作用
	printf("▇");
}

关于产生随机数可以看这篇文章https://blog.csdn.net/viafcccy/article/details/84311336

于是我们下一步就是要做到我们按键蛇会动我们使用头文件windows.h,具体看这篇https://blog.csdn.net/viafcccy/article/details/84262393

这里介绍一下遍历 就是将所有链表中的数据访问一遍 通常是打印出来

蛇的移动主要通过获取键值,然后如下操作

但是我们要将1的位置打印空格 否则实心方块不会消失 但是这样打印时会有光标始终跟着蛇尾 因此需要在外面打印一个东西让光标绝大部分时间在外面 只有一个瞬间在蛇尾我们就不会看到了 

 void snakeMove()
 {
  snake *nexthead;
  nexthead=(snake*)malloc(sizeof(snake));
  if(status=='R')
  {
      nexthead->x=head->x+2;
	  nexthead->y=head->y;
	  if(nexthead->x==food1->x&&nexthead->y==food->y)
	  {
	      nexthead->next=head;
          head=nexthead;
		  p=head;//遍历
          while(p!=NULL)
		  {
		      Pos(p->x,p->y);
			  printf("▇");
			  p=p->next;
		  }
	  }
	  else
	  {
	      nexthead->next=head;
          head=nexthead;
		  p=head;//遍历
		  while(p->next->next!=NULL)
		  {
		      Pos(p->x,p->y);
			  printf("▇");
			  p=p->next;
		  }
		  Pos(p->next->x,p->next->y);
		  printf(" ");//会带来一个光标闪烁
		  Pos(70,20);
		  printf("您的分数是:%d",score);
		  free(p->next);
		  p->next=NULL;
	  }
	  		if(status=='L')//向左走
	{
		nexthead->x=head->x-2;
		nexthead->y=head->y;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
		if(status=='U')//向上走
	{
		nexthead->x=head->x;
		nexthead->y=head->y-1;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
		if(status=='D')//向下走
	{
		nexthead->x=head->x;
		nexthead->y=head->y+1;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
	
	Sleep(sleepTime);//蛇移动的速度,里面是毫秒,越大速度越慢
	status=reDirection();//判别下方向先
	if(crossWall()==1||eatSelf()==1)
		//exit(0);//直接把程序关闭了
       endleap=1;
	return endleap;
	
}

int crossWall()//判断蛇有没穿透墙
{
	if(head->x==0||head->y==0||head->x==60||head->y==25)
		leap=1;
	return leap;
}
int eatSelf()//判断是否咬到了自己
{
	snake *q;//遍历的
	q=head->next;
	while(q!=NULL)
	{
		if(q->x==head->x&&head->y==q->y)
			leap=1;
		q=q->next;
	}
	return leap;
}

下面我们要解决gameover的判断

贪吃蛇结束就两种情况

1)撞到墙壁

2)咬到自己

int crossWall()//判断蛇有没撞墙
{
	if(head->x==0||head->y==0||head->x==60||head->y==25)
		leap=1;
	return leap;
}
int eatSelf()//判断是否咬到了自己
{
	snake *q;//遍历的
	q=head->next;
	while(q!=NULL)
	{
		if(q->x==head->x&&head->y==q->y)
			leap=1;
		q=q->next;
	}
	return leap;
}

最后经过优化的源代码在这里

/*********************************************头文件************************************/
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
/*********************************************函数声明**********************************/
void Pos(int x, int y);//光标位置设定
void muban();//打印模板
void initSnake();//蛇身的初始化
void creatFood();//创建食物
char reDirection();//识别方向
int snakeMove();//蛇移动
int crossWall();//不能穿墙
int eatSelf();//不能吃自己
/**********************************************结构体***********************************/
typedef struct Snake//相当于蛇一个节点
{
	int x;//横坐标
	int y;//纵坐标
    struct Snake *next;
}snake;
/***********************************************全局变量********************************/
snake *head;//头指针
snake *p;//用来遍历
snake *food1;//用来标记的
char status='L';//初始方向的状态,解决开始会动的问题
int score=0;//分数
int add=10;//一个食物的分
int leap=0;//用来标志是否结束,0没有,1代表蛇死了代表结束了
int endleap=0;//结束标志 1就是结束
int sleepTime=500;
/***********************************************自定义函数******************************/
void initSnake()//蛇身初始化,给定一个长度,用结构体表示是蛇的骨架,真正要显示出来是打印▇
{
	int i;
	snake *tail;//尾指针
	tail=(snake*)malloc(sizeof(snake));//第一个节点/头结点
	tail->x=30;//2的倍数,因为方块的长是两个单位
	tail->y=10;//1个单位
	tail->next=NULL;
	for(i=1;i<=4;i++)//尾插法
	{
		head=(snake*)malloc(sizeof(snake));//申请一个节点
		head->next=tail;//连接成链
		head->x=30-2*i;//下一个节点的位置
		head->y=10;
		tail=head;
	} 
	//遍历打印出来
	while(tail!=NULL)
	{
		Pos(tail->x,tail->y);
		printf("▇");
		tail=tail->next;
	}


}

char reDirection()//识别用户按下的键值  保留方向值
{
	if(GetAsyncKeyState(VK_F7))//热键 
	{
		if(sleepTime>300)//最多减到300
		{
			sleepTime-=50;//每次减50
			add++;//每次食物加1分
		}
	}
	if(GetAsyncKeyState(VK_F8))
	{
		if(sleepTime<800)//最多加到800
		{
			sleepTime+=50;//每次加50
			add--;//每次食物减1分
		}
	}
	if(GetAsyncKeyState(VK_UP)&&status!='D')
		status='U';
	if(GetAsyncKeyState(VK_DOWN)&&status!='U')
		status='D';
	if(GetAsyncKeyState(VK_LEFT)&&status!='R')
		status='L';
	if(GetAsyncKeyState(VK_RIGHT)&&status!='L')
		status='R';
		return status;
}
void Pos(int x, int y)//设置光标位置,从哪里开始输出
{
    COORD pos;//表示一个字符在控制台屏幕上的坐标,左上角(0,0)
    HANDLE hOutput;
    pos.X = x;
    pos.Y = y;
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//返回标准的输入、输出或错误的设备的句柄,也就是获得输入、输出/错误的屏幕缓冲区的句柄
    SetConsoleCursorPosition(hOutput, pos);
}


void creatFood()//创建食物
{
	snake *food;//创造一个食物
	food=(snake*)malloc(sizeof(snake));
	srand((unsigned int)time(NULL));//随着时间变化,产生不一样种子,就会得到没规律的食物
	while(food->x%2!=0)
	{
		food->x=rand()%56+2;
	}
	food->y=rand()%23+1;

	//上面虽然解决了食物不会出现在城墙里,没有考虑食物出现在蛇本身里面
	p=head;//用p来遍历
    while(p!=NULL)//解决食物出现在蛇本身
	{
	        if(food->x==p->x&&food->y==p->y)
			{
				free(food);
				creatFood();
			}
			p=p->next;
	}
	Pos(food->x,food->y);
	food1=food;//food1用来标记的作用
	printf("▇");
	Pos(70,20);//解决有光标闪烁的办法
	printf("您的分数是:%d",score);
}
void muban()
{	
	int i;
	for(i=0;i<=60;i+=2)//方块水平方向占两个单位
	{
       Pos(i,0);
	   printf("▇");//上行
	   Pos(i,26);
	   printf("▇");//下行
	}
	for(i=0;i<=25;i+=1)//方块垂直方向占1个单位
	{
       Pos(0,i);//左列
	   printf("▇");
	   Pos(60,i);//右列
	   printf("▇");
	}
}

int snakeMove()
{
	snake *nexthead;
	nexthead=(snake*)malloc(sizeof(snake));
	if(status=='R')//向右走
	{
		nexthead->x=head->x+2;
		nexthead->y=head->y;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");//会带来一个光标闪烁
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
	}
		if(status=='L')//向左走
	{
		nexthead->x=head->x-2;
		nexthead->y=head->y;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
		if(status=='U')//向上走
	{
		nexthead->x=head->x;
		nexthead->y=head->y-1;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
		if(status=='D')//向下走
	{
		nexthead->x=head->x;
		nexthead->y=head->y+1;
		if(nexthead->x==food1->x&&nexthead->y==food1->y)//吃掉了食物
		{
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}//吃掉了食物得创造
			score=score+add;
			creatFood();

		}
		else//没有食物
        {
			nexthead->next=head;
			head=nexthead;
            p=head;//p用来从头遍历,打印方块
			while(p->next->next!=NULL)
			{
				Pos(p->x,p->y);
				printf("▇");
				p=p->next;
			}
			Pos(p->next->x,p->next->y);
			printf("  ");
			Pos(70,20);//解决办法
			printf("您的分数是:%d",score);
			free(p->next);
			p->next=NULL;
		}
		}
	
	Sleep(sleepTime);//蛇移动的速度,里面是毫秒,越大速度越慢
	status=reDirection();//判别下方向先
	if(crossWall()==1||eatSelf()==1)
		//exit(0);//直接把程序关闭了
       endleap=1;
	return endleap;
	
}

int crossWall()//判断蛇有没穿透墙
{
	if(head->x==0||head->y==0||head->x==60||head->y==25)
		leap=1;
	return leap;
}
int eatSelf()//判断是否咬到了自己
{
	snake *q;//遍历的
	q=head->next;
	while(q!=NULL)
	{
		if(q->x==head->x&&head->y==q->y)
			leap=1;
		q=q->next;
	}
	return leap;
}
//打印食物的时候会出现光标,解决办法就是引开它
/*********************************************主函数***********************************/
int main()
{
	muban();//打印模板
	initSnake();//初始化蛇
	creatFood();//创建食物
	while(1)//死循环,让蛇一直动起来,直到蛇死了
	{
		if(snakeMove()==1)//判断是否结束
		{
			Pos(70,23);
			printf("蛇死了");
            system("pause");//用来暂停
			Pos(70,24);//解决press any key  to continue   在该地点打印  大家试下
			break;
		}
		
	}
	printf("是否继续游戏,y or n:");//y 继续
	if(getch()=='y')//重新游戏
	{
		//蛇一开始就死了,因为全局变量没有恢复原值,仍然保留上一局的值
		status='L';//初始方向的状态,解决开始会动的问题
		score=0;//分数
		add=10;//一个食物的分
		leap=0;//用来标志是否结束,0没有,1代表蛇死了代表结束了
		endleap=0;//结束标志 1就是结束
		sleepTime=500;
		system("cls");//清理屏幕
		main();//自己调用自己 看不一样的编译器,vc6.0允许调用自己
	}
	if(getch()=='n')
	{
		Pos(70,25);//定一个位置,再打印press 
		exit(0);//退出程序
	}
	
	return 0;
}

//蛇的速度变化,每个食物的分数增加
//是否继续游戏
//按键的作用

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值