#include<curses.h> //使用 ncurses 必须调用它的函数库
#include<stdlib.h>
#include<time.h>
#define UP 1 //宏定义 UP 与 1 等效
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake //结构体
{
int hang;
int lie;
struct Snake *next;
};
struct Snake *head; //定义结构体类型的指针 head
struct Snake *tail; //定义结构体类型的指针 tail
int key;
int dir;
struct Snake food; //定义结构体类型 food
void initNcurse() //初始化 Ncurses (Ncurses 是一套编程库)
{
initscr(); // Ncurses界面的初始化函数
//获取包括大小和特征在内的终端类型,并设置终端支持的 curses 环境。
keypad(stdscr,1); //使用keypad(stdscr,TURE) 就为“标准屏幕”(stdscr)激活了功能键。
// TURE-> ture-> 1
noecho(); //调用 noecho()函数 禁止输入的字符出现在屏幕上。(防止界面中出现一些乱码)
//echo()函数 允许输入的字符出现在屏幕上。
}
void initFood() //初始化食物
{
food.hang = rand()%20; //rand()%20 取余,取出来的余数是小于20的
food.lie = rand()%20;
}
int hasSnakeNode(int i,int j) //通过判断此时贪吃蛇身子的位置是否与gamPic() 中的位置相同 来决定返回 0 or 1
{
struct Snake *p; //定义结构体类型的指针 p
p = head;
while(p != NULL) //判断 P 存放的地址是否为空
{
if(p->hang ==i && p->lie == j) //判断结构体类型的指针 P 指向的 hang 与 lie 是否与 gamePic() 中的hang 与 lie 都相等
{
return 1; //相等 则返回 1 (在C语言中 1 代表真 ture)
}
p = p->next; //链表下移动, 指针 P 指向链表的的下一个 结构体
}
return 0; // p 存放的地址为空( NULL ), 返回 0 (在C语言中 0 代表假 fack)
}
int hasFood(int i,int j) //该函数是用来判断当 hang==i lie==j 时,是否打印出食物 打印就返回 1, 不打印就返回 0
{
//有返回值的 返回类型用 int ; 不需要返回值的 返回类型用 void (void 是无类型)
if(food.hang == i && food.lie == j){
//判断结构体 food 的 hang 与 lie 是否与 gamePic() 中的hang 与 lie 都相等
return 1; //相等 则返回 1 (在C语言中 1 代表真 ture)
}
return 0; //不相等 则返回 0 (在C语言中 0 代代表假)
}
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>=1 && hang<=18){
for(lie=0;lie<=20;lie++){
if(lie==0 || lie==20){
printw("|");
}else if(hasSnakeNode(hang,lie)){
printw("[]");
}
else if(hasFood(hang,lie)){
//判断 hasFood(hang,lie)返回的值是 1 or 2
printw("##"); //如果返回值是 1 就在地图中的 第 hang 行,第 lie 列; 打印"##"
}else{
printw(" "); //如果返回值是 0 就在打印两个空格 " "
}
}
printw("\n"); //换行
}
if(hang==19){
for(lie=0;lie<20;lie++){
printw("--");
}
printw("\n");
printw("By Wangbicheng key= %d\n",key);
printw("food.hang=%d,food.lie=%d",food.hang,food.lie);
}
}
}
void addNode()
{
struct Snake *new =(struct Snake*)malloc(sizeof(struct Snake)); //定义一个结构体类型的指针 new ,指向返回值为结构体 并有该结构体结构体大小的空间
new->next = NULL;
switch(dir){
//此处的 dir 表示的是你在键盘上输入的值 是上 还是 下 或者 左 右
case UP:
new->hang = tail->hang-1; //对 new->hang 进行赋值
new->lie = tail->lie; //对 new->lie 进行赋值
break; //break 表示结束 跳出 switch()
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;
}
tail->next = new; //让 tail->next 指向 新赋值后 new (此处的new 所带得值不重要 重要的是 new 再此成为最后一节)
tail = new; //让 tail的 hang与lie 等于 新赋值后的 new的 hang与lie
}
void initSnake() //对贪吃蛇得身子进行 初始化
{
struct Snake *p;
dir = RIGHT; //让 dir 一开始就等于 RIGHT 是为了 方便一开始就让蛇有身子
while(head!=NULL){
//判读head 是否为空指针 ,一直判断到 head为 NULL 为止
p = head; //如果不是空指针 就将 head存放得地址 赋给 指针p
head = head->next; //链表下移
free(p); //释放 head 所指向得 malloc 申请到得空间 ,因为每初始化依次 下方都会再次申请空间,有多少节蛇身,就释放几次
}
head = (struct Snake *)malloc(sizeof(struct Snake));//申请空间
head->hang = 1; //初始化
head->lie = 1; //初始化
head->next= NULL; //初始化
tail = head; //tail 是链表的尾部,贪吃蛇的头部 因为此时贪吃蛇还只有一个 [] 所以 tail=head
addNode(); //在链表的尾部增加一节 []
addNode();
addNode();
addNode(); //有四个 addNode() 表示增加了四个 [] 作为贪吃蛇的初始化蛇身
}
void deleNode() //该函数的作用是删除蛇的尾巴,(链表的头部)
{
struct Snake *p; //注意每个函数中 所定义的类型只有 该函数中有用
//例如 struct Snake *p; 只有在这个函数中用, 放到外面如果没有重新定义是无法使用的
p = head;
head = head->next; //链表下移, 表示将链表的头部删除掉
free(p); //释放删除掉的头部的空间
}
void moveSnake() //这个函数的作用是 给贪吃蛇增加能力与限制
//能力:能自己沿着头的方向移动 ; 吃到食物会长一节蛇身
//限制:例如活动的空间 以及碰到它自己就会死 还有判断食物是否被吃掉 并让食物在其他位置重新出现
{
struct Snake *p;
p = head;
addNode(); //一开始就给贪吃蛇的头部(链表的尾部)增加一节蛇身 []
if(hasFood(tail->hang,tail->lie)){
//判断蛇头的位置是否在食物"##"的坐标
initFood(); //蛇头在食物的位置 对食物进行初始化,初始化后食物的位置会随机改变
}
else{
deleNode(); //蛇头不在食物的位置 不对食物进行初始化,并删除掉一个蛇尾(链表的头部)
}
if(tail->hang==0 || tail->lie==0 || tail->hang==20 || tail->lie==20){
//判断贪吃蛇的头部所在的位置 是否在地图的边界线上
initSnake(); //贪吃蛇头部的所在位置在地图的边界线上, 贪吃蛇进行初始化 初始化后贪吃蛇会变回最初的位置和大小
}
while(p->next != NULL){
//对贪吃蛇的身子(链表)进行遍历,p->next = NULL,标志着贪吃蛇的身子(链表)遍历结束
if(p->hang == tail->hang && p->lie == tail->lie){
//判断 贪吃蛇的身子与头部是否重合
initSnake();//如果重合 则贪吃蛇进行初始化
initFood(); //食物进行初始化
}
p=p->next; //链表下移
}
if(food.hang==0 || food.lie ==0 || food.hang==19 || food.lie==20){
//这个条件语句是为了防止食物出现在地图的边界线上
//如果不写这个会出现一个bug ,那就是蛇无法吃到食物 ,导致食物在地图内无法出现新的食物
initFood(); //食物的初始化
}
}
void refreshJieMian()
{
while(1){
//while(1) 一直判断为真,一直死循环
moveSnake(); //判断贪吃蛇触发规则没有
gamePic(); //打印地图 食物 和 贪吃蛇
refresh(); //刷新界面,我们每一次看到的贪吃蛇连续的移动 都是该函数刷新界面后产生的
usleep(100000); //每刷新一次 等待 0.1 秒
}
}
void turn(int direction) //该函数的作用就是让你不能按与上一次按键相反的建 例如:按了左以后不能按右,只能按其他的 例如上与下
{
if(abs(dir) != abs(direction)) //abs() 该函数取可以取 括号() 内的绝对值 在代码的开头 就宏定义了 上下 是 1与 -1 ; 左右 是 2与-2
{
dir = direction; //如果这次按的键和上次相反 则本次按的键会强制变成和上一次一样的键 例如:上次按了左,这次按了右,那么右就会变成左
}
}
void changeDir()
{
while(1){
key = getch(); //获取键盘输入的内容给 key
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 t1; //t1 线程
pthread_t t2; //t2 线程
initNcurse(); //初始化 ncurse 界面
initSnake(); //初始化贪吃蛇
initFood(); //初始化食物的位置
gamePic(); // 打印出地图 食物 贪吃蛇
pthread_create(&t1,NULL,refreshJieMian,NULL); //t1 线程负责刷新页面 ;让贪吃蛇动起来
pthread_create(&t2,NULL,changeDir,NULL); //t2 线程负责输入 ;让人能控输入方向键进行控制
//双线程实现了让贪吃蛇动的同时又可以输入方向键对蛇进行控制
while(1); //作用:让程序不退出 不执行后面的程序
getch();
endwin(); //退出 curses 模式, endwin()函数释放了 curses 子系统和相关数据结构占用的内存,使你能够正常返回控制台模式。
return 0;
}
C贪吃蛇代码详解(ncurse)
猜你喜欢
转载自blog.csdn.net/weixin_49472648/article/details/107829792
今日推荐
周排行