C语言实现简单贪吃蛇游戏:指针数组与线程控制详解
利用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) 循环,顺序执行以下操作:
- 渲染(
show()):清屏并遍历矩阵输出当前图形。 - 休眠(
usleep(us)):控制游戏帧率。 - 碰撞检测(
fail()):检查蛇头坐标是否越界或与自身重叠。 - 进食判断(
eat()):根据运动方向,判断下一个位置是否为食物'*'。若是,长度增加并生成新食物。 - 执行移动(
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语言指针数组与多线程应用等资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









