天天看點

在 Linux 系統下基于 curses 的貪吃蛇實作,注釋超詳細

代碼實作

#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

//蛇身的節點,用雙向連結清單來表示. 資訊有橫坐标 x, 縱坐标 y, 前一個節點 pre, 後一個節點 next.
struct node{
	int x;
	int y;
	struct node * next;
	struct node * pre;
}; 
typedef struct node snakebody;	//把該結構體重命名

//表示一條蛇,隻需要存儲頭節點和尾節點即可,在蛇的移動過程中,隻需要把目前頭節點指向新建立的一個頭節點,尾節點指向它的前一個即可。
struct snake{
	snakebody * head;
	snakebody * tail;
};

//蛇要吃的點
struct point{
	int x;
	int y;
	int ifcreate;	//因為使用了多線程,是以用一個标志位來控制,每當蛇吃了一個點,則主線程修改 ifcreate 的值,建立得分點的線程建立一個點
};

//同樣,使用一個線程來判斷蛇是否死亡,為了傳遞參數,要建立一個結構體
struct dead {
	int flag;	//0 代表遊戲沒有開始,1 代表開始,2代表結束
	struct node * head;
};

//得到使用者的控制,為了實作非阻塞,使用了 select
int mygetchar(void);
//在頭節點前面加一個節點
snakebody * addnode(snakebody * head, int hori, int ver);
//建立蛇身
struct snake mksnake(void);
//建立得分點, 該函數運作起來是獨立的線程
void * createpoint(void *);
//判斷是否死亡, 該函數運作起來是獨立的線程
void * ifdead(void *);

int main(void)
{
	int hori, ver;	//橫、縱坐标
	char dir;	//使用者輸入的方向
	int begin;	//判斷是否開始
	struct snake snake;
	snakebody *head, *tail, *current;
	struct point * p =  (struct point *) malloc(sizeof(struct point));	//用于向 createpoing 函數傳遞的參數
	struct dead * dead = (struct dead *) malloc(sizeof(struct dead));	//用于向 ifdead 函數傳遞的參數

	WINDOW * pt = initscr();	//初始化視窗
	snake = mksnake();	//建立蛇身
	head = snake.head;
	tail = snake.tail;
	//繪制初始圖形
	current = head;
	while(current != NULL)
	{
		mvaddch(current->y, current->x, '*');
		current = current->next;
	}
	refresh();
	usleep(300000);
	//控制移動
	hori = ver = 0;
	begin = 0;

	p->x = p->y = -1;	//把初始的得分點設定成 (-1, -1)
	p->ifcreate = 1;	//開始建立的分店
	pthread_t t1;
	pthread_create(&t1, NULL, createpoint, (void *) p);	//啟動建立得分點的線程

	dead->head = head;	
	dead->flag = 0;	
	pthread_t t2;
	pthread_create(&t2, NULL, ifdead, (void *) dead);	//啟動判斷死亡的線程

	while(dead->flag != 2)	//當蛇沒死就一直運作
	{
		//擷取輸入
		switch(dir = mygetchar())
		{
			//在水準方向上不能按與運動方向相反的方向鍵
			case 'd': if (hori != -1) hori = 1;
					  begin = 1;	//使用者按了方向鍵,遊戲開始
				  ver = 0;
				  break;
			case 'a': if (hori != 1) hori = -1;
					  begin = 1;
				  ver = 0;
				  break;
			case 'w': if (ver != 1) ver = -1;
					  begin = 1;
				  hori = 0;
				  break;
			case 's': if (ver != -1) ver = 1;
					  begin = 1;
				  hori = 0;
				  break;
			default: break;
		}

		if(begin)
		{
			head = addnode(head, hori, ver);	//運動起來就像運動方向添加頭節點
			mvaddch(tail->y, tail->x, ' ');		//把尾節點設定成空格
			mvaddch(head->y, head->x, '*');		//螢幕上新加一個頭節點
			//如果得分
			if(head->y == p->y && head->x == p->x)	
			{
				head = addnode(head, hori, ver);
				mvaddch(head->y, head->x, '*');
				p->ifcreate = 1;	//再建立一個得分點
			}

			free(tail);	//把尾節點占的空空關鍵釋放
			tail = tail->pre;	//尾節點指向它的前一個
			tail->next = NULL;

			dead->head = head;
			dead->flag = 1;		//判斷是否死亡

			refresh();
			usleep(100000);
		}
	}
	
	sleep(2);
	endwin();
	return 0;
}

//使用 select 來使輸入非阻塞,直接複制的 man select 中的代碼
int mygetchar(void)
{
	fd_set rfds;
	struct timeval tv;
	int retval;
	char ch;

	/* Watch stdin (fd 0) to see when it has input. */
	FD_ZERO(&rfds);
	FD_SET(0, &rfds);

	/* Wait up to five seconds. */
	tv.tv_sec = 0;
	tv.tv_usec = 10000;

	retval = select(1, &rfds, NULL, NULL, &tv);
	/* Don't rely on the value of tv now! */

	ch = 0;
	if (retval == -1)
		perror("select()");
	else if (retval)
	{
		ch = getchar();
		setbuf(stdin,NULL);
	}

	/* FD_ISSET(0, &rfds) will be true. */
	else{
	}
	return ch;
}

//建立蛇身
struct snake mksnake(void)
{

	snakebody *head, *tail, *current;
	int i, j;


	head = (snakebody*) malloc(sizeof(snakebody));
	//初始化成 5 個長度的,頭節點的坐标就是 5
	head->x = 5;
	head->y = 0;
	head->next = NULL;
	tail = head;
	i = 5;
	j = 0;
	while(i-- >  0)
	{
		current = (snakebody *) malloc(sizeof(snakebody));
		current->x = i;
		current->y = j;
		current->pre = tail;
		current->next = NULL;
		tail->next = current;
		tail = current;
	}
	struct snake s = {head, tail};

	return s;
}

//建立得分點, 注意,該函數運作起來時作為一個獨立的線程
void * createpoint(void * point)
{
	struct point *p = (struct point *) point;
	while(1)
	{
		while(p->ifcreate == 0);	//如果玩家沒有吃了目前得分點,則在此處一直空轉等待
		
		//建立得分點
		p->x = rand() % 15 + 5;
		p->y = rand() % 15 + 5;
		mvaddch(p->y, p->x, '*');
		p->ifcreate = 0;
	}
	return (void *) p;
}

//添加節點,沒什麼好說的,就是連結清單添加節點
snakebody * addnode(snakebody * head, int hori, int ver)
{
	snakebody * current = (snakebody *) malloc(sizeof(snakebody));
	current->x = head->x + hori;
	current->y = head->y + ver;
	current->next = head;
	current->pre= NULL;
	head->pre = current;
	return current;
}

//判斷是否死亡,注意,該函數運作起來時一個獨立的線程
void * ifdead(void * d)
{
	struct dead * dead = (struct dead *) d;

	snakebody *head, *current;
	while(dead->flag != 2)
	{
		while(dead->flag == 0);	// 0 代表蛇沒有運動,就不用判斷,是以在此處空轉等待
		head = dead->head;
		current = head;
		while((current = current->next) != NULL)
		{
			//判斷蛇頭是否指向了蛇身
			if(current->x == head->x && current->y == head->y){
				clear();
				mvprintw(0, 0, "Game Over!");
				refresh();
				dead->flag = 2;
				break;
			}
			else dead->flag = 0;
		}
	}		

	return NULL;
}

           

繼續閱讀