代碼實作
#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;
}