用方向键来控制贪吃蛇的上下左右移动,吃到食物变长,吃到身体或者撞墙死亡。
贪吃蛇的身体:用链表实现。
动画展示:不断覆盖打印地图和蛇身体位置和食物,形成蛇的运动:吃到食物变长,移动转向和死亡
获取输入:不断获取用户的键盘输入,比如输入上下左右键使蛇转向,输入r重新开始游戏,输入q退出游戏等
1、贪吃蛇的身体
定义Snake结构体,代表蛇的身体,结构体包含每一节的行列坐标,和下一节蛇的指针,每吃到一个食物,就在尾部增加一节,并且根据尾节点的坐标,修改增加的节点的坐标,并且让蛇尾指针指向新的尾巴节点。
蛇身体定义三个全局的变量,head,tail和tailforward。
head代表蛇头,tail代表蛇尾,tailforward代表蛇尾的前一个节点,tailforward用来找到tail。
2、贪吃蛇的移动
创建moveSnake函数,addHead函数,addTail函数,deleteTail函数
moveSnake函数通过调用addHead函数,deleteTail函数,先在蛇头前增加一个节点,节点的坐标根据不同的移动方向,改变行列的值,再把蛇尾节点删除,再把蛇头坐标赋值为最新的头结点,来实现蛇的移动
addTail函数是当蛇的坐标与食物坐标重合的时候,根据蛇尾的坐标,增加一个新的尾部节点,行列赋值为对应坐标,再把tail节点赋值为最新的尾结点,来实现蛇的增长。
3、动画效果和功能实现
由于需要不断的覆盖打印,需要用到pthread线程库,创建一个线程和主线程一起运行,该线程传入printMap函数不断的打印地图,主线程里面不断的检测输入。因为不断打印地图是一个死循环,如果不用多线程的话执行到打印地图就不会退出,从而就检测不到用户输入了。
printwMap函数里面用二重for循环打印,在地图边界位置打印地图的边界,地图内部打印空白,然后再for循环中检测空白处的坐标,如果目标坐标和蛇的坐标重合,或者和食物的坐标重合,那么就不打印空白,而打印对应的蛇身或者食物,在函数开始会调用一次moveSnake函数,来改变蛇头的坐标,再调用move(0,0)在左上角开始打印地图,不断的循环调用move和同一位置覆盖打印,就可以形成动画,在最后调用usleep函数,通过控制睡眠的时间,来控制打印的刷新率,从而控制蛇的运动快慢,睡眠时间越长显示出来蛇运动的越慢,时间越短显示出来蛇运动的越快。
在打印地图还会检测蛇是否吃到食物,蛇的坐标如果和食物的坐标重合,那么调用addTai函数来增加一个蛇身(增加一个坐标),并且调用getFood函数再重新获取食物,而在打印完地图还会检测蛇是否撞到身体或者墙壁,撞到了就停止打印。
4、检测输入
在主线程中不断检测用户输入,上下左右或者退出或者重开,通过调用ncurses库里面的keypad函数来允许输入功能按键,在调用getch函数来获取输入,getch函数是输入数据之后不需要回车就可以获取到输入的数据,对于控制蛇的移动很便捷。
因为蛇是不可以直接转向的,比如蛇往左走,按下右键是不能直接掉头的,那么就可以在左走的时候按下右键无效,可以#define 左右的值为相反数,蛇运动的指标先abs取绝对值之后,再控制,最新的指令如果和之前的指令不等,才能转向。这样按下右键的时候,蛇还是相当于按下的是左键。
1、避免食物生成在地图外
2、蛇的直接调头问题可以通过读取输入间隔长一些来避免
3、不同的食物不能在同一地方生成
先下载ncurses库的文件,在Ubuntu的terminal中输入sudo apt-get install libncurses5-dev即可下载
其次头文件要包含#include
#include
想使用ncurses库里面的函数,就需要先调用initscr(),进入ncurse模式,这样才能正常使用ncurses库里面的函数。并且调用了initscr()之后,运行的程序会在专门的ncurse界面运行。
换句话说,所有ncurses函数必须跟在initscr后面。同样的约定,在结束使用ncurses后,应该使用endwin来释放所有ncurses使用的内存。在使用endwin后,您将不能在使用任何ncurses的函数,除非您再一次调用initscr函数。
注意:进入ncurses模式之后,输出函数用ncurses下的printw代替printf,在ncurses下使用printf可能会出乱码。
结束ncurses模式,需要调用endwin()函数来正常退出ncurse界面,退出ncurse界面后如果程序还未结束,程序就会进入一般terminal运行程序的界面。如果不调用结束函数,等程序运行完毕,terminal会出乱码崩掉。
在ncurses模式下,代码如果有运行可以在界面显示的代码,比如输出代码,代码会正常运行,但是运行了之后只会执行下一段代码,输出结果还在缓冲区,而不会显示在ncurses界面。如果需要显示输出,那么就要在输出代码后面添加refresh()函数来刷新界面,refresh执行后,界面会被刷新,从而显示最新的界面。也可以在输出代码后面添加getch()等待输入,运行完输出代码后,会自动刷新界面,然后再等待用户输入。
在ncurses模式下,如果ncurses界面的光标所在处已经有字符输出了,那么再次执行输出代码会覆盖上次的输出字符。可用不断用move函数来移动光标,然后不断的覆盖输出,就可以形成动画效果。
https://blog.csdn.net/ztq_12345/article/details/100560314
和ncurses一样编译的时候需要 -l进行链库,才能正常编译。
创建线程:先定义线程 pthread t;再调用pthread_create创建线程,第一个参数为线程地址,第二个参数用来设置线程属性,最后一个参数是运行函数的参数,如果不需要设置第二和第四个参数则传参NULL,第三个参数为功能函数名,并且功能函数返回值为void *
创建线程后线程会和主线程抢夺资源,不同时刻只会运行一个线程。
结束线程:pthread_exit或者pthread_cancle,exit在自己线程函数里面调用终结此线程,cancle可以在本线程里面调用来终结其他线程。exit传参指针;cancle传参其他线程名,cancle成功返回,不成功返回非0值
是在整个程序退出时,要终止各个线程,应该在成功发送cancle指令后,使用pthread_join函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。pthread_join是用来等待一个线程的结束,线程间同步的操作
注意:该代码是在Linux操作系统下编译的,在Windows系统下可能没有pthread库
#include
#include
#include
#include
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
typedef struct p1{
int hang;
int lie;
struct p1* next;
}Snake,*Psnake;
Snake food;
Snake food2;
int score=0;
int spee=1;
Psnake head=NULL;
Psnake tail=NULL;
Psnake tailforward=NULL;
int key=LEFT;
int dir=LEFT;
void initSnake(){
if(head!=NULL){
while(1){
Psnake t2=head;
head=head->next;
if(head==NULL){
break;
}
free(t2);
}
}
Psnake tmp=(Psnake)malloc(sizeof(Snake));
head=tmp;
head->hang=1;
head->lie=88;
head->next=NULL;
tail=head;
}
void getFood(){
while(1){
food.hang=rand()%39+1;
food.lie=rand()%87+1;
if(food.hang==food2.hang&&food.lie==food2.lie)continue;
else if(food.hang==1||food.hang==40||food.lie==1||food.lie==88)continue;
else if((food.lie>=28&&food.lie<=60)&&((food.hang>=0&&food.hang<=15)||(food.hang>=25&&food.hang<=41)))continue;
else if((food.hang>=13&&food.hang<=27)&&((food.lie>=0&&food.lie<=15)||(food.lie>=73&&food.hang<=89)))continue;
else if(food.hang>=18&&food.hang<=22&&food.lie>=42&&food.lie<=46)continue;
break;
}
}
void getFood2(){
while(1){
food2.hang=rand()%39+1;
food2.lie=rand()%87+1;
if(food2.hang==1||food2.hang==40||food2.lie==1||food2.lie==88)continue;
else if((food2.lie>=28&&food2.lie<=60)&&((food2.hang>=0&&food2.hang<=15)||(food2.hang>=25&&food2.hang<=41)))continue;
else if((food2.hang>=13&&food2.hang<=27)&&((food2.lie>=0&&food2.lie<=15)||(food2.lie>=73&&food2.hang<=89)))continue;
else if(food2.hang>=18&&food2.hang<=22&&food2.lie>=42&&food2.lie<=46)continue;
break;
}
}
int isDie(){
if(head->hang<1||head->hang>40||head->lie<1||head->lie>88)return 1;
else if((head->lie==29||head->lie==59)&&((head->hang>=1&&head->hang<=14)||(head->hang>=26&&head->hang<=40)))return 1;
else if((head->hang==14||head->hang==26)&&((head->lie>=1&&head->lie<=14)||(head->lie>=29&&head->lie<=59)||(head->lie>=74&&head->lie<=88)))return 1;
else if((head->hang>=14&&head->hang<=26)&&(head->lie==14||head->lie==74))return 1;
else if(head->hang>=19&&head->hang<=21&&head->lie>=43&&head->lie<=45)return 1;
Psnake tmp=head->next;
while(tmp!=NULL){
if(head->hang==tmp->hang&&head->lie==tmp->lie){
return 1;
}
tmp=tmp->next;
}
return 0;
}
int haveFood(int i,int j){
if(food.hang==i&&food.lie==j){
return 1;
}
return 0;
}
int haveFood2(int i,int j){
if(food2.hang==i&&food2.lie==j){
return 1;
}
return 0;
}
int haveSnake(int i,int j){
Psnake tmp=head;
while(1){
if(tmp==NULL)break;
if(tmp->hang==i&&tmp->lie==j){
return 1;
}
tmp=tmp->next;
}
return 0;
}
int checkKey(){
if(abs(key)==abs(dir))return 0;
return 1;
}
void addHead(){
Psnake tmp=(Psnake)malloc(sizeof(Snake));
switch(dir){
case UP:
tmp->hang=head->hang-1;
tmp->lie=head->lie;
tmp->next=head;
if(head->next==NULL){
head=tmp;
tailforward=head;
break;
}
head=tmp;
break;
case DOWN:
tmp->hang=head->hang+1;
tmp->lie=head->lie;
tmp->next=head;
if(head->next==NULL){
head=tmp;
tailforward=head;
break;
}
head=tmp;
break;
case LEFT:
tmp->hang=head->hang;
tmp->lie=head->lie-1;
tmp->next=head;
if(head->next==NULL){
head=tmp;
tailforward=head;
break;
}
head=tmp;
break;
case RIGHT:
tmp->hang=head->hang;
tmp->lie=head->lie+1;
tmp->next=head;
if(head->next==NULL){
head=tmp;
tailforward=head;
break;
}
head=tmp;
break;
}
}
void addTail(){
if(head->next==NULL){
Psnake tmp=(Psnake)malloc(sizeof(Snake));
switch(dir){
case UP:
tmp->hang=head->hang+1;
tmp->lie=head->lie;
head->next=tmp;
tail=tmp;
tailforward=head;
tail->next=NULL;
break;
case DOWN:
tmp->hang=head->hang-1;
tmp->lie=head->lie;
head->next=tmp;
tail=tmp;
tailforward=head;
tail->next=NULL;
break;
case LEFT:
tmp->hang=head->hang;
tmp->lie=head->lie+1;
head->next=tmp;
tail=tmp;
tailforward=head;
tail->next=NULL;
break;
case RIGHT:
tmp->hang=head->hang;
tmp->lie=head->lie-1;
head->next=tmp;
tail=tmp;
tailforward=head;
tail->next=NULL;
break;
}
return;
}
Psnake tmp=(Psnake)malloc(sizeof(Snake));
if(tailforward->hang==tail->hang&&tailforward->lielie){
tmp->hang=tail->hang;
tmp->lie=tail->lie+1;
}
if(tailforward->hang==tail->hang&&tailforward->lie>tail->lie){
tmp->hang=tail->hang;
tmp->lie=tail->lie-1;
}
if(tailforward->hanghang&&tailforward->lie==tail->lie){
tmp->hang=tail->hang+1;
tmp->lie=tail->lie;
}
if(tailforward->hang>tail->hang&&tailforward->lie==tail->lie){
tmp->hang=tail->hang-1;
tmp->lie=tail->lie;
}
tail->next=tmp;
tail=tmp;
tail->next=NULL;
Psnake tmp2=head;
while(1){
if(tmp2->next==tail){
tailforward=tmp2;
break;
}
tmp2=tmp2->next;
}
}
void deleteTail(){
Psnake tmp1=tail;
free(tmp1);
tail=tailforward;
tail->next=NULL;
Psnake tmp2=head;
while(1){
if(tmp2->next==NULL){
tailforward=head;
break;
}
if(tmp2->next==tail){
tailforward=tmp2;
break;
}
tmp2=tmp2->next;
}
}
void moveSnake(){
addHead();
deleteTail();
}
void* printMap(){
getFood2();
getFood();
initSnake();
while(1){
if(key==0)pthread_exit((void*)520);
if(key==999){
key=dir=LEFT;
getFood2();
getFood();
initSnake();
score=0;
}
move(0,0);
moveSnake();
if(head->hang==food.hang&&head->lie==food.lie){
score=score+10;
addTail();
getFood();
}
if(head->hang==food2.hang&&head->lie==food2.lie){
score=score+20;
addTail();
getFood2();
}
int i=0;
for(i;i<42;i++){
int j=0;
for(j;j<90;j++){
if(i==0||i==41){
if(j>=29&&j<59)printw(" ");
else printw("OO");
if(j==88)break;
}else if((i>=1&&i<=13)||(i>=27&&i<=40)){
if(j==0||j==29||j==59||j==89)printw("O");
else{
if(haveSnake(i,j))printw("[]");
else if(haveFood(i,j))printw("XX");
else if(haveFood2(i,j))printw("$$");
else{
if(j==50)printw(" ");
else printw(" ");
}
}
}
else if(i==14||i==26){
if(j==0||j==89)printw("O");
else if((j>0&&j<=14)||(j>=74&&j<89))printw("OO");
else if(j>=29&&j<=59)printw("OO");
else{
if(haveSnake(i,j))printw("[]");
else if(haveFood(i,j))printw("XX");
else if(haveFood2(i,j))printw("$$");
else printw(" ");
}
}
else if(i>=15&&i<=25){
if(i>=19&&i<=21){
if(j==14||j==74)printw("O");
else if(j>=43&&j<=45)printw("OO");
else {
if(haveSnake(i,j))printw("[]");
else if(haveFood(i,j))printw("XX");
else if(haveFood2(i,j))printw("$$");
else printw(" ");
}
}
else{
if(j==14||j==74)printw("O");
else{
if(haveSnake(i,j))printw("[]");
else if(haveFood(i,j))printw("XX");
else if(haveFood2(i,j))printw("$$");
else printw(" ");
}
}
}
}
printw("\n");
}
printw("\n PRESS Q TO EXIT THE GAME \n");
int l=178;
if(isDie()){
while(l--){
printw("@");
}
printw("\n");
printw("@");
printw(" YOU DIED !! ");
printw("@\n");
l=178;
while(l--){
printw("@");
}
printw("\nPOWERDED BY ZTWRJ\n");
refresh();
sleep(1);
move(45,0);
printw("@ PRESS R TO RESTART THE GAME @\n");
move(48,0);
refresh();
while(1){
if(key=='r'){
key=999;
break;
}
if(key=='q'){
key=0;
pthread_exit((void*)520);
}
continue;
}
}else{
l=178;
move(44,0);
while(l--){
printw("@");
}
printw("\n");
printw("@");
l=83;
while(l--){
printw(" ");
}
printw("YOUR SCORE:%d\t",score);
l=72;
while(l--){
printw(" ");
}
printw(" @\n");
l=178;
while(l--){
printw("@");
}
printw("\nPOWERDED BY ZTWRJ\n");
refresh();
}
int qq=150000/spee;
usleep(qq);
}
}
// void* getInput(){
// while(1){
// key=getch();
// switch(key){
// case KEY_UP:
// key=UP;
// break;
// case KEY_DOWN:
// key=DOWN;
// break;
// case KEY_LEFT:
// key=LEFT;
// break;
// case KEY_RIGHT:
// key=RIGHT;
// break;
// case 'q':
// printw("game over\n");
// key=0;
// refresh();
// sleep(5);
// pthread_exit((void*)1314);
// break;
// default:
// key=dir;
// break;
// }
// if( checkKey()){
// dir=key;
// }
// }
// }
int main(){
initscr();
keypad(stdscr,1);
noecho();
move(22,74);
printw("WELCOME TO THE GLUTTONOUS SNAKE\n");
refresh();
sleep(3);
move(22,74);
printw(" PRESS K TO START THE GAME\n");
refresh();
char k=0;
pthread_t t1;
while(1){
k=getch();
if(k=='k'){
int tm;
move(22,75);
printw(" SELECT THE SNAKE'S SPEED\n");
move(23,75);
printw(" INPUT 1 TO SET SLOW SPEED\n");
move(24,75);
printw(" INPUT 2 TO SET NORMAL SPEED\n");
move(25,75);
printw(" INPUT 3 TO SET FAST SPEED\n");
refresh();
while(1){
tm=getch();
if(!(tm=='1'||tm=='2'||tm=='3')){
continue;
}
if(tm=='1')spee=1;
if(tm=='2')spee=2;
if(tm=='3')spee=3;
break;
}
pthread_create(&t1,NULL,printMap,NULL);
break;
}
else continue;
}
// pthread_t t2;
// pthread_create(&t2,NULL,getInput,NULL);
//tiao su du
while(1){
if(spee==3) usleep(50000);
if(spee==2) usleep(75000);
if(spee==1) usleep(150000);
key=getch();
switch(key){
case KEY_UP:
key=UP;
break;
case KEY_DOWN:
key=DOWN;
break;
case KEY_LEFT:
key=LEFT;
break;
case KEY_RIGHT:
key=RIGHT;
break;
case 'r':
break;
case 'q':
key=0;
sleep(1);
move(42,0);
printw(" SOON CLOSE THE GAME ");
move(48,0);
refresh();
sleep(2);
break;
default:
key=dir;
break;
}
if(key==0){
move(42,0);
printw(" FIVE SECONDS TO EXIT ");
move(48,0);
refresh();
sleep(2);
int n=5;
while(n--){
move(42,0);
printw(" %d ",n+1);
move(48,0);
refresh();
sleep(1) ;
}
move(42,0);
printw(" GOODBYE! ");
move(48,0);
refresh();
sleep(1) ;
break;
}if(key!='r'){
if( checkKey()){
dir=key;
}
}
}
endwin();
}
#define命令是C语言中的一个宏定义命令,它用来将一个标识符(非数字)定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本,一般宏名为大写。
在预处理阶段会宏替换,在程序运行的时候,已经发生了宏替换,所有的宏名会被替换为替换文本
如果需要宏替换后程序能正常运行那么就需要保证宏替换后的代码符合语言规范
#undef [宏名]
取消宏定义
#define M 10
程序中所有的M在运行时会被替换为10;
#define M 10
#define N M*M
在运行时,会多次宏替换,N会被最终替换为10*10
#define XXX(m) m+3
编写代码时,写XXX(m),可以在将m传参为另外的变量,在运行的时候,也会被替换为传参变量+3
#define XXX(m) #m
编写代码时,可以将m传参为字符串或者字符,在运行的时候XXX(m),会被替换为m对应的字符串
#define XXX(m) @#m
编写代码时,可以将m传参字符,在运行的时候XXX(m),会被替换为m对应的字符
#define XXX(a,b) a##b
#define PJ(m) #m
XXX(a,b)可以把a文本和b文本拼接在一起。比如PJ(XXX(h,ello)),就等效于"hello",拼接后的文本如果需要使用,必须该文本已经存在。
#define XXX(m) printf("m");或者#define XXX(m) {printf("m");} 有多句话的时候可以括起来
可以把XXX(m),m传参后,当成printf函数,输出m
总之:替换文本可以是标识符,可以是数字,也可以是一段话,如果宏名包含(形参),调用时还可以传参,具体运行结果看宏名替换为替换文本运行后的结果