【C语言】做一个贪吃蛇小游戏,完整代码&附带视频演示

基于Ncueses库的贪吃蛇小游戏

1、项目简介

  • 视频演示:
    https://www.bilibili.com/video/BV1pt421a7Nu/?spm_id_from=333.999.0.0&vd_source=82b65865be0947de29bd55efc8cdb40a

  • 编译环境:linux(Vmware16.2.4 + Ubantu20.04.3);

  • 小蛇🐍只能在固定的范围内活动;

  • 可以利用键盘方向键控制小蛇🐍的前进方向;

  • 活动范围内会随机生成食物;

  • 小蛇🐍吃到食物,身体将会变长;

  • 小蛇🐍咬到自己或者碰到墙壁将会死亡,重新开始游戏;

完整代码文末奉上,欢迎大家一起交流谈论!

2、项目演示&整体框架

  • 请添加图片描述

  • 框架
    请添加图片描述

3、项目介绍

为了方便理解,我将从main() 函数程序的顺序开始讲起,慢慢将各各函数扩展开;

  • main()函数主要分为4块
    • initNcuse() //初始化ncuses,页面以及按键;
    • initSnake()//小蛇🐍的初始化和食物🍔的初始化
    • gamePic()//初始化地形、遍历各各点、打印边界、小蛇🐍 和食物🍔
    • pthread两个进程
      • refreshJieMian()//不断刷新页面
      • changeDir() //不断的检测按键
    • else

3.1、头文件&宏定义&全局变量

  • 本项目所需的头文件、宏定义和全局变量程序如下
    //头文件&宏定义&全局变量
    #include<curses.h>   //图形库
    #include<stdlib.h>
    #include<unistd.h>   //sheep(1)
    #include<pthread.h>  //多线程的头文件
    /*
    #define UP 1
    #define DOWN 2
    #define LEFT 3
    #define RIGHT 4
    *///利用正负的差别,方便为后面控制蛇,不能往倒着走
    #define UP 1
    #define DOWN -1
    #define LEFT 2
    #define RIGHT -2
    
    struct Snake
    {
    	int hang;
    	int lie;
    	struct Snake *next;
    };
    int key;
    int dir;
    struct Snake *head = NULL;
    struct Snake *tail = NULL;
    
    struct Snake food;
    

3.2、初始化ncuses,页面以及按键initNcuse()

  • 分为两步,均是ncurse库的函数
    • 第一:初始化ncurse页面
    • 第二:允许按键的使用

请添加图片描述

  • initNcuse()

    void initNcuse()
    {
    	initscr();  //初始化ncurse页面
    	keypad(stdscr,1);//允许使用功能键
    //	noecho();//不把无关的信息,功能类的信息显示在页面上
    }
    

3.3、小蛇🐍的初始化和食物🍔的初始化initSnake()

  • 分为5步
    • 第一:初始化小蛇的运动方向;
    • 第二:清空节点,仅留一个头;
    • 第三:随机生成食物;
    • 第四:设置小蛇起始位置;
    • 第五:初始化小蛇长度;

请添加图片描述

  • initSnake()

    void initFood() //随机生成食物//子函数
    {
           	int x = rand()%20;
    	int y = rand()%20;
    
    	food.hang = x;
    	food.lie = y;
    }
    
    void addNode() //根据方向添加节点,一节🐍//子函数
    {
    	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
    
    	new->next =  NULL;
    	switch(dir)
    	{
    		case UP:
    			new->hang = tail->hang-1;
    			new->lie = tail->lie;
    			break;
    		case DOWN:
    			new->hang = tail->hang+1;
    			new->lie = tail->lie;
    			break;
    		case LEFT:
    			new->hang = tail->hang;
    			new->lie = tail->lie-1;
    			break;
    		case RIGHT:
    			new->hang = tail->hang;
    			new->lie = tail->lie+1;
    			break;
    	}
    	tail->next = new;
    	tail = new;
    }
    
    void initSnake()//初始化
    {
    	struct Snake *p;
    	dir = RIGHT;//刚开始的方向是向右边
    
    	while(head!=NULL)
    	{
    		p = head;   //head向下一个结点移一下,直到最后一个,将所有结点清空,只留一个 head=NULL
    		head = head->next;
    		free(p);
    	}	
    	initFood();//随机生成食物
     	head = (struct Snake*)malloc(sizeof(struct Snake));
    	head->hang = 1; //🐍的起始位子
    	head->lie = 1;  
    	head->next = NULL;
    
    	tail = head; 
    	addNode();
    	addNode();
    	addNode();
    	addNode();
    }
    
    
    

3.4、初始化地形、遍历各各点、打印边界、小蛇🐍 和食物🍔gamePic()

  • 分为三步:
    • 第一:初始化光标;
    • 第二:遍历各点,打印所需;
    • 第三:打印作者及食物位置;

请添加图片描述

  • gamePic()

    int hasSnakeNode(int i,int j) //这个点有🐍吗?有返回1, 没有返回0
    {
    	struct Snake *p;
    	p = head;
    
    	while(p != NULL) //遍历🐍的身子,是不是这个点
    	{
    		if(p->hang==i && p->lie==j)
    		{
    			return 1;
    		}
    		p = p->next;
    	}	
    	return 0;
    }
    
    int hasFood(int i,int j)//这个点是不是食物所在的点,有的返回1;
    {
    	if(food.hang == i && food.lie == j)//吃到了返回1
    	{
    		return 1;
    	}
    	return 0;//没吃到,返回0
    }
    
    void gamePic()
    {
    	int hang,lie;
    
    	move(0,0);//将光标设定在(0,0)
    	for(hang=0;hang<20;hang++)
    	{
    		if(hang==0)//第一行,打印“------”
    		{
    			for(lie=0;lie<20;lie++)
    			{
    				printw("--");
    			}
    		}
    		if(hang>0 && hang<=19) //打印左右界限
    		{
    			printw("\n");
    			for(lie=0;lie<=20;lie++)
    			{
    				if(lie==0 || lie==20)
    				{
    					printw("|");
    				}
    				else if(hasSnakeNode(hang,lie))//🐍的位置是不是这个点,打印身子
    				{
    					printw("[]");
    				}
    				else if(hasFood(hang,lie))//打印食物的位置
    				{
    					printw("##");
    				}
    				else
    				{
    					printw("  ");
    				}
    			}
    		}
    		if(hang == 19)//最后一行,打印边界“------”
    		{
    			printw("\n");
    			for(lie=0;lie<20;lie++)
    			{
    				printw("--");
    			}
    		}
    	}
    	printw("\nBy TP, food.hang=%d,food.lie=%d\n\n",food.hang,food.lie);
    }
    

3.5 两个进程pthread

  • refreshJieMian()//不断刷新页面

    • moveSnake()
      • 添加蛇头节点;
      • hasFood()//有没有吃到食物
      • ifSnakeDie//小蛇的死亡条件
    • gamePic()//同3.4;
    • refresh() //ncurses库的文件内置函数,只有某次缓存与上次不一样,才输出缓冲区的值;
    • usleep() //让该进程休眠指定us;
  • changeDir() //不断的检测按键

    • getch()//获取键盘输入;
    • 根据键盘输入,改变方向;
  • 其他

    • while(1)//锁定程序,别退了;
    • getch()//等待键盘输入不让程序推出;
    • endwin()//恢复终端
      请添加图片描述
  • pthread

    void deleNode()//删除🐍尾巴的节点,动起来
    {
    	struct Snake *p;
    	p =head;
    	head = head->next;
    	
    	free(p);
    }
    
    void moveSnake()  //吃到了食物与没吃到食物,蛇的长度和位置的变化;蛇死了怎么办;
    {
    	addNode(); //添加节点
    	if(hasFood(tail->hang,tail->lie)) // 尾结点(头),是不是食物所在的点
    	{                                 
    		initFood(); 	          // 是--重新创建食物--且不删除head(蛇尾) 结点
    	}
    	else
    	{	
    		deleNode();//删除
    	}
    	if(ifSnakeDie()) //如果🐍死了
    	{
    		refresh();//Curses库,屏幕刷新
    		usleep(100000);//暂停100000us
    		initSnake();//小蛇🐍初始化和食物🍔初始化
    	}
    }
    
    int ifSnakeDie()//🐍的死亡条件
    {
    	struct Snake *p;
    	p = head;
    
    	if(tail->hang==0 || tail->lie==0 || tail->hang==20 || tail->lie==20)//撞墙
    	{  
    		return 1;
    	}
    	while(p->next != NULL)//从到了自己吗
    	{
    		if(p->hang == tail->hang && p->lie == tail->lie)
    		{
    			return 1;
    		}
    		p = p->next;
    	}
    	return 0;
    }
    
    void turn(int direction)//防止🐍上下往回走,左右走;
    {
    	if(abs(dir) != abs(direction))//绝对值不一样,才可以转变方向
    	{
    		dir = direction;
    	}
    }
    
    void *refreshJieMian()//进程1  刷新界面
    {
    	while(1)
    	{
    		moveSnake(); //前进一格,
    		gamePic();
    		refresh();
    		usleep(100000);  //0.1s,通过多线程,刷新界面,也起到了调速的作用;
    	}
    }
    
    void *changeDir() //进程2  通过按键改方向
    {
    	while(1)
    	{
    		key = getch();
    		switch(key)
    			{
    				case KEY_DOWN:
    					turn(DOWN);
    					break;
    				case KEY_UP:
    					turn(UP);
    					break;
    				case KEY_LEFT:
    					turn(LEFT);
    					break;
    				case KEY_RIGHT:
    					turn(RIGHT);
    					break;
    			}
    	}
    }
    
    *///将这些放在main函数里面
    pthread_t t1; //定义进程 1
    pthread_t t2; //定义进程 2
    
    pthread_create(&t1,NULL,refreshJieMian,NULL);//不断的刷新界面
    pthread_create(&t2,NULL,changeDir,NULL);     //不断的检测按键输入
    
    while(1);
    
    getch();   //等待键盘输入,不让程序退出
    endwin(); //恢复终端
    */
    

3.6、main()

int main()
{
	pthread_t t1; //定义进程 1
	pthread_t t2; //定义进程 2

	initNcuse(); //初始化ncuses,页面以及按键
	initSnake(); //🐍的初始化和食物
 
	gamePic();   //初始化地形,遍历各各点,打印边界、🐍、和食物
	
	pthread_create(&t1,NULL,refreshJieMian,NULL);//不断的刷新界面
	pthread_create(&t2,NULL,changeDir,NULL);     //不断的检测按键输入

	while(1);

	getch();   //等待键盘输入,不让程序退出
	endwin(); //恢复终端
	return 0;
}

4、代码整合

  • 283行
//snake.c
#include<curses.h>   //图形库
#include<stdlib.h>
#include<unistd.h>   //sheep(1)
#include<pthread.h>
/*
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
*/
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2

struct Snake
{
	int hang;
	int lie;
	struct Snake *next;
};
int key;
int dir;
struct Snake *head = NULL;
struct Snake *tail = NULL;

struct Snake food;

void initFood() //随机生成食物
{
       	int x = rand()%20;
	int y = rand()%20;

	food.hang = x;
	food.lie = y;
}


void initNcuse()
{
	initscr();  //初始化ncurse页面
	keypad(stdscr,1);//允许使用功能键
//	noecho();//不把无关的信息,功能类的信息显示在页面上
}

int hasSnakeNode(int i,int j) //这个点有🐍吗?有返回1, 没有返回0
{
	struct Snake *p;
	p = head;

	while(p != NULL) //遍历🐍的身子,是不是这个点
	{
		if(p->hang==i && p->lie==j)
		{
			return 1;
		}
		p = p->next;
	}	
	return 0;
}

int hasFood(int i,int j)//这个点是不是食物所在的点,有的返回1;
{
	if(food.hang == i && food.lie == j)//吃到了返回1
	{
		return 1;
	}
	return 0;//没吃到,返回0
}


void gamePic()
{
	int hang,lie;

	move(0,0);//将光标设定在(0,0)
	for(hang=0;hang<20;hang++)
	{
		if(hang==0)//第一行,打印“------”
		{
			for(lie=0;lie<20;lie++)
			{
				printw("--");
			}
		}
		if(hang>0 && hang<=19) //打印左右界限
		{
			printw("\n");
			for(lie=0;lie<=20;lie++)
			{
				if(lie==0 || lie==20)
				{
					printw("|");
				}
				else if(hasSnakeNode(hang,lie))//🐍的位置是不是这个点,打印身子
				{
					printw("[]");
				}
				else if(hasFood(hang,lie))//打印食物的位置
				{
					printw("##");
				}
				else
				{
					printw("  ");
				}
			}
		}
		if(hang == 19)//最后一行,打印边界“------”
		{
			printw("\n");
			for(lie=0;lie<20;lie++)
			{
				printw("--");
			}
		}
	}
	printw("\nBy TP, food.hang=%d,food.lie=%d\n\n",food.hang,food.lie);
}

void addNode() //根据方向添加节点,一节🐍
{
	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));

	new->next =  NULL;
	switch(dir)
	{
		case UP:
			new->hang = tail->hang-1;
			new->lie = tail->lie;
			break;
		case DOWN:
			new->hang = tail->hang+1;
			new->lie = tail->lie;
			break;
		case LEFT:
			new->hang = tail->hang;
			new->lie = tail->lie-1;
			break;
		case RIGHT:
			new->hang = tail->hang;
			new->lie = tail->lie+1;
			break;
	}
	tail->next = new;
	tail = new;
}

void initSnake()//初始化
{
	struct Snake *p;
	dir = RIGHT;//刚开始的方向是向右边

	while(head!=NULL)
	{
		p = head;   //head向下一个结点移一下,直到最后一个,将所有结点清空,只留一个 head=NULL
		head = head->next;
		free(p);
	}	
	initFood();//随机生成食物
 	head = (struct Snake*)malloc(sizeof(struct Snake));
	head->hang = 1; //🐍的起始位子
	head->lie = 1;  
	head->next = NULL;

	tail = head; 
	addNode();
	addNode();
	addNode();
	addNode();
}

void deleNode()//删除🐍尾巴的节点,动起来
{
	struct Snake *p;
	p =head;
	head = head->next;
	
	free(p);
}

int ifSnakeDie()//🐍的死亡条件
{
	struct Snake *p;
	p = head;

	if(tail->hang==0 || tail->lie==0 || tail->hang==20 || tail->lie==20)//撞墙
	{  
		return 1;
	}
	while(p->next != NULL)//从到了自己吗
	{
		if(p->hang == tail->hang && p->lie == tail->lie)
		{
			return 1;
		}
		p = p->next;
	}
	return 0;
}

void moveSnake()  //吃到了食物与没吃到食物,蛇的长度和位置的变化;蛇死了怎么办;
{
	addNode(); //添加节点
	if(hasFood(tail->hang,tail->lie)) // 尾结点(头),是不是食物所在的点
	{                                 
		initFood(); 	          // 是--重新创建食物--且不删除head(蛇尾) 结点
	}
	else
	{	
		deleNode();//删除
	}
	if(ifSnakeDie()) //如果🐍死了
	{
		refresh();//Curses库,屏幕刷新
		usleep(100000);//暂停100000us
		initSnake();//小蛇🐍初始化和食物🍔初始化
	}
}

void *refreshJieMian()//进程1  刷新界面
{
	while(1)
	{
		moveSnake(); //前进一格,
		gamePic();
		refresh();
		usleep(100000);  //0.1s,通过多线程,刷新界面,也起到了调速的作用;
	}
}

void turn(int direction)//防止🐍上下往回走,左右走;
{
	if(abs(dir) != abs(direction))//绝对值不一样,才可以转变方向
	{
		dir = direction;
	}
}


void *changeDir() //进程2  通过按键改方向
{
	while(1)
	{
		key = getch();
		switch(key)
			{
				case KEY_DOWN:
					turn(DOWN);
					break;
				case KEY_UP:
					turn(UP);
					break;
				case KEY_LEFT:
					turn(LEFT);
					break;
				case KEY_RIGHT:
					turn(RIGHT);
					break;
			}
	}
}

int main()
{
	pthread_t t1; //定义进程 1
	pthread_t t2; //定义进程 2

	initNcuse(); //初始化ncuses,页面以及按键
	initSnake(); //🐍的初始化和食物
 
	gamePic();   //初始化地形,遍历各各点,打印边界、🐍、和食物
	
	pthread_create(&t1,NULL,refreshJieMian,NULL);//不断的刷新界面
	pthread_create(&t2,NULL,changeDir,NULL);     //不断的检测按键输入

	while(1);

	getch();   //等待键盘输入,不让程序退出
	endwin(); //恢复终端
	return 0;
}


欢迎大家一起交流讨论!

  • 45
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮您实现一个简单的贪吃蛇小游戏。 以下是C语言代码实现: ```c #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <time.h> #define ROW 25 // 游戏地图的行数 #define COL 80 // 游戏地图的列数 // 定义蛇的结构体 struct Snake{ int x; // 蛇头的横坐标 int y; // 蛇头的纵坐标 int len; // 蛇的长度 int tailx[100]; // 蛇尾的横坐标数组 int taily[100]; // 蛇尾的纵坐标数组 char dir; // 蛇的方向 }; // 定义食物的结构体 struct Food{ int x; // 食物的横坐标 int y; // 食物的纵坐标 char ch; // 食物的字符 }; // 定义游戏地图的二维数组 char map[ROW][COL]; // 初始化游戏地图 void InitMap(){ int i, j; for(i=0; i<ROW; i++){ for(j=0; j<COL; j++){ if(i==0 || i==ROW-1 || j==0 || j==COL-1){ map[i][j] = '#'; } else{ map[i][j] = ' '; } } } } // 显示游戏地图 void DisplayMap(){ int i, j; for(i=0; i<ROW; i++){ for(j=0; j<COL; j++){ printf("%c", map[i][j]); } printf("\n"); } } // 初始化蛇 void InitSnake(struct Snake *ps){ ps->x = 10; ps->y = 20; ps->len = 3; ps->tailx[0] = 10; ps->taily[0] = 18; ps->tailx[1] = 10; ps->taily[1] = 19; ps->tailx[2] = 10; ps->taily[2] = 20; ps->dir = 'a'; } // 显示蛇 void DisplaySnake(struct Snake s){ int i; for(i=0; i<s.len; i++){ map[s.tailx[i]][s.taily[i]] = '*'; } map[s.x][s.y] = '@'; } // 蛇移动 void Move(struct Snake *ps, struct Food *pf){ // 计算蛇尾的位置 int tailx = ps->tailx[0]; int taily = ps->taily[0]; int i; for(i=0; i<ps->len-1; i++){ ps->tailx[i] = ps->tailx[i+1]; ps->taily[i] = ps->taily[i+1]; } ps->tailx[ps->len-1] = ps->x; ps->taily[ps->len-1] = ps->y; // 移动蛇头 switch(ps->dir){ case 'w': ps->x--; break; case 's': ps->x++; break; case 'a': ps->y--; break; case 'd': ps->y++; break; } // 判断是否吃到食物 if(ps->x==pf->x && ps->y==pf->y){ ps->len++; // 增加蛇的长度 // 生成新的食物 srand(time(NULL)); pf->x = rand() % (ROW-2) + 1; pf->y = rand() % (COL-2) + 1; pf->ch = '$'; } // 判断是否撞墙或撞到自己的身体 if(map[ps->x][ps->y]=='#' || map[ps->x][ps->y]=='*'){ printf("Game over!\n"); exit(0); } // 更新地图 map[tailx][taily] = ' '; for(i=0; i<ps->len; i++){ map[ps->tailx[i]][ps->taily[i]] = '*'; } map[ps->x][ps->y] = '@'; } // 初始化食物 void InitFood(struct Food *pf){ srand(time(NULL)); pf->x = rand() % (ROW-2) + 1; pf->y = rand() % (COL-2) + 1; pf->ch = '$'; } // 显示食物 void DisplayFood(struct Food f){ map[f.x][f.y] = f.ch; } // 监听键盘操作 void ListenKey(struct Snake *ps){ char ch; if(kbhit()){ // 判断是否有按键按下 ch = getch(); if((ch=='w'||ch=='s'||ch=='a'||ch=='d') && abs(ch-ps->dir)!=2){ ps->dir = ch; } } } int main(){ struct Snake snake; struct Food food; InitMap(); InitSnake(&snake); InitFood(&food); while(1){ system("cls"); // 清屏 DisplayMap(); DisplaySnake(snake); DisplayFood(food); Move(&snake, &food); ListenKey(&snake); Sleep(200); // 延时,控制蛇的移动速度 } return 0; } ``` 请注意,本AI无法保证以上代码正确运行,请自行测试和调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值