贪吃蛇项目的意义
- 承上启下:从C语言基础的学习:数据结构链表基础、C变量、流程控制、函数、指针、结构体等。过渡到Linux系统编程:文件编程、进程、线程、通信、第三方等。
Linux终端图形库curses
curses的名字起源于"cursor optimization",即光标优化。它最早由美国伯克利大学的Bill Joy和Ken Arnold编写的,用来处理一个游戏rogue的屏幕显示。后来贝尔实验室的Mark Horton在system III Unix中重新编写了curses。
现在几乎所有的Unix,Linux操作系统都带了curses函数库,curses也加入了对鼠标的支持,一些菜单和面板的处理。可以说,curses是Linux终端图形编程的不二选择。
#include <curses.h>int main()
{initscr();//ncurse界面的初始化函数printw("this is a curses window\n");//ncurse模式下的printfgetch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码,坏掉return 0;
}
-
绘制地图
void init_map(struct snake_node s) {int hang;int lie;for(hang=0;hang<20;hang++){if(hang == 0 ){for(lie=0;lie<20;lie++){printw("--");}printw("\n");}for(lie=0;lie<20;lie++){if(lie == 0 || lie == 19){printw("|");}else{printw(" ");}}if(hang == 19 ){printw("\n");for(lie=0;lie<20;lie++){printw("--");}}}printw("This is Snaker"); }
效果:
-
初始化贪吃蛇身体
void add_node()//增加一个节点 {struct snake_node *new = (struct snake_node *)malloc(sizeof(struct snake_node));new->hang = tail->hang;new->lie = tail->lie + 1;new->next = NULL;tail->next = new;tail = new; } void init_sanke()//初始化身体 {head = (struct snake_node*)malloc(sizeof(struct snake_node));head->hang = 5;head->lie = 5;//初始化头部的位置head->next = NULL;tail = head;add_node();add_node();//初始化身体,想长一点就加一个节点 }
-
贪吃蛇向右移动
void delet_node()//删除一个节点 {struct snake_node * p;p = head;head = head->next;free(p);//注意:删除节点需要free掉,所以创建一个p来承接原head的空间,避免内存泄漏 } void move_snake()//删掉头,加一个尾 {add_node();delet_node(); } //主函数中判断按键 while(1){dir = getch();switch(dir){case KEY_RIGHT://如果是右方向键move_snake();break;}init_map();//刷新地图,注意用:move(int x,int y)函数重置光标的位置}
-
贪吃蛇撞墙死掉
void move_snake() {add_node();delet_node(); //加入判断条件,判断tail的行和列,和墙体重合就死掉,重新生成if(tail->hang == 0 || tail->lie == 0 || tail->hang == 21 || tail->lie == 21){init_snake();} } void init_snake() { //加入判断条件,如果不是第一次创建蛇,把之前的蛇free掉,防止内存溢出struct snake_node *p;while(head != NULL){p = head;head = head->next;free(p);}head = (struct snake_node*)malloc(sizeof(struct snake_node));head->hang = 20;head->lie = 1;head->next = NULL;tail = head;add_node();add_node(); }
-
双线程实现刷新界面和响应按键
int main() {init_ncurse();init_snake();init_map();while(1){move_snake();init_map();refresh();usleep(200000);}while(1){key = getch();switch(key){case KEY_DOWN:printw("DOWN\n");break;case KEY_UP:printw("UP\n");break;case KEY_LEFT:printw("LEFT\n");break;case KEY_RIGHT:}}endwin();return 0; }
上面main函数中存在两个while(1),常规方法无法实现该函数的功能,因此引入线程。
#include <pthread.h> // 头文件pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;如:pthread_t t1; //多线程定义pthread_create(&t1,NULL,fun,NULL);参数1:传出参数,保存系统为我们分配好的线程ID参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。参数4:线程主函数执行期间所使用的参数,如要传多个参数, 可以用结构封装。使用多线程的函数必须返回指针型,如void *fun()注:gcc xxx.c -lcurses -lpthread //编译需要连接pthread库
-
线程demo
#include <stdio.h> #include <pthread.h>void * fun1() {while(1){printf("this is fun1\n");usleep(300000);} } void * fun2() {while(1){printf("this is fun2\n");usleep(300000);} } int main() {pthread_t t1;//定义线程pthread_t t2;pthread_create(&t1,NULL,fun1,NULL);//创建线程pthread_create(&t2,NULL,fun2,NULL);while(1);return 0; }
效果:
-
双线程实现刷新屏幕和按键改变方向
void* refresh_screen()//刷新屏幕的线程 {while(1){move_snake();init_map();refresh();usleep(200000);}} void* change_dir()//响应按键改变方向的线程 {while(1){dir = getch();switch(dir){case KEY_DOWN:printw("DOWN\n");break;case KEY_UP:printw("UP\n");break;case KEY_LEFT:printw("LEFT\n");break;case KEY_RIGHT:printw("RIGHT\n");break;}} } int main() {init_ncurse();init_snake();init_map();pthread_t t1;//定义两个线程pthread_t t2;pthread_create(&t1,NULL,refresh_screen,NULL);//启动这两个线程pthread_create(&t2,NULL,change_dir,NULL);while(1);//保证程序不退出endwin();return 0; }
-
贪吃蛇上下左右的移动
void add_node()//实现贪吃蛇的上下左右移动 {struct snake_node *new = (struct snake_node *)malloc(sizeof(struct snake_node));new->hang = tail->hang;switch(dir){case UP:new->hang = tail->hang - 1;new->lie = tail->lie;new->next = NULL;break;case DOWN:new->hang = tail->hang + 1;new->lie = tail->lie;new->next = NULL;break;case LEFT:new->hang = tail->hang;new->lie = tail->lie - 1;new->next = NULL;break;case RIGHT:new->hang = tail->hang ;new->lie = tail->lie + 1;new->next = NULL;break;}tail->next = new;tail = new; } void turn(int direction)//用绝对值来避免从上直接到下和从左直接到右的方向不合理转换 {if(abs(dir) != abs(direction)){dir = direction;} }
-
实物的生成和吃食物变长
void init_food()//随机生成食物 {//1---20int x = rand()%20 + 1;int y = rand()%20 + 1;food.hang = x;food.lie = y; } void move_snake() {add_node();if(tail->hang == food.hang && tail->lie == food.lie){init_food();//如果吃掉食物,长度+1,同时刷新食物位置}else{delet_node();}if(tail->hang == 0 || tail->lie == 0 || tail->hang == 21 || tail->lie == 21){init_snake();} }
-
贪吃蛇死亡
int snake_die()//贪吃蛇死亡的两种方式 {struct snake_node *p;p = head;//撞墙死亡if(tail->hang == 0 || tail->lie == 0 || tail->hang == 21 || tail->lie == 21){return 1;}//自杀,撞到身体while(p->next != NULL){if(p->hang == tail->hang && p->lie == tail->lie){return 1;}p = p->next;}return 0; } void move_snake() {add_node();if(tail->hang == food.hang && tail->lie == food.lie){init_food();}else{delet_node();}if(snake_die()){//判断是否满足死亡条件init_snake();} }
-
最终效果