Linux平台下基于ncurses的贪吃蛇

一.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;
}

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值