C语言实现简单贪吃蛇游戏:指针数组与线程控制详解

顾星河
顾星河
发布时间:2026-10-16| 24人浏览过
原创

利用C语言在Linux终端环境下开发贪吃蛇游戏,关键在于通过指针数组管理蛇身位置数据,并使用pthread库实现独立于主循环的键盘监听线程。

准备条件与编译环境

该示例适用于Linux或macOS等类Unix系统,依赖GNU C标准库与POSIX线程接口。在编写完代码后,需要使用gcc进行编译。由于程序中使用了多线程功能(pthread_create等),必须在编译指令中链接线程库。

编译命令如下:

gcc snake.c -o snake -lpthread

核心数据结构:指针数组定位蛇身

贪吃蛇的核心逻辑是蛇头的移动带动身体的跟进。为了避免频繁的移动二维数组元素造成性能损耗,程序使用了一个一维指针数组 p[X*Y]。

  • 定义方式:char *p[X*Y]={&a[0][3],&a[0][2],&a[0][1],&a[0][0]};
  • 作用原理:数组中的每个元素保存了蛇身节段对应的二维字符矩阵 a[X][Y] 中的实际内存地址。p[0] 始终指向蛇头,p[n] 指向蛇尾。
  • 更新逻辑:当蛇向前移动时,只需将后继节段的指针赋值给前驱节段(p[i]=p[i-1]),并更新新的蛇头指针位置,最后修改对应内存块中的字符状态。

第1步:初始化屏幕布局

游戏开始时,需要在控制台上绘制初始状态。代码中定义了蛇头字符为 '@',蛇身为 'O',并在二维矩阵 a[X][Y] 的前四个位置进行了赋值初始化。

第2步:实现无缓冲键盘输入

为了实现流畅的游戏控制,必须使字符输入无需按回车键即被读取。在 main 函数入口执行:

system("stty -icanon");

这一行代码关闭了标准输入的 canonical mode(规范模式),使得按键事件能立即触发读取响应。

多线程架构:主循环与输入分离

贪吃蛇需要同时处理两件事:一是定时刷新画面和计算逻辑(重力/移动),二是实时响应用户按键。若在主线程中等待输入,画面会卡死;若在画面刷新中处理输入,则可能丢失按键。

创建独立的键盘监听线程

程序创建了第二个线程来专门负责监控键盘:

pthread_t pid;
pthread_create(&pid , NULL , key , NULL);

在线程 key 中,使用 getchar() 持续监听按键。用户按下 w, a, s, d 会改变方向标志位 f;按下 j, k 调整延时变量 us 以改变速度;按下 ESC (k==27) 则设置退出标志。

主循环:渲染、判定与位移

主线程进入 while(1) 循环,顺序执行以下操作:

  1. 渲染(show()):清屏并遍历矩阵输出当前图形。
  2. 休眠(usleep(us)):控制游戏帧率。
  3. 碰撞检测(fail()):检查蛇头坐标是否越界或与自身重叠。
  4. 进食判断(eat()):根据运动方向,判断下一个位置是否为食物 '*'。若是,长度增加并生成新食物。
  5. 执行移动(right/left/up/down()):根据当前的方向标志位 f,调用相应的指针移位函数完成蛇的身形变换。

关键算法细节说明

随机食物生成

ran() 函数通过伪随机数算法获取坐标,并利用 goto 语句进行循环校验:如果该坐标上已有蛇身(值不为0),则重新生成,直到找到空位为止。

死亡判定逻辑

在 fail() 函数中,除了检查蛇头是否触及矩阵边界 (X*Y) 外,还遍历整个蛇身数组。通过比较移动后的头部指针是否与任何身体节点指针相等,实现了对“自咬”行为的精确捕获。

完整源代码

以下是整合了上述逻辑的完整C语言源码:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
 
#define X 20
#define Y 40
 
char head='@';//蛇头的形状
char body='O';//蛇身的形状
 
char a[X][Y]={'O','O','O','@'};
char *p[X*Y]={&a[0][3],&a[0][2],&a[0][1],&a[0][0]};//p[0]表示蛇头
 
int n=3; //蛇身的长度(不带蛇头)
int i,j;
int f=1; //标志位:1.右;2.上;3.左;4.下;-1.退出
int us=200000;//用于usleep,延时0.2秒
 
void right()
{
 *p[n]=0;
 for(i=n;i>0;i--)
 {
  p[i]=p[i-1];
 }
 *p[0]=body;
 p[0]=p[0]+1;//蛇头向右移
 *p[0]=head;
}
void left()
{
 *p[n]=0;
 for(i=n;i>0;i--)
 {
  p[i]=p[i-1];
 }
 *p[0]=body;
 p[0]=p[0]-1;
 *p[0]=head;
}
void down()
{
 *p[n]=0;
 for(i=n;i>0;i--)
 {
  p[i]=p[i-1];
 }
 *p[0]=body;
 p[0]=p[0]+Y;
 *p[0]=head;
}
void up()
{
 *p[n]=0;
 for(i=n;i>0;i--)
 {
  p[i]=p[i-1];
 }
 *p[0]=body;
 p[0]=p[0]-Y;
 *p[0]=head;
}
 
 
void show()
{
 system("clear");
 for(i=0;i<Y;i++)
  printf("-");
 printf("\n");
 for(i=0;i<X;i++)
 {
  for(j=0;j<Y;j++)
  {
   if(a[i][j]==0)
    printf(" ");
   else
    printf("%c",a[i][j]);
  }
  printf("\n");
 }
 for(i=0;i<Y;i++)
  printf("-");
 printf("\nw,s,a,d->上下左右;j,k->加减速;ESC退出\n");
}
 
 
int ran()//随机数生成*
{
 srand(time(NULL));
lb:
 i=rand()%X;
 j=rand()%Y;
 //随机位置的值为0,则产生*;否则继续找随机位置
 if(a[i][j]==0)
  a[i][j]='*';
 else
  goto lb;
}
 
void eat()
{
 if(f==1)//右
 {
  if(*(p[0]+1)=='*')
  {
   n++;//长度增加
   p[n]=p[n-1];
   ran();//随机数生成*
  }  
 }
 if(f==2)//上
 {
  if(*(p[0]-Y)=='*')
  {
   n++;//长度增加
   p[n]=p[n-1];
   ran();//随机数生成*
  }  
 }
 if(f==3)//左
 {
  if(*(p[0]-1)=='*')
  {
   n++;//长度增加
   p[n]=p[n-1];
   ran();//随机数生成*
  }  
 }
 if(f==4)//下
 {
  if(*(p[0]+Y)=='*')
  {
   n++;//长度增加
   p[n]=p[n-1];
   ran();//随机数生成*
  }  
 }
}
 
void fail()
{
 if(p[0]<&a[0][0]||p[0]>&a[X-1][Y-1])//蛇头不在矩阵内
 {
  printf("fail!\n");
  f=-1;
 }
 if(f==1)//右
 {
  for(i=n;i>0;i--)
  {
   if((p[0]+1)==p[i])//右边是自己的身体
   {
    printf("fail!\n");
    f=-1;
   }
  } 
 }
 if(f==2)//上
 {
  for(i=n;i>0;i--)
  {
   if((p[0]-Y)==p[i])//上边是自己的身体
   {
    printf("fail!\n");
    f=-1;
   }
  } 
 }
 if(f==3)//左
 {
  for(i=n;i>0;i--)
  {
   if((p[0]-1)==p[i])//左边是自己的身体
   {
    printf("fail!\n");
    f=-1;
   }
  } 
 }
 if(f==4)//下
 {
  for(i=n;i>0;i--)
  {
   if((p[0]+Y)==p[i])//下边是自己的身体
   {
    printf("fail!\n");
    f=-1;
   }
  } 
 }
}
 
void *key(void *arg)//控制方向:w,s,a,d-->上下左右
{
 char k;
 while(1)
 {
  k=getchar();
  if(k=='w')//上
   f=2;
  if(k=='s')//下
   f=4;
  if(k=='a')//左
   f=3;
  if(k=='d')//右
   f=1;
  if(k=='j')//加速
   us=us*4/5;
  if(k=='k')//减速
   us=us*5/4;
  if(k==27)//ESC
  {
   printf("退出!\n");
   f=-1;
  }
  usleep(100);
 }
}
 
int main()
{
 system("stty -icanon");//关闭缓冲区,输入字符无需回车直接接受
 pthread_t  pid;
    pthread_create(&pid , NULL , key , NULL);//创建线程,键盘控制
 ran();
 while(1)
 {
  show();  //显示
  usleep(us);
  fail();  //判断是否会咬到自己
  eat();  //判断是否能吃到*
  if(f==1)//右
   right();
  if(f==2)//上
   up();
  if(f==3)//左
   left();
  if(f==4)//下
   down();
  if(f==-1)//退出
  {
   pthread_cancel(pid);//关闭线程
   return -1;
  }
 }
 
 
 return 0;
}

总结与优化建议

本实例展示了C语言如何通过底层指针操作高效地管理动态结构体,以及如何利用多线程解决控制台游戏的交互痛点。代码虽然简洁,但涵盖了面向对象之外的另一种数据组织思路。

对于实际应用或进阶学习,以下几点值得注意: 1. 内存安全:当前代码未处理多输入并发竞争,实际项目中可能需要互斥锁保护共享变量。

2. 平台限制:程序强依赖于 system("clear") 和Linux特性,移植至Windows需更换清理命令并改用 conio.h。

以上就是C语言实现简单贪吃蛇游戏的详细内容,更多关于C语言指针数组与多线程应用等资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。