#include
#include
#include
#include
#include
struct Snake //贪吃蛇身子节点
{
char node; //节点序号
int row; //行坐标
int column; //列坐标
struct Snake *next; //下一个节点地址
};
struct Snake *head = NULL; //链表头(全局变量)
struct Snake *tail = NULL; //链表尾(全局变量):插入新节点后记住链表尾巴这个节点,方便尾插法操作
struct Snake food; //贪吃蛇食物(全局变量)
int key; //功能性按键的值(全局变量)
int dir; //贪吃蛇运动方向(全局变量)
#define UP 1 //符号常量,上
#define DOWN -1 //符号常量,下
#define LEFT 2 //符号常量,左
#define RIGHT -2 //符号常量,右
/* API1: 扫描贪吃蛇地图:可活动范围19×19 */
void gamePic();
/* API2: 判断是否显示贪吃蛇节点 */
char showSnakeNode(int row, int column);
/* API3: 初始化蛇的头(动态节点) */
void initSnakeHead();
/* API4: (吃食物后)创建新节点并用尾插法,从tail后面插入 */
void insertFromTail();
/* API5: 删除链表头(是上一节delNode函数的特殊情况) */
void delHead();
/* API6: 贪吃蛇按键移动,复活贪吃蛇,贪吃蛇吃食物长身体 */
void moveSnake();
/* API7: 新线程t1,不断刷新图形终端页面,用于贪吃蛇脱缰移动 */
void* refreshPage();
/* API8: 新线程t2,不断识别按键,用于改变贪吃蛇方向 */
void* changeDir();
/* API9: 当前dir与按键获取对比,判断是否给dir赋新值,防止贪吃蛇不合理走位和无效走位 */
void turn(int direction);
/* API10: 初始化贪吃蛇食物,考虑食物位置和贪吃蛇不要重叠 */
void initFood();
/* API11: 判断是否显示贪吃蛇食物,还可以用来判断贪吃蛇链表尾是否吃到食物 */
char showFood(int row, int column);
/* API12: 判断贪吃蛇是否死亡*/
char ifSnakeDie();
int main(int argc, char const *argv[])
{
pthread_t t1;
pthread_t t2;
initscr(); //进入curse图形终端
keypad(stdscr, 1);
noecho();
initFood(); //创建贪吃蛇食物
initSnakeHead(); //创建蛇头
insertFromTail(); //测试:给蛇加个尾巴
insertFromTail(); //测试:再给蛇加个尾巴
gamePic(); //扫描贪吃蛇地图
pthread_create(&t1, NULL, refreshPage, NULL); //新线程,贪吃蛇脱缰移动,while(1循环)
pthread_create(&t2, NULL, changeDir, NULL); //新线程,按键改变贪吃蛇移动
while(1); //防止主线程退出
endwin(); //退出curse图形终端
return 0;
}
void gamePic()
{
int row;
int column;
move(0,0); //Ncurse的光标定位函数
for(row=0; row<20; row++){
/* 地图第一部分 */
if (row == 0){
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
}
/* 地图第二部分 */
if(row>=0 && row<=19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else if(showSnakeNode(row, column)){ //每遍历到地图的一个空位置,就要判断贪吃蛇的身子是否显示
printw("[]");
}else if(showFood(row, column)){ //每遍历到地图的一个空位置,就要判断贪吃蛇食物是否显示
printw("##");
}else{
printw(" ");
}
}
printw("\n");
}
/* 地图第三部分 */
if(row == 19){
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
printw("by lzh, key=0%3o, food.row=%d, food.column=%d\n", key, food.row, food.column);
}
}
}
char showSnakeNode(int row, int column)
{
struct Snake *p = head; //指向链表头
while(p != NULL){ //遍历链表,从头开始判断链表节点是否显示
if(p->row==row && p->column==column){
return 1;
}
p = p->next;
}
return 0;
}
void initSnakeHead()
{
dir = RIGHT; //给蛇一个初始的移动方向: 向右
struct Snake *deletedNode;
while(head != NULL){
deletedNode = head;
head = head->next;
free(deletedNode);
}
head = (struct Snake*)malloc(sizeof(struct Snake));
if(head == NULL){
printw("malloc error\n");
exit(-1);
}
head->node = 1;
head->next = NULL;
head->row = 1; //测试:蛇头从第1行出现
head->column = 1; //测试:蛇头从第1列出现
tail = head;
}
void insertFromTail()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
if(new == NULL){
printw("malloc error\n");
exit(-1);
}
new->node = tail->node + 1;
new->next = NULL;
switch(dir){ //根据全局变量dir确定新节点的行列坐标增减趋势
case UP:
new->row = tail->row - 1;
new->column = tail->column;
break;
case DOWN:
new->row = tail->row + 1;
new->column = tail->column;
break;
case LEFT:
new->row = tail->row;
new->column = tail->column - 1;
break;
case RIGHT:
new->row = tail->row;
new->column = tail->column + 1;
break;
}
tail->next = new;
/* 最后记住新的尾巴 */
tail = new;
}
void delHead()
{
struct Snake *p = NULL;
struct Snake *deletedNode = head;
head = head->next;
free(deletedNode);
p = head;
while(p != NULL){
p->node = p->node - 1;
p = p->next;
}
}
void moveSnake()
{
insertFromTail();
if(showFood(tail->row, tail->column)){ //如果碰到食物就不删除链表头
initFood();
}else{
delHead();
}
if(ifSnakeDie()){ //如果贪吃蛇死(撞墙或吃自己)了就重新开始游戏
initSnakeHead();
insertFromTail();
insertFromTail();
}
}
void* refreshPage()
{
while(1){ //贪吃蛇脱缰移动
moveSnake();
gamePic();
refresh(); //Ncurse的刷新函数
usleep(100000); //Ncurse的睡眠函数,单位微秒
}
}
void* changeDir()
{
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;
}
}
}
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
void initFood()
{
int x, y;
char overlapOrNot = 0;
struct Snake *p;
srand((unsigned int)time(NULL));
x = rand()%19; //行坐标范围在0-18之间
y = rand()%19 + 1; //列坐标范围在1-19之间
/* while(1){ //防止食物出现的位置和贪吃蛇身子节点重合
p = head;
x = rand()%19; //行坐标范围在0-18之间
y = rand()%19 + 1; //列坐标范围在1-19之间
while(p != NULL){
if(x==p->row && y==p->column){
overlapOrNot = 1;
break;
}
p = p->next;
}
if(overlapOrNot == 0){
break;
}
} */
food.row = x;
food.column = y;
}
char showFood(int row, int column)
{
if(food.row==row && food.column==column){
return 1;
}
return 0;
}
char ifSnakeDie()
{
struct Snake *p;
p = head;
/* 撞墙判断,如果撞墙说明贪吃蛇死亡 */
if(tail->row<0 || tail->column==0 \
|| tail->row==20 || tail->column==20){
return 1;
}
/* 咬自己判断,如果贪吃蛇链表尾咬到其他部分,说明贪吃蛇死亡 */
while(p != tail){
if(p->row==tail->row && p->column==tail->column){
return 1;
}
p = p->next;
}
return 0;
}
1、为什么需要Ncurse:
2、Ncurse的上下左右键值:
#define KEY_DOWN 0402 /* down-arrow key */
#define KEY_UP 0403 /* up-arrow key */
#define KEY_LEFT 0404 /* left-arrow key */
#define KEY_RIGHT 0405 /* right-arrow key */
3、上下左右键的实时捕获:
vi /usr/include/curses.h
回车
先进入:curses.h头文件
/KEY_UP
回车
keypad(stdscr, 1); //使用keypad函数从标准stdscr中接收键盘的功能键(快捷键),1表示接收
/* 指令:
gcc snake.c -lcusres
回车
运行 */
#include //1.头文件
int main(int argc, char const *argv[])
{
int key; //2.不用char
initscr(); //3.必要语句,初始化curse的图形终端页面,并进入
keypad(stdscr, 1); //4.开启键盘功能键
while(1){
key = getch(); //5.getch函数
printw("you input: 0%3o\n", key); //6.printw函数
switch(key){
case KEY_DOWN: //宏定义后的符号常量
printw("down\n");
break;
case KEY_UP: //宏定义后的符号常量
printw("up\n");
break;
case KEY_LEFT: //宏定义后的符号常量
printw("left\n");
break;
case KEY_RIGHT: //宏定义后的符号常量
printw("right\n");
break;
}
}
endwin(); //7.必要语句,退出curse的图形终端页面
return 0;
}
1、地图规划:
2、地图编程实现:gamePic()
#include
void gamePic(); //API1: 扫描贪吃蛇地图
int main(int argc, char const *argv[])
{
initscr(); //进入curse图形终端
keypad(stdscr, 1);
gamePic(); //扫描贪吃蛇地图
getch();
endwin(); //退出curse图形终端
return 0;
}
void gamePic()
{
int row;
int column;
for(row=0; row<20; row++){
/* 打印地图第0行 */
if (row == 0){
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else{
printw(" ");
}
}
printw("\n");
}
/* 打印地图1到18行 */
if(row>0 && row<19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else{
printw(" ");
}
}
printw("\n");
}
/* 打印地图第19行 */
if(row == 19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else{
printw(" ");
}
}
printw("\n");
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
printw("by lzh");
}
}
}
#include
void gamePic(); //API1: 绘制贪吃蛇地图
int main(int argc, char const *argv[])
{
initscr(); //进入curse图形终端
keypad(stdscr, 1);
gamePic(); //扫描贪吃蛇地图
getch();
endwin(); //退出curse图形终端
return 0;
}
void gamePic()
{
int row;
int column;
for(row=0; row<20; row++){
/* 打印地图第1部分 */
if (row == 0){
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
}
/* 打印地图第2部分 */
if(row>=0 && row<=19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else{
printw(" ");
}
}
printw("\n");
}
/* 打印地图第3部分 */
if(row == 19){
for(column=0; column<20; column++){
printw("--");
}
printw("\n");
printw("by lzh");
}
}
}
1、身子规划:
2、贪吃蛇身子节点:
struct Snake //贪吃蛇身子节点
{
char node; //节点序号
int row; //行坐标
int column; //列坐标
struct Snake *next; //下一个节点地址
};
3、扫描显示贪吃蛇一个节点:
外部变量(测试):
struct Snake x = {1,2,2,NULL};
-------------------------------------------------------------
API1: gamePic()函数
/* 地图第二部分 */
if(row>=0 && row<=19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else if(x.row==row && x.column==column){ //通过节点的行列坐标判断是否显示
printw("[]");
}else{
printw(" ");
}
}
printw("\n");
}
--------------------------------------------------------------
4、扫描显示贪吃蛇初始节点:
外部变量(测试):
/* 贪吃蛇固定的三节点(复活后默认三个节点) */
struct Snake x = {1,2,2,NULL};
struct Snake x2 = {2,2,3,NULL};
struct Snake x3 = {3,2,4,NULL};
-------------------------------------------------------------
main函数:
x.next = &x2; //注意这2条赋值语句不能写在函数外部
x2.next = &x3;
gamePic(); //绘制贪吃蛇地图
-------------------------------------------------------------
API1: gamePic()函数
/* 地图第二部分 */
if(row>=0 && row<=19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else if(showSnakeNode(row, column)){ //API2. 通过节点的行列坐标判断是否显示
printw("[]");
}else{
printw(" ");
}
}
printw("\n");
}
-------------------------------------------------------------
API2: showSnakeNode()函数
char showSnakeNode(int row, int column)
{
struct Snake *p = &x; //指向链表头,链表头永远不变
while(p != NULL){ //遍历链表,从头开始判断链表节点是否显示
if(p->row==row && p->column==column){
return 1;
}
p = p->next;
}
return 0;
}
1、链表头和链表尾两个全局变量:
全局变量:
struct Snake *head = NULL; //链表头(全局变量)
struct Snake *tail = NULL; //链表尾(全局变量):插入新节点后记住链表尾巴这个节点,方便尾插法操作
2、动态创建链表头:initSnakeHead()
f3. 封装创建链表头的API: void initSnakeHead();
f3.1 调用malloc函数为全局变量head开辟动态内存:
head = (struct Snake*)malloc(sizeof(struct Snake));
f3.2 判断malloc是否成功,如果不成功则用exit(-1)结束整个程序
f3.3 初始化链表头的node: head->node = 1;
f3.4 初始化链表头的指向: head->next = NULL; //因为此时蛇头就是蛇尾
f3.5 初始化链表头的row,代表蛇头从哪一行出现
f3.6 初始化链表头的column,代表蛇头从哪一列出现
f3.7 初始化蛇尾: tail = head;
API3: 创建链表头
void initSnakeHead()
{
head = (struct Snake*)malloc(sizeof(struct Snake));
if(head == NULL){
printw("malloc error\n");
exit(-1);
}
head->node = 1;
head->next = NULL;
head->row = 2; //测试:蛇头从第2行出现
head->column = 2; //测试:蛇头从第2列出现
tail = head;
}
3、尾插法新建节点:insertFromTail()
f4. 封装创建新节点并用尾插法的API: void insertFromTail();
f4.1 调用malloc函数为新结点new开辟动态内存:
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
f4.2 判断malloc是否成功,如果不成功则用exit(-1)结束整个程序
f4.3 初始化新结点new的node,也就是tail往后一个: new->node = tail->node + 1;
f4.4 初始化链表头的指向: new->next = NULL; //尾插法的新节点必须指向NULL
f4.5 初始化链表头的row,代表新的蛇尾从哪一行出现
f4.6 初始化链表头的column,代表新的蛇尾从哪一列出现
f4.7 让原先链表的尾巴tail指向new: tail->next = new;
f4.8 最后修改全局变量tail,记住新的链表尾巴: tail = new;
API4: 创建新节点并用尾插法
void insertFromTail()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
if(new == NULL){
printw("malloc error\n");
exit(-1);
}
new->node = tail->node + 1;
new->next = NULL;
new->row = tail->row; //测试:新节点的行和tail的一样
new->column = tail->column + 1; //测试:新节点的列比tail的列多1
tail->next = new;
/* 最后记住新的尾巴 */
tail = new;
}
1、向右移动的原理:
2、删除链表头:delHead()
f5. 封装删除链表头的API: void insertFromTail();
f5.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
f5.2 新定义一个代表被删节点的结构体指针变量deletedNode: struct Test *deletedNode;
f5.3 删除的是链表头,所以用deletedNode记录: deletedNode = head;
f5.4 修改链表头的指向,让head指向节点2: head = head->next;
f5.5 用free函数释放掉被删节点: free(deletedNode);
f5.6 将节点p移动到新链表头,接下来准备修改往后各个节点的node: p = head;
f5.7 while循环,控制循环的变量p,当p!=NULL 时,进入循环,把后面所有节点的node-1
f5.7.1 从节点p开始,也就是从新链表头开始,修改node: p->node = p->node - 1;
f5.7.2 修改代表节点的循环变量p,让p往后走: p = p->next;
API5: 删除链表头(是上一节delNode函数的特殊情况)
void delHead()
{
struct Snake *p = NULL;
struct Snake *deletedNode = head;
head = head->next;
free(deletedNode);
p = head;
while(p != NULL){
p->node = p->node - 1;
p = p->next;
}
}
3、向右移动:moveSnake()
API6: 贪吃蛇移动
void moveSnake()
{
/* 向右走原理1:添加链表尾 */
insertFromTail();
/* 向右走原理2:删除链表头 */
delHead();
}
4、Ncurse光标移动函数:move(0, 0)
5、main函数中的测试代码:
int main(int argc, char const *argv[])
{
int key;
initscr(); //进入curse图形终端
keypad(stdscr, 1);
initSnakeHead(); //创建蛇头
insertFromTail(); //测试:给蛇加个尾巴
insertFromTail(); //测试:再给蛇加个尾巴
gamePic(); //扫描贪吃蛇地图
while(1){
key = getch();
if(key == KEY_RIGHT){ //贪吃蛇往右走,并且重新扫描地图
moveSnake();
gamePic();
}
}
endwin(); //退出curse图形终端
return 0;
}
1、重新布置moveSnake函数:
修改API6: 贪吃蛇移动 | 加入是否撞墙判断
void moveSnake()
{
/* 向右走原理1:添加链表尾 */
insertFromTail();
/* 向右走原理2:删除链表头 */
delHead();
/* 撞墙判断,如果撞墙游戏重新开始,但要注意之前的链表需要释放掉 */
if(tail->row<0 || tail->column==0 \
|| tail->row==20 || tail->column==20){
initSnakeHead();
insertFromTail();
insertFromTail();
}
}
2、重新布置initSnakeHead函数:
f3.1 新定义一个代表被删节点的结构体指针变量deletedNode: struct Test *deletedNode;
f3.2 while循环,控制循环的变量是head,当head!=NULL 时,说明原先就存在链表,接下来进行全部释放
f3.2.1 总是从链表头开始删除,用deletedNode记录: deletedNode = head;
f3.2.2 修改代表链表头的循环变量head的指向,让它指向下一个节点: head = head->next;
f3.2.2 调用free函数释放掉被删节点deletedNode: free(deletedNode);
修改API3: 创建链表头 | 加入清空原先链表的操作
void initSnakeHead()
{
struct Snake *deletedNode;
while(head != NULL){
deletedNode = head;
head = head->next;
free(deletedNode);
}
head = (struct Snake*)malloc(sizeof(struct Snake));
if(head == NULL){
printw("malloc error\n");
exit(-1);
}
head->node = 1;
head->next = NULL;
head->row = 2; //测试:蛇头从第2行出现
head->column = 2; //测试:蛇头从第2列出现
tail = head;
}
1、Ncurse的刷新和睡眠函数:
2、main函数中的测试代码:
int main(int argc, char const *argv[])
{
initscr(); //进入curse图形终端
keypad(stdscr, 1);
initSnakeHead(); //创建蛇头
insertFromTail(); //测试:给蛇加个尾巴
insertFromTail(); //测试:再给蛇加个尾巴
gamePic(); //扫描贪吃蛇地图
while(1){ //贪吃蛇脱缰右移
moveSnake();
gamePic();
refresh(); //Ncurse的刷新函数
usleep(100000); //Ncurse的睡眠函数,单位微秒
}
endwin(); //退出curse图形终端
return 0;
}
1、面临的问题:
2、Linux线程概念:
int main(int argc, char const *argv[])
{
int key;
initscr(); //进入curse图形终端
keypad(stdscr, 1);
initSnakeHead(); //创建蛇头
insertFromTail(); //测试:给蛇加个尾巴
insertFromTail(); //测试:再给蛇加个尾巴
gamePic(); //扫描贪吃蛇地图
while(1){ //贪吃蛇脱缰右移
moveSnake();
gamePic();
refresh(); //Ncurse的刷新函数
usleep(100000); //Ncurse的睡眠函数,单位微秒
}
while(1){
key = getch();
if(key == KEY_RIGHT){ //贪吃蛇往右走,并且重新扫描地图
moveSnake();
gamePic();
}
}
endwin(); //退出curse图形终端
return 0;
}
/* 指令:
gcc test.c -lpthread
回车
运行 */
#include
#include
void* func1(void *arg) //注意创建的线程中,函数返回值必须是void*
{
while(1){
printf("this is func1\n");
sleep(1);
}
}
void func2()
{
while(1){
printf("this is func2\n");
sleep(1);
}
}
int main(int argc, char const *argv[]) //main函数就是一个线程,接下来创建新的线程
{
pthread_t th1; //定义一个线程th1
pthread_create(&th1, NULL, func1, NULL);
//创建线程:给谁创建?答:th1,所以传递线程th1的地址。
// 创建以后调用哪个函数?答:func1函数,所以传递函数的地址,即函数名
// 给这个线程传递什么操作数?答:无需操作数,所以传递NULL
func2();
return 0;
}
3、测试实现三线程: main() 以及 refreshPage() 以及 changeDir() 。仿照上述线程代码举例。
API7: 新线程t1,不断刷新图形终端页面,用于贪吃蛇脱缰移动
void* refreshPage()
{
while(1){ //贪吃蛇脱缰右移
moveSnake();
gamePic();
refresh(); //Ncurse的刷新函数
usleep(100000); //Ncurse的睡眠函数,单位微秒
}
}
API8: 新线程t2,不断识别按键,用于改变贪吃蛇方向
void* changeDir()
{
while(1){
key = getch();
switch(key){
case KEY_DOWN:
printw("down\n");
break;
case KEY_UP:
printw("up\n");
break;
case KEY_LEFT:
printw("left\n");
break;
case KEY_RIGHT:
printw("right\n");
break;
}
}
}
/* 指令:
gcc snake.c -lcurses -lpthread
回车
运行 */
int main(int argc, char const *argv[])
{
pthread_t t1;
pthread_t t2;
initscr(); //进入curse图形终端
keypad(stdscr, 1);
initSnakeHead(); //创建蛇头
insertFromTail(); //测试:给蛇加个尾巴
insertFromTail(); //测试:再给蛇加个尾巴
gamePic(); //扫描贪吃蛇地图
pthread_create(&t1, NULL, refreshPage, NULL); //新线程,贪吃蛇脱缰移动,while(1循环)
pthread_create(&t2, NULL, changeDir, NULL); //新线程,按键改变贪吃蛇移动
while(1); //防止主线程退出
endwin(); //退出curse图形终端
return 0;
}
1、走位原理:根据让蛇在地图中移动起来的原理,贪吃蛇在进行四方位走位时(准确来说是三方位,因为我们不允许蛇掉头走,掉头走属于不合理的走位),也遵循的是在链表尾添加新节点,删除链表头的原理,但是新节点的行列坐标要根据贪吃蛇的移动方向来确定。
2、按键和方位两个全局变量:
int key; //功能性按键的值(全局变量)
int dir; //贪吃蛇运动方向(全局变量)
#define UP 1 //符号常量,上
#define DOWN 2 //符号常量,下
#define LEFT 3 //符号常量,左
#define RIGHT 4 //符号常量,右
3、总体思路:判断现阶段我们要添加哪些函数,修改哪些函数。
4、修改initSnakeHead()函数:
修改API3: 创建链表头 | 加入初始化蛇移动的方向
void initSnakeHead()
{
dir = RIGHT; //给蛇一个初始的移动方向: 向右
..............................
}
5、修改insertFromTail()函数:
f4. 封装创建新节点并用尾插法的API: void insertFromTail();
f4.1 调用malloc函数为新结点new开辟动态内存:
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
f4.2 判断malloc是否成功,如果不成功则用exit(-1)结束整个程序
f4.3 初始化新结点new的node,也就是tail往后一个: new->node = tail->node + 1;
f4.4 初始化链表头的指向: new->next = NULL; //尾插法的新节点必须指向NULL
//f4.5 初始化链表头的row,代表新的蛇尾从哪一行出现
//f4.6 初始化链表头的column,代表新的蛇尾从哪一列出现
f4.7 让原先链表的尾巴tail指向new: tail->next = new;
f4.8 最后修改全局变量tail,记住新的链表尾巴: tail = new;
/* 修改如下:*/
f4.5 switch选择语句,对表达式dir进行判断,
f4.5.1 当dir==UP时,说明贪吃蛇往上走
f4.5.1.1 新节点相对于链表尾巴的行坐标减1: new->row = tail->row - 1;
f4.5.1.2 新节点相对于链表尾巴的列坐标不变: new->column = tail->column;
f4.5.1.3 break跳出switch判断
f4.5.2 当dir==DOWN时,说明贪吃蛇往下走
f4.5.2.1 新节点相对于链表尾巴的行坐标加1: new->row = tail->row + 1;
f4.5.2.2 新节点相对于链表尾巴的列坐标不变: new->column = tail->column;
f4.5.2.3 break跳出switch判断
f4.5.3 当dir==LEFT时,说明贪吃蛇往左走
f4.5.3.1 新节点相对于链表尾巴的行坐标不变: new->row = tail->row;
f4.5.3.2 新节点相对于链表尾巴的列坐标减1: new->column = tail->column - 1;
f4.5.3.3 break跳出switch判断
f4.5.4 当dir==RIGHT时,说明贪吃蛇往右走
f4.5.4.1 新节点相对于链表尾巴的行坐标不变: new->row = tail->row;
f4.5.4.2 新节点相对于链表尾巴的列坐标加1: new->column = tail->column + 1;
f4.5.4.3 break跳出switch判断
修改API4: 创建新节点并用尾插法 | 加入对蛇方位的判断再决定新节点new的行列坐标
void insertFromTail()
{
struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
if(new == NULL){
printw("malloc error\n");
exit(-1);
}
new->node = tail->node + 1;
new->next = NULL;
switch(dir){ //根据全局变量dir确定新节点的行列坐标增减趋势
case UP:
new->row = tail->row - 1;
new->column = tail->column;
break;
case DOWN:
new->row = tail->row + 1;
new->column = tail->column;
break;
case LEFT:
new->row = tail->row;
new->column = tail->column - 1;
break;
case RIGHT:
new->row = tail->row;
new->column = tail->column + 1;
break;
}
tail->next = new;
/* 最后记住新的尾巴 */
tail = new;
}
6、修改changeDir()函数:
API8: 新线程t2,不断识别按键,用于改变贪吃蛇方向 | 加入对全局变量dir的修改
void* changeDir()
{
while(1){
key = getch();
switch(key){
case KEY_DOWN:
dir = DOWN;
break;
case KEY_UP:
dir = UP;
break;
case KEY_LEFT:
dir = LEFT;
break;
case KEY_RIGHT:
dir = RIGHT;
break;
}
}
}
1、原理:
#define UP 1 //符号常量,上
#define DOWN -1 //符号常量,下
#define LEFT 2 //符号常量,左
#define RIGHT -2 //符号常量,右
2、判断是否给dir赋新值:turn()函数
f9. 封装判断是否给dir赋新值的API: void turn(int direction);
形参direction从changeDir()函数中传递过来,turn()函数是changeDir()和insertFromTail()的中间过程
f9.1 判断当前代表移动方向的全局变量dir的绝对值是否不等于按键代表的方位direction
f9.1.1 如果是,说明当前获取按键的不会造成不合理走位或者无效走位
f9.1.1.1 那么,给全局变量dir赋新值: dir = direction;
f9.1.2 否则,就什么也不做,代表用户输入的是无效按键或者反向按键。
API9: 当前dir与按键获取对比,判断是否给dir赋新值
void turn(int direction)
{
if(abs(dir) != abs(direction)){
dir = direction;
}
}
3、修改changeDir()函数:
void* changeDir()
{
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;
}
}
}
4、可能出现的问题:有时候我们的Ncurses图形终端页面会扫描出乱七八糟的东西,我们可以在初始化Ncurses后,调用Ncurses的noecho()函数,它的作用是屏蔽掉无关信息(比如功能键的信息)。
1、食物规划:
struct Snake food; //贪吃蛇食物(全局变量)
2、C语言随机数函数的使用:
/* rand函数在stdlib.h头文件中被声明 */
rand(); //创造一个很大的随机数
/* srand和rand函数在stldlib.h头文件中被声明、time函数在time.h头文件中被声明 */
srand((unsigned int)time(NULL));
//time函数用于产生格林威治时间到现在的秒数
//srand用于设置随机种子,它的实参是unsigned int类型的
rand();
3、初始化贪吃蛇食物:initFood()
f10. 封装初始化贪吃蛇食物的API: void initFood();
f10.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
f10.2 设置随机种子: srand((unsigned int)time(NULL));
f10.3 while死循环,接下来不断产生新的食物坐标,直到新食物的坐标不和贪吃蛇身子重合
f10.3.1 将节点p复位到链表头head: p = head;
f10.3.2 根据食物行坐标的范围是0~18,通过随机数取余给代表食物行坐标的变量x赋值:
x = rand()%19;
f10.3.3 根据食物列坐标的范围是1~19,通过随机数取余给代表食物列坐标的变量y赋值:
y = rand()%19 + 1;
f10.3.4 while循环,控制循环的变量p,当p!=NULL 时,进入循环,遍历贪吃蛇所有节点坐标
f10.3.4.1 判断食物坐标x是否等于p->row,并且食物坐标y是否等于p->column
f10.3.4.1.1 如果是,那么说明食物的坐标和身子重合了,需要重新给x、y赋值
f10.3.4.1.1.1 修改代表食物和贪吃蛇身子覆盖的循环变量overlapOrNot:
overlapOrNot = 1; //用1代表重叠,用0代表不重叠,初始化成0
f10.3.4.1.1.2 用break提前退出f10.3.4的循环,准备进行下一次判断
f10.3.4.1.2 修改代表节点的循环变量p,让p往后走: p = p->next;
f10.3.5 通过变量overlapOrNot判断食物和贪吃蛇身子是否重叠
f10.3.5.1 如果不是,说明x、y的赋值是合理的,
f10.3.5.1.1 那么,直接用break提前退出f10.3的死循环
f10.4 修改代表食物的全局变量food的行坐标: food.row = x;
f10.5 修改代表食物的全局变量food的列坐标: food.column = y;
API10: 初始化贪吃蛇食物
void initFood()
{
int x, y;
char overlapOrNot = 0;
struct Snake *p;
srand((unsigned int)time(NULL));
x = rand()%19; //行坐标范围在0-18之间
y = rand()%19 + 1; //列坐标范围在1-19之间
/* while(1){ //防止食物出现的位置和贪吃蛇身子节点重合
p = head;
x = rand()%19; //行坐标范围在0-18之间
y = rand()%19 + 1; //列坐标范围在1-19之间
while(p != NULL){
if(x==p->row && y==p->column){
overlapOrNot = 1;
break;
}
p = p->next;
}
if(overlapOrNot == 0){
break;
}
} */
food.row = x;
food.column = y;
}
4、贪吃蛇食物显示:showFood()
API11: 判断是否显示贪吃蛇食物,还可以用来判断贪吃蛇链表尾是否吃到食物
char showFood(int row, int column)
{
if(food.row==row && food.column==column){
return 1;
}
return 0;
}
5、修改gamePic()函数:
---------------------------------------------------------------------------------------
API1: gamePic()函数
/* 地图第二部分 */
if(row>=0 && row<=19){
for(column=0; column<=20; column++){
if(column==0 || column==20){
printw("|");
}else if(showSnakeNode(row, column)){ //每遍历到地图的一个空位置,就要判断贪吃蛇的身子是否显示
printw("[]");
}else if(showFood(row, column)){ //每遍历到地图的一个空位置,就要判断贪吃蛇食物是否显示
printw("##");
}else{
printw(" ");
}
}
printw("\n");
}
----------------------------------------------------------------------------------------
6、修改moveSnake()函数:
修改API6: 贪吃蛇移动 | 加入是否撞墙判断 | 加入蛇是否碰到食物的判断
void moveSnake()
{
insertFromTail();
if(showFood(tail->row, tail->column)){ //如果碰到食物就不删除链表头
initFood();
}else{
delHead();
}
/* 撞墙判断,如果撞墙游戏重新开始,但要注意之前的链表需要释放掉 */
if(tail->row<0 || tail->column==0 \
|| tail->row==20 || tail->column==20){
initSnakeHead();
insertFromTail();
insertFromTail();
}
}
1、判断贪吃蛇是否死亡:ifSnakeDie()。贪吃蛇死亡有两种情况,除了我们一开始讲的碰壁,还有一种情况是咬到自己,也就是链表尾碰到了贪吃蛇身子的其他节点,那么我们不妨将这两种情况写在一起,一同作为moveSnake()函数中是否重新调用initSnakeHead()函数的判据。
f12. 封装判断贪吃蛇是否死亡的API: char ifSnakeDie(); //函数返回值1代表死亡,0代表不死亡
f12.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
f12.2 撞墙判断,撞墙就返回1,提前结束函数
//内在逻辑:如果能够顺利通过f12.2,说明没有撞墙,接下来对是否咬到自己进行判断
f12.3 while循环,控制循环的变量p,当p!=tail 时,进入循环,遍历除了链表尾之外所有节点坐标
f12.3.1 判断节点p的坐标是否等于链表尾tail的坐标
f12.3.1.1 如果是,说明蛇撞到自己,代表贪吃蛇死亡
f12.3.1.1.1 返回1
f12.3.2 修改代表节点的循环变量p,让p往后走: p = p->next;
f12.4 如果能够顺利通过f12.2和f12.3,那么说明贪吃蛇没死,就返回0
API12: 判断贪吃蛇是否死亡
char ifSnakeDie()
{
struct Snake *p;
p = head;
/* 撞墙判断,如果撞墙说明贪吃蛇死亡 */
if(tail->row<0 || tail->column==0 \
|| tail->row==20 || tail->column==20){
return 1;
}
/* 咬自己判断,如果贪吃蛇链表尾咬到其他部分,说明贪吃蛇死亡 */
while(p != tail){
if(p->row==tail->row && p->column==tail->column){
return 1;
}
p = p->next;
}
return 0;
}
2、修改moveSnake()函数:
修改API6: 贪吃蛇移动 | 加入是否撞墙判断 | 加入蛇是否碰到食物的判断 | 加入是否咬到自己判断
void moveSnake()
{
insertFromTail();
if(showFood(tail->row, tail->column)){ //如果碰到食物就不删除链表头
initFood();
}else{
delHead();
}
if(ifSnakeDie()){ //如果贪吃蛇死(撞墙或吃自己)了就重新开始游戏
initSnakeHead();
insertFromTail();
insertFromTail();
}
}