基于Linux的Ncurse库的贪吃蛇项目

本文介绍了如何从C语言基础出发,通过链表、变量等概念,过渡到Linux系统编程,特别关注了curses库在终端图形编程中的应用。文章详细展示了贪吃蛇游戏的实现,包括地图绘制、节点操作、多线程处理以及内存管理,展示了C语言在游戏开发中的实践过程。
摘要由CSDN通过智能技术生成

贪吃蛇项目的意义

  • 承上启下:从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;
}
  1. 绘制地图

    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");
    }                     
    

    效果:

    在这里插入图片描述

  2. 初始化贪吃蛇身体

    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();//初始化身体,想长一点就加一个节点
    }
    
  3. 贪吃蛇向右移动

    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)函数重置光标的位置
            }
    
    
  4. 贪吃蛇撞墙死掉

    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();
    }
    
    
  5. 双线程实现刷新界面和响应按键

    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库
    
    
  6. 线程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;
    }
    

    效果:

    在这里插入图片描述

  7. 双线程实现刷新屏幕和按键改变方向

    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;
    }
    
  8. 贪吃蛇上下左右的移动

    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;
            }
    }
    
  9. 实物的生成和吃食物变长

    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();
            }
    }
    
  10. 贪吃蛇死亡

    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();
            }
    }
    
  11. 最终效果

    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值