主要思路
一、封装函数定义结构体
①创建贪吃蛇结构点(行坐标,列左边,下一结点位置)
②封装初始化函数initNcurse( )
③封装判断蛇身位置函数 hasSnakeNode(int x,inty)
二、构建地图及判断蛇身显示
①第0行打印 (–)
②第1至19行列为0处打印 (|),此处插入函数hasSnakeNode(i,j)判断蛇身是否在地图内,
若在地图内则打印蛇身[],否打印" ";
③第20行打印(|)
三、动态创建蛇身链表
①封装添加蛇身结点函数addNode()和初始化蛇身函数initSnake();
四、移动贪吃蛇
①右移(删除头结点,增尾结点).
②判断是否撞墙,若撞墙则调用初始化地图.
注意:应移动光标位置,并且定于局部指针便于释放上一地图的空间.
③上下左右移动贪吃蛇(使用Linux线程概念)
移动蛇应该注意统一直线的移动问题,即蛇尾不能方向.
五、贪吃蛇吃食物
①定义食物
②初始化食物函数initFood();
③判断是否吃到食物hasFood()
六、优化贪吃蛇死亡方式
1.了解ncurse
①需要加头文件: #include <curses.h>
②项目编译时需要手动链接 eg: gcc 后需要接-lcurses
③进入手册 vi /usr/include/curses.h,查找 /要查找的东西
④keypad函数:设置了可以在stdscr中接收键盘的功能键(快捷键)
keypad(stdscr``,1);
initscr(); //ncurse界面的初始化函数.
printw("hellow world\n"); //在ncurse模式下的printf.
getch(); //等待用户输入,如果没有这句话,程序就退出了,看不到运行的结果.也就是看不到printw()的结果.
endwin(); //程序退出,调用改函数来恢复shell终端的显示,若没有这句话,shell终端字乱码。
2.ncurse上下左右键的获取.
#include<curses.h>
int main()
{
int key;
initscr();
keypad(stdscr,1);
while(1)
{
key=getch();
switch(key)
{
case KEY_UP:
printw("UP\n");
break;
case KEY_DOWN:
printw("DOWN\n");
break;
case KEY_LEFT:
printw("LEFT\n");
break;
case KEY_RIGHT:
printf("RIGHT\n");
break;
}
}
endwin();
return 0;
}
3、贪吃蛇地图搭建
构建20×20的贪吃蛇地图(分3部分进行打印输出)
①打印第1行
int i,j;
move(0,0) //移动光标至0,0位置
for(i=0;i<20;i++)
{
if(i==0)
{
for(j=0;j<20;j++)
{
printw("--"); //此时打印--
}
printw("\n");
for(j=0;j<=20;j++)
{
if(j==0||j==20)
{
printw("|"); //打印|
}
else
{
printw(" "); //因为--和|所占空间大小不同,此处应该用2个空格;
}
}
printw("\n");
}
}
②打印2-19行
if(i>0 && i<=19)
{
for(j=0;j<=20;j++)
{
if(j==0 || j==20)
{
printw("|");
}
else
{
printw(" ");
}
}
printw("\n");
}
③打印第19行的- -部分
if(i==19)
{
for(j=0;j<20;j++)
{
printw("--");
}
printw("\n");
printw("By PPPPPPPKD");
}
创建意义:创建空间以便下一步写入蛇身和果实扫描.
4、贪吃蛇身体显示
(1)
①单个结点的显示
设置贪吃蛇结构体(行坐标,列左边,下一个节点地址);
struct Snake
{
int i; //行
int j; //列
struct Snake *next;
}
定义蛇的一个结点
struct Snake node1={3,3,NULL};
地图中插入结点位置(修改0-19行代码)
if(i>0 && i<=19)
{
for(j=0;j<=20;j++)
{
if(j==0 || j==20)
{
printw("|");
}
else if(node1.i==i && node1.j==j) //判断是否插入;若坐标相同,则打印蛇身[],否则打印空格.
{
printw("[]");
}
else
{
printw(" ");
}
}
printw("\n");
}
效果如图:
②蛇身整体显示(动态链表添加蛇的结点)
(1)封装函数判断是否存在蛇身
struct Snake node1={2,2,NULL}; //创建蛇身结点.
int hasSnakeNode(int x,int y) //封装函数
{
struct Snake *p; //创建指向结构体的指针p
p=&node1; //指针指向node1的地址
while(p!=NULL) //指向地址不为空
{
if(p->i==x &&p->j==y) //判断当前左边是否在地图内
{
return 1;
}
p=p->next; //遍历指针
}
return 0;
}
将else if(node1.i==i && node1.j==j)
改为else if(hasSnakeNode(i,j))
(2)动态创建蛇身链表
定义全局变量
struct Snake *head=NULL; //头结点
struct Snkae *tail=NULL; //尾结点
②封装添加蛇身结点函数addNode()
void addNode()
{
struct Snake *new=(struct Snake *)malloc(sizeof(struct Snake));
new->i=tail->i;
new->j=tail->j+1;
new->next=NULL;
tail->next=new;
tail=new;
}
②封装初始化蛇身函数initSnake()
void initSnake()
{
struct Snake *p; //定义p得作用是方便释放空间
p=head;
while(head!=NULL) //每次撞墙后初始化地图都需要释放原本开辟的空间.
{
p=head;
head=head->next;
free(head);
}
head=(struct Snake*)malloc(sizeof(struct Snake));
//定义首个位置
head->i=2;
head->j=2;
head->next=NULL
tail=head; //尾结点指向头节点
addNode(); //调用函数
}
五、控制贪吃蛇移动
①右移
原理:删除头结点,增添尾结点.
a.增加结点函数
void addNode()
{
struct new *new=(struct Snake*)malloc(sizeof(struct Snake));
new->i == tail->i;
new->j == tail->j+1; //增添尾结点
new->next=NULL;
tail->next=new; //改变原本结点位置
tail=new;
}
②删除结点函数
void deleteNode()
{
struct Snake *p;
p=head; //定义p指向head的目的:方便释放删除的结点
head=head->next; //删除头节点
free(p);
}
③移动指令函数并判断是否撞墙
int moveSnake()
{
addNode();
deleteNode();
if(tail->i==0 || tail->j==0 ||tail->i==20 || tail->j==20)
{
initSnake(); //撞墙后调用初始化条件,输出原本地图
}
}
④控制右移
(1)按键KEY_LEFT一次右移一次
int control;
while(1)
{
control=getch();
if(control==KEY_RIGHT)
{
moveSnake();
gamePic(); //注意:此处得重新扫面地图才能显示
}
}
(2)自动向右移动
void refreshInterface()
{
while(1)
{
moveSnake();
gamePic();
refresh(); //刷新界面
usleep(100000); //移动速度
}
}
⑤上下左右移动贪吃蛇
a.问题:刷新界面和判断方向需要同时进行.
解决方法:创建Linux线程
线程函数介绍
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{
printf("I'm thread, Thread ID = %lu\n", pthread_self());
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
sleep(1); // 在多线程环境中,父线程终止,全部子线程被迫终止
printf("I am main, my pid = %d\n", getpid());
return 0;
封装函数refreshInterface()用来更新页面和changeDirection()函数用来判断方向。
创建线程
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,refreshInterface,NULL);
pthread_create(&t2,NULL,changeDirection,NULL);
b.实现贪吃蛇四个方向的移动
实现方法:修改addNode( )和changeDirection( )函数和turn()函数.
//定义全局变量
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
int dir
turn()函数
作用:使蛇尾不能移动方向
void turn(int direction)
{
if( abs(dir)!= abs(dirction)) //即水平方向不能都可移动蛇
{
dir=direction;
}
}
addNode( )函数
switch(dir) //判断方向增加结点.
{
case UP:
new->i=tail->i-1;
new->j=tail->j;
break;
case DOWN:
new->i=tail->i+1;
new->j=tail->j;
break;
case LEFT:
new->i=tail->i;
new->j=tail->j-1;
break;
case RIGHT:
new->i=tail->i;
new->j=tail->j+1;
break;
}
changeDirection( )函数
while(1)
{
key=getch(); //接受判断改变方向
switch(key)
{
case KEY_DOWN:
dir=turn(DOWN);
break;
case KEY_UP:
dir=turn(UP);
break;
case KEY_LEFT:
dir=turn(LEFT);
break;
case KEY_RIGHT:
dir=turn(RIGHT);
break;
}
}
五、贪吃蛇吃食物(##)
①食物结构体
struct Snake food;
②初始化食物
int x=rand()%20; //取余是为了确保随机值取值范围<20
int y=rand()%20;
food.i=x;
food.j=y;
}
③判断是否吃到食物
int hasFood(int x,int y)
{
if(food.i==x && food.j==y)
{
return 1;
}
return 0;
}
六、优化贪吃蛇死亡方式
贪吃蛇的两种死亡方式:①撞墙.②咬到自己
int ifSnakeDie()
{
struct Snake *p;
p=head;
if(tail->i<0 ||tail->i==20||tail->j==0||tail->j==20)
{
return 1;
}
while(p->next !=NULL)
{
if(p->i==tail->i && p->j==tail->j)
{
return 1;
}
p=p->next;
}
return 0;
}