本文主要是介绍Linux+C语言:基于ncurses库的贪吃蛇游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一.ncurses库基本使用方法
输入输出的应用:
二 构建地图:
三 蛇身体:
1.先让蛇头显示在地图里:
2.显示整个蛇身体
1.测试
2.动态创建链表
3.让蛇移动
1.先试试向右移动
2.move() 函数
3.全向移动的完善,撞墙找死,咬自己自杀和退出游戏
4.自行开始游走
1.Linux线程
2.实现游走:
5.食物
1.简单实现
2.实现食物的随机位置
6.完结
一.ncurses库基本使用方法
函数类似原版c库
# include <curses.h>
int main(){initscr(); //ncurses界面的初始化函数/* noecho(); //时输入不显示在交互界面中cbreak(); */printw("This is a ncurses window!"); //在ncurse模式下的打印函数getch(); //等待用户输入endwin(); //退出程序,调用函数来恢复shell终端显示,否则shell终端字乱码}
运行
gcc 【文件名】 -lcurses
如 gcc snake2.c -lpthread -lcurses
输入输出的应用:
#include <curses.h>
int main(){initscr();char c;while(c != 'q'){c = getch();printw("It is %c\n",c);}endwin();
}
直接输入不了 上下左右键这种。
进入库里可以看见 vi /usr/include/curses.h
#define KEY_CODE_YES 0400 /* A wchar_t contains a key code */
#define KEY_MIN 0401 /* Minimum curses key */
#define KEY_BREAK 0401 /* Break key (unreliable) */
#define KEY_SRESET 0530 /* Soft (partial) reset (unreliable) */
#define KEY_RESET 0531 /* Reset or hard reset (unreliable) */
/** These definitions were generated by /build/buildd/ncurses-5.9/include/MKkey_defs.sh /build/buildd/ncurses-5.9/include/Caps*/
#define KEY_DOWN 0402 /* down-arrow key */
#define KEY_UP 0403 /* up-arrow key */
#define KEY_LEFT 0404 /* left-arrow key */
#define KEY_RIGHT 0405 /* right-arrow key */
#define KEY_HOME 0406 /* home key */
#define KEY_BACKSPACE 0407 /* backspace key */
#define KEY_F0 0410 /* Function keys. Space for 64 */
#define KEY_F(n) (KEY_F0+(n)) /* Value of function key n */
#define KEY_DL 0510 /* delete-line key */
#define KEY_IL 0511 /* insert-line key */
#define KEY_DC 0512 /* delete-character key */
#define KEY_IC 0513 /* insert-character key */
#define KEY_EIC 0514 /* sent by rmir or smir in insert mode */
#define KEY_CLEAR 0515 /* clear-screen or erase key */
#define KEY_EOS 0516 /* clear-to-end-of-screen key */
#define KEY_EOL 0517 /* clear-to-end-of-line key */
#define KEY_SF 0520 /* scroll-forward key */
#define KEY_SR 0521 /* scroll-backward key */
#define KEY_NPAGE 0522 /* next-page key */
#define KEY_PPAGE 0523 /* previous-page key */
#define KEY_STAB 0524 /* set-tab key */
#define KEY_CTAB 0525 /* clear-tab key */
#define KEY_CATAB 0526 /* clear-all-tabs key */
#define KEY_ENTER 0527 /* enter/send key */
#define KEY_PRINT 0532 /* print key */
#define KEY_LL 0533 /* lower-left key (home down) */
#define KEY_A1 0534 /* upper left of keypad */
#define KEY_A3 0535 /* upper right of keypad */
#define KEY_B2 0536 /* center of keypad */
#define KEY_C1 0537 /* lower left of keypad */
#define KEY_C3 0540 /* lower right of keypad */
#define KEY_BTAB 0541 /* back-tab key */
#define KEY_BEG 0542 /* begin key */
#define KEY_CANCEL 0543 /* cancel key */
#define KEY_CLOSE 0544 /* close key */
#define KEY_COMMAND 0545 /* command key */
#define KEY_COPY 0546 /* copy key */
#define KEY_CREATE 0547 /* create key */
#define KEY_END 0550 /* end key */
#define KEY_EXIT 0551 /* exit key */
#define KEY_FIND 0552 /* find key */
#define KEY_HELP 0553 /* help key */
#define KEY_MARK 0554 /* mark key */
#define KEY_MESSAGE 0555 /* message key */
#define KEY_MOVE 0556 /* move key */
#define KEY_NEXT 0557 /* next key */
#define KEY_OPEN 0560 /* open key */
#define KEY_OPTIONS 0561 /* options key */
#define KEY_PREVIOUS 0562 /* previous key */
#define KEY_REDO 0563 /* redo key */
#define KEY_REFERENCE 0564 /* reference key */
#define KEY_REFRESH 0565 /* refresh key */
#define KEY_REPLACE 0566 /* replace key */
#define KEY_RESTART 0567 /* restart key */
#define KEY_RESUME 0570 /* resume key */
#define KEY_SAVE 0571 /* save key */
#define KEY_SBEG 0572 /* shifted begin key */
#define KEY_SCANCEL 0573 /* shifted cancel key */
#define KEY_SCOMMAND 0574 /* shifted command key */
#define KEY_SCOPY 0575 /* shifted copy key */
#define KEY_SCREATE 0576 /* shifted create key */
#define KEY_SDC 0577 /* shifted delete-character key */
#define KEY_SDL 0600 /* shifted delete-line key */
#define KEY_SELECT 0601 /* select key */
#define KEY_SEND 0602 /* shifted end key */
#define KEY_SEOL 0603 /* shifted clear-to-end-of-line key */
#define KEY_SEXIT 0604 /* shifted exit key */
#define KEY_SFIND 0605 /* shifted find key */
#define KEY_SHELP 0606 /* shifted help key */
#define KEY_SHOME 0607 /* shifted home key */
#define KEY_SIC 0610 /* shifted insert-character key */
#define KEY_SLEFT 0611 /* shifted left-arrow key */
#define KEY_SMESSAGE 0612 /* shifted message key */
#define KEY_SMOVE 0613 /* shifted move key */
#define KEY_SNEXT 0614 /* shifted next key */
#define KEY_SOPTIONS 0615 /* shifted options key */
#define KEY_SPREVIOUS 0616 /* shifted previous key */
#define KEY_SPRINT 0617 /* shifted print key */
#define KEY_SREDO 0620 /* shifted redo key */
#define KEY_SREPLACE 0621 /* shifted replace key */
#define KEY_SRIGHT 0622 /* shifted right-arrow key */
#define KEY_SRSUME 0623 /* shifted resume key */
#define KEY_SSAVE 0624 /* shifted save key */
#define KEY_SSUSPEND 0625 /* shifted suspend key */
#define KEY_SUNDO 0626 /* shifted undo key */
#define KEY_SUSPEND 0627 /* suspend key */
#define KEY_UNDO 0630 /* undo key */
#define KEY_MOUSE 0631 /* Mouse event has occurred */
#define KEY_RESIZE 0632 /* Terminal resize event */
#define KEY_EVENT 0633 /* We were interrupted by an event */#define KEY_MAX 0777 /* Maximum key value is 0633 */
找到对应的数字之后这样写:
#include <curses.h>
int main(){int key;initscr();keypad(stdscr,1);printw("Which dirction? (Input BACKSPACE to exit)\n");while(key != 0407){key = getch();switch(key){case 0402:printw("DONE\n");break;case 0403:printw("UP\n");break;case 0404:printw("LEFT\n");break;case 0405:printw("RIGHT\n");break;}}endwin();
}
二 构建地图:
实现地图20*20大小
# include <curses.h>void initNcurses()
{initscr();keypad(stdscr,1);
}void map()
{int x;int y;for(x=0;x<20;x++){if(x==0){for(y=0;y<20;y++){printw("--");}printw("\n");}else if(x==19){for(y=0;y<20;y++){printw("--");}printw("\n");}else{printw("| ");for(y=1;y<19;y++){printw(" ");}printw(" |");printw("\n");}}
}int main(){initNcurses();map();getch();endwin();
}
三 蛇身体:
使用链表结构体
1.先让蛇头显示在地图里:
struct Snake
{int X;int Y;struct Snake *next;
};struct Snake node1 = {2,4,NULL};void map()
{int x;int y;for(x=0;x<20;x++){//first lineif(x==0){for(y=0;y<20;y++){printw("--");}printw("\n");}//last line else if(x==19){for(y=0;y<20;y++){printw("--");}printw("\n");}//else locationelse{printw("| ");for(y=1;y<19;y++){//Snake loctionif(x==node1.X && y==node1.Y){printw("[]");}//empty pleaceelse{printw(" ");}}printw(" |");printw("\n");}}
}
2.显示整个蛇身体
1.测试
struct Snake head = {2,4,NULL};
struct Snake node2 = {2,3,NULL};
struct Snake node3 = {2,2,NULL};int snakeLocation(int x,int y)
{struct Snake *p;p = &head;while(p != NULL){if(x == p->X && y == p->Y){return 1;}p = p->next;}return 0;
}
void map()
{...............if(snakeLocation(x,y)){printw("[]");}//empty pleaceelse{printw(" ");}................
}int main(){initNcurses();//connect nodes to be the snakehead.next = &node2;head.next->next = &node3;map();getch();endwin();
}
2.动态创建链表
如此创建写法太low了;现在动态创建链表;注意一直开始游戏一直开辟空间创建列表内存会积累,要清理
struct Snake *head;
struct Snake *tail;struct Snake *head;
int direction = KEY_RIGHT;void addNode(){struct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));p->X = head->X;p->Y = head->Y + 1;p->next = head;head = p;
}void initSnake(){//clear old liststruct Snake *p;while(head != NULL){p =head;head = head->next;free(p);}//creat new listhead = (struct Snake *)malloc(sizeof(struct Snake));head->X = 2;head->Y = 2;head->next = NULL;addNode();addNode();
}int snakeLocation(int x,int y)
{struct Snake *p;p = head;
..........
效果相同
3.让蛇移动
1.先试试向右移动
原理很简单,蛇在地图上移动就是生成一个新位置的节点,然后让head指向新的节点,然后删除尾节点。向右,Y + 1 即可。注意循环里添加一个map(),来刷新地图。
void snakeMove(){
//add new headstruct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));p->X = head->X;p->Y = head->Y + 1;p->next = head;head = p;//remove tailwhile(p->next->next != NULL){p = p->next;}struct Snake *p2 = p->next;p->next =NULL;}
......
int main(){int direction;initNcurses();initSnake();map();while(1){dircetion = getch();if(dirction == KEY_RIGHT){snakeMove();map();}}endwin();
}
2.move() 函数
但是这样刷新地图不像话,这时可以调用一个新的函数
move(0,0);
到 map() 函数里即可,这样就有游戏的样子了!!
3.全向移动的完善,撞墙找死,咬自己自杀和退出游戏
全向移动的代码很简单,设置xy为不同移动方向对应的横轴纵轴增量,按照输入分别确定x和y。
这里初始定义一个666,下面就可以判断传入的参数是否符合上下左右键,不符合就不进行移动,避免错误输入导致链表节点丢失。
void snakeMove(int dir){int x = 666;int y;switch(dir){case KEY_RIGHT:y = 1;x = 0;break;case KEY_LEFT:y = -1;x = 0;break;case KEY_UP:y = 0;x = -1;break;case KEY_DOWN:y = 0;x = 1;break;}if(x != 666){//add new headstruct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));p->X = head->X + x;p->Y = head->Y + y;p->next = head;head = p;//remove tailwhile(p->next->next != NULL){p = p->next;}struct Snake *p2 = p->next;p->next = NULL;free(p2);}}
写一个死亡判断函数dead(),返回1表示死了,0没死
头撞墙会死,头咬到自己身子也会死
int Dead(){struct Snake *p;p = head;
//if touch the wall?if(p->X == 0 || p->X == 19 || p->Y == 0 || p->Y == 19){return 1;}
//if eat itself?p = p->next;while(p != NULL){if(p->X == head->X && p->Y == head->Y){return 1;}p = p->next;}return 0;
}
main函数:
int main(){int direction;initNcurses();initSnake();map();while(direction != KEY_BACKSPACE){direction = getch();snakeMove(direction);if(Dead()){break;}map();}endwin();
}
4.自行开始游走
一直手动游走肯定不行,没法玩了,正常while(1)加上refresh(),sleep(),可以让蛇自己动,但是和输入函数冲突,要等输入才能动。
1.Linux线程
所以这里,引入多线程,一起运行,就不冲突了。
pthread_create(&th1(线程声明符号),NULL(一般是null,先不管他),func1(函数名),NULL(函数参数));
Linux线程:注意函数定义要*
#include <stdio.h>
#include <pthread.h>void* func1(){while(1){puts("func1");sleep(1);}
}void* func2(){while(1){puts("func2");sleep(1);}
}int main(){pthread_t th1;pthread_t th2;pthread_create(&th1,NULL,func1,NULL);pthread_create(&th2,NULL,func2,NULL);while(1);return 0;
}
运行代码要加后缀 -lpthread
可以改一下代码,没必要两条独立线程,其实一条就够了
#include <stdio.h>
#include <pthread.h>void* func1(){while(1){puts("func1");sleep(1);}
}void func2(){while(1){puts("func2");sleep(1);}
}int main(){pthread_t th1;//pthread_t th2;pthread_create(&th1,NULL,func1,NULL);//pthread_create(&th2,NULL,func2,NULL);func2();while(1);return 0;
}
2.实现游走:
主要在于调整main函数,
首先把方向改为全局变量,方便一些,
这里加了个简单的开始功能,以防运行反应不过来
再定义一个全局变量用于判断游戏是否继续,
增加了生命值全局变量
int main(){//int direction;initNcurses();initSnake();map();printw("Press any key to Start\n");getch();pthread_t t1;pthread_t t2;pthread_create(&t1,NULL,run,NULL);pthread_create(&t2,NULL,getKey,NULL); while(Continue);endwin();
}
两个线程函数,
加一个变量Continue判断游戏是否继续,
死亡后改为直接复活,减少一条命,
在方向输入获取做了充足筛选避免了错误输入和不合理的方向(180度转头)
int Continue = 1;void* run(){while(Continue){snakeMove();if(Dead()){life--;if(life == 0){Continue = 0;break;}direction = KEY_RIGHT;initSnake(); }map();refresh();usleep(200000);}
}void* getKey(){while(Continue){int key;key = getch();int K;int D;if(key == KEY_RIGHT || key == KEY_LEFT || key == KEY_UP || key == KEY_DOWN){K = key == KEY_RIGHT || key == KEY_LEFT;D = direction == KEY_RIGHT || direction == KEY_LEFT;if(K!=D){direction = key;} }else if (key == KEY_BACKSPACE){Continue = 0;}}
}
其它函数看情况微调,不必要的判断都删掉。
运行次数多了有个新问题,终端窗口总是莫名其妙卡bug
其中有一种状况会导致终端打字看不见
用
stty echo
(26条消息) Linux的命令行打字不显示咋办?_weixin_33725722的博客-CSDN博客
5.食物
1.简单实现
struct Snake food;
void initFood(){int x = 6;int y = 6;food.X = x;food.Y = y;
}
对map()函数增加食物显示
for(y=1;y<19;y++){//Snake loctionif(snakeLocation(x,y)){printw("[]");}//food locationelse if(x == food.X && y == food.Y){printw("##");}//empty pleaceelse{printw(" ");}}
在snakeMove()最后增加一个吃到食物就变长的函数
if(head->X == food.X && head->Y == food.Y){addNode(); }
关于变长的函数addNode()也要做一些修改,不能只向右增加
void addNode(){struct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));int x;int y;switch(direction){case KEY_RIGHT:y = 1;x = 0;break;case KEY_LEFT:y = -1;x = 0;break;case KEY_UP:y = 0;x = -1;break;case KEY_DOWN:y = 0;x = 1;break;}p->X = head->X + x;p->Y = head->Y + y;p->next = head;head = p;
}
这里addNode()是加长头部,但是如果食物出现在边界,直接加头容易噶,不如改成snakeMove()内不删除尾部,我这里就先改回去了。
还是修改移动函数
//remove tail if not touch foodif(head->X != food.X || head->Y != food.Y){while(p->next->next != NULL){p = p->next;}struct Snake *p2 = p->next;p->next = NULL;free(p2);}
2.实现食物的随机位置
食物不能固定位置不动,被吃了要换随机位置
这里开始引入随机函数
随机数大小不定,取余19,可以限制大小在0 ~ 18 之内,再排除一下0
struct Snake food;
void initFood(){int x = rand()%19;int y = rand()%19;if(x == 0){x++;}if(y == 0){y++;}food.X = x;food.Y = y;
}
最后在死亡后重置时也重置食物位置,
if(Dead()){life--;if(life == 0){Continue = 0;break;}direction = KEY_RIGHT;initSnake();initFood(); }
在移动函数里最后的去尾判断加一个else
//remove tail if not touch foodif(head->X != food.X || head->Y != food.Y){while(p->next->next != NULL){p = p->next;}struct Snake *p2 = p->next;p->next = NULL;free(p2);}else{initFood(); }
6.完结
至此,游戏基本完成,可以在添加一些其它小功能,例如穿墙会出现在另一端等等
我这里添加一个得分score来统计分数。
main函数里,一个新语句 join 暂停线程,避免线程和主线程输入冲突
while(Continue);pthread_join(t1,NULL);pthread_join(t2,NULL);printw("Your Max Score is ' %d '\n",scoreMax);getch();endwin();
全部代码如下
gcc Snake.c -lpthread -lcurses
即可运行
代码有两百多行一共,比较繁琐还请见谅。
#include <curses.h>
#include <stdlib.h>
#include <pthread.h>//intialize the direction
int direction = KEY_RIGHT;
//intialize snake's life
int life = 3;
//score that player get
int score = 0;
int scoreMax = 0;void initNcurses()
{initscr();keypad(stdscr,1);
}struct Snake
{int X;int Y;struct Snake *next;
};struct Snake *head;void addNode(){struct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));p->X = head->X;p->Y = head->Y + 1;p->next = head;head = p;
}void initSnake(){//clear old liststruct Snake *p = head;while(head != NULL){p = head;head = head->next;free(p);}//creat new listhead = (struct Snake *)malloc(sizeof(struct Snake));head->X = 2;head->Y = 2;head->next = NULL;addNode();addNode();addNode();addNode();
}struct Snake food;
void initFood(){int x = rand()%19;int y = rand()%19;if(x == 0){x++;}if(y == 0){y++;}food.X = x;food.Y = y;
}void snakeMove(){int x;int y;switch(direction){case KEY_RIGHT:y = 1;x = 0;break;case KEY_LEFT:y = -1;x = 0;break;case KEY_UP:y = 0;x = -1;break;case KEY_DOWN:y = 0;x = 1;break;} //add new headstruct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));p->X = head->X + x;p->Y = head->Y + y;p->next = head;head = p;//remove tail if not touch foodif(head->X != food.X || head->Y != food.Y){while(p->next->next != NULL){p = p->next;}struct Snake *p2 = p->next;p->next = NULL;free(p2);}else{initFood();score++; }
}int Dead(){struct Snake *p;p = head;//if touch the wall?if(p->X == 0 || p->X == 19 || p->Y == 0 || p->Y == 19){return 1;}//if eat itself?p = p->next;while(p != NULL){if(p->X == head->X && p->Y == head->Y){return 1;}p = p->next;}return 0;
}int snakeLocation(int x,int y)
{ struct Snake *p;p = head;while(p != NULL){if(x == p->X && y == p->Y){return 1;}p = p->next;}return 0;
}void map()
{int x;int y;move(0,0);printw("Impot 'BACKSPACE' to exit.\n");printw("Import 'UP DOWN RIGHT LEFT' to move.\n");for(x=0;x<20;x++){//first lineif(x==0){for(y=0;y<20;y++){printw("--");}printw("\n");}//last line else if(x==19){for(y=0;y<20;y++){printw("--");}printw("\n");}//else locationelse{printw("| ");for(y=1;y<19;y++){//Snake loctionif(snakeLocation(x,y)){printw("[]");}//food locationelse if(x == food.X && y == food.Y){printw("##");}//empty pleaceelse{printw(" ");}}printw(" |");printw("\n");}}printw("life: ' %d '; Score: ' %d '\n",life,score);printw("food Loction: ' %d , %d '\n",food.X,food.Y);
}int Continue = 1;int revive(){if(Dead()){life--;if(life == 0){Continue = 0;return 1;}direction = KEY_RIGHT;initSnake();initFood();if(score > scoreMax){scoreMax = score; }score = 0;}return 0;}void* run(){while(Continue){snakeMove();if(revive()){break;}map();refresh();usleep(150000);}
}void* getKey(){while(Continue){int key;key = getch();int K;int D;if(key == KEY_RIGHT || key == KEY_LEFT || key == KEY_UP || key == KEY_DOWN){K = key == KEY_RIGHT || key == KEY_LEFT;D = direction == KEY_RIGHT || direction == KEY_LEFT;if(K!=D){direction = key;} }else if (key == KEY_BACKSPACE){Continue = 0;}}
}int main(){//int direction;initNcurses();initSnake();initFood();map();printw("Press any key to Start\n");getch();pthread_t t1;pthread_t t2;pthread_create(&t1,NULL,run,NULL);pthread_create(&t2,NULL,getKey,NULL); while(Continue);pthread_join(t1,NULL);pthread_join(t2,NULL);printw("Your Max Score is ' %d '\n",scoreMax);getch();endwin();
}
这篇关于Linux+C语言:基于ncurses库的贪吃蛇游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!