#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LEN sizeof(struct snack)
int c_long,c_high; //界面大小
int score; //分数
int num; //链表节点数,也是蛇的长度
int level; //等级
int direct; //键入方向
int direct_last; //保存上一次的键入方向
bool jude; //蛇是否死亡标志
bool again; //游戏是否结束标志
char end; //获取游戏结束指令
struct snack *p1,*p2,*head,*tail; //创建链表指针
WINDOW *win1,*win2,*win3; //创建游戏窗口
void interface(int x,int y); //绘制窗口win1,win2
void food_direct(void); //产生随机食物
void game_start(void); //游戏开始,产生随机蛇头,赋予初始移动方向
void move_control(int x); //根据键入方向,产生前进坐标
char snack_dead(void); //判断蛇是否死亡
void snack_show(void); //将蛇身链表显示在屏幕上
void snack_move(void); //蛇移动
void score_level(void); //分数和等级
void move_order(void); //键入移动方向
void game_end(void); //游戏结束
struct food //存储食物坐标
{
int y;
int x;
}food;
struct snack //蛇身节点坐标
{
int x;
int y;
struct snack *next;
struct snack *back;
}snack;
struct snack * Creat(void) //建立蛇身链表
{
++num;
p1=(struct snack *)malloc (LEN);
if(num==1) head=p1;
else tail->next=p1;
p1->x=snack.x;
p1->y=snack.y;
p1->next=NULL;
p1->back=tail;
tail=p1;
return (tail);
}
int main()
{
struct winsize size; //获取界面大小
ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
c_long=size.ws_col; //屏幕长
c_high=size.ws_row; //屏幕高
initscr(); //开启curses模式
start_color(); //开启颜色模式
begin: //循环开始节点
score=0; //考虑到代码循环使用,全局变量的初始化放在main函数循环体中
num=0;
jude=false;
tail=NULL;
keypad(stdscr,TRUE); //curses接管键盘转义
noecho(); //输入不回显
curs_set(0); //不显示光标
interface(c_long,c_high); //调用函数,游戏开始
game_start();
snack_show();
food_direct();
pthread_t snack; //创建两个线程
pthread_t control;
pthread_create(&snack, NULL, (void *)snack_move, NULL); //蛇移动线程
pthread_create(&control, NULL, (void *)move_order, NULL); //方向控制线程
pthread_join(snack, NULL); //等待蛇移动线程结束
pthread_cancel(control); //终止方向控制线程
game_end(); //调用函数,决定是否循环
if (again) goto begin;
endwin(); //关闭curses模式
return 0;
}
void interface(int x,int y) //绘制界面
{
win1=newwin(c_high,c_long-40,0,0);//新窗口(行,列,begin_y,begin_x) //创建新窗口win1
box(win1,ACS_VLINE,ACS_HLINE);
win2=newwin(c_high,39,0,c_long-39);//新窗口(行,列,begin_y,begin_x) //创建新窗口win2
box(win2,ACS_VLINE,ACS_HLINE);
mvwprintw(win2,16,14,"Retro Snaker"); //窗口win2显示实时数据
mvwprintw(win2,24,14,"Level: 1");
mvwprintw(win2,32,14,"You Score: 0");
refresh(); //刷新三个窗口
wrefresh(win1);
wrefresh(win2);
}
void game_start(void) //游戏开始,产生一个随机蛇头和初始运动方向
{
int a,b;
a=c_long-50; //蛇头产生的位置限制在win1内
b=c_high-5;
srand((int)time(NULL));
snack.y=rand()%a+1;
srand((int)time(NULL));
snack.x=rand()%b+1; //产生随机蛇头坐标
move(snack.x,snack.y);
if(snack.y<=a/2) direct=KEY_RIGHT; //产生初始运动方向
else direct=KEY_LEFT; //蛇头在左半面初始向右,在右半面初始向左
}
void snack_show(void) //蛇链表处理与显示
{
p2=Creat(); //snack结构体中的坐标纳入链表,p2指针指向蛇头
if(num!=score+1) //蛇长度没有变化执行
{
num--;
move(head->x,head->y); //蛇尾位置输出空字符
addch(' ');
p1=head; //释放蛇尾(链表头)
head=p1->next;
head->back=NULL;
}
do{
move(p2->x,p2->y); //光标移动至p2所指坐标
attron(COLOR_PAIR(1)); //设置输出字符格式为黑底黄字
init_pair(1, COLOR_YELLOW, COLOR_BLACK);
addch('*'); //输出*字符
p1=p2->back; //p2指针后移
p2=p1;
}while(p2!=NULL); //输出完毕退出
}
void food_direct(void) //产生随机食物
{
int a,b,x,y;
bool j=true; //食物是否合法标志
a=c_long-42; //食物产生的位置限制在win1内
b=c_high-2;
do{
srand((int)time(NULL)); //产生随机坐标
y=rand()%a+1;
srand((int)time(NULL));
x=rand()%b+1;
p1=tail; //坐标是否在蛇身上
do{
if(x==p1->x&&y==p1->y) j=false; //是,坐标非法
else {
p2=p1->back;
p1=p2;
}
}while(p2!=NULL&&j==true);
}while(j==false); //循环直至坐标合法
food.x=x; //合法坐标送至食物坐标结构体
food.y=y;
attron(COLOR_PAIR(2)|A_BLINK); //食物显示为黑底白字,闪烁
init_pair(2, COLOR_WHITE, COLOR_BLACK);
mvprintw(food.x,food.y,"*");
attroff(COLOR_PAIR(2)|A_BLINK);
wrefresh(win1);
}
void move_order(void) //方向移动指令
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); //设置线程响应cancel指令的方式为立即响应
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
while(!jude){ //蛇没有死亡则进入循环
direct_last=direct; //保存当前的移动方向
usleep(300000-level*5000); //延时去抖
direct=getch(); //键入指令
switch(direct) //指令与当前移动方向相反则指令无效
{
case KEY_UP:if(direct_last==KEY_DOWN) direct=KEY_DOWN;break;
case KEY_DOWN:if(direct_last==KEY_UP) direct=KEY_UP;break;
case KEY_RIGHT:if(direct_last==KEY_LEFT) direct=KEY_LEFT;break;
case KEY_LEFT:if(direct_last==KEY_RIGHT) direct=KEY_RIGHT;break;
default:direct=direct_last;break; //键入非法指令则指令无效
}
}
}
void move_control(int x) //方向控制
{
switch (x) {
case KEY_UP:snack.x--;break; //键入为上,x-1
case KEY_DOWN:snack.x++;break; //键入为下,x+1
case KEY_RIGHT:snack.y++;break; //键入为右,y+1
case KEY_LEFT:snack.y--;break; //键入为坐,y-1
}
}
char snack_dead(void) //判断蛇是否死亡
{
if(snack.x==0||snack.x==c_high-1||snack.y==0||snack.y>=c_long-41) jude=true; //蛇撞墙,死亡
else if(num!=1) //蛇头撞上蛇身,死亡
{ p1=tail->back;
do
{
if(snack.x==p1->x&&snack.y==p1->y) jude=true;
else
{
p2=p1->back;
p1=p2;
}
}while(p2!=NULL&&!jude);
}
return (jude);
}
void snack_move(void) //蛇移动
{
while(1)
{
move_control(direct);
snack_dead();
if(jude) break; //蛇撞墙,退出
score_level();
if(score==5428) break; //屏幕满,退出
snack_show();
refresh();
usleep(300000-level*50000); //蛇移动的速度
}
}
void score_level(void) //分数与等级
{
if(tail->x==food.x&&tail->y==food.y) //蛇吃到食物
{
score++; //分数+1
food_direct(); //重新产生食物坐标
mvwprintw(win2,32,25,"%d",score);
wrefresh(win2);
move(tail->x,tail->y);
}
if(score<10) level=1; //根据分数设置等级,用来控制蛇的移动速度
else if(10<=score&&score<25) level=2;
else if(25<=score&&score<50) level=3;
else if(50<=score&&score<75) level=4;
else if(75<=score) level=5;
mvwprintw(win2,24,25,"%d",level);
wrefresh(win2);
}
void game_end(void) //游戏结束界面
{
win3=newwin(10,40,c_high/2-8,(c_long-40)/2-20); //创建新窗口win3
box(win3,ACS_VLINE,ACS_HLINE);
if(score==5428) mvwprintw(win3,3,10,"You Win,try again?");
else mvwprintw(win3,3,10,"You Lost,try again?");
mvwprintw(win3,6,13,"Y");
mvwprintw(win3,6,25,"N");
touchwin(win3);
wrefresh(win3);
do //再来一次?
{
end=getch();
}while(end!='Y'&&end!='N'&&end!='y'&&end!='n');
if(end=='Y'||end=='y') again=true;
else again=false;
while(head!= NULL) //释放链表
{
p1 = head;
head = head->next;
free(p1);
}
}
运行结果展示