如何在Linux环境基于Ncurses编写C语言贪吃蛇?

本文介绍了一个使用C语言编写的贪吃蛇游戏,主要锻炼了C语言的结构体运用和编程思维。游戏利用ncurses库进行终端界面操作,通过多线程实现蛇的自动移动和用户输入的键位控制。在编写过程中,作者遇到了switch语句漏写break、线程函数定义以及运行时的显示问题,通过逐步调试解决了这些问题。
摘要由CSDN通过智能技术生成

这个贪吃蛇项目主要是锻炼C语言能力,多次运用到结构体,以及锻炼个人的编程思维和调试能力;

以下为贪吃蛇的代码思路:

头文件及预装载:

#include<curses.h>
#include<stdlib.h>
#include<pthread.h>
#include<stdio.h>
#define UP     1
#define DOWN  -1
#define LEFT   2
#define RIGHT -2

定义结构体以及后期用到的全局变量:

struct Snake;
{
    int hang;
    int lie;
    struct Snake *next;
};
struct Snake *head = NULL; 
struct Snake *tail = NULL;

struct Snake Food;
int key;//方向键值。
int dir;//方向键值。

初始化Ncurses:

void initNcurses()
{
    initscr();//初始化ncurses
    keypad(stdscr,1); //识别键位
    noecho(); //减少乱码显示
}

初始化蛇的位置,以及添加、删除节点

void initSnake() //初始化蛇
{         
         struct Snake *p = NULL;
         dir = RIGHT;//贪吃蛇的初始方向:右。
         while(head != NULL)
         {  
              p = head;
              head = head->next;
              free(p);
         }

         initFood();
         head = (struct Snake*)malloc(sizeof(struct Snake));
         head->hang = 3;//贪吃蛇的初始位置。
         head->lie  = 4;
         head->next = NULL; 
         tail = head;     
         addNode();  //蛇的初始状态有四个节点。
         addNode();    
         addNode();         
}

打印蛇的身子判断

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 turn(int direction) //abs函数,取键位绝对值
{
        if(abs(dir) != abs(direction))
        {
                dir =direction;
        }
}
void addNode() //蛇添加节点以及键位
{
        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 RIGHT:
                                new->hang  = tail->hang;
                                new->lie   = tail->lie  + 1;
                break;
                           case LEFT:
                                new->hang  = tail->hang;
                                new->lie   = tail->lie  - 1;
                break;
                }

        
        tail->next = new; 
        tail = new; 
}
//移动过程中除了要增加节点,还要删除节点。
void deleNode()
{
         struct Snake *p;
         p = head;
         head = head->next;
         free(p);//释放头节点内存
}

判断蛇的状态

int ifsnakeDie() //判断蛇是否撞墙或者咬自己
{
        struct Snake *p = NULL;
        p = head;
        if(tail->hang < 0||tail->lie == 20||tail->lie == 0||tail->lie == 0){ //撞墙
             return 1; 
        }
        while(p->next!=NULL){
             if(p->hang == tail->hang && p->lie == tail->lie){//咬自己
              return 1;
             }
             p=p->next;
        }
       return 0;

}
void moveSnake() //蛇的移动过程,判定蛇吃食物增长、自由行走、撞墙、吃自己
{
         addNode();
         if(hasFood(tail->hang,tail->lie)){ //如获取食物
            initFood(); //随机刷新食物
         }else{ 
            deleNode(); //正常行走,删除最尾节点
         }
         if(ifsnakeDie()){ //蛇死亡
         initSnake(); //蛇初始化重置
         }
}

初始化食物以及遍历食物

void initFood()
{
    int x = rand()%15+3; //随机函数rand();
    int y = rand()%15+3 ;
    Food.hang = x;
    Food.lie  = y;
}

int hasFood(int i,int j)
{
   if(Food.hang==i && Food.lie==j){ 
             return 1; 
             }
   return 0;
}

建立多线程,让蛇移动同时识别键位

void* refreshJieMian() //让蛇自动移动
{
   while(1){
            moveSnake();
            gamePic(); //地图遍历
            refresh(); //刷新函数
            usleep(100000); //移动间隔
  }

}

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;

           }
  }
}

遍历地图、实时打印蛇的身子、蛇的食物、

void gamePic() 
{
     int hang;
     int lie;
     move(0,0);
     for(hang=0;hang<20;hang++){
         if(hang==0){
          for(lie=0;lie<20;lie++){
              printw("--");
              }
              printw("\n");
           }
          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 Ivan, key = %d",key);
             }
        }
}

main程序调用如下:

int main()
{ 
  pthread_t t1; //创建多线程
  pthread_t t2;
  initNcurses(); //初始化Ncurses
  initSnake(); //初始化蛇
  gamePic(); //地图、蛇、食物遍历
  
  pthread_create(&t1,NULL,refreshJieMian,NULL); //线程1
  pthread_create(&t2,NULL,changeDir,NULL); //线程2

  while(1); 不让退出程序,线程3
  getch(); 
  endwin();
  return 0;
}

以上皆是完整代码,拆散开来更好理解思路;

在Ubuntu命令提示符运行的执行代码如:

gcc IvanSnakePro.c -o IvanSnakePro -lcurses -lpthread
// IvanSnakePro.c ---文件名
// -o 后面接生存文件的名字,使它变为 IvanSnakePro
// -lcurses  链接Nucurses的库
// -lpthread 链接pthread函数的库

本人写项目中遇到的问题:

  1. 比如switch里忘记写break导致找了好久问题;

  1. 在写线程函数时pthread遇到问题:记得加头文件<pthread.h>,写函数是记得它是void *shanshu();

  1. 运行时遇到代码错乱在初始化中加入noecho();函数;

总结:一步一步来,慢慢实现,及时的发现问题所在,及时调试bug

作者----Ivan

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值