linux环境下Ncurses实现贪吃蛇游戏

游戏说明: linux环境下基于Ncurses图形库的C语言小游戏。

Ncurses介绍:
Ncurses(new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。 Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库。Ncurses用得最多的地方是linux内核编译之前的内核配置,Ncurses早已淡出舞台,甚至体验感完爆Ncurses的C图形库GTK、C++图形库QT也区趋于落伍嵌入式设备上的Android 系统。这个游戏只是使用Ncurses并不以学习它为目的,主要还是通过这个游戏锻炼我们C语言的能力。

准备工作:
在ubuntu上面安装Ncurses库,输入以下指令:

sudo apt-get install libncurses5-dev

来编写第一个程序看看是否能成功运行:

#include<curses.h>
int main()
{
        initscr();//ncurses界面初始化函数
        printw("This is a ncurses window.\n");//在ncurses模式下的printf
        getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果
        endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉
        return 0;
}

运行上面那个程序时需要用到-lcurses:

gcc ncurses.c -lcurses -o ncurses  //因为需要用到curses库所以要链入curses

以上代码输出界面是:
在这里插入图片描述
Ncurses的上下左右键值:

#define KEY_DOWN 0402
#define KEY_UP   0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405
//这些是Ncurses的一些宏定义,表示上下左右键,这些数都是八进制的
/*
这些内容可以通过命令查看curses.h的文件可知
命令:cd /usr/include/curses.h
*/

代码如下:

#include<curses.h>
int main()
{
        initscr();//ncurses界面初始化函数
        printw("This is a ncurses window.\n");//在ncurses模式下的printf
        keypad(stdscr,1);//这个函数是一个函数,第一个参数是从stdscr中接受功能建,第二个是参数表示是否接收,1表示接收
        while(1){
                //char c=getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果
                int key=getch();//这里要把char类型改为int类型,因为char类型最大表示是128,而这些方向键的值(0404等)要>128,所以用int
                printw("you input :%d\n",key);
        }
        endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉
        return 0;
}

代码运行结果如下:
This is a ncurses window.
you input :259
you input :258
you input :260
you input :261
按下上下左右键,分别输出259258260261
输出的这些数字都是十进制的,分别对应宏定义中的:0403040204040405

代码可以进一步优化:

#include<curses.h>
int main()
{
        initscr();//ncurses界面初始化函数
        printw("This is a ncurses window.\n");//在ncurses模式下的printf
        keypad(stdscr,1);//这个函数是一个函数,第一个参数是从stdscr中接受功能建,第二个是参数表示是否接收,1表示接收
        while(1){
                //char c=getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果
                int key=getch();//这里要把char类型改为int类型,因为char类型最大表示是128,而这些方向键的值(0404等)要>128,所以用int
                switch(key){
                        case 0402:
                                printw("DOWN");
                                break;
                        case 0402:
                                printw("DOWN");
                                break;
                        case 0402:
                        case 0402:
                                printw("DOWN");
                                break;
                                printw("DOWN");
                                break;


                }
        }
        endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉
        return 0;
}
当然程序中的代表方向的数字也可以用宏表示。
输入上下左右键,程序运行结果如下:
This is a ncurses window.
UP
DOWN
LEFT
RIGHT

地图规划:

两个- 和一个 | 组成一个方格,下图是20×20的一个地图。
在这里插入图片描述地图代码:

#include<curses.h>

void initNcurses()
{
        initscr();
        keypad(stdscr,1);
}

void map()
{
        int hang;
        int lie;
        for(hang=0;hang<20;hang++){
                if(hang==0){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                        printw("");
                }
                if(hang>=0&&hang<=19){
                        for(lie=0;lie<=20;lie++){
                                if(lie==0||lie==20){
                                        printw("|");
                                }else{
                                        printw("  ");
                                }
                        }
                        printw("\n");
                }
                if(hang==19){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                        printw("");
                }
        }
        printw("By Feng Hui Nan.");
}
int main()
{
        initNcurses();
        map();
        getch();
        endwin();//没有这行会破坏shell终端
        return 0;
}

贪吃蛇身子节点构成:

struct Snake
{
	int hang;
	int lie;
	struct Snake*next;
};

实现蛇身子的全部显示,并向右移动:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
void initNcurses()
{
        initscr();
        keypad(stdscr,1);
}

struct Snake //贪吃蛇身子的一个结点
{
        int hang;
        int lie;
        struct Snake*next;
};
struct Snake* head=NULL;//将蛇的头节点定义为全局变量
struct Snake* tail=NULL;//将蛇的尾节点定义为全局变量,防止错误过多
int hasSnakeNode(int i,int j)
{
        struct Snake *p;
        p=head;
        while(p!=NULL){
                if(p->hang==i && p->lie==j){
                        return 1;
                }
                p=p->next;
        }
        return 0;
}
void map()
{
        int hang;
        int lie;
        move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置
        for(hang=0;hang<20;hang++){
                if(hang==0){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                        printw("");
                }
                if(hang>=0&&hang<=19){
                        for(lie=0;lie<=20;lie++){
                                if(lie==0||lie==20){
                                        printw("|");
                                }
                                else if(hasSnakeNode(hang,lie)){
                                        printw("[]");
                                }
                                else{
                                        printw("  ");
                                }
                        }
                        printw("\n");

                }
                if(hang==19){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                }
        }
        printw("By Feng Hui Nan.");
}
void addSnake()
{
        struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));
        new->hang=tail->hang;
        new->lie=tail->lie+1;
        new->next=NULL;
        tail->next=new;
        tail=new;
}
void initSnake()
{
        struct Snake*p;
        while(head!=NULL){
                p=head;
                head=head->next;//将链表的每一个节点都释放掉,指针后移
                free(p);
        }//添加这个循环的目的是在一次游戏结束后释放当局游戏创建的链表,避免内存泄露
        head=(struct Snake*)malloc(sizeof(struct Snake));
        head->hang=1;
        head->lie=1;
        head->next=NULL;
        tail=head;
        addSnake();
        addSnake();
        addSnake();
}
void deleSnake()
{
        struct Snake*p;
        p=head;
        head=head->next;
        free(p);
}
void moveSnake()
{
        addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界
        deleSnake();
        if(tail->hang==0 || tail->lie==20 || tail->lie==0 || tail->hang==20){
                initSnake();
        }
}
int main()
{
        initNcurses();
        initSnake();
        map();
        while(1){
                moveSnake();
                map();//删除后再次刷新地图显示移动后的界面
                refresh();//刷新界面函数
                //sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率
                usleep(100000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度
        }

        getch();
        endwin();//没有这行会破坏shell终端
        return 0;
}

效果图:
在这里插入图片描述遇到问题:
如何在响应方向键的同时,控制蛇的移动?好像需要两个while循环同时执行。我想到一个方法,就是用switch判断输入的方向键,然后再封装向上下左右移动的函数,在switch中进行调用,仔细思考以下发现不可行,因为在这个游戏中地图是每隔100毫秒刷新一次要不断的刷新,所以不可行。

问题解决:
linux线程即可解决该问题,我们将一个程序里的执行路线叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列。

线程demo:

#include<stdio.h>
#include<pthread.h>

void*thread(void *arg)
{
        printf("this is a thread and arg=%d.\n",*(int*)arg);
        *(int*)arg=0;
        return arg;
}
int main(int argc,char *argv[])
{
        pthread_t th;
        int ret;
        int arg=10;
        int *thread_ret=NULL;
        ret=pthread_create(&th,NULL,thread,&arg);//pthread_create这个函数是线程的创建函数
        //第一个参数th是线程的描述符,就是上边定义的pthread_t th中的th,就相当于给第三那个参数的一个ID吧
        //第二个参数一般就写NULL
        //第三个参数是做好的准备被调用的函数,最后一个参数是函数的参数
        if(ret!=0){
                printf("Creat thread error!\n");
                return -1;
        }
        printf("this is the mian process.\n");
        pthread_join(th,(void**)&thread_ret);
        //int pthread_join(pthread_t thread, void **value_ptr);
        //thread:等待退出线程的线程号。
        //value_ptr:退出线程的返回值。
        return 0;
}

自己实现线程的创建:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void* fun1()
{
        while(1){
                printf("this is func1.\n");
                sleep(1);
        }
}
void* fun2()
{
        while(1){
                printf("this is fun2.\n");
                sleep(1);
        }
}
int main()
{
        pthread_t th1;
        pthread_t th2;
        int ret1;
        int ret2;
        int *pthread_th1=NULL;
        int *pthread_th2=NULL;
        ret1=pthread_create(&th1,NULL,fun1,NULL);
        ret2=pthread_create(&th2,NULL,fun2,NULL);
        if(ret1!=0 || ret2!=0){
                printf("thread creat error.\n");
                perror("error");
                return -1;
        }
//      while(1);//在主线程等待其他线程运行完后再退出
        pthread_join(th1,(void **)&pthread_th1);
        pthread_join(th2,(void **)&pthread_th2);//这种方法也可以使主线程等待指定线程退出后才退出
                                                //th2是线程的ID
        //int pthread_join(pthread_t thread, void **value_ptr);
        //thread:等待退出线程的线程号。
        //value_ptr:退出线程的返回值。
        return 0;

}

程序总体代码:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
#include<pthread.h>

#define UP     1
#define DOWN  -1
#define RIGHT  2
#define LEFT  -2

struct Snake //贪吃蛇身子的一个结点
{
        int hang;
        int lie;
        struct Snake*next;
};

struct Snake food;
void initFood()
{
        int x=rand()%20;
        int y=rand()%20;//%20是为了保证使rand出现的随机数尽量的出现在地图范围内
        food.hang=x;
        food.lie=y;
}
void initNcurses()
{
        initscr();
        keypad(stdscr,1);
        noecho();//不要把无关的东西打印在界面上 
}


struct Snake* head=NULL;//将蛇的头节点定义为全局变量
struct Snake* tail=NULL;//将蛇的尾节点定义为全局变量,防止错误过多
int key;
int dir;

int hasFood(int i,int j)
{
                if(food.hang==i && food.lie==j){
                        return 1;
                }
        return 0;
}
int hasSnakeNode(int i,int j)
{
        struct Snake *p;
        p=head;
        while(p!=NULL){
                if(p->hang==i && p->lie==j){
                        return 1;
                }
                p=p->next;
        }
        return 0;
}
void map()
{
        int hang;
        int lie;
        move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置
        for(hang=0;hang<20;hang++){
                if(hang==0){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                        printw("");
                }
                if(hang>=0&&hang<=19){
                        for(lie=0;lie<=20;lie++){
                                if(lie==0||lie==20){
                                        printw("|");
                                }
                                else if(hasSnakeNode(hang,lie)){
                                        printw("[]");
                                }
                                else if(hasFood(hang,lie)){
                                        printw("**");
                                }
                                else{
                                        printw("  ");
                                }
                        }
                        printw("\n");

                }
                if(hang==19){
                        for(lie=0;lie<20;lie++){
                                printw("--");
                        }
                        printw("\n");
                }
        }
        printw("By Feng Hui Nan.");
}
void addSnake()
{
        struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));
        new->next=NULL;
        switch(dir){
                case UP:
                        new->hang=tail->hang-1;
                        new->lie=tail->lie;
                        break;
                case DOWN:
                        new->hang=tail->hang+1;
                        new->lie=tail->lie;
                        break;
                case LEFT:
                        new->hang=tail->hang;
                        new->lie=tail->lie-1;
                        break;
                case RIGHT:
                        new->hang=tail->hang;
                        new->lie=tail->lie+1;
                        break;


        }
        tail->next=new;
        tail=new;
}
void initSnake()//蛇的初始化函数
{
        struct Snake*p;
        dir=RIGHT;
        while(head!=NULL){
                p=head;
                head=head->next;//将链表的每一个节点都释放掉,指针后移
                free(p);
        }//添加这个循环的目的是在一次游戏结束后释放当局游戏创建的链表,避免内存泄露
        initFood();
        head=(struct Snake*)malloc(sizeof(struct Snake));
        head->hang=1;
        head->lie=1;
        head->next=NULL;
        tail=head;
        addSnake();
        addSnake();
        addSnake();
}
void deleSnake()
{
        struct Snake*p;
        p=head;
        head=head->next;
        free(p);
}

int ifSnakeDie()
{
        struct Snake*p;
        p=head;
        if(tail->hang<0 || tail->lie==20 || tail->lie==0 || tail->hang==20){
                return 1;
        }
        while(p->next!=NULL){
                if(p->hang==tail->hang && p->lie==tail->lie){
                        return 1;
                }
                p=p->next;
        }
        return 0;

}
void* moveSnake()
{
        addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界
        if(hasFood(tail->hang,tail->lie)){
                initFood();
        }
        else{
                deleSnake();
        }
        if(ifSnakeDie()){
                initSnake();
        }
}
void* refreshUi()//封装的界面刷新函数
{
        while(1){
                moveSnake();
                map();//删除后再次刷新地图显示移动后的界面
                refresh();//刷新界面函数
                //sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率
                usleep(150000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度
        }
}
void turn(int direction)//这个函数的作用是当蛇在竖直方向上运动时使上下键无效
                        //在水平方向运动时,左右方向键无效
{
        if(abs(dir)!=abs(direction)){
                dir=direction;
        }
}
void* changeDir()
{
        while(1){
                key=getch();
                switch(key){
                        case KEY_DOWN:
                                turn(DOWN);
                                break;
                        case KEY_UP:
                                turn(UP);
                                break;
                        case KEY_RIGHT:
                                turn(RIGHT);
                                break;
                        case KEY_LEFT:
                                turn(LEFT);
                                break;
                }

        }

}
int main()
{
        initNcurses();
        initSnake();
        map();
        pthread_t t1;
        pthread_t t2;
        int *pthread1=NULL;
        int *pthread2=NULL;
        pthread_create(&t1,NULL,changeDir,NULL);
        pthread_create(&t2,NULL,refreshUi,NULL);
//      pthread_join(t1,(void**)&pthread1);
//      pthread_join(t2,(void**)&pthread2);
        while(1);
        getch();
        endwin();//没有这行会破坏shell终端
        return 0;
}

编译的时时候记得要加-lpthread、-lcurses

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现贪吃蛇游戏的方法有很多种,下面是一种使用C语言Linux平台上实现的方法: 1. 首先需要安装ncurses库,该库提供了在终端中进行文本图形化操作的支持。在终端中执行以下命令安装: ``` sudo apt-get install libncurses5-dev libncursesw5-dev ``` 2. 创建一个空白的.c文件,例如snake.c,然后在该文件中编写代码实现贪吃蛇游戏。 ``` #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <ncurses.h> #define ROWS 20 #define COLS 40 #define SCORE_POS 22 #define SNAKE_INIT_LEN 3 enum direction {UP, DOWN, LEFT, RIGHT}; struct point { int x; int y; }; typedef struct point Point; int score = 0; int gameover = 0; enum direction dir = RIGHT; Point snake[ROWS * COLS]; Point food; void initialize() { initscr(); cbreak(); noecho(); curs_set(0); keypad(stdscr, TRUE); timeout(100); } void draw() { clear(); mvprintw(SCORE_POS, 0, "Score: %d", score); for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { if (i == food.y && j == food.x) { mvaddch(i, j, '*'); } else if (i == snake[0].y && j == snake[0].x) { mvaddch(i, j, 'H'); } else { int found = 0; for (int k = 1; k < SNAKE_INIT_LEN; k++) { if (i == snake[k].y && j == snake[k].x) { mvaddch(i, j, 'T'); found = 1; break; } } if (!found) { mvaddch(i, j, ' '); } } } } refresh(); } void generate_food() { srand(time(NULL)); do { food.x = rand() % COLS; food.y = rand() % ROWS; } while (food.x == snake[0].x && food.y == snake[0].y); } void move_snake() { Point head = snake[0]; switch (dir) { case UP: head.y--; break; case DOWN: head.y++; break; case LEFT: head.x--; break; case RIGHT: head.x++; break; } if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) { gameover = 1; return; } snake[0] = head; for (int i = SNAKE_INIT_LEN - 1; i > 0; i--) { snake[i] = snake[i - 1]; } if (head.x == food.x && head.y == food.y) { score++; SNAKE_INIT_LEN++; generate_food(); } for (int i = 1; i < SNAKE_INIT_LEN; i++) { if (snake[i].x == head.x && snake[i].y == head.y) { gameover = 1; break; } } } void get_input() { int key = getch(); switch (key) { case KEY_UP: if (dir != DOWN) { dir = UP; } break; case KEY_DOWN: if (dir != UP) { dir = DOWN; } break; case KEY_LEFT: if (dir != RIGHT) { dir = LEFT; } break; case KEY_RIGHT: if (dir != LEFT) { dir = RIGHT; } break; default: break; } } void cleanup() { endwin(); } int main() { initialize(); snake[0].x = COLS / 2; snake[0].y = ROWS / 2; for (int i = 1; i < SNAKE_INIT_LEN; i++) { snake[i].x = snake[i - 1].x - 1; snake[i].y = snake[i - 1].y; } generate_food(); while (!gameover) { draw(); move_snake(); get_input(); } cleanup(); printf("Game over! Your score is %d.\n", score); return 0; } ``` 3. 编译代码并运行。 ``` gcc snake.c -o snake -lncurses ./snake ``` 4. 在终端中就可以玩贪吃蛇游戏了。使用方向键控制蛇的移动,吃掉食物可以增加分数,撞到墙或自己就会游戏结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值