【C语言】贪吃蛇实现思路详解

这篇具有很好参考价值的文章主要介绍了【C语言】贪吃蛇实现思路详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  贪吃蛇小游戏主要运用了链表和线程实现游戏的运行,三要素分别是:地图->蛇身移动、增加、撞墙和咬自己->在地图范围内随机生成食物。接下来分步实现:

1. 地图

1.1 ncurse图形库库

1.2 接收功能键

1.3 通过ncurse绘制地图

2. 蛇身

2.1 静态构造蛇身

2.2 动态构造蛇身

2.3 蛇身的移动

2.4 控制方向

2.5 完善蛇的死亡方式

3. 食物


1. 地图

1.1 ncurse图形库库

在讲地图之前,先简单介绍一下ncurse图形库,在C语言库函数中常用获取按键响应的方式主要有:scanf()、getchar()、gets()等,但是必须按键后回车才能完成接收,为了蛇身自主移动方便通过按键控制其方向,我们就要引入ncurse库实现不需要回车响应就能完成按键的接收。

当然,有人会说ncurse早就out了,更甚于完爆它的GTK、c++图形库QT也逐渐落伍,现在大部分嵌入式设备也都开始安卓系统,所以在这里我们只是简单引用一下通过它实现对链表的操作不做过多了解。

那怎么使用ncurse呢?

Ubuntu下输入指令安装:

sudo apt-get install libncurses5-dev
#include<curses.h> //调用库函数;

int main()
{
    initscr();//初始化ncurse界面;
    printw("we are into ncurse\n");//相当于printf;
    getch();等待用户输入,如果没这句话程序会直接退出,看不到运行结果;
    endwin();程序退出,通过它来恢复shell终端的显示,如果没这句话,shell终端会乱码甚至崩掉;
    return 0;
}

【C语言】贪吃蛇实现思路详解

 运行结果 :

【C语言】贪吃蛇实现思路详解

 接下来再接收一个按键:

#include<curses.h>

int main()
{
    int n;
    initscr();
    n = getch();
    printw("your input is :%d\n",n);
    getch();
    endwin();
    return 0;
}

运行结果:

【C语言】贪吃蛇实现思路详解

 按a,a的ASCII码是97,接收成功;

1.2 接收功能键

那如何获取↑ ↓ ← →功能键呢?

#include<curses.h>

int main()
{
    int key;
    initscr();
    keypad(stdscr,1);//接收功能键,1表示是
    while(1){
        key = getch();
        printw("your input is :%d\n",key);
    }
    
    getch();
    endwin();
    return 0;
}

结果如下依次输入↑ ↓ ← →:

【C语言】贪吃蛇实现思路详解

获取到使用功能键的值,方便接下来通过它来控制小蛇,O的K

1.3 通过ncurse绘制地图

很显然我们的地图可以看作一个二维数组,既然是二维数组,我们就可以用for()进行遍历打印,先构建一个20×30的数组打印地图边界(用#表示):

#include<curses.h>

int main()
{
        int i,j;
        initscr();
        for(i=0;i<20;i++){
                for(j=0;j<30;j++){
                        printw("#");
                }
                printw("\n");
        }
        getch();
        endwin();
        return 0;
}

运行结果:

【C语言】贪吃蛇实现思路详解

 地图我们只留下边框就好,中间部分需要去除一下:

#include<curses.h>
void initCury()
{
        initscr();
        keypad(stdscr,1);
        /*keypad设置了在stdscr中可以接收键盘的功能键,
        如:↑ ↓ ← → F1等*/
}

void mapGame()
{
        int i,j;
        for(i=0;i<20;i++){
                if(i == 0){
                        for(j=0;j<30;j++){
                                printw("#");
                        }
                        printw("\n");
                }//第零行,地图上侧,全部打印#
                if(i>=0 && i<=18){
                        for(j=0;j<=29;j++){
                                if(j == 0 || j == 29){
                                        printw("#");
                                }else{
                                        printw(" ");
                                }
                        }
                        printw("\n");
                }//第1~18行,列只有为0和29的时候才打印#
                if(i == 19){
                        for(j=0;j<=29;j++){
                                printw("#");
                        }
                }//第19行,同第0行
        }
        printw("\n");
}

void main()
{
        initCury();//将初始化ncurse封装
        mapGame();//将地图打印封装
        getch();
        endwin();
}

运行结果:

【C语言】贪吃蛇实现思路详解

地图 O的K。

2. 蛇身

2.1 静态构造蛇身

 我们知道蛇身始终在地图内,这里用@表示,先静态定义几个蛇身以作演示:

#include<curses.h>

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

int score = 10;

struct Snake initSnake1 = {0,14,NULL};//蛇尾,表头
struct Snake initSnake2 = {1,14,NULL};
struct Snake initSnake3 = {2,14,NULL};
struct Snake initSnake4 = {3,14,NULL};//蛇头

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

int hasNode(int x,int y)
{
        struct Snake *p;
        p = &initSnake1;//定义一个局部变量将链表头传过来
        while(p->next != NULL){
                if(p->hang < x && p->lie == y){    //判断该坐标是否有蛇身
                        return 1;
                }
                p = p->next;
        }
        return 0;
}

void mapGame()
{
        int i,j;
        for(i=0;i<20;i++){
                if(i == 0){
                        for(j=0;j<=29;j++){
                                printw("#");
                        }
                        printw("\n");
                }
                if(i>=0 && i<=18){
                        for(j=0;j<=29;j++){
                                if(j == 0 || j == 29){
                                        printw("#");
                                }
                                else if(hasNode(i,j)){
                                        printw("@");
                                        /*如果蛇身坐标等于所遍历的坐标,则打印蛇身@ */
                                }
                                else{
                                        printw(" ");
                                }
                        }
                                }
                        }
                        printw("\n");
                }
                if(i == 19){
                        for(j=0;j<=29;j++){
                                printw("#");
                        }
                }
        }
}

void main()
{
        initSnake1.next = &initSnake2;
        initSnake2.next = &initSnake3;
        initSnake3.next = &initSnake4;
        initCury();
        mapGame();
        getch();
        endwin();
}

【C语言】贪吃蛇实现思路详解

2.2 动态构造蛇身

静态蛇身不利于蛇身移动,我们来动态开辟一个蛇身:

struct Snake{
        int hang;
        int lie;
        struct Snake *next;
};//将蛇身定义为一个结构体方便移动和增加

struct Snake *head;
struct Snake *tail;
/*将head和tail定义为全局变量*/
void initSnake()
{
        head = (struct Snake*)malloc(sizeof(struct Snake));//malloc动态开辟一个head空间
        head->hang = 0;
        head->lie  = 15;
        head->next = NULL;//定义一个表头初始坐标
        tail = head;
}

2.3 蛇身的移动

 蛇身打印好那怎么让他还是动呢?这里采取的方法是将将链表头移到表尾:

【C语言】贪吃蛇实现思路详解

 既然移动要删除头节点并且向表尾增加一个节点,我们先来构造增加和删除节点的函数:

void addBody()
{
        struct Snake *newBody = (struct Snake*)malloc(sizeof(struct Snake));
        //动态开辟一个新空间存储删掉的节点
        newBody->hang = tail->hang+1;//由于初始方向我们给定向下,所以行坐标+1,纵坐标不变
        newBody->lie  = tail->lie;
        newBody->next = NULL;
        tail->next = newBody;
        tail = newBody;//重新使表尾为新节点
}

void delBody()
{
        struct Snake *p;
        p = head;
        head = head->next;//将链表头指向下一个节点,表头就独立出来了
        free(p);//释放掉旧表头
}

那如何使用构造出来的两个函数呢?

void moveBody()
{
        addBody();
        delBody();//通过不断增加表尾释放表头使蛇向下移动
        if(tail->hang==0||tail->lie==0||tail->hang==20||tail->lie==30){
                /*判断如果蛇撞墙,重新初始化蛇身,意味着游戏重新开始*/
                initSnake();
        }
}

用while(1)使用这个函数蛇身就能不断向下移动

void main()
{
        int conKey;//定义一个局部变量存储按键
        initCury();
        initSnake();
        mapGame();
        while(1){
                conKey=getch();
                if(conKey==KEY_DOWN){//KEY_DOWN是ncurse中的宏参数,即↓键
                        moveBody();
                        mapGame();
                        refresh();//每次移动坐标都要刷新一下才能显示移动的效果
                }
        }
        getch();
        endwin();
}

我们玩贪吃蛇不能靠我们自己按键来移动吧,所以要构造一个函数帮我们刷新:

void refreshPage()
{
        while(1){
                moveBody();
                mapGame();
                refresh();
                usleep(150000);//延时150毫秒
        }
}

2.4 控制方向

调用它我们就能不使用方向键让蛇自己向下移动,接下来就是通过方向键改变蛇的方向了,首先需要宏定义一下方向,其次需要定义一个全局变量dir来表示蛇运动的方向,并将值赋为DOWN也就是初始向下,然后通过按键来控制蛇的方向:

#define UP    1
#define DOWN  2
#define LEFT  3
#define RIGHT 4

int dir;//定义dir(方向)全局变量,dir初始方向为DOWN,在initSnake()中初始化

void addBody()
{
        struct Snake *newBody = (struct Snake*)malloc(sizeof(struct Snake));
        newBody->hang = tail->hang+1;
        newBody->lie  = tail->lie;
        newBody->next = NULL;
        switch(dir){    //控制蛇头方向:比如按↑,蛇头移动到链表尾上方
                case UP:
                        newBody->hang = tail->hang-1;
                        newBody->lie  = tail->lie;
                        break;
                case DOWN:
                        newBody->hang = tail->hang+1;
                        newBody->lie  = tail->lie;
                        break;
                case LEFT:
                        newBody->hang = tail->hang;
                        newBody->lie  = tail->lie-1;
                        break;
                case RIGHT:
                        newBody->hang = tail->hang;
                        newBody->lie  = tail->lie+1;
                        break;
        }
        tail->next = newBody;
        tail = newBody;
}


void contKey()
{
        while(1){
                key = getch();
                switch(key){
                        case KEY_UP:
                                printw("UP\n");
                                dir = UP;
                                break;
                        case KEY_DOWN:
                                printw("DOWN\n");
                                dir = DOWN;
                                break;
                        case KEY_LEFT:
                                printw("LEFT\n");
                                dir = LEFT;
                                break;
                        case KEY_RIGHT:
                                printw("RIGHT\n");
                                dir = RIGHT;
                                break;
                }
        }
}
                                                        150,1-8       89%

写到这里运行的话就会崩掉,因为刷新界面和控制方向存在同步关系,为了使得移动刷新的同时操作蛇改变方向就要用到线程来解决:

线程的创建:

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);

pthread_t *restrict tidp:传递一个 pthread_t 类型的指针变量,也可以直接传递某个 pthread_t 类型变量的地址。pthread_t 是一种用于表示线程的数据类型,每一个 pthread_t 类型的变量都可以表示一个线程。

const pthread_attr_t *restrict attr:通过该参数可以定制线程的属性,比如可以指定新建线程栈的大小、调度策略等。如果创建线程无特殊的要求,该值也可以是NULL,表示采用默认属性,通常都用NULL。

oid *(*start_rtn)(void *):线程需要执行的函数。创建线程,是为了让线程执行一定的任务。线程创建成功之后, 该线程就会执行start_routine函数,该函数之于线程,就如同main函数之于主线程。

void *restrict arg:传递给start_routine函数的实参,当不需要传递任何数据时,将arg赋值为NULL即可。

创建三个线程同时进行:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* fun1()
{
        while(1){
                printf("this is fun1\n");
                sleep(1);
        }
}

void* fun2()
{
        while(1){
                printf("this is fun2\n");
                sleep(1);
        }
}
void* fun3()
{
        while(1){
                printf("this is fun3\n");
                sleep(1);
        }
}

int main()
{
        pthread_t t1;
        pthread_t t2;
        pthread_t t3;
        pthread_create(&t1,NULL,fun1,NULL);
        pthread_create(&t2,NULL,fun2,NULL);
        pthread_create(&t3,NULL,fun3,NULL);
        while(1);
        return 0;
}

【C语言】贪吃蛇实现思路详解

 现在三个while就可以同时运行了,创建两个线程同时运行refreshPage()和contKey():

void main()
{
        pthread_t t1;
        pthread_t t2;

        initCury();
        initSnake();
        mapGame();
        pthread_create(&t1,NULL,refreshPage,NULL);
        pthread_create(&t2,NULL,contKey,NULL);
        while(1);

        getch();
        endwin();
}

现在就可以控制蛇上下左右移动了,但是有个bug,如果蛇头方向为下但是按上键,会发现蛇会直接往上移动,简直开了挂!这里就要用到一个函数:abs()

abs()函数用于求整数的绝对值,比如abs(-1)和abs(1)的返回值都是1;

根据abs()函数我们就可将方向进行define,按键绝对值相同时不发生方向的改变:

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

void turn(int direction)
{
        if(abs(dir) != abs(direction)){    
        /*将目前方向传入,判断按键方向绝对值是否等于目前方向,如果不等于,改变其方向,(即:dir=1,        方向向上,按↓时dir=-1,但abs(-1)=1=原先的dir,方向不发生改变,只有按←和→时,abs(dir)变为2,方向才会发生改变)*/
                dir = direction;
        }
}

void* contKey()
{
        while(1){
                key = getch();
                switch(key){
                        case KEY_UP:
                                printw("key:UP\n");
                                turn(UP);
                                break;
                        case KEY_DOWN:
                                printw("key:DOWN\n");
                                turn(DOWN);
                                break;
                        case KEY_LEFT:
                                printw("key:LEFT\n");
                                turn(LEFT);
                                break;
                        case KEY_RIGHT:
                                printw("key:RIGHT\n");
                                turn(RIGHT);
                                break;
                }
        }
        noecho();//屏蔽掉控制字符(如组合键操作)   
}

现在蛇身的控制就已经写完了。

2.5 完善蛇的死亡方式

蛇的死亡方式有两种:撞墙和咬自己,现在封装一个函数来完美实现蛇的死亡用于mapGame()的判断。

int snakeKilled()
{
        struct Snake *p;
        p = head;
        if(tail->hang<0||tail->lie==0||tail->hang==20||tail->lie==30){    //撞墙
                return 1;
        }
        while(p->next!=NULL){
                if(p->hang==tail->hang && p->lie==tail->lie){    
                /*判断tail(蛇头)是否与链表节点相等,相等就是咬到自己*/
                        return 1;
                }
                p = p->next;
        }
        return 0;
}

在moveBody()中根据该函数返回值判断蛇是否死亡就好,如果死亡重新initSnake()就实现死亡后重新开始游戏。

3. 食物

在贪吃蛇中食物是随机生成的,这里要用到rand()函数:

rand():C语言中用来产生一个随机数的函数.使用方法是rand ()% (n-m+1)+m,这个式子表示产生 [m,n]范围内的随机数。

struct Snake food;
int score=0;

void* initFood()
{
        int x = rand()%20;//行:随机在0~20行生成食物x坐标
        int y = rand()%30;//列:随即在0~30列生成食物y坐标
        while(x==0 || y==0 || x==20 || y==30){    //避免食物在墙体内部,重新随机生成食物
                x = rand()%20;
                y = rand()%30;
        }
        food.hang = x;
        food.lie = y;
        score+=1;    //每增加一个食物,得分+1
        noecho();
}

int hasFood(int x,int y)    
/*将墙内坐标传入,如果食物坐标与传入坐标相等,在mapGame()中打印$表示食物*/
{
        if(food.hang == x && food.lie == y){
                return 1;
        }
        return 0;
}

void mapGame()
{
        int i,j;
        move(0,0);
        for(i=0;i<=19;i++){
                if(i == 0){
                        for(j=0;j<=30;j++){
                                printw("#");
                        }
                        printw("\n");
                }// 0  
                if(i>=0 || i<=19){
                        for(j=0;j<=30;j++){
                                if(j == 0 || j == 30){
                                        printw("#");
                                }
                                else if(hasNode(i,j)){
                                        printw("@");
                                }
                                else if(hasFood(i,j)){
                                        printw("$");
                                }
                                else{
                                        printw(" ");
                                }
                        }
                        printw("\n");
                }
                if(i == 19){
                        for(j=0;j<=30;j++){
                                printw("#");
                        }
                }
        }
        printw("\nScore: %d$\n",score);
        printw("food.x:%d, food.y:%d\n",food.hang,food.lie);
        noecho();
}

到这里贪吃蛇小游戏就已经成功了,时不时写一个贪吃蛇有助于加强操作链表的记忆,欢迎交流分享,完整代码在专栏中可以找到。文章来源地址https://www.toymoban.com/news/detail-506186.html

到了这里,关于【C语言】贪吃蛇实现思路详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c语言及数据结构实现简单贪吃蛇小游戏

    目录 一·贪吃蛇简单介绍: 二·贪吃蛇的实现的开始准备: 2.1:欢迎界面的实现: 2.2地图的绘制: 2.3.1初始化蛇: 2.3.2初始化食物:  三·贪吃蛇的运行操作: 3.1辅助信息的打印: 3.2蛇的下一步移动操作: 3.2.1判断玩家按键情况: 3.2.2下一步遇到食物: 3.2.3下一步不是食物:

    2024年04月27日
    浏览(56)
  • 【C语言小游戏】贪吃蛇

      使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇 实现基本的功能: 贪吃蛇地图绘制 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作) 蛇撞墙死亡 蛇撞⾃⾝死亡 计算得分 蛇⾝加速、减速 暂停游戏 游戏指引页面 游戏页面 指针; 动态内存; 结构体;

    2024年01月19日
    浏览(41)
  • C语言——贪吃蛇小游戏

    目录 一、ncurse 1.1 为什么需要用ncurse: 1.2 ncurse的输入输出: 1.2.1 如何使用ncurse: 1.2.2 编译ncurse的程序: 1.2.3 测试输入一个按键ncurse的响应速度: 1.3 ncurse上下左右键获取: 1.3.1 如何查看宏定义的.h文件: 1.3.2 ncurse上下左右键获取: 二、地图规划 2.1 地图规划算法显示第一

    2024年02月07日
    浏览(42)
  • 小游戏:人生中写的第一个小游戏——贪吃蛇(C语言)

            小编开了一个关于游戏的专栏,主要是运用easyx图形库进行的。        第一章:人生中写的第一个小游戏——贪吃蛇(C语言)         这个游戏的代码我在gitee上发布了,大家如果不嫌弃,可以进入这个网址进行查看和复制:https://gitee.com/rising-sun-1。      

    2024年02月09日
    浏览(61)
  • C语言实战项目-贪吃蛇小游戏

    1.定义蛇对象、食物对象 2.初始化蛇、初始化食物 3.控制流程: ( 1)蛇头和墙壁的碰撞 (2)蛇头和蛇身体碰撞 (3)蛇头和食物碰撞 (4)移动速度增大 4.游戏效果: ( 1)蛇身增长 (2)食物消失 -- 新食物产生 (3)分数累加 (4)移动速度增大 (5)蛇的移动 (6)显示分

    2024年02月19日
    浏览(48)
  • C语言结课实战项目_贪吃蛇小游戏

    ✨✨所属专栏:C语言✨✨ ✨✨作者主页:嶔某✨✨ 游戏源代码链接: function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com) • 贪吃蛇地图绘制 • 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、

    2024年04月26日
    浏览(44)
  • 【C语言】做一个贪吃蛇小游戏,完整代码&附带视频演示

    视频演示: https://www.bilibili.com/video/BV1pt421a7Nu/?spm_id_from=333.999.0.0vd_source=82b65865be0947de29bd55efc8cdb40a 编译环境:linux(Vmware16.2.4 + Ubantu20.04.3); 小蛇🐍只能在固定的范围内活动; 可以利用键盘方向键控制小蛇🐍的前进方向; 活动范围内会随机生成食物; 小蛇🐍吃到食物,身

    2024年02月21日
    浏览(45)
  • 挑战!贪吃蛇小游戏的实现(2)

    在贪吃蛇小游戏的实现(1)中,我们学习了win32 相关的一些知识,本篇文章,博主将带领大家从0开始实现贪吃蛇小游戏! locale.h实现本地化 ,该头文件提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分,如:数字量的格式,货币量的格式,字符集以及日

    2024年02月21日
    浏览(44)
  • TypeScript实现一个贪吃蛇小游戏

    游戏效果 文件目录 准备1 :新建index.html,编写游戏静态页面 准备2 :使用less,修改样式,编写CSS 准备3: 创建4个类:食物类-Food、记分牌等级类-ScorePanel、蛇类-Snake、操控类-GameControl 准备4 :创建index.ts文件,执行游戏

    2024年01月19日
    浏览(58)
  • Android Studio实现贪吃蛇小游戏

    贪吃蛇是一款经典的街机游戏,不仅在电子游戏史上占有一席之地,也在很多人的童年回忆中留下了深刻的印象。在游戏中,玩家需要操纵一条蛇通过吃食物来增加自己的长度,同时要避免撞到墙或自己的身体。随着蛇不断吃食物,它的身体会越来越长,游戏的难度也会随之

    2024年02月05日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包