C语言小项目:基于ncurses库和链表的贪吃蛇小游戏

目录

一、项目准备

二、项目步骤

(1)选择ncurses库的原因

(2)ncurses库的基本入门

(3)贪吃蛇地图的整体规划

(4)实现贪吃蛇第一个节点的显示

(5)显示贪吃蛇的完整身子

(6)实现贪吃蛇的右移

(7)实现贪吃蛇的撞墙死亡

(8)实现贪吃蛇的自由行走

(9)了解什么是线程

(10)用线程解决上述问题,实现贪吃蛇的分骚走位

(11)解决贪吃蛇的不合理走位

(12)实现贪吃蛇食物的打印

(13)实现食物的随机出现

(14)实现贪吃蛇咬到自己结束游戏,重新开始的操作

三、项目总结

一、项目准备

在Ubuntu的Linux系统上安装好ncurses图形库,不需要对ncurses库十分精通,只要会基本使用即可,该小项目的目的是对于C语言的一个总结与提高,提升自我的编程自信心。

二、项目步骤

(1)选择ncurses库的原因

在进行贪吃蛇游戏时,贪吃蛇的行进方向需要你按下上下左右键进行操控,如果使用C语言自带的函数,例如:scanf或者getchar之类的,需要你按下回车键,程序才能进行响应,而这显然是十分不方便的,但是ncurses库就很好的解决了这个问题。ncurses库自带的函数getch就能实现迅速便捷的贪吃蛇方向响应。

(2)ncurses库的基本入门

对于该项目而言,ncurses库我们不需要进行过于深入的学习,只需要知道一些基本的函数使用即可。下列程序中的函数,就是一个基于ncurses库的基本代码框架。

#include <curses.h>

int main()
{
        initscr();//ncurse界面的初始化函数
        printw("this is a curses window\n");//在ncurse模式下的printf
        getch();//等待用户的输入,如果没有这句话,程序就退出了,看不到运行的结果,也就是无法看到上面那句话
        endwin();//程序退出,恢复shell终端的显示,如果没有这句话,shell终端字乱码,坏掉

        return 0;
}

(3)贪吃蛇地图的整体规划

整个贪吃蛇地图的大小将它设置成一个20*20的近似正方形,使用"|"来表示左右边框,使用"--"来表示上下边框。

(4)实现贪吃蛇第一个节点的显示

(5)显示贪吃蛇的完整身子

注意,在这里,我们设置两个全局变量,struct Snake *head和struct Snake *tail,一个指向贪吃蛇的头,一个指向贪吃蛇的尾。在将第一个节点打印完后,将尾指向头,即:head = tail。每一次节点的添加,我们调用一个单独的函数去执行,并其使用尾插法实现。

(6)实现贪吃蛇的右移

贪吃蛇的移动,整体来说就是链表节点的删除与添加。我们首先实现贪吃蛇的右移,每当按键按下时,贪吃蛇右移一格,即左侧的头结点删除head = head->next,右侧再次添加一个新的节点。新节点的坐标应该是行不变,列加一。注意:不要忘记清楚垃圾节点。

(7)实现贪吃蛇的撞墙死亡

将贪吃蛇的尾节点坐标进行判断,判断其是否达到边界坐标。满足条件时,将贪吃蛇重新初始化。注意:不要忘记清楚垃圾节点。

(8)实现贪吃蛇的自由行走

在这里,我们发现了一个问题,地图需要实时刷新进行贪吃蛇位置的变更,这是一个while(1)的死循环,而获取键值也是一个实时读取的操作,因此也是一个while(1)死循环,代码执行逻辑上出现了问题,所以我们引入了线程的概念。

(9)了解什么是线程

(10)用线程解决上述问题,实现贪吃蛇的分骚走位

开辟两个线程,一个用来执行地图刷新操作,一个用来获取键值。

 pthread_create(&t1,NULL,refreshScreen,NULL);
 pthread_create(&t2,NULL,changeDir,NULL);

(11)解决贪吃蛇的不合理走位

在这里,我们使用绝对值法来解决问题,abs函数的作用便是取绝对值,我们将上下左右键,两两对应,宏定义为1,-1,2,-2之类的数就能成功解决问题。

(12)实现贪吃蛇食物的打印

(13)实现食物的随机出现

取随机数,C语言有一个自带的函数可以解决这个问题,rand()函数可以实现随机取数,我们只要再对它进行取余操作,便可以防止食物出现在地图以外的位置。

(14)实现贪吃蛇咬到自己结束游戏,重新开始的操作

当贪吃蛇的尾节点与自身除尾巴节点以外的其他节点进行比较后,若行列数相同,则初始化整个贪吃蛇,注意:不要忘记垃圾节点的清除(我们可以在每次贪吃蛇初始化之前进行这个操作)。

三、项目总结

到这里,整个项目就基本上完成了,程序上可能还存在着一些小问题,但是都可以通过对于细节的优化来解决 。总体上,项目的目的已经达到,是对于C语言能力的一个很好的提高。

项目代码如下:

snake.h

#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

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

#define UP     1
#define DOWN  -1
#define LEFT   2
#define RIGHT -2

void initNcurses();               /*ncurses库的初始化函数*/
void gameMap();                   /*贪吃蛇地图的初始化*/
int printSnakeNode(int i,int j);  /*在地图上打印贪吃蛇的节点*/
void initSnake();                 /*初始化贪吃蛇*/
void addNode();                   /*从尾部插入新节点*/
void deletNode();                 /*删除头结点*/
void moveSnake();                 /*实现贪吃蛇的移动*/
void *refreshScreen();            /*线程实现图像刷新*/
void *changeDir();                /*线程实现贪吃蛇方向的改变*/
void turn(int direction);         /*防止出现不合理走位*/
void creatFood();                 /*随机出现食物*/
int hasFood(int i,int j);         /*打印食物*/
int ifSnakeDie();                 /*判断贪吃蛇是否死亡*/

snake.c

#include "snake.h"

struct Snake *head = NULL;
struct Snake *tail = NULL;
struct Snake food;
int key;
int dir;

/*随机出现食物*/
void creatFood()
{
	int x = rand()%20;
	int y = rand()%19+1;

	food.hang = x;
	food.lie = y;
}

int hasFood(int i,int j)
{
	if(food.hang == i && food.lie == j){
		return 1;
	}
	return 0;
}

/*ncurses库的初始化函数*/
void initNcurses()
{
	initscr();//ncurse界面的初始化函数
	keypad(stdscr,1);//使用keypad函数,才可以使用键盘功能键
	noecho();//防止打印无关键值
}

/*贪吃蛇地图的初始化*/
void gameMap()
{
	int hang;
	int lie;

	move(0,0);//把光标的位置移到头,实现地图刷新时的覆盖
	for(hang=0;hang<20;hang++){
		if(hang == 0){
			for(lie=0;lie<20;lie++){
				printw("--");
			}
			printw("\n");
		}
		if(hang>=0 && hang<=19){
			for(lie=0;lie<=20;lie++){
				if(lie == 0 || lie == 20){
					printw("|");
				}else if(printSnakeNode(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 lyf,food.hang = %d,food.lie = %d\n",food.hang,food.lie);
}

/*在地图上打印贪吃蛇的节点*/
int printSnakeNode(int i,int j)
{
	struct Snake *p = head;
	while(p != NULL){
		if(p->hang == i && p->lie == j){
			return 1;
		}
		p = p->next;
	}
	return 0;
}

/*初始化贪吃蛇*/
void initSnake()
{	
	struct Snake *p = NULL;
	if(head != NULL){   //当贪吃蛇死亡后,把多余节点释放
		p = head;
		head = head->next;	
		free(p);
	}
	
	creatFood();
	dir = RIGHT;

	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->hang = 1;
	head->lie = 1;
	head->next = NULL;

	tail = head;
	
	addNode();
	addNode();
	addNode();
}

/*从尾部插入新节点*/
void addNode()
{
	struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));

	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;
	}
	new->next = NULL;
	tail->next = new;
	tail = new;

}

/*删除头结点*/
void deletNode()
{
	struct Snake *p = head;
	head = head->next;
	free(p);	
}

/*判断贪吃蛇是否死亡*/
int ifSnakeDie()
{
	struct Snake *p;
	p = head;
	if(tail->hang < 0 || tail->hang == 20 || tail->lie == 0 || tail->lie == 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()
{
	addNode();
	if(hasFood(tail->hang,tail->lie)){
		creatFood();
	}else{
		deletNode();
	}

	if(ifSnakeDie()){
		initSnake();
	}

}

/*线程实现图像刷新*/
void *refreshScreen()
{
	usleep(100000);
	while(1){
		moveSnake();
		gameMap();//刷新地图	
		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;
		}
	}
}

main.c 

#include "snake.h"

int main()
{
	pthread_t t1;
	pthread_t t2;

	initNcurses();
	initSnake();
	gameMap();

	pthread_create(&t1,NULL,refreshScreen,NULL);
	pthread_create(&t2,NULL,changeDir,NULL);

	while(1);
	getch();//等待用户的输入,如果没有这句话,程序就退出了,看不到运行的结果,也就是无法看到上面那句话
	endwin();//程序退出,恢复shell终端的显示,如果没有这句话,shell终端字乱码,坏掉

	return 0;
}

代码整体上还有很大的优化空间,但基本功能已经实现,就不做优化了。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值