基于C+ncurses 实现的贪吃蛇

本文详细介绍了如何在Linux环境下使用C语言和Ncurses库开发一个贪吃蛇游戏,涉及键盘输入处理、链表数据结构、贪吃蛇移动、多线程实现、方向控制和游戏界面刷新等内容。

游戏说明:


linux 环境下的,基于Ncursse 图形图的C语言小游戏

基础要求:


C语言基础
Linux 基本操作:  
如何编写代码 如何编译代码  如何运行程序  如何创建文件夹


为什么需要应用ncurse 库:


C语言的键盘输入 函数  没法满足实时性 -- scanf gets getchar -- 都需要 回车确认

curse 输入和输出:


键盘响应快 
#include<curses.h>
initscr();
printw("This is a curse window.\n");  // ncurse 模式的printf
getch();  //等待用户输入 ,if 没这句话程序会自动退出,看不到运行结果
endwin(); //程序退出-- 调用函数来恢复shell终端的显示,没有这句话,shell终端就会乱码

nurse 的上下左右 键:
#define  KEY_DOWN 0402
#define KEY_UP  0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405

上面四个键是功能键 -- 使用的时候需要我们 引入 keypad函数

keypad(stdscr,1);


#include <curses.h>

int main()
{
    int key;
    initscr();
    keypad(stdscr, 1);
    while (1)
    {
      key = getch();
      switch (key)
      {
     // case 0402:
      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;
  
      }

        
    }
    endwin();

    return 0;
}


======================================

贪吃蛇地图:


20 * 20 的格子 :
地图上界"--"
地图左界"|"
苹果(食物)"##"
蛇身 "[][][[]"

void myPrintMap()
{
  int line, row;

  for (line = 0; line < 20; ++line)
  {
    if (line == 0)
    {
      for (row = 0; row < 20; row++)
        printw("--");
      printw("\n");
    }

    if (line >= 0 && line < 20)
    {
      for (row = 0; row <= 20; row++)
      {
        if (row == 0)
          printw("|");
        else if (row == 20)
          printw("|\n");
        else
          printw("  ");
      }
      if (line == 19)
      {
        for (row = 0; row < 20; row++)
        {
          if (row == 19)
            printw("--\nby mxjun");
          else
            printw("--");
        }
      }
    }
  }
}

=========================================


贪吃蛇身子 节点 -- 结构体--使用链表实现

//扫描匹配到贪吃蛇的身体节点 --行列都匹配 
不打空格 ,打出蛇身子
-- printw("[]") --显示出来

//判断是否需要显示蛇的身子 
int haveSnack(int line,int row)
{
struct snack * p=&s1;

 while(p!=NULL)
 {
  if(p->line==line && p->row==row)
   return 1;
  p=p->next;
 }
return 0;

}

=======================================


链表添加蛇的节点:


void addSnack()
{
 //尾插法实现
 struct Snack *new = (struct Snack*)malloc(sizeof(struct Snack));
 new->line=tail->line; 
 new->row=tail->row+1;
 new->next=NULL; //作为新的尾巴
 tail->next=new;
 tail=new;
}

void initSnack()
{
head = (struct Snack*)malloc(sizeof(struct Snack));
head->line=2;
head->row=2;
head->next=NULL;
tail=head;

addSnack();
addSnack();
addSnack();

}

=========================================


实现贪吃蛇的向右移动:

1.右移:
原理 删除第一个, 在最右边一个节点就没添加一个

void delSnack()
{
 struct Snack* new;
 new=head;
 head=head->next;
 free(new);
}

void moveRight()
{
 addSnack();
 delSnack();

}

===============================


贪吃蛇撞墙 ;

void initSnack()
{
struct Snack*new=NULL;
while(head!=NULL)
 {
  new=head;
 head=head->next;
free(new);

 }


head = (struct Snack*)malloc(sizeof(struct Snack));
head->line=2;
head->row=2;
head->next=NULL;
tail=head;

addSnack();
addSnack();
addSnack();

}


void moveRight()
{
 addSnack();
 delSnack();
  //判断是否撞到墙
 if(tail->row==0 || tail->row==20)
   {
   initSnack();

   }

}

usleep () -- 休眠
refresh()  -- 刷新

========================


贪吃蛇的自行游走

 -- while -- 一直执行, move函数 usleep() -- 控制时间

 warning: implicit declaration of function ‘usleep’ [-Wimplicit-function-declaration]
  156 |        usleep(100000);
// 这种警报 可以直接man usleep 查看他的头文件

===============================

方向变化 -- 多线程


引入 -- .界面一边在刷新,我们也要求一边能接收我们的按键  -- 多线程 --让两个while 同时进行


怎么实现多线程:


Linux 线程:

创建线程的代码:
#include<pthread.h>
ret = pthread_create(&th,NULL,thread,&arg ); // thread --可以是函数 -- 我们要执行的


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

void* func1()
{
 while(1)
  {
  puts("func1 is running");
  sleep(1);
  }

}

void* func2()
{
 while(1)
  {
  puts("func2 is running");
  sleep(1);
  }

}

int main()
{
 pthread_t th1;
 pthread_t th2;


 pthread_create(&th1,NULL,func1,NULL);
 pthread_create(&th2,NULL,func2,NULL);


 while(1);

}


===================================


蛇的转弯


定义全局 的方向变量dir 
在键入 方向键的时候进行修改 -- 然后在moveSncak()的addSnack() 的时候 方向 被改变


===================================

绝对值优化蛇的不合理走位;

abs 函数 -- 去绝对值

initNcurses() -- noecho -- 不要把无关信息显示在界面上

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

oid turn(int newDir)
{
 if(abs(newDir)!=abs(dir))
   dir=newDir;

}

void *inputKey()
{

  while (1)
  {
    key = getch();
    switch (key)
    {
    case KEY_DOWN:
      turn(DOWN);
      break;
    case KEY_UP:
      turn(UP);
      break;
    case KEY_LEFT:
      turn(LEFT);
      break;
    case KEY_RIGHT:
     turn(RIGHT);
      break;
    }
  }
}

=====================================


苹果 -- 食物

食物可以使用之前的snack 结构体里面的坐标表示

static -- 静态变量 -- 函数被再次调用打的时候他的值不会发生变化

rand()  --实现食物随机生成: % 20 -- 限制范围

==================================


蛇能吃自己 -- 链表尾巴 和 身体的重合

//这里我们再明确一点 ,由于我们使用的是尾插法,so我们得到的链表尾巴就是蛇头

代码整体实现

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

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



struct Snack //定义蛇类
{
  int line;
  int row;

  struct Snack *next; //链表实现贪吃蛇变长
};
// 定义蛇头,蛇尾
struct Snack *head = NULL;
struct Snack *tail = NULL;
struct Snack food; //定义食物类
int key;
int dir; //记录方向

void initNcurse() //对Ncurse初始化
{

  initscr();
  keypad(stdscr, 1);
  noecho(); //避免键入方向键的时候屏幕错乱,返回值
}

void initFood() //初始化食物
{
int x=rand()%20; //随机生成0-19的位置
int y=rand()%20;

food.line=x;
food.row=y;



}
int haveFood(int line, int row) //判断这个格子上是否存在食物
{
  if(food.line==line && food.row==row)
   return 1;

  return 0;
}


int haveSnack(int line, int row)//判断这个格子上是否存在蛇
{
  struct Snack *p = head;

  while (p != NULL)
  {
    if (p->line == line && p->row == row)
      return 1;
    p = p->next;
  }
  return 0;
}

void addSnack() //增加蛇的长度
{
  // 尾插法实现
  struct Snack *new = (struct Snack *)malloc(sizeof(struct Snack));

 
  switch (dir) //依据当前的方向判断
  {
  case UP:
  new->line = tail->line-1;
  new->row = tail->row;
  new->next = NULL; // 作为新的尾巴
    break;
  
   case DOWN:
  new->line = tail->line+1;
  new->row = tail->row;
  new->next = NULL; // 作为新的尾巴
    break;
    
     case LEFT:
  new->line = tail->line;
  new->row = tail->row-1;
  new->next = NULL; // 作为新的尾巴
    break;

     case RIGHT:
  new->line = tail->line;
  new->row = tail->row+1;
  new->next = NULL; // 作为新的尾巴
    break;

  }


  tail->next = new;
  tail = new;
}

void initSnack() //初始化蛇 - 死后 复活
{
  dir=RIGHT;
  initFood();
  struct Snack *new = NULL;
  while (head != NULL)
  {
    new = head;
    head = head->next;
    free(new);
  }

  head = (struct Snack *)malloc(sizeof(struct Snack));
  head->line = 2;
  head->row = 2;
  head->next = NULL;
  tail = head;

  addSnack();
  addSnack();
  addSnack();
}

void myPrintMap() //打印地图
{
  int line, row;

  move(0, 0); //每次新的地图都移动到(0,0),覆盖之前的地图实现实时性
  for (line = 0; line < 20; ++line)
  {
    if (line == 0)
    {
      for (row = 0; row < 20; row++)
        printw("--");
      printw("\n");
    }

    if (line >= 0 && line < 20)
    {
      for (row = 0; row <= 20; row++)
      {
        if (row == 0)
          printw("|");
        else if (row == 20)
          printw("|\n");
        else if (haveSnack(line, row))
          printw("[]");
           else if (haveFood(line, row))
          printw("##");
        else
          printw("  ");
      }
      if (line == 19)
      {
        for (row = 0; row < 20; row++)
        {
          if (row == 19)
            printw("--\nby mxjun");
          else
            printw("--");
        }
      }
    }
  }
}

void delSnack()
{
  struct Snack *new;
  new = head;
  head = head->next;
  free(new);
}
int ifSnackDead() //判断边界
{
 if(tail->line<0 || tail->line==20 || tail->row==0 || tail->row==20)
   return 1;
 struct Snack *p=head;
 while(p->next!=NULL)
  {
    if(p->line==tail->line && p->row==tail->row)
      return 1;
      p=p->next;
  } 
return 0;

}


void moveSnack()
{ 
  if(tail->line==food.line && tail->row==food.row) //吃到食物蛇变长
  {
  addSnack();
  initFood();
  }
  else //不然只是移动
  {
  addSnack();
  delSnack();
  }
  // 判断是否撞到墙
  if (ifSnackDead())
  {
    initSnack();//死了重开
  }
}

void *refreshInterface() //线程之一,实现蛇的自动行走
{

  while (1)
  {

    moveSnack();
    myPrintMap();
    refresh();
    usleep(100000);
  }
}

void turn(int newDir) //防止蛇出行头变为尾巴这样的逆天走位
{
 if(abs(newDir)!=abs(dir))
   dir=newDir;

}

void *inputKey() //第二个线程,时刻等待键盘输入
{

  while (1)
  {
    key = getch();
    switch (key)
    {
    case KEY_DOWN:
      turn(DOWN);
      break;
    case KEY_UP:
      turn(UP);
      break;
    case KEY_LEFT:
      turn(LEFT);
      break;
    case KEY_RIGHT:
     turn(RIGHT);
      break;
    }
  }
}

int main()
{
//定义两个线程
  pthread_t th1;
  pthread_t th2;
//初始化蛇和Ncurse
  initNcurse();
  initSnack();

  pthread_create(&th1, NULL, refreshInterface, NULL);
  pthread_create(&th2, NULL, inputKey, NULL);
  while (1) //避免程序退出
  {
  }
  getch();
  endwin(); //结束使用ncurse,退出程序,避免乱码

  getch();
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值