一.ncurses
1.为什么要用ncurses???
C语言的键盘输入函数在接收时都需要敲回车来进行确认,而贪吃蛇作为一个游戏,需要满足时效性和操作方便,ncurses的键盘输入函数不需要敲回车即可接收,非常好的满足了我们的需求
2.ncurses库的基本使用方法
# include <curses.h>
int main(){
initscr(); //ncurses界面的初始化函数
/* noecho(); //时输入不显示在交互界面中
cbreak(); */
printw("This is a ncurses window!"); //在ncurse模式下的打印函数
getch(); //等待用户输入
endwin(); //退出程序,调用函数来恢复shell终端显示,否则shell终端字乱码
}
3.ncurses库对于方向键的定义
ncurses 的上下左右 键:
#define KEY_DOWN 0402
#define KEY_UP 0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405
注:在使用ncurses库定义的方向键时应提前使用函数keypad(stdscr,1)来进行启用
二.贪吃蛇的实现
1.界面的绘制
用“[]”来表示蛇身,分别用“--”,“|”表示上下边界,用“##”表示食物
void gamePic()
{
int line;
int column;
move(0,0);
for(line = 0; line < 20; line++){
if(line == 0){
for(column = 0; column < 20; column++){
printw("--");
}
printw("\n");
}
for(column = 0; column <= 20; column++){
if(column == 0 || column == 20){
printw("|");
}else if(hasSnakeNode(line,column)){
printw("[]");
}else if(hasFood(line,column)){
printw("##");
}else{
printw(" ");
}
}
printw("\n");
if(line == 19){
for(column = 0; column < 20; column++){
printw("--");
}
printw("\n");
printw("By CCY key = %d",key);
}
}
}
在进行页面刷新时需将光标移到开头,否则会在当前光标位置即结尾位置开始生成新地图,所以需要调用move(0,0)将光标移到开头,再进行rfresh()刷新
其中hasSnakeNode()和hasFood()函数均为判断当前位置的具体事物的函数
原型均为
int hasSnakeNode(int x, int y)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->line == x && p->column == y){
return 1;
}
p = p->next;
}
return 0;
}
//若为判断食物可直接写入if语句,无需遍历链表
2.贪吃蛇身子和移动的实现
void addNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP:
new->line = tail->line - 1;
new->column = tail->column;
break;
case DOWN:
new->line = tail->line + 1;
new->column = tail->column;
break;
case LEFT:
new->line = tail->line;
new->column = tail->column - 1;
break;
case RIGHT:
new->line = tail->line;
new->column = tail->column + 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->line = 1;
head->column = 1;
head->next = NULL;
tail = head;
addNode();
addNode();
addNode();
}
贪吃蛇的运动可以视为删去头节点,添加尾节点,所以运用尾插法进行蛇身子的添加
在initSnake()函数中应进行是否是死亡重生的判断,即判断head节点是否为空,若为空直接创建蛇身,若不为空则应删除完节点再创建,创建一个临时节点来接收抛弃的节点并进行free(),避免抛弃的节点继续占用空间
3.移动和页面刷新无法同时进行问题的结局
在单线程中只能存在一个while循环来进行操作,所以无法及时进行刷新和移动,在这里引入线程(pthread),运用多线程来实现移动和刷新的同时进行
void *refreshInterFace()
{
while(1){
moveSnake();
gamePic();
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;
}
}
}
//在主函数中创建线程
pthread_t th1;
pthread_t th2;
pthread_create(&th1, NULL, refreshInterFace, NULL);
sleep(1);
pthread_create(&th2, NULL, changeDir, NULL);
//加入while死循环避免线程退出
while(1);
4.实现食物的随机出现
调用rand()函数对食物的行列值随机赋初值
5.解决蛇的不合理走位
蛇不能向左移动时立即转向右移动,这不符合常理(PS:如果觉得炫酷其实也无所谓)
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
在获取到方向键时判断是不是和上一次方向的绝对值相同,若相同则为不合理走位,不进行转向
三.完结及源码
以下源码在配置好ncurses环境后执行
gcc snake.c -o snake -lncurses -lpthread
注:-lncurses -lpthread均为链接所调用的库
即可运行
源代码:
#include <ncurses.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake
{
int line;
int column;
struct Snake *next;
};
struct Food
{
int line;
int column;
};
struct Food food;
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;
void initFood()
{
food.line = rand()%20;
food.column = rand()%20;
}
void initNcurses()
{
initscr();
keypad(stdscr,1);
noecho();
}
int hasSnakeNode(int x, int y)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->line == x && p->column == y){
return 1;
}
p = p->next;
}
return 0;
}
bool hasFood(int x, int y)
{
if(food.line == x && food.column == y){
return 1;
}
return 0;
}
void gamePic()
{
int line;
int column;
move(0,0);
for(line = 0; line < 20; line++){
if(line == 0){
for(column = 0; column < 20; column++){
printw("--");
}
printw("\n");
}
for(column = 0; column <= 20; column++){
if(column == 0 || column == 20){
printw("|");
}else if(hasSnakeNode(line,column)){
printw("[]");
}else if(hasFood(line,column)){
printw("##");
}else{
printw(" ");
}
}
printw("\n");
if(line == 19){
for(column = 0; column < 20; column++){
printw("--");
}
printw("\n");
printw("By CCY key = %d",key);
}
}
}
void addNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));
new->next = NULL;
switch(dir){
case UP:
new->line = tail->line - 1;
new->column = tail->column;
break;
case DOWN:
new->line = tail->line + 1;
new->column = tail->column;
break;
case LEFT:
new->line = tail->line;
new->column = tail->column - 1;
break;
case RIGHT:
new->line = tail->line;
new->column = tail->column + 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->line = 1;
head->column = 1;
head->next = NULL;
tail = head;
addNode();
addNode();
addNode();
}
void deleteNode()
{
struct Snake *p;
p = head;
head = head->next;
free(p);
}
int ifSnakeDie()
{
struct Snake *p;
p = head;
if(tail->line < 0 || tail->line == 20 || tail->column == 0|| tail->column == 20){
return 1;
}
while(p->next != NULL){
if(p->line == tail->line && p->column == tail->column){
return 1;
}
p = p->next;
}
}
void moveSnake()
{
addNode();
if(hasFood(tail->line,tail->column)){
initFood();
}else{
deleteNode();
}if(ifSnakeDie()){
initSnake();
}
}
void *refreshInterFace()
{
while(1){
moveSnake();
gamePic();
refresh();
usleep(100000);
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
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;
pthread_t th2;
initSnake();
initNcurses();
gamePic();
pthread_create(&th1, NULL, refreshInterFace, NULL);
sleep(1);
pthread_create(&th2, NULL, changeDir, NULL);
while(1);
endwin();
return 0;
}