C贪吃蛇代码详解(ncurse)

#include<curses.h>		//使用 ncurses 必须调用它的函数库 
#include<stdlib.h>
#include<time.h>

#define UP		1		//宏定义  UP 与 1 等效 
#define DOWN 	-1
#define LEFT	2
#define RIGHT	-2
struct Snake      		//结构体 
{
    
    
	int hang;
	int lie;
	struct Snake *next;
};

struct Snake *head;		//定义结构体类型的指针 head 
struct Snake *tail;		//定义结构体类型的指针 tail
int key;
int dir;

struct Snake food;     	//定义结构体类型   food 

void initNcurse()		//初始化 Ncurses (Ncurses 是一套编程库) 
{
    
    
	initscr();			// Ncurses界面的初始化函数  
						//获取包括大小和特征在内的终端类型,并设置终端支持的 curses 环境。
	keypad(stdscr,1);	//使用keypad(stdscr,TURE) 就为“标准屏幕”(stdscr)激活了功能键。 
						// TURE-> ture-> 1
						 
	noecho();			//调用 noecho()函数 禁止输入的字符出现在屏幕上。(防止界面中出现一些乱码)
						//echo()函数  允许输入的字符出现在屏幕上。
} 

void initFood()			//初始化食物 
{
    
    	
	food.hang = rand()%20;	//rand()%20  取余,取出来的余数是小于20的 
	food.lie = rand()%20;
}

int hasSnakeNode(int i,int j)	//通过判断此时贪吃蛇身子的位置是否与gamPic() 中的位置相同 来决定返回 0 or 1 
{
    
    
	struct Snake *p;	//定义结构体类型的指针 p  
	p = head;
	
	while(p != NULL)	//判断 P 存放的地址是否为空 
	{
    
    
		if(p->hang ==i && p->lie == j)	//判断结构体类型的指针 P 指向的 hang 与 lie 是否与 gamePic() 中的hang 与 lie 都相等 
		{
    
    
			return 1;					//相等 则返回 1  (在C语言中 1 代表真 ture) 
		}
		p = p->next;					//链表下移动, 指针 P 指向链表的的下一个 结构体 
	}
	return 0;				// p 存放的地址为空( NULL ), 返回 0 (在C语言中 0 代表假 fack) 
}

int hasFood(int i,int j)	//该函数是用来判断当 hang==i lie==j 时,是否打印出食物 打印就返回 1, 不打印就返回 0				
{
    
    							//有返回值的 返回类型用 int ; 不需要返回值的 返回类型用 void (void 是无类型) 
 
	if(food.hang == i && food.lie == j){
    
    //判断结构体 food 的 hang 与 lie 是否与 gamePic() 中的hang 与 lie 都相等 
		return 1;			//相等 则返回 1  (在C语言中 1 代表真 ture)  
	}
	return 0;				//不相等 则返回 0  (在C语言中 0 代代表假) 
}

void gamePic()				//该函数的作用是打印 地图 和 食物 和 贪吃蛇的身子 
{
    
    
	int hang;	
	int lie;
	
	move(0,0);				//可以移动地图的显示位置 ,(我实验后只改变了地图在屏幕上下的位置  左右的位置没有改变) 

	for(hang=0;hang<20;hang++){
    
    	 
		if(hang==0){
    
    
			for(lie=0;lie<20;lie++){
    
    
				printw("--");
			}
		printw("\n");
		}   
		if(hang>=1 && hang<=18){
    
    
			for(lie=0;lie<=20;lie++){
    
    
                                if(lie==0 || lie==20){
    
    
                                        printw("|");
				}else if(hasSnakeNode(hang,lie)){
    
    
					printw("[]");
                }
				else if(hasFood(hang,lie)){
    
    	//判断 hasFood(hang,lie)返回的值是 1 or 2  
					printw("##");			//如果返回值是 1 就在地图中的 第 hang 行,第 lie 列; 打印"##" 
				}else{
    
    
                    printw("  ");			//如果返回值是 0 就在打印两个空格 "  " 
                }
            }	
		printw("\n");						//换行 
		}
		if(hang==19){
    
    
			for(lie=0;lie<20;lie++){
    
    
                        	printw("--");
                        }
			printw("\n");
			printw("By Wangbicheng key= %d\n",key);
			printw("food.hang=%d,food.lie=%d",food.hang,food.lie);
		}       	
	}
}

void addNode()
{
    
    
        struct Snake *new =(struct Snake*)malloc(sizeof(struct Snake)); 	//定义一个结构体类型的指针 new ,指向返回值为结构体 并有该结构体结构体大小的空间 

        new->next = NULL; 
		
	switch(dir){
    
    	//此处的 dir 表示的是你在键盘上输入的值  是上 还是 下 或者 左 右 
		case UP:
						new->hang = tail->hang-1;	//对 new->hang 进行赋值 
		        		new->lie = tail->lie;		//对 new->lie 进行赋值  
						break;						//break 表示结束 跳出 switch() 
		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->next 指向 新赋值后 new (此处的new 所带得值不重要 重要的是 new 再此成为最后一节) 
        tail = new;   			//让 tail的 hang与lie 等于 新赋值后的 new的 hang与lie  

}


void initSnake()			//对贪吃蛇得身子进行 初始化 
{
    
    
	struct Snake *p;  
	
	dir = RIGHT;        	//让 dir 一开始就等于 RIGHT 是为了 方便一开始就让蛇有身子 

	while(head!=NULL){
    
    		//判读head 是否为空指针 ,一直判断到 head为 NULL 为止 
		p = head;			//如果不是空指针 就将 head存放得地址 赋给 指针p 
		head = head->next;	//链表下移 
		free(p);            //释放 head 所指向得 malloc 申请到得空间  ,因为每初始化依次 下方都会再次申请空间,有多少节蛇身,就释放几次 
	}

	head = (struct Snake *)malloc(sizeof(struct Snake));//申请空间 
	
	head->hang = 1;			//初始化 
	head->lie = 1;			//初始化 
   	head->next= NULL;       //初始化 

	tail = head;			//tail 是链表的尾部,贪吃蛇的头部  因为此时贪吃蛇还只有一个 [] 所以 tail=head 

	addNode();				//在链表的尾部增加一节 [] 
	addNode();
	addNode();
	addNode();				//有四个 addNode() 表示增加了四个 [] 作为贪吃蛇的初始化蛇身 
}

void deleNode()				//该函数的作用是删除蛇的尾巴,(链表的头部) 
{
    
    
	struct Snake *p;		//注意每个函数中 所定义的类型只有 该函数中有用  
							//例如 struct Snake *p; 只有在这个函数中用, 放到外面如果没有重新定义是无法使用的 
	p = head;	 
	head = head->next;		//链表下移, 表示将链表的头部删除掉 
	
	free(p);				//释放删除掉的头部的空间 
}

void moveSnake()			//这个函数的作用是 给贪吃蛇增加能力与限制  
							//能力:能自己沿着头的方向移动 ; 吃到食物会长一节蛇身 
							//限制:例如活动的空间 以及碰到它自己就会死  还有判断食物是否被吃掉 并让食物在其他位置重新出现 
{
    
    
	struct Snake *p;
        p = head;

	addNode();				//一开始就给贪吃蛇的头部(链表的尾部)增加一节蛇身 [] 
	
	if(hasFood(tail->hang,tail->lie)){
    
    	//判断蛇头的位置是否在食物"##"的坐标  
		initFood();			//蛇头在食物的位置  对食物进行初始化,初始化后食物的位置会随机改变
	}
	else{
    
    
		deleNode();			//蛇头不在食物的位置  不对食物进行初始化,并删除掉一个蛇尾(链表的头部) 
	}

	if(tail->hang==0 || tail->lie==0 || tail->hang==20 || tail->lie==20){
    
    	//判断贪吃蛇的头部所在的位置 是否在地图的边界线上 
		initSnake();		//贪吃蛇头部的所在位置在地图的边界线上, 贪吃蛇进行初始化 初始化后贪吃蛇会变回最初的位置和大小 
	}
	while(p->next != NULL){
    
    	//对贪吃蛇的身子(链表)进行遍历,p->next = NULL,标志着贪吃蛇的身子(链表)遍历结束 
                if(p->hang == tail->hang && p->lie == tail->lie){
    
    	//判断 贪吃蛇的身子与头部是否重合 
                initSnake();//如果重合 则贪吃蛇进行初始化  
				initFood();	//食物进行初始化  
                }
        p=p->next;			//链表下移 
        }
	
	if(food.hang==0 || food.lie ==0 || food.hang==19 || food.lie==20){
    
    	//这个条件语句是为了防止食物出现在地图的边界线上  
																		//如果不写这个会出现一个bug ,那就是蛇无法吃到食物 ,导致食物在地图内无法出现新的食物
		initFood();			//食物的初始化											 
	}
}

void refreshJieMian()
{
    
    
	while(1){
    
    					//while(1) 一直判断为真,一直死循环
				moveSnake();	//判断贪吃蛇触发规则没有 
                gamePic();		//打印地图 食物 和 贪吃蛇 
                refresh();		//刷新界面,我们每一次看到的贪吃蛇连续的移动 都是该函数刷新界面后产生的 
                usleep(100000);	//每刷新一次 等待 0.1 秒  
	}
}

void turn(int direction)		//该函数的作用就是让你不能按与上一次按键相反的建  例如:按了左以后不能按右,只能按其他的 例如上与下 
{
    
    
	if(abs(dir) != abs(direction))	//abs() 该函数取可以取 括号() 内的绝对值 在代码的开头 就宏定义了 上下 是 1与 -1 ; 左右 是 2与-2 
	{
    
    
		dir = direction;			//如果这次按的键和上次相反  则本次按的键会强制变成和上一次一样的键 例如:上次按了左,这次按了右,那么右就会变成左 
	}
}

void changeDir()
{
    
    
	while(1){
    
    

                key = getch();		//获取键盘输入的内容给 key 
                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;		//t1 线程 
	pthread_t t2;		//t2 线程  

	initNcurse();		//初始化 ncurse 界面 

	initSnake();		//初始化贪吃蛇 

	initFood();			//初始化食物的位置 

	gamePic();			// 打印出地图 食物 贪吃蛇 

	pthread_create(&t1,NULL,refreshJieMian,NULL);	//t1 线程负责刷新页面 	;让贪吃蛇动起来 
	pthread_create(&t2,NULL,changeDir,NULL);		//t2 线程负责输入 		;让人能控输入方向键进行控制 
													//双线程实现了让贪吃蛇动的同时又可以输入方向键对蛇进行控制 
	
	while(1);			//作用:让程序不退出 不执行后面的程序 

	getch();			
	endwin();			//退出 curses 模式, endwin()函数释放了 curses 子系统和相关数据结构占用的内存,使你能够正常返回控制台模式。 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_49472648/article/details/107829792