目录
ncurse库的基本使用
为啥要用ncurse库?
对于贪吃蛇我们需要用到方向键控制方向;在按下方向键就需要立马响应的话,需要无缓冲输入。而nucrse库中的函数getch()函数可以满足需求。
ncurse库的基本使用
#include <curses.h>
int main ()
{
initscr();//ncurse界面初始化函数
printw("this is a ncurses window\n");//在ncurses模式下的printf
getch();//等待用户输入,如果没有这句话,程序就退出了
endwin();//程序退出,该函数来恢复shell终端的显示,如果没有这句话,终端会出现乱>码
return 0;
}
ncurse获取上下左右键值
ncurse库安装的指令:sudo apt-get install libncurses5-dev(编译程序时要链接该库 -lcurses)
1.路径切换到 /usr/include/ncursesw/找到curses.h头文件,vi进入编辑该头文件。
2.输入/KEY_LEFT就可以查找到
#include <curses.h>
int main ()
{
initscr();//ncurse界面初始化函数
keypad(stdscr,1);//在stdscr中接受键盘的功能键
int dir;
while(1)
{
printw("plese input dir:\n");
dir = getch();
switch(dir)
{
case KEY_UP:
printw("up\n");
break;
case KEY_DOWN:
printw("down\n");
break;
case KEY_LEFT:
printw("left\n");
break;
case KEY_RIGHT:
printw("right\n");
break;
}
}
getch();//等待用户输入,如果没有这句话,程序就退出了
endwin();//程序退出,该函数来恢复shell终端的显示,如果没有这句话,终端会出现乱码
return 0;
}
地图规划
地图大小:20×20
地图竖直方向上的边界"|"
地图水平方向上的边界"--"
贪吃蛇的身子"[ ]"
贪吃蛇的食物"##"
使用for嵌套循环
地图的实现:
地图是一个20乘20的方格通过for嵌套循环打印,外层循环控制行,当循环到第一行或者第二十行时,打印20列 "--"符号;在第二行至第十九行打印时,如果是第一列或者是第二十列,则打印"|";否则打印" "符号。(打印完每一行后都需要打印一个换行符)
void initpicture()
{
int row;
int column;
for(row = 0;row <= 20; row++)
{
if(row == 0 || row == 20)
{
for(column = 0;column < 20;column++)
{
printw("--");
}
printw("\n");
}
else
{
for(column = 0;column <= 20;column++)
{
if(column == 0 || column == 20)
{
printw("|");
}
else
{
printw(" ");
}
}
printw("\n");
}
}
}
使用结构体数据显示贪吃蛇身
贪吃蛇身子节点:
1. 行坐标
2. 列坐标
3. 下一个节点的位置(地址/指针)
struct Snake
{
int row; //行坐标
int column; //列坐标
struct Snake *next;
}
2.在地图打印空格之前判断蛇节点的row和column是否等于当前的row和column , 如果是 就 打印"[ ]"作为蛇身的节点。
假设蛇身的节点是:行坐标2,列坐标2
if(row == x.row && column == x.lie)
{
printw("[ ]");
}
显示贪吃蛇多个节点
1.先手动静态创建几个节点,并且使用链表将节点串起来,在打印空格之前判断是不方便的,不能把所有的节点都一一进行判断。
可以封装一个hasSnakeBody( )函数进行判断,然后在这个函数中进行蛇身链表的遍历判断。如果找到相同的节点,函数返回1;否则函数返回0。
int hasSnakeBody(int i,int j)
{
Node *p = &node1;
while(p)
{
if(p->row == i && p->column == j)
{
return 1;
}
p = p->next;
}
return 0;
}
贪吃蛇身子使用链表动态添加
蛇身的链表创建是静态创建的,如果要增加节点也是非常不方便,需要手动添加。现在使用链表动态添加,定义两个全局变量;一个是头节点head,一个是尾节点tail;刚刚开始时head等于tail。使用尾插法创建新的节点。
void initSnake()
{
head = (Node*)malloc(sizeof(Node));
head->row = 2;
head->column = 2;
// head->next = NULL;
tail=head;
addNode();
addNode();
addNode();
}
void addNode()
{
Node *new = (Node*)malloc(sizeof(Node));
new->next = NULL;
new->row = tail->row+1;
new->column = tail->column;
tail->next = new;
tail = new;
}
贪吃蛇移动
对蛇身链表进行改动 删除一个头节点,增加一个尾节点。
然后再次打印地图就会形成移动的效果
void deleteNode()
{
Node *p = head;
head = head->next;
free(p);
}
void moveSnake()
{
addNode();
deleteNode();
}
要在打印地图函数里面加上光标定位函数move(0,0),这样打印就会显示在同一位置。
获取移动的方向和刷新界面
需要创建两个线程,一个线程刷新界面;另一个线程获取方向键,而方向键的改变可以影响addNode()函数新增节点的里面的数据的改变,这样就能形成移动的效果。
刷新界面线程:
void* refreshPic()
{
while(1)
{
moveSnake();
initpicture();
usleep(100000);
refresh();
}
}
获取方向线程:
void* changeDir()
{
for(;;)
{
dir = getch();
}
}
修改addNode( )函数,根据方向键的值来判断新增节点的值
void addNode()
{
Node *new = (Node*)malloc(sizeof(Node));
new->next = NULL;
switch(dir)
{
case KEY_RIGHT:
new->row = tail->row;
new->column = tail->column+1;
break;
case KEY_LEFT:
new->row = tail->row;
new->column = tail->column-1;
break;
case KEY_DOWN:
new->row = tail->row+1;
new->column = tail->column;
break;
case KEY_UP:
new->row = tail->row-1;
new->column = tail->column;
break;
}
tail->next = new;
tail = new;
}
使用绝对值解决走位不合理
定义4个方向宏:
将键盘获取的键值转换成相应的宏,通过判断上一次方向的绝对值与这一次方向绝对值是否相等;如果不相等,就改变方向的值。
void turn(int direction)
{
if(abs(dir) != abs(direction))
{
dir = direction;
}
}
之前是直接通过键值判断的,所以addNode( )和changdir( )函数都需要修改一下:
void addNode()
{
Node *new = (Node*)malloc(sizeof(Node));
new->next = NULL;
switch(dir)
{
case right:
new->row = tail->row;
new->column = tail->column+1;
break;
case left:
new->row = tail->row;
new->column = tail->column-1;
break;
case down:
new->row = tail->row+1;
new->column = tail->column;
break;
case up:
new->row = tail->row-1;
new->column = tail->column;
break;
}
tail->next = new;
tail = new;
}
void* changeDir()
{
int key;
for(;;)
{
key = getch();
switch(key)
{
case KEY_RIGHT:
turn(right);
break;
case KEY_LEFT:
turn(left);
break;
case KEY_DOWN:
turn(down);
break;
case KEY_UP:
turn(up);
break;
}
}
}
贪吃蛇食物
食物同样使用结构体来存储,打印地图时判断行和列是否和食物的数据相等,如果相等则显示食物为“##”。
void initFood()
{
food.row = rand()%20;
food.column = rand()%20;
}
判断尾节点是数据是否跟食物的数据是否重合,如果重合,重新生成一个食物;如果不重合,就删除一个头节点。
撞墙挂掉和撞自己挂掉
挂掉就相当于在打印地图之前,删除原来的链表数据;然后重新调用下initSnake( )函数重新初始化链表。
void deleteSnake()
{
Node *p;
while(head)
{
p = head;
head = head->next;
free(p);
}
return 0;
}
撞墙就判断尾节点否和墙重合:
void hitWall()
{
if(tail->row == 0 || tail->row == 20 || tail->column == 0 || tail->column == 20)
{
deleteSnake();
initSnake();
}
}
撞自己就判断尾节点是否和链表本身进行重合:
void hitSelf()
{
Node *p = head;
while(p->next != NULL)
{
if(tail->row == p->row && tail->column == p->column)
{
deleteSnake();
initSnake();
}
p = p->next;
}
}
全部代码
#include <curses.h>
#include <stdlib.h>
#include<pthread.h>
#include<unistd.h>
#define right 1
#define left -1
#define down 4
#define up -4
typedef struct node{
int row;
int column;
struct node *next;
}Node;
void initcurses();
void initpicture();
int hasSnakeBody(int i,int j);
void initSnake();
void addNode();
void deleteNode();
void moveSnake();
void* refreshPic();
void* changeDir();
void turn(int direction);
void initFood();
void deleteSnake();
void hitWall();
void hitSelf();
Node *head;
Node *tail;
Node food;
int dir;
int main ()
{
int dir;
pthread_t t1;
pthread_t t2;
initcurses();
initSnake();
initpicture();
pthread_create(&t1,NULL,refreshPic,NULL);
pthread_create(&t2,NULL,changeDir,NULL);
while(1);
getch();//等待用户输入,如果没有这句话,程序就退出了
endwin();//程序退出,该函数来恢复shell终端的显示,如果没有这句话,终端会出现乱码
return 0;
}
void initcurses()
{
initscr();//ncurse界面初始化函数
keypad(stdscr,1);//在stdscr中接受键盘的功能键
}
void initpicture()
{
int row;
int column;
move(0,0);
for(row = 0;row <= 20; row++)
{
if(row == 0 || row == 20)
{
for(column = 0;column < 20;column++)
{
printw("--");
}
printw("\n");
}
else
{
for(column = 0;column <= 20;column++)
{
if(column == 0 || column == 20)
{
printw("|");
}
else if(hasSnakeBody(row,column))
{
printw("[]");
}
else if(food.row == row && food.column == column)
{
printw("##");
}
else
{
printw(" ");
}
}
printw("\n");
}
}
printw("by lizhiwen\n");
}
int hasSnakeBody(int i,int j)
{
Node *p = head;
while(p)
{
if(p->row == i && p->column == j)
{
return 1;
}
p = p->next;
}
return 0;
}
void initSnake()
{
dir = right;
initFood();
head = (Node*)malloc(sizeof(Node));
head->row = 2;
head->column = 2;
// head->next = NULL;
tail=head;
addNode();
addNode();
addNode();
}
void addNode()
{
Node *new = (Node*)malloc(sizeof(Node));
new->next = NULL;
switch(dir)
{
case right:
new->row = tail->row;
new->column = tail->column+1;
break;
case left:
new->row = tail->row;
new->column = tail->column-1;
break;
case down:
new->row = tail->row+1;
new->column = tail->column;
break;
case up:
new->row = tail->row-1;
new->column = tail->column;
break;
}
tail->next = new;
tail = new;
}
void deleteNode()
{
Node *p = head;
head = head->next;
free(p);
}
void moveSnake()
{
addNode();
if(food.row == tail->row && food.column == tail->column)
{
initFood();
}
else
{
deleteNode();
}
hitWall();
hitSelf();
}
void* refreshPic()
{
while(1)
{
moveSnake();
initpicture();
usleep(90000);
refresh();
}
}
void* changeDir()
{
int key;
for(;;)
{
key = getch();
switch(key)
{
case KEY_RIGHT:
turn(right);
break;
case KEY_LEFT:
turn(left);
break;
case KEY_DOWN:
turn(down);
break;
case KEY_UP:
turn(up);
break;
}
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction))
{
dir = direction;
}
}
void initFood()
{
food.row = rand()%20;
food.column = rand()%20;
}
void deleteSnake()
{
Node *p;
while(head)
{
p = head;
head = head->next;
free(p);
}
}
void hitWall()
{
if(tail->row == 0 || tail->row == 20 || tail->column == 0 || tail->column == 20)
{
deleteSnake();
initSnake();
}
}
void hitSelf()
{
Node *p = head;
while(p->next != NULL)
{
if(tail->row == p->row && tail->column == p->column)
{
deleteSnake();
initSnake();
}
p = p->next;
}
}