代码实现
#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;
}