一、Ncurses简介
curses是一个在命令行下面的图形函数库,而ncurses的意思是 new curses。
当对使用curses函数库的程序进行编译时,你必须在程序中包含头文件curses.h,并在编译命令行中用-lcurses选项来链接curses函数库。
Ncurses相关函数简介
initscr();//ncurse界面的初始化函数,打开curses模式 进入终端
endwin();//程序退出,关闭窗口stdscr。
printw("This is a curses window.\n");//在ncurse模式下的printf,如果没有这句话,程序就退出了
//看不到运行的结果,也就是看不到上面那句话
getch(); //用于获取用户的键盘输入。它将等待用户按下一个键,并返回该键的ASCII码值。
//以下是一个示例:
int dir;
dir = getch();//可以使用getch();来实现交互,等待用户按下任意键后继续执行程序,
//例如本文的按下方向键改变贪吃蛇移动方向。
keypad(stdscr,1);//这个函数允许使用功能键,例如:F1、F2、方向键等功能键。几乎所有的交互式程
//序都需要使用功能键,因为绝大多数用户界面主要用方向键进行操作。
//使用keypad(stdscr,TURE)就为“标准屏幕”(stdscr)激活了功能键。
move();//移动光标的函数,move函数用来将逻辑光标的位置移到指定地点,
//屏幕坐标以左上角(0,0)为起点。
//在贪吃蛇移动时,我们希望地图刷新的位置不变,需要设置光标位置不变
refresh();//刷新屏幕,在绘制或修改界面后,需要使用refresh();函数来刷新终端屏幕,
//以便用户可以看到更新后的内容。
echo();//设置回显模式,当echo模式设置后,它使得在键盘上输入的每一个字符都在终端屏幕上
//当前光标处显示出来,默认是回显模式
noecho(); //关闭回显模式,非回显模式下按键通常用来控制屏幕的操作而不是用来进行字符输入。
//(按下一个方向键后,屏幕并不显示键值)
这两个函数用来控制是否将从键盘输入的字符显示在终端上。调用noecho()函数禁止输入的字符出现在屏幕
上。也许程序员希望用户在进行控制操作时,需要屏蔽掉控制字符(如组合键操作),或调用getch()函数
读取键盘字符时,不想显示输入的字符(如在控制台输入登陆密码)。大多数的交互式应用程序在初始化
时会调用noecho()函数,用于在进行控制操作时不显示输入的控制字符。这两个函数给予程序员很大的灵活
性,使程序员可以在窗口中的任意地方,实现输入字符的显示和屏蔽,而不需要刷新屏幕。
二、贪吃蛇代码实现
贪吃蛇的游戏逻辑很简单,首先要确定游戏地图的边框,然后考虑贪吃蛇如何移动,移动过程中吃到食物身体变长的同时食物重新刷新,然后碰到地图边界或者自己的身体会死亡,死亡后食物要重新复位初始化,食物是在地图上随机刷新。下面,我就根据这个逻辑将贪吃蛇小游戏分为地图界面、贪吃蛇身体显示、控制贪吃蛇移动和食物刷新四个部分来写。
1、贪吃蛇的地图绘制
-
地图规划:大小20×20
-
地图竖直方向上的边界“|”
-
地图水平方向上的边界”–”
#include <curses.h>//ncurses的头文件
void initNcurses()
{
initscr();//ncurses初始化函数
}
void snakeMap()//贪吃蛇地图界面
{
int row,col;
for(row=0; row<20; row++){
if(row == 0){
for(col=0; col<20; col++){
printw("--");//第一行用--代表上边界
}
printw("\n");
}
if(row>=0 || row<=19){
for(col=0; col<=20; col++){
if(col ==0 || col == 20){
//这里为了对齐上边框用21列
printw("|");//第0列和第21列用|代表边界
}else{
printw(" ");//边框中间都用两个空格代替
}
}
printw("\n");
}
if(row == 19){
for(col=0; col<20; col++){
printw("--");//第20行用--代表下边界
}
printw("\n");
printw("By Zhang\n");//地图打印完后,可以自定义一些显示内容
}
}
}
int main()
{
initNcurses();//ncurses初始化
snakeMap();//地图界面
getch();//按下任意键退出地图界面
endwin();//程序退出函数
return 0;
}
2、贪吃蛇身体显示
贪吃蛇的身子用”[ ]”表示。
贪吃蛇显示身体的关键是身体节点坐标和地图左边产生关系:先创建贪吃蛇的一个节点,然后用链表创建多个连在一起的节点构成贪吃蛇的身体。贪吃蛇身体创建完后用地图扫描身体只要和地图坐标相同就显示相关身体节点。
贪吃蛇的节点:
struct Snake{
//贪吃蛇身子节点
int row;//行坐标
int col;//列坐标
struct Snake *next;//下一个节点的位置(地址/指针)
};
显示贪吃蛇的一个节点:设置一个节点位置,假设头节点行列坐标都是2,第一个节点的头即尾,然后把头节点的值赋给尾巴节点。在地图上扫描这个节点如果有就显示[ ]。
struct Snake *head = NULL;//贪吃蛇的头节点
struct Snake *tail = NULL;//贪吃蛇尾巴节点
head->row = 2;
head->col = 2;
head->next = NULL;
tail = head;
if(head->row == row && head->col == col){
printw("[]");
}
头节点创建完成后增加节点和头节点坐标构成关系就成了一条蛇。
#include <curses.h>
#include <stdlib.h>
struct Snake{
//贪吃蛇身子节点
int row;//行坐标
int col;//列坐标
struct Snake *next;//下一个节点的位置(地址/指针)
};
struct Snake *head = NULL;//为了方便操作定义全局变量贪吃蛇的头节点
struct Snake *tail = NULL;//为了方便操作定义全局变量贪吃蛇的尾巴节点
void initNcurses()
{
initscr();
}
int ifHaveSnake(int row, int col)
{
struct Snake *p;//定义一个变量
p = head; //将头节点位置赋给变量,让变量从头节点开始遍历蛇的身体
while(p != NULL){
if(p->row == row && p->col == col){
return 1;//地图扫描蛇身,如果和地图坐标相同就返回1让地图显示蛇身
}
p = p->next;
}
return 0;
}
void addSnakeNode()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));//开辟一个内存空间存放新节点
new->row = tail->row;//新节点和尾巴节点产生关系,尾巴的节点和新节点的行坐标相同
new->col = tail->col+1;//尾巴的节点的列坐标是新节点的列坐标加1
new->next = NULL;//新节点的下一个节点指向为空
tail->next = new;//尾巴节点的下一个节点指向新节点
tail = new;//让新节点成为尾巴节点
}
void initSnake()
{
head = (struct Snake*)malloc(sizeof(struct Snake));//开辟一个内存空间存放头节点
head->row = 2;//头结点初始化行坐标为2
head->col = 2;//头结点初始化列坐标为2
head->next = NULL;//头结点下一个节点指向为空
tail = head;//第一个节点,头即尾
addSnakeNode();//增加新节点
addSnakeNode();
addSnakeNode();
}
void snakeMap()
{
int row,col;
for(row=0; row<20; row++){
if(row == 0){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
}
if(row>=0 || row<=19){
for(col=0; col<=20; col++){
if(col ==0 || col == 20){
printw("|");
}else if(ifHaveSnake(row,col)){
printw("[]");//返回值为1显示蛇身
}
else{
printw(" ");
}
}
printw("\n");
}
if(row == 19){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
printw("By Zhang\n");
}
}
}
int main()
{
initNcurses();
initSnake();
snakeMap();
getch();
endwin();
return 0;
}
3、控制贪吃蛇方向移动
贪吃蛇的移动本质上是尾巴节点增加然后删除头节点,中间身体不动。
在贪吃蛇移动环节会用到一些ncurses函数:
keypad(stdscr,1);//开启功能键(方向键)的使用功能
move(0,0);//让地图的光标始终不变,在控制蛇身体移动的时候,我们希望地图界面刷新的同时界面光标不会跟随按键移动
refresh();//刷新地图界面函数
ncurses库中的方向键值:
#define KEY_DOWN 0402 /*down-arrow key */
#define KEY_UP 0403 /*up-arrow key */
#define KEY_LEFT 0404 /*left-arrow key */
#define KEY_RIGHT 0405 /*right-arrow key*/
在控制蛇身体移动的同时,地图界面也需要不断刷新,这里需要用到线程让控制方向和地图刷新两个任务同时进行,对于线程本文不做重点介绍,会设置和使用即可。
#include <pthread.h>//线程的头文件
pthread_t th1,th2;//设置线程名
pthread_create(&th2,NULL,refreshMap,NULL);//创建刷新地图的线程
pthread_create(&th1,NULL,changeDir,NULL);//创建改变方向的线程
#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
#define UP 1//宏定义贪吃蛇的方向
#define DOWN -1//这里使用绝对值可以解决控制方向时,按上和下(左和右)都可以改变向
#define LEFT 2
#define RIGHT -2
struct Snake{
int row;
int col;
struct Snake *next;
};
int dir;//定义改变方向的全局变量
int key;//定义ncurses库函数中的方向键值
struct Snake *head = NULL;
struct Snake *tail = NULL;
void initNcurses()
{
initscr();
keypad(stdscr,1);//开启功能键(方向键)的使用功能
}
int ifHaveSnake(int row, int col)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->row == row && p->col == col){
return 1;
}
p = p->next;
}
return 0;
}
void addSnakeNode()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP://向上,行坐标-1,列坐标不变
new->row = tail->row-1;
new->col = tail->col;
break;
case DOWN://向下,行坐标+1,列坐标不变
new->row = tail->row+1;
new->col = tail->col;
break;
case LEFT://向左,行坐标不变,列坐标-1
new->row = tail->row;
new->col = tail->col-1;
break;
case RIGHT://向右,行坐标不变,列坐标+1
new->row = tail->row;
new->col = tail->col+1;
break;
}
tail->next = new;
tail = new;
}
void deletSnakeNode()
{
struct Snake *p = (struct Snake*)malloc(sizeof(struct Snake));
p = head;
head = head->next;//删除头节点就是让头节点指向下一个节点
free(p);//释放头节点的内存空间
}
void initSnake()
{
struct Snake *p;
dir = RIGHT;//初始化蛇的方向向右
while(head != NULL){
//当蛇死亡时,蛇会初始化,但“尸体”还会占据内存空间,所以需要遍历身体释放空间
p = head;
head = head->next;
free(p);
}
head = (struct Snake*)malloc(sizeof(struct Snake));
head->row = 2;
head->col = 2;
head->next = NULL;
tail = head;
addSnakeNode();
addSnakeNode();
addSnakeNode();
}
void moveSnake()
{
addSnakeNode();//蛇的移动就是增加尾节点删除头节点
deletSnakeNode();
if(tail->row < 0 || tail->row == 20 || tail->col == 0 || tail->col == 20){
initSnake();//蛇尾撞到边界蛇死亡,初始化蛇的位置
}
}
void snakeMap()
{
int row,col;
move(0,0);
for(row=0; row<20; row++){
if(row == 0){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
}
if(row>=0 || row<=19){
for(col=0; col<=20; col++){
if(col ==0 || col == 20){
printw("|");
}else if(ifHaveSnake(row,col)){
printw("[]");
}
else{
printw(" ");
}
}
printw("\n");
}
if(row == 19){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
printw("By Zhang\n");
printw("key = %d\n",key);
printw("dir = %d\n",dir);
}
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;//如果蛇的方向绝对值相同,不能改变方向上下(左右)不能同时改变
}
}
void* refreshMap()//地图刷新函数线程
{
while(1){
moveSnake();
snakeMap();
refresh();//地图界面刷新函数
usleep(100000);
}
}
void* changeDir()//控制方向函数线程
{
while(1){
key = getch();//接收键盘输入方向的键值
switch(key){
//判断键值来改变方向
case KEY_UP:
turn(UP);
break;
case KEY_DOWN:
turn(DOWN);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
int main()
{
pthread_t th1,th2;
initNcurses();
initSnake();
snakeMap();
pthread_create(&th2,NULL,refreshMap,NULL);//创建刷新地图界面线程
pthread_create(&th1,NULL,changeDir,NULL);//创建改变方向线程
while(1);//不让程序界面退出
getch();
endwin();
return 0;
}
4、贪吃蛇吃食物
贪吃蛇的食物用”##”表示
贪吃蛇的食物显示和身体类似,即扫描地图时如果食物坐标和地图坐标相同就显示食物
食物需要在地图中随机刷新,用到rand()函数
#include<stdlib.h>//rand()函数需要的头文件是:
rand();//rand()会返回一个范围在0到RAND_MAX(至少是32767)之间的伪随机数(整数)。
//rand()函数用来产生随机数,但是rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。
#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake{
int row;
int col;
struct Snake *next;
};
int dir;
int key;
struct Snake *head = NULL;
struct Snake *tail = NULL;
struct Snake food;//定义食物的结构体变量
void initNcurses()
{
initscr();
keypad(stdscr,1);
}
int ifHaveSnake(int row, int col)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->row == row && p->col == col){
return 1;
}
p = p->next;
}
return 0;
}
void addSnakeNode()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP:
new->row = tail->row-1;
new->col = tail->col;
break;
case DOWN:
new->row = tail->row+1;
new->col = tail->col;
break;
case LEFT:
new->row = tail->row;
new->col = tail->col-1;
break;
case RIGHT:
new->row = tail->row;
new->col = tail->col+1;
break;
}
tail->next = new;
tail = new;
}
void deletSnakeNode()
{
struct Snake *p = (struct Snake*)malloc(sizeof(struct Snake));
p = head;
head = head->next;
free(p);
}
void initSnakeFood()
{
int x = rand()%20;//使用rand函数让食物随机生成
int y = rand()%20+1;
food.row = x;//让食物的行坐标等于随机生成的x
food.col = y;//让食物的行列标等于随机生成的y
food.next = NULL;
}
int ifHaveSnakeFood(int row, int col)
{
if(food.row == row && food.col == col){
return 1;//扫描地图,如果食物坐标和地图坐标相同,返回1让地图显示食物
}
return 0;
}
void initSnake()
{
dir = RIGHT;
initSnakeFood();//初始化蛇身体的同时让食物也随机刷新
struct Snake *p;
while(head != NULL){
p = head;
head = head->next;
free(p);
}
head = (struct Snake*)malloc(sizeof(struct Snake));
head->row = 2;
head->col = 2;
head->next = NULL;
tail = head;
addSnakeNode();
addSnakeNode();
addSnakeNode();
}
int ifSnakeDie()
{
struct Snake *p;
p = head;
while(p->next != NULL){
//当蛇吃的食物足够多时,移动过程中尾巴碰到身体就死亡
if(p->row == tail->row && p->col == tail->col){
return 1;
}
p = p->next;
}
if(tail->row < 0 || tail->row == 20 || tail->col == 0 || tail->col == 20){
return 1;
}
return 0;
}
void moveSnake()
{
addSnakeNode();
if(tail->row == food.row && tail->col == food.col){
initSnakeFood();
}else{
deletSnakeNode();
}
if(ifSnakeDie()){
initSnake();//如果蛇死就初始化蛇的身体
}
}
void snakeMap()
{
int row,col;
move(0,0);
for(row=0; row<20; row++){
if(row == 0){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
}
if(row>=0 || row<=19){
for(col=0; col<=20; col++){
if(col ==0 || col == 20){
printw("|");
}else if(ifHaveSnake(row,col)){
printw("[]");
}else if(ifHaveSnakeFood(row,col)){
printw("##");
}
else{
printw(" ");
}
}
printw("\n");
}
if(row == 19){
for(col=0; col<20; col++){
printw("--");
}
printw("\n");
printw("By Zhang\n");
printw("key = %d\n",key);
printw("dir = %d\n",dir);
}
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void* refreshMap()
{
while(1){
moveSnake();
snakeMap();
refresh();
usleep(100000);
}
}
void* changeDir()
{
while(1){
key = getch();
switch(key){
case KEY_UP:
turn(UP);
break;
case KEY_DOWN:
turn(DOWN);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
int main()
{
pthread_t th1,th2;
initNcurses();
initSnake();
snakeMap();
pthread_create(&th2,NULL,refreshMap,NULL);
pthread_create(&th1,NULL,changeDir,NULL);
while(1);
getch();
endwin();
return 0;
}
至此,贪吃蛇简易小游戏算是已经完成,如有不足烦请指正,下面是贪吃蛇游戏的运行画面。
Linux环境下基于Ncurses的贪吃蛇小游戏