linux操作环境下贪吃蛇项目总结
一.贪吃蛇项目介绍
1.项目简介
该项目分为四个阶段步骤
(1)使用ncurse图形库库
(2)首先建立一个20x20的游戏界面
(3)创建贪吃蛇的身子
(4)创建贪吃蛇的食物
2.科普ncurse库
1.ncurses(new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。
2.ncurses名字中的n意味着“new”,因为它是curses的自由软件版本。由于AT&T“臭名昭著”的版权政策,人们不得不在后来用ncurses去代替它。
3.ncurses是GNU计划的一部分,但它却是少数几个不使用GNU GPL或L.GPL授权的GNU软件之一。
3.安装Ncurse图型库
(1)安装命令
sudo apt-get install libncurses5-dev
4.linux 线程的基本介绍(除了深度学习会用即可)
(1)先看这个博主的文章掌握基本概念,我下面的代码分析会写到。
https://blog.csdn.net/qq_22847457/article/details/89371217
5.代码片段分析
(1)这部分首先是头文件的定义,以及介绍一下curses这个头文件的作用。
#include<curses.h> //可以在vi工具里面找到一些关于这个库的信息
#include<stdlib.h>
#include<pthread.h>//一下这两个头文件和linux线程有关,初学习就不用去深究
#include<unistd.h>//在百度上模仿其他up主的写法去模仿即可
#define UP 1 //这部分就涉及到curses这个头文件的特殊性
#define DOWN -1 //分别代表了上下左右
#define LEFT 2 //这样写的话提高代码的阅读性
#define RIGHT -2
struct Snake //这个结构体包含了贪吃蛇游戏界面的行和列以及节点位置
{
int hang; //定义游戏界面的行
int lie; //定义游戏界面的列
struct Snake *next;
};
(2)讲解贪吃蛇食物出现的随机性
(3)科普rand函数
rand()函数是Excel中产生随机数的一个随机函数。返回的随机数是大于等于 0 及小于 1 的均匀分布随机实数,rand()函数每次计算工作表时都将返回一个新的随机实数。
(4)noecho的使用(一下这一段代码都有涉及到以上的三点)
1.echo模式中的一个函数。
2.ECHO模式即回显模式,ECHO模式用来决定用户的输入是否立即回显。
3.echo()用来设置回显模式,noecho()关闭回显模式。
4.函数语法:int echo(),返回值为OK或ERR;int noecho(),返回值为OK或ERR。(默认情况下回显模式是打开的)
**这两个函数不用去深究知道怎么去合理的使用即可
struct Snake*head = NULL; //定义蛇的头结点
struct Snake*tail = NULL; //定义尾结点,为了避免野指针要定义为空
int key;
int dir; //定义了方向键
struct Snake food;//定义了贪吃蛇的食物
void initFood()
{
int x = rand()%20; //使得贪吃蛇出现的食物出现随机性
int y = rand()%20; //%20代表取余
food.hang = y; //将食物的行定义为y
food.lie = x; //将食物的列定义为x
}
void initNcurse()
{
initscr();
keypad(stdscr,1);
noecho(); //关闭回显模式,处理界面bug问题
}
(5)这两端代码主要是判断蛇身和食物是否会出现指定的界面里面
int hasSnakeNode(int i, int j) //这段代码讲的贪吃蛇身上的节点
{
struct Snake *p; //定义一个指针p指向头结点
p = head;
while(p != NULL){
if(p->hang == i && p->lie == j){ //判断头结点的行和列是否为定义的i和j
return 1;
}
p = p->next; //如果执行循环不符合则执行p指向p的下一个节点
}
return 0;
}
int hasFood(int i, int j) //寻找食物,避免食物定义到界面外
{
if(food.hang == i && food.lie == j){ //判断食物的行和列是否会超出界面
return 1;
}
return 0;
}
(6)这个代码主要是讲的是游戏20x20界面的设置
void gamePic()
{
int hang;
int lie;
move(0,0);
for(hang=0;hang<20;hang++){ //这里是行为20
if(hang == 0){ //这一块的代码主要是上边界和两个列这一模块
for(lie=0;lie<=19;lie++){
printw("--"); //为什么不用printf因为在nurces库中只能用这个printw才能显示出该有的图案
}
printw("\n"); //这里换行是因为让列到达指定的位置,不然出来的图像是混乱的顺序。
}
if(hang>=0 || hang<=19){ //判断这个界面的行在0到19这个区间内
for(lie=0;lie<=20;lie++){//要把列进行遍历
if(lie == 0 || lie==20){ //同样的判断和行一样
printw("|");
}else if(hasSnakeNode(hang,lie)){
printw("[]"); //这个符号【】代表的是蛇身
}else if(hasFood(hang,lie)){
printw("##"); //这个##代表蛇的食物
}
else{
printw(" "); //空格的原因是要使列在行的最两端
}
}
printw("\n"); //这个代码完成的是中间列的完成
}
if(hang == 19){ //最后这个代码为的最下面的行,行代码从0算起
for(lie=0;lie<=19;lie++){
printw("--");//这个符号--代表的是行
}
printw("\n");//每次换行就是害怕串行
printw("by xutanghao,food.hang=%d,food.lie=%d\n",food.hang,food.lie);//这段代码代表的是作者以及食物出现的位置
}
}
}
(7)这段代码是为了蛇在跑动过程中节点的删除及增加,以及蛇身的上下左右的移动。
void addNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake)); //这一行是为了开辟一个空间
new->next = NULL;//新节点的下一个节点(避免野指针)
switch(dir){ //这段代码为了控制方向的移动
case UP://向上的情况
new->hang = tail->hang-1;//相当于最顶端的行去判断尾节点就需要减一,其他的同理可得,注意看有的是hang+1,有的是lie+1
new->lie = tail->lie;
break;
case DOWN:
new->hang = tail->hang+1;
new->lie = tail->lie;
break;
case RIGHT:
new->hang = tail->hang;
new->lie = tail->lie+1;
break;
case LEFT:
new->hang = tail->hang;
new->lie = tail->lie-1;
break;
}
tail->next = new;//尾节点的下一个节点是新节点
tail = new; //尾节点等于新节点
}
void deleNode() //节点的删除
{
struct Snake *p;
p = head;
head = head->next;
free(p); //释放指针防止内存泄漏
}
(8)蛇身和食物的出现
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->hang = 1;
head->lie = 1;
head->next = NULL;
tail = head; //判断头结点和尾节点,防止头咬到尾巴不会死亡的情况
addNode(); //添加新的节点,在跑动的过程中
addNode();
addNode();
}
(9)这三段代码主要是贪吃蛇撞墙、吃到自己身体,移动速度以及刷新新界面这几方面。
int ifSnakeDie() //判断蛇的死亡
{
struct Snake *p;
p = head;
if(tail->hang<0 || tail->lie==0 || tail->hang==20 || 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)){
initFood();
}else{
deleNode();
}
if(ifSnakeDie()){
initSnake();
}
}
void *refreshJieMian ()//刷新界面
{
while(1){
moveSnake();
gamePic();
refresh();
usleep(100000);//移动速度为100000微秒
}
}
(10)使的按键有反应可以改变贪吃蛇的移动反向。
void turn(int direction)//控制转向
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void *changeDir()//改变移动方向
{
int key;
while(1){
key = getch();
switch(key){
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
(11)代码的完整展示,main函数讲解我放到下面进行注释
#include<curses.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<time.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake
{
int hang;
int lie;
struct Snake *next;
};
struct Snake*head = NULL;
struct Snake*tail = NULL;
int key;
int dir;
struct Snake food;
void initFood()
{
int x = rand()%20;
int y = rand()%20;
food.hang = y;
food.lie = x;
}
void initNcurse()
{
initscr();
keypad(stdscr,1);
noecho();
}
int hasSnakeNode(int i, int j)
{
struct Snake *p;
p = head;
while(p != NULL){
if(p->hang == i && p->lie == j){
return 1;
}
p = p->next;
}
return 0;
}
int hasFood(int i, int j)
{
if(food.hang == i && food.lie == j){
return 1;
}
return 0;
}
void gamePic()
{
int hang;
int lie;
move(0,0);
for(hang=0;hang<20;hang++){
if(hang == 0){
for(lie=0;lie<=19;lie++){
printw("--");
}
printw("\n");
}
if(hang>=0 || hang<=19){
for(lie=0;lie<=20;lie++){
if(lie == 0 || lie==20){
printw("|");
}else if(hasSnakeNode(hang,lie)){
printw("[]");
}else if(hasFood(hang,lie)){
printw("##");
}
else{
printw(" ");
}
}
printw("\n");
}
if(hang == 19){
for(lie=0;lie<=19;lie++){
printw("--");
}
printw("\n");
printw("by xutanghao,food.hang=%d,food.lie=%d\n",food.hang,food.lie);
}
}
}
void addNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));
new->next = NULL;
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 RIGHT:
new->hang = tail->hang;
new->lie = tail->lie+1;
break;
case LEFT:
new->hang = tail->hang;
new->lie = tail->lie-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->hang = 1;
head->lie = 1;
head->next = NULL;
tail = head;
addNode();
addNode();
addNode();
}
void deleNode()
{
struct Snake *p;
p = head;
head = head->next;
free(p);
}
int ifSnakeDie()
{
struct Snake *p;
p = head;
if(tail->hang<0 || tail->lie==0 || tail->hang==20 || 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)){
initFood();
}else{
deleNode();
}
if(ifSnakeDie()){
initSnake();
}
}
void *refreshJieMian ()
{
while(1){
moveSnake();
gamePic();
refresh();
usleep(100000);
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void *changeDir()
{
int key;
while(1){
key = getch();
switch(key){
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
int main()
{
pthread_t t1; //写线程需要定义的两个量
pthread_t t2;
initNcurse();
initSnake();
gamePic();
pthread_create(&t1, NULL, refreshJieMian, NULL);//这里就是linux线程部分,怎么让两个while进程同时工作,确定好地址即可
pthread_create(&t2, NULL, changeDir, NULL);
while(1);
getch();
endwin();
return 0;
}
二.视频展示
链接:https://pan.baidu.com/s/11MLycM7ukkia7H52hpg2vA
提取码:9n8u
运行结果就放在里面了
总结:本次项目做的还是有很多需要改进的地方,若有什么问题欢迎指出 ,毕竟自己第一次玩ubuntu哪里写的不好还望多多保含。
师承陈立臣老师