linux环境下Ncurses实现贪吃蛇游戏

游戏说明: linux环境下基于Ncurses图形库的C语言小游戏。

Ncurses介绍:
Ncurses(new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。 Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库。Ncurses用得最多的地方是linux内核编译之前的内核配置,Ncurses早已淡出舞台,甚至体验感完爆Ncurses的C图形库GTK、C++图形库QT也区趋于落伍嵌入式设备上的Android 系统。这个游戏只是使用Ncurses并不以学习它为目的,主要还是通过这个游戏锻炼我们C语言的能力。

准备工作:
在ubuntu上面安装Ncurses库,输入以下指令:

sudo apt-get install libncurses5-dev

来编写第一个程序看看是否能成功运行:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函数printw("This is a ncurses window.\n");//在ncurses模式下的printfgetch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉return 0;
}

运行上面那个程序时需要用到-lcurses:

gcc ncurses.c -lcurses -o ncurses  //因为需要用到curses库所以要链入curses

以上代码输出界面是:
在这里插入图片描述
Ncurses的上下左右键值:

#define KEY_DOWN 0402
#define KEY_UP   0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405
//这些是Ncurses的一些宏定义,表示上下左右键,这些数都是八进制的
/*
这些内容可以通过命令查看curses.h的文件可知
命令:cd /usr/include/curses.h
*/

代码如下:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函数printw("This is a ncurses window.\n");//在ncurses模式下的printfkeypad(stdscr,1);//这个函数是一个函数,第一个参数是从stdscr中接受功能建,第二个是参数表示是否接收,1表示接收while(1){//char c=getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果int key=getch();//这里要把char类型改为int类型,因为char类型最大表示是128,而这些方向键的值(0404等)要>128,所以用intprintw("you input :%d\n",key);}endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉return 0;
}代码运行结果如下:
This is a ncurses window.
you input :259
you input :258
you input :260
you input :261
按下上下左右键,分别输出259258260261
输出的这些数字都是十进制的,分别对应宏定义中的:0403040204040405

代码可以进一步优化:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函数printw("This is a ncurses window.\n");//在ncurses模式下的printfkeypad(stdscr,1);//这个函数是一个函数,第一个参数是从stdscr中接受功能建,第二个是参数表示是否接收,1表示接收while(1){//char c=getch();//等待用户输入,如果没有这句话,程序就退出了,看不到运行结果int key=getch();//这里要把char类型改为int类型,因为char类型最大表示是128,而这些方向键的值(0404等)要>128,所以用intswitch(key){case 0402:printw("DOWN");break;case 0402:printw("DOWN");break;case 0402:case 0402:printw("DOWN");break;printw("DOWN");break;}}endwin();//程序退出,调用该函数来恢复shell终端的显示,如果没有这句话,shell终端字乱码坏掉return 0;
}
当然程序中的代表方向的数字也可以用宏表示。
输入上下左右键,程序运行结果如下:
This is a ncurses window.
UP
DOWN
LEFT
RIGHT

地图规划:

两个- 和一个 | 组成一个方格,下图是20×20的一个地图。
在这里插入图片描述地图代码:

#include<curses.h>void initNcurses()
{initscr();keypad(stdscr,1);
}void map()
{int hang;int lie;for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}}printw("By Feng Hui Nan.");
}
int main()
{initNcurses();map();getch();endwin();//没有这行会破坏shell终端return 0;
}

贪吃蛇身子节点构成:

struct Snake
{int hang;int lie;struct Snake*next;
};

实现蛇身子的全部显示,并向右移动:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
void initNcurses()
{initscr();keypad(stdscr,1);
}struct Snake //贪吃蛇身子的一个结点
{int hang;int lie;struct Snake*next;
};
struct Snake* head=NULL;//将蛇的头节点定义为全局变量
struct Snake* tail=NULL;//将蛇的尾节点定义为全局变量,防止错误过多
int hasSnakeNode(int i,int j)
{struct Snake *p;p=head;while(p!=NULL){if(p->hang==i && p->lie==j){return 1;}p=p->next;}return 0;
}
void map()
{int hang;int lie;move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else if(hasSnakeNode(hang,lie)){printw("[]");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");}}printw("By Feng Hui Nan.");
}
void addSnake()
{struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));new->hang=tail->hang;new->lie=tail->lie+1;new->next=NULL;tail->next=new;tail=new;
}
void initSnake()
{struct Snake*p;while(head!=NULL){p=head;head=head->next;//将链表的每一个节点都释放掉,指针后移free(p);}//添加这个循环的目的是在一次游戏结束后释放当局游戏创建的链表,避免内存泄露head=(struct Snake*)malloc(sizeof(struct Snake));head->hang=1;head->lie=1;head->next=NULL;tail=head;addSnake();addSnake();addSnake();
}
void deleSnake()
{struct Snake*p;p=head;head=head->next;free(p);
}
void moveSnake()
{addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界deleSnake();if(tail->hang==0 || tail->lie==20 || tail->lie==0 || tail->hang==20){initSnake();}
}
int main()
{initNcurses();initSnake();map();while(1){moveSnake();map();//删除后再次刷新地图显示移动后的界面refresh();//刷新界面函数//sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率usleep(100000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度}getch();endwin();//没有这行会破坏shell终端return 0;
}

效果图:
在这里插入图片描述遇到问题:
如何在响应方向键的同时,控制蛇的移动?好像需要两个while循环同时执行。我想到一个方法,就是用switch判断输入的方向键,然后再封装向上下左右移动的函数,在switch中进行调用,仔细思考以下发现不可行,因为在这个游戏中地图是每隔100毫秒刷新一次要不断的刷新,所以不可行。

问题解决:
用linux线程即可解决该问题,我们将一个程序里的执行路线叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列。

线程demo:

#include<stdio.h>
#include<pthread.h>void*thread(void *arg)
{printf("this is a thread and arg=%d.\n",*(int*)arg);*(int*)arg=0;return arg;
}
int main(int argc,char *argv[])
{pthread_t th;int ret;int arg=10;int *thread_ret=NULL;ret=pthread_create(&th,NULL,thread,&arg);//pthread_create这个函数是线程的创建函数//第一个参数th是线程的描述符,就是上边定义的pthread_t th中的th,就相当于给第三那个参数的一个ID吧//第二个参数一般就写NULL//第三个参数是做好的准备被调用的函数,最后一个参数是函数的参数if(ret!=0){printf("Creat thread error!\n");return -1;}printf("this is the mian process.\n");pthread_join(th,(void**)&thread_ret);//int pthread_join(pthread_t thread, void **value_ptr);//thread:等待退出线程的线程号。//value_ptr:退出线程的返回值。return 0;
}

自己实现线程的创建:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void* fun1()
{while(1){printf("this is func1.\n");sleep(1);}
}
void* fun2()
{while(1){printf("this is fun2.\n");sleep(1);}
}
int main()
{pthread_t th1;pthread_t th2;int ret1;int ret2;int *pthread_th1=NULL;int *pthread_th2=NULL;ret1=pthread_create(&th1,NULL,fun1,NULL);ret2=pthread_create(&th2,NULL,fun2,NULL);if(ret1!=0 || ret2!=0){printf("thread creat error.\n");perror("error");return -1;}
//      while(1);//在主线程等待其他线程运行完后再退出pthread_join(th1,(void **)&pthread_th1);pthread_join(th2,(void **)&pthread_th2);//这种方法也可以使主线程等待指定线程退出后才退出//th2是线程的ID//int pthread_join(pthread_t thread, void **value_ptr);//thread:等待退出线程的线程号。//value_ptr:退出线程的返回值。return 0;}

程序总体代码:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
#include<pthread.h>#define UP     1
#define DOWN  -1
#define RIGHT  2
#define LEFT  -2struct Snake //贪吃蛇身子的一个结点
{int hang;int lie;struct Snake*next;
};struct Snake food;
void initFood()
{int x=rand()%20;int y=rand()%20;//%20是为了保证使rand出现的随机数尽量的出现在地图范围内food.hang=x;food.lie=y;
}
void initNcurses()
{initscr();keypad(stdscr,1);noecho();//不要把无关的东西打印在界面上 
}struct Snake* head=NULL;//将蛇的头节点定义为全局变量
struct Snake* tail=NULL;//将蛇的尾节点定义为全局变量,防止错误过多
int key;
int dir;int hasFood(int i,int j)
{if(food.hang==i && food.lie==j){return 1;}return 0;
}
int hasSnakeNode(int i,int j)
{struct Snake *p;p=head;while(p!=NULL){if(p->hang==i && p->lie==j){return 1;}p=p->next;}return 0;
}
void map()
{int hang;int lie;move(0,0);//在每次调用地图的时候用move函数将光标移动到地图的第一个方格的位置for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else if(hasSnakeNode(hang,lie)){printw("[]");}else if(hasFood(hang,lie)){printw("**");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");}}printw("By Feng Hui Nan.");
}
void addSnake()
{struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));new->next=NULL;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;}tail->next=new;tail=new;
}
void initSnake()//蛇的初始化函数
{struct Snake*p;dir=RIGHT;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;addSnake();addSnake();addSnake();
}
void deleSnake()
{struct Snake*p;p=head;head=head->next;free(p);
}int ifSnakeDie()
{struct Snake*p;p=head;if(tail->hang<0 || tail->lie==20 || tail->lie==0 || tail->hang==20){return 1;}while(p->next!=NULL){if(p->hang==tail->hang && p->lie==tail->lie){return 1;}p=p->next;}return 0;}
void* moveSnake()
{addSnake();//在蛇的尾部添加节点并且删除节点后然后判断尾节点的行和列是否达到边界if(hasFood(tail->hang,tail->lie)){initFood();}else{deleSnake();}if(ifSnakeDie()){initSnake();}
}
void* refreshUi()//封装的界面刷新函数
{while(1){moveSnake();map();//删除后再次刷新地图显示移动后的界面refresh();//刷新界面函数//sleep(1);//每隔一秒蛇移动一下,并刷新界面,这行控制蛇的移动速率usleep(150000);//sleep以秒为单位有点慢,usleep以微秒为单位此处控制蛇的移动速度}
}
void turn(int direction)//这个函数的作用是当蛇在竖直方向上运动时使上下键无效//在水平方向运动时,左右方向键无效
{if(abs(dir)!=abs(direction)){dir=direction;}
}
void* changeDir()
{while(1){key=getch();switch(key){case KEY_DOWN:turn(DOWN);break;case KEY_UP:turn(UP);break;case KEY_RIGHT:turn(RIGHT);break;case KEY_LEFT:turn(LEFT);break;}}}
int main()
{initNcurses();initSnake();map();pthread_t t1;pthread_t t2;int *pthread1=NULL;int *pthread2=NULL;pthread_create(&t1,NULL,changeDir,NULL);pthread_create(&t2,NULL,refreshUi,NULL);
//      pthread_join(t1,(void**)&pthread1);
//      pthread_join(t2,(void**)&pthread2);while(1);getch();endwin();//没有这行会破坏shell终端return 0;
}

编译的时时候记得要加-lpthread、-lcurses

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/272265.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#实现简体繁体转换代码示例

//简体转繁体 public static string _ConvertChinTrad(string strInput) { EncodeRobert edControl new EncodeRobert(); string strResult ""; if (strInput null) return strResult; if (strInput.ToString().Length > 1) strResult edControl.SCTCConvert(…

java基础JDK的安装和环境变量的配置

JRE和JDK&#xff1a; JRE是java程序运行时环境&#xff0c;包含JVM&#xff08;相当于java在不同操作系统上运行时java和操作系统之间的翻译&#xff0c;保证java程序的跨平台&#xff09;和运行时所需要的核心库。所以我们想要运行一个已有的java程序&#xff0c;那么只需要…

C#通过SMTP发送邮件代码示例

1、新建SMTP.cs类库文件 public class SMTP { /// <summary> /// SMTP服务器 /// </summary> public string smtp { get; set; } /// <summary> /// SMTP服务器端口 /// </summary> public int port { get; set; } /// <summary> /// 发件人 ///…

docker下载tomact

docker run -it -p 8080:8080 tomcat 比如下载tomcat,你现在去访问&#xff0c;先访问docker里面的tomcat, 左边的8080是对外暴露的服务端口&#xff0c;对应着右边的8080是tomact的实际端口 下载tomcat 启动tomcat docker run -it -p 8080:8080 tomcat

Wijmo 2016年蓝图

2015年很快就过去了&#xff0c;这是 Wijmo 重要的一年&#xff0c;尤其是对 Wijmo5。脱离传统的小部件&#xff0c;重新写一套 JS 控件&#xff0c;现在看来这个决定是正确的。用 TypeScript 写 Wijmo5&#xff0c;意味着我们没有任何依赖&#xff0c;不再需要 jQuery&#xf…

IDEA安装和运行HelloWorld

IDEA安装&#xff1a; IDEA中Hello World步骤&#xff1a; ① ②点击创建空项目&#xff0c;下一步 ③ ④在打开后会弹出以下界面&#xff0c;然后点击新建模块 ⑤点击新建模块后出现以下界面&#xff0c;选择java并选择JDK的安装路径。 ⑥然后修改模块名称&#xff0c;点击…

C#获取电脑IP、MAC地址示例代码

/// <summary> /// 使用 C# 自带的类库实现计算机信息获取 /// </summary> public class DefaultDeviceInfo { public virtual string GetCpuId() { try { string cpuInfo " "; ManagementClass cimobject new ManagementClass("Win32_Processor…

docker运行随机分配端口

docker run -d -it -P tomcat -P这个是大写的P&#xff0c;表示随机分配端口 执行后可以看到32768为随机分配的端口&#xff0c;8080是tomcat端口 测试成功

C# Stream 和 byte[] 之间的转换

/// <summary> /// 将 Stream 转成 byte[] /// </summary> public byte[] StreamToBytes(Stream stream) { byte[] bytes new byte[stream.Length]; stream.Read(bytes, 0, bytes.Length); // 设置当前流的位置为流的开始 stream.Seek(0, SeekOrigin.Begi…

docker下如何进入到容器中

1:查看容器 docker ps -a 2:进入容器 docker exec -it e3cc80415dc7 /bin/bash 退出容器 exit

java方法和 IDEA Debug调试

方法的概述&#xff1a; 方法就是将具有独立功能的代码块&#xff0c;组织成为一个整体&#xff0c;使其具有特殊功能的代码集。我感觉方法就是类里面的函数 注意&#xff1a; 方法必须先创建才可以使用&#xff0c;该过程称为方法的定义。方法创建后并不是直接运行的&#xf…

编译性语言、解释性语言和脚本语言的区别

计算机是不能理解高级语言&#xff0c;当然也就不能直接执行高级语言了。计算机只能直接理解机器语言&#xff0c;所以任何语言&#xff0c;都必须将其翻译成机器语言&#xff0c;计算机才能运行高级语言编写的程序。 一、翻译和解释的不同 翻译的方式有两种&#xff0c;一个是…

为什么JAVA的垃圾回收机制无法避免内存泄漏

一、本文参考&#xff1a;1.《深入理解java虚拟机 JVM高级特性与最佳实践》2.http://coderevisited.com/memory-leaks-in-java/二、对象已死的判定方法要进行JVM中对象回收首先要判断对象是否已经死亡&#xff0c;判断的方法有如下几个&#xff1a;1.引用计数法给对象中添加一个…

【代码笔记】iOS-点击城市中的tableView跳转到旅游景点的tableView,下面会有“显示”更多。...

一&#xff0c;效果图。 二&#xff0c;工程图。 三&#xff0c;代码。 RootViewController.h #import <UIKit/UIKit.h>interface RootViewController : UIViewController <UITableViewDelegate,UITableViewDataSource> {UITableView * _tableView;NSMutableArray …

Android Studio项目结构介绍

新建一个空的工程会出现以下界面&#xff1a; 从上往下依次是&#xff1a;项目名称、包名、项目存储的位置、项目所用的语言、项目所用的Android的系统版本。 进入之后右侧可选择界面的展示结构&#xff1a; 如果选择Android则会出现下面的结构展示&#xff1a;MainActivity是…

对于Ping的过程,你真的了解吗?

作者&#xff1a; 木木匠链接&#xff1a;https://juejin.im/post/5c15ec0f6fb9a049ec6af8b2一、概览对于ping命令&#xff0c;想必只要是程序员都知道吧&#xff1f;当我们检查网络情况的时候&#xff0c;最先使用的命令肯定是ping命令吧&#xff1f;一般我们用ping查看网络情…