在Linux下用C语言写贪吃蛇;

项目思路

ncurses上下左右键的获得——>贪吃蛇地图的实现——>显示贪吃蛇的完整身子——>贪吃蛇向右移动——>贪吃蛇撞墙找死——>贪吃蛇自行向右行走与页面一起刷新(利用线程解决)——>贪吃蛇四个方向的自由走位——>贪吃蛇吃饭——>贪吃蛇咬到自己撞墙找死
按照这个项目思路来设计游戏

运用ncuses库获得上下左右键

头文件#include<stdio.h>无法方便写出贪吃蛇游戏,因为如果使用<stdio.h>头文件,printf打印都要按回车,这对游戏的实现根本不可能,所以引进ncurses库。

#include<curses.h>
 //获取
 void  initcurses()
 {
       initscr();
       keypad(stdscr,1);
       noecho();
 }

打印一个20*20的坐标系作为地图

地图规划也用函数封装起来!
0和20行以–为界,0和20列以|为界,中间为空格,顺便显示蛇的身子和食物;分三步打印,首先打印第一行与第0列,然后打印1到18行,再打印19行19列;蛇的身子与食物用函数封装起来。

void gamePic()
{
    int hang;
    int lie;
    move(0,0);//ncurses的库函数,用它改变光标位置,使下一次的地图覆盖在上一次的地图上。
    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++){//这里<=20刚好对齐第一行第0列
                      if(lie==0||lie==20){
                          printw("|");
                      }else if(hadSnake(hang,lie)){
                          printw("[]");
                      }else  if(hadFood(hang,lie)){
                           printw("##");
                      }else{
                          printw("  ");
                      }
                  }
                   printw("\n");
             }
             if(hang==19){
                 for(lie=0;lie<20;lie++){
                     printw("--");
                 }
                 printw("\n");
                 printw("by xiewenhui");
            }
  }
}

显示贪吃蛇的身子

1.## 显示蛇的身子需要用到结构体和链表,先声明链表头的指针和链表尾的指针。

#include<stdlib.h>
int dir;//全局变量,通过按键控制蛇身子的移动

struct Snake
{
   int hang;
   int lie;
   struct Snake *next;
};
Struct Snake *head=NULL;//防止变成野指针内存泄漏。
Struct Snake *tail=NULL;

2.封装一个函数初始化蛇的身子;蛇的身子是通过尾插法插入;动态创建链表来插入

#include<stdlib.h>

void initSnake()
{
    struct Snake *p;
    
    dir=RIGHT;//初始化按键;
    
    while(head!=NULL){//判断蛇是否撞墙找死;然后清理之前的数据,防止内存泄漏
         p=head;
         p=p->next;
         free(p);
    }
        
    head=(struct Snake*)malloc(sizeof(struct Snake));
    head->hang=1;
    head->lie=1;
    head->next=NULL;

    tail=head;//从尾巴插入,做一个尾巴;

    addNode();
    addNode();
    addNode();
    addNode();
    addNode();
}
void addNode()
{
    struct Snake *p;
    new=(struct snake*)malloc(sizeof(struct Snake));
    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;
    }
}

在地图上显示蛇的身子,封装一个函数判断蛇身子的坐标是否与地图上的坐标对应。

int handSnake(int hang,int lie)
{
   struct Snake *p;
   p=head;
   while(p!=NULL){//如果头节点是一个空指针不用返回行列坐标,因为根本没有节点增加;只有满足非空才能置1。
         if(p->hang==hang&&p->lie==lie){
              return 1;
         }
         p=p->next;
   }
     return 0;
}

蛇的移动与死亡

蛇的移动是通过在尾部增加一个节点,然后在删除一个节点,这样保持蛇身子保持不变,然后实现蛇的移动效果。蛇的死亡有两种:一是撞墙而死,二是咬到自己而死;代码如下:

#include<curses>

void addNode()
{
    struct Snake *p;
    p=head;
    new=(struct Snake*)malloc(sizeof(struct Snake));
    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;
    }
}

void  deletNode()
{
     struct Snake *P;
     p=head;
     head=head->next;
     free(p);//防止游戏运行过久导致内存被撑爆,清除掉。
}

int ifSnakeDie()
{
    struct Snake *p;
    p=head;
    if(tail->hang==0||tail->hang==20||tail->lie==0||tail->lie==20){
             return 1;
    }
    while(p->next!=NULL){//每次遍历到最后一个节点会一直死,所以只要遍历到第二个节点就可以
       if(p->hang==tail->hang&&p->lie==tail->lie){
             return 0;
       }
        p=p->next;
    }   
}

void moveSnake()
{
   addNode()
   deleteNode();
   if(ifSnakeDie()){
        initSnake();
   }
}

贪吃蛇的按键与界面必须同时运行,linux线程解决这个问题;显示食物;解决不合理走位

如果不引入线程概念,他只能运行一个函数,比如他只能运行刷新界面的函数,根本不会运行按键这个函数,那么这样的话,这个游戏根本没有任何的意义,所以这个游戏就没必要做了。我们解决这个问题的话引进线程的概念,同时运行两个函数,引用线程的库<pthread.h>,把两个不同的函数放进pthread_create中,因为刷新界面与按键不能一次就退出运行,所以我们写一个死循环,一直刷新界面与按键。
代码如下:
主函数:

#include<pthread>

int main()
{
    pthread_t t1; //定义两个地址
    pthread_t t2;

    iniNcurses();//初始化按键函数
    initSnake();//初始化贪吃蛇
    initFood();//初始化食物
    gamePic();//地图扫描

   pthread_create(&t1,NULL,refreshjiemian,NULL);//线程1界面函数
   pthread_create(&t2,NULL,chageDir,NULL);//线程2按键函数
   while(1);//死循环,防止退出
  
   getch();//获取按键
   endwin();//nuurses库自带

   return 0;
}

界面函数

void refreshjiemain()
{
   while(1){
           moveSnake();//蛇的移动
           gamepic();//地图扫描
           refresh();//刷新界面,ncurses自带的函数
           usleep(100000);//提升蛇运行的速度
   }
}
这段代码的意思是,当你控制按键让蛇移动的时候,按键与地图都会同时刷新,用到ncurses库的refresh函数,然后用usleep提升蛇的速度,用while(1)循环,防止一次就退出。

按键函数

#include<curses>
#include<pthread>
int key//定义一个按键,全局变量;

void  chageDir()
{
     while(1){
      int key;
      switch(key){
           case KEY_UP:
                  turn(UP);
                  break;
           case KEY_DOWN:
                  turn(UP);
                  break;
            case KEY_LEFT:
                  turn(LEFT);
                  break;
             case KEY_RIGHT:
                  turn(UP);
                  break;
       }
    }
}

解决不合理走位,因为如果贪吃蛇一下子往上又往右,这样的话会显得很奇怪,所以利用绝对值解决这个问题;让上下的绝对值一样,左右的绝对值一样。
代码如下:

#include<curses>
#include<pthread>

#define UP     1  //宏定义四个按键
#define DOWN  -1
#define LEFT   2
#define RIGHT -2



void turn(int direction)
{
    if(abs(dir)!=abs(direction)){
           dir=direction;
   }
}
以上代码的意思是,绝对值不相等的时候方向才改变,如果绝对值相等方向不变。
abs是绝对值的意思

食物的节点显示

#include<curses>
#include<pthread>
struct Snake food;

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

食物的初始化

#include<curses>
#include<pthread>

void hasFood()
{
   int x=rand()%20;    
   int y=rand()%20;
    
   food.hang=x;
   food.lie=y;
}

食物被吃掉,贪吃蛇身子变长;这个逻辑其实很简单,因为只要节点只增加而不删除,在链表的尾巴后面插入就能实现这个功能。然后吃完之后食物在初始化,不要让食物消失。

代码如下:

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

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

void moveSnake()
{
    addNode();//增加节点显示贪吃蛇身子,如果要移动的话就要删除节点
    if(hasFood()){
         initFood();
    }else{
         deleteNode();
    }
    if(ifSnakeDie()){
          initSnake();
    }
}

蛇的死亡和吃饭都是在蛇的移动中实现的,所以必须在这个函数中实现这个功能。

贪吃蛇的实现逻辑

1.通过ncurses库来获取贪吃蛇的功能键与函数
2.地图显示,食物显示,蛇的身子显示
3.蛇的移动和死亡和蛇进行吃饭

根据以上三步来写出贪吃蛇的代码,并实现功能
全部代码如下:

#include<stdio.h>
#include<curses.h>

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

int dir;
int key;
struct Snake food;

void initFood()
{
     int x=rand()%20;
     int y=rand()%20;

     food.hang=x;
     food.lie=y;
}

void addNode()
{
    struct Snake *p;
    p=head;
    new=(struct Snake*)malloc(sizeof(struct Snake));
    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+1;
                    new->lie=tail->lie;
                    break;
   } 
}


void initSnake()//初始化的目的是创建链表,先创建头节点,然后从尾巴插入
{
    struct Snake *p;
    
    dir=LEFT;//初始化按键
    
    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;
     
    addNode();
    addNode();
    addNode();
    addNode();  
}

void deletNode()
{
   struct Snake *p;
   p=head;
   head=head->next;
   free(p);

}

int   ifSnakeDie()
{
     struct Snake *p;
     p=head;

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

void moveSnake()
{
    addNode();
    if(hasFood()){
          initFood();
    }else{
         deletNode();
    }
    if(ifSnakeDie()){
        initSnake();
    }
}

void  refreshjiemian()
{
   while(1){
         moveSnake();
         gamePic();
         refresh();
         usleep(100000);
   }
}

void turn(int direction)
{
   if(abs(dir)!=abs(direction)){
        dir=direction;
   }
}

void chageDir()
{
    int key;
    switch(key){
            case KEY_UP:
                     trun(UP);
                     break;
            case KEY_DOWN:
                     trun(DOWN);
                     break;
            case KEY_LEFT:
                     trun(LEFT);
                     break;
            case KEY_RIGHT:
                     trun(RIGHT);
                     break;
    }

}
  

void initNcurses()//初始化功能键函数
{
    initscr();//初始化屏幕
    keypad(stdscr,1);//在std中接受功能键
    noecho();//控制输入的字符
}

int  hasSnake(int hang,int lie)
{
   struct Snake *p;
   p=head;
   while(p!=NULL){
      if(p->hang=hang&&p->lie=lie){
          return 1;
      }
      p=p->next;
  }
    return 0;
}

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

void gamePic()
{
     int hang;
     int lie;

     move(0,0);//锁定光标在(0,0);

     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==19){
                         printw("|");
                     }else  if(hasSnake()){
                         printw("[]");
                     }else if(hasFood()){
                         printw("#");
                     }else{
                        printw("  ");
                     }                
               }
               printw("\n");
           }

          if(hang==19){
               for(lie=0;lie<20;lie++){
                    printw("--")
              }
              printw("\n");
              printw("by xiewenhui\n");
          }
          
    }

}

int main()
{
    pthread t1;
    pthread  t2;

   initNucrses();
   initSnake();
   initFood();
   gamePic()

   pthread_create(&t1,NULL,refreshjiemain,NULL);
   pthread_create(&t2,NULL,refreshjiemain,NULL);
   while(1);
   
   getch();
   endwin();
   return 0;
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值