贪吃蛇项目的意义
- 承上启下:从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模式下的printf
getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果
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---20 int 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(); } }
-
最终效果