Linux+C语言:基于ncurses库的贪吃蛇游戏

编程入门 行业动态 更新时间:2024-10-09 20:23:12

Linux+C语言:基于ncurses库的<a href=https://www.elefans.com/category/jswz/34/1769263.html style=贪吃蛇游戏"/>

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库的贪吃蛇游戏

本文发布于:2024-02-07 03:11:02,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1752915.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:贪吃蛇   语言   游戏   Linux   ncurses

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!