目录
一.ncurses库基本使用方法
输入输出的应用:
二 构建地图:
三 蛇身体:
1.先让蛇头显示在地图里:
2.显示整个蛇身体
1.测试
2.动态创建链表
3.让蛇移动
1.先试试向右移动
2.move() 函数
3.全向移动的完善,撞墙找死,咬自己自杀和退出游戏
4.自行开始游走
1.Linux线程
2.实现游走:
5.食物
1.简单实现
2.实现食物的随机位置
6.完结
函数类似原版c库
# include
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
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
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
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();
}
使用链表结构体
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 line
if(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 location
else{
printw("| ");
for(y=1;y<19;y++){
//Snake loction
if(x==node1.X && y==node1.Y){
printw("[]");
}
//empty pleace
else{
printw(" ");
}
}
printw(" |");
printw("\n");
}
}
}
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 pleace
else{
printw(" ");
}
................
}
int main(){
initNcurses();
//connect nodes to be the snake
head.next = &node2;
head.next->next = &node3;
map();
getch();
endwin();
}
如此创建写法太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 list
struct Snake *p;
while(head != NULL){
p =head;
head = head->next;
free(p);
}
//creat new list
head = (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;
..........
效果相同
原理很简单,蛇在地图上移动就是生成一个新位置的节点,然后让head指向新的节点,然后删除尾节点。向右,Y + 1 即可。注意循环里添加一个map(),来刷新地图。
void snakeMove(){
//add new head
struct Snake *p = (struct Snake *)malloc(sizeof(struct Snake));
p->X = head->X;
p->Y = head->Y + 1;
p->next = head;
head = p;
//remove tail
while(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();
}
但是这样刷新地图不像话,这时可以调用一个新的函数
move(0,0);
到 map() 函数里即可,这样就有游戏的样子了!!
全向移动的代码很简单,设置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 head
struct 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
while(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();
}
一直手动游走肯定不行,没法玩了,正常while(1)加上refresh(),sleep(),可以让蛇自己动,但是和输入函数冲突,要等输入才能动。
所以这里,引入多线程,一起运行,就不冲突了。
pthread_create(&th1(线程声明符号),NULL(一般是null,先不管他),func1(函数名),NULL(函数参数));
Linux线程:注意函数定义要*
#include
#include
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
#include
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;
}
主要在于调整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博客
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 loction
if(snakeLocation(x,y)){
printw("[]");
}
//food location
else if(x == food.X && y == food.Y){
printw("##");
}
//empty pleace
else{
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 food
if(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);
}
食物不能固定位置不动,被吃了要换随机位置
这里开始引入随机函数
随机数大小不定,取余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 food
if(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来统计分数。
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
#include
#include
//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 list
struct Snake *p = head;
while(head != NULL){
p = head;
head = head->next;
free(p);
}
//creat new list
head = (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 head
struct 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 food
if(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 line
if(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 location
else{
printw("| ");
for(y=1;y<19;y++){
//Snake loction
if(snakeLocation(x,y)){
printw("[]");
}
//food location
else if(x == food.X && y == food.Y){
printw("##");
}
//empty pleace
else{
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();
}